<?php
declare(strict_types=1);
namespace App\Framework\Security\ContentSecurityPolicy\EventSubscriber;
use App\Framework\Security\ContentSecurityPolicy\Computer\PageScriptCSPComputer;
use App\Framework\Security\ContentSecurityPolicy\Computer\ResponseNonceComputer;
use App\Framework\Security\ContentSecurityPolicy\DirectivesSet;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class ContentSecurityPolicyListener implements EventSubscriberInterface
{
private DirectivesSet $directiveSet;
private ?string $nonce = null;
public function __construct(
private readonly PageScriptCSPComputer $pageScriptCSPComputer
) {
$this->directiveSet = new DirectivesSet();
}
public function resetDirectiveSet(): void
{
$this->directiveSet = new DirectivesSet();
}
public function addCSPHeader(ResponseEvent $event): void
{
if (HttpKernelInterface::MAIN_REQUEST !== $event->getRequestType()) {
return;
}
$event->getResponse()->headers->set(
'Content-Security-Policy',
$this->directiveSet->buildHeaderValue($this->getNonce())
);
}
public function addNonceToScripts(ResponseEvent $event): void
{
$event->setResponse(ResponseNonceComputer::computeNoncesToResponseContent($event->getResponse(), $this->getNonce()));
}
public function calculateAddedScripts(RequestEvent $event): void
{
if (HttpKernelInterface::MAIN_REQUEST !== $event->getRequestType()) {
return;
}
$hashes = $this->pageScriptCSPComputer->computeShaSet();
$this->directiveSet->addShaSet('script-src', $hashes);
}
public function calculateNonce(RequestEvent $event): void
{
if (HttpKernelInterface::MAIN_REQUEST !== $event->getRequestType()) {
return;
}
$this->getNonce();
}
public function getNonce(): string
{
if (null === $this->nonce) {
$this->nonce = base64_encode(random_bytes(16));
}
return $this->nonce;
}
public static function getSubscribedEvents(): array
{
return [
RequestEvent::class => [
['resetDirectiveSet', -7],
['calculateNonce', -8],
['calculateAddedScripts', -9],
],
ResponseEvent::class => [
['addNonceToScripts'],
['addCSPHeader', 1],
],
];
}
}