panda_pal 4.1.0.beta3 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 )