vendor/ibexa/rest/src/lib/Server/Security/RestAuthenticator.php line 35

Open in your IDE?
  1. <?php
  2. /**
  3.  * @copyright Copyright (C) Ibexa AS. All rights reserved.
  4.  * @license For full copyright and license information view LICENSE file distributed with this source code.
  5.  */
  6. namespace Ibexa\Rest\Server\Security;
  7. use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface;
  8. use Ibexa\Core\MVC\Symfony\Security\Authentication\AuthenticatorInterface;
  9. use Ibexa\Core\MVC\Symfony\Security\UserInterface as IbexaUser;
  10. use Ibexa\Rest\Server\Exceptions\InvalidUserTypeException;
  11. use Ibexa\Rest\Server\Exceptions\UserConflictException;
  12. use Psr\Log\LoggerInterface;
  13. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  14. use Symfony\Component\HttpFoundation\Request;
  15. use Symfony\Component\HttpFoundation\Response;
  16. use Symfony\Component\HttpKernel\Event\RequestEvent;
  17. use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
  18. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  19. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  20. use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
  21. use Symfony\Component\Security\Core\Exception\TokenNotFoundException;
  22. use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
  23. use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface;
  24. use Symfony\Component\Security\Http\Logout\SessionLogoutHandler;
  25. use Symfony\Component\Security\Http\SecurityEvents;
  26. /**
  27.  * Authenticator for REST API, mainly used for session based authentication (session creation resource).
  28.  *
  29.  * Implements \Symfony\Component\Security\Http\Firewall\ListenerInterface to be able to receive the provider key
  30.  * (firewall identifier from configuration).
  31.  */
  32. class RestAuthenticator implements AuthenticatorInterface
  33. {
  34.     /**
  35.      * @var \Psr\Log\LoggerInterface
  36.      */
  37.     private $logger;
  38.     /**
  39.      * @var \Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface
  40.      */
  41.     private $authenticationManager;
  42.     /**
  43.      * @var string
  44.      */
  45.     private $providerKey;
  46.     /**
  47.      * @var \Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface
  48.      */
  49.     private $tokenStorage;
  50.     /**
  51.      * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
  52.      */
  53.     private $dispatcher;
  54.     /**
  55.      * @var \Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface
  56.      */
  57.     private $configResolver;
  58.     /**
  59.      * @var \Symfony\Component\Security\Http\Logout\LogoutHandlerInterface[]
  60.      */
  61.     private $logoutHandlers = [];
  62.     public function __construct(
  63.         TokenStorageInterface $tokenStorage,
  64.         AuthenticationManagerInterface $authenticationManager,
  65.         $providerKey,
  66.         EventDispatcherInterface $dispatcher,
  67.         ConfigResolverInterface $configResolver,
  68.         LoggerInterface $logger null
  69.     ) {
  70.         $this->tokenStorage $tokenStorage;
  71.         $this->authenticationManager $authenticationManager;
  72.         $this->providerKey $providerKey;
  73.         $this->dispatcher $dispatcher;
  74.         $this->configResolver $configResolver;
  75.         $this->logger $logger;
  76.     }
  77.     /**
  78.      * Doesn't do anything as we don't use this service with main Firewall listener.
  79.      *
  80.      * @param \Symfony\Component\HttpKernel\Event\RequestEvent $event
  81.      */
  82.     public function __invoke(RequestEvent $event)
  83.     {
  84.         return;
  85.     }
  86.     public function authenticate(Request $request)
  87.     {
  88.         // If a token already exists and username is the same as the one we request authentication for,
  89.         // then return it and mark it as coming from session.
  90.         $previousToken $this->tokenStorage->getToken();
  91.         if (
  92.             $previousToken instanceof TokenInterface
  93.             && $previousToken->getUsername() === $request->attributes->get('username')
  94.         ) {
  95.             $previousToken->setAttribute('isFromSession'true);
  96.             return $previousToken;
  97.         }
  98.         $token $this->attemptAuthentication($request);
  99.         if (!$token instanceof TokenInterface) {
  100.             if ($this->logger) {
  101.                 $this->logger->error('REST: No token could be found in SecurityContext');
  102.             }
  103.             throw new TokenNotFoundException();
  104.         }
  105.         $this->tokenStorage->setToken($token);
  106.         $this->dispatcher->dispatch(new InteractiveLoginEvent($request$token), SecurityEvents::INTERACTIVE_LOGIN);
  107.         // Re-fetch token from SecurityContext since an INTERACTIVE_LOGIN listener might have changed it
  108.         // i.e. when using multiple user providers.
  109.         // @see \Ibexa\Core\MVC\Symfony\Security\EventListener\SecurityListener::onInteractiveLogin()
  110.         $token $this->tokenStorage->getToken();
  111.         $user $token->getUser();
  112.         if (!$user instanceof IbexaUser) {
  113.             if ($this->logger) {
  114.                 $this->logger->error('REST: Authenticated user must be Ibexa\\Core\\MVC\\Symfony\\Security\\User, got ' is_string($user) ? $user get_class($user));
  115.             }
  116.             $e = new InvalidUserTypeException('Authenticated user is not an Ibexa User.');
  117.             $e->setToken($token);
  118.             throw $e;
  119.         }
  120.         // Check if newly logged in user differs from previous one.
  121.         if ($this->isUserConflict($user$previousToken)) {
  122.             $this->tokenStorage->setToken($previousToken);
  123.             throw new UserConflictException();
  124.         }
  125.         return $token;
  126.     }
  127.     /**
  128.      * @param \Symfony\Component\HttpFoundation\Request $request
  129.      *
  130.      * @return \Symfony\Component\Security\Core\Authentication\Token\TokenInterface
  131.      */
  132.     private function attemptAuthentication(Request $request)
  133.     {
  134.         return $this->authenticationManager->authenticate(
  135.             new UsernamePasswordToken(
  136.                 $request->attributes->get('username'),
  137.                 $request->attributes->get('password'),
  138.                 $this->providerKey
  139.             )
  140.         );
  141.     }
  142.     /**
  143.      * Checks if newly matched user is conflicting with previously non-anonymous logged in user, if any.
  144.      *
  145.      * @param \Ibexa\Core\MVC\Symfony\Security\UserInterface $user
  146.      * @param \Symfony\Component\Security\Core\Authentication\Token\TokenInterface $previousToken
  147.      *
  148.      * @return bool
  149.      */
  150.     private function isUserConflict(IbexaUser $userTokenInterface $previousToken null)
  151.     {
  152.         if ($previousToken === null || !$previousToken instanceof UsernamePasswordToken) {
  153.             return false;
  154.         }
  155.         $previousUser $previousToken->getUser();
  156.         if (!$previousUser instanceof IbexaUser) {
  157.             return false;
  158.         }
  159.         $wasAnonymous $previousUser->getAPIUser()->getUserId() == $this->configResolver->getParameter('anonymous_user_id');
  160.         // TODO: isEqualTo is not on the interface
  161.         return !$wasAnonymous && !$user->isEqualTo($previousUser);
  162.     }
  163.     public function addLogoutHandler(LogoutHandlerInterface $handler)
  164.     {
  165.         $this->logoutHandlers[] = $handler;
  166.     }
  167.     public function logout(Request $request)
  168.     {
  169.         $response = new Response();
  170.         // Manually clear the session through session storage.
  171.         // Session::invalidate() is not called on purpose, to avoid unwanted session migration that would imply
  172.         // generation of a new session id.
  173.         // REST logout must indeed clear the session cookie.
  174.         // See \Ibexa\Rest\Server\Security\RestLogoutHandler
  175.         $request->getSession()->clear();
  176.         $token $this->tokenStorage->getToken();
  177.         foreach ($this->logoutHandlers as $handler) {
  178.             // Explicitly ignore SessionLogoutHandler as we do session invalidation manually here,
  179.             // through the session storage, to avoid unwanted session migration.
  180.             if ($handler instanceof SessionLogoutHandler) {
  181.                 continue;
  182.             }
  183.             $handler->logout($request$response$token);
  184.         }
  185.         return $response;
  186.     }
  187. }
  188. class_alias(RestAuthenticator::class, 'EzSystems\EzPlatformRest\Server\Security\RestAuthenticator');