Paillasse/docs/adr/0010-lti-moodle-comme-sixieme-facon-de-rejoindre.md

77 lines
4 KiB
Markdown
Raw Normal View History

# ADR 0010 — LTI 1.3 Moodle : une 6ᵉ façon de rejoindre une classe, pas un lancement de lab
- **Date** : 2026-10-14
- **Statut** : accepté
## Contexte
CLAUDE.md, section 6 : « Moodle LTI 1.3 (V2) » est listé **avec** les autres
façons d'ajouter des élèves (code/QR, import CSV, saisie rapide, invitation
e-mail) — pas dans la description de l'espace de lab. Avancé à la demande
explicite d'Adam (bloc C terminé, V2 lancée par anticipation).
## Décisions
**LTI sert à rejoindre une classe, pas à lancer un lab précis.** Un
lancement Moodle authentifie l'élève (OIDC + jeton signé) et le fait
atterrir sur son tableau de bord habituel (`eleve.show`) — depuis là, il
choisit un lab ou un devoir normalement, par le portail. Construire un
lien direct vers un lab précis aurait demandé de router l'admission dans
une séance (`SeanceController::admettre`), bien plus complexe, pour un
besoin que le CLAUDE.md ne demande pas.
**L'URL de lancement réutilise `code_classe` existant**
(`/lti/lancement/{code_classe}`), pas un nouveau jeton ou une nouvelle
table de liaison. Exactement la même donnée que celle déjà affichée pour
« Faire rejoindre tes élèves » (code/QR) — LTI est juste une 6ᵉ porte vers
la même classe, pas un concept à part.
**`packbackbooks/lti-1p3-tool` (Apache-2.0)**, recommandé par le CLAUDE.md.
Bibliothèque bas niveau (pas de dépendance à un framework) : il faut lui
fournir trois implémentations (`ICache`, `ICookie`, `IDatabase`, dans
`App\Services\Lti\`) qui s'appuient sur Redis, les cookies et les tables
`lti_plateformes`/`lti_deploiements` déjà en place.
**Élève provisionné automatiquement au premier lancement, retrouvé par
`(classe_id, lti_emetteur, lti_sub)` ensuite** — jamais de doublon de
compte à chaque connexion. Prénom/nom lus depuis les claims standards
(`given_name`/`family_name`, puis `name`, jamais inventés : « Élève » /
« Moodle » si Moodle n'envoie rien). Code secret généré au hasard, haché,
jamais affiché : cet élève se connecte toujours via Moodle, jamais par
identifiant/code comme les autres.
**Cookie `SameSite=None; Secure` pour le jeton d'état OIDC.** Sans ça, le
cookie ne survit jamais au POST final que Moodle envoie depuis son propre
domaine — chaque lancement échouerait silencieusement (« Please make sure
you have cookies… », message déjà prévu par la bibliothèque pour ce cas
précis, qui n'arrive qu'avec un réglage de cookie insuffisant).
**Sécurité x-pack… non, pas de rapport ici — mais même logique que pour
Elasticsearch/Ceph** : une seule clé RSA par plateforme Moodle enregistrée
(générée par le portail lui-même à l'enregistrement, `phpseclib4\Crypt\RSA`,
jamais collée par Adam), exposée en public via `/lti/cles.json` — c'est le
fonctionnement normal du protocole (Moodle doit pouvoir vérifier les jetons
que *nous* lui envoyons, si jamais le portail appelle un service LTI plus
tard), pas une fuite.
**Enregistrement manuel, par commande Artisan, jamais une page
d'administration.** Une plateforme Moodle se configure une fois par
établissement, par Adam, avec des informations lues dans l'administration
Moodle — une page web pour ça serait une fonctionnalité sans usage réel
(CLAUDE.md, section 8). `lti:enregistrer-plateforme` et
`lti:enregistrer-deploiement`, voir `docs/runbooks/lti-moodle.md`.
## Conséquences
- Testé avec un jeton LTI réellement signé (RS256, paire de clés générée
dans le test), jeu de clés de « Moodle » simulé : lancement validé de
bout en bout, élève créé avec le bon prénom/nom, deuxième lancement du
même élève ne crée pas de deuxième compte. Pas de vrai Moodle disponible
pour vérifier en conditions réelles — à refaire dès qu'Adam a un Moodle
de test.
- En dette : pas de web service LTI (AGS pour les notes, NRPS pour la
liste des inscrits, Deep Linking) — seul le lancement de ressource est
géré, comme décidé ci-dessus. Les classes de la bibliothèque pour ces
services existent déjà (`LtiAssignmentsGradesService` etc.) si le besoin
devient réel.