panda_pal 5.19.1 → 5.20.0
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 +152 -703
- data/app/controllers/panda_pal/lti_v1_p3_controller.rb +3 -0
- data/app/models/panda_pal/organization.rb +1 -1
- data/app/models/panda_pal/platform/canvas.rb +1 -1
- data/app/models/panda_pal/platform.rb +1 -1
- data/config/initializers/apartment.rb +1 -12
- data/db/migrate/20171205194657_remove_old_organization_settings.rb +1 -0
- data/docs/action-cable.md +60 -0
- data/docs/api-calls.md +39 -0
- data/docs/architecture.md +210 -0
- data/docs/before-you-build.md +83 -0
- data/docs/configuration.md +120 -0
- data/docs/deep-linking.md +355 -0
- data/docs/development.md +31 -0
- data/docs/frontend.md +129 -0
- data/docs/grade-passback.md +27 -0
- data/docs/installation.md +129 -0
- data/docs/multi-tenancy.md +117 -0
- data/docs/multiple-tools.md +73 -0
- data/docs/organizations.md +159 -0
- data/docs/roles-and-abilities.md +82 -0
- data/docs/routing-and-launches.md +108 -0
- data/docs/security.md +60 -0
- data/docs/sessions.md +150 -0
- data/docs/task-scheduling.md +49 -0
- data/docs/upgrading.md +69 -0
- data/lib/panda_pal/version.rb +1 -1
- data/panda_pal.gemspec +1 -1
- metadata +21 -2
- /data/{AGS.md → docs/ags.md} +0 -0
data/docs/sessions.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Sessions and panda tokens.
|
|
2
|
+
|
|
3
|
+
Browsers increasingly block cookies inside `<iframe>`s, so PandaPal doesn't rely on the Rails session
|
|
4
|
+
cookie. Instead, it keeps sessions in the database (`PandaPal::Session`, one table per tenant) and
|
|
5
|
+
identifies them with a signed **panda token** that your frontend sends back with every request.
|
|
6
|
+
|
|
7
|
+
## Using the session.
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
current_session # The PandaPal::Session for this request, or nil
|
|
11
|
+
current_session[:key] # Read session data
|
|
12
|
+
current_session[:key] = 1 # Write session data (saved automatically after the action)
|
|
13
|
+
current_session.dig(:a, :b) # Dig into session data
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
| Helper | Purpose |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| `current_session` / `current_panda_session` | The session for this request, or `nil`. |
|
|
19
|
+
| `start_panda_session!` | Start a new session. Launches do this for you. |
|
|
20
|
+
| `session_changed?` | `true` if the session changed during the request. |
|
|
21
|
+
| `save_session` | Save the session now. |
|
|
22
|
+
| `valid_session?` | `true` if there's a saved session for the current Organization and tenant. |
|
|
23
|
+
| `forbid_access_if_lacking_session` | `before_action` that renders a 401 unless `valid_session?`. See [security.md](security.md). |
|
|
24
|
+
|
|
25
|
+
`current_session(create_missing: true)` and `current_session_data` are deprecated. Use
|
|
26
|
+
`start_panda_session!` and `current_session[...]` instead.
|
|
27
|
+
|
|
28
|
+
Outside a controller, use `PandaPal::Session.for_request(request)` or
|
|
29
|
+
`PandaPal::Session.for_panda_token(token)`.
|
|
30
|
+
|
|
31
|
+
## Passing the token to the frontend.
|
|
32
|
+
|
|
33
|
+
Serve the full panda token, `current_session.session_key`, to the page. With
|
|
34
|
+
`@inst_proserv/toolkit`, put it in the `js-env` meta tag as `session_key`, and toolkit sends it for
|
|
35
|
+
you (see [frontend.md](frontend.md#passing-the-environment-to-the-page)):
|
|
36
|
+
|
|
37
|
+
```erb
|
|
38
|
+
<meta name="js-env" content="<%= { session_key: current_session.session_key }.to_json %>">
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
PandaPal looks for the token in these places, in order:
|
|
42
|
+
|
|
43
|
+
1. `X-Panda-Token: <TOKEN>` header
|
|
44
|
+
2. `Authorization: Bearer panda:<TOKEN>` header (note the `panda:` prefix)
|
|
45
|
+
3. `Authorization: token=<TOKEN>` header (legacy, but toolkit `APIBase` still sends it)
|
|
46
|
+
4. `panda_token` param
|
|
47
|
+
5. `session_token` or `session_key` param (legacy, but toolkit `create_href` still uses `session_key`)
|
|
48
|
+
|
|
49
|
+
Prefer a header. Use the `panda_token` param for non-`GET` HTML `<form>`s (with the full token) and for
|
|
50
|
+
`GET` requests where you can't set a header (with a limited-use token, below).
|
|
51
|
+
|
|
52
|
+
## Token format.
|
|
53
|
+
|
|
54
|
+
A **full-key panda token** (`session_key`) looks like this:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
<Base64({ v: 1, o: ORG ID, s: SESSION ID, typ: "KEY" })>.<SESSION SECRET>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
A **limited-use panda token** carries a type and is signed with the session secret instead of
|
|
61
|
+
containing it:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
<Base64({ v: 1, o: ORG ID, s: SESSION ID, typ: TYPE, ... })>.<HMAC(SESSION SECRET, Base64(...))>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
| `typ` | Extra fields | Behavior |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| `T-NONCE` | `nnc` | Single use. Also expires as soon as another `T-NONCE` token is minted for the session. |
|
|
70
|
+
| `T-IP` | `ip`, `exp` | Only valid from the IP address it was minted for. |
|
|
71
|
+
| `T-EXP` | `exp` | Valid until it expires. |
|
|
72
|
+
| `T-URI` | `usig` | Valid for `GET`ting exactly one URL (path and query). `T-URL` is accepted as an alias. Toolkit `signURL` mints these in the browser (see [frontend.md](frontend.md#minting-panda-tokens-in-the-browser)). |
|
|
73
|
+
|
|
74
|
+
Any limited-use token may include `exp`, an ISO 8601 expiration timestamp.
|
|
75
|
+
|
|
76
|
+
## Links and redirects.
|
|
77
|
+
|
|
78
|
+
Each page navigation needs its own token. Use these helpers:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
redirect_with_session_to :somewhere_else_url, other_param: 1
|
|
82
|
+
link_to "Name", url_with_session(:somewhere_else_path, arg, kwarg: 1)
|
|
83
|
+
link_with_session_to :somewhere_else_path
|
|
84
|
+
session_url_for(controller: 'reports', action: 'show')
|
|
85
|
+
|
|
86
|
+
# Or add the token yourself
|
|
87
|
+
redirect_to somewhere_else_path(panda_token: link_nonce)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
These add `panda_token:` (and, for older apps whose routes need it, `organization_id:`). In
|
|
91
|
+
development they use the full session key, so reloads keep working.
|
|
92
|
+
|
|
93
|
+
`link_nonce` mints one limited-use token per type per request. The default type is `T-NONCE`. Change
|
|
94
|
+
it per call or per controller:
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
class ApplicationController < ActionController::Base
|
|
98
|
+
link_nonce_type "T-IP" # or "T-NONCE", "T-EXP"
|
|
99
|
+
|
|
100
|
+
# Lifetime of T-IP and T-EXP tokens. Defaults to 15.
|
|
101
|
+
def session_expiration_period_minutes
|
|
102
|
+
120
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
link_nonce(type: "T-EXP")
|
|
107
|
+
link_nonce(report_url, type: :url) # A T-URI token for one URL
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
To mint a `T-URI` token, pass `type: :url` (or `"T-URL"`). `type: "T-URI"` raises
|
|
111
|
+
`Unsupported link_nonce_type`.
|
|
112
|
+
|
|
113
|
+
The older names `:nonce`, `:fixed_ip`, and `:expiring` still work as aliases.
|
|
114
|
+
|
|
115
|
+
## Reading launch data.
|
|
116
|
+
|
|
117
|
+
| Method | Returns |
|
|
118
|
+
| --- | --- |
|
|
119
|
+
| `launch_params` | The decoded LTI 1.3 `id_token`, or the LTI 1.0 launch params. Also available as `lti_launch_params` in controllers. |
|
|
120
|
+
| `custom_lti_params` | The custom params, from either LTI version. |
|
|
121
|
+
| `get_lti_cust_param(key, default:)` | One custom param. See [configuration.md](configuration.md#custom-params). |
|
|
122
|
+
| `lti_launch_placement` | The placement that was launched. |
|
|
123
|
+
| `lti_tool` | Which [tool](multiple-tools.md) was launched. |
|
|
124
|
+
| `lti_platform` | The LTI 1.3 platform. |
|
|
125
|
+
| `canvas_user_id` | The `canvas_user_id` custom param. |
|
|
126
|
+
| `user` | `User.find_by(canvas_id: canvas_user_id)`, if the app has a `User` model. |
|
|
127
|
+
| `ags_client` | An [AGS](ags.md) client for this launch. |
|
|
128
|
+
| `cache(key) { ... }` | Memoize a value in the session. A `nil` result is cached too. |
|
|
129
|
+
|
|
130
|
+
Roles have their own page: [roles-and-abilities.md](roles-and-abilities.md).
|
|
131
|
+
|
|
132
|
+
## Persisting the session.
|
|
133
|
+
|
|
134
|
+
An `after_action` saves the session automatically. It's set to run last. If another callback needs
|
|
135
|
+
to change the session after that, `skip_after_action :auto_save_session` and save it yourself.
|
|
136
|
+
|
|
137
|
+
## Session cleanup.
|
|
138
|
+
|
|
139
|
+
Old sessions pile up. If `sidekiq-scheduler` is set up (see [task-scheduling.md](task-scheduling.md)),
|
|
140
|
+
each Organization deletes sessions not updated in the last week, every day at 3:00. Otherwise,
|
|
141
|
+
schedule `rake panda_pal:clean_sessions` to run regularly (weekly is fine).
|
|
142
|
+
|
|
143
|
+
## Safari.
|
|
144
|
+
|
|
145
|
+
Safari blocks cookies in `<iframe>`s unless the site already set one outside the frame. Panda tokens
|
|
146
|
+
avoid that problem. Keep these rules in mind:
|
|
147
|
+
|
|
148
|
+
1. Use `redirect_with_session_to` (or another helper above) when you redirect.
|
|
149
|
+
2. Send the token in a header on API calls.
|
|
150
|
+
3. Add a token to every `link_to` in multi-page apps.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Task scheduling.
|
|
2
|
+
|
|
3
|
+
When `sidekiq-scheduler` is loaded, Organizations can declare cron tasks. PandaPal registers one
|
|
4
|
+
schedule per Organization per task, and runs each task inside that Organization's tenant.
|
|
5
|
+
|
|
6
|
+
```ruby
|
|
7
|
+
# app/models/organization_extension.rb
|
|
8
|
+
module OrganizationExtension
|
|
9
|
+
extend ActiveSupport::Concern
|
|
10
|
+
|
|
11
|
+
included do
|
|
12
|
+
# Calls CanvasSyncStarterWorker.perform_async on the cron schedule
|
|
13
|
+
scheduled_task '0 15 05 * * *', :identifier, worker: CanvasSyncStarterWorker
|
|
14
|
+
|
|
15
|
+
# Calls the Organization method `organization_method_and_identifier`
|
|
16
|
+
scheduled_task '0 15 05 * * *', :organization_method_and_identifier
|
|
17
|
+
|
|
18
|
+
# Calls the same method on more than one schedule
|
|
19
|
+
scheduled_task '0 15 05 * * *', :other_identifier, worker: :organization_method
|
|
20
|
+
|
|
21
|
+
# Runs a block (in the context of the Organization)
|
|
22
|
+
scheduled_task '0 15 05 * * *', :block_identifier do
|
|
23
|
+
# Do stuff
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Computes the schedule with a Proc (in the context of the Organization)
|
|
27
|
+
scheduled_task -> { settings[:cron] }, :proc_identifier
|
|
28
|
+
|
|
29
|
+
# Uses an explicit timezone, and a non-default Sidekiq queue
|
|
30
|
+
scheduled_task '0 15 05 * * * America/Denver', :tz_identifier, worker: :organization_method, queue: 'low'
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Rules.
|
|
36
|
+
|
|
37
|
+
- Identifiers must be unique. If you leave one out (`nil`), PandaPal makes one from the line number.
|
|
38
|
+
- If the cron string has no timezone, PandaPal appends `settings[:timezone]`, or else
|
|
39
|
+
`settings[:canvas][:root_account_timezone]`, when either is set.
|
|
40
|
+
- `settings[:task_schedules][:identifier]` overrides the schedule for one Organization. Set it to
|
|
41
|
+
`false` to turn the task off.
|
|
42
|
+
- `task_scheduled?(:identifier)` and `remove_scheduled_task(:identifier)` check for and remove tasks
|
|
43
|
+
declared elsewhere, such as by a gem.
|
|
44
|
+
- Schedules are registered but turned off in development.
|
|
45
|
+
- Schedules re-sync each time the Sidekiq server boots or reloads, and schedules for deleted
|
|
46
|
+
Organizations are removed.
|
|
47
|
+
|
|
48
|
+
PandaPal declares one task itself: `clean_old_sessions`, which runs every day at 3:00 (see
|
|
49
|
+
[sessions.md](sessions.md#session-cleanup)).
|
data/docs/upgrading.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Upgrading.
|
|
2
|
+
|
|
3
|
+
PandaPal ships migrations. Apps pick them up automatically, so run `rails db:migrate` after every
|
|
4
|
+
upgrade. Apartment runs the per-tenant migrations in each Organization's schema.
|
|
5
|
+
|
|
6
|
+
## To 5.19.1.
|
|
7
|
+
|
|
8
|
+
PandaPal no longer hooks CableRoom. The hook only ever applied to the CableRoom beta. If your app
|
|
9
|
+
still relies on it, add it yourself:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
CableRoom::Room::Base.around_work do |room, blk|
|
|
13
|
+
Apartment::Tenant.with_lazy_tenant(room.tenant, &blk)
|
|
14
|
+
end
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## To 5.19.
|
|
18
|
+
|
|
19
|
+
- **Migrations:** `create_panda_pal_lti_registrations` (in `public`) and
|
|
20
|
+
`add_tool_to_panda_pal_sessions` (per tenant).
|
|
21
|
+
- **Multiple tools:** new and opt-in. Single-tool apps don't need to change. See
|
|
22
|
+
[multiple-tools.md](multiple-tools.md).
|
|
23
|
+
- **`install_lti` default context** is now `:org_account` (the Organization's `canvas_account_id`)
|
|
24
|
+
instead of the API token's root account. Pass `context: :root_account` to keep the old behavior.
|
|
25
|
+
|
|
26
|
+
## To 5.16.
|
|
27
|
+
|
|
28
|
+
LTI 1.3 Deep Linking. Every content item must include `launch_type` in its `custom` hash. See
|
|
29
|
+
[deep-linking.md](deep-linking.md).
|
|
30
|
+
|
|
31
|
+
## To 5.14.
|
|
32
|
+
|
|
33
|
+
- **Migration:** renames `panda_pal_sessions.session_key` to `session_secret`.
|
|
34
|
+
- **Sessions moved into tenant schemas.** `PandaPal::Session` is no longer an Apartment excluded
|
|
35
|
+
model, so existing sessions aren't found after the upgrade. Users need to launch again.
|
|
36
|
+
- **New panda token format.** `session_key` now returns a full-key panda token, and link tokens are
|
|
37
|
+
limited-use panda tokens. Old-style `session_key` and `session_token` params still work. See
|
|
38
|
+
[sessions.md](sessions.md).
|
|
39
|
+
|
|
40
|
+
## To 5.13.
|
|
41
|
+
|
|
42
|
+
- Use `current_session[:key]` instead of `current_session_data[:key]`.
|
|
43
|
+
- Use `start_panda_session!` instead of `current_session(create_missing: true)`.
|
|
44
|
+
- In launch controllers, put `before_action :validate_launch!` before `around_action :switch_tenant`.
|
|
45
|
+
|
|
46
|
+
## To 5.12.
|
|
47
|
+
|
|
48
|
+
Adds optional [sharding](multi-tenancy.md#sharding), and requires `ros-apartment ~> 3.0`.
|
|
49
|
+
|
|
50
|
+
## From 4 to 5.
|
|
51
|
+
|
|
52
|
+
If your tool follows a standard pattern (like `canvas_group_enrollment`), you shouldn't need to
|
|
53
|
+
change anything. If your launch controller redirects, make sure it uses `redirect_with_session_to`.
|
|
54
|
+
See [routing-and-launches.md](routing-and-launches.md#validating-launches-yourself) for a launch
|
|
55
|
+
controller.
|
|
56
|
+
|
|
57
|
+
## To 3.
|
|
58
|
+
|
|
59
|
+
Version 3 encrypts `Organization#settings` with the app's `secret_key_base` (see
|
|
60
|
+
[organizations.md](organizations.md#encryption)).
|
|
61
|
+
|
|
62
|
+
1. Save your existing settings somewhere safe.
|
|
63
|
+
2. Make sure `secret_key_base` is set to a stable value that isn't committed in plain text. If it's
|
|
64
|
+
lost, the settings can't be decrypted.
|
|
65
|
+
3. Undo any encryption you applied to settings yourself. The migration expects plain settings.
|
|
66
|
+
4. Run `rails db:migrate`.
|
|
67
|
+
5. In the console, check that `PandaPal::Organization.first.settings` shows decrypted settings.
|
|
68
|
+
|
|
69
|
+
To roll back, run `rails db:rollback STEP=2` and pin PandaPal below 3.
|
data/lib/panda_pal/version.rb
CHANGED
data/panda_pal.gemspec
CHANGED
|
@@ -17,7 +17,7 @@ Gem::Specification.new do |s|
|
|
|
17
17
|
s.summary = "LTI mountable engine"
|
|
18
18
|
s.license = "MIT"
|
|
19
19
|
|
|
20
|
-
s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md", "
|
|
20
|
+
s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md", "docs/**/*", "panda_pal.gemspec"]
|
|
21
21
|
# s.test_files = Dir["spec/**/*"].reject{|f| f =~ /\/log\//}
|
|
22
22
|
|
|
23
23
|
s.add_dependency "rails", ">= 4.2"
|
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.20.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Instructure CustomDev
|
|
@@ -147,7 +147,6 @@ executables: []
|
|
|
147
147
|
extensions: []
|
|
148
148
|
extra_rdoc_files: []
|
|
149
149
|
files:
|
|
150
|
-
- AGS.md
|
|
151
150
|
- MIT-LICENSE
|
|
152
151
|
- README.md
|
|
153
152
|
- Rakefile
|
|
@@ -200,6 +199,26 @@ files:
|
|
|
200
199
|
- db/migrate/20250401214421_rename_session_session_key.rb
|
|
201
200
|
- db/migrate/20261006000000_create_panda_pal_lti_registrations.rb
|
|
202
201
|
- db/migrate/20261006000001_add_tool_to_panda_pal_sessions.rb
|
|
202
|
+
- docs/action-cable.md
|
|
203
|
+
- docs/ags.md
|
|
204
|
+
- docs/api-calls.md
|
|
205
|
+
- docs/architecture.md
|
|
206
|
+
- docs/before-you-build.md
|
|
207
|
+
- docs/configuration.md
|
|
208
|
+
- docs/deep-linking.md
|
|
209
|
+
- docs/development.md
|
|
210
|
+
- docs/frontend.md
|
|
211
|
+
- docs/grade-passback.md
|
|
212
|
+
- docs/installation.md
|
|
213
|
+
- docs/multi-tenancy.md
|
|
214
|
+
- docs/multiple-tools.md
|
|
215
|
+
- docs/organizations.md
|
|
216
|
+
- docs/roles-and-abilities.md
|
|
217
|
+
- docs/routing-and-launches.md
|
|
218
|
+
- docs/security.md
|
|
219
|
+
- docs/sessions.md
|
|
220
|
+
- docs/task-scheduling.md
|
|
221
|
+
- docs/upgrading.md
|
|
203
222
|
- lib/panda_pal.rb
|
|
204
223
|
- lib/panda_pal/concerns/ability_helper.rb
|
|
205
224
|
- lib/panda_pal/engine.rb
|
/data/{AGS.md → docs/ags.md}
RENAMED
|
File without changes
|