vendor/symfony/security-http/RememberMe/PersistentTokenBasedRememberMeServices.php line 24

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 Symfony\Component\HttpFoundation\Cookie;
  12. use Symfony\Component\HttpFoundation\Request;
  13. use Symfony\Component\HttpFoundation\Response;
  14. use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken;
  15. use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentTokenInterface;
  16. use Symfony\Component\Security\Core\Authentication\RememberMe\TokenProviderInterface;
  17. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  18. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  19. use Symfony\Component\Security\Core\Exception\CookieTheftException;
  20. trigger_deprecation('symfony/security-http', '5.4', 'The "%s" class is deprecated, use "%s" instead.', PersistentTokenBasedRememberMeServices::class, PersistentRememberMeHandler::class);
  21. /**
  22. * Concrete implementation of the RememberMeServicesInterface which needs
  23. * an implementation of TokenProviderInterface for providing remember-me
  24. * capabilities.
  25. *
  26. * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  27. *
  28. * @deprecated since Symfony 5.4, use {@see PersistentRememberMeHandler} instead
  29. */
  30. class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices
  31. {
  32. private const HASHED_TOKEN_PREFIX = 'sha256_';
  33. /** @var TokenProviderInterface */
  34. private $tokenProvider;
  35. public function setTokenProvider(TokenProviderInterface $tokenProvider)
  36. {
  37. $this->tokenProvider = $tokenProvider;
  38. }
  39. /**
  40. * {@inheritdoc}
  41. */
  42. protected function cancelCookie(Request $request)
  43. {
  44. // Delete cookie on the client
  45. parent::cancelCookie($request);
  46. // Delete cookie from the tokenProvider
  47. if (null !== ($cookie = $request->cookies->get($this->options['name']))
  48. && 2 === \count($parts = $this->decodeCookie($cookie))
  49. ) {
  50. [$series] = $parts;
  51. $this->tokenProvider->deleteTokenBySeries($series);
  52. }
  53. }
  54. /**
  55. * {@inheritdoc}
  56. */
  57. protected function processAutoLoginCookie(array $cookieParts, Request $request)
  58. {
  59. if (2 !== \count($cookieParts)) {
  60. throw new AuthenticationException('The cookie is invalid.');
  61. }
  62. [$series, $tokenValue] = $cookieParts;
  63. $persistentToken = $this->tokenProvider->loadTokenBySeries($series);
  64. if (!$this->isTokenValueValid($persistentToken, $tokenValue)) {
  65. throw new CookieTheftException('This token was already used. The account is possibly compromised.');
  66. }
  67. if ($persistentToken->getLastUsed()->getTimestamp() + $this->options['lifetime'] < time()) {
  68. throw new AuthenticationException('The cookie has expired.');
  69. }
  70. $tokenValue = base64_encode(random_bytes(64));
  71. $this->tokenProvider->updateToken($series, $this->generateHash($tokenValue), new \DateTime());
  72. $request->attributes->set(self::COOKIE_ATTR_NAME,
  73. new Cookie(
  74. $this->options['name'],
  75. $this->encodeCookie([$series, $tokenValue]),
  76. time() + $this->options['lifetime'],
  77. $this->options['path'],
  78. $this->options['domain'],
  79. $this->options['secure'] ?? $request->isSecure(),
  80. $this->options['httponly'],
  81. false,
  82. $this->options['samesite']
  83. )
  84. );
  85. $userProvider = $this->getUserProvider($persistentToken->getClass());
  86. // @deprecated since Symfony 5.3, change to $persistentToken->getUserIdentifier() in 6.0
  87. if (method_exists($persistentToken, 'getUserIdentifier')) {
  88. $userIdentifier = $persistentToken->getUserIdentifier();
  89. } else {
  90. trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "getUserIdentifier()" in persistent token "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', get_debug_type($persistentToken));
  91. $userIdentifier = $persistentToken->getUsername();
  92. }
  93. // @deprecated since Symfony 5.3, change to $userProvider->loadUserByIdentifier() in 6.0
  94. if (method_exists($userProvider, 'loadUserByIdentifier')) {
  95. return $userProvider->loadUserByIdentifier($userIdentifier);
  96. } else {
  97. trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', get_debug_type($userProvider));
  98. return $userProvider->loadUserByUsername($userIdentifier);
  99. }
  100. }
  101. /**
  102. * {@inheritdoc}
  103. */
  104. protected function onLoginSuccess(Request $request, Response $response, TokenInterface $token)
  105. {
  106. $series = base64_encode(random_bytes(64));
  107. $tokenValue = base64_encode(random_bytes(64));
  108. $this->tokenProvider->createNewToken(
  109. new PersistentToken(
  110. \get_class($user = $token->getUser()),
  111. // @deprecated since Symfony 5.3, change to $user->getUserIdentifier() in 6.0
  112. method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(),
  113. $series,
  114. $this->generateHash($tokenValue),
  115. new \DateTime()
  116. )
  117. );
  118. $response->headers->setCookie(
  119. new Cookie(
  120. $this->options['name'],
  121. $this->encodeCookie([$series, $tokenValue]),
  122. time() + $this->options['lifetime'],
  123. $this->options['path'],
  124. $this->options['domain'],
  125. $this->options['secure'] ?? $request->isSecure(),
  126. $this->options['httponly'],
  127. false,
  128. $this->options['samesite']
  129. )
  130. );
  131. }
  132. private function generateHash(string $tokenValue): string
  133. {
  134. return self::HASHED_TOKEN_PREFIX.hash_hmac('sha256', $tokenValue, $this->getSecret());
  135. }
  136. private function isTokenValueValid(PersistentTokenInterface $persistentToken, string $tokenValue): bool
  137. {
  138. if (0 === strpos($persistentToken->getTokenValue(), self::HASHED_TOKEN_PREFIX)) {
  139. return hash_equals($persistentToken->getTokenValue(), $this->generateHash($tokenValue));
  140. }
  141. return hash_equals($persistentToken->getTokenValue(), $tokenValue);
  142. }
  143. }