# 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//`) 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/(?.*)$`) 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).