This commit is contained in:
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Listener;
|
namespace App\Listener;
|
||||||
|
|
||||||
|
use App\Service\DomainInterface;
|
||||||
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpKernel\Event\ResponseEvent;
|
use Symfony\Component\HttpKernel\Event\ResponseEvent;
|
||||||
@@ -15,6 +16,11 @@ use Symfony\Component\HttpKernel\Event\ResponseEvent;
|
|||||||
*/
|
*/
|
||||||
final readonly class SecurityHeadersListener
|
final readonly class SecurityHeadersListener
|
||||||
{
|
{
|
||||||
|
public function __construct(
|
||||||
|
private DomainInterface $domainManager,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
#[AsEventListener(priority: 0)]
|
#[AsEventListener(priority: 0)]
|
||||||
public function onKernelResponse(ResponseEvent $event): void
|
public function onKernelResponse(ResponseEvent $event): void
|
||||||
{
|
{
|
||||||
@@ -36,11 +42,24 @@ final readonly class SecurityHeadersListener
|
|||||||
|
|
||||||
/* Content-Security-Policy — the login page uses inline styles
|
/* Content-Security-Policy — the login page uses inline styles
|
||||||
* and scripts (via Twig includes), so we allow 'unsafe-inline'
|
* and scripts (via Twig includes), so we allow 'unsafe-inline'
|
||||||
* for those. No external resources are loaded. */
|
* for those. No external resources are loaded.
|
||||||
$headers->set(
|
*
|
||||||
'Content-Security-Policy',
|
* When subdomain redirection is off (or the request is not on
|
||||||
"default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline';"
|
* the auth subdomain), the login form is served inline on the
|
||||||
);
|
* protected host and submission is performed via a same-origin
|
||||||
|
* fetch() call in _script.html.twig. That fetch is blocked by
|
||||||
|
* the default 'none' policy, so we add connect-src 'self' only
|
||||||
|
* in that case — the least privilege needed to make the form
|
||||||
|
* work. On the auth subdomain the form POSTs normally and no
|
||||||
|
* inline script is included, so the stricter policy applies. */
|
||||||
|
$inlineScript = $this->domainManager->getAuthSubdomain() !== $event->getRequest()->getHost();
|
||||||
|
$csp = "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline';";
|
||||||
|
|
||||||
|
if ($inlineScript) {
|
||||||
|
$csp .= " connect-src 'self';";
|
||||||
|
}
|
||||||
|
|
||||||
|
$headers->set('Content-Security-Policy', $csp);
|
||||||
|
|
||||||
/* HSTS — enforce HTTPS for one year (app is designed for HTTPS behind a proxy) */
|
/* HSTS — enforce HTTPS for one year (app is designed for HTTPS behind a proxy) */
|
||||||
$headers->set('Strict-Transport-Security', 'max-age=31536000');
|
$headers->set('Strict-Transport-Security', 'max-age=31536000');
|
||||||
|
|||||||
Reference in New Issue
Block a user