vendor/shopware/storefront/Framework/Csrf/CsrfPlaceholderHandler.php line 45

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Storefront\Framework\Csrf;
  3. use Shopware\Core\Framework\Feature;
  4. use Symfony\Component\HttpFoundation\Cookie;
  5. use Symfony\Component\HttpFoundation\Request;
  6. use Symfony\Component\HttpFoundation\RequestStack;
  7. use Symfony\Component\HttpFoundation\Response;
  8. use Symfony\Component\HttpFoundation\Session\Session;
  9. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  10. use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageFactoryInterface;
  11. use Symfony\Component\HttpFoundation\StreamedResponse;
  12. use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
  13. /**
  14.  * @deprecated tag:v6.5.0 - class will be removed as the csrf system will be removed in favor for the samesite approach
  15.  */
  16. class CsrfPlaceholderHandler
  17. {
  18.     public const CSRF_PLACEHOLDER '3afd91a002e8e6a6a4a20bdf5.uGlRYkZfGR8ER5dK9UocWA-XpGh_tM2G1LtaJ2-BWpk.8Ac6ER49Syk3FvUNlzhYHWbw6jEb14LVrcMqfSvJbf_dCiEncjd1QDQj4A007700">;
  19.     private CsrfTokenManagerInterface $csrfTokenManager;
  20.     private bool $csrfEnabled;
  21.     private string $csrfMode;
  22.     private RequestStack $requestStack;
  23.     private SessionStorageFactoryInterface $sessionFactory;
  24.     /**
  25.      * @internal
  26.      */
  27.     public function __construct(CsrfTokenManagerInterface $csrfTokenManagerbool $csrfEnabledstring $csrfModeRequestStack $requestStackSessionStorageFactoryInterface $sessionFactory)
  28.     {
  29.         $this->csrfTokenManager $csrfTokenManager;
  30.         $this->csrfEnabled $csrfEnabled;
  31.         $this->csrfMode $csrfMode;
  32.         $this->requestStack $requestStack;
  33.         $this->sessionFactory $sessionFactory;
  34.     }
  35.     public function replaceCsrfToken(Response $responseRequest $request): Response
  36.     {
  37.         Feature::triggerDeprecationOrThrow(
  38.             'v6.5.0.0',
  39.             Feature::deprecatedClassMessage(__CLASS__'v6.5.0.0')
  40.         );
  41.         if ($response instanceof StreamedResponse) {
  42.             return $response;
  43.         }
  44.         if (!$this->csrfEnabled || $this->csrfMode !== CsrfModes::MODE_TWIG) {
  45.             return $response;
  46.         }
  47.         if ($response->getStatusCode() !== Response::HTTP_OK && $response->getStatusCode() !== Response::HTTP_NOT_FOUND) {
  48.             return $response;
  49.         }
  50.         $content $response->getContent();
  51.         if ($content === false) {
  52.             return $response;
  53.         }
  54.         // Early return if the placeholder is not present in body to save cpu cycles with the regex
  55.         if (!\str_contains($contentself::CSRF_PLACEHOLDER)) {
  56.             return $response;
  57.         }
  58.         // Get session from session provider if not provided in session. This happens when the page is fully cached
  59.         $session $request->hasSession() ? $request->getSession() : $this->createSession($request);
  60.         $request->setSession($session);
  61.         if ($session !== null) {
  62.             // StorefrontSubscriber did not run and set the session name. This can happen when the page is fully cached in the http cache
  63.             if (!$session->isStarted()) {
  64.                 $session->setName('session-');
  65.             }
  66.             // The SessionTokenStorage gets the session from the RequestStack. This is at this moment empty as the Symfony request cycle did run already
  67.             $this->requestStack->push($request);
  68.         }
  69.         $processedIntents = [];
  70.         // https://regex101.com/r/fefx3V/1
  71.         $content preg_replace_callback(
  72.             '/' self::CSRF_PLACEHOLDER '(?<intent>[^#]*)#/',
  73.             function ($matches) use ($response$request, &$processedIntents) {
  74.                 $intent $matches['intent'];
  75.                 $token $processedIntents[$intent] ?? null;
  76.                 // Don't generate the token and set the cookie again
  77.                 if ($token === null) {
  78.                     $token $this->getToken($intent);
  79.                     $cookie Cookie::create('csrf[' $intent ']'$token);
  80.                     $cookie->setSecureDefault($request->isSecure());
  81.                     $response->headers->setCookie($cookie);
  82.                     $processedIntents[$intent] = $token;
  83.                 }
  84.                 return $token;
  85.             },
  86.             $content
  87.         );
  88.         $response->setContent($content);
  89.         if ($session !== null) {
  90.             // Pop out the request injected some lines above. This is important for long running applications with roadrunner or swoole
  91.             $this->requestStack->pop();
  92.         }
  93.         return $response;
  94.     }
  95.     private function getToken(string $intent): string
  96.     {
  97.         return $this->csrfTokenManager->getToken($intent)->getValue();
  98.     }
  99.     private function createSession(Request $request): SessionInterface
  100.     {
  101.         $session = new Session($this->sessionFactory->createStorage($request));
  102.         $session->setName('session-');
  103.         $request->setSession($session);
  104.         return $session;
  105.     }
  106. }