I'm working on a Symfony 2.7 project and I'd like to redirect users on a specific page if their session expires.
I created a Listener that checks roles and routes and if a user is trying to access an authenticated route without being authenticated i redirect to the specific page.
This is not working for the moment because the default redirection to homepage has an higher priority.
This is the listener:
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Routing\Router;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
class UserDisconnectionListener
{
protected $router;
protected $security;
protected $tokenStorage;
public function __construct(Router $router, AuthorizationChecker $security, TokenStorage $tokenStorage)
{
$this->router = $router;
$this->security = $security;
$this->tokenStorage = $tokenStorage;
}
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$loggedRoutes = ['user_account', 'user_index', '...'];
$url = '';
if($this->tokenStorage->getToken() !== null) {
if(!$this->security->isGranted('IS_AUTHENTICATED_OPENID')) {
if (in_array($request->get('_route'), $loggedRoutes)) {
$url = $this->router->generate('disconnection_route');
}
}
if(strlen($url) > 0) {
$response = new RedirectResponse($url);
$event->setResponse($response);
}
}
}
}
app.userroute.listener:
class: app\UserBundle\Listener\UserDisconnectionListener
arguments: ['#router.default', #security.authorization_checker, #security.token_storage]
scope: request
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
Is there a better way to do it ?
I tried to use priority tag but i don't which priority set.
I tried to log and when i tried to access 'user_index' route my listerner log said that i was already redirected on the homepage 'general_index' route.
PS: I'm using FOSUserBundle coupled with an OpenID Bundle.
tags:
- { name: kernel.event_listener, priority: -256, event: kernel.request, method: onKernelRequest }
https://symfony.com/doc/2.7/event_dispatcher.html check the symfony documentation for events, and search for priority
This tag option specify to symfony in which order he haves to call listeners.
It isn't the best way to 'hardcode' the list of authenticated routes, if you forget to add one your code won't work. You should try to retrieve dynamically the authenticated routes
Related
I can't figure out how to impersonate a user by user's id instead of user's username in Symfony?
The following trick which works with username can't work with id, as symfony is looking for username:
?_switch_user={id}
This is impossible to do without implementing your own firewall listener, as behind the scenes it loads the user from the userprovider (which only has a loadUserByUsername() method in its interface).
You could however implement your own firewall listener and get inspired by having a look at the code in Symfony\Component\Security\Http\Firewall\SwitchUserListener. For detailed information on implementing your own authentication provider, check the cookbook article.
EDIT:
One possible solution might be registering an extra request listener:
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class LookupSwitchUserListener implements EventSubscriberInterface
{
private $repository;
public function __construct(UserRepository $repository)
{
$this->repository = $repository;
}
public static function getSubscribedEvents()
{
return [
KernelEvents::REQUEST => ['lookup', 12] // before the firewall
];
}
public function lookup(GetResponseEvent $event)
{
$request = $event->getRequest();
if ($request->has('_switch_user') {
return; // do nothing if already a _switch_user param present
}
if (!$id = $request->query->has('_switch_user_by_id')) {
return; // do nothing if no _switch_user_by_id param
}
// lookup $username by $id using the repository here
$request->attributes->set('_switch_user', $username);
}
}
Now register this listener in the service container:
services:
my_listener:
class: LookupSwitchUserListener
tags:
- { name: kernel.event_subscriber }
Calling a url with the ?_switch_user_by_id=xxx parameter should now correctly look up the username and set it so the SwitchUserListener can switch to the specified user.
I have a problem with translation. I use
symfony 2.7
sonata admin-bundle 2.3
I have create the interactive login listener, when the user log in the application I get the user locale and set the session _locale, but this is ignore in sonata.
Listener is
class UserLocaleListener {
/**
* #var Session
*/
private $container;
public function __construct(Session $session)
{
$this->session = $session;
}
public function onInteractiveLogin(InteractiveLoginEvent $event)
{
$request = $event->getRequest();
$user = $event->getAuthenticationToken()->getUser();
if (null !== $user->getLocale()) {
$this->session->set('_locale', $user->getLocale());
//$request->setLocale($user->getLocale());
var_dump($request->getSession()->get('_locale'));
}
}
}
in service.yml add
app.user_locale_listener:
class: xxxxxx\xxxxxxxx\EventListener\UserLocaleListener
arguments: ["#session"]
tags:
- { name: kernel.event_listener, event: security.interactive_login, method: onInteractiveLogin }
Where is my mistake ?
the locale is set on the request, and will not "stick" , so each request it will be the default again, unless you do something like this:
http://symfony.com/doc/current/cookbook/session/locale_sticky_session.html
Wich will on each request, take the locale from the session, and set it on the current request.
(make sure that LocaleListener has a lower priority then your UserLocaleListener, so that it runs after it)
In my project, I allow only one user to manage the content of the website. This user will be added using the command line at first.
Now, I want to get the registration action inaccessible and I don't know how?
Till now, I just put the ROLE_ADMIN in the access control for the route register to avoid that visitors can go throw it.
Any tips?
Take a look at the routing configuration imported from
vendor/friendsofsymfony/user-bundle/Resources/config/routing/all.xml
If you want just the basic security actions, just import
#FOSUserBundle/Resources/config/routing/security.xml
instead of
#FOSUserBundle/Resources/config/routing/all.xml
This way you can simply select which components (security, profile, resetting, change_password) you want to use or event import only specific routes from those components.
There are many ways to solve this issue. You can simply remove fos_user_registration_register route from routing.yml. Or use more complicated solution: set up event listener to FOS\UserBundle\FOSUserEvents::REGISTRATION_INITIALIZE event and redirect user to login page.
services.xml
<service id="app.registration.listener" class="AppBundle\EventListener\RegistrationListener">
<tag name="kernel.event_subscriber" />
<argument type="service" id="router" />
</service>
RegistrationListener.php
<?php
namespace AppBundle\EventListener;
use FOS\UserBundle\Event\GetResponseUserEvent;
use FOS\UserBundle\FOSUserEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class RegistrationListener implements EventSubscriberInterface
{
/**
* #var UrlGeneratorInterface
*/
private $router;
/**
* #param UrlGeneratorInterface $router
*/
public function __construct(UrlGeneratorInterface $router) {
$this->router = $router;
}
public static function getSubscribedEvents()
{
return [
FOSUserEvents::REGISTRATION_INITIALIZE => 'onRegistrationInitialize',
];
}
public function onRegistrationInitialize(GetResponseUserEvent $event)
{
$url = $this->router->generate('fos_user_security_login');
$response = new RedirectResponse($url);
$event->setResponse($response);
}
}
You can just change app/config/security.yml:
- { path: ^/register, role: ROLE_ADMIN }
Change from the default (IS_AUTHENTICATED_ANONYMOUSLY) to ROLE_ADMIN and it will stop allowing anonymous users from getting to the /register form.
Another simple solution (the one I used) is to overwrite the registerAction() default FOSUserBundle controller method:
namespace Acme\UserBundle\Controller;
use FOS\UserBundle\Controller\RegistrationController as FOSRegistrationController;
use Symfony\Component\HttpFoundation\Request;
class RegistrationController extends FOSRegistrationController
{
public function registerAction(Request $request)
{
return $this->redirectToRoute('getStarted', array(), 301);
}
}
Doing this will leave active other routes, as the confirmation page.
I simply overwrote the register action and redirect the user to my first registration page (getStarted).
If you use the JMSSecurityExtraBundle you can use the denyAll directive like so:
- { path: ^/register, access: denyAll }
This is how I solve this issue...
First you have to define your listener in the services.yml file:
services:
registrationListner:
class: App\YourBundle\Listener\RegistrationListener
arguments: [#service_container]
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest}
Then create your class RegistrationListener:
<?php
namespace App\YourBundle\Listener;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class RegistrationListener
{
private $router;
public function __construct(ContainerInterface $container){
$this->router = $container->get('router');
}
public function onKernelRequest(GetResponseEvent $event)
{
$route = $event->getRequest()->attributes->get('_route');
if ($route == 'fos_user_registration_register') {
//here we're gonna to redirect to you_route for example, I guess in the most cases it will be the index...
$event->setResponse(new RedirectResponse($this->router->generate('your_route')));
}
}
}
Hope it helps.
You can try to change your routing.yml
fos_user_registration_register:
path: /register{trailingSlash}
defaults: { _controller: AcmeBundle:Default:register, trailingSlash : "/" }
requirements: { trailingSlash : "[/]{0,1}" }
And in your DefaultController
public function registerAction(Request $request)
{
return $this->redirectToRoute('404OrWhatYouWant');
}
Is it possible, in Symfony2, to logout a user that has been logged in one machine, when user logs in another machine?
My User entity has a field that contains the machine identifier where user has logged in last place and I had thought using the login listener to invalidate the previous user's session, but i have no success with it.
¿Have you any idea?
Thanks
You can use AuthenticationSuccessHandlerInterface if user provides correct credentials and then check the machine id.
Take a look for example from my app:
Define your listener as service:
services:
blogjakon.user.authentication_handler:
class: BlogJakon\UserBundle\ActionListener\AuthenticationHandler
calls:
- [ setContainer, [ #service_container ] ]
security.yml:
form_login:
[...]
success_handler: blogjakon.user.authentication_handler
Listener class:
class AuthenticationHandler extends ContainerAware implements AuthenticationSuccessHandlerInterface{
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
$templating = $this->container->get('templating');
$machineIdentifier = $this->container->get('machine.identifier');
if( $token->getUser()->getMachineId() == $machineIdentifier->getCurrentId() )
{
return $templating->renderResponse('yourBundle::logged.html.twig');
}
else
{
// Update user entity with new machineId and redirect.
return $templating->renderResponse('yourBundle::logged-in-new-machine.html.twig');
}
}
}
Of course service machine.identifier is your service that provides functionality to get/generate/check your machine id. You have to implement this service by yourself.
On each request you have to check if user is all the time on the same machine.
You can do it in 2 ways:
If you have your own user provider you can implement refreshUser(UserInterface $user) method.
Register request listener.
I will show you how to register request listener:
service for this listener:
services:
listener.requestresponse:
class: My\AwesomeBundle\Listener\MyListener
arguments: [ #service_container ]
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
Listener class:
class MyListener
{
protected $container;
public function __construct(ContainerInterface $container) // this is #service_container
{
$this->container = $container;
}
public function onKernelRequest(GetResponseEvent $event)
{
$securityContex = $this->get('security.context');
$machineIdentifier = $this->container->get('machine.identifier');
if( $securityContext>getToken()->getUser()>getMachineId() != $machineIdentifier->getCurrentId() )
$this->get('security.context')->setToken(NULL);
}
}
This example comes from this question.
Hope it helps.
How can I trigger redirect to a specific router in event listener?
There are many examples but I couldn't find one for "GetResponseForExceptionEvent". For exampe, when I pass #roter as an argument $this->router.... doesn't seem to be working so on.
I checked these but I probably missed something:
Show a specific route instead of error page (404) - Symfony2
symfony2: hook into NotFoundHttpException for redirection
Symfony2 redirect for event listener?
Symfony2: Getting Route in Page Load Event Listener
Redirect if the user is logged in
service.yml
services:
kernel.listener.kernel_request:
class: Booking\AdminBundle\EventListener\ErrorRedirect
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
Event listener:
namespace Booking\AdminBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class ErrorRedirect
{
public function onKernelException(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
if ($exception instanceof NotFoundHttpException) {
// redirect to '/' router or '/error'
//$event->setResponse(...........);
}
}
}
You have 2 options:
Use an Event Listener
Use a route that matches all the routes that don't exist and trigger an action in a controller.
Let me know if that works.
Option 1
Note: Have a look at Symfony2 redirect for event listener
1 Pass the router to your event listener:
kernel.listener.kernel_request:
class: Booking\AdminBundle\EventListener\ErrorRedirect
arguments:
router: "#router"
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
2 Use the router within the listener to redirect users:
namespace Booking\AdminBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
class ErrorRedirect
{
/**
* Holds Symfony2 router
*
*#var Router
*/
protected $router;
/**
* #param Router
*/
public function __construct(Router $router)
{
$this->router = $router;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
if ($exception instanceof NotFoundHttpException) {
/** Choose your router here */
$route = 'route_name';
if ($route === $event->getRequest()->get('_route')) {
return;
}
$url = $this->router->generate($route);
$response = new RedirectResponse($url);
$event->setResponse($response);
}
}
}
Option 2
You can create a special route that will match all routes that don't exist; You can then handle the redirect the way you want within the controller of your choice, here PageNotFoundController:
1 At the end of app/config/routing.yml
pageNotFound:
pattern: /{path}
defaults: { _controller: AcmeDemoBundle:PageNotFound:pageNotFound, path: '' }
requirements:
path: .*
2
<?php
namespace Acme\DemoBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class PageNotFoundController extends Controller
{
public function pageNotFoundAction()
{
// redirect the way you want
}
}