vendor/contao/core-bundle/src/Csrf/ContaoCsrfTokenManager.php line 117

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4.  * This file is part of Contao.
  5.  *
  6.  * (c) Leo Feyer
  7.  *
  8.  * @license LGPL-3.0-or-later
  9.  */
  10. namespace Contao\CoreBundle\Csrf;
  11. use Symfony\Component\HttpFoundation\Request;
  12. use Symfony\Component\HttpFoundation\RequestStack;
  13. use Symfony\Component\HttpFoundation\Session\Session;
  14. use Symfony\Component\Security\Csrf\CsrfToken;
  15. use Symfony\Component\Security\Csrf\CsrfTokenManager;
  16. use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface;
  17. use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface;
  18. use Symfony\Contracts\Service\ResetInterface;
  19. class ContaoCsrfTokenManager extends CsrfTokenManager implements ResetInterface
  20. {
  21.     private RequestStack $requestStack;
  22.     private string $csrfCookiePrefix;
  23.     private ?string $defaultTokenName;
  24.     /**
  25.      * @var array<int, string>
  26.      */
  27.     private array $usedTokenValues = [];
  28.     /**
  29.      * @var array<string, CsrfToken>
  30.      */
  31.     private array $tokenCache = [];
  32.     /**
  33.      * @param string|RequestStack|callable|null $namespace
  34.      */
  35.     public function __construct(RequestStack $requestStackstring $csrfCookiePrefix, ?TokenGeneratorInterface $generator null, ?TokenStorageInterface $storage null$namespace null, ?string $defaultTokenName null)
  36.     {
  37.         $this->requestStack $requestStack;
  38.         $this->csrfCookiePrefix $csrfCookiePrefix;
  39.         $this->defaultTokenName $defaultTokenName;
  40.         parent::__construct($generator$storage$namespace);
  41.     }
  42.     /**
  43.      * @return array<int, string>
  44.      */
  45.     public function getUsedTokenValues(): array
  46.     {
  47.         return $this->usedTokenValues;
  48.     }
  49.     public function getToken($tokenId): CsrfToken
  50.     {
  51.         $this->tokenCache[$tokenId] ??= parent::getToken($tokenId);
  52.         $this->usedTokenValues[] = $this->tokenCache[$tokenId]->getValue();
  53.         return $this->tokenCache[$tokenId];
  54.     }
  55.     public function refreshToken($tokenId): CsrfToken
  56.     {
  57.         $this->tokenCache[$tokenId] = parent::refreshToken($tokenId);
  58.         $this->usedTokenValues[] = $this->tokenCache[$tokenId]->getValue();
  59.         return $this->tokenCache[$tokenId];
  60.     }
  61.     public function removeToken(string $tokenId): ?string
  62.     {
  63.         unset($this->tokenCache[$tokenId]);
  64.         return parent::removeToken($tokenId);
  65.     }
  66.     public function isTokenValid(CsrfToken $token): bool
  67.     {
  68.         if (
  69.             ($request $this->requestStack->getMainRequest())
  70.             && 'POST' === $request->getRealMethod()
  71.             && $this->canSkipTokenValidation($request$this->csrfCookiePrefix.$token->getId())
  72.         ) {
  73.             return true;
  74.         }
  75.         return parent::isTokenValid($token);
  76.     }
  77.     /**
  78.      * Skip the CSRF token validation if the request has no cookies, no
  79.      * authenticated user and the session has not been started.
  80.      */
  81.     public function canSkipTokenValidation(Request $requeststring $tokenCookieName): bool
  82.     {
  83.         return
  84.             !$request->getUserInfo()
  85.             && (
  86.                 === $request->cookies->count()
  87.                 || [$tokenCookieName] === $request->cookies->keys()
  88.             )
  89.             && $this->isSessionEmpty($request);
  90.     }
  91.     public function getDefaultTokenValue(): string
  92.     {
  93.         if (null === $this->defaultTokenName) {
  94.             throw new \RuntimeException('The Contao CSRF token manager was not initialized with a default token name.');
  95.         }
  96.         return $this->getToken($this->defaultTokenName)->getValue();
  97.     }
  98.     public function reset(): void
  99.     {
  100.         $this->usedTokenValues = [];
  101.         $this->tokenCache = [];
  102.     }
  103.     private function isSessionEmpty(Request $request): bool
  104.     {
  105.         if (!$request->hasSession()) {
  106.             return true;
  107.         }
  108.         $session $request->getSession();
  109.         if (!$session->isStarted()) {
  110.             return true;
  111.         }
  112.         if ($session instanceof Session) {
  113.             // Marked @internal but no other way to check all attribute bags
  114.             return $session->isEmpty();
  115.         }
  116.         return [] === $session->all();
  117.     }
  118. }