panda_pal 4.1.0.beta3 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ef5b128c1f14031f45d4955e56094db8cab358ba
4
- data.tar.gz: 0e55cffec0dc1dd61ce847d018f1140a047549ab
2
+ SHA256:
3
+ metadata.gz: b728d9c41289ccb3ddeba5c13245345750900733052dd26482f1f2efc2d048a8
4
+ data.tar.gz: e5418d426bbe784e5a4ec804339a81f326d936297b6d0c20bba432c3e2d851d2
5
5
  SHA512:
6
- metadata.gz: db77dbe724b48bad8a1439e6296c48f1063cb90b22c9bf22a96f7e351cd5c6cbe186b1abe847983e35f3417bba4f1d4d9ce849f0628e158873a4db46d515b20f
7
- data.tar.gz: a9257806e5ad3c1d24b5faf0b0ca030ed7819b2ddd4aa3f8c20f80da103a1f95fb08a0bda9fe714b4cda4da2b678c46ff2ed4a176e730e7627fc4ad2f4e2dc13
6
+ metadata.gz: 58c36a718b50b9f320f101b3d52318ad6557cae9aa80dbb13531a798061a6243156834cc9170a452235be0b330f06772e6d571dc73fa25da2517254fc81850a7
7
+ data.tar.gz: 903d775a21f5550e0aab21593242308ff3e7ae991fc0df1467376a6a7b210b3c4fec7ae567e074595f48a254ea896f66d9770ebfb8904522cc754e2f7ac14d0b
data/README.md CHANGED
@@ -28,38 +28,6 @@ Use one of these 6 options in `PandaPal.lti_options` hash.
28
28
  5. Leave this property off, and you will get the dynamic host with the root path ('http://appdomain.com/') by default.
29
29
  6. If you really do not want this property use the option `launch_url: false` for it to be left off.
30
30
 
31
- ### Task Scheduling
32
- `PandaPal` includes an integration with `sidekiq-scheduler`. You can define tasks on an Organization class Stub like so:
33
- ```ruby
34
- # <your_app>/app/models/organization.rb
35
- module PandaPal
36
- class Organization
37
- # Will invoke CanvasSyncStarterWorker.perform_async() according to the cron schedule
38
- scheduled_task '0 15 05 * * *', :identifier, worker: CanvasSyncStarterWorker
39
-
40
- # Will invoke the method 'organization_method' on the Organization
41
- scheduled_task '0 15 05 * * *', :organization_method_and_identifier
42
-
43
- # If you need to invoke the same method on multiple schedules
44
- scheduled_task '0 15 05 * * *', :identifier, worker: :organization_method
45
-
46
- # You can also use a block
47
- scheduled_task '0 15 05 * * *', :identifier do
48
- # Do Stuff
49
- end
50
-
51
- # You can use a Proc (called in the context of the Organization) to determine the schedule
52
- scheduled_task -> { settings[:cron] }, :identifier
53
-
54
- # You can specify a timezone. If a TZ is not coded and settings[:timezone] is present, it will be appended automatically
55
- scheduled_task '0 15 05 * * * America/Denver', :identifier, worker: :organization_method
56
-
57
- # Setting settings[:task_schedules][:identifier] will override the code cron schedule. Setting it to false will disable the Task
58
- # :identifer values _must_ be unique, but can be nil, in which case they will be determined by where (lineno etc) scheduled_task is called
59
- end
60
- end
61
- ```
62
-
63
31
  # Organization Attributes
64
32
  id: Primary Key
65
33
  name: Name of the organization. Used to on requests to select the tenant
