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

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\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 <bschussek@gmail.com>
  21.  * @author Kévin Dunglas <dunglas@gmail.com>
  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 null, ?TokenStorageInterface $storage nullstring|RequestStack|callable|null $namespace null)
  36.     {
  37.         $this->generator $generator ?? new UriSafeTokenGenerator();
  38.         $this->storage $storage ?? new NativeSessionTokenStorage();
  39.         $superGlobalNamespaceGenerator = fn () => !empty($_SERVER['HTTPS']) && 'off' !== strtolower($_SERVER['HTTPS']) ? 'https-' '';
  40.         if (null === $namespace) {
  41.             $this->namespace $superGlobalNamespaceGenerator;
  42.         } elseif ($namespace instanceof RequestStack) {
  43.             $this->namespace = function () use ($namespace$superGlobalNamespaceGenerator) {
  44.                 if ($request $namespace->getMainRequest()) {
  45.                     return $request->isSecure() ? 'https-' '';
  46.                 }
  47.                 return $superGlobalNamespaceGenerator();
  48.             };
  49.         } elseif ($namespace instanceof \Closure || \is_string($namespace)) {
  50.             $this->namespace $namespace;
  51.         } elseif (\is_callable($namespace)) {
  52.             $this->namespace $namespace(...);
  53.         } else {
  54.             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)));
  55.         }
  56.     }
  57.     public function getToken(string $tokenId): CsrfToken
  58.     {
  59.         $namespacedId $this->getNamespace().$tokenId;
  60.         if ($this->storage->hasToken($namespacedId)) {
  61.             $value $this->storage->getToken($namespacedId);
  62.         } else {
  63.             $value $this->generator->generateToken();
  64.             $this->storage->setToken($namespacedId$value);
  65.         }
  66.         return new CsrfToken($tokenId$this->randomize($value));
  67.     }
  68.     public function refreshToken(string $tokenId): CsrfToken
  69.     {
  70.         $namespacedId $this->getNamespace().$tokenId;
  71.         $value $this->generator->generateToken();
  72.         $this->storage->setToken($namespacedId$value);
  73.         return new CsrfToken($tokenId$this->randomize($value));
  74.     }
  75.     public function removeToken(string $tokenId): ?string
  76.     {
  77.         return $this->storage->removeToken($this->getNamespace().$tokenId);
  78.     }
  79.     public function isTokenValid(CsrfToken $token): bool
  80.     {
  81.         $namespacedId $this->getNamespace().$token->getId();
  82.         if (!$this->storage->hasToken($namespacedId)) {
  83.             return false;
  84.         }
  85.         return hash_equals($this->storage->getToken($namespacedId), $this->derandomize($token->getValue()));
  86.     }
  87.     private function getNamespace(): string
  88.     {
  89.         return \is_callable($ns $this->namespace) ? $ns() : $ns;
  90.     }
  91.     private function randomize(string $value): string
  92.     {
  93.         $key random_bytes(32);
  94.         $value $this->xor($value$key);
  95.         return sprintf('%s.%s.%s'substr(hash('xxh128'$key), 0+ (\ord($key[0]) % 32)), rtrim(strtr(base64_encode($key), '+/''-_'), '='), rtrim(strtr(base64_encode($value), '+/''-_'), '='));
  96.     }
  97.     private function derandomize(string $value): string
  98.     {
  99.         $parts explode('.'$value);
  100.         if (!== \count($parts)) {
  101.             return $value;
  102.         }
  103.         $key base64_decode(strtr($parts[1], '-_''+/'));
  104.         if ('' === $key || false === $key) {
  105.             return $value;
  106.         }
  107.         $value base64_decode(strtr($parts[2], '-_''+/'));
  108.         return $this->xor($value$key);
  109.     }
  110.     private function xor(string $valuestring $key): string
  111.     {
  112.         if (\strlen($value) > \strlen($key)) {
  113.             $key str_repeat($keyceil(\strlen($value) / \strlen($key)));
  114.         }
  115.         return $value $key;
  116.     }
  117. }