Merge branch 'master' of https://github.com/FarmBot/Farmbot-Web-App into finally_fix_devices_tsx

pull/493/head
Rick Carlino 2017-10-10 16:35:45 -05:00
commit 10b0922acf
13 changed files with 178 additions and 80 deletions

View File

@ -24,12 +24,13 @@ gem "webpack-rails"
# Active on the "official" FarmBot server, set the appropriate ENV
# vars if you wish to use them on your own servers.
gem "rollbar"
gem "skylight"
gem "skylight", "1.4.0"
group :development, :test do
gem "codecov", require: false
gem "database_cleaner"
gem "pry"
gem "pry-rails"
gem "factory_girl_rails"
gem "faker"
gem "smarf_doc", git: "https://github.com/RickCarlino/smarf_doc.git"

View File

@ -156,6 +156,8 @@ GEM
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
pry-rails (0.3.6)
pry (>= 0.10.4)
public_suffix (3.0.0)
rack (2.0.3)
rack-attack (5.0.1)
@ -229,7 +231,7 @@ GEM
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
skylight (1.3.1)
skylight (1.4.0)
activesupport (>= 3.0.0)
slop (3.6.0)
sprockets (3.7.1)
@ -278,6 +280,7 @@ DEPENDENCIES
pg
polymorphic_constraints
pry
pry-rails
rack-attack
rack-cors
rails
@ -287,7 +290,7 @@ DEPENDENCIES
rspec (~> 3.5.0)
rspec-rails (~> 3.5.0)
simplecov
skylight
skylight (= 1.4.0)
smarf_doc!
thin
tzinfo

View File

@ -8,30 +8,10 @@ module Api
# If some logs fail to save, they will fail silently.
# As a matter of policy, not all log types are stored in the DB.
def create
case raw_json
when Array
logs = Log
.create(raw_json.last(current_device.max_log_count)
.map { |i| new_log(i) }
.select { |i| i.success? } # <= Ignore rejects
.map { |i| i.result }
.reject do |i|
# Don't save jokes or debug info:
t = i.meta["type"]
["fun", "debug"].include?(t)
end
.map { |i| i.as_json })
.tap { |i| maybe_deliver(i) }
render json: logs
when Hash
outcome = new_log(raw_json)
if outcome.success?
outcome.result.save!
maybe_deliver(outcome.result)
end
return mutate outcome
else
sorry "Post a JSON array or object.", 422
case raw_json
when Array then handle_many_logs
when Hash then handle_single_log
else; sorry "Post a JSON array or object.", 422
end
end
@ -47,12 +27,25 @@ module Api
private
def handle_many_logs
mutate Logs::BatchCreate.run(device: current_device, logs: raw_json)
end
def handle_single_log
outcome = new_log(raw_json)
if outcome.success?
outcome.result.save!
maybe_deliver(outcome.result)
end
mutate outcome
end
def new_log(input)
Logs::Create.run(input, device: current_device)
end
def maybe_deliver(log_or_logs)
LogDispatch.deliver(current_device, log_or_logs)
LogDispatch.delay.deliver(current_device, log_or_logs)
end
end
end

View File

@ -24,21 +24,26 @@ module Api
end
private
# Don't proceed with login if they need to sign the EULA
instrument_method
def maybe_halt_login(result)
result.result[:user].try(:require_consent!) if result.success?
end
instrument_method
def guess_aud_claim
when_farmbot_os { return AbstractJwtToken::BOT_AUD }
return AbstractJwtToken::HUMAN_AUD if xhr?
AbstractJwtToken::UNKNOWN_AUD
end
instrument_method
def xhr? # I only wrote this because `request.xhr?` refused to be stubbed
request.xhr?
end
instrument_method
def if_properly_formatted
user = params.as_json.deep_symbolize_keys.fetch(:user, {})
# If data handling for this method gets any more complicated,

View File

@ -0,0 +1,7 @@
class BatchLogDispatchJob < ApplicationJob
queue_as :default
def perform(device, logs)
LogDispatch.deliver(device, Log.create!(logs))
end
end

View File

@ -15,9 +15,9 @@ class Device < ApplicationRecord
has_many :tools, dependent: :destroy
has_many :images, dependent: :destroy
has_many :webcam_feeds, dependent: :destroy
validates :timezone, inclusion: { in: TIMEZONES,
message: BAD_TZ,
allow_nil: true }
validates :timezone, inclusion: { in: TIMEZONES,
message: BAD_TZ,
allow_nil: true }
validates_presence_of :name
# Give the user back the amount of logs they are allowed to view.