@@ -258,43 +226,9 @@ In your panda_pal initializer (i.e. config/initializers/panda_pal.rb or config/i
258
226
  You can specify options that can include a structure for your settings. If specified, PandaPal will
259
227
  enforce this structure on any new / updated organizations.
260
228
 
261
- ```ruby
262
- PandaPal.lti_options = {
263
- title: 'LBS Gradebook',
264
- settings_structure: {
265
- allow_additional: true, # Allow additional properties that aren't included in the :properties Hash.
266
- allow_additional: { type: 'String' }, # You can also set :allow_additional to a settings specification that will be used to validate each additional setting
267
- validate: ->(value, spec, **kwargs) {
268
- # kwargs currently includes:
269
- # :errors => [An array to push errors to]
270
- # :path => [An array representation of the current path in the settings object]
271
-
272
- # To add errors, you may:
273
- # Push strings to the kwargs[:errors] Array:
274
- kwargs[:errors] << "Your error message at <path>" unless value < 10
275
- # Or return a string or string array:
276
- value.valid? ? nil : "Your error message at <path>" # <path> will be replaced with the actual path that the error occurred at
277
- },
278
- properties: {
279
- canvas_api_token: { type: 'String', required: true, },
280
- catalog: { # :validate, :allow_additional, :properties keys are all supported at this level as well
281
- type: 'Hash',
282
- required: false,
283
- validate: -> (*args) {},
284
- allow_additional: false,
285
- properties: {
286
-
287
- },
288
- }
289
- }
290
- },
291
- }
292
- ```
293
-
294
- #### Legacy Settings Structure:
295
229
  Here is an example options specification:
296
230
 
297
- ```ruby
231
+ ```
298
232
  PandaPal.lti_options = {
299
233
  title: 'LBS Gradebook',
300
234
  settings_structure: YAML.load("
@@ -347,7 +281,62 @@ Safari is weird, and you'll potentially run into issues getting `POST` requests
347
281
 
348
282
  This will allow `PandaPal` to apply an iframe cookie fix that will allow CSRF validation to work.
349
283
 
350
- ## Running Specs:
351
- Initialize the Specs DB:
352
- `cd spec/dummy; bundle exec rake db:drop; bundle exec rake db:create; bundle exec rake db:schema:load`
353
- Then `bundle exec rspec`
284
+
285
+ ### PandaPal 5
286
+
287
+ It has been a constant struggle to force safari to store and allow
288
+ access to a rails session while the application is embedded in Canvas.
289
+
290
+ As of PandaPal 5, a persistent session is no longer required by panda_pal.
291
+
292
+ This means that safari will likely refuse to send info about your rails session
293
+ back to the LTI, and the application will start up a new session each time the
294
+ browser navigates. This likely means a new session each time the LTI launches.
295
+
296
+ You will want to watch out for a few scenarios:
297
+
298
+ 1) Make sure you are using "redirect_with_session_to" if you need to redirect
299
+ and have your PandaPal session_key persisted server side.
300
+ 2) Use the "Authorization" header with "token={session_key}" to send your
301
+ PandaPal session info into api calls.
302
+ 3) If you use link_to and navigate in your LTI (apps that are not single page)
303
+ make sure you include an encrypted_session_key parameter in your links.
304
+
305
+ # Upgrading from PandaPal 4 to 5:
306
+
307
+ If your tool is setup according to a pretty standard pattern (see pace_plans,
308
+ canvas_group_enrollment, etc), you shouldn't have to do anything to upgrade.
309
+
310
+ You will want to make sure that IF your launch controller is redirecting, it is
311
+ using "redirect_with_session_to".
312
+
313
+ Here is an example launch / account controller setup, assuming an account launch.
314
+
315
+ ```
316
+ class LaunchController < ApplicationController
317
+ # We don't verify CSRF on launch because the LTI launch is done via a POST
318
+ # request, and Canvas wouldn't know anything about the CSRF
319
+ skip_before_action :verify_authenticity_token
320
+ skip_before_action :forbid_access_if_lacking_session # We don't have a session yet
321
+ around_action :switch_tenant
322
+ before_action :validate_launch!
323
+ before_action :handle_launch
324
+
325
+ def handle_launch
326
+ current_session_data[:canvas_user_id] = params[:custom_canvas_user_id]
327
+ current_session_data[:canvas_course_id] = params[:custom_canvas_course_id]
328
+ end
329
+
330
+ def account
331
+ redirect_with_session_to :accounts_url
332
+ end
333
+ end
334
+
335
+ class AccountController < ApplicationController
336
+ prepend_before_action :forbid_access_if_lacking_session
337
+
338
+ def index
339
+ end
340
+ end
341
+ ```
342
+
@@ -1,23 +1,15 @@
1
- Dir[File.dirname(__FILE__) + "/organization/*.rb"].each { |file| require file }
2
-
3
1
  module PandaPal
