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>
92 lines
3.3 KiB
PHP
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:'));
|
|
}
|
|
}
|