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.
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.
@@ -1,3 +1,3 @@
1
1
  module PandaPal
2
- VERSION = '5.19.1'
2
+ VERSION = '5.20.0'
3
3
  end
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", "AGS.md", "panda_pal.gemspec"]
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.19.1
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
File without changes