4
- module OrganizationConcerns; end
5
2
 
6
3
  class Organization < ActiveRecord::Base
7
- include OrganizationConcerns::SettingsValidation
8
- include OrganizationConcerns::TaskScheduling if defined?(Sidekiq.schedule)
9
-
10
4
  attribute :settings
11
- serialize :settings, Hash
12
5
  attr_encrypted :settings, marshal: true, key: :encryption_key
13
6
  before_save {|a| a.settings = a.settings} # this is a hacky work-around to a bug where attr_encrypted is not saving settings in place
14
-
15
7
  validates :key, uniqueness: { case_sensitive: false }, presence: true
16
8
  validates :secret, presence: true
17
9
  validates :name, uniqueness: { case_sensitive: false }, presence: true, format: { with: /\A[a-z0-9_]+\z/i }
18
10
  validates :canvas_account_id, presence: true
19
11
  validates :salesforce_id, presence: true, uniqueness: true
20
-
12
+ validate :validate_settings
21
13
  after_create :create_schema
22
14
  after_commit :destroy_schema, on: :destroy
23
15
 
@@ -25,6 +17,8 @@ module PandaPal
25
17
  errors.add(:name, 'should not be changed after creation') if name_changed?
26
18
  end
27
19
 
20
+ serialize :settings, Hash
21
+
28
22
  def encryption_key
29
23
  # production environment might not have loaded secret_key_base yet.
30
24
  # In that case, just read it from env.
@@ -35,14 +29,6 @@ module PandaPal
35
29
  end
36
30
  end
37
31
 
38
- def switch_tenant(&block)
39
- if block_given?
40
- Apartment::Tenant.switch(name, &block)
41
- else
42
- Apartment::Tenant.switch!(name)
43
- end
44
- end
45
-
46
32
  private
47
33
 
48
34
  def create_schema
@@ -52,5 +38,48 @@ module PandaPal
52
38
  def destroy_schema
53
39
  Apartment::Tenant.drop name
54
40
  end
