diff --git a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php index 64e0ad9fc3..8c1ef6177d 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php +++ b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php @@ -16,6 +16,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag; +use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector; use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; @@ -44,8 +45,9 @@ class WebDebugToolbarListener implements EventSubscriberInterface protected $mode; protected $excludedAjaxPaths; private $cspHandler; + private $dumpDataCollector; - public function __construct(Environment $twig, bool $interceptRedirects = false, int $mode = self::ENABLED, UrlGeneratorInterface $urlGenerator = null, string $excludedAjaxPaths = '^/bundles|^/_wdt', ContentSecurityPolicyHandler $cspHandler = null) + public function __construct(Environment $twig, bool $interceptRedirects = false, int $mode = self::ENABLED, UrlGeneratorInterface $urlGenerator = null, string $excludedAjaxPaths = '^/bundles|^/_wdt', ContentSecurityPolicyHandler $cspHandler = null, DumpDataCollector $dumpDataCollector = null) { $this->twig = $twig; $this->urlGenerator = $urlGenerator; @@ -53,6 +55,7 @@ class WebDebugToolbarListener implements EventSubscriberInterface $this->mode = $mode; $this->excludedAjaxPaths = $excludedAjaxPaths; $this->cspHandler = $cspHandler; + $this->dumpDataCollector = $dumpDataCollector; } public function isEnabled(): bool @@ -89,7 +92,14 @@ class WebDebugToolbarListener implements EventSubscriberInterface return; } - $nonces = $this->cspHandler ? $this->cspHandler->updateResponseHeaders($request, $response) : []; + $nonces = []; + if ($this->cspHandler) { + if ($this->dumpDataCollector && $this->dumpDataCollector->getDumpsCount() > 0) { + $this->cspHandler->disableCsp(); + } + + $nonces = $this->cspHandler->updateResponseHeaders($request, $response); + } // do not capture redirects or modify XML HTTP Requests if ($request->isXmlHttpRequest()) { diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/toolbar.php b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/toolbar.php index 626f6feece..473b3630f7 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/toolbar.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/toolbar.php @@ -24,6 +24,7 @@ return static function (ContainerConfigurator $container) { service('router')->ignoreOnInvalid(), abstract_arg('paths that should be excluded from the AJAX requests shown in the toolbar'), service('web_profiler.csp.handler'), + service('data_collector.dump')->ignoreOnInvalid(), ]) ->tag('kernel.event_subscriber') ; diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php index 11d9054ca6..f4d0f7e037 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php @@ -20,6 +20,7 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector; use Symfony\Component\HttpKernel\KernelInterface; class WebProfilerExtensionTest extends TestCase @@ -55,6 +56,7 @@ class WebProfilerExtensionTest extends TestCase $this->kernel = $this->createMock(KernelInterface::class); $this->container = new ContainerBuilder(); + $this->container->register('data_collector.dump', DumpDataCollector::class)->setPublic(true); $this->container->register('error_handler.error_renderer.html', HtmlErrorRenderer::class)->setPublic(true); $this->container->register('event_dispatcher', EventDispatcher::class)->setPublic(true); $this->container->register('router', $this->getMockClass('Symfony\\Component\\Routing\\RouterInterface'))->setPublic(true); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php index 01d586346a..4e84567fa5 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php @@ -12,11 +12,13 @@ namespace Symfony\Bundle\WebProfilerBundle\Tests\EventListener; use PHPUnit\Framework\TestCase; +use Symfony\Bundle\WebProfilerBundle\Csp\ContentSecurityPolicyHandler; use Symfony\Bundle\WebProfilerBundle\EventListener\WebDebugToolbarListener; use Symfony\Component\HttpFoundation\HeaderBag; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector; use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\Kernel; @@ -300,6 +302,48 @@ class WebDebugToolbarListenerTest extends TestCase $this->assertEquals('Exception: This multiline tabbed text should come out on a single plain line', $response->headers->get('X-Debug-Error')); } + public function testCspIsDisabledIfDumperWasUsed() + { + $response = new Response(''); + $response->headers->set('X-Debug-Token', 'xxxxxxxx'); + + $event = new ResponseEvent($this->createMock(Kernel::class), $this->getRequestMock(), HttpKernelInterface::MASTER_REQUEST, $response); + + $cspHandler = $this->createMock(ContentSecurityPolicyHandler::class); + $cspHandler->expects($this->once()) + ->method('disableCsp'); + $dumpDataCollector = $this->createMock(DumpDataCollector::class); + $dumpDataCollector->expects($this->once()) + ->method('getDumpsCount') + ->willReturn(1); + + $listener = new WebDebugToolbarListener($this->getTwigMock(), false, WebDebugToolbarListener::ENABLED, null, '', $cspHandler, $dumpDataCollector); + $listener->onKernelResponse($event); + + $this->assertEquals("\nWDT\n", $response->getContent()); + } + + public function testCspIsKeptEnabledIfDumperWasNotUsed() + { + $response = new Response(''); + $response->headers->set('X-Debug-Token', 'xxxxxxxx'); + + $event = new ResponseEvent($this->createMock(Kernel::class), $this->getRequestMock(), HttpKernelInterface::MASTER_REQUEST, $response); + + $cspHandler = $this->createMock(ContentSecurityPolicyHandler::class); + $cspHandler->expects($this->never()) + ->method('disableCsp'); + $dumpDataCollector = $this->createMock(DumpDataCollector::class); + $dumpDataCollector->expects($this->once()) + ->method('getDumpsCount') + ->willReturn(0); + + $listener = new WebDebugToolbarListener($this->getTwigMock(), false, WebDebugToolbarListener::ENABLED, null, '', $cspHandler, $dumpDataCollector); + $listener->onKernelResponse($event); + + $this->assertEquals("\nWDT\n", $response->getContent()); + } + protected function getRequestMock($isXmlHttpRequest = false, $requestFormat = 'html', $hasSession = true) { $request = $this->getMockBuilder(Request::class)->setMethods(['getSession', 'isXmlHttpRequest', 'getRequestFormat'])->disableOriginalConstructor()->getMock(); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php index 32e9d936fb..915506df43 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php @@ -10,6 +10,7 @@ use Symfony\Bundle\WebProfilerBundle\WebProfilerBundle; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector; use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; @@ -65,6 +66,7 @@ class WebProfilerBundleKernel extends Kernel protected function build(ContainerBuilder $container) { + $container->register('data_collector.dump', DumpDataCollector::class); $container->register('logger', NullLogger::class); }