Paillasse/portail/app/Http/Middleware/PolitiqueSecuriteContenu.php
Adam Belkacemi dd4f9c3c55 permet aux élèves de rejoindre leur classe avec son code, et les fiches en lot
L'élève saisit le code de classe et son prénom, le prof accepte, l'élève
voit une seule fois son identifiant et son code. Aucun e-mail. Le prof
peut aussi régénérer les fiches de plusieurs élèves d'un coup. Procédure
pour prouver la copie de la clé des sauvegardes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:18:53 +02:00

92 lines
3.3 KiB
PHP

<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Vite;
use Symfony\Component\HttpFoundation\Response;
/**
* Content-Security-Policy, en deux niveaux (étape 12) :
*
* - application (connexion, inscription, toute page authentifiée) : seuls
* nos scripts et ceux qui portent le nonce de la requête s'exécutent. Une
* injection de script dans un nom d'élève, par exemple, serait bloquée
* par le navigateur ;
* - vitrine (pages reprises du design) : scripts en ligne et CDN autorisés,
* faute de pouvoir les réécrire ici. Ces pages n'affichent aucune donnée
* d'utilisateur.
*
* Les violations sont envoyées à /csp-rapport (journal Laravel).
* Les autres en-têtes (HSTS, nosniff…) sont posés par Nginx.
*/
class PolitiqueSecuriteContenu
{
public function handle(Request $request, Closure $next): Response
{
Vite::useCspNonce();
$reponse = $next($request);
if (! $reponse->headers->has('Content-Security-Policy')) {
$reponse->headers->set('Content-Security-Policy', $this->politique($request));
}
return $reponse;
}
private function politique(Request $request): string
{
$communes = [
'default-src' => "'self'",
'img-src' => "'self' data:",
// Polices servies par le portail (Vite), depuis l'étape 14.
'font-src' => "'self'",
'object-src' => "'none'",
'base-uri' => "'self'",
'form-action' => "'self'",
'frame-ancestors' => "'none'",
'report-uri' => '/csp-rapport',
];
$directives = $this->estApplication($request)
? [
'script-src' => "'self' 'nonce-".Vite::cspNonce()."'",
// Attributs style= présents dans le gabarit commun : sans
// risque d'exécution de code, tolérés.
'style-src' => "'self' 'unsafe-inline'",
'connect-src' => "'self' wss://{$request->getHost()}",
]
: [
// jsDelivr : Pyodide et sql.js, téléchargés par /ide à la
// demande (WebAssembly, d'où 'wasm-unsafe-eval', qui n'autorise
// pas eval() en JavaScript). Tailwind : CDN d'exécution des
// pages du design. Voir docs/dette.md.
'script-src' => "'self' 'unsafe-inline' 'wasm-unsafe-eval' https://cdn.jsdelivr.net https://cdn.tailwindcss.com",
'style-src' => "'self' 'unsafe-inline'",
'connect-src' => "'self' https://cdn.jsdelivr.net",
'frame-src' => 'https://www.youtube-nocookie.com',
];
return collect($communes + $directives)
->map(fn (string $valeur, string $directive) => "{$directive} {$valeur}")
->implode('; ');
}
private function estApplication(Request $request): bool
{
$route = $request->route();
if ($route === null) {
return false;
}
if (in_array($route->getName(), ['login', 'register'], true) || str_starts_with((string) $route->getName(), 'rejoindre')) {
return true;
}
return collect($route->gatherMiddleware())
->contains(fn ($middleware) => is_string($middleware) && str_starts_with($middleware, 'auth:'));
}
}