panda_pal 5.18.2 → 5.19.0.beta1
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 +26 -0
- data/app/controllers/panda_pal/lti_v1_p0_controller.rb +11 -4
- data/app/controllers/panda_pal/lti_v1_p3_controller.rb +17 -8
- data/app/lib/lti_xml/base_platform.rb +32 -18
- data/app/lib/lti_xml/bridge_platform.rb +2 -2
- data/app/lib/panda_pal/launch_url_helpers.rb +21 -14
- data/app/models/panda_pal/lti_registration.rb +41 -0
- data/app/models/panda_pal/organization.rb +2 -0
- data/app/models/panda_pal/platform/canvas.rb +63 -28
- data/app/models/panda_pal/session.rb +19 -1
- data/config/initializers/apartment.rb +1 -1
- data/db/migrate/20261006000000_create_panda_pal_lti_registrations.rb +14 -0
- data/db/migrate/20261006000001_add_tool_to_panda_pal_sessions.rb +5 -0
- data/lib/panda_pal/helpers/controller_helper.rb +20 -1
- data/lib/panda_pal/helpers/route_helper.rb +8 -1
- data/lib/panda_pal/helpers/session_replacement.rb +2 -2
- data/lib/panda_pal/version.rb +1 -1
- data/lib/panda_pal.rb +82 -10
- data/panda_pal.gemspec +1 -0
- data/spec/controllers/panda_pal/lti_v1_p0_controller_spec.rb +85 -1
- data/spec/controllers/panda_pal/lti_v1_p3_tools_spec.rb +144 -0
- data/spec/lib/panda_pal/lti_tools_spec.rb +153 -0
- data/spec/models/panda_pal/lti_registration_spec.rb +72 -0
- data/spec/models/panda_pal/platform/canvas_install_spec.rb +115 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/support/isolated_lti_config.rb +24 -0
- metadata +28 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe PandaPal::LtiRegistration, :isolated_lti_config do
|
|
4
|
+
let(:organization) { create(:panda_pal_organization) }
|
|
5
|
+
|
|
6
|
+
before { PandaPal.define_tool(:translate) }
|
|
7
|
+
|
|
8
|
+
def register(**attrs)
|
|
9
|
+
organization.lti_registrations.create!(tool: 'translate', client_id: '10000000000001', **attrs)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe '.for_launch' do
|
|
13
|
+
it 'prefers a deployment-specific registration' do
|
|
14
|
+
key_wide = register
|
|
15
|
+
dedicated = register(deployment_id: 'dep-1')
|
|
16
|
+
|
|
17
|
+
expect(described_class.for_launch('10000000000001', 'dep-1')).to eq(dedicated)
|
|
18
|
+
expect(described_class.for_launch('10000000000001', 'dep-2')).to eq(key_wide)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'matches a key-wide registration when the launch has no deployment_id' do
|
|
22
|
+
key_wide = register
|
|
23
|
+
|
|
24
|
+
expect(described_class.for_launch('10000000000001', nil)).to eq(key_wide)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'matches numeric client_ids' do
|
|
28
|
+
key_wide = register
|
|
29
|
+
|
|
30
|
+
expect(described_class.for_launch(10000000000001, nil)).to eq(key_wide)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'returns nil for unknown or blank client_ids' do
|
|
34
|
+
register
|
|
35
|
+
|
|
36
|
+
expect(described_class.for_launch('99', nil)).to be_nil
|
|
37
|
+
expect(described_class.for_launch(nil, 'dep-1')).to be_nil
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe 'validations' do
|
|
42
|
+
it 'rejects a tool that was never defined' do
|
|
43
|
+
registration = organization.lti_registrations.build(tool: 'nope', client_id: '1')
|
|
44
|
+
|
|
45
|
+
expect(registration).not_to be_valid
|
|
46
|
+
expect(registration.errors[:tool]).to be_present
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'allows only one key-wide registration per client_id' do
|
|
50
|
+
register
|
|
51
|
+
|
|
52
|
+
expect { register }.to raise_error(ActiveRecord::RecordInvalid)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'allows only one registration per client_id and deployment' do
|
|
56
|
+
register(deployment_id: 'dep-1')
|
|
57
|
+
|
|
58
|
+
expect { register(deployment_id: 'dep-1') }.to raise_error(ActiveRecord::RecordInvalid)
|
|
59
|
+
expect { register(deployment_id: 'dep-2') }.not_to raise_error
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'stores a blank deployment_id as nil' do
|
|
63
|
+
expect(register(deployment_id: '').deployment_id).to be_nil
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'is destroyed with its organization' do
|
|
68
|
+
register
|
|
69
|
+
|
|
70
|
+
expect { organization.destroy }.to change(described_class, :count).by(-1)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe 'PandaPal::Platform::Canvas::OrgExtension#install_lti', :isolated_lti_config do
|
|
4
|
+
let(:organization) { create(:panda_pal_organization, key: '10000000000001') }
|
|
5
|
+
let(:client) { double('Bearcat::Client') }
|
|
6
|
+
let(:host) { 'https://tool.example.com' }
|
|
7
|
+
let(:installed_tools) { [] }
|
|
8
|
+
|
|
9
|
+
before do
|
|
10
|
+
# _install_lti only checks that Bearcat exists; the client itself is stubbed.
|
|
11
|
+
stub_const('Bearcat', Module.new)
|
|
12
|
+
c = client
|
|
13
|
+
organization.define_singleton_method(:bearcat_client) { c }
|
|
14
|
+
|
|
15
|
+
PandaPal.lti_environments = { domain: 'tool.example.com' }
|
|
16
|
+
PandaPal.lti_options = { title: 'Shared Title' }
|
|
17
|
+
PandaPal.define_tool(:translate, title: 'Translate Title')
|
|
18
|
+
PandaPal.stage_placement(:editor_button, tool: :translate, url: :panda_pal)
|
|
19
|
+
|
|
20
|
+
allow(client).to receive(:account_external_tools).with('self').and_return(double(all_pages_each: installed_tools))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
after { Apartment::Tenant.reset }
|
|
24
|
+
|
|
25
|
+
describe 'LTI 1.3' do
|
|
26
|
+
let(:developer_key) do
|
|
27
|
+
{ id: 10000000000002, api_key: 'dev-key-secret', developer_key_account_binding: { workflow_state: 'on' } }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
before do
|
|
31
|
+
allow(client).to receive(:get).and_return([])
|
|
32
|
+
allow(HTTParty).to receive(:get) do |url, **|
|
|
33
|
+
double(body: { target_link_uri: "#{host}/panda_pal/v1p3/resource_link_request", extensions: [] }.to_json, url: url)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'creates a Developer Key from the tool config, installs it, and registers it without touching the org key' do
|
|
38
|
+
expect(client).to receive(:post).with('api/lti/accounts/self/developer_keys/tool_configuration', hash_including(
|
|
39
|
+
developer_key: hash_including(name: 'Translate Title'),
|
|
40
|
+
)).and_return({ developer_key: developer_key })
|
|
41
|
+
expect(client).to receive(:create_account_external_tool).with('self', { client_id: 10000000000002 }).and_return({ id: 7, deployment_id: 'dep-9' })
|
|
42
|
+
|
|
43
|
+
organization.install_lti(host: host, tool: :translate)
|
|
44
|
+
|
|
45
|
+
expect(HTTParty).to have_received(:get).with(a_string_including('v1p3/config?tool=translate'), format: :plain)
|
|
46
|
+
expect(organization.reload.key).to eq('10000000000001')
|
|
47
|
+
expect(organization.lti_registrations.pluck(:tool, :client_id, :deployment_id)).to eq([['translate', '10000000000002', nil]])
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'records the deployment_id for a dedicated deployment' do
|
|
51
|
+
allow(client).to receive(:post).and_return({ developer_key: developer_key })
|
|
52
|
+
allow(client).to receive(:create_account_external_tool).and_return({ id: 7, deployment_id: 'dep-9' })
|
|
53
|
+
|
|
54
|
+
organization.install_lti(host: host, tool: :translate, dedicated_deployment: true)
|
|
55
|
+
|
|
56
|
+
expect(organization.lti_registrations.pluck(:deployment_id)).to eq(['dep-9'])
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
context 'when the tool is already registered and installed' do
|
|
60
|
+
let(:installed_tools) { [{ id: 7, developer_key_id: 2, deployment_id: 'dep-9' }] }
|
|
61
|
+
|
|
62
|
+
before do
|
|
63
|
+
organization.lti_registrations.create!(tool: 'translate', client_id: '10000000000002')
|
|
64
|
+
allow(client).to receive(:get).and_return([developer_key])
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'updates the registered Developer Key in place and reuses its install' do
|
|
68
|
+
expect(client).not_to receive(:post)
|
|
69
|
+
expect(client).not_to receive(:create_account_external_tool)
|
|
70
|
+
expect(client).to receive(:put).with('api/lti/developer_keys/10000000000002/tool_configuration', anything).and_return(developer_key)
|
|
71
|
+
|
|
72
|
+
organization.install_lti(host: host, tool: :translate, exists: :update)
|
|
73
|
+
|
|
74
|
+
expect(organization.lti_registrations.count).to eq(1)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'parses dotted versions (1.3 used to resolve to v1p1)' do
|
|
79
|
+
allow(client).to receive(:post).and_return({ developer_key: developer_key })
|
|
80
|
+
allow(client).to receive(:create_account_external_tool).and_return({ id: 7, deployment_id: 'dep-9' })
|
|
81
|
+
|
|
82
|
+
expect { organization.install_lti(host: host, tool: :translate, version: '1.3') }.not_to raise_error
|
|
83
|
+
expect(organization.lti_registrations.count).to eq(1)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it 'raises for a tool that was never defined' do
|
|
87
|
+
expect { organization.install_lti(host: host, tool: :nope) }.to raise_error(PandaPal::UnknownLtiTool)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
describe 'LTI 1.0' do
|
|
92
|
+
let(:installed_tools) do
|
|
93
|
+
[
|
|
94
|
+
{ id: 1, consumer_key: organization.key, custom_fields: {} },
|
|
95
|
+
{ id: 2, consumer_key: organization.key, custom_fields: { PandaPal::TOOL_CUSTOM_FIELD => 'translate' } },
|
|
96
|
+
]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "updates only the matching tool's install, pointing it at that tool's config" do
|
|
100
|
+
expect(client).to receive(:edit_account_external_tool).with('self', 2, hash_including(
|
|
101
|
+
name: 'Translate Title',
|
|
102
|
+
consumer_key: organization.key,
|
|
103
|
+
config_url: a_string_including('v1p0/config?tool=translate'),
|
|
104
|
+
))
|
|
105
|
+
|
|
106
|
+
organization.install_lti(host: host, tool: :translate, version: '1.0', exists: :update)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it 'still matches the default install by consumer key alone' do
|
|
110
|
+
expect(client).to receive(:edit_account_external_tool).with('self', 1, hash_including(name: 'Shared Title'))
|
|
111
|
+
|
|
112
|
+
organization.install_lti(host: host, version: 'v1p0', exists: :update)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -27,6 +27,7 @@ require "panda_pal/spec_helper"
|
|
|
27
27
|
ActiveRecord::Schema.verbose = false
|
|
28
28
|
|
|
29
29
|
Dir[File.dirname(__FILE__) + "/factories/**/*.rb"].each {|f| require f }
|
|
30
|
+
Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f }
|
|
30
31
|
|
|
31
32
|
RSpec.configure do |config|
|
|
32
33
|
config.include PandaPal::SpecHelper
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# PandaPal's LTI configuration (placements, tools, options, ...) lives in class
|
|
2
|
+
# variables that host apps set once at boot. Specs that change it tag themselves
|
|
3
|
+
# `:isolated_lti_config` so whatever they register is rolled back afterwards and
|
|
4
|
+
# never leaks into later spec files.
|
|
5
|
+
module IsolatedLtiConfig
|
|
6
|
+
VARS = %i[
|
|
7
|
+
@@lti_navigation
|
|
8
|
+
@@lti_tools
|
|
9
|
+
@@lti_options
|
|
10
|
+
@@lti_custom_params
|
|
11
|
+
@@lti_environments
|
|
12
|
+
].freeze
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
RSpec.shared_context 'isolated LTI config', isolated_lti_config: true do
|
|
16
|
+
around do |example|
|
|
17
|
+
saved = IsolatedLtiConfig::VARS.to_h { |var| [var, PandaPal.class_variable_get(var).deep_dup] }
|
|
18
|
+
begin
|
|
19
|
+
example.run
|
|
20
|
+
ensure
|
|
21
|
+
saved.each { |var, value| PandaPal.class_variable_set(var, value) }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: panda_pal
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.
|
|
4
|
+
version: 5.19.0.beta1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Instructure CustomDev
|
|
@@ -113,6 +113,20 @@ dependencies:
|
|
|
113
113
|
- - ">="
|
|
114
114
|
- !ruby/object:Gem::Version
|
|
115
115
|
version: '0'
|
|
116
|
+
- !ruby/object:Gem::Dependency
|
|
117
|
+
name: json-jwt
|
|
118
|
+
requirement: !ruby/object:Gem::Requirement
|
|
119
|
+
requirements:
|
|
120
|
+
- - ">="
|
|
121
|
+
- !ruby/object:Gem::Version
|
|
122
|
+
version: '1.15'
|
|
123
|
+
type: :runtime
|
|
124
|
+
prerelease: false
|
|
125
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
126
|
+
requirements:
|
|
127
|
+
- - ">="
|
|
128
|
+
- !ruby/object:Gem::Version
|
|
129
|
+
version: '1.15'
|
|
116
130
|
- !ruby/object:Gem::Dependency
|
|
117
131
|
name: httparty
|
|
118
132
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -158,6 +172,7 @@ files:
|
|
|
158
172
|
- app/lib/panda_pal/launch_url_helpers.rb
|
|
159
173
|
- app/lib/panda_pal/lti_jwt_validator.rb
|
|
160
174
|
- app/models/panda_pal/api_call.rb
|
|
175
|
+
- app/models/panda_pal/lti_registration.rb
|
|
161
176
|
- app/models/panda_pal/organization.rb
|
|
162
177
|
- app/models/panda_pal/organization_concerns/multi_database_sharding.rb
|
|
163
178
|
- app/models/panda_pal/organization_concerns/organization_builder.rb
|
|
@@ -183,6 +198,8 @@ files:
|
|
|
183
198
|
- db/migrate/20171205194657_remove_old_organization_settings.rb
|
|
184
199
|
- db/migrate/20220721095653_create_panda_pal_api_calls.rb
|
|
185
200
|
- db/migrate/20250401214421_rename_session_session_key.rb
|
|
201
|
+
- db/migrate/20261006000000_create_panda_pal_lti_registrations.rb
|
|
202
|
+
- db/migrate/20261006000001_add_tool_to_panda_pal_sessions.rb
|
|
186
203
|
- lib/panda_pal.rb
|
|
187
204
|
- lib/panda_pal/concerns/ability_helper.rb
|
|
188
205
|
- lib/panda_pal/engine.rb
|
|
@@ -202,6 +219,7 @@ files:
|
|
|
202
219
|
- spec/controllers/panda_pal/api_call_controller_spec.rb
|
|
203
220
|
- spec/controllers/panda_pal/lti_v1_p0_controller_spec.rb
|
|
204
221
|
- spec/controllers/panda_pal/lti_v1_p3_controller_spec.rb
|
|
222
|
+
- spec/controllers/panda_pal/lti_v1_p3_tools_spec.rb
|
|
205
223
|
- spec/core/apartment_multidb_spec.rb
|
|
206
224
|
- spec/factories/panda_pal_organizations.rb
|
|
207
225
|
- spec/factories/panda_pal_sessions.rb
|
|
@@ -215,14 +233,18 @@ files:
|
|
|
215
233
|
- spec/lib/panda_pal/ags_client_spec.rb
|
|
216
234
|
- spec/lib/panda_pal/helpers/secure_headers_spec.rb
|
|
217
235
|
- spec/lib/panda_pal/helpers/session_replacement_spec.rb
|
|
236
|
+
- spec/lib/panda_pal/lti_tools_spec.rb
|
|
218
237
|
- spec/models/panda_pal/api_call_spec.rb
|
|
238
|
+
- spec/models/panda_pal/lti_registration_spec.rb
|
|
219
239
|
- spec/models/panda_pal/organization/settings_validation_spec.rb
|
|
220
240
|
- spec/models/panda_pal/organization/task_scheduling_spec.rb
|
|
221
241
|
- spec/models/panda_pal/organization_spec.rb
|
|
242
|
+
- spec/models/panda_pal/platform/canvas_install_spec.rb
|
|
222
243
|
- spec/models/panda_pal/platform_spec.rb
|
|
223
244
|
- spec/models/panda_pal/session_account_roles_spec.rb
|
|
224
245
|
- spec/models/panda_pal/session_spec.rb
|
|
225
246
|
- spec/spec_helper.rb
|
|
247
|
+
- spec/support/isolated_lti_config.rb
|
|
226
248
|
homepage: http://instructure.com
|
|
227
249
|
licenses:
|
|
228
250
|
- MIT
|
|
@@ -248,6 +270,7 @@ test_files:
|
|
|
248
270
|
- spec/controllers/panda_pal/api_call_controller_spec.rb
|
|
249
271
|
- spec/controllers/panda_pal/lti_v1_p0_controller_spec.rb
|
|
250
272
|
- spec/controllers/panda_pal/lti_v1_p3_controller_spec.rb
|
|
273
|
+
- spec/controllers/panda_pal/lti_v1_p3_tools_spec.rb
|
|
251
274
|
- spec/core/apartment_multidb_spec.rb
|
|
252
275
|
- spec/factories/panda_pal_organizations.rb
|
|
253
276
|
- spec/factories/panda_pal_sessions.rb
|
|
@@ -261,11 +284,15 @@ test_files:
|
|
|
261
284
|
- spec/lib/panda_pal/ags_client_spec.rb
|
|
262
285
|
- spec/lib/panda_pal/helpers/secure_headers_spec.rb
|
|
263
286
|
- spec/lib/panda_pal/helpers/session_replacement_spec.rb
|
|
287
|
+
- spec/lib/panda_pal/lti_tools_spec.rb
|
|
264
288
|
- spec/models/panda_pal/api_call_spec.rb
|
|
289
|
+
- spec/models/panda_pal/lti_registration_spec.rb
|
|
265
290
|
- spec/models/panda_pal/organization/settings_validation_spec.rb
|
|
266
291
|
- spec/models/panda_pal/organization/task_scheduling_spec.rb
|
|
267
292
|
- spec/models/panda_pal/organization_spec.rb
|
|
293
|
+
- spec/models/panda_pal/platform/canvas_install_spec.rb
|
|
268
294
|
- spec/models/panda_pal/platform_spec.rb
|
|
269
295
|
- spec/models/panda_pal/session_account_roles_spec.rb
|
|
270
296
|
- spec/models/panda_pal/session_spec.rb
|
|
271
297
|
- spec/spec_helper.rb
|
|
298
|
+
- spec/support/isolated_lti_config.rb
|