panda_pal 5.3.10 → 5.4.0.beta2

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: 42e9ffbf3704935100156e28b5d3f2490fba75a5974b352f0092c14bb8b87ffb
4
- data.tar.gz: 5d511a85fe356dfe659ecaacfc7023315f649947ff483c6969e253802549e825
3
+ metadata.gz: 711da479bf7e495d72287d508109c520c7c0dd0fba08d67b4f746853ac20b535
4
+ data.tar.gz: d18ae002b72f96f88213245ca18fc4d756cbf896de87b623cb02d44686c2e1fd
5
5
  SHA512:
6
- metadata.gz: a4d2a7242e203c4c86866ff7efb3ba211c2b3b7f41463b776785371e06997470fdee569ed4e4ba0025a7eef6198bb067e1fdf71fc8b7c9307bf26c1d343de7db
7
- data.tar.gz: 7c3f0facf0767b7afea02ad0368bf6a0d79c8522109289e463a95719cd7f3762a34b5ad641499ff96871815f2db1ec76f192dd6221936d3af2586399a402bf1f
6
+ metadata.gz: 7257fd004deacbc44fdf45818a85cc5c8294587003679f84e9f144288ee75680bdd1478b55b63839e30d4f1df587d9f8c69261ad143aa327aab49a8370df8a70
7
+ data.tar.gz: b8d09741fc6743ec3a2eff3b8f4ba68f3205afd574dd17ed596fb331f6b98e31aba8e24a615a34f59a2fa498e07051fd5f2776ade4edea5f83544a3e5667546c
@@ -42,7 +42,7 @@ module LtiXml
42
42
  else
43
43
  ims_lti_config[:launch_url] = parsed_request_url.host
44
44
  end
45
- @tc = IMS::LTI::Services::ToolConfig.new(ims_lti_config)
45
+ @tc = IMS::LTI::ToolConfig.new(ims_lti_config)
46
46
  end
47
47
 
48
48
  def add_domain
@@ -1,3 +1,6 @@
1
+ require_relative './organization_concerns/settings_validation'
2
+ require_relative './organization_concerns/task_scheduling'
3
+
1
4
  module PandaPal
2
5
  module OrganizationConcerns; end
3
6
 
@@ -8,8 +8,16 @@ module PandaPal
8
8
  end
9
9
 
10
10
  class_methods do
11
+ def define_setting(*args, &blk)
12
+ @_injected_settings_definitions ||= []
13
+ @_injected_settings_definitions << {
14
+ args: args,
15
+ block: blk,
16
+ }
17
+ end
18
+
11
19
  def settings_structure
12
- if PandaPal.lti_options&.[](:settings_structure).present?
20
+ struc = if PandaPal.lti_options&.[](:settings_structure).present?
13
21
  normalize_settings_structure(PandaPal.lti_options[:settings_structure])
14
22
  else
15
23
  {
@@ -18,6 +26,32 @@ module PandaPal
18
26
  properties: {},
19
27
  }
20
28
  end
29
+
30
+ (@_injected_settings_definitions || []).each do |sub|
31
+ args = [*sub[:args]]
32
+ path = args.shift || []
33
+ path = path.split('.') if path.is_a?(String)
34
+ path = Array(path)
35
+
36
+ if path.present?
37
+ key = path.pop
38
+
39
+ root = struc
40
+ path.each do |p|
41
+ root = root[:properties][p.to_sym]
42
+ end
43
+
44
+ if sub[:block]
45
+ root[:properties][key.to_sym] = sub[:block].call
46
+ else
47
+ root[:properties][key.to_sym] = args.shift
48
+ end
49
+ else
50
+ sub[:block].call(struc)
51
+ end
52
+ end
53
+
54
+ struc
21
55
  end
22
56
 
23
57
  def normalize_settings_structure(struc)
@@ -69,7 +103,7 @@ module PandaPal
69
103
  any_match = norm_types.any? do |t|
70
104
  if t == 'Boolean'
71
105
  settings == true || settings == false
72
- else
106
+ elsif t.is_a?(Class)
73
107
  settings.is_a?(t)
74
108
  end
75
109
  end
@@ -11,6 +11,31 @@ module PandaPal
11
11
  included do
12
12
  after_commit :sync_schedule, on: [:create, :update]
13
13
  after_commit :unschedule_tasks, on: :destroy
