Legacy support removals, Part I
parent
01f3d03dbe
commit
1b8b87b58b
|
@ -6,11 +6,15 @@ module Api
|
|||
class AbstractController < ApplicationController
|
||||
# This error is thrown when you try to use a non-JSON request body on an
|
||||
# endpoint that requires JSON.
|
||||
class OnlyJson < Exception; end;
|
||||
CONSENT_REQUIRED = \
|
||||
class OnlyJson < Exception; end
|
||||
|
||||
CONSENT_REQUIRED =
|
||||
"all device users must agree to terms of service."
|
||||
NOT_JSON = "That request was not valid JSON. Consider checking the"\
|
||||
" request body with a JSON validator.."
|
||||
NOT_JSON = "That request was not valid JSON. Consider checking the" \
|
||||
" request body with a JSON validator.."
|
||||
NULL = Gem::Version.new("0.0.0")
|
||||
NOT_FBOS = Gem::Version.new("999.999.999")
|
||||
|
||||
respond_to :json
|
||||
before_action :check_fbos_version
|
||||
before_action :set_default_stuff
|
||||
|
@ -50,11 +54,11 @@ module Api
|
|||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordInvalid do |exc|
|
||||
render json: {error: exc.message}, status: 422
|
||||
render json: { error: exc.message }, status: 422
|
||||
end
|
||||
|
||||
rescue_from Errors::LegalConsent do |exc|
|
||||
render json: {error: CONSENT_REQUIRED}, status: 451
|
||||
render json: { error: CONSENT_REQUIRED }, status: 451
|
||||
end
|
||||
|
||||
rescue_from ActiveModel::RangeError do |_|
|
||||
|
@ -63,10 +67,10 @@ module Api
|
|||
end
|
||||
|
||||
def default_serializer_options
|
||||
{root: false, user: current_user}
|
||||
{ root: false, user: current_user }
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
def clean_expired_farm_events
|
||||
FarmEvents::CleanExpired.run!(device: current_device)
|
||||
|
@ -76,7 +80,7 @@ private
|
|||
# Our API does not do things the "Rails way" (we use Mutations for input
|
||||
# sanitation) so we can ignore this and grab the raw input.
|
||||
def raw_json
|
||||
@raw_json ||= JSON.parse(request.body.read).tap{ |x| symbolize(x) }
|
||||
@raw_json ||= JSON.parse(request.body.read).tap { |x| symbolize(x) }
|
||||
rescue JSON::ParserError
|
||||
raise OnlyJson
|
||||
end
|
||||
|
@ -92,9 +96,9 @@ private
|
|||
REQ_ID = "X-Farmbot-Rpc-Id"
|
||||
|
||||
def set_default_stuff
|
||||
request.format = "json"
|
||||
id = request.headers[REQ_ID] || SecureRandom.uuid
|
||||
response.headers[REQ_ID] = id
|
||||
request.format = "json"
|
||||
id = request.headers[REQ_ID] || SecureRandom.uuid
|
||||
response.headers[REQ_ID] = id
|
||||
# # IMPORTANT: We need to hoist X-Farmbot-Rpc-Id to a global so that it is
|
||||
# # accessible for use with auto_sync.
|
||||
Transport.current.set_current_request_id(response.headers[REQ_ID])
|
||||
|
@ -112,7 +116,7 @@ private
|
|||
def authenticate_user!
|
||||
# All possible information that could be needed for any of the 3 auth
|
||||
# strategies.
|
||||
context = { jwt: request.headers["Authorization"],
|
||||
context = { jwt: request.headers["Authorization"],
|
||||
user: current_user }
|
||||
# Returns a symbol representing the appropriate auth strategy, or nil if
|
||||
# unknown.
|
||||
|
@ -132,12 +136,12 @@ private
|
|||
mark_as_seen
|
||||
rescue Mutations::ValidationException => e
|
||||
errors = e.errors.message.merge(strategy: strategy)
|
||||
render json: {error: errors}, status: 401
|
||||
render json: { error: errors }, status: 401
|
||||
end
|
||||
|
||||
def auth_err
|
||||
sorry("You failed to authenticate with the API. Ensure that you " \
|
||||
" provide a JSON Web Token in the `Authorization:` header." , 401)
|
||||
" provide a JSON Web Token in the `Authorization:` header.", 401)
|
||||
end
|
||||
|
||||
def sorry(msg, status)
|
||||
|
@ -153,7 +157,7 @@ private
|
|||
end
|
||||
|
||||
def bad_version
|
||||
render json: {error: "Upgrade to latest FarmBot OS"}, status: 426
|
||||
render json: { error: "Upgrade to latest FarmBot OS" }, status: 426
|
||||
end
|
||||
|
||||
EXPECTED_VER = Gem::Version::new GlobalConfig.dump["MINIMUM_FBOS_VERSION"]
|
||||
|
@ -165,7 +169,7 @@ private
|
|||
# Attempt 1:
|
||||
# The device is using an HTTP client that does not provide a user-agent.
|
||||
# We will assume this is an old FBOS version and set it to 0.0.0
|
||||
return CalculateUpgrade::NULL if ua == FbosDetector::NO_UA_FOUND
|
||||
return NULL if ua == FbosDetector::NO_UA_FOUND
|
||||
|
||||
# Attempt 2:
|
||||
# If the user agent was missing, we would have returned by now.
|
||||
|
@ -176,8 +180,8 @@ private
|
|||
end
|
||||
|
||||
# Attempt 3:
|
||||
# Pass CalculateUpgrade::NOT_FBOS if all other attempts fail.
|
||||
return CalculateUpgrade::NOT_FBOS
|
||||
# Pass NOT_FBOS if all other attempts fail.
|
||||
return NOT_FBOS
|
||||
end
|
||||
|
||||
# This is how we lock old versions of FBOS out of the API:
|
||||
|
@ -197,10 +201,10 @@ private
|
|||
def mark_as_seen(bot = (current_user && current_user.device))
|
||||
when_farmbot_os do
|
||||
if bot
|
||||
v = fbos_version
|
||||
v = fbos_version
|
||||
bot.last_saw_api = Time.now
|
||||
# Do _not_ set the FBOS version to 0.0.0 if the UA header is missing.
|
||||
bot.fbos_version = v.to_s if v != CalculateUpgrade::NULL
|
||||
bot.fbos_version = v.to_s if v != NULL
|
||||
bot.save!
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
class CalculateUpgrade < Mutations::Command
|
||||
NULL = Gem::Version.new("0.0.0")
|
||||
NOT_FBOS = Gem::Version.new("999.999.999")
|
||||
MEDIUM_OLDISH = Gem::Version.new("5.0.9")
|
||||
LEGACY_CUTOFF = Gem::Version.new("5.0.6")
|
||||
|
||||
# For extremely old versions:
|
||||
OLD_OS_URL = "https://api.github.com/repos/farmbot/farmbot_os" +
|
||||
"/releases/8772352"
|
||||
|
||||
# For versions that are slightly out of date:
|
||||
MID_OS_URL = "https://api.github.com/repos/FarmBot/farmbot_os/releases" +
|
||||
"/9200943"
|
||||
|
||||
# Latest version when you don't have a custom release URL.
|
||||
DEFAULT_OS = "https://api.github.com/repos/farmbot/farmbot_os/releases" +
|
||||
"/latest"
|
||||
|
||||
# Custom URL, or fallback to default if no custom URL is set.
|
||||
OS_RELEASE = ENV.fetch("OS_UPDATE_SERVER") { DEFAULT_OS }
|
||||
|
||||
required do
|
||||
model :version, class: Gem::Version
|
||||
end
|
||||
|
||||
def execute
|
||||
return OLD_OS_URL if version <= LEGACY_CUTOFF
|
||||
return MID_OS_URL if version <= MEDIUM_OLDISH
|
||||
return OS_RELEASE
|
||||
end
|
||||
end
|
|
@ -1,51 +1,53 @@
|
|||
# Generates a JSON Web Token (JWT) for a given user. Typically placed in the
|
||||
# `Authorization` header, or used a password to gain access to the MQTT server.
|
||||
class SessionToken < AbstractJwtToken
|
||||
MUST_VERIFY = "Verify account first"
|
||||
MQTT = ENV.fetch("MQTT_HOST")
|
||||
MUST_VERIFY = "Verify account first"
|
||||
MQTT = ENV.fetch("MQTT_HOST")
|
||||
# No beta URL provided? Then provide the latest stable.
|
||||
DEFAULT_BETA_URL = \
|
||||
DEFAULT_BETA_URL =
|
||||
"https://api.github.com/repos/FarmBot/farmbot_os/releases/latest"
|
||||
# If you are not using the standard MQTT broker (eg: you use a 3rd party
|
||||
# MQTT vendor), you will need to change this line.
|
||||
DEFAULT_MQTT_WS = \
|
||||
DEFAULT_MQTT_WS =
|
||||
"#{ENV["FORCE_SSL"] ? "wss://" : "ws://"}#{ENV.fetch("MQTT_HOST")}:3002/ws"
|
||||
MQTT_WS = ENV["MQTT_WS"] || DEFAULT_MQTT_WS
|
||||
EXPIRY = 40.days
|
||||
VHOST = ENV.fetch("MQTT_VHOST") { "/" }
|
||||
BETA_OS_URL = ENV["BETA_OTA_URL"] || DEFAULT_BETA_URL
|
||||
MQTT_WS = ENV["MQTT_WS"] || DEFAULT_MQTT_WS
|
||||
EXPIRY = 40.days
|
||||
VHOST = ENV.fetch("MQTT_VHOST") { "/" }
|
||||
BETA_OS_URL = ENV["BETA_OTA_URL"] || DEFAULT_BETA_URL
|
||||
# Originally imported from `CalculateVersion` mutation (check source control
|
||||
# for context) - RC
|
||||
OS_RELEASE_SERVER = ENV.fetch("OS_UPDATE_SERVER") { DEFAULT_OS }
|
||||
|
||||
def self.issue_to(user,
|
||||
iat: Time.now.to_i,
|
||||
exp: EXPIRY.from_now.to_i,
|
||||
iss: $API_URL,
|
||||
aud: AbstractJwtToken::UNKNOWN_AUD,
|
||||
fbos_version:) # Gem::Version
|
||||
|
||||
unless user.verified?
|
||||
Rollbar.info("Verification Error", email: user.email)
|
||||
raise Errors::Forbidden, MUST_VERIFY
|
||||
end
|
||||
url = CalculateUpgrade.run!(version: fbos_version)
|
||||
jti = SecureRandom.uuid
|
||||
TokenIssuance.create!(device_id: user.device.id, exp: exp, jti: jti)
|
||||
self.new([{ aud: aud,
|
||||
sub: user.id,
|
||||
iat: iat,
|
||||
jti: jti,
|
||||
iss: iss,
|
||||
exp: exp,
|
||||
mqtt: MQTT,
|
||||
bot: "device_#{user.device.id}",
|
||||
vhost: VHOST,
|
||||
mqtt_ws: MQTT_WS,
|
||||
os_update_server: url,
|
||||
beta_os_update_server: BETA_OS_URL }])
|
||||
self.new([{ aud: aud,
|
||||
sub: user.id,
|
||||
iat: iat,
|
||||
jti: jti,
|
||||
iss: iss,
|
||||
exp: exp,
|
||||
mqtt: MQTT,
|
||||
bot: "device_#{user.device.id}",
|
||||
vhost: VHOST,
|
||||
mqtt_ws: MQTT_WS,
|
||||
os_update_server: OS_RELEASE_SERVER,
|
||||
beta_os_update_server: BETA_OS_URL }])
|
||||
end
|
||||
|
||||
def self.as_json(user, aud, fbos_version)
|
||||
{ token: SessionToken.issue_to(user, iss: $API_URL,
|
||||
aud: aud,
|
||||
fbos_version: fbos_version),
|
||||
user: user }
|
||||
user: user }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,40 +1,40 @@
|
|||
# Farmbot Device models all data related to an actual FarmBot in the real world.
|
||||
class Device < ApplicationRecord
|
||||
DEFAULT_MAX_CONFIGS = 100
|
||||
DEFAULT_MAX_IMAGES = 100
|
||||
DEFAULT_MAX_LOGS = 1000
|
||||
DEFAULT_MAX_IMAGES = 100
|
||||
DEFAULT_MAX_LOGS = 1000
|
||||
|
||||
TIMEZONES = TZInfo::Timezone.all_identifiers
|
||||
BAD_TZ = "%{value} is not a valid timezone"
|
||||
THROTTLE_ON = "Device is sending too many logs (%s). " \
|
||||
"Suspending log storage and display until %s."
|
||||
THROTTLE_OFF = "Cooldown period has ended. "\
|
||||
"Resuming log storage."
|
||||
CACHE_KEY = "devices.%s"
|
||||
TIMEZONES = TZInfo::Timezone.all_identifiers
|
||||
BAD_TZ = "%{value} is not a valid timezone"
|
||||
THROTTLE_ON = "Device is sending too many logs (%s). " \
|
||||
"Suspending log storage and display until %s."
|
||||
THROTTLE_OFF = "Cooldown period has ended. " \
|
||||
"Resuming log storage."
|
||||
CACHE_KEY = "devices.%s"
|
||||
|
||||
has_many :farmware_envs, dependent: :destroy
|
||||
has_many :farm_events, dependent: :destroy
|
||||
has_many :farmware_installations, dependent: :destroy
|
||||
has_many :images, dependent: :destroy
|
||||
has_many :logs, dependent: :destroy
|
||||
has_many :peripherals, dependent: :destroy
|
||||
has_many :pin_bindings, dependent: :destroy
|
||||
has_many :plant_templates, dependent: :destroy
|
||||
has_many :points, dependent: :destroy
|
||||
has_many :regimens, dependent: :destroy
|
||||
has_many :saved_gardens, dependent: :destroy
|
||||
has_many :sensor_readings, dependent: :destroy
|
||||
has_many :sensors, dependent: :destroy
|
||||
has_many :sequences, dependent: :destroy
|
||||
has_many :token_issuances, dependent: :destroy
|
||||
has_many :tools, dependent: :destroy
|
||||
has_many :webcam_feeds, dependent: :destroy
|
||||
has_many :diagnostic_dumps, dependent: :destroy
|
||||
has_many :fragments, dependent: :destroy
|
||||
has_one :fbos_config, dependent: :destroy
|
||||
has_many :in_use_tools
|
||||
has_many :in_use_points
|
||||
has_many :users
|
||||
has_many :farmware_envs, dependent: :destroy
|
||||
has_many :farm_events, dependent: :destroy
|
||||
has_many :farmware_installations, dependent: :destroy
|
||||
has_many :images, dependent: :destroy
|
||||
has_many :logs, dependent: :destroy
|
||||
has_many :peripherals, dependent: :destroy
|
||||
has_many :pin_bindings, dependent: :destroy
|
||||
has_many :plant_templates, dependent: :destroy
|
||||
has_many :points, dependent: :destroy
|
||||
has_many :regimens, dependent: :destroy
|
||||
has_many :saved_gardens, dependent: :destroy
|
||||
has_many :sensor_readings, dependent: :destroy
|
||||
has_many :sensors, dependent: :destroy
|
||||
has_many :sequences, dependent: :destroy
|
||||
has_many :token_issuances, dependent: :destroy
|
||||
has_many :tools, dependent: :destroy
|
||||
has_many :webcam_feeds, dependent: :destroy
|
||||
has_many :diagnostic_dumps, dependent: :destroy
|
||||
has_many :fragments, dependent: :destroy
|
||||
has_one :fbos_config, dependent: :destroy
|
||||
has_many :in_use_tools
|
||||
has_many :in_use_points
|
||||
has_many :users
|
||||
|
||||
validates_presence_of :name
|
||||
validates :timezone,
|
||||
|
@ -73,7 +73,7 @@ class Device < ApplicationRecord
|
|||
# background jobs. If you are not receiving auto_sync data on your client,
|
||||
# you probably need to use this method.
|
||||
def auto_sync_transaction
|
||||
prev = Device.current
|
||||
prev = Device.current
|
||||
Device.current = self
|
||||
yield
|
||||
Device.current = prev
|
||||
|
@ -114,7 +114,7 @@ class Device < ApplicationRecord
|
|||
if (violation && throttled_until.nil?)
|
||||
et = violation.ends_at
|
||||
reload.update_attributes!(throttled_until: et,
|
||||
throttled_at: Time.now)
|
||||
throttled_at: Time.now)
|
||||
refresh_cache
|
||||
cooldown = et.in_time_zone(self.timezone || "UTC").strftime("%I:%M%p")
|
||||
info = [violation.explanation, cooldown]
|
||||
|
@ -131,16 +131,17 @@ class Device < ApplicationRecord
|
|||
cooldown_notice(THROTTLE_OFF, old_time, "info")
|
||||
end
|
||||
end
|
||||
|
||||
# Send a realtime message to a logged in user.
|
||||
def tell(message, channels = [], type = "info")
|
||||
log = Log.new({ device: self,
|
||||
message: message,
|
||||
created_at: Time.now,
|
||||
channels: channels,
|
||||
major_version: 99,
|
||||
minor_version: 99,
|
||||
meta: {},
|
||||
type: type })
|
||||
log = Log.new({ device: self,
|
||||
message: message,
|
||||
created_at: Time.now,
|
||||
channels: channels,
|
||||
major_version: 99,
|
||||
minor_version: 99,
|
||||
meta: {},
|
||||
type: type })
|
||||
json = LogSerializer.new(log).as_json.to_json
|
||||
|
||||
Transport.current.amqp_send(json, self.id, "logs")
|
||||
|
@ -148,9 +149,9 @@ class Device < ApplicationRecord
|
|||
end
|
||||
|
||||
def cooldown_notice(message, throttle_time, type, now = Time.current)
|
||||
hours = ((throttle_time - now) / 1.hour).round
|
||||
hours = ((throttle_time - now) / 1.hour).round
|
||||
channels = [(hours > 2) ? "email" : "toast"]
|
||||
tell(message, channels , type).save
|
||||
tell(message, channels, type).save
|
||||
end
|
||||
|
||||
def regimina
|
||||
|
@ -182,7 +183,7 @@ class Device < ApplicationRecord
|
|||
# Used by sys admins to debug problems without performing a password reset.
|
||||
def create_token
|
||||
# If something manages to call this method, I'd like to be alerted of it.
|
||||
Rollbar.error("Someone is creating a debug user token", {device: self.id})
|
||||
Rollbar.error("Someone is creating a debug user token", { device: self.id })
|
||||
fbos_version = Api::AbstractController::EXPECTED_VER
|
||||
SessionToken
|
||||
.as_json(users.first, "SUPER", fbos_version)
|
||||
|
|
|
@ -8,68 +8,33 @@ module Logs
|
|||
# Had to change this from "model" to "duck" as a result.
|
||||
# See Device#refresh_cache(). Rails thinks cached `Device` objects
|
||||
# are unsaved.
|
||||
duck :device, methods: [:id, :is_device]
|
||||
duck :device, methods: [:id, :is_device]
|
||||
string :message
|
||||
end
|
||||
|
||||
optional do
|
||||
array :channels,
|
||||
class: String,
|
||||
in: CeleryScriptSettingsBag::ALLOWED_CHANNEL_NAMES
|
||||
# LEGACY SHIM AHEAD!!! ===================================================
|
||||
#
|
||||
# We once stored certain fields in a `meta` column.
|
||||
# The API has evolved since that time, the requirements are pretty solid
|
||||
# at this point and we need the ability to perform SQL queries. The `meta`
|
||||
# field is no longer useful nor is it a clean solution.
|
||||
#
|
||||
# Legacy FBOS versions expect logs to be in the same shape as before
|
||||
# and they also produce logs with the expectation that logs have a `meta`
|
||||
# field.
|
||||
#
|
||||
# We will keep the `meta` field around for now, but ideally, API users
|
||||
# should access `log.FOO` instead of `log.meta.FOO` for future
|
||||
# compatibility.
|
||||
#
|
||||
# TODO: delete the `meta` field once FBOS < v6.4.0 reach EOL.
|
||||
string :type, in: Log::TYPES
|
||||
in: CeleryScriptSettingsBag::ALLOWED_CHANNEL_NAMES,
|
||||
default: []
|
||||
string :type, in: Log::TYPES, default: "info"
|
||||
float :x
|
||||
float :y
|
||||
float :z
|
||||
integer :verbosity
|
||||
integer :verbosity, default: 1
|
||||
integer :major_version
|
||||
integer :minor_version
|
||||
integer :created_at
|
||||
|
||||
hash :meta do # This can be transitioned out soon.
|
||||
string :type, in: Log::TYPES
|
||||
optional do
|
||||
float :x
|
||||
float :y
|
||||
float :z
|
||||
integer :verbosity
|
||||
integer :major_version
|
||||
integer :minor_version
|
||||
end
|
||||
end
|
||||
# END LEGACY SHIM ========================================================
|
||||
end
|
||||
|
||||
def validate
|
||||
add_error :log, :private, BAD_WORDS if has_bad_words
|
||||
add_error :device, :no_id, "ID of device can't be nil" unless device.id
|
||||
@log = Log.new
|
||||
@log.device_id = device.id
|
||||
@log.message = message
|
||||
@log.channels = channels || []
|
||||
@log.x = transitional_field(:x)
|
||||
@log.y = transitional_field(:y)
|
||||
@log.z = transitional_field(:z)
|
||||
@log.verbosity = transitional_field(:verbosity, 1)
|
||||
@log.major_version = transitional_field(:major_version)
|
||||
@log.minor_version = transitional_field(:minor_version)
|
||||
@log.type = transitional_field(:type, "info")
|
||||
@log.created_at = DateTime.strptime(created_at.to_s,'%s') if created_at
|
||||
|
||||
# I think this was here for legacy reasons and can be removed.
|
||||
# Delete this comment if no log issues are noted after 25 MAR 19. - RC
|
||||
# add_error :device, :no_id, "ID of device can't be nil" unless device.id
|
||||
|
||||
@log = Log.new(clean_inputs)
|
||||
@log.validate!
|
||||
end
|
||||
|
||||
|
@ -80,6 +45,17 @@ module Logs
|
|||
|
||||
private
|
||||
|
||||
def clean_inputs
|
||||
@clean_inputs ||= inputs
|
||||
.except(:device, :created_at)
|
||||
.merge(created_at: formatted_timestamp, device_id: device.id)
|
||||
end
|
||||
|
||||
def formatted_timestamp
|
||||
@formatted_timestamp ||= created_at ?
|
||||
DateTime.strptime(created_at.to_s, "%s") : nil
|
||||
end
|
||||
|
||||
def maybe_deliver
|
||||
Log.delay.deliver(device, @log)
|
||||
end
|
||||
|
@ -87,11 +63,5 @@ module Logs
|
|||
def has_bad_words
|
||||
!!inputs[:message].upcase.match(BLACKLIST)
|
||||
end
|
||||
|
||||
# Helper for dealing with the gradual removal of the meta field.
|
||||
def transitional_field(name, default = nil)
|
||||
m = meta || {} # New logs wont have `meta`.
|
||||
return inputs[name] || m[name] || m[name.to_s] || default
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,13 +22,6 @@ module Sequences
|
|||
# first level, though. Because of how EdgeNode and PrimaryNode work,
|
||||
# superfluous attributes will disappear on save and that's OK.
|
||||
(inputs[:body] || []).map! { |x| x.slice(*ALLOWED_NODE_KEYS) }
|
||||
has_scope = inputs.dig(:args, :locals, :body).present?
|
||||
if has_scope
|
||||
sequence_id = inputs[:sequence].try(:id)
|
||||
has_ri = \
|
||||
sequence_id && RegimenItem.where(sequence_id: sequence_id).present?
|
||||
add_error :parent, :parent, NO_REGIMENS if has_ri
|
||||
end
|
||||
add_error :body, :syntax_error, checker.error.message if !checker.valid?
|
||||
end
|
||||
|
||||
|
|
|
@ -47,23 +47,6 @@ describe Api::SequencesController do
|
|||
expect(response.status).to eq(200)
|
||||
end
|
||||
|
||||
|
||||
it 'disallows adding `parent` to sequences used in a regimen' do
|
||||
sign_in user
|
||||
sequence = FakeSequence.create(device: user.device)
|
||||
regimen = Regimens::Create.run!(device: user.device,
|
||||
name: "X",
|
||||
color: "red",
|
||||
regimen_items: [
|
||||
{ time_offset: 10, sequence_id: sequence.id}
|
||||
])
|
||||
try_to_add_parent(sequence)
|
||||
expect(response.status).to eq(422)
|
||||
err = \
|
||||
Sequences::CeleryScriptValidators::NO_REGIMENS
|
||||
expect(json[:parent]).to include(err)
|
||||
end
|
||||
|
||||
it 'does not let you use other peoples point resources' do
|
||||
sign_in user
|
||||
sequence = FakeSequence.create( device: user.device)
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
require 'spec_helper'
|
||||
require "spec_helper"
|
||||
|
||||
describe SessionToken do
|
||||
let(:user) { FactoryBot.create(:user) }
|
||||
|
||||
FAKE_TOKEN = [
|
||||
{ "sub" => "admin@admin.com",
|
||||
"iat" => 1474570171,
|
||||
"jti" => "c315f378-a318-4d4c-ba06-e4544cbc0621",
|
||||
"iss" => "//localhost:3000",
|
||||
"exp" => 1474915771,
|
||||
"mqtt" => "localhost",
|
||||
"bot" => "04b57247-763a-4e99-b1a7-3743fe946a1a" },
|
||||
{ "typ" => "JWT",
|
||||
"alg" => "RS256" }
|
||||
]
|
||||
{ "sub" => "admin@admin.com",
|
||||
"iat" => 1474570171,
|
||||
"jti" => "c315f378-a318-4d4c-ba06-e4544cbc0621",
|
||||
"iss" => "//localhost:3000",
|
||||
"exp" => 1474915771,
|
||||
"mqtt" => "localhost",
|
||||
"bot" => "04b57247-763a-4e99-b1a7-3743fe946a1a" },
|
||||
{ "typ" => "JWT",
|
||||
"alg" => "RS256" },
|
||||
]
|
||||
|
||||
it 'initializes' do
|
||||
it "initializes" do
|
||||
token = SessionToken.new(FAKE_TOKEN)
|
||||
expect(token.unencoded).to be_kind_of(Hash)
|
||||
actual = token.unencoded
|
||||
actual = token.unencoded
|
||||
expected = FAKE_TOKEN[0]
|
||||
expect(actual["sub"]).to eq(expected["sub"])
|
||||
expect(actual["iat"]).to eq(expected["iat"])
|
||||
|
@ -29,43 +29,23 @@ describe SessionToken do
|
|||
expect(actual["bot"]).to eq(expected["bot"])
|
||||
end
|
||||
|
||||
it 'issues a token to a user' do
|
||||
it "issues a token to a user" do
|
||||
token = SessionToken.issue_to(user, iat: 000,
|
||||
exp: 456,
|
||||
iss: "//lycos.com:9867",
|
||||
fbos_version: Gem::Version.new("9.9.9"))
|
||||
exp: 456,
|
||||
iss: "//lycos.com:9867",
|
||||
fbos_version: Gem::Version.new("9.9.9"))
|
||||
expect(token.unencoded[:beta_os_update_server]).to be_kind_of(String)
|
||||
end
|
||||
|
||||
it 'conditionally sets `os_update_server`' do
|
||||
test_case = -> (ver) do
|
||||
SessionToken
|
||||
.issue_to(user, fbos_version: Gem::Version.new(ver))
|
||||
.unencoded[:os_update_server]
|
||||
end
|
||||
|
||||
expect(test_case["0.0.0"]).to eq(CalculateUpgrade::OLD_OS_URL)
|
||||
expect(test_case["5.0.5"]).to eq(CalculateUpgrade::OLD_OS_URL)
|
||||
expect(test_case["5.0.6"]).to eq(CalculateUpgrade::OLD_OS_URL)
|
||||
expect(test_case["5.0.8"]).to eq(CalculateUpgrade::MID_OS_URL)
|
||||
expect(test_case["5.0.9"]).to eq(CalculateUpgrade::MID_OS_URL)
|
||||
expect(test_case["6.0.1"]).to eq(CalculateUpgrade::OS_RELEASE)
|
||||
expect(test_case["999.999.999"])
|
||||
.to eq(CalculateUpgrade::OS_RELEASE)
|
||||
expect(test_case["0.0.0"]).to eq(CalculateUpgrade::OLD_OS_URL)
|
||||
expect(test_case[CalculateUpgrade::NOT_FBOS]).to eq(CalculateUpgrade::OS_RELEASE)
|
||||
end
|
||||
|
||||
it "doesn't honor expired tokens" do
|
||||
user.update_attributes!(confirmed_at: Time.now)
|
||||
token = SessionToken.issue_to(user, iat: 000,
|
||||
exp: 1,
|
||||
iss: "//lycos.com:9867",
|
||||
fbos_version: Gem::Version.new("9.9.9"))
|
||||
token = SessionToken.issue_to(user, iat: 000,
|
||||
exp: 1,
|
||||
iss: "//lycos.com:9867",
|
||||
fbos_version: Gem::Version.new("9.9.9"))
|
||||
result = Auth::FromJWT.run(jwt: token.encoded)
|
||||
expect(result.success?).to be(false)
|
||||
expect(result.errors.values.first.message)
|
||||
.to eq(Auth::ReloadToken::BAD_SUB)
|
||||
expect(result.errors.values.first.message).to eq(Auth::ReloadToken::BAD_SUB)
|
||||
end
|
||||
|
||||
unless ENV["NO_EMAILS"]
|
||||
|
|
|
@ -1,58 +1,43 @@
|
|||
require 'spec_helper'
|
||||
require "spec_helper"
|
||||
|
||||
describe Auth::FromJWT do
|
||||
let(:user) { FactoryBot.create(:user) }
|
||||
|
||||
let(:user) { FactoryBot.create(:user) }
|
||||
|
||||
def fake_credentials(email, password)
|
||||
# Input -> JSONify -> encrypt -> Base64ify
|
||||
secret = { email: email, password: password }.to_json
|
||||
ct = KeyGen.current.public_encrypt(secret)
|
||||
ct = KeyGen.current.public_encrypt(secret)
|
||||
return Base64.encode64(ct)
|
||||
end
|
||||
|
||||
it 'rejects bad credentials' do
|
||||
it "rejects bad credentials" do
|
||||
results = Auth::CreateTokenFromCredentials
|
||||
.run(credentials: "FOO", fbos_version: Gem::Version.new("999.9.9"))
|
||||
expect(results.success?).to eq(false)
|
||||
expect(results.errors.message_list)
|
||||
.to include(Auth::CreateTokenFromCredentials::BAD_KEY)
|
||||
expect(results.errors.message_list).to include(Auth::CreateTokenFromCredentials::BAD_KEY)
|
||||
end
|
||||
|
||||
it 'accepts good credentials' do
|
||||
pw = "password123"
|
||||
user = FactoryBot.create(:user, password: pw)
|
||||
email = user.email
|
||||
creds = fake_credentials(email, pw)
|
||||
it "accepts good credentials" do
|
||||
pw = "password123"
|
||||
user = FactoryBot.create(:user, password: pw)
|
||||
email = user.email
|
||||
creds = fake_credentials(email, pw)
|
||||
results = Auth::CreateTokenFromCredentials
|
||||
.run!(credentials: creds, fbos_version: Gem::Version.new("999.9.9"))
|
||||
expect(results[:token]).to be_kind_of(SessionToken)
|
||||
expect(results[:user]).to eq(user)
|
||||
expect(results[:token].unencoded[:os_update_server]).to eq(CalculateUpgrade::OS_RELEASE)
|
||||
expect(results[:token].unencoded[:os_update_server]).to eq(SessionToken::OS_RELEASE_SERVER)
|
||||
end
|
||||
|
||||
it 'Rejects bad email / password' do
|
||||
it "Rejects bad email / password" do
|
||||
input = {
|
||||
email: "x@y.z",
|
||||
password: "password123",
|
||||
fbos_version: Gem::Version.new("10.9.8")
|
||||
email: "x@y.z",
|
||||
password: "password123",
|
||||
fbos_version: Gem::Version.new("10.9.8"),
|
||||
}
|
||||
x = Auth::CreateToken.run(input)
|
||||
expect(x.success?).to be false
|
||||
expect(x.errors["auth"]).to be
|
||||
expect(x.errors["auth"].message_list).to include("Bad email or password.")
|
||||
end
|
||||
|
||||
it 'sometimes renders the legacy URL' do
|
||||
pw = "password123"
|
||||
user = FactoryBot.create(:user, password: pw)
|
||||
email = user.email
|
||||
creds = fake_credentials(email, pw)
|
||||
results = Auth::CreateTokenFromCredentials
|
||||
.run!(credentials: creds, fbos_version: Gem::Version.new("5.0.5"))
|
||||
expect(results[:token]).to be_kind_of(SessionToken)
|
||||
expect(results[:user]).to eq(user)
|
||||
expect(results[:token].unencoded[:os_update_server])
|
||||
.to eq(CalculateUpgrade::OLD_OS_URL)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue