panda_pal 5.3.10 → 5.4.0.beta2

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
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: