<?php
declare(strict_types=1);
/**
* @author Maciej Kaczmarek <maciej.kaczmarek@autentika.pl>
*/
namespace Hitso\Bundle\CommonBundle\EventListener;
use Anyx\LoginGateBundle\Service\BruteForceChecker;
use Doctrine\ORM\EntityManagerInterface;
use FOS\UserBundle\Event\UserEvent;
use FOS\UserBundle\FOSUserEvents;
use Hitso\Bundle\CommonBundle\Entity\Log\Security;
use Hitso\Bundle\CommonBundle\Entity\User;
use Hitso\Bundle\CommonBundle\Entity\UserConnection;
use Hitso\Bundle\CommonBundle\Entity\UserLogEntry;
use Hitso\Bundle\CommonBundle\Security\Event\OAuthEvent;
use Hitso\Bundle\MultiSiteBundle\MultiSite\SiteContext;
use Hitso\Bundle\MultiSiteBundle\Router\MultiSiteRouter;
use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface;
use Symfony\Component\Security\Http\ParameterBagUtils;
use Symfony\Component\Security\Http\SecurityEvents;
/**
* Class SecurityListener
*
* @codeCoverageIgnore
*/
class SecurityListener implements EventSubscriberInterface, LogoutHandlerInterface, AuthenticationSuccessHandlerInterface
{
/**
* @var EntityManagerInterface
*/
private $manager;
/**
* @var $router MultiSiteRouter
*/
protected $router;
/**
* @var $session Session
*/
protected $session;
/**
* @var UserConnection
*/
private $connection;
/**
* @var SiteContext
*/
private $context;
/**
* @var BruteForceChecker
*/
private $bruteForceChecker;
/**
* SecurityListener constructor.
*
* @param EntityManagerInterface $manager
* @param MultiSiteRouter $router
* @param SessionInterface $session
* @param SiteContext $context
*/
public function __construct(
EntityManagerInterface $manager,
MultiSiteRouter $router,
SessionInterface $session,
SiteContext $context,
BruteForceChecker $bruteForceChecker
) {
$this->manager = $manager;
$this->router = $router;
$this->session = $session;
$this->context = $context;
$this->bruteForceChecker = $bruteForceChecker;
}
/**
* @inheritdoc
*/
public static function getSubscribedEvents()
{
return [
SecurityEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin',
FOSUserEvents::SECURITY_IMPLICIT_LOGIN => 'onImplicitLogin',
FOSUserEvents::REGISTRATION_COMPLETED => 'onRegisterComplete',
FOSUserEvents::REGISTRATION_CONFIRMED => 'onRegisterConfirmed',
FOSUserEvents::RESETTING_RESET_COMPLETED => 'onResetComplete',
OAuthEvent::EVENT_NAME => 'onOAuthEvent',
];
}
/**
* Listens to interactive login event fired by Symfony Security component and logs:
* - OAuth login
* - interactive login
*
* @param InteractiveLoginEvent $e
*
* @throws \Hitso\Bundle\CommonBundle\Exception\InvalidArgumentException
* @throws \Hitso\Bundle\MultiSiteBundle\Exception\InvalidArgumentException
*/
public function onInteractiveLogin(InteractiveLoginEvent $e)
{
$token = $e->getAuthenticationToken();
$this->setDefaultLocale($token);
if ($token instanceof OAuthToken) {
$this->log(
new Security(
Security::ACTION_LOGIN,
Security::METHOD_OAUTH,
$token->getResourceOwnerName(),
$this->connection ? $this->connection->getIdentifier() : null
),
$token
);
} elseif ($token instanceof RememberMeToken) {
$this->log(new Security(Security::ACTION_LOGIN, Security::METHOD_REMEMBER_ME), $token);
} else {
$this->log(new Security(Security::ACTION_LOGIN, Security::METHOD_INTERACTIVE), $token);
}
}
/**
* Save a log entry to the database.
*
* @param UserLogEntry $entry
* @param null $tokenOrUser
*/
private function log(UserLogEntry $entry, $tokenOrUser = null)
{
$user = $this->getUserFromToken($tokenOrUser);
if ($user instanceof User) {
$entry->setUser($user);
$entry->setExtraData([
'device' => $user->getLastLoginDevice(),
]);
}
if (null !== $entry->getUser()) {
$this->manager->persist($entry);
$this->manager->flush();
}
}
/**
* @param null $tokenOrUser
*
* @throws \Hitso\Bundle\CommonBundle\Exception\InvalidArgumentException
* @throws \Hitso\Bundle\MultiSiteBundle\Exception\InvalidArgumentException
*/
private function setDefaultLocale($tokenOrUser = null)
{
$user = $this->getUserFromToken($tokenOrUser);
$sites = $this->context->getSites();
if ($sites->has($user->getDefaultLocale())) {
$site = $sites->get($user->getDefaultLocale());
if (isset($user, $site)) {
$this->context->setContentSite($site);
}
}
}
/**
* @param null $tokenOrUser
*
* @return mixed|null
*/
private function getUserFromToken($tokenOrUser = null)
{
if ($tokenOrUser instanceof TokenInterface) {
return $tokenOrUser->getUser();
} elseif ($tokenOrUser instanceof User) {
return $tokenOrUser;
}
return null;
}
/**
* Listens to implicit login event fired by FOSUserBundle.
*
* @param UserEvent $e
*
* @throws \Hitso\Bundle\CommonBundle\Exception\InvalidArgumentException
* @throws \Hitso\Bundle\MultiSiteBundle\Exception\InvalidArgumentException
*/
public function onImplicitLogin(UserEvent $e)
{
$this->setDefaultLocale($e->getUser());
$this->log(new Security(Security::ACTION_LOGIN, Security::METHOD_IMPLICIT), $e->getUser());
}
/**
* Listens to register complete event fired by FOSUserBundle.
*
* @param UserEvent $e
*/
public function onRegisterComplete(UserEvent $e)
{
$this->log(new Security(Security::ACTION_REGISTER, Security::METHOD_INTERACTIVE), $e->getUser());
}
/**
* Listens to register confirmed event fired by FOSUserBundle.
*
* @param UserEvent $e
*/
public function onRegisterConfirmed(UserEvent $e)
{
$this->log(new Security(Security::ACTION_REGISTER_CONFIRMED, Security::METHOD_INTERACTIVE), $e->getUser());
}
/**
* Listens to reset complete event fired by FOSUserBundle.
*
* @param UserEvent $e
*/
public function onResetComplete(UserEvent $e)
{
$this->log(new Security(Security::ACTION_RESET_PASSWORD, Security::METHOD_INTERACTIVE), $e->getUser());
}
/**
* Listens to OAuth event fired by Hitso's OAuth user provider.
*
* @param OAuthEvent $e
*
* @throws \Hitso\Bundle\CommonBundle\Exception\InvalidArgumentException
* @throws \Hitso\Bundle\MultiSiteBundle\Exception\InvalidArgumentException
*/
public function onOAuthEvent(OAuthEvent $e)
{
$this->connection = $connection = $e->getConnection();
$this->setDefaultLocale($e->getUser());
if ($e->isNewUser()) {
$this->log(
new Security(
Security::ACTION_REGISTER,
Security::METHOD_OAUTH,
$connection->getProvider(),
$connection->getIdentifier()
),
$e->getUser()
);
if ($e->getUser()->isEnabled()) {
$this->log(
new Security(
Security::ACTION_REGISTER_CONFIRMED, Security::METHOD_IMPLICIT
),
$e->getUser()
);
}
}
if ($e->isNewConnection()) {
$this->log(
new Security(
Security::ACTION_CONNECT,
Security::METHOD_OAUTH,
$connection->getProvider(),
$connection->getIdentifier()
),
$e->getUser()
);
}
}
/**
* This method is called by the LogoutListener when a user has requested
* to be logged out. Usually, you would unset session variables, or remove
* cookies, etc.
*
* @param Request $request
* @param Response $response
* @param TokenInterface $token
*/
public function logout(Request $request, Response $response, TokenInterface $token)
{
$securityLog = new Security(Security::ACTION_LOGOUT);
$this->log($securityLog, $token);
}
/**
* @param InteractiveLoginEvent $event
*
* @throws \Hitso\Bundle\CommonBundle\Exception\InvalidArgumentException
* @throws \Hitso\Bundle\MultiSiteBundle\Exception\InvalidArgumentException
*/
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
$token = $event->getAuthenticationToken();
$request = $event->getRequest();
$this->setDefaultLocale($token);
$this->onAuthenticationSuccess($request, $token);
}
/**
* @param Request $request
* @param TokenInterface $token
*
* @return RedirectResponse|Response
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
$targetUrl = urldecode(ParameterBagUtils::getRequestParameterValue($request, '_target_path'));
if ($targetUrl === '') {
$targetUrl = $this->router->getRouteCollection()->get('homepage') ? $this->router->generate('homepage') : '/';
}
$this->bruteForceChecker->getStorage()->clearCountAttempts($request);
return new RedirectResponse($targetUrl);
}
}