14
+
15
+ define_setting do |struc|
16
+ next unless _schedule_descriptors.present?
17
+
18
+ struc[:properties][:timezone] ||= {
19
+ type: 'String',
20
+ required: false,
21
+ validate: ->(timezone, *args) {
22
+ ActiveSupport::TimeZone[timezone].present? ? nil : "<path> Invalid Timezone '#{timezone}'"
23
+ },
24
+ }
25
+
26
+ struc[:properties][:task_schedules] = {
27
+ type: 'Hash',
28
+ required: false,
29
+ properties: _schedule_descriptors.keys.reduce({}) do |hash, k|
30
+ desc = _schedule_descriptors[k]
31
+
32
+ hash.tap do |hash|
33
+ kl = ' ' * (k.to_s.length - 4)
34
+ hash[k.to_sym] = hash[k.to_s] = PandaPal::OrganizationConcerns::TaskScheduling.build_settings_entry(desc)
35
+ end
36
+ end,
37
+ }
38
+ end
14
39
  end
15
40
 
16
41
  class_methods do
@@ -18,35 +43,6 @@ module PandaPal
18
43
  @_schedule_descriptors ||= {}
19
44
  end
20
45
 
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
- desc = _schedule_descriptors[k]
40
-
41
- hash.tap do |hash|
42
- kl = ' ' * (k.to_s.length - 4)
43
- hash[k.to_sym] = hash[k.to_s] = PandaPal::OrganizationConcerns::TaskScheduling.build_settings_entry(desc)
44
- end
45
- end,
46
- }
47
- end
48
- end
49
-
50
46
  def scheduled_task(cron_time, name_or_method = nil, worker: nil, queue: nil, &block)
51
47
  task_key = (name_or_method.presence || "scheduled_task_#{caller_locations[0].lineno}").to_s
52
48
  raise "Task key '#{task_key}' already taken!" if _schedule_descriptors.key?(task_key)
@@ -168,13 +164,17 @@ module PandaPal
168
164
  return nil unless cron_time.present?
169
165
 
170
166
  cron_time = instance_exec(&cron_time) if cron_time.is_a?(Proc)
171
- if !Rufus::Scheduler.parse(cron_time).zone.present? && settings && settings[:timezone]
172
- cron_time += " #{settings[:timezone]}"
167
+ if !Rufus::Scheduler.parse(cron_time).zone.present? && settings && settings_timezone
168
+ cron_time += " #{settings_timezone}"
173
169
  end
174
170
 
175
171
  cron_time
176
172
  end
177
173
 
174
+ def settings_timezone
175
+ settings[:timezone] || settings.dig(:canvas, :root_account_timezone).presence || nil
176
+ end
177
+
178
178
  class ScheduledTaskExecutor
179
179
  include Sidekiq::Worker
180
180
 
@@ -10,10 +10,10 @@ Apartment.configure do |config|
10
10
  end
11
11
 
12
12
  Rails.application.config.middleware.use Apartment::Elevators::Generic, lambda { |request|
13
- if request.path.starts_with?('/rails/active_storage/blobs/')
14
- PandaPal::Organization.find_by(id: request.params['organization_id']).try(:name)
15
- elsif match = request.path.match(/\/(?:orgs?|organizations?)\/(\d+)/)
13
+ if match = request.path.match(/\/(?:orgs?|organizations?)\/(\d+)/)
16
14
  PandaPal::Organization.find_by(id: match[1]).try(:name)
15
+ elsif request.path.starts_with?('/rails/active_storage/blobs/')
16
+ PandaPal::Organization.find_by(id: request.params['organization_id']).try(:name)
17
17
  end
18
18
  }
19
19
 
data/lib/panda_pal.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require "panda_pal/engine"
2
2
  require 'panda_pal/plugins'
3
3
  require 'panda_pal/helpers'
4
+ require 'oauth/request_proxy/rack_request'
5
+ require 'oauth/request_proxy/action_dispatch_request'
4
6
 
5
7
  module PandaPal
6
8
  class LtiNavigationInUse < StandardError;end
