Why do I get "An error occurred while loading the web debug toolbar." when using this Event Subscriber?

1.6k views Asked by At

I'm creating configured maintenance page.

I created an event subscriber:

    public function onKernelRequest(RequestEvent $event)
    {
        $maintenance = $this->container->hasParameter('maintenance') ? $this->container->getParameter('maintenance') : false;
        $underMaintenanceUntil = $this->container->hasParameter('underMaintenanceUntil') ? $this->container->getParameter('underMaintenanceUntil') : false;

        if (!$event->isMasterRequest()){
            return;
        }

        $debug = in_array($this->container->get('kernel')->getEnvironment(), ['test','dev']);
        $uri = $event->getRequest()->getRequestUri();

        if ($uri == '/login' || $uri == '/logout') {

            return;
        }

        if ($maintenance && !$debug && !$this->container->get('security.authorization_checker')->isGranted('ROLE_ADMIN')) {
            $engine = $this->container->get('twig');

            $content = $engine->render('pages/maintenance.html.twig', array('underMaintenanceUntil' => $underMaintenanceUntil));
            $event->setResponse(new Response($content, 503));
            $event->stopPropagation();
        }
    }

And profiler bar doesn't work. I get

An error occurred while loading the web debug toolbar.

In log:

[Application] Oct 5 08:24:41 |INFO | REQUES Matched route "_wdt". method="GET" request_uri="https://localhost:8001/_wdt/914ef7" route="_wdt" route_parameters={"_controller":"web_profiler.controller.profiler::toolbarAction","_route":"_wdt","token":"914ef7"} [Application] Oct 5 08:24:41 |CRITICA| REQUES Uncaught PHP Exception Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException: "The token storage contains no authentication token. One possible reason may be that there is no firewall configured for this URL." at /home/marcin/projects/indali/vendor/symfony/security-core/Authorization/AuthorizationChecker.php line 52

And even if I type /login page I get the same error. Should't this if ($uri == '/login' || $uri == '/logout') {return;} prevents to not executing further code if it is login uri?

This $this->container->get('security.authorization_checker')->isGranted('ROLE_ADMIN') generates my errors because in dev firewall there is no auth token. I can check if token exists but why doesn't my code work?.

1

There are 1 answers

1
yivi On BEST ANSWER

Your code doesn't work because the profiler is a separate request from the main request to either /login or /logout.

The profiler bar is loaded via an AJAX request to a different URL. If you check your security configuration, the default is usually:

dev:
    pattern: '^/(_(profiler|wdt)|css|images|js)/'
    security: false

Profiler calls are the ones that go through _profiler or _wdt. So your check for login or logout URL is simply not useful for this. It affects the main request, but no the profiler bar request:

Some options are, from worse to better:

Check the token

As you mention, check that the token is not null, but that may not work as you expect, since it will end up allowing anonymous users reach your site when "under maintenance".

Check the URL Path

Cheap and simple: check the request path against the dev pattern (or against _(profiler|wdt) at least). This should be easy to incorporate in your code, but could conceivably be problematic in the future if you end up adding another non-secured firewall:

if (preg_match('/^\/(logout|login|_(wdt|profiler))/', $uri) {
         return;
}

Check the FirewallConfig for the request

If you want to do it cleanly, you should inject FirewallMap in your event subscriber, and get the firewall for the request.

A simple example which you would need to adapt to your own needs:

use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component;

class FooSubscriber implements Component\EventDispatcher\EventSubscriberInterface
{

    private Security\FirewallMap  $firewallMap;

    public function __construct(Security\FirewallMap $firewallMap)
    {
        $this->firewallMap = $firewallMap;
    }

    public static function getSubscribedEvents(): array
    {
        return [Component\HttpKernel\KernelEvents::REQUEST => 'onKernelRequest'];
    }

    public function onKernelRequest(Component\HttpKernel\Event\RequestEvent $event): void
    {
        if (!$event->isMasterRequest()) {
            return;
        }

        $config = $this->firewallMap->getFirewallConfig($event->getRequest());

        if (!$config instanceof Security\FirewallConfig || (!$config->isSecurityEnabled() || $config->allowsAnonymous())) {
            return;
        }
    }
}

Since the FirewallMap is not auto-wireable, you'll need to configure the service to inject it explicitly:

// services.php

$services->set(FooSubscriber::class)
             ->args([service('security.firewall.map')]);

or in old school YAML:

# services.yaml
services:
    App\FooSubscriber:
        arguments:
            - '@security.firewall.map'

In your case, it seems you are doing away with dependency injection and retrieving the container directly. Albeit that's a bad practice and you should change it if possible, with your current code you could simply get it from $this-container->get('security.firewall.map');