Paillasse/portail/app/Http/Middleware/PolitiqueSecuriteContenu.php

88 lines
3 KiB
PHP
Raw Normal View History

<?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:",
'font-src' => "'self' https://fonts.gstatic.com",
'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' https://fonts.googleapis.com",
'connect-src' => "'self' wss://{$request->getHost()}",
]
: [
'script-src' => "'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdn.tailwindcss.com",
'style-src' => "'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net",
'connect-src' => "'self'",
'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)) {
return true;
}
return collect($route->gatherMiddleware())
->contains(fn ($middleware) => is_string($middleware) && str_starts_with($middleware, 'auth:'));
}
}