ldc-ovoteam/app/Http/Middlewares/SecureHeaders.php

173 lines
6 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Http\Middlewares;
use Closure;
use Illuminate\Contracts\Container\Container;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Vite;
use Symfony\Component\HttpFoundation\Response;
class SecureHeaders
{
/**
* Enumerate headers which you do not want in your application's responses.
* Great starting point would be to go check out @Scott_Helme's:
* https://securityheaders.com/
*
* @var string[]
*/
private $unwantedHeaderList = [
'X-Powered-By',
'Server',
];
public function __construct(
private readonly Container $container
) {
}
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request):(\Illuminate\Http\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$appUrl = Config::string('app.url');
$appDebug = Config::boolean('app.debug');
$telescopeEnabled = Config::boolean('telescope.enabled', false);
// * For Vite DEV
$host = \parse_url($appUrl, \PHP_URL_HOST);
$viteDevOrigin = $appDebug && \is_string($host) && $host !== ''
? "https://{$host}:4443"
: '';
$viteDevSocket = $appDebug && \is_string($host) && $host !== ''
? "wss://{$host}:4443"
: '';
$nonce = Vite::cspNonce();
$jsBo = $request->routeIs('bo*') ? "'unsafe-eval'" : '';
$jsdelivrUrl = 'https://cdn.jsdelivr.net';
$openStreetMapTiles = 'https://*.tile.openstreetmap.org';
$this->declareNonce($nonce);
$this->removeUnwantedHeaders($this->unwantedHeaderList);
$response = $next($request);
$response->headers->set('Referrer-Policy', 'no-referrer-when-downgrade');
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('X-XSS-Protection', '1; mode=block');
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');
$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
// * Ignore CSP on some conditions.
if (
$appDebug && (
$response->isClientError() || // Ignition
$response->isServerError() || // Ignition
Vite::isRunningHot() || // Vite
($telescopeEnabled && str_starts_with($request->path(), 'telescope')) // Telescope
)
) {
return $response;
}
$cspPolicies = [
"default-src 'none';",
"base-uri 'self';",
"manifest-src 'self';",
"media-src 'self' blob: {$appUrl};",
// * CKEDITOR Specific https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/csp.html
"connect-src 'self' blob: " . implode(' ', [
'https://yl.s-web.fr/',
// * BAN address autocomplete (api-adresse.data.gouv.fr)
'https://api-adresse.data.gouv.fr',
'https://maps.googleapis.com',
// * Google reCAPTCHA
'https://www.google.com/recaptcha/',
'https://recaptcha.google.com/recaptcha/',
'https://www.google.com',
'https://www.recaptcha.net/',
// * VITE DEV
$viteDevSocket,
$viteDevOrigin,
]) . ';',
"img-src 'self' data: blob: " . implode(' ', [
'https://maps.googleapis.com',
'https://maps.gstatic.com',
$viteDevOrigin,
$openStreetMapTiles,
]) . ' data:;',
'child-src ' . implode(' ', []) . ';',
"frame-src 'self' " . implode(' ', [
'https://www.google.com/recaptcha/',
'https://recaptcha.google.com/recaptcha/',
'https://www.google.com/maps/',
'https://www.youtube.com/embed/',
'https://player.vimeo.com/',
'https://e.issuu.com/embed.html?',
]) . ';',
"object-src 'none';",
"font-src 'self' data: " . \implode(' ', [
'https://fonts.gstatic.com',
// * VITE DEV
$viteDevOrigin,
]) . ';',
// ! Using CKEditor will must have unsafe-inline style
"style-src 'self' 'unsafe-inline' " . \implode(' ', [
$jsdelivrUrl,
'https://cdnjs.cloudflare.com',
'https://fonts.googleapis.com',
'https://use.fontawesome.com',
// * File Manager
// * VITE DEV
$viteDevOrigin,
]) . ';',
// ! If you dont use ckeditor use this
// "style-src 'self' 'nonce-$nonce';",
"script-src 'self' 'nonce-$nonce' 'strict-dynamic' " . \implode(' ', [
'https: blob:',
$jsBo,
'https://www.googletagmanager.com',
'https://cdnjs.cloudflare.com',
'https://www.google-analytics.com',
// * VITE DEV
$viteDevOrigin,
]) . ';',
];
$response->headers->set(
'Content-Security-Policy',
implode(' ', $cspPolicies)
);
$response->headers->set('Feature-Policy', 'fullscreen \'self\'; geolocation \'self\';');
return $response;
}
/**
* Remove unwanted headers
*
* @param string[] $headerList List of headers to remove from the response
* @return void
*/
private function removeUnwantedHeaders(array $headerList)
{
foreach ($headerList as $header) {
header_remove($header);
}
}
private function declareNonce(?string $value): void
{
Vite::useCspNonce($value);
$this->container->singleton('csp_nonce', $value);
}
}