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

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