Passing name of security user to listener in services.yml in Symfony2

1.2k views Asked by At

I am using Gedmo Loggable to keep track of changes that users make to entities. The username is not stored by default for each change, but it is necessary to provide it to the listener.

An example of this, is found in the documentation of Loggable:

  $loggableListener = new Gedmo\Loggable\LoggableListener;
  $loggableListener->setAnnotationReader($cachedAnnotationReader);
  $loggableListener->setUsername('admin');
  $evm->addEventSubscriber($loggableListener);

This does not work for me for two reasons:

  1. I am registering the listener in services.yml, not in a controller
  2. I do not wish to store a pre-known username like in the example but the username of the user that is logged in

The method setUsername of the loggableListener either expects a string or an object with a method getUsername that provides a string.

How can I pass either one to the listener? I found a way to pass the security_token but this is not sufficient. What I currently have is this:

(...)
gedmo.listener.loggable:
class: Gedmo\Loggable\LoggableListener
tags:
    - { name: doctrine.event_subscriber, connection: default }
calls:
    - [ setAnnotationReader, [ "@annotation_reader" ] ]
    #- [ setUsername, [ "A fixed value works" ] ]
    - [ setUserValue, [ @security.??? ] ]

I am also using Blameable and found a nice workaround for a similar problem (the entire listener is overriden). I tried to do the same for Loggable, but this appears to be a bit more complex.

Main question: how can I pass the security user object (or its username) to a listener in services.yml?

Update

Matteo showed me how to pass the result of a function as a parameter to a listener. This almost solves the problem. There is another service, that provides the username when given the token_storage. But this means that I need to pass a parameter, to a service, that is given as a parameter to another service. This example will explain:

- [ setUsername, [ "@=service('gedmo.listener.blameable').getUsername( @security.token_storage )" ] ]

The problem now is, that @security.token_storage is not accepted in this context. How can I pass a parameter to the method getUsername() ?

3

There are 3 answers

1
Matteo On

You can use the Symfony Service Expressions, as example you can try the following:

gedmo.listener.loggable:
class: Gedmo\Loggable\LoggableListener
tags:
    - { name: doctrine.event_subscriber, connection: default }
calls:
    - [ setAnnotationReader, [ "@annotation_reader" ] ]
    - [ setUserValue, ["@=service('security.token_storage').getToken()->getUser()->getUsername()"] ]

But in some case the user can be null, so you can a condition with ? (see the doc).

Hope this help

0
VaN On

I think it's not possible to inject the authed user in a service. But you can inject the token storage : @security.token_storage (before symfony 2.6, you will have to use @security.context instead of @security.token_storage) In your service (LoggableListener), you'll be able to get the authed username like this : $tokenStorage->getToken()->getUser()->getUsername()

0
Alexandre Tranchant On

I encountered the same problem and I solved it by creating a logger listener like in stofDoctrineExtensionsBundle.

To do that, I created this file:

//src/AppBundle/Listener/LoggerListener 
<?php

namespace AppBundle\Listener;

use Gedmo\Loggable\LoggableListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;

/**
 * LoggerListener
 *
 * @author Alexandre Tranchant <[email protected]>
 * @author Christophe Coevoet <[email protected]>
 */
class LoggerListener implements EventSubscriberInterface
{
    /**
     * @var AuthorizationCheckerInterface
     */
    private $authorizationChecker;
    /**
     * @var TokenStorageInterface
     */
    private $tokenStorage;
    /**
     * @var LoggableListener
     */
    private $loggableListener;

    /**
     * LoggerListener constructor.
     *
     * @param LoggableListener $loggableListener
     * @param TokenStorageInterface|null $tokenStorage
     * @param AuthorizationCheckerInterface|null $authorizationChecker
     */
    public function __construct(LoggableListener $loggableListener, TokenStorageInterface $tokenStorage = null, AuthorizationCheckerInterface $authorizationChecker = null)
    {
        $this->loggableListener = $loggableListener;
        $this->tokenStorage = $tokenStorage;
        $this->authorizationChecker = $authorizationChecker;
    }
    /**
     * Set the username from the security context by listening on core.request
     *
     * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
     */
    public function onKernelRequest(GetResponseEvent $event)
    {
        if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
            return;
        }
        if (null === $this->tokenStorage || null === $this->authorizationChecker) {
            return;
        }
        $token = $this->tokenStorage->getToken();
        if (null !== $token && $this->authorizationChecker->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
            $this->loggableListener->setUsername($token);
        }
    }
    public static function getSubscribedEvents()
    {
        return array(
            KernelEvents::REQUEST => 'onKernelRequest',
        );
    }
}

Then I declared this listener in my services.yml file.

services:
    #Loggable
    gedmo.listener.loggable:
            class: Gedmo\Loggable\LoggableListener
            tags:
                - { name: doctrine.event_subscriber, connection: default }
            calls:
                - [ setAnnotationReader, [ "@annotation_reader" ] ]

    # KernelRequest listener
    app.listener.loggable:
        class: AppBundle\Listener\LoggerListener
        tags:
            # loggable hooks user username if one is in token_storage or authorization_checker
            - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest}
        arguments:
            - '@gedmo.listener.loggable'
            - '@security.token_storage'
            - '@security.authorization_checker'

As you can see in this dump screenshot, it works fine: Screenshot of Log dump