View File

@ -15,8 +15,8 @@ class FarmEvent < ApplicationRecord
validate :within_20_year_window
def within_20_year_window
too_early = start_time < (Time.now - 20.years)
too_late = end_time > (Time.now + 20.years)
too_early = start_time && start_time < (Time.now - 20.years)
too_late = end_time && end_time > (Time.now + 20.years)
errors.add :start_time, "too far in the past" if too_early
errors.add :end_time, "too far in the future" if too_late
end

View File

@ -1,6 +1,7 @@
module Auth
class CreateToken < Mutations::Command
include Auth::ConsentHelpers
include Skylight::Helpers
required do
string :email
@ -14,6 +15,7 @@ module Auth
default: AbstractJwtToken::UNKNOWN_AUD
end
instrument_method
def validate
@user = User.where(email: email.downcase).first
whoops! unless @user && @user.valid_password?(password)
@ -22,6 +24,7 @@ module Auth
end
end
instrument_method
def execute
@user.update_attributes(agreed_to_terms_at: Time.now) if agree_to_terms
SessionToken.as_json(@user, aud)
@ -29,6 +32,7 @@ module Auth
private
instrument_method
def whoops!
add_error :auth, :*, "Bad email or password."
end

View File

@ -1,5 +1,7 @@
module Auth
class CreateTokenFromCredentials < Mutations::Command
include Skylight::Helpers
PRIVATE_KEY = KeyGen.current
BAD_KEY = "You are most likely on the wrong server env. That's not a "\
"valid credentials file."
@ -10,6 +12,7 @@ module Auth
string :credentials
end
instrument_method
def validate
cipher_text = Base64.decode64(credentials)
plain_text = PRIVATE_KEY.private_decrypt(cipher_text)
@ -20,12 +23,14 @@ module Auth
whoops!(BAD_KEY)
end
instrument_method
def execute
SessionToken.as_json(user, AbstractJwtToken::BOT_AUD)
end
private
instrument_method
def whoops!(reason = "Bad email or password.")
add_error :auth, :*, reason
end

View File

@ -0,0 +1,44 @@
module Logs
class BatchCreate < Mutations::Command
required do
model :device, class: Device
array :logs do
hash do
string :message
optional do
array :channels,
class: String,
in: CeleryScriptSettingsBag::ALLOWED_CHANNEL_NAMES
hash :meta do
integer :x
integer :y
integer :z
string :type, in: Log::TYPES
end
end
end
end
end
def execute
BatchLogDispatchJob.perform_later(device, clean_logs)
return clean_logs
end
private
def clean_logs
@clean_logs ||= logs
.last(device.max_log_count)
.map { |i| Logs::Create.run(i, device: device) }
.select { |i| i.success? } # <= Ignore rejects
.map { |i| i.result }
.reject do |i|
# Don't save jokes or debug info:
t = i.meta["type"]
["fun", "debug"].include?(t)
end
.map { |i| i.as_json.except("id") }
end
end
end

View File

@ -9,7 +9,9 @@ module Logs
end
optional do
array :channels, class: String
array :channels,
class: String,
in: CeleryScriptSettingsBag::ALLOWED_CHANNEL_NAMES
hash :meta do
integer :x
integer :y

View File

