Add cookie-check template and saltire testing documentation
The real-handshake test against the saltire LTI platform emulator turned up a launch bug dev mode cannot reach: pylti1p3's Flask adapter requires the application to supply the cookies_allowed_js_check.html template used by enable_check_cookies(), and without it every OIDC login 500s. The template tests whether cookies survive, then re-enters the login flow or offers a new-window fallback. docs/SALTIRE.md records the verified test setup: local HTTPS with a self-signed certificate, the platform description for instance/lti_config.json, the saltire-side configuration, and the session-id caveat on saltire's token/jwks URLs. The 2026-08-01 run verified the full OIDC handshake, JWT validation, NRPS roster sync over the wire, and instructor/learner role routing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
33
coldcall_lti/templates/cookies_allowed_js_check.html
Normal file
33
coldcall_lti/templates/cookies_allowed_js_check.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{{ loading_text }}</title>
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; margin: 2rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p id="loading">{{ loading_text }}</p>
|
||||
<div id="blocked" style="display:none">
|
||||
<p>{{ main_text }}</p>
|
||||
<p><a href="#" id="newwin">{{ click_text }}</a></p>
|
||||
</div>
|
||||
<script>
|
||||
var params = {{ params | tojson }};
|
||||
var target = window.location.pathname + "?" +
|
||||
new URLSearchParams(params).toString();
|
||||
document.cookie = "lti1p3_cc=1; path=/; SameSite={{ 'None; Secure' if protocol == 'https' else 'Lax' }}";
|
||||
if (document.cookie.indexOf("lti1p3_cc=") !== -1) {
|
||||
window.location = target;
|
||||
} else {
|
||||
document.getElementById("loading").style.display = "none";
|
||||
document.getElementById("blocked").style.display = "block";
|
||||
document.getElementById("newwin").addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
window.open(target, "_blank");
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
85
docs/SALTIRE.md
Normal file
85
docs/SALTIRE.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Testing against the saltire LTI platform emulator
|
||||
|
||||
[saltire](https://saltire.lti.app) is the ceLTIc project's hosted LTI
|
||||
test harness. Its Platform Emulator impersonates an LMS: it performs
|
||||
the real OIDC handshake, signs a real id_token, and answers NRPS
|
||||
requests, which makes it the way to exercise the app's actual launch
|
||||
path without a Canvas Developer Key. This setup was first run and
|
||||
verified on 2026-08-01 (launch, role routing for both Instructor and
|
||||
Learner, and a live NRPS roster sync all worked).
|
||||
|
||||
## Local side
|
||||
|
||||
The app must be served over HTTPS, because real LTI session cookies
|
||||
are `Secure; SameSite=None`. A self-signed certificate is fine — you
|
||||
accept it once in the browser:
|
||||
|
||||
```
|
||||
# one-time: tool keypair and TLS cert (all live in instance/, gitignored)
|
||||
openssl genrsa -out instance/private.key 4096
|
||||
openssl rsa -in instance/private.key -pubout -out instance/public.key
|
||||
openssl req -x509 -newkey rsa:2048 -keyout instance/tls-key.pem \
|
||||
-out instance/tls-cert.pem -days 30 -nodes -subj "/CN=localhost"
|
||||
|
||||
# run
|
||||
.venv/bin/python -m flask --app coldcall_lti run --port 5443 \
|
||||
--cert instance/tls-cert.pem --key instance/tls-key.pem
|
||||
```
|
||||
|
||||
Then open https://localhost:5443/healthz once and click through the
|
||||
certificate warning (Advanced → Proceed).
|
||||
|
||||
`instance/lti_config.json` describes the saltire platform to the app.
|
||||
The working shape (fill the session id — see the caveat below):
|
||||
|
||||
```json
|
||||
{
|
||||
"https://saltire.lti.app/platform": [
|
||||
{
|
||||
"default": true,
|
||||
"client_id": "saltire.lti.app",
|
||||
"auth_login_url": "https://saltire.lti.app/platform/auth",
|
||||
"auth_token_url": "https://saltire.lti.app/platform/token/<SESSION>",
|
||||
"key_set_url": "https://saltire.lti.app/platform/jwks/<SESSION>",
|
||||
"private_key_file": "private.key",
|
||||
"public_key_file": "public.key",
|
||||
"deployment_ids": ["<DEPLOYMENT-ID-FROM-SALTIRE>"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## saltire side
|
||||
|
||||
On https://saltire.lti.app → Test Platform → **Security Model**:
|
||||
|
||||
1. Set LTI version to **1.3.0**. The Platform Details panel then shows
|
||||
the issuer, client id, deployment id, and the auth/token/jwks URLs —
|
||||
copy these into `instance/lti_config.json`.
|
||||
2. Set **Message URL** to `https://localhost:5443/lti/launch`.
|
||||
3. Under **Tool Details**: Initiate login URL
|
||||
`https://localhost:5443/lti/login`; Redirection URI(s)
|
||||
`https://localhost:5443/lti/launch`; clear the tool's public keyset
|
||||
URL (saltire's server cannot reach localhost) and instead paste the
|
||||
contents of `instance/public.key` into the tool **Public key** box.
|
||||
4. **Save**, then **Connect** launches the tool. The default saltire
|
||||
user carries the Instructor role and lands on the instructor page,
|
||||
with the fake "Telecommunications 101" roster synced via NRPS. To
|
||||
test the student experience, change the role under **User** to
|
||||
`http://purl.imsglobal.org/vocab/lis/v2/membership#Learner`, save,
|
||||
and Connect again — you land on /me.
|
||||
|
||||
## Caveats
|
||||
|
||||
- **The token and jwks URLs embed a saltire session id** (the
|
||||
`/token/s...`/`/jwks/s...` suffix). Anonymous saltire sessions get a
|
||||
new id eventually, which breaks `lti_config.json` until you update
|
||||
it. To keep a stable setup, use saltire's Save menu to store the
|
||||
configuration under a saltire account and reconnect from there.
|
||||
- saltire sometimes raises native browser dialogs (e.g. "save your
|
||||
changes first" flows); nothing about that affects the tool under
|
||||
test.
|
||||
- The cookie-check interstitial (`cookies_allowed_js_check.html`) is
|
||||
part of the app and required: pylti1p3's Flask adapter expects the
|
||||
application to supply that template when `enable_check_cookies()` is
|
||||
used.
|
||||
Reference in New Issue
Block a user