41
+
42
+ def validate_settings
43
+ record = self
44
+ if PandaPal.lti_options && PandaPal.lti_options[:settings_structure]
45
+ validate_level(record, PandaPal.lti_options[:settings_structure], record.settings, [])
46
+ end
47
+ end
48
+ def validate_level(record, expectation, reality, previous_keys)
49
+ # Verify that the data elements at this level conform to requirements.
50
+ if expectation
51
+ expectation.each do |key, value|
52
+ is_required = expectation[key].try(:delete, :is_required)
53
+ data_type = expectation[key].try(:delete, :data_type)
54
+ value = reality.is_a?(Hash) ? reality.dig(key) : reality
55
+
56
+ if is_required && !value
57
+ record.errors[:settings] << "PandaPal::Organization.settings requires key [:#{previous_keys.push(key).join("][:")}]. It was not found."
58
+ end
59
+ if data_type && value
60
+ if value.class.to_s != data_type
61
+ record.errors[:settings] << "PandaPal::Organization.settings expected key [:#{previous_keys.push(key).join("][:")}] to be #{data_type} but it was instead #{value.class}."
62
+ end
63
+ end
64
+ end
65
+ end
66
+ # Verify that anything that is in the real settings has an expectation.
67
+ if reality
68
+ if reality.is_a? Hash
69
+ reality.each do |key, value|
70
+ was_expected = expectation.has_key?(key)
71
+ if !was_expected
72
+ record.errors[:settings] << "PandaPal::Organization.settings had unexpected key: #{key}. If settings have expanded please update your lti_options accordingly."
73
+ end
74
+ end
75
+ end
76
+ end
77
+ # Recursively check out any children settings as well.
78
+ if expectation
79
+ expectation.each do |key, value|
80
+ validate_level(record, expectation[key], (reality.is_a?(Hash) ? reality.dig(key) : nil), previous_keys.deep_dup.push(key))
81
+ end
82
+ end
83
+ end
55
84
  end
56
85
  end
@@ -42,17 +42,6 @@ module PandaPal::Helpers::ControllerHelper
42
42
  render plain: 'Invalid Credentials, please contact your Administrator.', :status => :unauthorized unless authorized
43
43
  return authorized
44
44
  end
45
- if cookies_need_iframe_fix?
46
- fix_iframe_cookies
47
- return false
48
- end
49
- # For safari we may have been launched temporarily full-screen by canvas. This allows us to set the session cookie.
50
- # In this case, we should make sure the session cookie is fixed and redirect back to canvas to properly launch the embedded LTI.
51
- if params[:platform_redirect_url]
52
- session[:safari_cookie_fixed] = true
53
- redirect_to params[:platform_redirect_url]
54
- return false
55
- end
56
45
  return authorized
57
46
  end
58
47
 
@@ -65,29 +54,8 @@ module PandaPal::Helpers::ControllerHelper
65
54
  end
66
55
  end
67
56
 
68
- # Browsers that prevent 3rd party cookies by default (Safari and IE) run into problems
69
- # with CSRF handling because the Rails session cookie isn't set. To fix this, we
70
- # redirect the current page to the LTI using JavaScript, which will set the cookie,
71
- # and then immediately redirect back to Canvas.
72
- def fix_iframe_cookies
73
- if params[:safari_cookie_fix].present?
74
- session[:safari_cookie_fixed] = true
75
- redirect_to params[:return_to]
76
- else
77
- render 'panda_pal/lti/iframe_cookie_fix', layout: false
78
- end
79
- end
80
-
81
- def cookies_need_iframe_fix?
82
- browser.safari? && !request.referrer&.include?('sessionless_launch') && !session[:safari_cookie_fixed] && !params[:platform_redirect_url]
83
- end
84
-
85
57
  def forbid_access_if_lacking_session
86
- if cookies_need_iframe_fix?
87
- fix_iframe_cookies
88
- else
89
- render plain: 'You should do an LTI Tool Launch.', status: :unauthorized unless valid_session?
90
- end
58
+ render plain: 'You should do an LTI Tool Launch.', status: :unauthorized unless valid_session?
91
59
  safari_override
92
60
  end
93
61
 
@@ -114,6 +82,10 @@ module PandaPal::Helpers::ControllerHelper
114
82
  end
115
83
 
116
84
  def session_key
85
+ if params[:encrypted_session_key]
86
+ crypt = ActiveSupport::MessageEncryptor.new(Rails.application.secret_key_base[0..31])
87
+ return crypt.decrypt_and_verify(params[:encrypted_session_key])
88
+ end
117
89
  params[:session_key] || session_key_header || flash[:session_key] || session[:session_key]
118
90
  end
119
91
 
@@ -122,4 +94,37 @@ module PandaPal::Helpers::ControllerHelper
122
94
  match[1]
123
95
  end
124
96
  end
97
+
98
+ # Redirect with the session key intact. In production,
99
+ # handle this by encrypting the session key. That way if the
100
+ # url is logged anywhere, it will all be encrypted data. In dev,
101
+ # just put it in the URL. Putting it in the URL
102
+ # is insecure, but is fine in development.
103
+ # Keeping it in the URL in development means that it plays
104
+ # nicely with webpack-dev-server live reloading (otherwise
105
+ # you get an access error everytime it tries to live reload).
106
+
107
+ def redirect_with_session_to(location, params = {})
108
+ if Rails.env.development?
109
+ redirect_development_mode(location, params)
110
+ else
111
+ redirect_production_mode(location, params)
112
+ end
113
+ end
114
+
115
+ def redirect_development_mode(location, params)
116
+ redirect_to send(location, {
117
+ session_key: current_session.session_key,
118
+ organization_id: current_organization.id
119
+ }.merge(params))
120
+ end
121
+
122
+ def redirect_production_mode(location, params)
123
+ crypt = ActiveSupport::MessageEncryptor.new(Rails.application.secret_key_base[0..31])
124
+ encrypted_data = crypt.encrypt_and_sign(current_session.session_key)
125
+ redirect_to send(location, {
126
+ encrypted_session_key: encrypted_data,
127
+ organization_id: current_organization.id
128
+ }.merge(params))
129
+ end
125
130
  end
@@ -1,3 +1,3 @@
1
1
  module PandaPal
2
- VERSION = "4.1.0.beta3"
2
+ VERSION = "5.0.0"
3
3
  end
@@ -22,9 +22,6 @@ Gem::Specification.new do |s|
22
22
  s.add_dependency 'browser', '2.5.0'
23
23
  s.add_dependency 'attr_encrypted', '~> 3.0.0'
24
24
  s.add_dependency 'secure_headers', '~> 6.1.2'
25
-
26
- s.add_development_dependency 'sidekiq'
27
- s.add_development_dependency 'sidekiq-scheduler'
28
25
  s.add_development_dependency 'rspec-rails'
29
26
  s.add_development_dependency 'factory_girl_rails'
30
27
  end
@@ -1,12 +1,6 @@
1
1
  require File.expand_path('../boot', __FILE__)
2
2
 
3
- require "active_model/railtie"
4
- require "active_job/railtie"
5
- require "active_record/railtie"
6
- # require "active_storage/engine"
7
- require "action_controller/railtie"
8
- require "action_mailer/railtie"
9
- require "action_view/railtie"
3
+ require 'rails/all'
10
4
 
11
5
  Bundler.require(*Rails.groups)
12
6
  require "panda_pal"
@@ -22,6 +22,20 @@ Rails.application.configure do
22
22
  # Raise an error on page load if there are pending migrations.
23
23
  config.active_record.migration_error = :page_load
24
24
 
25
+ # Debug mode disables concatenation and preprocessing of assets.
26
+ # This option may cause significant delays in view rendering with a large
27
+ # number of complex assets.
28
+ config.assets.debug = true
29
+
30
+ # Asset digests allow you to set far-future HTTP expiration dates on all assets,
31
+ # yet still be able to expire them through the digest params.
32
+ config.assets.digest = true
33
+
34
+ # Adds additional error checking when serving assets at runtime.
35
+ # Checks for improperly declared sprockets dependencies.
36
+ # Raises helpful error messages.
37
+ config.assets.raise_runtime_errors = true
38
+
25
39
  # Raises error for missing translations
26
40
  # config.action_view.raise_on_missing_translations = true
27
41
  end
@@ -24,6 +24,17 @@ Rails.application.configure do
24
24
  # Apache or NGINX already handles this.
25
25
  config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
26
26
 
27
+ # Compress JavaScripts and CSS.
28
+ config.assets.js_compressor = :uglifier
29
+ # config.assets.css_compressor = :sass
30
+
31
+ # Do not fallback to assets pipeline if a precompiled asset is missed.
32
+ config.assets.compile = false
33
+
34
+ # Asset digests allow you to set far-future HTTP expiration dates on all assets,
35
+ # yet still be able to expire them through the digest params.
36
+ config.assets.digest = true
37
+
27
38
  # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
28
39
 
29
40
  # Specifies the header that your server uses for sending files.
@@ -0,0 +1,11 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Version of your assets, change this if you want to expire all your assets.
4
+ Rails.application.config.assets.version = '1.0'
5
+
6
+ # Add additional assets to the asset load path
7
+ # Rails.application.config.assets.paths << Emoji.images_path
8
+
9
+ # Precompile additional assets.
10
+ # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
11
+ # Rails.application.config.assets.precompile += %w( search.js )