diff --git a/assets/bootstrap.js b/assets/bootstrap.js index 4ab2df643..2f80be76b 100644 --- a/assets/bootstrap.js +++ b/assets/bootstrap.js @@ -1,3 +1,4 @@ +import * as _Turbo from '@hotwired/turbo'; import { startStimulusApp } from '@symfony/stimulus-bridge'; // Registers Stimulus controllers from controllers.json and in the controllers/ directory @@ -9,3 +10,13 @@ export const app = startStimulusApp(require.context( // register any custom, 3rd party controllers here // app.register('some_controller_name', SomeImportedController); + +// See: https://github.com/hotwired/turbo/issues/294#issuecomment-877842232 +document.addEventListener('turbo:before-fetch-request', (event) => { + event.detail.fetchOptions.headers['X-CSP-Nonce'] = document.querySelector('meta[name="csp-nonce"]').content; +}); +document.addEventListener('turbo:before-cache', () => { + document.querySelectorAll('script[nonce]').forEach((element) => { + element.setAttribute('nonce', element.nonce); + }); +}); diff --git a/config/packages/nelmio_security.yaml b/config/packages/nelmio_security.yaml index 057101cfc..f51a11c5b 100644 --- a/config/packages/nelmio_security.yaml +++ b/config/packages/nelmio_security.yaml @@ -29,6 +29,7 @@ nelmio_security: csp: enabled: true enforce: + level1_fallback: true default-src: ['self'] script-src: ['self'] style-src: ['self'] diff --git a/config/services.yaml b/config/services.yaml index 86fad94c5..bf6bc407b 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -64,6 +64,10 @@ services: DateTimeInterface: class: DateTimeImmutable + App\Infrastructure\Security\ContentSecurityPolicy\NonceGenerator: + decorates: 'nelmio_security.nonce_generator' + arguments: ['@.inner'] + when@test: services: App\Tests\Mock\APIAdresseMockClient: diff --git a/src/Infrastructure/Security/ContentSecurityPolicy/NonceGenerator.php b/src/Infrastructure/Security/ContentSecurityPolicy/NonceGenerator.php new file mode 100644 index 000000000..9b792ab80 --- /dev/null +++ b/src/Infrastructure/Security/ContentSecurityPolicy/NonceGenerator.php @@ -0,0 +1,51 @@ +requestNonce) { + return $this->requestNonce; + } else { + return $this->parent->generate(); + } + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::REQUEST => ['onKernelRequest', 400], + KernelEvents::RESPONSE => 'onKernelResponse', + ]; + } + + public function onKernelRequest(RequestEvent $event): void + { + if ($event->getRequest()->headers->has('X-CSP-Nonce')) { + $this->requestNonce = $event->getRequest()->headers->get('X-CSP-Nonce'); + } + } + + public function onKernelResponse(ResponseEvent $event): void + { + $this->requestNonce = null; + } +} diff --git a/templates/base.html.twig b/templates/base.html.twig index a96288787..085e4eace 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -4,6 +4,20 @@ + + {# + Turbo manipulates the DOM by inserting elements, stylesheets, and scripts. + So we need to grant it a nonce to pass the Content-Security-Policy. + We suppress CSP errors on initial page load thanks to the following double-call + to csp_nonce(), some Turbo configuration to send the nonce along with Turbo Drive + requests, and the custom NonceGenerator which reuses the nonce in the CSP header. + But page navigations may still generate CSP errors. + See: https://github.com/nelmio/NelmioSecurityBundle/issues/321 + #} + {% set _nonce = csp_nonce('script') %} + {% set _nonce = csp_nonce('style') %} + +