vendor/symfony/security-http/RememberMe/AbstractRememberMeServices.php line 29

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\Cookie;
  13. use Symfony\Component\HttpFoundation\Request;
  14. use Symfony\Component\HttpFoundation\Response;
  15. use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken;
  16. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  17. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  18. use Symfony\Component\Security\Core\Exception\CookieTheftException;
  19. use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
  20. use Symfony\Component\Security\Core\Exception\UserNotFoundException;
  21. use Symfony\Component\Security\Core\User\UserInterface;
  22. use Symfony\Component\Security\Core\User\UserProviderInterface;
  23. use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface;
  24. use Symfony\Component\Security\Http\ParameterBagUtils;
  25. trigger_deprecation('symfony/security-http''5.4''The "%s" class is deprecated, use "%s" instead.'AbstractRememberMeServices::class, AbstractRememberMeHandler::class);
  26. /**
  27.  * Base class implementing the RememberMeServicesInterface.
  28.  *
  29.  * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  30.  *
  31.  * @deprecated since Symfony 5.4, use {@see AbstractRememberMeHandler} instead
  32.  */
  33. abstract class AbstractRememberMeServices implements RememberMeServicesInterfaceLogoutHandlerInterface
  34. {
  35.     public const COOKIE_DELIMITER ':';
  36.     protected $logger;
  37.     protected $options = [
  38.         'secure' => false,
  39.         'httponly' => true,
  40.         'samesite' => null,
  41.         'path' => null,
  42.         'domain' => null,
  43.     ];
  44.     private $firewallName;
  45.     private $secret;
  46.     private $userProviders;
  47.     /**
  48.      * @throws \InvalidArgumentException
  49.      */
  50.     public function __construct(iterable $userProvidersstring $secretstring $firewallName, array $options = [], ?LoggerInterface $logger null)
  51.     {
  52.         if (empty($secret)) {
  53.             throw new \InvalidArgumentException('$secret must not be empty.');
  54.         }
  55.         if ('' === $firewallName) {
  56.             throw new \InvalidArgumentException('$firewallName must not be empty.');
  57.         }
  58.         if (!\is_array($userProviders) && !$userProviders instanceof \Countable) {
  59.             $userProviders iterator_to_array($userProvidersfalse);
  60.         }
  61.         if (=== \count($userProviders)) {
  62.             throw new \InvalidArgumentException('You must provide at least one user provider.');
  63.         }
  64.         $this->userProviders $userProviders;
  65.         $this->secret $secret;
  66.         $this->firewallName $firewallName;
  67.         $this->options array_merge($this->options$options);
  68.         $this->logger $logger;
  69.     }
  70.     /**
  71.      * Returns the parameter that is used for checking whether remember-me
  72.      * services have been requested.
  73.      *
  74.      * @return string
  75.      */
  76.     public function getRememberMeParameter()
  77.     {
  78.         return $this->options['remember_me_parameter'];
  79.     }
  80.     /**
  81.      * @return string
  82.      */
  83.     public function getSecret()
  84.     {
  85.         return $this->secret;
  86.     }
  87.     /**
  88.      * Implementation of RememberMeServicesInterface. Detects whether a remember-me
  89.      * cookie was set, decodes it, and hands it to subclasses for further processing.
  90.      *
  91.      * @throws CookieTheftException
  92.      * @throws \RuntimeException
  93.      */
  94.     final public function autoLogin(Request $request): ?TokenInterface
  95.     {
  96.         if (($cookie $request->attributes->get(self::COOKIE_ATTR_NAME)) && null === $cookie->getValue()) {
  97.             return null;
  98.         }
  99.         if (null === $cookie $request->cookies->get($this->options['name'])) {
  100.             return null;
  101.         }
  102.         if (null !== $this->logger) {
  103.             $this->logger->debug('Remember-me cookie detected.');
  104.         }
  105.         $cookieParts $this->decodeCookie($cookie);
  106.         try {
  107.             $user $this->processAutoLoginCookie($cookieParts$request);
  108.             if (!$user instanceof UserInterface) {
  109.                 throw new \RuntimeException('processAutoLoginCookie() must return a UserInterface implementation.');
  110.             }
  111.             if (null !== $this->logger) {
  112.                 $this->logger->info('Remember-me cookie accepted.');
  113.             }
  114.             return new RememberMeToken($user$this->firewallName$this->secret);
  115.         } catch (CookieTheftException $e) {
  116.             $this->loginFail($request$e);
  117.             throw $e;
  118.         } catch (UserNotFoundException $e) {
  119.             if (null !== $this->logger) {
  120.                 $this->logger->info('User for remember-me cookie not found.', ['exception' => $e]);
  121.             }
  122.             $this->loginFail($request$e);
  123.         } catch (UnsupportedUserException $e) {
  124.             if (null !== $this->logger) {
  125.                 $this->logger->warning('User class for remember-me cookie not supported.', ['exception' => $e]);
  126.             }
  127.             $this->loginFail($request$e);
  128.         } catch (AuthenticationException $e) {
  129.             if (null !== $this->logger) {
  130.                 $this->logger->debug('Remember-Me authentication failed.', ['exception' => $e]);
  131.             }
  132.             $this->loginFail($request$e);
  133.         } catch (\Exception $e) {
  134.             $this->loginFail($request$e);
  135.             throw $e;
  136.         }
  137.         return null;
  138.     }
  139.     /**
  140.      * Implementation for LogoutHandlerInterface. Deletes the cookie.
  141.      */
  142.     public function logout(Request $requestResponse $responseTokenInterface $token)
  143.     {
  144.         $this->cancelCookie($request);
  145.     }
  146.     /**
  147.      * Implementation for RememberMeServicesInterface. Deletes the cookie when
  148.      * an attempted authentication fails.
  149.      */
  150.     final public function loginFail(Request $request, ?\Exception $exception null)
  151.     {
  152.         $this->cancelCookie($request);
  153.         $this->onLoginFail($request$exception);
  154.     }
  155.     /**
  156.      * Implementation for RememberMeServicesInterface. This is called when an
  157.      * authentication is successful.
  158.      */
  159.     final public function loginSuccess(Request $requestResponse $responseTokenInterface $token)
  160.     {
  161.         // Make sure any old remember-me cookies are cancelled
  162.         $this->cancelCookie($request);
  163.         if (!$token->getUser() instanceof UserInterface) {
  164.             if (null !== $this->logger) {
  165.                 $this->logger->debug('Remember-me ignores token since it does not contain a UserInterface implementation.');
  166.             }
  167.             return;
  168.         }
  169.         if (!$this->isRememberMeRequested($request)) {
  170.             if (null !== $this->logger) {
  171.                 $this->logger->debug('Remember-me was not requested.');
  172.             }
  173.             return;
  174.         }
  175.         if (null !== $this->logger) {
  176.             $this->logger->debug('Remember-me was requested; setting cookie.');
  177.         }
  178.         // Remove attribute from request that sets a NULL cookie.
  179.         // It was set by $this->cancelCookie()
  180.         // (cancelCookie does other things too for some RememberMeServices
  181.         // so we should still call it at the start of this method)
  182.         $request->attributes->remove(self::COOKIE_ATTR_NAME);
  183.         $this->onLoginSuccess($request$response$token);
  184.     }
  185.     /**
  186.      * Subclasses should validate the cookie and do any additional processing
  187.      * that is required. This is called from autoLogin().
  188.      *
  189.      * @return UserInterface
  190.      */
  191.     abstract protected function processAutoLoginCookie(array $cookiePartsRequest $request);
  192.     protected function onLoginFail(Request $request, ?\Exception $exception null)
  193.     {
  194.     }
  195.     /**
  196.      * This is called after a user has been logged in successfully, and has
  197.      * requested remember-me capabilities. The implementation usually sets a
  198.      * cookie and possibly stores a persistent record of it.
  199.      */
  200.     abstract protected function onLoginSuccess(Request $requestResponse $responseTokenInterface $token);
  201.     final protected function getUserProvider(string $class): UserProviderInterface
  202.     {
  203.         foreach ($this->userProviders as $provider) {
  204.             if ($provider->supportsClass($class)) {
  205.                 return $provider;
  206.             }
  207.         }
  208.         throw new UnsupportedUserException(sprintf('There is no user provider for user "%s". Shouldn\'t the "supportsClass()" method of your user provider return true for this classname?'$class));
  209.     }
  210.     /**
  211.      * Decodes the raw cookie value.
  212.      *
  213.      * @return array
  214.      */
  215.     protected function decodeCookie(string $rawCookie)
  216.     {
  217.         return explode(self::COOKIE_DELIMITERbase64_decode($rawCookie));
  218.     }
  219.     /**
  220.      * Encodes the cookie parts.
  221.      *
  222.      * @return string
  223.      *
  224.      * @throws \InvalidArgumentException When $cookieParts contain the cookie delimiter. Extending class should either remove or escape it.
  225.      */
  226.     protected function encodeCookie(array $cookieParts)
  227.     {
  228.         foreach ($cookieParts as $cookiePart) {
  229.             if (str_contains($cookiePartself::COOKIE_DELIMITER)) {
  230.                 throw new \InvalidArgumentException(sprintf('$cookieParts should not contain the cookie delimiter "%s".'self::COOKIE_DELIMITER));
  231.             }
  232.         }
  233.         return base64_encode(implode(self::COOKIE_DELIMITER$cookieParts));
  234.     }
  235.     /**
  236.      * Deletes the remember-me cookie.
  237.      */
  238.     protected function cancelCookie(Request $request)
  239.     {
  240.         if (null !== $this->logger) {
  241.             $this->logger->debug('Clearing remember-me cookie.', ['name' => $this->options['name']]);
  242.         }
  243.         $request->attributes->set(self::COOKIE_ATTR_NAME, new Cookie($this->options['name'], null1$this->options['path'], $this->options['domain'], $this->options['secure'] ?? $request->isSecure(), $this->options['httponly'], false$this->options['samesite']));
  244.     }
  245.     /**
  246.      * Checks whether remember-me capabilities were requested.
  247.      *
  248.      * @return bool
  249.      */
  250.     protected function isRememberMeRequested(Request $request)
  251.     {
  252.         if (true === $this->options['always_remember_me']) {
  253.             return true;
  254.         }
  255.         $parameter ParameterBagUtils::getRequestParameterValue($request$this->options['remember_me_parameter']);
  256.         if (null === $parameter && null !== $this->logger) {
  257.             $this->logger->debug('Did not send remember-me cookie.', ['parameter' => $this->options['remember_me_parameter']]);
  258.         }
  259.         return 'true' === $parameter || 'on' === $parameter || '1' === $parameter || 'yes' === $parameter || true === $parameter;
  260.     }
  261. }