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

  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 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.     public function getToken(string $tokenId): CsrfToken
  60.     {
  61.         $namespacedId $this->getNamespace().$tokenId;
  62.         if ($this->storage->hasToken($namespacedId)) {
  63.             $value $this->storage->getToken($namespacedId);
  64.         } else {
  65.             $value $this->generator->generateToken();
  66.             $this->storage->setToken($namespacedId$value);
  67.         }
  68.         return new CsrfToken($tokenId$this->randomize($value));
  69.     }
  70.     public function refreshToken(string $tokenId): CsrfToken
  71.     {
  72.         $namespacedId $this->getNamespace().$tokenId;
  73.         $value $this->generator->generateToken();
  74.         $this->storage->setToken($namespacedId$value);
  75.         return new CsrfToken($tokenId$this->randomize($value));
  76.     }
  77.     public function removeToken(string $tokenId): ?string
  78.     {
  79.         return $this->storage->removeToken($this->getNamespace().$tokenId);
  80.     }
  81.     public function isTokenValid(CsrfToken $token): bool
  82.     {
  83.         $namespacedId $this->getNamespace().$token->getId();
  84.         if (!$this->storage->hasToken($namespacedId)) {
  85.             return false;
  86.         }
  87.         return hash_equals($this->storage->getToken($namespacedId), $this->derandomize($token->getValue()));
  88.     }
  89.     private function getNamespace(): string
  90.     {
  91.         return \is_callable($ns $this->namespace) ? $ns() : $ns;
  92.     }
  93.     private function randomize(string $value): string
  94.     {
  95.         $key random_bytes(32);
  96.         $value $this->xor($value$key);
  97.         return sprintf('%s.%s.%s'substr(md5($key), 0+ (\ord($key[0]) % 32)), rtrim(strtr(base64_encode($key), '+/''-_'), '='), rtrim(strtr(base64_encode($value), '+/''-_'), '='));
  98.     }
  99.     private function derandomize(string $value): string
  100.     {
  101.         $parts explode('.'$value);
  102.         if (!== \count($parts)) {
  103.             return $value;
  104.         }
  105.         $key base64_decode(strtr($parts[1], '-_''+/'));
  106.         if ('' === $key || false === $key) {
  107.             return $value;
  108.         }
  109.         $value base64_decode(strtr($parts[2], '-_''+/'));
  110.         return $this->xor($value$key);
  111.     }
  112.     private function xor(string $valuestring $key): string
  113.     {
  114.         if (\strlen($value) > \strlen($key)) {
  115.             $key str_repeat($keyceil(\strlen($value) / \strlen($key)));
  116.         }
  117.         return $value $key;
  118.     }
  119. }