vendor/symfony/security-http/RememberMe/PersistentRememberMeHandler.php line 38

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Security\Http\RememberMe;
  11. use Psr\Log\LoggerInterface;
  12. use Symfony\Component\HttpFoundation\RequestStack;
  13. use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken;
  14. use Symfony\Component\Security\Core\Authentication\RememberMe\TokenProviderInterface;
  15. use Symfony\Component\Security\Core\Authentication\RememberMe\TokenVerifierInterface;
  16. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  17. use Symfony\Component\Security\Core\Exception\CookieTheftException;
  18. use Symfony\Component\Security\Core\User\UserInterface;
  19. use Symfony\Component\Security\Core\User\UserProviderInterface;
  20. /**
  21.  * Implements remember-me tokens using a {@see TokenProviderInterface}.
  22.  *
  23.  * This requires storing remember-me tokens in a database. This allows
  24.  * more control over the invalidation of remember-me tokens. See
  25.  * {@see SignatureRememberMeHandler} if you don't want to use a database.
  26.  *
  27.  * @author Wouter de Jong <wouter@wouterj.nl>
  28.  */
  29. final class PersistentRememberMeHandler extends AbstractRememberMeHandler
  30. {
  31.     private $tokenProvider;
  32.     private $tokenVerifier;
  33.     public function __construct(TokenProviderInterface $tokenProvider, string $secret, UserProviderInterface $userProvider, RequestStack $requestStack, array $options, LoggerInterface $logger = null, TokenVerifierInterface $tokenVerifier = null)
  34.     {
  35.         parent::__construct($userProvider, $requestStack, $options, $logger);
  36.         if (!$tokenVerifier && $tokenProvider instanceof TokenVerifierInterface) {
  37.             $tokenVerifier = $tokenProvider;
  38.         }
  39.         $this->tokenProvider = $tokenProvider;
  40.         $this->tokenVerifier = $tokenVerifier;
  41.     }
  42.     /**
  43.      * {@inheritdoc}
  44.      */
  45.     public function createRememberMeCookie(UserInterface $user): void
  46.     {
  47.         $series = random_bytes(66);
  48.         $tokenValue = strtr(base64_encode(substr($series, 33)), '+/=', '-_~');
  49.         $series = strtr(base64_encode(substr($series, 0, 33)), '+/=', '-_~');
  50.         $token = new PersistentToken(\get_class($user), method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(), $series, $tokenValue, new \DateTime());
  51.         $this->tokenProvider->createNewToken($token);
  52.         $this->createCookie(RememberMeDetails::fromPersistentToken($token, time() + $this->options['lifetime']));
  53.     }
  54.     public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails): UserInterface
  55.     {
  56.         if (!str_contains($rememberMeDetails->getValue(), ':')) {
  57.             throw new AuthenticationException('The cookie is incorrectly formatted.');
  58.         }
  59.         [$series, $tokenValue] = explode(':', $rememberMeDetails->getValue());
  60.         $persistentToken = $this->tokenProvider->loadTokenBySeries($series);
  61.         if ($this->tokenVerifier) {
  62.             $isTokenValid = $this->tokenVerifier->verifyToken($persistentToken, $tokenValue);
  63.         } else {
  64.             $isTokenValid = hash_equals($persistentToken->getTokenValue(), $tokenValue);
  65.         }
  66.         if (!$isTokenValid) {
  67.             throw new CookieTheftException('This token was already used. The account is possibly compromised.');
  68.         }
  69.         if ($persistentToken->getLastUsed()->getTimestamp() + $this->options['lifetime'] < time()) {
  70.             throw new AuthenticationException('The cookie has expired.');
  71.         }
  72.         return parent::consumeRememberMeCookie($rememberMeDetails->withValue($persistentToken->getLastUsed()->getTimestamp().':'.$rememberMeDetails->getValue().':'.$persistentToken->getClass()));
  73.     }
  74.     public function processRememberMe(RememberMeDetails $rememberMeDetails, UserInterface $user): void
  75.     {
  76.         [$lastUsed, $series, $tokenValue, $class] = explode(':', $rememberMeDetails->getValue(), 4);
  77.         $persistentToken = new PersistentToken($class, $rememberMeDetails->getUserIdentifier(), $series, $tokenValue, new \DateTime('@'.$lastUsed));
  78.         // if a token was regenerated less than a minute ago, there is no need to regenerate it
  79.         // if multiple concurrent requests reauthenticate a user we do not want to update the token several times
  80.         if ($persistentToken->getLastUsed()->getTimestamp() + 60 >= time()) {
  81.             return;
  82.         }
  83.         $tokenValue = strtr(base64_encode(random_bytes(33)), '+/=', '-_~');
  84.         $tokenLastUsed = new \DateTime();
  85.         if ($this->tokenVerifier) {
  86.             $this->tokenVerifier->updateExistingToken($persistentToken, $tokenValue, $tokenLastUsed);
  87.         }
  88.         $this->tokenProvider->updateToken($series, $tokenValue, $tokenLastUsed);
  89.         $this->createCookie($rememberMeDetails->withValue($series.':'.$tokenValue));
  90.     }
  91.     /**
  92.      * {@inheritdoc}
  93.      */
  94.     public function clearRememberMeCookie(): void
  95.     {
  96.         parent::clearRememberMeCookie();
  97.         $cookie = $this->requestStack->getMainRequest()->cookies->get($this->options['name']);
  98.         if (null === $cookie) {
  99.             return;
  100.         }
  101.         $rememberMeDetails = RememberMeDetails::fromRawCookie($cookie);
  102.         [$series] = explode(':', $rememberMeDetails->getValue());
  103.         $this->tokenProvider->deleteTokenBySeries($series);
  104.     }
  105.     /**
  106.      * @internal
  107.      */
  108.     public function getTokenProvider(): TokenProviderInterface
  109.     {
  110.         return $this->tokenProvider;
  111.     }
  112. }