I would like to have some action after a success authentification.
I created a class extended DefaultAuthenticationSuccessHandler, and redefined the onAuthenticationSuccess function.
Though the authentication is a success, the programm never goes to the function : why ?
namespace my_name_space;
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Bridge\Monolog\Logger;
class AuthenticationSuccessHandler extends DefaultAuthenticationSuccessHandler {
private $em;
private $logger;
function __construct(Logger $logger, HttpUtils $httpUtils, EntityManager $em) {
parent::__construct($httpUtils);
$this->em = $em;
$this->logger = $logger;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token) {
$this->logger->info("onAuthenticationSuccess");
$response = parent::onAuthenticationSuccess($request, $token);
$token->eraseCredentials();
$token->getUser()->addRole('ROLE_USER');
return $response;
}
}
services:
security.authentication.success.handler:
class: MY\NAMESPACE\AuthenticationSuccessHandler
arguments: ["#monolog.logger.php","#security.http_utils", "#doctrine.orm.default_entity_manager"]
security.yml
form_login:
check_path: _security_check
use_referer: true
use_forward: true
login_path: my_login
success_handler: security.authentication.success.handler
You can listen for the event that is triggered after a successful login attempt by registering a service to the related security tag:
app.login_listener:
class: AppBundle\EventListener\LoginListener
arguments:
- #security.authorization_checker
- #security.token_storage
tags:
- { name: kernel.event_listener, event: security.interactive_login, method: yourMethod }
Where yourMethod can contain the code you want to execute.
There are a lot of same issues on SO, github, google groups, most of them are unsolved.
Also, you should define a custom AuthenticationSuccessHandler rather than extending the default.
Example:
services.yml
login_success_handler:
class: YourBundle\EventListener\CustomAuthenticationSuccessHandler
arguments: ["#doctrine", "#router"]
tags:
- { name: kernel.event_listener, event: security.interactive_login, method: onSecurityInteractiveLogin }
CustomAuthenticationSuccessHandler.php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\HttpFoundation\Routing\RouterInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Doctrine\ORM\EntityManager;
class CustomAuthenticationSuccessHandler {
private $em;
private $logger;
public function __construct(EntityManager $em, RouterInterface $router)
{
$this->em = $em;
$this->router = $router;
}
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
$this->onAuthenticationSuccess($event->getRequest(), $event->getAuthenticationToken());
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
$user = $token->getUser();
$user->eraseCredentials();
$user->addRole('ROLE_USER');
$this->em->flush() // If you don't do this, your changes will not be saved.
$response = new RedirectResponse($this->router->generate('your_success_route'));
return $response;
}
}
Don't need of manually set the success_handler in your form, because the SuccessHandler is an EventListener and will be used on the corresponding event.
I don't know what the interactive login event is proposed here therefore I write a separate answer. #chalasr's answer worked for me but with security.authentication.success event:
login_success_handler:
class: YourBundle\EventListener\CustomAuthenticationSuccessHandler
arguments: ["#doctrine", "#router"]
tags:
- { name: kernel.event_listener, event: security.authentication.success, method: onAuthenticationSuccess }
Related
In symfony 3 I have subscribed to
FOSUserEvents::REGISTRATION_COMPLETED
to fill a second table with address data and writing a log.
namespace AppBundle\EventListener;
use AppBundle\Entity\Address;
use Doctrine\ORM\EntityManager;
use Psr\Container\ContainerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FilterUserResponseEvent;
class RegistrationSubscriber implements EventSubscriberInterface
{
private $em;
private $container;
public function __construct(EntityManager $em,ContainerInterface $container)
{
$this->em = $em;
$this->container = $container;
}
public static function getSubscribedEvents()
{
return [
FOSUserEvents::REGISTRATION_COMPLETED => [
['onRegistrationCompleted',0]
],
];
}
public function onRegistrationCompleted(FilterUserResponseEvent $event)
{
$user = $event->getUser();
$address = new Address();
$address->addAddress($user);
$this->em->persist($address);
$this->em->flush();
$this->container->get('app.logging')->write('Anmeldung',array('customer'=>$user->getId()));
}
}
services.yml:
app.registration_listener:
class: 'AppBundle\EventListener\RegistrationSubscriber'
arguments: ['#doctrine.orm.entity_manager']
tags:
- { name: kernel.event_subscriber}
app.logging:
class: AppBundle\Util\LogHandler
arguments: ['#doctrine.orm.entity_manager']
public: true
After submitting the registration form, FOSUserEvents::REGISTRATION_COMPLETED is triggered, but it is running twice, because address data and log data are written twice.
What can be the reason ?
I found it by myself.
In services.yml of symfony 3.3 I used the option
autoconfigure: true,
so services and event subscribers are loaded automatically.
That is why the event was triggered twice.
I'm trying to override the LastLoginListener to add functionality to it.
I;m trying to do it as described here
It seems
In AppBundle\DependencyInjection\OverrideServiceCompilerPass.php
<?php
namespace AppBundle\DependencyInjection\Compiler;
use AppBundle\EventListener\LastLoginListener;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class OverrideServiceCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition('"fos_user.security.interactive_login_listener');
$definition->setClass(LastLoginListener::class);
}
services.yml
services:
app.login_listener:
class: AppBundle\EventListener\LastLoginListener
arguments: []
tags:
- { name: kernel.event_subscriber }
The listener itself is copied from the bundle.
The autoloader expected class "AppBundle\DependencyInjection\OverrideServiceCompilerPass" to be defined in file "/vendor/composer/../../src/AppBundle/DependencyInjection/OverrideServiceCompilerPass.php". The file was found but the class was not in it, the class name or namespace probably has a typo.
in DebugClassLoader.php (line 261)
My goal is to add the ip address of the last login with the listener, but I'll need to create another to add a role and a registration date
I'm trying to do it "the right way" instead of doing something hackish
Its much better to use success_handler and failure_handler services.
# app/config/security.yml
firewalls:
main:
...
form_login:
...
success_handler: authentication_success_handler
failure_handler: authentication_failure_handler
Next you need to register your services and add arguments that fit your needs (probably #router and #doctrine.orm.entity_manager)
# app/config/services.yml
authentication_success_handler:
class: AppBundle\Handler\AuthenticationSuccessHandler
arguments: ['#router', '#doctrine.orm.entity_manager']
authentication_failure_handler:
class: AppBundle\Handler\AuthenticationFailureHandler
arguments: ['#router', '#doctrine.orm.entity_manager']
Then you need to create your services
// src/AppBundle/Handler/AuthenticationSuccessHandler.php
<?php
namespace AppBundle\Handler;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Router;
use Doctrine\Common\Persistence\ObjectManager;
class AuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface {
protected $router;
private $em;
public function __construct(Router $router, ObjectManager $em) {
$this->router = $router;
$this->em = $om;
}
public function onAuthenticationSuccess(Request $request, AuthenticationException $exception) {
// your code here - creating new object. redirects etc.
}
}
and
// src/AppBundle/Handler/AuthenticationFailureHandler.php
<?php
namespace AppBundle\Handler;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Router;
use Doctrine\Common\Persistence\ObjectManager;
class AuthenticationFailureHandler implements AuthenticationFailureHandlerInterface {
protected $router;
private $em;
public function __construct(Router $router, ObjectManager $em) {
$this->router = $router;
$this->em = $om;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception) {
// your code here - creating new object. redirects etc.
}
}
If you want to hook into another FOSUserBundle Controller use this
I have a config table with a 'web_enabled' key, when true I want to show the route requested but when false I want to show a 'Site in maintenance' page. Obviously this check has to be performed before any route action.
I have been reading about Events and Listeners but I don't see how to implement the access to the doctrine and template.
Thanks for the help.
This is the solution I implemented finally, differs from the proposed by Alsatian because I don't use a parameter in the service.yml. Is just a question of taste, nothing else.
in app/config/services.yml
services:
app.request_listener:
class: AppBundle\EventListener\RequestListener
arguments: ["#doctrine.orm.entity_manager","#templating"]
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelController }
in src/AppBundle/EventListener/RequestListener.php
namespace AppBundle\EventListener;
use \Symfony\Component\HttpKernel\Event\GetResponseEvent;
use \Symfony\Component\HttpFoundation\Response;
class RequestListener
{
private $em;
private $templating;
public function __construct($em, $templating)
{
$this->em = $em;
$this->templating = $templating;
}
public function onKernelController(GetResponseEvent $event)
{
if ( !$this->configKey = $this->em->getRepository('AppBundle:Config')->getconfig('web_enabled') )
$event->setResponse($this->templating->renderResponse('default/construction.html.twig'));
}
}
and in src/AppBundle/Repository/ConfigRepository.php explaining the getconfig method:
namespace AppBundle\Repository;
class ConfigRepository extends \Doctrine\ORM\EntityRepository
{
public function getconfig( $config_name )
{
$config = $this->getEntityManager()
->createQuery('SELECT p.config_value FROM AppBundle:Config p WHERE p.config_name = :config_name')
->setParameter('config_name', $config_name)
->getResult();
if (sizeof($config)){
return $config[0]['config_value'];
}else{
return false;
}
}
}
Hope this helps.
You have just to inject both EntityManager and Templating in your listener :
Definition as service :
# src/AppBundle/Ressources/config/services.yml
services:
app.request_listener:
class: AppBundle\EventListener\RequestListener
arguments: ["%web_enabled%","#doctrine.orm.entity_manager","#templating"]
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
Listener :
namespace AppBundle\EventListener;
use \Symfony\Component\HttpKernel\Event\GetResponseEvent;
use \Symfony\Component\HttpFoundation\Response;
class RequestListener
{
private $configKey;
private $em;
private $templating;
public __construct($configKey, $em, $templating)
{
$this->configKey = $configKey;
$this->em = $em;
$this->templating = $templating;
}
public function onKernelRequest(GetResponseEvent $event)
{
if(!$this->configKey){
$var = $this->em->getRepository('AppBundle:MyEntity')->findOne(1);
$event->setResponse($this->templating->renderResponse('my_template.html.twig',array('var'=>$var));
}
}
}
I have a problem in the code of event listener because i need to make a EventListener when i login as type 1 ==>redirect to:
return $this->redirect($this->generateUrl('demands_patient'));
else redirect to another path .And this is the code of loginlistener
use Doctrine\ORM\Event\LifecycleEventArgs;
use Register\UserBundle\Entity\User;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use FOS\UserBundle\Event\GetResponseUserEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Core\AuthenticationEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
class LoginListener implements EventSubscriberInterface
{
private $router;
public function __construct(UrlGeneratorInterface $router)
{
$this->router = $router;
}
public static function onSecurityInteractiveLogin()
{
return array(
FOSUserEvents::AUTHENTICATION_SUCCESS => 'redirectlogin',
);
}
public function redirectlogin(InteractiveLoginEvent $event)
{
$user = $event->getUser();
if($user->getType()=="1")
{
return $this->redirect($this->generateUrl('demands_patient'));
}
else
{
return $this->redirect($this->generateUrl('demands'));
}
}
}
and i add configuration in services.yml:
register_user.eventlistener.loginlistener:
class: Register\UserBundle\EventListener\LoginListener
arguments: [#router]
tags:
- { name: kernel.onSecurityInteractiveLogin }
Just today I faced a similar problem.
My solution was to use a Handler instead of a Listener, as explained in http://www.reecefowell.com/2011/10/26/redirecting-on-loginlogout-in-symfony2-using-loginhandlers/
What I did is:
In my bundle's `services.yml':
parameters:
cambra_comunitats.handler.user_login_redirect_handler.class: Cambra\ComunitatsBundle\Handler\UserLoginRedirectHandler
services:
cambra_comunitats.handler.user_login_redirect_handler:
class: "%cambra_comunitats.handler.user_login_redirect_handler.class%"
arguments: [#router, #security.context]
tags:
- { name: 'monolog.logger', channel: 'security' }
Then, I created the Cambra\ComunitatsBundle\Handler\UserLoginRedirectHandler class:
class UserLoginRedirectHandler implements \Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface
{
protected $router;
protected $security;
public function __construct(\Symfony\Component\Routing\Router $router, \Symfony\Component\Security\Core\SecurityContext $security)
{
$this->router = $router;
$this->security = $security;
}
public function onAuthenticationSuccess(\Symfony\Component\HttpFoundation\Request $request, \Symfony\Component\Security\Core\Authentication\Token\TokenInterface $token)
{
if ($this->security->isGranted('ROLE_ADMIN') && $this->security->getToken()->getUser()->isMustRedirect())
{
$response = new \Symfony\Component\HttpFoundation\RedirectResponse($this->router->generate('new_destination'));
} else
{
$referer_url = $this->router->generate('index_page');
$response = new \Symfony\Component\HttpFoundation\RedirectResponse($referer_url);
}
return $response;
}
}
And, finally, I enabled it on security.yml, inside of firewalls.main.form_login:
//.....
firewalls:
//...
main:
//...
form_login:
//...
success_handler: cambra_comunitats.handler.user_login_redirect_handler
I am listening to security.authentication.success event. Everytime a customer logs in i need to check if he still got main information. If not, then i want to redirect him to the form for creation. It is running good but in which way can i redirect out of an event? Simply returning an Redirect Object does not work.
Service:
applypie_userbundle.newuser_listener:
class: Applypie\Bundle\UserBundle\EventListener\NewUserListener
arguments: [#router]
tags:
- { name: kernel.event_listener, event: security.authentication.success, method: onLogin }
Listener:
namespace Applypie\Bundle\UserBundle\EventListener;
use Symfony\Component\Security\Core\Event\AuthenticationEvent;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Router;
class NewUserListener
{
protected $context;
protected $router;
public function __construct(Router $router)
{
$this->router = $router;
}
/**
* #param AuthenticationEvent $event
*
* if current user neither have an applicant row or an company row, redirect him to create applicant row
*/
public function onLogin(AuthenticationEvent $event)
{
$user = $event->getAuthenticationToken()->getUser();
if(!$user->getApplicant() && !count($user->getCompanies()))
{
return new RedirectResponse($this->router->generate('applypie_user_applicant_create'));
}
}
}
To cut the long story short, yes, you are listening to the correct event but... is someone waiting your method return?... NOPE!
I can't test this right now, but this is the idea:
security.yml
firewalls:
main:
pattern: ^/
form_login:
success_handler: my.security.login_handler
services.yml
services:
my.security.login_handler:
class: Applypie\Bundle\UserBundle\EventListener\NewUserListener
arguments: [#router]
NewUserListener
namespace Applypie\Bundle\UserBundle\EventListener;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\HttpFoundation\Request;
class NewUserListener implements AuthenticationSuccessHandlerInterface
{
protected $router;
public function __construct(RouterInterface $router)
{
$this->router = $router;
}
protected function getResponse($name)
{
$url = $this->router->generate($name);
$response = new RedirectResponse($url);
return $response;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
$user = $token->getUser();
$route = 'user_home';
if(!$user->getApplicant() && empty($user->getCompanies())) {
$route = 'applypie_user_applicant_create';
}
return $this->getResponse($route);
}
}
Sources:
http://api.symfony.com/2.3/Symfony/Component/Security/Http/Authentication/AuthenticationSuccessHandlerInterface.html
Do something just after symfony2 login success and before redirect?