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

4.6 KiB

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