panda_pal 5.1.0 → 5.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +139 -93
- 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 +11 -0
- data/app/models/panda_pal/organization.rb +6 -3
- data/app/models/panda_pal/{organization → organization_concerns}/settings_validation.rb +21 -5
- data/app/models/panda_pal/{organization → organization_concerns}/task_scheduling.rb +32 -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 +138 -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 +3 -2
- metadata +32 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 581c5acd9f7a7ade63f59c20f00f2dcbe7ee4e77670a6e5b98e3b29491f2edc9
|
4
|
+
data.tar.gz: d42d100b86c391585fa5f45d23368711487c78eff96916aa9868ba1e22f868c1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ec8c2b412e14dcfc0398fbb496d68d62aec0b897a3f30b5471df867d32b3db8279bd774c0b620a074b204cbbcf5f51c4edb80cfc92285205849f77c11d68122
|
7
|
+
data.tar.gz: 397d0cfccec6719e1ca201925fd7b5d23e87dff5a4170169c9f7aafd741a2956c8540abcf19dcbbff672faa3e85c54d0e81bc73d033a5e7d5f1ba21d5437577e
|
data/README.md
CHANGED
@@ -11,7 +11,12 @@ PandaPal.lti_properties = {oauth_compliant: 'true'}
|
|
11
11
|
PandaPal.lti_environments = { domain: ENV['PROD_DOMAIN'], beta_domain: ENV['BETA_DOMAIN'], test_domain: ENV['TEST_DOMAIN'] }
|
12
12
|
PandaPal.lti_custom_params = { custom_canvas_role: '$Canvas.membership.roles' }
|
13
13
|
# :user_navigation, :course_navigation for user context and course context endpoints respectively
|
14
|
-
PandaPal::stage_navigation(:account_navigation, {
|
14
|
+
PandaPal::stage_navigation(:account_navigation, {
|
15
|
+
enabled: true,
|
16
|
+
# url: :account_index, # Optional if using lti_nav
|
17
|
+
text: 'Teacher Reports',
|
18
|
+
visibility: 'admins',
|
19
|
+
})
|
15
20
|
```
|
16
21
|
|
17
22
|
Configuration data for an installation can be set by creating a `PandaPal::Organization` record. Due to the nature of the data segregation, once created, the name of the organization should not be changed (and will raise a validation error).
|
@@ -62,16 +67,16 @@ module PandaPal
|
|
62
67
|
end
|
63
68
|
```
|
64
69
|
|
65
|
-
|
66
|
-
id
|
67
|
-
name
|
68
|
-
key
|
69
|
-
secret
|
70
|
-
canvas_account_id
|
71
|
-
settings
|
72
|
-
salesforce_id
|
70
|
+
### Organization Attributes
|
71
|
+
`id`: Primary Key
|
72
|
+
`name`: Name of the organization. Used to on requests to select the tenant
|
73
|
+
`key`: Key field from CanvasLMS
|
74
|
+
`secret`: Secret field from CanvasLMS
|
75
|
+
`canvas_account_id`: ID of the corresponding Canvas account.
|
76
|
+
`settings`: Hash of settings for this Organization
|
77
|
+
`salesforce_id`: ID of this organization in Salesforce
|
73
78
|
|
74
|
-
XML for an installation can be generated by visiting
|
79
|
+
XML for an installation can be generated by visiting `/lti/config` in your application.
|
75
80
|
|
76
81
|
### Routing
|
77
82
|
|
@@ -80,7 +85,7 @@ The following routes should be added to the routes.rb file of the implementing L
|
|
80
85
|
```ruby
|
81
86
|
# config/routes.rb
|
82
87
|
mount PandaPal::Engine, at: '/lti'
|
83
|
-
lti_nav account_navigation: 'accounts#launch'
|
88
|
+
lti_nav account_navigation: 'accounts#launch' # Use lti_nav to provide a custom Launch implementation, otherwise use the url: param of stage_navigation to let PandaPal handle launch.
|
84
89
|
root to: 'panda_pal/lti#launch'
|
85
90
|
```
|
86
91
|
|
@@ -115,9 +120,10 @@ It is also possible to switch tenants by implementing `around_action :switch_ten
|
|
115
120
|
However, it should be noted that calling `switch_tenant` must be used in an `around_action` hook (or given a block), and is only intended to be used for LTI launches, and not subsequent requests.
|
116
121
|
|
117
122
|
### Rake tasks and jobs
|
118
|
-
|
123
|
+
**Delayed Job Support has been removed. This allows each project to assess it's need to handle background jobs.**
|
124
|
+
|
119
125
|
The Apartment Gem makes it so background jobs need to be run within the current tenant. If using sidekiq, there is a gem that
|
120
|
-
does this automatically called apartment-sidkiq
|
126
|
+
does this automatically called `apartment-sidkiq`. If Delayed Jobs, see how it's done in version 1 of PandaPal.
|
121
127
|
|
122
128
|
For rake tasks, the current tenant will be set to `public`, and thus special handling must be taken when running the task or queueing jobs as part of it.
|
123
129
|
|
@@ -159,25 +165,52 @@ Controllers will automatically have access to a number of methods that can be he
|
|
159
165
|
* `save_session` - Saves the session given by `current_session` to the database.
|
160
166
|
|
161
167
|
## Sessions and `current_session`
|
162
|
-
|
163
|
-
|
168
|
+
Because of the ever-increasing security around `<iframe>`s and cookies, a custom session implementation has been rolled into `PandaPal`.
|
169
|
+
The implementation is mostly passive, but requires some changes and caveats as opposed to the native Rails implementation.
|
164
170
|
|
165
|
-
|
171
|
+
The current session object can be accessed from any controller using `current_session`. This object isn't the actual data container (as `session` _is_ in Rails).
|
172
|
+
Session data can be accessed and modified using `current_session.data[:key]` or `current_session_data[:key]`.
|
166
173
|
|
167
|
-
|
168
|
-
|
169
|
-
params[:session_key] || session_key_header || flash[:session_key] || session[:session_key]
|
170
|
-
end
|
174
|
+
Because cookies are unreliable, it becomes the responsibility of the LTI developer to ensure that a custom "cookie" is passed to the frontend with each response
|
175
|
+
and returned to the backend with each request, otherwise the backend won't be able to access the current session. There are a few ways to do this.
|
171
176
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
end
|
176
|
-
end
|
177
|
+
Generally, the method for passing the session "cookie" to the frontend looks something like:
|
178
|
+
```html
|
179
|
+
<meta name="session_key" content="<%= current_session.session_key %>">
|
177
180
|
```
|
178
181
|
|
182
|
+
### Returning to the backend
|
183
|
+
The `session_key` can be passed to the backend in multiple ways:
|
184
|
+
1. (Recommended) Via the `Authorization` Header (usually defining it as a default in your AJAX/XHR/`fetch` library):
|
185
|
+
```http
|
186
|
+
Authorization: token=SESSION_KEY_HERE
|
187
|
+
```
|
188
|
+
2. Via a `session_key` parameter in a `POST` request (Useful for native HTML `<form>`s).
|
189
|
+
3. (Used for Dev, but not highly discouraged in Prod) via a `GET`/URL Query parameter: `?session_key=SESSION_KEY_HERE`.
|
190
|
+
|
191
|
+
The `session_key` does not contain any secrets itself, so it is safe to pass to the frontend, but it is encouraged to keep it away from the
|
192
|
+
end-user as much as possible because it should not be shared.
|
193
|
+
|
194
|
+
### Redirecting and Multi-Page Applications
|
195
|
+
Special instructions must be followed when using `link_to` or `redirect_to` to ensure that the Session is passed correctly:
|
196
|
+
|
197
|
+
Add a `session_token:` (notice the use of _`_token`_ as opposed to _`_key`_!) parameter when using `link_to` or `redirect_to`:
|
198
|
+
```ruby
|
199
|
+
link_to "Link Name", somewhere_else_path(session_token: link_nonce)
|
200
|
+
redirect_to somewhere_else_path(session_token: link_nonce)
|
201
|
+
```
|
202
|
+
You can also use the `redirect_with_session_to` helper, which will automatically add `organization_id:` and `session_token:` params:
|
203
|
+
```ruby
|
204
|
+
redirect_with_session_to :somewhere_else_path, other_param: 1
|
205
|
+
```
|
206
|
+
For each request (not each call), `link_nonce` generates a nonce and stores it in the Session. The generated value can be used
|
207
|
+
for at-most-one future request. This means that browser back-forward navigation **will not work** (if it actually ever worked for
|
208
|
+
iframe-based LTIs in the first place).
|
209
|
+
|
179
210
|
### Persisting the session
|
180
|
-
|
211
|
+
The session is automatically saved using an `after_action` callback. This callback is poised to run last, but if that causes issues
|
212
|
+
(eg some other callback running afterwards and needing to modify the session), `skip_after_action :auto_save_session` can be used
|
213
|
+
and a custom implementation can be supplemented.
|
181
214
|
|
182
215
|
### Session cleanup
|
183
216
|
Over time, the sessions table will become bloated with sessions that are no longer active.
|
@@ -185,11 +218,6 @@ As such, `rake panda_pal:clean_sessions` should be run periodically to clean up
|
|
185
218
|
|
186
219
|
## Organizations and `current_organization`
|
187
220
|
Similar to `current_session`, `current_organization` can be returned with a number of methods, shown below
|
188
|
-
```ruby
|
189
|
-
def organization_key
|
190
|
-
params[:oauth_consumer_key] || params[:organization_id] || current_session_data[:organization_key] || session[:organization_key]
|
191
|
-
end
|
192
|
-
```
|
193
221
|
|
194
222
|
## Security
|
195
223
|
|
@@ -213,49 +241,21 @@ It can be overridden on a action-by-action basis by using `skip_before_action`.
|
|
213
241
|
skip_before_action :forbid_access_if_lacking_session, only: :launch
|
214
242
|
```
|
215
243
|
|
216
|
-
###
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
ENV['SECRET_KEY_BASE']. For production environments, config/secrets.yml should not have a plain-text secret,
|
226
|
-
it should be referencing an ENV variable. Make sure your secret is not plain-text committed to a repository!
|
227
|
-
|
228
|
-
The secret key is used to encrypt / decrypt the settings hash as necessary.
|
229
|
-
|
230
|
-
Before upgrading to version 3, you should confirm that the secret key is set to a consistent value (if the
|
231
|
-
value is lost your settings will be hosed).
|
232
|
-
|
233
|
-
You should also rollback any local encryption you have done on the settings hash. The settings hash
|
234
|
-
should just be plainly visible when you upgrade to V3. Otherwise the panda_pal migrations will
|
235
|
-
probably fail.
|
236
|
-
|
237
|
-
Once you have upgraded your gem, you will need to run migrations. Before doing that, I would store off your
|
238
|
-
unencrypted settings (just in case).
|
239
|
-
|
240
|
-
`rake db:migrate`
|
241
|
-
|
242
|
-
If all goes well, you should be set! Log into console, and verify that you can still access settings:
|
243
|
-
|
244
|
-
`PandaPal::Organization.first.settings`
|
245
|
-
|
246
|
-
should show unencrypted settings in your console.
|
247
|
-
|
248
|
-
If anything goes wrong, you should be able to rollback the change:
|
249
|
-
|
250
|
-
`rake db:rollback STEP=2`
|
251
|
-
|
252
|
-
If you need to give up on the change, just make sure to change your gem version for panda_pal to be < 3.
|
244
|
+
### Secure Headers
|
245
|
+
PandaPal bundles the `secure_headers` Gem and provides a default config. The PandaPal default works for really basic apps, but you may need to provide
|
246
|
+
a custom configuration. This can be done by creating a `secure_headers.rb` initializer in you App like so:
|
247
|
+
```ruby
|
248
|
+
SecureHeaders::Configuration.default do |config|
|
249
|
+
PandaPal::SecureHeaders.apply_defaults(config) # Optional, but recommended
|
250
|
+
# ...
|
251
|
+
end
|
252
|
+
```
|
253
253
|
|
254
|
-
|
254
|
+
## Validating settings in your LTI.
|
255
255
|
|
256
256
|
You can specify a structure that you would like to have enforced for your settings.
|
257
257
|
|
258
|
-
In your panda_pal initializer (i.e. config/initializers/panda_pal.rb or config/initializers/lti.rb)
|
258
|
+
In your panda_pal initializer (i.e. `config/initializers/panda_pal.rb` or `config/initializers/lti.rb`)
|
259
259
|
|
260
260
|
You can specify options that can include a structure for your settings. If specified, PandaPal will
|
261
261
|
enforce this structure on any new / updated organizations.
|
@@ -339,9 +339,41 @@ This is determined by `PandaPal.lti_options[:platform]`.
|
|
339
339
|
Set this to `platform: 'canvas.instructure.com'` (default)
|
340
340
|
OR `platform: 'bridgeapp.com'`
|
341
341
|
|
342
|
-
|
342
|
+
## Development:
|
343
|
+
### Running Specs:
|
344
|
+
Initialize the Specs DB:
|
345
|
+
`cd spec/dummy; bundle exec rake db:drop; bundle exec rake db:create; bundle exec rake db:schema:load`
|
346
|
+
Then `bundle exec rspec`
|
343
347
|
|
344
|
-
Safari
|
348
|
+
## Safari Support
|
349
|
+
Safari is weird (it blocks cookies in `<iframe>`s unless the site has set a cookie _outside_ of an `<iframe>` first), and you'll run into
|
350
|
+
issues with Rails-native Sessions and CSRF because of that.
|
351
|
+
|
352
|
+
This means that safari will likely refuse to send info about your rails session
|
353
|
+
back to the LTI, and the application will start up a new session each time the
|
354
|
+
browser navigates. This likely means a new session each time the LTI launches.
|
355
|
+
|
356
|
+
### PandaPal 5
|
357
|
+
It has been a constant struggle to force safari to store and allow
|
358
|
+
access to a rails session while the application is embedded in Canvas.
|
359
|
+
|
360
|
+
As of PandaPal 5, a session cookie is no longer required by panda_pal.
|
361
|
+
|
362
|
+
See the Section on [Sessions and `current_session`](#sessions-and-current_session)
|
363
|
+
|
364
|
+
You will want to watch out for a few scenarios:
|
365
|
+
1) Make sure you are using `redirect_with_session_to` if you need to redirect
|
366
|
+
and have your PandaPal session_key persisted server side.
|
367
|
+
2) Use the `Authorization` header with `token={session_key}` to send your
|
368
|
+
PandaPal session info into api calls.
|
369
|
+
3) If you use `link_to` and navigate in your LTI (apps that are not single page)
|
370
|
+
make sure you include the `link_nonce` like so:
|
371
|
+
```ruby
|
372
|
+
link_to "Link Name", somewhere_else_path(session_token: link_nonce)
|
373
|
+
```
|
374
|
+
|
375
|
+
### Previous Safari Instructions
|
376
|
+
Safari is weird and you'll potentially run into issues getting `POST` requests to properly validate CSRF if you don't do the following:
|
345
377
|
|
346
378
|
- Make sure you have both a `launch` controller and your normal controllers. The `launch` controller should call `before_action :validate_launch!` and then redirect
|
347
379
|
to your other controller.
|
@@ -349,28 +381,47 @@ Safari is weird, and you'll potentially run into issues getting `POST` requests
|
|
349
381
|
|
350
382
|
This will allow `PandaPal` to apply an iframe cookie fix that will allow CSRF validation to work.
|
351
383
|
|
384
|
+
## Migrating Major Versions
|
352
385
|
|
353
|
-
###
|
386
|
+
### Upgrading to version 3
|
354
387
|
|
355
|
-
|
356
|
-
|
388
|
+
Before upgrading save existing settings somewhere safe in case you need to
|
389
|
+
restore them for whatever reason.
|
357
390
|
|
358
|
-
|
391
|
+
panda_pal v3 introduces an encrypted settings hash. This should provide more security in the case where a
|
392
|
+
customer may have gained access to Organization information in the database. Settings cannot be decrypted
|
393
|
+
without knowing the secret decryption key. For panda_pal, we are relying on the secret_key_base to be set
|
394
|
+
(`typically in config/secrets.yml`), if that is not available we are falling back to directly use
|
395
|
+
`ENV['SECRET_KEY_BASE']`. For production environments, `config/secrets.yml` should not have a plain-text secret,
|
396
|
+
it should be referencing an ENV variable. Make sure your secret is not plain-text committed to a repository!
|
359
397
|
|
360
|
-
|
361
|
-
back to the LTI, and the application will start up a new session each time the
|
362
|
-
browser navigates. This likely means a new session each time the LTI launches.
|
398
|
+
The secret key is used to encrypt / decrypt the settings hash as necessary.
|
363
399
|
|
364
|
-
|
400
|
+
Before upgrading to version 3, you should confirm that the secret key is set to a consistent value (if the
|
401
|
+
value is lost your settings will be hosed).
|
365
402
|
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
PandaPal session info into api calls.
|
370
|
-
3) If you use link_to and navigate in your LTI (apps that are not single page)
|
371
|
-
make sure you include an encrypted_session_key parameter in your links.
|
403
|
+
You should also rollback any local encryption you have done on the settings hash. The settings hash
|
404
|
+
should just be plainly visible when you upgrade to V3. Otherwise the panda_pal migrations will
|
405
|
+
probably fail.
|
372
406
|
|
373
|
-
|
407
|
+
Once you have upgraded your gem, you will need to run migrations. Before doing that, I would store off your
|
408
|
+
unencrypted settings (just in case).
|
409
|
+
|
410
|
+
`rake db:migrate`
|
411
|
+
|
412
|
+
If all goes well, you should be set! Log into console, and verify that you can still access settings:
|
413
|
+
|
414
|
+
`PandaPal::Organization.first.settings`
|
415
|
+
|
416
|
+
should show unencrypted settings in your console.
|
417
|
+
|
418
|
+
If anything goes wrong, you should be able to rollback the change:
|
419
|
+
|
420
|
+
`rake db:rollback STEP=2`
|
421
|
+
|
422
|
+
If you need to give up on the change, just make sure to change your gem version for panda_pal to be < 3.
|
423
|
+
|
424
|
+
### Upgrading from PandaPal 4 to 5:
|
374
425
|
|
375
426
|
If your tool is setup according to a pretty standard pattern (see pace_plans,
|
376
427
|
canvas_group_enrollment, etc), you shouldn't have to do anything to upgrade.
|
@@ -380,7 +431,7 @@ using "redirect_with_session_to".
|
|
380
431
|
|
381
432
|
Here is an example launch / account controller setup, assuming an account launch.
|
382
433
|
|
383
|
-
```
|
434
|
+
```ruby
|
384
435
|
class LaunchController < ApplicationController
|
385
436
|
# We don't verify CSRF on launch because the LTI launch is done via a POST
|
386
437
|
# request, and Canvas wouldn't know anything about the CSRF
|
@@ -407,8 +458,3 @@ class AccountController < ApplicationController
|
|
407
458
|
end
|
408
459
|
end
|
409
460
|
```
|
410
|
-
|
411
|
-
## Running Specs:
|
412
|
-
Initialize the Specs DB:
|
413
|
-
`cd spec/dummy; bundle exec rake db:drop; bundle exec rake db:create; bundle exec rake db:schema:load`
|
414
|
-
Then `bundle exec rspec`
|
@@ -2,23 +2,5 @@ require_dependency "panda_pal/application_controller"
|
|
2
2
|
|
3
3
|
module PandaPal
|
4
4
|
class LtiController < ApplicationController
|
5
|
-
def tool_config
|
6
|
-
if PandaPal.lti_environments.empty?
|
7
|
-
render plain: 'Domains must be set in lti_environments'
|
8
|
-
return
|
9
|
-
end
|
10
|
-
platform = PandaPal.lti_options.delete(:platform) || 'canvas.instructure.com'
|
11
|
-
request_url = "#{request.scheme}://#{request.host_with_port}"
|
12
|
-
case platform
|
13
|
-
when 'canvas.instructure.com'
|
14
|
-
xml_config = LtiXml::CanvasPlatform.new(platform, request_url, main_app)
|
15
|
-
when 'bridgeapp.com'
|
16
|
-
xml_config = LtiXml::BridgePlatform.new(platform, request_url, main_app)
|
17
|
-
else
|
18
|
-
render plain: 'platform must be set under lti_options'
|
19
|
-
return
|
20
|
-
end
|
21
|
-
render xml: xml_config.xml
|
22
|
-
end
|
23
5
|
end
|
24
6
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_dependency "panda_pal/application_controller"
|
2
|
+
|
3
|
+
module PandaPal
|
4
|
+
class LtiV1P0Controller < ApplicationController
|
5
|
+
def launch
|
6
|
+
current_session_data.merge!({
|
7
|
+
lti_version: 'v1p0',
|
8
|
+
lti_launch_placement: params[:launch_type],
|
9
|
+
launch_params: params.to_unsafe_h,
|
10
|
+
})
|
11
|
+
|
12
|
+
redirect_with_session_to(:"#{LaunchUrlHelpers.launch_route(params[:launch_type])}_url", route_context: main_app)
|
13
|
+
end
|
14
|
+
|
15
|
+
def tool_config
|
16
|
+
if PandaPal.lti_environments.empty?
|
17
|
+
render plain: 'Domains must be set in lti_environments'
|
18
|
+
return
|
19
|
+
end
|
20
|
+
platform = PandaPal.lti_options.delete(:platform) || 'canvas.instructure.com'
|
21
|
+
request_url = "#{request.scheme}://#{request.host_with_port}"
|
22
|
+
case platform
|
23
|
+
when 'canvas.instructure.com'
|
24
|
+
xml_config = LtiXml::CanvasPlatform.new(platform, request_url, main_app)
|
25
|
+
when 'bridgeapp.com'
|
26
|
+
xml_config = LtiXml::BridgePlatform.new(platform, request_url, main_app)
|
27
|
+
else
|
28
|
+
render plain: 'platform must be set under lti_options'
|
29
|
+
return
|
30
|
+
end
|
31
|
+
render xml: xml_config.xml
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require_dependency "panda_pal/application_controller"
|
2
|
+
|
3
|
+
module PandaPal
|
4
|
+
class LtiV1P3Controller < ApplicationController
|
5
|
+
skip_before_action :verify_authenticity_token
|
6
|
+
|
7
|
+
before_action :validate_launch!, only: [:resource_link_request]
|
8
|
+
around_action :switch_tenant, only: [:resource_link_request]
|
9
|
+
|
10
|
+
def login
|
11
|
+
current_session_data[:lti_oauth_nonce] = SecureRandom.uuid
|
12
|
+
|
13
|
+
@form_action = current_lti_platform.authentication_redirect_url
|
14
|
+
@method = :post
|
15
|
+
@form_data = {
|
16
|
+
scope: 'openid',
|
17
|
+
response_type: 'id_token',
|
18
|
+
response_mode: 'form_post',
|
19
|
+
prompt: 'none',
|
20
|
+
redirect_uri: params[:target_link_uri] || v1p3_resource_link_request_url,
|
21
|
+
client_id: params.require(:client_id),
|
22
|
+
login_hint: params.require(:login_hint),
|
23
|
+
lti_message_hint: params.require(:lti_message_hint),
|
24
|
+
state: current_session.session_key,
|
25
|
+
nonce: current_session_data[:lti_oauth_nonce]
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def resource_link_request
|
30
|
+
# Redirect to correct region/env?
|
31
|
+
if params[:launch_type]
|
32
|
+
current_session_data.merge!({
|
33
|
+
lti_version: 'v1p3',
|
34
|
+
lti_launch_placement: params[:launch_type],
|
35
|
+
launch_params: @decoded_lti_jwt,
|
36
|
+
})
|
37
|
+
|
38
|
+
redirect_with_session_to(:"#{LaunchUrlHelpers.launch_route(params[:launch_type])}_url", route_context: main_app)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def tool_config
|
43
|
+
if PandaPal.lti_environments.empty?
|
44
|
+
render plain: 'Domains must be set in lti_environments'
|
45
|
+
return
|
46
|
+
end
|
47
|
+
|
48
|
+
platform = PandaPal.lti_options.delete(:platform) || 'canvas.instructure.com'
|
49
|
+
request_url = "#{request.scheme}://#{request.host_with_port}"
|
50
|
+
parsed_request_url = URI.parse(request_url)
|
51
|
+
|
52
|
+
mapped_placements = PandaPal.lti_paths.map do |k, opts|
|
53
|
+
opts = opts.dup
|
54
|
+
opts.delete(:route_helper_key)
|
55
|
+
opts.merge!({
|
56
|
+
placement: k,
|
57
|
+
target_link_uri: LaunchUrlHelpers.absolute_launch_url(k.to_sym, host: parsed_request_url, launch_handler: v1p3_resource_link_request_path),
|
58
|
+
})
|
59
|
+
opts
|
60
|
+
end
|
61
|
+
|
62
|
+
config_json = {
|
63
|
+
title: PandaPal.lti_options[:title],
|
64
|
+
scopes: [],
|
65
|
+
public_jwk_url: v1p3_public_jwks_url,
|
66
|
+
description: PandaPal.lti_options[:description] || 'PandaPal LTI',
|
67
|
+
target_link_uri: v1p3_resource_link_request_url, #app_url(:resource_link_request, request),
|
68
|
+
oidc_initiation_url: v1p3_oidc_login_url,
|
69
|
+
extensions: [{
|
70
|
+
platform: platform,
|
71
|
+
privacy_level: "public",
|
72
|
+
settings: {
|
73
|
+
placements: mapped_placements,
|
74
|
+
environments: PandaPal.lti_environments,
|
75
|
+
},
|
76
|
+
}],
|
77
|
+
custom_fields: PandaPal.lti_custom_params, # PandaPal.lti_options[:custom_fields],
|
78
|
+
}
|
79
|
+
|
80
|
+
render json: config_json
|
81
|
+
end
|
82
|
+
|
83
|
+
def public_jwks
|
84
|
+
render json: {
|
85
|
+
keys: [JWT::JWK.new(PandaPal.lti_private_key).export]
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def auth_redirect_query
|
92
|
+
return unless params[:target_link_uri]&.include? 'platform_redirect_url='
|
93
|
+
|
94
|
+
platform_redirect_url = Rack::Utils.parse_query(URI(params[:target_link_uri]).query)&.dig('platform_redirect_url')
|
95
|
+
"?platform_redirect_url=#{platform_redirect_url}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|