Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony2 - Redirect response from request EventListener in dev mode while ignoring built in request events

I am building my own user management system in Symfony2 (not using FOSUserBundle) and want to be able to force users to change their password.

I have setup an EventListener to listen to the kernal.request event, then I perform some logic in the listener to determine if the user needs to change their password; if they do, then they are redirected to a "Change Password" route.

I add the service to my config.yml to listen on the kernal.request:

password_change_listener:
    class: Acme\AdminBundle\EventListener\PasswordChangeListener
        arguments: [ @service_container ]
        tags:
            - { name: kernel.event_listener, event: kernel.request, method: onMustChangepasswordEvent }

And then the listener:

public function onMustChangepasswordEvent(GetResponseEvent $event) {

  $securityContext = $this->container->get('security.context');

  // if not logged in, no need to change password
  if ( !$securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED') )
    return;

  // If already on the change_password page, no need to change password
  $changePasswordRoute = 'change_password';
  $_route  = $event->getRequest()->get('_route');
  if ($changePasswordRoute == $_route)
    return;

  // Check the user object to see if user needs to change password
  $user = $this->getUser();
  if (!$user->getMustChangePassword())
    return;

  // If still here, redirect to the change password page
  $url = $this->container->get('router')->generate($changePasswordRoute);
  $response = new RedirectResponse($url);
  $event->setResponse($response);
}

The problem I am having is that in dev mode, my listener is also redirecting the profiler bar and assetic request events. It works when I dump assets and clear cache and view the site in production mode.

Is there a way I can ignore the events from assetic/profiler bar/any other internal controllers? Or a better way to redirect a user to the change_password page (not only on login success)?

Going crazy thinking up wild hack solutions, but surely there is a way to handle this elegantly in Symfony2?

like image 586
Desi Cochrane Avatar asked Oct 17 '13 12:10

Desi Cochrane


1 Answers

This is the very hack solution I am using for now:

  1. Determine if in dev environment
  2. If so, get an array of all the routes
  3. Filter the route array so that only the routes I have added remain
  4. Compare the current route to the array of routes
  5. If a match is found, this means that the event is not an in-built controller, but must be one that I have added, so perform the redirect.

And this is the madness that makes that work:

// determine if in dev environment
if (($this->container->getParameter('kernel.environment') == 'dev'))
{

  // Get array of all routes that are not built in 
  // (i.e You have added them yourself in a routing.yml file).
  // Then get the current route, and check if it exists in the array 
  $myAppName = 'Acme';
  $routes = $this->getAllNonInternalRoutes($myAppName);
  $currentRoute = $event->getRequest()->get('_route');
  if(!in_array($currentRoute, $routes))
    return;
}

// If still here, success, you have ignored the assetic and 
// web profiler actions, and any other actions that you did not add
// yourself in a routing.yml file! Go ahead and redirect!
$url = $this->container->get('router')->generate('change_password_route');
$response = new RedirectResponse($url);
$event->setResponse($response);

And the crazy hack function getAllNonInternalRoutes() that makes it work (which is a modification of code I found here by Qoop:

private function getAllNonInternalRoutes($app_name) {

  $router = $this->container->get('router');
  $collection = $router->getRouteCollection();
  $allRoutes = $collection->all();

  $routes = array();

  foreach ($allRoutes as $route => $params)
  {
    $defaults = $params->getDefaults();

    if (isset($defaults['_controller']))
    {
      $controllerAction = explode(':', $defaults['_controller']);
      $controller = $controllerAction[0];

      if ((strpos($controller, $app_name) === 0)) 
        $routes[]= $route;
    }
  }
  return $routes;
}
like image 165
Desi Cochrane Avatar answered Sep 20 '22 16:09

Desi Cochrane