Paillasse/docs/adr/0008-webdav-eleve-auth-request.md
Adam Belkacemi e549f03e1b ajoute le dossier WebDAV de l'élève (bloc C)
Dossier personnel de l'élève monté comme lecteur réseau : rclone
sert le dossier local, l'authentification est déléguée au portail
via auth_request Nginx (mêmes identifiants que la connexion), sans
nouveau système de comptes. Nettoyé par la purge RGPD comme les
autres données d'élève.

Corrige au passage un vrai bug d'isolation trouvé en testant avec
deux comptes jetables : rewrite injectait la variable posée par
auth_request_set, mais rewrite s'exécute dans une phase Nginx
antérieure à cette variable, donc toujours vide - un élève pouvait
lire le fichier d'un autre en devinant son nom. Corrigé par une
location à regex nommée + proxy_pass. Détails : ADR 0008.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-14 13:04:40 +02:00

82 lines
4.6 KiB
Markdown

# 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).