Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony2 Logout Event Listener Issue

I'm working on adding Audit logging to a Symfony2 project which logs all page loads and post requests into a custom audit table. The project uses the default logout route for Symfony2 (visiting /logout) which destroys the session then re-directs to the /login route.

There is an event listener set up for onKernelRequest which then writes the correct data into the table. In the security.yml file I have the following listed for the logout route.

security:
    firewalls:
        main:
            logout:
                path: /logout
                target: /login

The Audit logging is working fine for all pages except the logout event. After logging out I have tried visiting the profiler then selecting the '/logout' action from the "Last 10" option in the sidebar. When clicking on "Events" this lists the default Symfony events for kernel.request such as the DebugHandler and the ProfileListener however my custom callback is showing under the "Not Called Listeners" tab.

There is a success_handler that can be added to the security.yml file however I would need a method that can run my event listener before the session is destroyed. Is there a way to make the existing listener also record the logout event prior the Symfony actioning the logout?

Edit

<?php

// src/AuditBundle/EventListener/AuditListener.php
namespace AuditBundle\EventListener;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpFoundation\RequestStack;
use AuditBundle\Entity\AuditLog;

class AuditListener
{
    protected $requestStack;
    protected $em;
    protected $tokenStorage;
    protected $authorizationChecker;

    public function __construct(RequestStack $requestStack, \Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage $tokenStorage, $authorizationChecker, \Doctrine\ORM\EntityManager $em = NULL)
    {
        $this->requestStack = $requestStack;
        $this->em = $em;
        $this->tokenStorage = $tokenStorage;
        $this->authorizationChecker = $authorizationChecker;
    }

    public function onKernelRequest(GetResponseEvent $response)
    {
        $request = $response->getRequest();
        if ( strpos($request->getRequestUri(), 'fonts') !== false)
            return;
        if ( strpos($request->getRequestUri(), 'css') !== false)
            return;
        if ( strpos($request->getRequestUri(), '_wdt') !== false)
            return;
        if ( strpos($request->getRequestUri(), 'js') !== false)
            return;

        if ( strpos($request->getRequestUri(), '_profiler') !== false)
            return;

        $this->log('Request', $request);
    }

    public function postUpdate(\Doctrine\ORM\Event\LifecycleEventArgs $args)
    {
        $entity = $args->getEntity();
        $this->em = $args->getEntityManager();
        $className = $this->em->getClassMetadata(get_class($entity))->getName();
        if ($entity instanceof \AuditBundle\Entity\AuditLog)
            return;
        $this->log($className.' Updated', $this->requestStack->getCurrentRequest(), $entity);
    }

    public function postPersist(\Doctrine\ORM\Event\LifecycleEventArgs $args)
    {
        $entity = $args->getEntity();
        $this->em = $args->getEntityManager();
        $className = $this->em->getClassMetadata(get_class($entity))->getName();
        if ($entity instanceof \AuditBundle\Entity\AuditLog)
            return;
        $this->log($className.' Created', $this->requestStack->getCurrentRequest(), $entity);
    }

    public function postDelete(\Doctrine\ORM\Event\LifecycleEventArgs $args)
    {
        $entity = $args->getEntity();
        $this->em = $args->getEntityManager();
        $className = $this->em->getClassMetadata(get_class($entity))->getName();
        if ($entity instanceof \AuditBundle\Entity\AuditLog)
            return;
        $this->log($className.' Deleted', $this->requestStack->getCurrentRequest());
    }

    protected function log($message, $request, $entity = NULL)
    {
        $log = new AuditLog();
        $log->setType($request->getRealMethod());
        if ($this->authorizationChecker->isGranted('IS_AUTHENTICATED_FULLY'))
        {
            $log->setUser($this->tokenStorage->getToken()->getUser());
        }
        if ($entity)
        {
            $log->setEntityId($entity->getId());
        }

        $log->setUriString($request->getRequestUri());
        $log->setMessage($message);
        $log->setDatetime(new \DateTime());
        $log->setIp($request->getClientIp());
        $log->setBrowser(isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '');
        $this->em->persist($log);
        $this->em->flush();
    }
}

services.yml

services:
    audits.audit_listener:
        class: AuditBundle\EventListener\AuditListener
        arguments: [@request_stack, @security.token_storage, @security.authorization_checker, @doctrine.orm.entity_manager]
        tags:
            - { name: kernel.event_listener, event: kernel.request }
like image 964
tgrobinson Avatar asked Dec 24 '22 03:12

tgrobinson


1 Answers

The best way to hook into Symfony's logout event is to implement LogoutHandlerInterface like this,

Event Listener:

<?php

namespace AppBundle\EventListener;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface;

class MyListener implements LogoutHandlerInterface 
{
    public function logout(Request $request, Response $response, TokenInterface $token)
    {
        // Your handling here
    }   
}

Config:

services:
    appbundle_mylistener:
        class: AppBundle\EventListener\MyListener

security:
    firewalls:
        main:
            logout:
                handlers: [appbundle_mylistener]

So all you really need to do, is implement the LogoutHandlerInterface from your AuditListener with the logout method and then register the handlers parameter in the config

like image 51
Prof Avatar answered Dec 26 '22 15:12

Prof