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 +4 -4
- data/app/lib/lti_xml/base_platform.rb +1 -1
- data/app/models/panda_pal/organization.rb +3 -0
- data/app/models/panda_pal/organization_concerns/settings_validation.rb +36 -2
- data/app/models/panda_pal/organization_concerns/task_scheduling.rb +31 -31
- data/config/initializers/apartment.rb +3 -3
- data/lib/panda_pal.rb +2 -0
- data/lib/panda_pal/ability_mixin.rb +85 -0
- data/lib/panda_pal/engine.rb +23 -0
- data/lib/panda_pal/helpers/controller_helper.rb +2 -2
- data/lib/panda_pal/jobs/grade_passback_job.rb +50 -0
- data/lib/panda_pal/version.rb +1 -1
- data/panda_pal.gemspec +2 -2
- metadata +22 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 711da479bf7e495d72287d508109c520c7c0dd0fba08d67b4f746853ac20b535
|
4
|
+
data.tar.gz: d18ae002b72f96f88213245ca18fc4d756cbf896de87b623cb02d44686c2e1fd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7257fd004deacbc44fdf45818a85cc5c8294587003679f84e9f144288ee75680bdd1478b55b63839e30d4f1df587d9f8c69261ad143aa327aab49a8370df8a70
|
7
|
+
data.tar.gz: b8d09741fc6743ec3a2eff3b8f4ba68f3205afd574dd17ed596fb331f6b98e31aba8e24a615a34f59a2fa498e07051fd5f2776ade4edea5f83544a3e5667546c
|
@@ -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
|
-
|
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 &&
|
172
|
-
cron_time += " #{
|
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.
|
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
@@ -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
|
data/lib/panda_pal/engine.rb
CHANGED
@@ -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
|
-
|
54
|
-
authorized =
|
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
|
data/lib/panda_pal/version.rb
CHANGED
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 '
|
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.
|
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-
|
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:
|
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:
|
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:
|
319
|
+
version: 1.3.1
|
318
320
|
requirements: []
|
319
321
|
rubygems_version: 3.0.3
|
320
322
|
signing_key:
|