vendor/symfony/security-csrf/CsrfTokenManager.php line 52

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <[email protected]>
  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\Csrf;
  11. use Symfony\Component\HttpFoundation\RequestStack;
  12. use Symfony\Component\Security\Core\Exception\InvalidArgumentException;
  13. use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface;
  14. use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator;
  15. use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage;
  16. use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface;
  17. /**
  18.  * Default implementation of {@link CsrfTokenManagerInterface}.
  19.  *
  20.  * @author Bernhard Schussek <[email protected]>
  21.  * @author Kévin Dunglas <[email protected]>
  22.  */
  23. class CsrfTokenManager implements CsrfTokenManagerInterface
  24. {
  25.     private TokenGeneratorInterface $generator;
  26.     private TokenStorageInterface $storage;
  27.     private \Closure|string $namespace;
  28.     /**
  29.      * @param $namespace
  30.      *                   * null: generates a namespace using $_SERVER['HTTPS']
  31.      *                   * string: uses the given string
  32.      *                   * RequestStack: generates a namespace using the current main request
  33.      *                   * callable: uses the result of this callable (must return a string)
  34.      */
  35.     public function __construct(TokenGeneratorInterface $generator nullTokenStorageInterface $storage nullstring|RequestStack|callable $namespace null)
  36.     {
  37.         $this->generator $generator ?? new UriSafeTokenGenerator();
  38.         $this->storage $storage ?? new NativeSessionTokenStorage();
  39.         $superGlobalNamespaceGenerator = function () {
  40.             return !empty($_SERVER['HTTPS']) && 'off' !== strtolower($_SERVER['HTTPS']) ? 'https-' '';
  41.         };
  42.         if (null === $namespace) {
  43.             $this->namespace $superGlobalNamespaceGenerator;
  44.         } elseif ($namespace instanceof RequestStack) {
  45.             $this->namespace = function () use ($namespace$superGlobalNamespaceGenerator) {
  46.                 if ($request $namespace->getMainRequest()) {
  47.                     return $request->isSecure() ? 'https-' '';
  48.                 }
  49.                 return $superGlobalNamespaceGenerator();
  50.             };
  51.         } elseif ($namespace instanceof \Closure || \is_string($namespace)) {
  52.             $this->namespace $namespace;
  53.         } elseif (\is_callable($namespace)) {
  54.             $this->namespace $namespace(...);
  55.         } else {
  56.             throw new InvalidArgumentException(sprintf('$namespace must be a string, a callable returning a string, null or an instance of "RequestStack". "%s" given.'get_debug_type($namespace)));
  57.         }
  58.     }
  59.     /**
  60.      * {@inheritdoc}
  61.      */
  62.     public function getToken(string $tokenId): CsrfToken
  63.     {
  64.         $namespacedId $this->getNamespace().$tokenId;
  65.         if ($this->storage->hasToken($namespacedId)) {
  66.             $value $this->storage->getToken($namespacedId);
  67.         } else {
  68.             $value $this->generator->generateToken();
  69.             $this->storage->setToken($namespacedId$value);
  70.         }
  71.         return new CsrfToken($tokenId$this->randomize($value));
  72.     }
  73.     /**
  74.      * {@inheritdoc}
  75.      */
  76.     public function refreshToken(string $tokenId): CsrfToken
  77.     {
  78.         $namespacedId $this->getNamespace().$tokenId;
  79.         $value $this->generator->generateToken();
  80.         $this->storage->setToken($namespacedId$value);
  81.         return new CsrfToken($tokenId$this->randomize($value));
  82.     }
  83.     /**
  84.      * {@inheritdoc}
  85.      */
  86.     public function removeToken(string $tokenId): ?string
  87.     {
  88.         return $this->storage->removeToken($this->getNamespace().$tokenId);
  89.     }
  90.     /**
  91.      * {@inheritdoc}
  92.      */
  93.     public function isTokenValid(CsrfToken $token): bool
  94.     {
  95.         $namespacedId $this->getNamespace().$token->getId();
  96.         if (!$this->storage->hasToken($namespacedId)) {
  97.             return false;
  98.         }
  99.         return hash_equals($this->storage->getToken($namespacedId), $this->derandomize($token->getValue()));
  100.     }
  101.     private function getNamespace(): string
  102.     {
  103.         return \is_callable($ns $this->namespace) ? $ns() : $ns;
  104.     }
  105.     private function randomize(string $value): string
  106.     {
  107.         $key random_bytes(32);
  108.         $value $this->xor($value$key);
  109.         return sprintf('%s.%s.%s'substr(md5($key), 0+ (\ord($key[0]) % 32)), rtrim(strtr(base64_encode($key), '+/''-_'), '='), rtrim(strtr(base64_encode($value), '+/''-_'), '='));
  110.     }
  111.     private function derandomize(string $value): string
  112.     {
  113.         $parts explode('.'$value);
  114.         if (!== \count($parts)) {
  115.             return $value;
  116.         }
  117.         $key base64_decode(strtr($parts[1], '-_''+/'));
  118.         if ('' === $key || false === $key) {
  119.             return $value;
  120.         }
  121.         $value base64_decode(strtr($parts[2], '-_''+/'));
  122.         return $this->xor($value$key);
  123.     }
  124.     private function xor(string $valuestring $key): string
  125.     {
  126.         if (\strlen($value) > \strlen($key)) {
  127.             $key str_repeat($keyceil(\strlen($value) / \strlen($key)));
  128.         }
  129.         return $value $key;
  130.     }
  131. }