Skip to content

Commit 8d8b03b

Browse files
committed
Merge branch 'sw-26704/change-csrf-token-validation' into '5.7'
Revert "Merge branch 'sw-26689/set-cookie-secure' into '5.7'" See merge request shopware/5/product/shopware!805
2 parents 0b03afc + 09492de commit 8d8b03b

File tree

8 files changed

+57
-252
lines changed

8 files changed

+57
-252
lines changed

.phpstan.neon

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,3 @@ parameters:
177177
- # For testing purposes the constructor of the price struct gets strings
178178
message: '#Parameter .* of class Shopware\\Components\\Cart\\Struct\\Price constructor expects float.*string given#'
179179
path: tests/Functional/Components/Cart/ProportionalTaxCalculatorTest.php
180-
181-
- # The Enlight_Response and Enlight_Request interfaces do not extend from Symfony, and therefore we have to find a unified usage pattern
182-
message: '#Parameter .* of method Shopware\\Components\\CSRFTokenValidator\:\:regenerateToken\(\) expects Symfony\\Component\\HttpFoundation\\Request.* given#'
183-
path: engine/Shopware/Core/sAdmin.php

engine/Shopware/Components/CSRFTokenValidator.php

Lines changed: 30 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,17 @@
3333
use Symfony\Component\DependencyInjection\ContainerInterface;
3434
use Symfony\Component\HttpFoundation\Cookie;
3535
use Symfony\Component\HttpFoundation\Request;
36-
use Symfony\Component\HttpFoundation\Response;
3736

3837
class CSRFTokenValidator implements SubscriberInterface
3938
{
40-
public const CSRF_SESSION_KEY = '__csrf_token-';
39+
public const CSRF_KEY = '__csrf_token-';
4140

4241
public const CSRF_TOKEN_ARGUMENT = '__csrf_token';
4342

4443
public const CSRF_TOKEN_HEADER = 'X-CSRF-Token';
4544

45+
private const CSRF_WAS_VALIDATED = 'isValidated';
46+
4647
/**
4748
* @var ContainerInterface
4849
*/
@@ -140,16 +141,13 @@ public function checkFrontendTokenValidation(Enlight_Event_EventArgs $args)
140141
/** @var Enlight_Controller_Action $controller */
141142
$controller = $args->getSubject();
142143
$request = $controller->Request();
143-
$response = $controller->Response();
144144

145145
// do not check internal sub-requests or validated requests
146-
if ($request->getAttribute('_isSubrequest') || $request->getAttribute('isValidated')) {
146+
if ($request->getAttribute('_isSubrequest') || $request->getAttribute(self::CSRF_WAS_VALIDATED)) {
147147
return;
148148
}
149149

150150
if ($request->isGet() && !$this->isProtected($controller)) {
151-
$this->updateCsrfTokenIfNotAvailable($request, $response);
152-
153151
return;
154152
}
155153

@@ -162,76 +160,40 @@ public function checkFrontendTokenValidation(Enlight_Event_EventArgs $args)
162160
return;
163161
}
164162

165-
if (!$this->checkRequest($request, $response)) {
166-
$this->regenerateToken($request, $response);
163+
if (!$this->checkRequest($request)) {
167164
throw new CSRFTokenValidationException(sprintf('The provided X-CSRF-Token for path "%s" is invalid. Please go back, reload the page and try again.', $request->getRequestUri()));
168165
}
169166

170167
// mark request as validated to avoid double validation
171-
$request->setAttribute('isValidated', true);
168+
$request->setAttribute(self::CSRF_WAS_VALIDATED, true);
172169
}
173170

174-
public function regenerateToken(Request $request, Response $response): string
171+
public function clearExistingCookie(): void
175172
{
176173
$shop = $this->contextService->getShopContext()->getShop();
177174
$name = $this->getCsrfName();
178175

179-
$token = Random::getAlphanumericString(30);
180-
$this->container->get('session')->set($name, $token);
181-
182-
$response->headers->clearCookie($name);
183-
184-
/*
185-
* Appending a '/' to the $basePath is not strictly necessary, but it is
186-
* done to all cookie base paths in the
187-
* `themes/Frontend/Bare/frontend/index/index.tpl` template. It's done
188-
* here as well for compatibility reasons.
189-
*/
190-
$response->headers->setCookie(new Cookie(
176+
$front = $this->container->get('front');
177+
$response = $front->Response();
178+
$response->headers->clearCookie(
191179
$name,
192-
$token,
193-
0,
194180
sprintf('%s/', $shop->getPath() ?: ''),
195-
null,
181+
'',
196182
$shop->getSecure(),
197183
false
198-
));
199-
200-
return $token;
201-
}
202-
203-
private function updateCsrfTokenIfNotAvailable(Request $request, Response $response): void
204-
{
205-
$name = $this->getCsrfName();
206-
207-
if ($this->container->get('session')->has($name)) {
208-
return;
209-
}
210-
211-
$this->regenerateToken($request, $response);
212-
}
213-
214-
private function getCsrfName(): string
215-
{
216-
$shop = $this->contextService->getShopContext()->getShop();
217-
218-
$name = self::CSRF_SESSION_KEY . $shop->getId();
219-
220-
if ($shop->getParentId() && $this->componentsConfig->get('shareSessionBetweenLanguageShops')) {
221-
$name = self::CSRF_SESSION_KEY . $shop->getParentId();
222-
}
223-
224-
return $name;
184+
);
225185
}
226186

227187
/**
228-
* Check if the submitted CSRF token in cookie or header matches with the token stored in the session
188+
* Check if the submitted CSRF token matches with the token stored in the cookie or header
189+
*
190+
* @return bool
229191
*/
230-
private function checkRequest(Request $request, Response $response): bool
192+
private function checkRequest(Request $request)
231193
{
232194
$name = $this->getCsrfName();
233195

234-
$token = $this->container->get('session')->get($name);
196+
$token = $request->cookies->get($name);
235197

236198
if (!\is_string($token)) {
237199
return false;
@@ -246,6 +208,19 @@ private function checkRequest(Request $request, Response $response): bool
246208
return hash_equals($token, $requestToken);
247209
}
248210

211+
private function getCsrfName(): string
212+
{
213+
$shop = $this->contextService->getShopContext()->getShop();
214+
215+
$name = self::CSRF_KEY . $shop->getId();
216+
217+
if ($shop->getParentId() && $this->componentsConfig->get('shareSessionBetweenLanguageShops')) {
218+
$name = self::CSRF_KEY . $shop->getParentId();
219+
}
220+
221+
return $name;
222+
}
223+
249224
/**
250225
* Check if the controller has opted in for CSRF whitelisting and if the
251226
* called action is on the whitelist

engine/Shopware/Components/DependencyInjection/Controllers/frontend.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
</service>
3939

4040
<service id="shopware_controllers_frontend_csrftoken" class="Shopware_Controllers_Frontend_Csrftoken">
41-
<argument type="service" id="shopware.csrftoken_validator"/>
4241
<tag name="shopware.controller" module="frontend" controller="csrftoken"/>
4342
</service>
4443

engine/Shopware/Controllers/Frontend/Csrftoken.php

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,9 @@
2222
* our trademarks remain entirely with us.
2323
*/
2424

25-
use Shopware\Components\CSRFTokenValidator;
26-
use Symfony\Component\HttpFoundation\Request;
27-
2825
class Shopware_Controllers_Frontend_Csrftoken extends Enlight_Controller_Action
2926
{
30-
private CSRFTokenValidator $CSRFTokenValidator;
31-
32-
public function __construct(CSRFTokenValidator $CSRFTokenValidator)
33-
{
34-
$this->CSRFTokenValidator = $CSRFTokenValidator;
35-
}
27+
private const CSRF_TOKEN_HEADER = 'x-csrf-token';
3628

3729
/**
3830
* Loads auth and script renderer resource
@@ -45,10 +37,10 @@ public function preDispatch()
4537
/**
4638
* Generates a token and fills the cookie and session
4739
*/
48-
public function indexAction(Request $request)
40+
public function indexAction()
4941
{
50-
$token = $this->CSRFTokenValidator->regenerateToken($request, $this->Response());
42+
$token = \Shopware\Components\Random::getAlphanumericString(30);
5143

52-
$this->Response()->headers->set(CSRFTokenValidator::CSRF_TOKEN_HEADER, $token);
44+
$this->Response()->headers->set(self::CSRF_TOKEN_HEADER, $token);
5345
}
5446
}

engine/Shopware/Core/sAdmin.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -718,7 +718,7 @@ public function logout()
718718

719719
$this->contextService->initializeContext();
720720

721-
$this->csrfTokenValidator->regenerateToken($this->front->Request(), $this->front->Response());
721+
$this->csrfTokenValidator->clearExistingCookie();
722722

723723
if (!$this->config->get('clearBasketAfterLogout')) {
724724
$this->moduleManager->Basket()->sRefreshBasket();
@@ -3277,7 +3277,7 @@ protected function loginUser($getUser, $email, $password, $isPreHashed, $encoder
32773277
$this->session->offsetSet('sUserId', $userId);
32783278
$this->session->offsetSet('sNotesQuantity', $this->moduleManager->Basket()->sCountNotes());
32793279

3280-
$this->csrfTokenValidator->regenerateToken($this->front->Request(), $this->front->Response());
3280+
$this->csrfTokenValidator->clearExistingCookie();
32813281

32823282
if (!$this->sCheckUser()) {
32833283
return;

0 commit comments

Comments
 (0)