@@ -0,0 +1,85 @@
1
+ module PandaPal
2
+ module AbilityMixin
3
+ extend ActiveSupport::Concern
4
+
5
+ class_methods do
6
+ def self.role_permissions(labels)
7
+ roles = find_roles_by_label(labels)
8
+
9
+ if Rails.env.test? && roles.count == 0 && (labels || []).include?('Administrator')
10
+ return Hash.new(true)
11
+ end
12
+
13
+ final = {}
14
+ roles.find_each do |role|
15
+ role.permissions.each do |perm_name, perm|
16
+ final[perm_name] = false if final[perm_name].nil?
17
+ final[perm_name] = true if perm['enabled'] == true
18
+ end
19
+ end
20
+ final
21
+ end
22
+
23
+ def self.find_roles_by_label(labels)
24
+ raise "PandaPal AbilityMixin's support for Roles and Permissions requires that Roles be synced by CanvasSync" unless defined?(Role)
25
+
26
+ built_ins = []
27
+ labels = labels.split(',') if labels.is_a?(String)
28
+ custom_labels = Array(labels).reject do |l|
29
+ if role_is_default?(l)
30
+ built_ins << l
31
+ elsif l == 'Account Admin'
32
+ built_ins << 'AccountMembership'
33
+ else
34
+ next
35
+ end
36
+ true
37
+ end
38
+ Role.where(workflow_state: 'built_in', base_role_type: built_ins)
39
+ .or(Role.where.not(workflow_state: 'built_in').where(label: custom_labels))
40
+ end
41
+
42
+ def self.role_is_default?(role)
43
+ %w[TeacherEnrollment TaEnrollment StudentEnrollment DesignerEnrollment ObserverEnrollment].include? role
44
+ end
45
+ end
46
+
47
+ def is_lti_launch?
48
+ @controller.current_session.present?
49
+ end
50
+
51
+ def panda_pal_session
52
+ @panda_pal_session ||= @controller.current_session&.data || {}
53
+ end
54
+
55
+ def rails_session
56
+ @rails_session ||= @controller.session
57
+ end
58
+
59
+ def launch_params
60
+ @launch_params ||= panda_pal_session[:launch_params] || {}
61
+ end
62
+
63
+ # Roles and Permissions
64
+ def lti_roles
65
+ @lti_roles ||= LTIRoles::RoleManager.new(launch_params['ext_roles'] || '')
66
+ end
67
+
68
+ def canvas_permissions
69
+ panda_pal_session[:canvas_permissions] ||= self.class.role_permissions(launch_params['custom_canvas_role'])
70
+ end
71
+
72
+ def canvas_role_labels
73
+ labels = launch_params['custom_canvas_role']
74
+ labels.is_a?(String) ? labels.split(',') : []
75
+ end
76
+
77
+ def canvas_roles
78
+ self.class.find_roles_by_label(canvas_role_labels)
79
+ end
80
+
81
+ def launch_role_ids
82
+ panda_pal_session[:launch_role_ids] ||= canvas_roles.map(&:canvas_id)
83
+ end
84
+ end
85
+ end
@@ -24,6 +24,29 @@ module PandaPal
24
24
  end
25
25
  end
26
26
 
27
+ initializer 'interop dependencies' do
28
+ if $LOADED_FEATURES.grep(/\/ros-apartment-\d/).present?
29
+ if $LOADED_FEATURES.grep(/\/apartment-\d/).present? || $LOADED_FEATURES.grep(/\/apartment-sidekiq-\d/).present?
30
+ raise "ros-apartment is used, but apartment or apartment-sidekiq is loaded. Do not mix legacy and ros- variants! (You most likely need to update your Gemfile to use the ros- variant)"
31
+ end
32
+ end
33
+ if defined?(Sidekiq)
34
+ begin
35
+ require 'apartment-sidekiq'
36
+ rescue LoadError
37
+ raise "Sidekiq is used, but apartment-sidekiq is not installed. Add [gem 'ros-apartment-sidekiq'] to your Gemfile"
38
+ end
39
+ end
40
+ end
41
+
42
+ initializer 'Sidekiq Scheduler Hooks' do
43
+ ActiveSupport.on_load(:active_record) do
44
+ if defined?(Sidekiq) && Sidekiq.server? && PandaPal::Organization.respond_to?(:sync_schedules)
45
+ PandaPal::Organization.sync_schedules
46
+ end
47
+ end
48
+ end
49
+
27
50
  initializer 'panda_pal.app_controller' do |app|
28
51
  OAUTH_10_SUPPORT = true
29
52
  ActiveSupport.on_load(:action_controller) do
@@ -50,8 +50,8 @@ module PandaPal::Helpers
50
50
  safe_unexpected_params.each do |p|
51
51
  sanitized_params.delete(p)
52
52
  end
53
- authenticator = IMS::LTI::Services::MessageAuthenticator.new(request.original_url, sanitized_params, @organization.secret)
54
- authorized = authenticator.valid_signature?
53
+ tp = IMS::LTI::ToolProvider.new(@organization.key, @organization.secret, sanitized_params)
54
+ authorized = tp.valid_request?(request)
55
55
  end
56
56
 
57
57
  if !authorized
