Symfony logout handler, differentiate between button hit and session expiration - php

I got my custom logout handler which is the following:
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\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface;
use FOS\UserBundle\Model\UserManagerInterface;
use Doctrine\ORM\EntityManager;
use AppBundle\Entity\User;
class LogoutListener implements LogoutHandlerInterface
{
protected $securityContext;
protected $entityManager;
protected $logger;
public function __construct(TokenStorage $securityContext, EntityManager $entityManager, $logger)
{
$this->securityContext = $securityContext;
$this->entityManager = $entityManager;
$this->logger = $logger;
}
public function logout(Request $Request, Response $Response, TokenInterface $Token)
{
$em = $this->entityManager;
$user = $this->securityContext->getToken()->getUser();
$em->getConnection()->executeUpdate("UPDATE subjects SET edited_by = NULL
WHERE edited_by=" . $user->getId());
$this->logger->makelog(1, 'Выход из системы');
}
Here I log into a file all logout actions. Now I need to differentiate between user logout upon button hit, and session expiration. Any ideas how to do that? What service should I implement. Now logout upon session expiration is simply handled in config.yml
session:
save_path: "%kernel.root_dir%/../var/sessions/%kernel.environment%"
cookie_lifetime: 3600
gc_maxlifetime: 3600

Related

How to retrieve azure-user's group memberships?

I'm using knpuniversity's oauthbundle in symfony 5.4 and I have a fairly straightforward implementation of the AzureAuthenticator.
When I fetch the user from Microsoft365 I would like to also retrieve the group memberships of that user. It seems this requires a separate API call, or is there a smoother way to do this?
Can I control in the azure portal what gets passed in the callback? Currently it only holds email, name and a few other values, nothing related to groups.
<?php
namespace App\Security;
use App\Entity\WebtoolsUser as User;
use Doctrine\ORM\EntityManagerInterface;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use KnpU\OAuth2ClientBundle\Security\Authenticator\OAuth2Authenticator;
use KnpU\OAuth2ClientBundle\Client\Provider\AzureClient;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
class AzureAuthenticator extends OAuth2Authenticator
{
private $clientRegistry;
private $em;
private $router;
public function __construct(ClientRegistry $clientRegistry, EntityManagerInterface $em, RouterInterface $router)
{
$this->clientRegistry = $clientRegistry;
$this->em = $em;
$this->router = $router;
}
public function supports(Request $request): ?bool
{
// continue ONLY if the current ROUTE matches the check ROUTE
return $request->attributes->get('_route') === 'login_microsoft_check';
}
public function authenticate(Request $request): PassportInterface
{
$client = $this->clientRegistry->getClient('azure');
$accessToken = $this->fetchAccessToken($client);
return new SelfValidatingPassport(
new UserBadge($accessToken->getToken(), function() use ($accessToken, $client) {
$azureUser = $client->fetchUserFromToken($accessToken);
IS SEPARATE CALL NEEDED HERE? ------->
$email = $azureUser->claim('unique_name');
// 1) have they logged in with a Microsoft account ? Easy!
$existingUser = $this->em->getRepository(User::class)->findOneBy(['azureId' => $azureUser->getId()]);

Redirect from event subscriber

I want to redirect user conditionnaly from my event subscriber on kernel.controller event and change it if user can't acces the one asked.
<?php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\RouterInterface;
class WebPageAccessSuscriber implements EventSubscriberInterface
{
/**
* #var SessionInterface
*/
private $session;
/**
* Contient l'ensemble des parametres de config
*
* #var ParameterBagInterface
*/
private $params;
private $router;
public function __construct(SessionInterface $session, ParameterBagInterface $params, RouterInterface $router)
{
// Retrieve the client session
$this->session = $session;
// Retrieve configuration variable
$this->params = $params;
$this->router = $router;
}
public function onKernelController(ControllerEvent $event)
{
error_log(__METHOD__);
$controller = $event->getController();
if(!is_array($controller)) return;
if ($this->params->get('visitor_access') === false && $controller[0] instanceof \App\Controller\restictedController) {
$event->setController(function() use ($event) {
return new RedirectResponse($this->router->generate('login_' . $event->getRequest()->getLocale()));
});
}
}
public static function getSubscribedEvents()
{
return [
// must be registered before (i.e. with a higher priority than) the default Locale listener
KernelEvents::CONTROLLER => [['onKernelController']]
];
}
}
It works but symfony web profiler doesn't shows anymore.
I mean it does but his content is the one from my login template
Why symfony is redirecting webprofiler to login page ? How can prevent this behavior ? Should I use another way to achieve this ?
If you want to protect a whole section of your application use the symfony security component to create a firewall with its own authentication.
If you want to protect specific pages you could use a Security Voter https://symfony.com/doc/current/security/voters.html
Edit: After denying access via a Voter you can redirect the user with a https://symfony.com/doc/current/security/access_denied_handler.html

Symfony 2.7 - Unable to use render() - onAuthenticationFailure()

I have a file "AuthenticationHandler.php" which is using "onAuthenticationFailure()" function to be able to lock an account after 5 failed attempts. If an account is locked, an email is sent to this user with an unlock link to his account... Everything is working fine here.
The problem is when I try to use "render()" instead of simply adding a sentence in "setBody("something", 'text/html')"
I have tried injecting #twig/#templating/#service_container in my AuthenticationHandler but none of them worked. When I fail on purpose to log in, I'm not receiving any error nor any flashbag saying "Bad Credential". It simply does nothing. It goes back to the login page without any message or mail.
I know my imports(use) are good because if I ommit them, I receive some common error where it says that I forgot a use statement.
I have tried putting a try/catch around "render()", but it simply ignores the rest of my code the moment I reach "$this->template->render('email:myTestPage.html.twig')
Again, every other service I inject works fine, If I simply don't use the "render()" function, an email is sent correctly. Is there any other way to capture an error message? Or at least something to put me on the right track!
service.yml:
authentication_handler:
class: Dbm\UserBundle\Handler\AuthenticationHandler
arguments: ["#session", "#router", "#security.context", "#doctrine.orm.entity_manager", "#mailer", "#twig"]
AuthenticationHandler.php
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\Routing\Router;
use Doctrine\ORM\EntityManager;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
class AuthenticationHandler implements AuthenticationSuccessHandlerInterface, AuthenticationFailureHandlerInterface
{
private $session;
private $security;
private $router;
private $em;
private $mailer;
private $twig;
public function __construct(Session $session, Router $router, SecurityContext $security, EntityManager $em, \Swift_Mailer $mailer, \Twig_Environment $twig)
{
$this->session = $session;
$this->security = $security;
$this->router = $router;
$this->em = $em;
$this->mailer = $mailer;
$this->twig = $twig;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
...
$message = (new \Swift_Message())
->setSubject('Unlock Account')
->setFrom('foobar#foobar.com')
->setTo($mail)
->setBody($this->twig->render('Email:new-unexpire-account.html.twig', array('hash' => $hash)), 'text/html');
$this->mailer->send($message);
}
}
I have also tried with $foo = $this->twig->render('Email:new-unexpire-account.html.twig', array('hash' => $hash)), 'text/html') alone and I'm not getting anything either. It stops there.

Symfony logout after inactivity, make log entry

based on this question
I have implemented an automatic logout of users after a certain period of inactivity (like in question above). This works fine, but I need to make a log entry for this event.
The problem is that when logout fires, I get multiple records in my log file instead of 1 record. I guess I need to listen to some other request, instead of onKernelRequest. Any ideas how to do that? My code is as follows:
<?php
namespace AppBundle\EventListener;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Exception\CredentialsExpiredException;
class RequestListener{
protected $session;
protected $securityToken;
protected $router;
protected $logger;
protected $maxIdleTime;
public function __construct(Session $session, TokenStorage $securityToken, RouterInterface $router, $logger, $maxIdleTime)
{
$this->session = $session;
$this->securityToken = $securityToken;
$this->router = $router;
$this->logger = $logger;
$this->maxIdleTime = $maxIdleTime;
}
public function onKernelRequest(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST != $event->getRequestType()) {
return;
}
if ($this->maxIdleTime > 0) {
$lapse = time() - $this->session->getMetadataBag()->getCreated();
if ($lapse > $this->maxIdleTime) {
$username = $this->securityToken->getToken()->getUser();
if ($username !== 'anon.'){
$username = $username->getUsername();
}
$this->securityToken->setToken(null);
$this->session->getFlashBag()->set('error', 'Your session expired, you need to login again');
$this->session->invalidate();
$this->logger->makelog(//I get multiple log entries here instead of 1
0,
'Session timeout',
$username
);
$event->setResponse(new RedirectResponse($this->router->generate('login')));
}
}
}
}
UPD_1
I have already created a logout listener, but it listens only for logout event when the Logout button is pressed and this action is logged with different log entry. In my code above I use $this->session->invalidate() in order to logout the user. My code for logout listener is as follows:
<?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\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface;
use Doctrine\ORM\EntityManager;
class LogoutListener implements LogoutHandlerInterface
{
protected $securityContext;
protected $entityManager;
protected $logger;
public function __construct(TokenStorage $securityContext, EntityManager $entityManager, $logger)
{
$this->securityContext = $securityContext;
$this->entityManager = $entityManager;
$this->logger = $logger;
}
public function logout(Request $Request, Response $Response, TokenInterface $Token)
{
$em = $this->entityManager;
$user = $this->securityContext->getToken()->getUser();
$this->logger->makelog(1, 'Logout action, logout button', $user);
}
}

Listen to the handle method of firewall symfony

I'm developing a project in Symfony2(.0.x) and I'm building a simple auto login system.
Now to do that I want to listen to the event that triggers the handle() method in the Firewall. Only problem is that I can't find out how to do it. (I'm also using the FOSUserBundle combined with the FOSFacebookbundle)
Someone who could help me. (Or tell me if i'm doing it all wrong)
This is my service:
project.user.auto_login_listener:
class: Project\UserBundle\Listener\AutoLoginListener
public: false
abstract: true
arguments: [#security.context, #security.authentication.manager, '' , '' , #logger, #event.dispatcher]
I have removed my event listener in this example because it doesn't work
<?php
namespace Project\UserBundle\Listener;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Log\LoggerInterface;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\SecurityEvents;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
class AutoLoginListener implements ListenerInterface {
private $authenticationManager;
private $dispatcher;
private $logger;
private $providerKey;
private $securityContext;
private $tokenParam;
public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, $providerKey, $tokenParam, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null)
{
$this->securityContext = $securityContext;
$this->authenticationManager = $authenticationManager;
$this->providerKey = $providerKey;
$this->tokenParam = $tokenParam;
$this->logger = $logger;
$this->dispatcher = $dispatcher;
}
public function handle(GetResponseEvent $event)
{
die("test");
}
}
?>
Thanks!
You have to create a new authentication provider. Follow this cookbook entry.

Categories