@ -48,19 +48,21 @@ describe Api::LogsController do
it 'creates many logs (with an Array)' do
sign_in user
before_count = Log.count
post :create,
body: [
{ meta: { x: 1, y: 2, z: 3, type: "info" },
channels: ["toast"],
message: "one" },
{ meta: { x: 1, y: 2, z: 3, type: "info" },
channels: ["toast"],
message: "two" },
{ meta: { x: 1, y: 2, z: 3, type: "info" },
channels: ["toast"],
message: "three" },
].to_json,
params: {format: :json}
run_jobs_now do
post :create,
body: [
{ meta: { x: 1, y: 2, z: 3, type: "info" },
channels: ["toast"],
message: "one" },
{ meta: { x: 1, y: 2, z: 3, type: "info" },
channels: ["toast"],
message: "two" },
{ meta: { x: 1, y: 2, z: 3, type: "info" },
channels: ["toast"],
message: "three" },
].to_json,
params: {format: :json}
end
expect(response.status).to eq(200)
expect(before_count + 3).to eq(Log.count)
end
@ -68,39 +70,51 @@ describe Api::LogsController do
it 'does not bother saving `fun` or `debug` logs' do
sign_in user
Log.destroy_all
LogDispatch.destroy_all
before_count = Log.count
dispatch_before = LogDispatch.count
post :create,
body: [
{ meta: { x: 1, y: 2, z: 3, type: "info" },
channels: ["toast"],
message: "one" },
{ meta: { x: 1, y: 2, z: 3, type: "fun" }, # Ignored
channels: [],
message: "two" },
{ meta: { x: 1, y: 2, z: 3, type: "debug" }, # Ignored
channels: [],
message: "two" },
{ meta: { x: 1, y: 2, z: 3, type: "info" },
channels: ["email"],
message: "three" },
].to_json,
params: {format: :json}
expect(response.status).to eq(200)
expect(before_count + 2).to eq(Log.count)
expect(dispatch_before + 1).to eq(LogDispatch.count)
run_jobs_now do
post :create,
body: [
{ meta: { x: 1, y: 2, z: 3, type: "info" },
channels: ["toast"],
message: "one" },
{ meta: { x: 1, y: 2, z: 3, type: "fun" }, # Ignored
channels: [],
message: "two" },
{ meta: { x: 1, y: 2, z: 3, type: "debug" }, # Ignored
channels: [],
message: "two" },
{ meta: { x: 1, y: 2, z: 3, type: "info" },
channels: ["email"],
message: "three" },
].to_json,
params: {format: :json}
expect(response.status).to eq(200)
expect(Log.count).to eq(before_count + 2)
expect(LogDispatch.count).to eq(dispatch_before + 1)
end
end
it 'Runs compaction when the logs pile up' do
stub = {
meta: { x: 1, y: 2, z: 3, type: "info" }, channels: ["toast"],
message: "one" }
payl = []
100.times { payl.push(stub) }
100.times do
payl.push({ meta: { x: 1,
y: 2,
z: 3,
type: "info"
},
channels: ["toast"],
message: "one" })
end
sign_in user
user.device.update_attributes!(max_log_count: 15)
LogDispatch.destroy_all
Log.destroy_all
before_count = Log.count
post :create, body: payl.to_json, params: {format: :json}
run_jobs_now do
post :create, body: payl.to_json, params: {format: :json}
end
expect(response.status).to eq(200)
expect(json.length).to eq(user.device.max_log_count)
end
@ -121,14 +135,16 @@ describe Api::LogsController do
body = { meta: { x: 1, y: 2, z: 3, type: "info" },
channels: ["email"],
message: "Heyoooo" }.to_json
post :create, body: body, params: {format: :json}
after_count = LogDispatch.count
expect(response.status).to eq(200)
expect(last_email).to be
expect(last_email.body.to_s).to include("Heyoooo")
expect(last_email.to).to include(user.email)
expect(before_count).to be < after_count
expect(LogDispatch.where(sent_at: nil).count).to eq(0)
run_jobs_now do
post :create, body: body, params: {format: :json}
after_count = LogDispatch.count
expect(response.status).to eq(200)
expect(last_email).to be
expect(last_email.body.to_s).to include("Heyoooo")
expect(last_email.to).to include(user.email)
expect(before_count).to be < after_count
expect(LogDispatch.where(sent_at: nil).count).to eq(0)
end
end
it "handles bug that Connor reported" do

View File

@ -3,10 +3,28 @@ require 'spec_helper'
describe Auth::FromJWT do
let(:user) { FactoryGirl.create(:user) }
it 'gets user from jwt' do
def fake_credentials(email, password)
# Input -> JSONify -> encrypt -> Base64ify
secret = { email: email, password: password }.to_json
ct = KeyGen.current.public_encrypt(secret)
return Base64.encode64(ct)
end
it 'rejects bad credentials' do
results = Auth::CreateTokenFromCredentials.run(credentials: "FOO" )
expect(results.success?).to eq(false)
expect(results.errors.message_list)
.to include(Auth::CreateTokenFromCredentials::BAD_KEY)
end
it 'accepts good credentials' do
pw = "password123"
user = FactoryGirl.create(:user, password: pw)
email = user.email
creds = fake_credentials(email, pw)
results = Auth::CreateTokenFromCredentials.run!(credentials: creds)
expect(results[:token]).to be_kind_of(SessionToken)
expect(results[:user]).to eq(user)
end
end