@@ -0,0 +1,50 @@
1
+ module PandaPal::Jobs
2
+ class MissingGradePassbackParams < StandardError; end
3
+
4
+ class GradePassbackFailure < StandardError; end
5
+
6
+ class GradePassbackJob < ActiveJob::Base
7
+ sidekiq_options retry: 5
8
+
9
+ attr_accessor :organization, :opts
10
+
11
+ # Required values for opts: passback_guid, passback_url, score AND/OR total_score.
12
+ # Possible values for opts: cdata_text, text, url, submitted_at, lti_launch_url.
13
+ # passback_guid is sent in launch params as 'lis_result_sourcedid'.
14
+ # passback_url is sent in LTI launch params as 'lis_outcome_service_url'.
15
+ # See https://canvas.instructure.com/doc/api/file.assignment_tools.html
16
+ def perform(organization, opts = {})
17
+ opts = opts.with_indifferent_access
18
+ raise MissingGradePassbackParams unless valid_options(opts)
19
+
20
+ @organization = organization
21
+ @opts = opts
22
+
23
+ post_to_lms
24
+ end
25
+
26
+ private
27
+
28
+ def post_to_lms
29
+ result = tool_provider.post_extended_replace_result!(opts)
30
+ unless result.success?
31
+ Rails.logger.error "Grade passback failure: #{result.response_code} #{result.code_major} #{result.severity} #{result.description}"
32
+ raise GradePassbackFailure
33
+ end
34
+ end
35
+
36
+ def tool_provider
37
+ @tool_provider ||= IMS::LTI::ToolProvider.new(
38
+ organization.key,
39
+ organization.secret,
40
+ 'lis_result_sourcedid' => opts[:passback_guid],
41
+ 'lis_outcome_service_url' => opts[:passback_url]
42
+ ).extend(IMS::LTI::Extensions::OutcomeData::ToolProvider)
43
+ end
44
+
45
+ def valid_options(opts = {})
46
+ return opts[:passback_guid] && opts[:passback_url] && (opts[:score] || opts[:total_score])
47
+ end
48
+
49
+ end
50
+ end
@@ -1,3 +1,3 @@
1
1
  module PandaPal
2
- VERSION = "5.3.10"
2
+ VERSION = "5.4.0.beta2"
3
3
  end
data/panda_pal.gemspec CHANGED
@@ -16,9 +16,9 @@ Gem::Specification.new do |s|
16
16
  s.test_files = Dir["spec/**/*"]
17
17
 
18
18
  s.add_dependency "rails", ">= 4.2"
19
+ s.add_dependency 'ros-apartment', '~> 2.2'
19
20
  s.add_dependency 'pg', '>= 0.20', '< 1.0.0'
20
- s.add_dependency 'apartment', '~> 2.2.0'
21
- s.add_dependency 'ims-lti', '~> 2.2.3'
21
+ s.add_dependency 'ims-lti', '~> 1.2.4'
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'
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.3.10
4
+ version: 5.4.0.beta2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Instructure ProServe
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-05 00:00:00.000000000 Z
11
+ date: 2021-04-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '4.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: ros-apartment
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.2'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: pg
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -44,34 +58,20 @@ dependencies:
44
58
  - - "<"
45
59
  - !ruby/object:Gem::Version
46
60
  version: 1.0.0
47
- - !ruby/object:Gem::Dependency
48
- name: apartment
49
- requirement: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: 2.2.0
54
- type: :runtime
55
- prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - "~>"
59
- - !ruby/object:Gem::Version
60
- version: 2.2.0
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: ims-lti
63
63
  requirement: !ruby/object:Gem::Requirement
64
64
  requirements:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: 2.2.3
67
+ version: 1.2.4
68
68
  type: :runtime
69
69
  prerelease: false
70
70
  version_requirements: !ruby/object:Gem::Requirement
71
71
  requirements:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: 2.2.3
74
+ version: 1.2.4
75
75
  - !ruby/object:Gem::Dependency
76
76
  name: browser
77
77
  requirement: !ruby/object:Gem::Requirement
@@ -243,12 +243,14 @@ files:
243
243
  - db/migrate/20171205183457_encrypt_organization_settings.rb
244
244
  - db/migrate/20171205194657_remove_old_organization_settings.rb
245
245
  - lib/panda_pal.rb
246
+ - lib/panda_pal/ability_mixin.rb
246
247
  - lib/panda_pal/engine.rb
247
248
  - lib/panda_pal/helpers.rb
248
249
  - lib/panda_pal/helpers/controller_helper.rb
249
250
  - lib/panda_pal/helpers/route_helper.rb
250
251
  - lib/panda_pal/helpers/secure_headers.rb
251
252
  - lib/panda_pal/helpers/session_replacement.rb
253
+ - lib/panda_pal/jobs/grade_passback_job.rb
252
254
  - lib/panda_pal/plugins.rb
253
255
  - lib/panda_pal/version.rb
254
256
  - lib/tasks/panda_pal_tasks.rake
@@ -312,9 +314,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
312
314
  version: '0'
313
315
  required_rubygems_version: !ruby/object:Gem::Requirement
314
316
  requirements:
315
- - - ">="
317
+ - - ">"
316
318
  - !ruby/object:Gem::Version
317
- version: '0'
319
+ version: 1.3.1
318
320
  requirements: []
319
321
  rubygems_version: 3.0.3
320
322
  signing_key: