Paillasse/docs/adr/0008-webdav-eleve-auth-request.md

83 lines
4.6 KiB
Markdown
Raw Normal View History

# ADR 0008 — WebDAV de l'élève : authentification déléguée (Nginx auth_request) + rclone
- **Date** : 2026-10-14
- **Statut** : accepté
## Contexte
Bloc C du CLAUDE.md : « dossier de l'élève montable comme lecteur réseau. »
Besoin : chaque élève a un dossier personnel, accessible en WebDAV (Windows/
macOS/Linux savent tous monter un lecteur réseau WebDAV nativement), avec les
mêmes identifiants que la connexion, et **jamais** accès au dossier d'un
autre élève.
## Décisions
**`rclone serve webdav`, pas un module WebDAV Nginx/Apache.** rclone sait
servir aussi bien un dossier local qu'un bucket S3 avec la même commande —
bascule possible plus tard vers Hetzner Object Storage sans rien changer à
l'architecture d'authentification. Empreinte mémoire très faible (~20 Mo).
**Authentification déléguée au portail via `auth_request` (Nginx), pas une
authentification propre à rclone.** rclone n'a pas de notion de comptes
multiples ; Nginx vérifie les identifiants auprès du portail (même logique
que la connexion, `Auth::guard('eleve')->once(...)`) via une sous-requête
interne (`WebdavAuthController`), puis limite la suite de la requête au seul
sous-dossier de l'élève authentifié.
**Un seul process rclone pour tous les élèves, pas un par élève.** Chaque
sous-dossier (`storage/app/webdav/<id élève>/`) est injecté dans le chemin
transmis à rclone via `proxy_pass http://127.0.0.1:.../$eleve_dossier/$chemin`
— $eleve_dossier vient de l'en-tête `X-Eleve-Dossier` posé par
`WebdavAuthController` après vérification des identifiants.
## Deux bugs trouvés en vérifiant en réel (pas en local, Nginx n'a pas de test unitaire)
**1. `rewrite` ne voit jamais la variable posée par `auth_request_set`.**
Nginx exécute les directives `rewrite` dans la phase *rewrite*, qui a lieu
**avant** la phase *access* où tourne `auth_request`/`auth_request_set` —
même écrites après dans le fichier, textuellement. Une première version
utilisait `rewrite ^/webdav/(.*)$ /$eleve_dossier/$1 break;` : `$eleve_dossier`
y était toujours vide, donc tous les fichiers de tous les élèves atterrissaient
à la racine commune du dossier WebDAV — **un élève pouvait lire le fichier
d'un autre simplement en devinant son nom.** Jamais vu en local (pas de test
automatisé qui exerce une vraie pile Nginx+rclone) : découvert en testant
avec deux comptes élèves jetables en vrai sur le serveur.
Corrigé : une `location` avec une regex à groupe nommé
(`location ~ ^/webdav/(?<chemin_webdav>.*)$`) et `proxy_pass` avec la
variable directement dans l'URL — `proxy_pass` construit sa requête dans une
phase plus tardive, où `$eleve_dossier` est déjà posée.
**2. Le cache de répertoires de rclone (`--dir-cache-time`, 5 min par
défaut) ne voit pas les dossiers créés par Laravel.** Chaque dossier élève
est créé par PHP (`Storage::disk('webdav')->makeDirectory(...)`), en dehors
de rclone — qui le découvre seulement à la prochaine actualisation de son
cache de liste. Un élève dont le dossier venait d'être créé pouvait recevoir
une 404 pendant plusieurs minutes. Corrigé par `--dir-cache-time 1s` dans le
service systemd (`infra`/`ansible/roles/portail/templates/rclone-webdav.service.j2`).
`--vfs-cache-mode off` (déjà en place, pour ne jamais garder de contenu de
fichier en cache) ne couvre pas ce cache de répertoires : deux réglages
distincts.
**3. `SCRIPT_NAME`/`REQUEST_URI` ne sont pas fiables dans une sous-requête
`auth_request` quand on construit son propre appel FastCGI.** `$request_uri`
dans la sous-requête reflétait la requête d'origine (`/webdav/…`), pas l'URI
de la sous-requête elle-même (`/interne/webdav-auth`) : Laravel routait donc
sur le mauvais chemin et répondait 404 à chaque appel. Comme cette location
a une URI toujours identique (`= /interne/webdav-auth`), la solution a été
d'écrire `SCRIPT_FILENAME`, `SCRIPT_NAME` et `REQUEST_URI` en dur dans les
paramètres FastCGI plutôt que de les déduire de variables Nginx.
## Conséquences
- Testé en réel avec trois comptes élèves jetables : upload, lecture,
PROPFIND, isolation stricte entre élèves (un 404 propre, jamais le
contenu d'un autre), dossier d'un nouvel élève utilisable immédiatement.
Comptes et fichiers de test supprimés ensuite.
- Cette classe de bug (interaction entre phases Nginx, cache rclone) n'est
détectable qu'en testant la vraie pile déployée — aucun test Pest ne
l'aurait vue, puisque les tests appellent le contrôleur Laravel
directement, sans passer par Nginx ni rclone.
- Reste en dette : pas de quota de stockage par élève, dossier local
seulement (pas encore sur Hetzner Object Storage).