panda_pal 5.0.0 → 5.2.3
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/README.md +208 -90
- data/app/controllers/panda_pal/lti_controller.rb +0 -18
- data/app/controllers/panda_pal/lti_v1_p0_controller.rb +34 -0
- data/app/controllers/panda_pal/lti_v1_p3_controller.rb +98 -0
- data/app/lib/lti_xml/base_platform.rb +4 -4
- data/app/lib/panda_pal/launch_url_helpers.rb +69 -0
- data/app/lib/panda_pal/lti_jwt_validator.rb +88 -0
- data/app/lib/panda_pal/misc_helper.rb +13 -0
- data/app/models/panda_pal/organization.rb +21 -47
- data/app/models/panda_pal/organization_concerns/settings_validation.rb +127 -0
- data/app/models/panda_pal/organization_concerns/task_scheduling.rb +204 -0
- data/app/models/panda_pal/platform.rb +40 -0
- data/app/views/panda_pal/lti_v1_p3/login.html.erb +1 -0
- data/app/views/panda_pal/partials/_auto_submit_form.html.erb +9 -0
- data/config/dev_lti_key.key +27 -0
- data/config/routes.rb +12 -2
- data/db/migrate/20160412205931_create_panda_pal_organizations.rb +1 -1
- data/db/migrate/20160413135653_create_panda_pal_sessions.rb +1 -1
- data/db/migrate/20160425130344_add_panda_pal_organization_to_session.rb +1 -1
- data/db/migrate/20170106165533_add_salesforce_id_to_organizations.rb +1 -1
- data/db/migrate/20171205183457_encrypt_organization_settings.rb +1 -1
- data/db/migrate/20171205194657_remove_old_organization_settings.rb +8 -3
- data/lib/panda_pal.rb +28 -15
- data/lib/panda_pal/engine.rb +8 -39
- data/lib/panda_pal/helpers.rb +1 -0
- data/lib/panda_pal/helpers/controller_helper.rb +139 -44
- data/lib/panda_pal/helpers/route_helper.rb +8 -8
- data/lib/panda_pal/helpers/secure_headers.rb +79 -0
- data/lib/panda_pal/version.rb +1 -1
- data/panda_pal.gemspec +6 -2
- data/spec/dummy/config/application.rb +7 -1
- data/spec/dummy/config/environments/development.rb +0 -14
- data/spec/dummy/config/environments/production.rb +0 -11
- data/spec/models/panda_pal/organization/settings_validation_spec.rb +175 -0
- data/spec/models/panda_pal/organization/task_scheduling_spec.rb +144 -0
- data/spec/models/panda_pal/organization_spec.rb +0 -89
- data/spec/spec_helper.rb +4 -0
- metadata +64 -8
- data/spec/dummy/config/initializers/assets.rb +0 -11
@@ -0,0 +1,204 @@
|
|
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
|
+
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] = {
|
44
|
+
required: false,
|
45
|
+
description: <<~MARKDOWN,
|
46
|
+
Override schedule for '#{k.to_s}' task.
|
47
|
+
|
48
|
+
**Default**: #{desc[:schedule].is_a?(String) ? desc[:schedule] : '<Computed>'}
|
49
|
+
|
50
|
+
Set to `false` to disable or supply a Cron string:
|
51
|
+
```yaml
|
52
|
+
#{k.to_s}: 0 0 0 * * * America/Denver
|
53
|
+
##{kl} │ │ │ │ │ │ └── Timezone (Optional)
|
54
|
+
##{kl} │ │ │ │ │ └── Day of Week
|
55
|
+
##{kl} │ │ │ │ └── Month
|
56
|
+
##{kl} │ │ │ └── Day of Month
|
57
|
+
##{kl} │ │ └── Hour
|
58
|
+
##{kl} │ └── Minute
|
59
|
+
##{kl} └── Second (Optional)
|
60
|
+
````
|
61
|
+
MARKDOWN
|
62
|
+
json_schema: {
|
63
|
+
oneOf: [
|
64
|
+
{ type: 'string', pattern: '^((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,6})(\w+\/\w+)?$' },
|
65
|
+
{ enum: [false] },
|
66
|
+
],
|
67
|
+
default: desc[:schedule].is_a?(String) ? desc[:schedule] : '0 0 3 * * * America/Denver',
|
68
|
+
},
|
69
|
+
validate: ->(value, *args, errors:, **kwargs) {
|
70
|
+
begin
|
71
|
+
Rufus::Scheduler.parse(value) if value
|
72
|
+
nil
|
73
|
+
rescue ArgumentError
|
74
|
+
errors << "<path> must be false or a Crontab string"
|
75
|
+
end
|
76
|
+
}
|
77
|
+
}
|
78
|
+
end
|
79
|
+
end,
|
80
|
+
}
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def scheduled_task(cron_time, name_or_method = nil, worker: nil, queue: nil, &block)
|
85
|
+
task_key = (name_or_method.presence || "scheduled_task_#{caller_locations[0].lineno}").to_s
|
86
|
+
raise "Task key '#{task_key}' already taken!" if _schedule_descriptors.key?(task_key)
|
87
|
+
|
88
|
+
_schedule_descriptors[task_key] = {
|
89
|
+
key: task_key,
|
90
|
+
schedule: cron_time,
|
91
|
+
worker: worker || block || name_or_method.to_sym,
|
92
|
+
queue: queue || 'default',
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
96
|
+
def remove_scheduled_task(name_or_method)
|
97
|
+
dval = _schedule_descriptors.delete(name_or_method.to_s)
|
98
|
+
Rails.logger.warn("No task with key '#{name_or_method}' to delete!") unless dval.present?
|
99
|
+
end
|
100
|
+
|
101
|
+
def sync_schedules
|
102
|
+
# Ensure deleted Orgs are removed
|
103
|
+
existing_orgs = pluck(:name)
|
104
|
+
old_schedules = Sidekiq.get_schedule.select do |k, v|
|
105
|
+
m = k.match(/^org:([a-z0-9_]+)\-/i)
|
106
|
+
m.present? && !existing_orgs.include?(m[1])
|
107
|
+
end
|
108
|
+
old_schedules.keys.each do |k|
|
109
|
+
Sidekiq.remove_schedule(k)
|
110
|
+
end
|
111
|
+
|
112
|
+
find_each(&:sync_schedule)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def generate_schedule
|
117
|
+
schedule = {}
|
118
|
+
self.class._schedule_descriptors.values.each do |desc|
|
119
|
+
cron_time = schedule_task_cron_time(desc)
|
120
|
+
next unless cron_time.present?
|
121
|
+
|
122
|
+
schedule["org:#{name}-#{desc[:key]}"] = {
|
123
|
+
'cron' => cron_time,
|
124
|
+
'queue' => desc[:queue],
|
125
|
+
'class' => ScheduledTaskExecutor.to_s,
|
126
|
+
'args' => [name, desc[:key]],
|
127
|
+
}
|
128
|
+
end
|
129
|
+
schedule
|
130
|
+
end
|
131
|
+
|
132
|
+
def sync_schedule
|
133
|
+
new_schedules = generate_schedule
|
134
|
+
unschedule_tasks(new_schedules.keys)
|
135
|
+
new_schedules.each do |k, v|
|
136
|
+
Sidekiq.set_schedule(k, v)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
def unschedule_tasks(new_task_keys = nil)
|
143
|
+
current_schedules = Sidekiq.get_schedule.select { |k,v| k.starts_with?("org:#{name}-") }
|
144
|
+
del_tasks = current_schedules.keys
|
145
|
+
del_tasks -= new_task_keys if new_task_keys
|
146
|
+
del_tasks.each do |k|
|
147
|
+
Sidekiq.remove_schedule(k)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def schedule_task_cron_time(desc)
|
152
|
+
cron_time = nil
|
153
|
+
cron_time = settings&.dig(:task_schedules, desc[:key].to_s) if cron_time.nil?
|
154
|
+
cron_time = settings&.dig(:task_schedules, desc[:key].to_sym) if cron_time.nil?
|
155
|
+
cron_time = desc[:schedule] if cron_time.nil?
|
156
|
+
|
157
|
+
return nil unless cron_time.present?
|
158
|
+
|
159
|
+
cron_time = instance_exec(&cron_time) if cron_time.is_a?(Proc)
|
160
|
+
if !Rufus::Scheduler.parse(cron_time).zone.present? && settings && settings[:timezone]
|
161
|
+
cron_time += " #{settings[:timezone]}"
|
162
|
+
end
|
163
|
+
|
164
|
+
cron_time
|
165
|
+
end
|
166
|
+
|
167
|
+
class ScheduledTaskExecutor
|
168
|
+
include Sidekiq::Worker
|
169
|
+
|
170
|
+
def perform(org_name, task_key)
|
171
|
+
org = Organization.find_by!(name: org_name)
|
172
|
+
task = Organization._schedule_descriptors[task_key]
|
173
|
+
worker = task[:worker]
|
174
|
+
|
175
|
+
Apartment::Tenant.switch(org.name) do
|
176
|
+
if worker.is_a?(Proc)
|
177
|
+
org.instance_exec(&worker)
|
178
|
+
elsif worker.is_a?(Symbol)
|
179
|
+
org.send(worker)
|
180
|
+
elsif worker.is_a?(String)
|
181
|
+
worker.constantize.perform_async
|
182
|
+
elsif worker.is_a?(Class)
|
183
|
+
worker.perform_async
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
SidekiqScheduler::Scheduler.instance.dynamic = true
|
193
|
+
|
194
|
+
module SidekiqScheduler
|
195
|
+
module Schedule
|
196
|
+
original_schedule_setter = instance_method(:schedule=)
|
197
|
+
|
198
|
+
define_method :schedule= do |sched|
|
199
|
+
original_schedule_setter.bind(self).(sched).tap do
|
200
|
+
PandaPal::Organization.sync_schedules
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module PandaPal
|
2
|
+
class Platform
|
3
|
+
def public_jwks
|
4
|
+
response = HTTParty.get(jwks_url)
|
5
|
+
return nil unless response.success?
|
6
|
+
|
7
|
+
JSON::JWK::Set.new(JSON.parse(response.body))
|
8
|
+
rescue
|
9
|
+
nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Platform::Canvas < Platform
|
14
|
+
attr_accessor :base_url
|
15
|
+
|
16
|
+
def initialize(base_url)
|
17
|
+
@base_url = base_url
|
18
|
+
end
|
19
|
+
|
20
|
+
def host
|
21
|
+
base_url
|
22
|
+
end
|
23
|
+
|
24
|
+
def jwks_url
|
25
|
+
"#{base_url}/api/lti/security/jwks"
|
26
|
+
end
|
27
|
+
|
28
|
+
def authentication_redirect_url
|
29
|
+
"#{base_url}/api/lti/authorize_redirect"
|
30
|
+
end
|
31
|
+
|
32
|
+
def grant_url
|
33
|
+
"#{base_url}/login/oauth2/token"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Platform
|
38
|
+
CANVAS = Platform::Canvas.new('https://canvas.instructure.com')
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= render "panda_pal/partials/auto_submit_form" %>
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<%= form_tag(@form_action, method: @method, id: 'redirect-form') do %>
|
2
|
+
<% @form_data.each do |k, v| %>
|
3
|
+
<%= hidden_field_tag(k, v) %>
|
4
|
+
<% end %>
|
5
|
+
<% end %>
|
6
|
+
|
7
|
+
<script type="text/javascript" nonce="<%= content_security_policy_script_nonce %>">
|
8
|
+
document.getElementById('redirect-form').submit();
|
9
|
+
</script>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIIEowIBAAKCAQEAt+Za2scYD223YjVvAeuxpYvBjTf4yp2Y3udPFllG85zIVH/K
|
3
|
+
XKsP4QVP9de/fh4FzouZPOfXMDY1KmWSlMK9jNYUm2rh0zp7+X/F6cnc5QMCzbqV
|
4
|
+
MNnfALI4kvGM8yNPLbMJx4nA47T7T9TmlMJUZQxtyvC8zxBMv8Mv4Ae0arXevpI1
|
5
|
+
Vcm/3Huz6dAJPRjTnL2CBsXrmNzf51OJU5r27HYFE4367g9njix1lGOcQqVDVjnT
|
6
|
+
uXFTlidfUa2Bp2v5owzZU7Fe9jSQmeAQpqQ/XdYzWQNEJPrz8ESz5jP3brJ4q5Y5
|
7
|
+
FaK9l8DHILqomGWhttYQ6IMCfdpLb4D4B2gcLQIDAQABAoIBAAuaXislmrAGhSaO
|
8
|
+
JoXhgCDo03p8iJcIIIgX4haP5XkjcERcl8EHDgZtlmD1juB/NnCUwENmgV5KXUpi
|
9
|
+
hEAclWcYbs5rjPoN25qfZDZfBS/x47BlUFp3tKlPlWA4G2OP28QPYtOTLndviNe9
|
10
|
+
oBrMtBR4F0lRrSgHaEBFKXUiJ1EAMycKcXab63HA7Fp9HArLAu0NM0VWCsJBmxB/
|
11
|
+
BprW4LtYRG7iJl9kfsHyAzOaAV5H1zLqhfA2YvEI2XADrILQtpPwZpmNKq/5L5On
|
12
|
+
RlGy1WcBgxj8cqWdqir1PxVN/xDKVnUT9Mrf+SHn7xzgDL2uXvhD1Qt/5iLeVlvN
|
13
|
+
nQTNJyECgYEA681T7A0oj9/FuqLwE1gxm+RUczv+EyXcNvHCUkpFSaiyEyqdQB9f
|
14
|
+
EpHgeg7pXmlRmUwNkT3ZAH7O0uv3CVR6b8j9N7XmYbmcuRU2A6loeiBB/HkOOt21
|
15
|
+
hxiC+tXD/K7c5BfRkThL6ca213xqztbqxxj/C8qGnWVaLc7SLPlJZfUCgYEAx6bp
|
16
|
+
7tRGq8Q5xkyKvSDUSln9MI/iZjSysd44rTtj8Vo9LfQsBi5JbWm0+UEtaZoabeC1
|
17
|
+
xaAp/ZETLe4oGu7CJxBreSE+UE/a6zc5MIcJblP+8RIiAYkf67iHNwSbiqSBvPqj
|
18
|
+
S0HFxUZqw1NigmqsptHrPtvuTUZI8Hx3hKMgwlkCgYAa8RrlnZtE1QyChptnmmwQ
|
19
|
+
o8YCZJhjF7BRls3dGR9RizTNe9D7wpnaRVCgoZOIdgAcw9PJBIgGxnZbIxrWthBH
|
20
|
+
NW+5Lc9k2xBNFV9Wi8SkL4tajXpSv4I+LU7J2iLKfDBA33fSX9xMmafKdyy89VFd
|
21
|
+
7j01264Fzc6/7SGWgeUhAQKBgDKGSgMXkz7arKhDLIUKLs8WEN3eO7QTt/kNPJiS
|
22
|
+
RAuLA5qChTWXNxvKOXMujFiCGBggWr/FdXrm4MypzVpre5S5Mgl4YTWfz83grsda
|
23
|
+
FQfnl8fYB+UNl5dmnklNEDO4x+BUKUjdPzhaRqBhlLdeWYzp6LeCnr7Nf53kUbau
|
24
|
+
NZcZAoGBAJcYyWegsQCEVMY/ck42uNmZ00DmK1XSAU7hYulBo8iD3OqvXZ7Etppw
|
25
|
+
SaRhSTCoTsLBWlqGccNDGoXH/r2/jJAFb8hUjGa2Z5dKP9byWMONEQEh6g8wzL/w
|
26
|
+
XRthIsgdIE58a+cJGwHh/raUQwvD72S1C/epQSYcLe3TdUumtZm/
|
27
|
+
-----END RSA PRIVATE KEY-----
|
data/config/routes.rb
CHANGED
@@ -1,4 +1,14 @@
|
|
1
1
|
PandaPal::Engine.routes.draw do
|
2
|
-
get '/config' => '
|
3
|
-
|
2
|
+
get '/config' => 'lti_v1_p0#tool_config' # Legacy Support
|
3
|
+
|
4
|
+
scope '/v1p0', as: 'v1p0' do
|
5
|
+
get '/config' => 'lti_v1_p0#tool_config'
|
6
|
+
end
|
7
|
+
|
8
|
+
scope '/v1p3', as: 'v1p3' do
|
9
|
+
get '/config' => 'lti_v1_p3#tool_config'
|
10
|
+
post '/oidc_login' => 'lti_v1_p3#login'
|
11
|
+
post '/resource_link_request' => 'lti_v1_p3#resource_link_request'
|
12
|
+
get '/public_jwks' => 'lti_v1_p3#public_jwks'
|
13
|
+
end
|
4
14
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
class AddPandaPalOrganizationToSession <
|
1
|
+
class AddPandaPalOrganizationToSession < PandaPal::MiscHelper::MigrationClass
|
2
2
|
def change
|
3
3
|
add_column :panda_pal_sessions, :panda_pal_organization_id, :integer
|
4
4
|
add_index :panda_pal_sessions, :panda_pal_organization_id
|
@@ -1,4 +1,4 @@
|
|
1
|
-
class EncryptOrganizationSettings <
|
1
|
+
class EncryptOrganizationSettings < PandaPal::MiscHelper::MigrationClass
|
2
2
|
def up
|
3
3
|
# don't rerun this if it was already run before we renamed the migration.
|
4
4
|
existing_versions = execute ("SELECT * from schema_migrations where version = '30171205183457'")
|
@@ -1,4 +1,4 @@
|
|
1
|
-
class RemoveOldOrganizationSettings <
|
1
|
+
class RemoveOldOrganizationSettings < PandaPal::MiscHelper::MigrationClass
|
2
2
|
def current_tenant
|
3
3
|
@current_tenant ||= PandaPal::Organization.find_by_name(Apartment::Tenant.current)
|
4
4
|
end
|
@@ -10,14 +10,16 @@ class RemoveOldOrganizationSettings < ActiveRecord::Migration[5.1]
|
|
10
10
|
execute "DELETE from schema_migrations where version = '30171205194657'"
|
11
11
|
return
|
12
12
|
end
|
13
|
+
|
13
14
|
# migrations run for public and local tenants. However, PandaPal::Organization
|
14
15
|
# is going to always go to public tenant. So don't do this active record
|
15
16
|
# stuff unless we are on the public tenant.
|
16
|
-
if
|
17
|
+
if Apartment::Tenant.current == 'public'
|
17
18
|
#PandaPal::Organization.connection.schema_cache.clear!
|
18
19
|
#PandaPal::Organization.reset_column_information
|
19
20
|
PandaPal::Organization.find_each do |o|
|
20
21
|
# Would like to just be able to do this:
|
22
|
+
# PandaPal::Organization.reset_column_information
|
21
23
|
# o.settings = YAML.load(o.old_settings)
|
22
24
|
# o.save!
|
23
25
|
# but for some reason that is always making the settings null. Instead we will encrypt the settings manually.
|
@@ -26,14 +28,17 @@ class RemoveOldOrganizationSettings < ActiveRecord::Migration[5.1]
|
|
26
28
|
key = o.encryption_key
|
27
29
|
encrypted_settings = PandaPal::Organization.encrypt_settings(YAML.load(o.old_settings), iv: iv, key: key)
|
28
30
|
o.update_columns(encrypted_settings_iv: [iv].pack("m"), encrypted_settings: encrypted_settings)
|
31
|
+
o = PandaPal::Organization.find_by!(name: o.name)
|
32
|
+
raise "Failed to migrate PandaPal Settings" if o.settings != YAML.load(o.old_settings)
|
29
33
|
end
|
30
34
|
end
|
35
|
+
|
31
36
|
remove_column :panda_pal_organizations, :old_settings
|
32
37
|
end
|
33
38
|
|
34
39
|
def down
|
35
40
|
add_column :panda_pal_organizations, :old_settings, :text
|
36
|
-
if
|
41
|
+
if Apartment::Tenant.current == 'public'
|
37
42
|
PandaPal::Organization.find_each do |o|
|
38
43
|
o.old_settings = o.settings.to_yaml
|
39
44
|
o.save
|
data/lib/panda_pal.rb
CHANGED
@@ -7,11 +7,11 @@ module PandaPal
|
|
7
7
|
class NotMounted < StandardError;end
|
8
8
|
|
9
9
|
@@lti_navigation = {}
|
10
|
-
@@staged_navigation = {}
|
11
10
|
@@lti_options = {}
|
12
11
|
@@lti_properties = {}
|
13
12
|
@@lti_environments = {}
|
14
13
|
@@lti_custom_params = {}
|
14
|
+
@@lti_private_key = nil
|
15
15
|
|
16
16
|
def self.lti_options= lti_options
|
17
17
|
@@lti_options = lti_options
|
@@ -45,31 +45,44 @@ module PandaPal
|
|
45
45
|
@@lti_custom_params.deep_dup
|
46
46
|
end
|
47
47
|
|
48
|
-
def self.register_navigation(navigation)
|
49
|
-
@@lti_navigation[navigation] ||= {}
|
50
|
-
end
|
51
|
-
|
52
48
|
def self.stage_navigation(navigation, options)
|
53
|
-
@@
|
54
|
-
@@
|
49
|
+
@@lti_navigation[navigation] ||= {}
|
50
|
+
@@lti_navigation[navigation].merge!(options)
|
55
51
|
end
|
56
52
|
|
57
53
|
def self.lti_paths
|
58
54
|
@@lti_navigation.deep_dup
|
59
55
|
end
|
60
56
|
|
61
|
-
def self.
|
62
|
-
@@
|
63
|
-
|
64
|
-
|
65
|
-
|
57
|
+
def self.lti_private_key
|
58
|
+
key = @@lti_private_key.presence
|
59
|
+
key ||= ENV['LTI_PRIVATE_KEY'].presence
|
60
|
+
key ||= File.read(File.join( File.dirname(__FILE__), "../config/dev_lti_key.key")) if Rails.env.development?
|
61
|
+
return nil unless key.present?
|
62
|
+
|
63
|
+
key = OpenSSL::PKey::RSA.new(key) if key.is_a?(String)
|
64
|
+
key
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.lti_private_key=(v)
|
68
|
+
@@lti_private_key = k
|
66
69
|
end
|
67
70
|
|
68
71
|
private
|
69
72
|
|
70
|
-
def self.
|
71
|
-
|
72
|
-
|
73
|
+
def self.validate_pandapal_config!
|
74
|
+
errors = []
|
75
|
+
validate_lti_navigation(errors)
|
76
|
+
if errors.present?
|
77
|
+
lines = errors.map { |e| " - #{e}" }
|
78
|
+
raise "PandaPal was not configured correctly:\n#{lines.join("\n")}"
|
79
|
+
end
|
73
80
|
end
|
74
81
|
|
82
|
+
def self.validate_lti_navigation(errors = [])
|
83
|
+
@@lti_navigation.each do |k, v|
|
84
|
+
errors << "lti navigation '#{k}' does not have a Route!" unless (LaunchUrlHelpers.launch_url(k) rescue nil)
|
85
|
+
end
|
86
|
+
errors
|
87
|
+
end
|
75
88
|
end
|