panda_pal 5.0.0.beta.1 → 5.1.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
2
  SHA256:
3
- metadata.gz: 417de10d63af26bbeadb9b1fa5d57f74fad8070dec477ecf42f7ba914c46be4e
4
- data.tar.gz: '0268833a242fda47935ec01207644c3a74daaa31b37b311401ac216b9e34f9bf'
3
+ metadata.gz: 8a2c4fed3544a682a44b4e9acfc30742ab7f7bcdf2ec22db97d90df80af31e04
4
+ data.tar.gz: f0ee1ba262458c9a5fee095c17c1c673e58e90cc821ea92d29b59cee2afcd1e4
5
5
  SHA512:
6
- metadata.gz: 1093764dca3b51778eb98cf8c8ce64bc88af73fb970e9f61597c3f71c92761729ad636ea156da7f2aad7d656ca2f35311f4cce72a338c3eb49f8cb85b788f229
7
- data.tar.gz: ee960dc3a06b1da790af9ce3f9a7e6bb76bb06e75a2645097654833226ed84f1f9655455c396607b74981879bed44029147b55ead6efc6437b4abe1c9feb4699
6
+ metadata.gz: adcb31a168a7c5eb09c5ac4ac4c28de5f45ba480873210e309bf5ba60c4c29b4f172648ed03ccee54cfc0e585eac0a25c56be3686dadf582d11e8b95c24a2870
7
+ data.tar.gz: b06936171d9f3424d2de2cac678726e33d081084a0a99195c6c6d4b7d4286d0a88bb91c9e1ea269ccccbb3f3ea9698a60096b3b7baf8b162f5db751f7b45521c
data/README.md CHANGED
@@ -28,6 +28,40 @@ 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/panda_pal/organization.rb
35
+ require File.expand_path('../../app/models/panda_pal/organization.rb', PandaPal::Engine.called_from)
36
+
37
+ module PandaPal
38
+ class Organization
39
+ # Will invoke CanvasSyncStarterWorker.perform_async() according to the cron schedule
40
+ scheduled_task '0 15 05 * * *', :identifier, worker: CanvasSyncStarterWorker
41
+
42
+ # Will invoke the method 'organization_method' on the Organization
43
+ scheduled_task '0 15 05 * * *', :organization_method_and_identifier
44
+
45
+ # If you need to invoke the same method on multiple schedules
46
+ scheduled_task '0 15 05 * * *', :identifier, worker: :organization_method
47
+
48
+ # You can also use a block
49
+ scheduled_task '0 15 05 * * *', :identifier do
50
+ # Do Stuff
51
+ end
52
+
53
+ # You can use a Proc (called in the context of the Organization) to determine the schedule
54
+ scheduled_task -> { settings[:cron] }, :identifier
55
+
56
+ # You can specify a timezone. If a TZ is not coded and settings[:timezone] is present, it will be appended automatically
57
+ scheduled_task '0 15 05 * * * America/Denver', :identifier, worker: :organization_method
58
+
59
+ # Setting settings[:task_schedules][:identifier] will override the code cron schedule. Setting it to false will disable the Task
60
+ # :identifer values _must_ be unique, but can be nil, in which case they will be determined by where (lineno etc) scheduled_task is called
61
+ end
62
+ end
63
+ ```
64
+
31
65
  # Organization Attributes
32
66
  id: Primary Key
33
67
  name: Name of the organization. Used to on requests to select the tenant
@@ -226,9 +260,43 @@ In your panda_pal initializer (i.e. config/initializers/panda_pal.rb or config/i
226
260
  You can specify options that can include a structure for your settings. If specified, PandaPal will
227
261
  enforce this structure on any new / updated organizations.
228
262
 
263
+ ```ruby
264
+ PandaPal.lti_options = {
265
+ title: 'LBS Gradebook',
266
+ settings_structure: {
267
+ allow_additional: true, # Allow additional properties that aren't included in the :properties Hash.
268
+ allow_additional: { type: 'String' }, # You can also set :allow_additional to a settings specification that will be used to validate each additional setting
269
+ validate: ->(value, spec, **kwargs) {
270
+ # kwargs currently includes:
271
+ # :errors => [An array to push errors to]
272
+ # :path => [An array representation of the current path in the settings object]
273
+
274
+ # To add errors, you may:
275
+ # Push strings to the kwargs[:errors] Array:
276
+ kwargs[:errors] << "Your error message at <path>" unless value < 10
277
+ # Or return a string or string array:
278
+ value.valid? ? nil : "Your error message at <path>" # <path> will be replaced with the actual path that the error occurred at
279
+ },
280
+ properties: {
281
+ canvas_api_token: { type: 'String', required: true, },
282
+ catalog: { # :validate, :allow_additional, :properties keys are all supported at this level as well
283
+ type: 'Hash',
284
+ required: false,
285
+ validate: -> (*args) {},
286
+ allow_additional: false,
287
+ properties: {
288
+
289
+ },
290
+ }
291
+ }
292
+ },
293
+ }
294
+ ```
295
+
296
+ #### Legacy Settings Structure:
229
297
  Here is an example options specification:
230
298
 
231
- ```
299
+ ```ruby
232
300
  PandaPal.lti_options = {
233
301
  title: 'LBS Gradebook',
234
302
  settings_structure: YAML.load("
@@ -287,7 +355,7 @@ This will allow `PandaPal` to apply an iframe cookie fix that will allow CSRF va
287
355
  It has been a constant struggle to force safari to store and allow
288
356
  access to a rails session while the application is embedded in Canvas.
289
357
 
290
- As of PandaPal 5, a forced persistent session is now optional, and defaults to off.
358
+ As of PandaPal 5, a persistent session is no longer required by panda_pal.
291
359
 
292
360
  This means that safari will likely refuse to send info about your rails session
293
361
  back to the LTI, and the application will start up a new session each time the
@@ -299,29 +367,8 @@ You will want to watch out for a few scenarios:
299
367
  and have your PandaPal session_key persisted server side.
300
368
  2) Use the "Authorization" header with "token={session_key}" to send your
301
369
  PandaPal session info into api calls.
302
-
303
- You can force a persistent session with -
304
- PandaPal.lti_options = {
305
- require_persistent_session: true
306
- }
307
- in your config/initializer. With that setting, the user will be required to
308
- allow our application to store / access cookies in safari before they can use
309
- the LTI.
310
-
311
- # Notes on require_persistent_session
312
-
313
- IF you must have a persistent session this is the logical flow of how panda_pal
314
- attempts to set that up.
315
-
316
- 1) LTI laumches.
317
- 2) LTI will attempt to POST message from iframe to top window (canvas) telling
318
- canvas to relaunch in full screen so we aren't inhibited by safari.
319
- 3) LTI will setup session and cookies in full-screen mode. Session will be saved
320
- in browser.
321
- 4) LTI will redirect to an authorization page, that will require user to give
322
- access to the session store to our application.
323
- 5) Once the user gives access to the session store, we will reload the LTI
324
- and the cookie should now be persistent.
370
+ 3) If you use link_to and navigate in your LTI (apps that are not single page)
371
+ make sure you include an encrypted_session_key parameter in your links.
325
372
 
326
373
  # Upgrading from PandaPal 4 to 5:
327
374
 
@@ -361,3 +408,7 @@ class AccountController < ApplicationController
361
408
  end
362
409
  ```
363
410
 
411
+ ## Running Specs:
412
+ Initialize the Specs DB:
413
+ `cd spec/dummy; bundle exec rake db:drop; bundle exec rake db:create; bundle exec rake db:schema:load`
414
+ Then `bundle exec rspec`
@@ -1,15 +1,23 @@
1
+ Dir[File.dirname(__FILE__) + "/organization/*.rb"].each { |file| require file }
2
+
1
3
  module PandaPal
4
+ module OrganizationConcerns; end
2
5
 
3
6
  class Organization < ActiveRecord::Base
7
+ include OrganizationConcerns::SettingsValidation
8
+ include OrganizationConcerns::TaskScheduling if defined?(Sidekiq.schedule)
9
+
4
10
  attribute :settings
11
+ serialize :settings, Hash
5
12
  attr_encrypted :settings, marshal: true, key: :encryption_key
6
13
  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
+
7
15
  validates :key, uniqueness: { case_sensitive: false }, presence: true
8
16
  validates :secret, presence: true
9
17
  validates :name, uniqueness: { case_sensitive: false }, presence: true, format: { with: /\A[a-z0-9_]+\z/i }
10
18
  validates :canvas_account_id, presence: true
11
19
  validates :salesforce_id, presence: true, uniqueness: true
12
- validate :validate_settings
20
+
13
21
  after_create :create_schema
14
22
  after_commit :destroy_schema, on: :destroy
15
23
 
@@ -17,8 +25,6 @@ module PandaPal
17
25
  errors.add(:name, 'should not be changed after creation') if name_changed?
18
26
  end
19
27
 
20
- serialize :settings, Hash
21
-
22
28
  def encryption_key
23
29
  # production environment might not have loaded secret_key_base yet.
24
30
  # In that case, just read it from env.
@@ -29,6 +35,14 @@ module PandaPal
29
35
  end
30
36
  end
31
37
 
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
+
32
46
  private
33
47
 
34
48
  def create_schema
@@ -38,48 +52,5 @@ module PandaPal
38
52
  def destroy_schema
39
53
  Apartment::Tenant.drop name
40
54
  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
84
55
  end
85
56
  end
@@ -0,0 +1,111 @@
1
+ module PandaPal
2
+ module OrganizationConcerns
3
+ module SettingsValidation
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ validate :validate_settings
8
+ end
9
+
10
+ class_methods do
11
+ def settings_structure
12
+ if PandaPal.lti_options&.[](:settings_structure).present?
13
+ normalize_settings_structure(PandaPal.lti_options[:settings_structure])
14
+ else
15
+ {
16
+ type: Hash,
17
+ allow_additional: true,
18
+ properties: {},
19
+ }
20
+ end
21
+ end
22
+
23
+ def normalize_settings_structure(struc)
24
+ return {} unless struc.present?
25
+ return struc if struc[:properties] || struc[:type] || struc.key?(:required)
26
+
27
+ struc = struc.dup
28
+ nstruc = {}
29
+
30
+ nstruc[:type] = struc.delete(:data_type) if struc.key?(:data_type)
31
+ nstruc[:required] = struc.delete(:is_required) if struc.key?(:is_required)
32
+ nstruc[:properties] = struc.map { |k, sub| [k, normalize_settings_structure(sub)] }.to_h if struc.present?
33
+
34
+ nstruc
35
+ end
36
+ end
37
+
38
+ def settings_structure
39
+ self.class.settings_structure
40
+ end
41
+
42
+ def validate_settings
43
+ validate_settings_level(settings || {}, settings_structure).each do |err|
44
+ errors[:settings] << err
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def validate_settings_level(settings, spec, path: [], errors: [])
51
+ human_path = "[:#{path.join('][:')}]"
52
+
53
+ if settings.nil?
54
+ errors << "Entry #{human_path} is required" if spec[:required]
55
+ return
56
+ end
57
+
58
+ if spec[:type]
59
+ resolved_type = spec[:type]
60
+ resolved_type = resolved_type.constantize if resolved_type.is_a?(String)
61
+ unless settings.is_a?(resolved_type)
62
+ errors << "Expected #{human_path} to be a #{spec[:type]}. Was a #{settings.class.to_s}"
63
+ return
64
+ end
65
+ end
66
+
67
+ if spec[:validate].present?
68
+ val_errors = []
69
+ if spec[:validate].is_a?(Symbol)
70
+ proc_result = send(spec[:validate], settings, spec, path: path, errors: val_errors)
71
+ elsif spec[:validate].is_a?(String)
72
+ split_val = spec[:validate].split?('.')
73
+ split_val << 'validate_settings' if split_val.count == 1
74
+ resolved_module = split_val[0].constantize
75
+ proc_result = resolved_module.send(split_val[1].to_sym, settings, spec, path: path, errors: val_errors)
76
+ elsif spec[:validate].is_a?(Proc)
77
+ proc_result = instance_exec(settings, spec, path: path, errors: val_errors, &spec[:validate])
78
+ end
79
+ val_errors << proc_result unless val_errors.present? || proc_result == val_errors
80
+ val_errors = val_errors.flatten.uniq.compact.map do |ve|
81
+ ve.gsub('<path>', human_path)
82
+ end
83
+ errors.concat(val_errors)
84
+ end
85
+
86
+ if settings.is_a?(Hash)
87
+ if spec[:properties] != nil
88
+ spec[:properties].each do |key, pspec|
89
+ validate_settings_level(settings[key], pspec, path: [*path, key], errors: errors)
90
+ end
91
+ end
92
+
93
+ if spec[:properties] != nil || spec[:allow_additional] != nil
94
+ extra_keys = settings.keys - (spec[:properties]&.keys || [])
95
+ if extra_keys.present?
96
+ if spec[:allow_additional].is_a?(Hash)
97
+ extra_keys.each do |key|
98
+ validate_settings_level(settings[key], spec[:allow_additional], path: [*path, key], errors: errors)
99
+ end
100
+ elsif !spec[:allow_additional]
101
+ errors << "Did not expect #{human_path} to contain [#{extra_keys.join(', ')}]"
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ errors
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,172 @@
1
+ return unless defined?(Sidekiq.schedule)
2
+
3
+ require_relative 'settings_validation'
4
+
5
+ module PandaPal
6
+ module OrganizationConcerns
7
+ module TaskScheduling
8
+ extend ActiveSupport::Concern
9
+ include OrganizationConcerns::SettingsValidation
10
+
11
+ included do
12
+ after_commit :sync_schedule, on: [:create, :update]
13
+ after_commit :unschedule_tasks, on: :destroy
14
+ end
15
+
16
+ class_methods do
17
+ def _schedule_descriptors
18
+ @_schedule_descriptors ||= {}
19
+ end
20
+
21
+ def settings_structure
22
+ return super unless _schedule_descriptors.present?
23
+
24
+ super.tap do |struc|
25
+ struc[:properties] ||= {}
26
+
27
+ struc[:properties][:timezone] ||= {
28
+ type: 'String',
29
+ required: false,
30
+ validate: ->(timezone, *args) {
31
+ ActiveSupport::TimeZone[timezone].present? ? nil : "<path> Invalid Timezone '#{timezone}'"
32
+ },
33
+ }
34
+
35
+ struc[:properties][:task_schedules] = {
36
+ type: 'Hash',
37
+ required: false,
38
+ properties: _schedule_descriptors.keys.reduce({}) do |hash, k|
39
+ hash.tap do |hash|
40
+ hash[k.to_sym] = hash[k.to_s] = {
41
+ required: false,
42
+ validate: ->(value, *args, errors:, **kwargs) {
43
+ begin
44
+ Rufus::Scheduler.parse(value) if value
45
+ nil
46
+ rescue ArgumentError
47
+ errors << "<path> must be false or a Crontab string"
48
+ end
49
+ }
50
+ }
51
+ end
52
+ end,
53
+ }
54
+ end
55
+ end
56
+
57
+ def scheduled_task(cron_time, name_or_method = nil, worker: nil, queue: nil, &block)
58
+ task_key = (name_or_method.presence || "scheduled_task_#{caller_locations[0].lineno}").to_s
59
+ raise "Task key '#{task_key}' already taken!" if _schedule_descriptors.key?(task_key)
60
+
61
+ _schedule_descriptors[task_key] = {
62
+ key: task_key,
63
+ schedule: cron_time,
64
+ worker: worker || block || name_or_method.to_sym,
65
+ queue: queue || 'default',
66
+ }
67
+ end
68
+
69
+ def sync_schedules
70
+ # Ensure deleted Orgs are removed
71
+ existing_orgs = pluck(:name)
72
+ old_schedules = Sidekiq.get_schedule.select do |k, v|
73
+ m = k.match(/^org:([a-z0-9_]+)\-/i)
74
+ m.present? && !existing_orgs.include?(m[1])
75
+ end
76
+ old_schedules.keys.each do |k|
77
+ Sidekiq.remove_schedule(k)
78
+ end
79
+
80
+ find_each(&:sync_schedule)
81
+ end
82
+ end
83
+
84
+ def generate_schedule
85
+ schedule = {}
86
+ self.class._schedule_descriptors.values.each do |desc|
87
+ cron_time = schedule_task_cron_time(desc)
88
+ next unless cron_time.present?
89
+
90
+ schedule["org:#{name}-#{desc[:key]}"] = {
91
+ 'cron' => cron_time,
92
+ 'queue' => desc[:queue],
93
+ 'class' => ScheduledTaskExecutor.to_s,
94
+ 'args' => [name, desc[:key]],
95
+ }
96
+ end
97
+ schedule
98
+ end
99
+
100
+ def sync_schedule
101
+ new_schedules = generate_schedule
102
+ unschedule_tasks(new_schedules.keys)
103
+ new_schedules.each do |k, v|
104
+ Sidekiq.set_schedule(k, v)
105
+ end
106
+ end
107
+
108
+ private
109
+
110
+ def unschedule_tasks(new_task_keys = nil)
111
+ current_schedules = Sidekiq.get_schedule.select { |k,v| k.starts_with?("org:#{name}-") }
112
+ del_tasks = current_schedules.keys
113
+ del_tasks -= new_task_keys if new_task_keys
114
+ del_tasks.each do |k|
115
+ Sidekiq.remove_schedule(k)
116
+ end
117
+ end
118
+
119
+ def schedule_task_cron_time(desc)
120
+ cron_time = nil
121
+ cron_time = settings&.dig(:task_schedules, desc[:key].to_s) if cron_time.nil?
122
+ cron_time = settings&.dig(:task_schedules, desc[:key].to_sym) if cron_time.nil?
123
+ cron_time = desc[:schedule] if cron_time.nil?
124
+
125
+ return nil unless cron_time.present?
126
+
127
+ cron_time = instance_exec(&cron_time) if cron_time.is_a?(Proc)
128
+ if !Rufus::Scheduler.parse(cron_time).zone.present? && settings && settings[:timezone]
129
+ cron_time += " #{settings[:timezone]}"
130
+ end
131
+
132
+ cron_time
133
+ end
134
+
135
+ class ScheduledTaskExecutor
136
+ include Sidekiq::Worker
137
+
138
+ def perform(org_name, task_key)
139
+ org = Organization.find_by!(name: org_name)
140
+ task = Organization._schedule_descriptors[task_key]
141
+ worker = task[:worker]
142
+
143
+ Apartment::Tenant.switch(org.name) do
144
+ if worker.is_a?(Proc)
145
+ org.instance_exec(&worker)
146
+ elsif worker.is_a?(Symbol)
147
+ org.send(worker)
148
+ elsif worker.is_a?(String)
149
+ worker.constantize.perform_async
150
+ elsif worker.is_a?(Class)
151
+ worker.perform_async
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+
160
+ SidekiqScheduler::Scheduler.instance.dynamic = true
161
+
162
+ module SidekiqScheduler
163
+ module Schedule
164
+ original_schedule_setter = instance_method(:schedule=)
165
+
166
+ define_method :schedule= do |sched|
167
+ original_schedule_setter.bind(self).(sched).tap do
168
+ PandaPal::Organization.sync_schedules
169
+ end
170
+ end
171
+ end
172
+ end
@@ -42,29 +42,9 @@ 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 require_persistent_session
46
- if cookies_need_iframe_fix?(false)
47
- fix_iframe_cookies
48
- return false
49
- end
50
- # For safari we may have been launched temporarily full-screen by canvas. This allows us to set the session cookie.
51
- # In this case, we should make sure the session cookie is fixed and redirect back to canvas to properly launch the embedded LTI.
52
- if params[:platform_redirect_url]
53
- session[:safari_cookie_fixed] = true
54
- redirect_to params[:platform_redirect_url]
55
- return false
56
- end
57
- end
58
45
  return authorized
59
46
  end
60
47
 
61
- def require_persistent_session
62
- if PandaPal.lti_options.has_key?(:require_persistent_session) && PandaPal.lti_options[:require_persistent_session] == true
63
- return true
64
- end
65
- return false
66
- end
67
-
68
48
  def switch_tenant(organization = current_organization, &block)
69
49
  return unless organization
70
50
  raise 'This method should be called in an around_action callback' unless block_given?
@@ -74,35 +54,8 @@ module PandaPal::Helpers::ControllerHelper
74
54
  end
75
55
  end
76
56
 
77
- # Browsers that prevent 3rd party cookies by default (Safari and IE) run into problems
78
- # with CSRF handling because the Rails session cookie isn't set. To fix this, we
79
- # redirect the current page to the LTI using JavaScript, which will set the cookie,
80
- # and then immediately redirect back to Canvas.
81
- def fix_iframe_cookies
82
- if params[:safari_cookie_authorized].present?
83
- session[:safari_cookie_authorized] = true
84
- redirect_to params[:return_to]
85
- elsif (session[:safari_cookie_fixed] && !params[:safari_cookie_authorized])
86
- render 'panda_pal/lti/iframe_cookie_authorize', layout: false
87
- else
88
- render 'panda_pal/lti/iframe_cookie_fix', layout: false
89
- end
90
- end
91
-
92
- def cookies_need_iframe_fix?(check_authorized=true)
93
- if check_authorized
94
- return browser.safari? && !request.referrer&.include?('sessionless_launch') && !(session[:safari_cookie_fixed] && session[:safari_cookie_authorized]) && !params[:platform_redirect_url]
95
- else
96
- return browser.safari? && !request.referrer&.include?('sessionless_launch') && !session[:safari_cookie_fixed] && !params[:platform_redirect_url]
97
- end
98
- end
99
-
100
57
  def forbid_access_if_lacking_session
101
- if require_persistent_session && cookies_need_iframe_fix?(true)
102
- fix_iframe_cookies
103
- else
104
- render plain: 'You should do an LTI Tool Launch.', status: :unauthorized unless valid_session?
105
- end
58
+ render plain: 'You should do an LTI Tool Launch.', status: :unauthorized unless valid_session?
106
59
  safari_override
107
60
  end
108
61
 
@@ -129,6 +82,10 @@ module PandaPal::Helpers::ControllerHelper
129
82
  end
130
83
 
131
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
132
89
  params[:session_key] || session_key_header || flash[:session_key] || session[:session_key]
133
90
  end
134
91
 
@@ -139,7 +96,8 @@ module PandaPal::Helpers::ControllerHelper
139
96
  end
140
97
 
141
98
  # Redirect with the session key intact. In production,
142
- # handle this by saving it to the flash. In dev,
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,
143
101
  # just put it in the URL. Putting it in the URL
144
102
  # is insecure, but is fine in development.
145
103
  # Keeping it in the URL in development means that it plays
@@ -162,8 +120,10 @@ module PandaPal::Helpers::ControllerHelper
162
120
  end
163
121
 
164
122
  def redirect_production_mode(location, params)
165
- flash['session_key'] = current_session.session_key
123
+ crypt = ActiveSupport::MessageEncryptor.new(Rails.application.secret_key_base[0..31])
124
+ encrypted_data = crypt.encrypt_and_sign(current_session.session_key)
166
125
  redirect_to send(location, {
126
+ encrypted_session_key: encrypted_data,
167
127
  organization_id: current_organization.id
168
128
  }.merge(params))
169
129
  end
@@ -1,3 +1,3 @@
1
1
  module PandaPal
2
- VERSION = "5.0.0.beta.1"
2
+ VERSION = "5.1.0"
3
3
  end
@@ -22,6 +22,9 @@ 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'
25
28
  s.add_development_dependency 'rspec-rails'
26
29
  s.add_development_dependency 'factory_girl_rails'
27
30
  end
@@ -1,6 +1,12 @@
1
1
  require File.expand_path('../boot', __FILE__)
2
2
 
3
- require 'rails/all'
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"
4
10
 
5
11
  Bundler.require(*Rails.groups)
6
12
  require "panda_pal"
@@ -22,20 +22,6 @@ 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
-
39
25
  # Raises error for missing translations
40
26
  # config.action_view.raise_on_missing_translations = true
41
27
  end
@@ -24,17 +24,6 @@ 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
-
38
27
  # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
39
28
 
40
29
  # Specifies the header that your server uses for sending files.
@@ -0,0 +1,175 @@
1
+ require 'rails_helper'
2
+
3
+ module PandaPal
4
+ PandaPal::Organization
5
+ RSpec.describe OrganizationConcerns::SettingsValidation, type: :model do
6
+ let!(:org) { create :panda_pal_organization }
7
+
8
+ def set_test_settings_structure
9
+ PandaPal.lti_options = {
10
+ title: 'Test App',
11
+ settings_structure: structure,
12
+ }
13
+ end
14
+
15
+ RSpec.shared_examples "shared stuff" do
16
+ it 'does not perform any validations if settings is not defined' do
17
+ PandaPal.lti_options = {}
18
+ expect(org.valid?).to be_truthy
19
+ end
20
+
21
+ it 'does not perform any validations if options is not defined' do
22
+ PandaPal.lti_options = nil
23
+ expect(org.valid?).to be_truthy
24
+ end
25
+
26
+ it 'does perform validations if settings_structure is defined' do
27
+ set_test_settings_structure
28
+ org.valid?
29
+ expect(org.valid?).to be_falsey
30
+ end
31
+
32
+ it 'will fail if a required setting is not present' do
33
+ set_test_settings_structure
34
+ expect(org.valid?).to be_falsey
35
+ errors = org.errors.messages[:settings]
36
+ expect(errors[0]).to eq("Entry [:canvas] is required")
37
+ end
38
+
39
+ it 'will fail if a setting is supplied but data_type is wrong' do
40
+ set_test_settings_structure
41
+ org.settings = {canvas: "Dog", reports: {}}
42
+ expect(org.valid?).to be_falsey
43
+ errors = org.errors.messages[:settings]
44
+ expect(errors[0]).to eq("Expected [:canvas] to be a Hash. Was a String")
45
+ end
46
+
47
+ it 'will fail if a required subsetting is missing' do
48
+ set_test_settings_structure
49
+ org.settings = {canvas: {base_url: 'http://'}, reports: {}}
50
+ expect(org.valid?).to be_falsey
51
+ errors = org.errors.messages[:settings]
52
+ expect(errors[0]).to eq("Entry [:canvas][:api_token] is required")
53
+ end
54
+
55
+ it 'will fail if extra options are specified' do
56
+ set_test_settings_structure
57
+ org.settings = {canvas: {base_url: 'http://', api_token: 'TEST'}, reports: {}, unknown_option: "WHAT IS THIS?"}
58
+ expect(org.valid?).to be_falsey
59
+ errors = org.errors.messages[:settings]
60
+ expect(errors[0]).to eq("Did not expect [:] to contain [unknown_option]")
61
+ end
62
+
63
+ it 'will pass if all structure is maintained' do
64
+ set_test_settings_structure
65
+ org.settings = {canvas: {base_url: 'http://', api_token: 'TEST'}, reports: {submissions_report_time_length: 30.minutes, max_recheck_time: 10.hours}}
66
+ expect(org.valid?).to be_truthy
67
+ end
68
+ end
69
+
70
+ context 'new settings_structure' do
71
+ let!(:properties) do
72
+ {
73
+ canvas: {
74
+ required: true,
75
+ type: 'Hash',
76
+ properties: {
77
+ api_token: { type: 'String', required: true },
78
+ base_url: { type: 'String', required: true },
79
+ },
80
+ },
81
+ reports: {
82
+ required: true,
83
+ type: 'Hash',
84
+ allow_additional: true,
85
+ }
86
+ }
87
+ end
88
+
89
+ let!(:structure) do
90
+ {
91
+ properties: properties,
92
+ }
93
+ end
94
+
95
+ it_behaves_like("shared stuff")
96
+
97
+ describe ':allow_additional' do
98
+ it 'passes extra properties if allow_additional is a matching Hash' do
99
+ structure[:allow_additional] = { type: 'String' }
100
+ set_test_settings_structure
101
+ org.settings = {canvas: {base_url: 'http://', api_token: 'TEST'}, reports: {}, unknown_option: "WHAT IS THIS?"}
102
+ expect(org).to be_valid
103
+ end
104
+
105
+ it 'fails extra properties if allow_additional is an unmatching Hash' do
106
+ structure[:allow_additional] = { type: 'Hash' }
107
+ set_test_settings_structure
108
+ org.settings = {canvas: {base_url: 'http://', api_token: 'TEST'}, reports: {}, unknown_option: "WHAT IS THIS?"}
109
+ expect(org).not_to be_valid
110
+ end
111
+
112
+ it 'passes extra properties if allow_additional is a blank Hash' do
113
+ structure[:allow_additional] = { }
114
+ set_test_settings_structure
115
+ org.settings = {canvas: {base_url: 'http://', api_token: 'TEST'}, reports: {}, unknown_option: "WHAT IS THIS?"}
116
+ expect(org).to be_valid
117
+ end
118
+ end
119
+
120
+ describe ':validate' do
121
+ let!(:properties) do
122
+ {
123
+ blah: {
124
+ type: 'String',
125
+ validate: -> (v, *args) { v == 'blah' ? nil : '<path> failed validation' },
126
+ }
127
+ }
128
+ end
129
+
130
+ it 'supports a custom validation proc' do
131
+ set_test_settings_structure
132
+ org.settings = { blah: 'blah' }
133
+ expect(org).to be_valid
134
+ end
135
+
136
+ it 'replaces <path> with the human readable path' do
137
+ set_test_settings_structure
138
+ org.settings = { blah: '' }
139
+ expect(org.valid?).to be_falsey
140
+ errors = org.errors.messages[:settings]
141
+ expect(errors[0]).to eq("[:blah] failed validation")
142
+ end
143
+ end
144
+ end
145
+
146
+ context 'old settings_structure' do
147
+ let!(:structure) do
148
+ YAML.load("
149
+ canvas:
150
+ is_required: true
151
+ data_type: Hash
152
+ api_token:
153
+ is_required: true
154
+ data_type: String
155
+ base_url:
156
+ is_required: true
157
+ data_type: String
158
+ reports:
159
+ is_required: true
160
+ data_type: Hash
161
+ active_term_allowance:
162
+ submissions_report_time_length:
163
+ is_required: false
164
+ data_type: ActiveSupport::Duration
165
+ recheck_wait:
166
+ data_type: ActiveSupport::Duration
167
+ max_recheck_time:
168
+ is_required: false
169
+ ").deep_symbolize_keys
170
+ end
171
+
172
+ it_behaves_like("shared stuff")
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,144 @@
1
+ require 'rails_helper'
2
+
3
+ module PandaPal
4
+ PandaPal::Organization
5
+
6
+ RSpec.describe OrganizationConcerns::TaskScheduling, type: :model do
7
+ let!(:organization) { create(:panda_pal_organization) }
8
+ let(:schedules) { {} }
9
+
10
+ before :each do
11
+ Organization.instance_variable_set(:@_schedule_descriptors, nil)
12
+ Organization.scheduled_task('0 0 0 * * *', :ident) { }
13
+
14
+ allow(Sidekiq).to receive(:remove_schedule)
15
+ allow(Sidekiq).to receive(:get_schedule) { schedules }
16
+ end
17
+
18
+ def descriptors
19
+ Organization.instance_variable_get(:@_schedule_descriptors)
20
+ end
21
+
22
+ def descriptor
23
+ descriptors[descriptors.keys[0]]
24
+ end
25
+
26
+ describe '.scheduled_task' do
27
+ it 'adds to the set of descriptors' do
28
+ expect(descriptors).to match(
29
+ 'ident' => {
30
+ :key=>"ident",
31
+ :schedule=>"0 0 0 * * *",
32
+ :worker=>Proc,
33
+ :queue=>"default",
34
+ }
35
+ )
36
+ end
37
+ end
38
+
39
+ describe '.sync_schedules' do
40
+ it 'adds new schedules' do
41
+ expect(Sidekiq).to receive(:set_schedule).with(/org:\w+-ident/, anything)
42
+ Organization.sync_schedules
43
+ end
44
+
45
+ it 'removes schedules for deleted Orgs' do
46
+ schedules['org:deleted_org-schedule'] = {}
47
+ expect(Sidekiq).to receive(:remove_schedule).with('org:deleted_org-schedule')
48
+ Organization.sync_schedules
49
+ end
50
+
51
+ it 'keeps schedules created from other sources' do
52
+ schedules['other-schedule'] = {}
53
+ expect(Sidekiq).not_to receive(:remove_schedule).with('other-schedule')
54
+ Organization.sync_schedules
55
+ end
56
+ end
57
+
58
+ describe '#generate_schedule' do
59
+ it 'generates the expected schedule' do
60
+ expect(organization.generate_schedule).to eq({
61
+ "org:#{organization.name}-ident" => {
62
+ "cron"=>"0 0 0 * * *",
63
+ "queue"=>"default",
64
+ "class"=>"PandaPal::OrganizationConcerns::TaskScheduling::ScheduledTaskExecutor",
65
+ "args"=>[organization.name, "ident"],
66
+ }
67
+ })
68
+ end
69
+ end
70
+
71
+ describe '#schedule_task_cron_time' do
72
+ before :each do
73
+ organization.settings = { timezone: 'America/Denver' }
74
+ end
75
+
76
+ it 'includes timezone if in settings' do
77
+ expect(organization.send(:schedule_task_cron_time, descriptor)).to eq '0 0 0 * * * America/Denver'
78
+ end
79
+
80
+ it 'does not re-append timezone if already present' do
81
+ descriptor[:schedule] = '1 1 1 * * * America/Chicago'
82
+ expect(organization.send(:schedule_task_cron_time, descriptor)).to eq '1 1 1 * * * America/Chicago'
83
+ end
84
+ end
85
+
86
+ describe '#sync_schedule' do
87
+ it 'adds new schedules' do
88
+ expect(Sidekiq).to receive(:set_schedule).with(/org:\w+-ident/, anything)
89
+ organization.sync_schedule
90
+ end
91
+
92
+ it 'removes old jobs' do
93
+ schedules["org:#{organization.name}-old_schedule"] = {}
94
+ expect(Sidekiq).to receive(:remove_schedule).with("org:#{organization.name}-old_schedule")
95
+ organization.sync_schedule
96
+ end
97
+ end
98
+
99
+ describe 'SettingsValidation' do
100
+ before :each do
101
+ organization.settings = {}
102
+ end
103
+
104
+ it 'passes a valid timezone' do
105
+ organization.settings[:timezone] = 'America/Denver'
106
+ expect(organization).to be_valid
107
+ end
108
+
109
+ it 'fails an invalid timezone' do
110
+ organization.settings[:timezone] = 'Timezone/Blorg'
111
+ expect(organization).not_to be_valid
112
+ end
113
+
114
+ it 'allows [:task_schedules] entry to be missing' do
115
+ expect(organization).to be_valid
116
+ end
117
+
118
+ it 'does not require [:task_schedules] sub-entries' do
119
+ organization.settings[:task_schedules] = {}
120
+ expect(organization).to be_valid
121
+ end
122
+
123
+ it 'allows task entry to be false' do
124
+ organization.settings[:task_schedules] = { ident: false }
125
+ expect(organization).to be_valid
126
+ end
127
+
128
+ it 'allows task entry to be a valid cron string' do
129
+ organization.settings[:task_schedules] = { ident: '0 0 0 * * * America/Denver' }
130
+ expect(organization).to be_valid
131
+ end
132
+
133
+ it 'does not allow task entry to be an invalid cron string' do
134
+ organization.settings[:task_schedules] = { ident: 'blort' }
135
+ expect(organization).not_to be_valid
136
+ end
137
+
138
+ it 'does not allow sub-entries for unknown tasks' do
139
+ organization.settings[:task_schedules] = { missing_ident: '0 0 0 * * * America/Denver' }
140
+ expect(organization).not_to be_valid
141
+ end
142
+ end
143
+ end
144
+ end
@@ -2,35 +2,6 @@ require 'rails_helper'
2
2
 
3
3
  module PandaPal
4
4
  RSpec.describe Organization, type: :model do
5
-
6
- def set_test_settings_structure
7
- PandaPal.lti_options = {
8
- title: 'Test App',
9
- settings_structure: YAML.load("
10
- canvas:
11
- is_required: true
12
- data_type: Hash
13
- api_token:
14
- is_required: true
15
- data_type: String
16
- base_url:
17
- is_required: true
18
- data_type: String
19
- reports:
20
- is_required: true
21
- data_type: Hash
22
- active_term_allowance:
23
- submissions_report_time_length:
24
- is_required: true
25
- data_type: ActiveSupport::Duration
26
- recheck_wait:
27
- data_type: ActiveSupport::Duration
28
- max_recheck_time:
29
- is_required: true
30
- ").deep_symbolize_keys
31
- }
32
- end
33
-
34
5
  it 'creates a schema upon creation' do
35
6
  expect(Apartment::Tenant).to receive(:create)
36
7
  create :panda_pal_organization
@@ -59,65 +30,5 @@ module PandaPal
59
30
  org2 = build :panda_pal_organization, salesforce_id: 'salesforce'
60
31
  expect(org2.valid?).to be_falsey
61
32
  end
62
-
63
- context 'settings validation' do
64
- let!(:org) { create :panda_pal_organization }
65
-
66
- it 'does not perform any validations if settings is not defined' do
67
- PandaPal.lti_options = {}
68
- expect_any_instance_of(PandaPal::Organization).not_to receive(:validate_level)
69
- expect(org.valid?).to be_truthy
70
- end
71
-
72
- it 'does not perform any validations if options is not defined' do
73
- PandaPal.lti_options = nil
74
- expect_any_instance_of(PandaPal::Organization).not_to receive(:validate_level)
75
- expect(org.valid?).to be_truthy
76
- end
77
-
78
- it 'does perform validations if settings_structure is defined' do
79
- set_test_settings_structure
80
- org.valid?
81
- expect(org.valid?).to be_falsey
82
- end
83
-
84
- it 'will fail if a required setting is not present' do
85
- set_test_settings_structure
86
- expect(org.valid?).to be_falsey
87
- errors = org.errors.messages[:settings]
88
- expect(errors[0]).to eq("PandaPal::Organization.settings requires key [:canvas]. It was not found.")
89
- end
90
-
91
- it 'will fail if a setting is supplied but data_type is wrong' do
92
- set_test_settings_structure
93
- org.settings = {canvas: "Dog"}
94
- expect(org.valid?).to be_falsey
95
- errors = org.errors.messages[:settings]
96
- expect(errors[0]).to eq("PandaPal::Organization.settings expected key [:canvas] to be Hash but it was instead String.")
97
- end
98
-
99
- it 'will fail if a required subsetting is missing' do
100
- set_test_settings_structure
101
- org.settings = {canvas: {base_url: 'http://'}, reports: {}}
102
- expect(org.valid?).to be_falsey
103
- errors = org.errors.messages[:settings]
104
- expect(errors[0]).to eq("PandaPal::Organization.settings requires key [:canvas][:api_token]. It was not found.")
105
- end
106
-
107
- it 'will fail if extra options are specified' do
108
- set_test_settings_structure
109
- org.settings = {canvas: {base_url: 'http://'}, reports: {}, unknown_option: "WHAT IS THIS?"}
110
- expect(org.valid?).to be_falsey
111
- errors = org.errors.messages[:settings]
112
- expect(errors[0]).to eq("PandaPal::Organization.settings had unexpected key: unknown_option. If settings have expanded please update your lti_options accordingly.")
113
- end
114
-
115
- it 'will pass if all structure is maintained' do
116
- set_test_settings_structure
117
- org.settings = {canvas: {base_url: 'http://', api_token: 'TEST'}, reports: {submissions_report_time_length: 30.minutes, max_recheck_time: 10.hours}}
118
- expect(org.valid?).to be_truthy
119
- end
120
-
121
- end
122
33
  end
123
34
  end
@@ -1,4 +1,8 @@
1
1
  ENV["RAILS_ENV"] ||= 'test'
2
+
3
+ require 'sidekiq/testing'
4
+ require 'sidekiq-scheduler'
5
+
2
6
  require File.expand_path("../dummy/config/environment.rb", __FILE__)
3
7
  require 'rspec/rails'
4
8
  require 'rspec/autorun'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: panda_pal
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.0.beta.1
4
+ version: 5.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Instructure ProServe
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-20 00:00:00.000000000 Z
11
+ date: 2020-06-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -114,6 +114,34 @@ dependencies:
114
114
  - - "~>"
115
115
  - !ruby/object:Gem::Version
116
116
  version: 6.1.2
117
+ - !ruby/object:Gem::Dependency
118
+ name: sidekiq
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ - !ruby/object:Gem::Dependency
132
+ name: sidekiq-scheduler
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
117
145
  - !ruby/object:Gem::Dependency
118
146
  name: rspec-rails
119
147
  requirement: !ruby/object:Gem::Requirement
@@ -163,10 +191,10 @@ files:
163
191
  - app/lib/lti_xml/bridge_platform.rb
164
192
  - app/lib/lti_xml/canvas_platform.rb
165
193
  - app/models/panda_pal/organization.rb
194
+ - app/models/panda_pal/organization/settings_validation.rb
195
+ - app/models/panda_pal/organization/task_scheduling.rb
166
196
  - app/models/panda_pal/session.rb
167
197
  - app/views/layouts/panda_pal/application.html.erb
168
- - app/views/panda_pal/lti/iframe_cookie_authorize.html.erb
169
- - app/views/panda_pal/lti/iframe_cookie_fix.html.erb
170
198
  - app/views/panda_pal/lti/launch.html.erb
171
199
  - config/initializers/apartment.rb
172
200
  - config/routes.rb
@@ -208,7 +236,6 @@ files:
208
236
  - spec/dummy/config/environments/development.rb
209
237
  - spec/dummy/config/environments/production.rb
210
238
  - spec/dummy/config/environments/test.rb
211
- - spec/dummy/config/initializers/assets.rb
212
239
  - spec/dummy/config/initializers/backtrace_silencers.rb
213
240
  - spec/dummy/config/initializers/cookies_serializer.rb
214
241
  - spec/dummy/config/initializers/filter_parameter_logging.rb
@@ -230,6 +257,8 @@ files:
230
257
  - spec/dummy/public/favicon.ico
231
258
  - spec/factories/panda_pal_organizations.rb
232
259
  - spec/factories/panda_pal_sessions.rb
260
+ - spec/models/panda_pal/organization/settings_validation_spec.rb
261
+ - spec/models/panda_pal/organization/task_scheduling_spec.rb
233
262
  - spec/models/panda_pal/organization_spec.rb
234
263
  - spec/models/panda_pal/session_spec.rb
235
264
  - spec/rails_helper.rb
@@ -249,9 +278,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
249
278
  version: '0'
250
279
  required_rubygems_version: !ruby/object:Gem::Requirement
251
280
  requirements:
252
- - - ">"
281
+ - - ">="
253
282
  - !ruby/object:Gem::Version
254
- version: 1.3.1
283
+ version: '0'
255
284
  requirements: []
256
285
  rubygems_version: 3.1.2
257
286
  signing_key:
@@ -283,7 +312,6 @@ test_files:
283
312
  - spec/dummy/config/initializers/filter_parameter_logging.rb
284
313
  - spec/dummy/config/initializers/session_store.rb
285
314
  - spec/dummy/config/initializers/wrap_parameters.rb
286
- - spec/dummy/config/initializers/assets.rb
287
315
  - spec/dummy/config/initializers/cookies_serializer.rb
288
316
  - spec/dummy/config/initializers/inflections.rb
289
317
  - spec/dummy/config.ru
@@ -298,6 +326,8 @@ test_files:
298
326
  - spec/dummy/log/test.log
299
327
  - spec/dummy/log/development.log
300
328
  - spec/dummy/README.rdoc
329
+ - spec/models/panda_pal/organization/settings_validation_spec.rb
330
+ - spec/models/panda_pal/organization/task_scheduling_spec.rb
301
331
  - spec/models/panda_pal/session_spec.rb
302
332
  - spec/models/panda_pal/organization_spec.rb
303
333
  - spec/factories/panda_pal_sessions.rb
@@ -1,19 +0,0 @@
1
- <html>
2
- <p>Safari requires your consent to access session information when applications are hosted inside of Canvas. Please consent by clicking the following button.</p>
3
- <button id="myButton">Authorize application to use browser session</button>
4
- <script nonce=<%= content_security_policy_script_nonce %>>
5
- function makeRequestWithUserGesture() {
6
- var promise = document.requestStorageAccess();
7
- promise.then(
8
- function () {
9
- var referrer = document.referrer;
10
- window.location='?safari_cookie_authorized=true&return_to='.concat(encodeURI(window.location));
11
- },
12
- function () {
13
- // If the user doesn't consent, then do nothing.
14
- }
15
- );
16
- }
17
- document.getElementById("myButton").addEventListener("click", makeRequestWithUserGesture);
18
- </script>
19
- </html>
@@ -1,12 +0,0 @@
1
- <script nonce=<%= content_security_policy_script_nonce %>>
2
- const mainWindow = window.parent;
3
- var url = window.location.href;
4
- // Until PLAT-4836 is resolved, we need to make sure our url has a "?" in it.
5
- if (!(url.indexOf("?") > -1)) {
6
- url = url + "?dummy_param=1"
7
- }
8
- mainWindow.postMessage({
9
- messageType: "requestFullWindowLaunch",
10
- data: url
11
- }, '*');
12
- </script>
@@ -1,11 +0,0 @@
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 )