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 }
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With