vendor/contao/core-bundle/src/EventListener/RequestTokenListener.php line 68

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\EventListener;
  11. use Contao\Config;
  12. use Contao\CoreBundle\Csrf\ContaoCsrfTokenManager;
  13. use Contao\CoreBundle\Exception\InvalidRequestTokenException;
  14. use Contao\CoreBundle\Framework\ContaoFramework;
  15. use Contao\CoreBundle\Routing\ScopeMatcher;
  16. use Symfony\Component\HttpFoundation\Request;
  17. use Symfony\Component\HttpKernel\Event\RequestEvent;
  18. use Symfony\Component\Security\Csrf\CsrfToken;
  19. /**
  20.  * Validates the request token if the request is a Contao request.
  21.  *
  22.  * @internal
  23.  */
  24. class RequestTokenListener
  25. {
  26.     /**
  27.      * @var ContaoFramework
  28.      */
  29.     private $framework;
  30.     /**
  31.      * @var ScopeMatcher
  32.      */
  33.     private $scopeMatcher;
  34.     /**
  35.      * @var ContaoCsrfTokenManager
  36.      */
  37.     private $csrfTokenManager;
  38.     /**
  39.      * @var string
  40.      */
  41.     private $csrfTokenName;
  42.     /**
  43.      * @var string
  44.      */
  45.     private $csrfCookiePrefix;
  46.     public function __construct(ContaoFramework $frameworkScopeMatcher $scopeMatcherContaoCsrfTokenManager $csrfTokenManagerstring $csrfTokenNamestring $csrfCookiePrefix 'csrf_')
  47.     {
  48.         $this->framework $framework;
  49.         $this->scopeMatcher $scopeMatcher;
  50.         $this->csrfTokenManager $csrfTokenManager;
  51.         $this->csrfTokenName $csrfTokenName;
  52.         $this->csrfCookiePrefix $csrfCookiePrefix;
  53.     }
  54.     /**
  55.      * @throws InvalidRequestTokenException
  56.      */
  57.     public function __invoke(RequestEvent $event): void
  58.     {
  59.         // Don't do anything if it's not the master request
  60.         if (!$event->isMasterRequest()) {
  61.             return;
  62.         }
  63.         $request $event->getRequest();
  64.         // Only check the request token if a) the request is a POST request, b)
  65.         // the request is not an Ajax request, c) the _token_check attribute is
  66.         // not false, d) the _token_check attribute is set or the request is a
  67.         // Contao request and e) the request has cookies, an authenticated user
  68.         // or the session has been started
  69.         if (
  70.             'POST' !== $request->getRealMethod()
  71.             || $request->isXmlHttpRequest()
  72.             || false === $request->attributes->get('_token_check')
  73.             || $this->csrfTokenManager->canSkipTokenValidation($request$this->csrfCookiePrefix.$this->csrfTokenName)
  74.             || (!$request->attributes->has('_token_check') && !$this->scopeMatcher->isContaoRequest($request))
  75.         ) {
  76.             return;
  77.         }
  78.         /** @var Config $config */
  79.         $config $this->framework->getAdapter(Config::class);
  80.         if (\defined('BYPASS_TOKEN_CHECK')) {
  81.             trigger_deprecation('contao/core-bundle''4.0''Defining the BYPASS_TOKEN_CHECK constant has been deprecated and will no longer work in Contao 5.0.');
  82.             return;
  83.         }
  84.         if ($config->get('disableRefererCheck')) {
  85.             trigger_deprecation('contao/core-bundle''4.0''Using the "disableRefererCheck" setting has been deprecated and will no longer work in Contao 5.0.');
  86.             return;
  87.         }
  88.         if ($config->get('requestTokenWhitelist')) {
  89.             trigger_deprecation('contao/core-bundle''4.0''Using the "requestTokenWhitelist" setting has been deprecated and will no longer work in Contao 5.0.');
  90.             $hostname gethostbyaddr($request->getClientIp());
  91.             foreach ($config->get('requestTokenWhitelist') as $domain) {
  92.                 if ($domain === $hostname || preg_match('/\.'.preg_quote($domain'/').'$/'$hostname)) {
  93.                     return;
  94.                 }
  95.             }
  96.         }
  97.         $token = new CsrfToken($this->csrfTokenName$this->getTokenFromRequest($request));
  98.         if ($this->csrfTokenManager->isTokenValid($token)) {
  99.             return;
  100.         }
  101.         throw new InvalidRequestTokenException('Invalid CSRF token. Please reload the page and try again.');
  102.     }
  103.     private function getTokenFromRequest(Request $request): ?string
  104.     {
  105.         if ($request->request->has('REQUEST_TOKEN')) {
  106.             return $request->request->get('REQUEST_TOKEN');
  107.         }
  108.         // Look for the token inside the root level arrays as they would be in named Symfony forms
  109.         foreach ($request->request as $value) {
  110.             if (\is_array($value) && isset($value['REQUEST_TOKEN'])) {
  111.                 return $value['REQUEST_TOKEN'];
  112.             }
  113.         }
  114.         return null;
  115.     }
  116. }