Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit ea80c5c

Browse filesBrowse files
committed
Make CSRF validation opt-in
1 parent c300eef commit ea80c5c
Copy full SHA for ea80c5c

File tree

5 files changed

+112
-49
lines changed
Filter options

5 files changed

+112
-49
lines changed

‎src/Symfony/Bundle/SecurityBundle/Security/Security.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Security/Security.php
+7-4Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public function login(UserInterface $user, string $authenticatorName = null, str
7171
*
7272
* @return Response|null The LogoutEvent's Response if any
7373
*/
74-
public function logout(): ?Response
74+
public function logout(bool $validateCsrfToken = false): ?Response
7575
{
7676
/** @var TokenStorageInterface $tokenStorage */
7777
$tokenStorage = $this->container->get('security.token_storage');
@@ -86,17 +86,20 @@ public function logout(): ?Response
8686
throw new LogicException('Unable to logout as the request is not behind a firewall.');
8787
}
8888

89-
if ($this->container->has('security.csrf.token_manager') && $logoutConfig = $firewallConfig->getLogout()) {
89+
if ($validateCsrfToken) {
90+
if (!$this->container->has('security.csrf.token_manager') || !$logoutConfig = $firewallConfig->getLogout()) {
91+
throw new LogicException(sprintf('Unable to logout with CSRF token validation. Either make sure that CSRF protection is enabled and "logout" is configured on the "%s" firewall, or bypass CSRF token validation explicitly by passing false to the $validateCsrfToken argument of this method.', $firewallConfig->getName()));
92+
}
9093
$csrfToken = ParameterBagUtils::getRequestParameterValue($request, $logoutConfig['csrf_parameter']);
91-
if (!\is_string($csrfToken) || false === $this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($logoutConfig['csrf_token_id'], $csrfToken))) {
94+
if (!\is_string($csrfToken) || !$this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($logoutConfig['csrf_token_id'], $csrfToken))) {
9295
throw new LogoutException('Invalid CSRF token.');
9396
}
9497
}
9598

9699
$logoutEvent = new LogoutEvent($request, $token);
97100
$this->container->get('security.firewall.event_dispatcher_locator')->get($firewallConfig->getName())->dispatch($logoutEvent);
98101

99-
$tokenStorage->setToken();
102+
$tokenStorage->setToken(null);
100103

101104
return $logoutEvent->getResponse();
102105
}

‎src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php
+62-20Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@
1414
use Symfony\Bundle\SecurityBundle\Security\FirewallConfig;
1515
use Symfony\Bundle\SecurityBundle\Security\Security;
1616
use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\SecuredPageBundle\Security\Core\User\ArrayUserProvider;
17+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
1718
use Symfony\Component\HttpFoundation\JsonResponse;
1819
use Symfony\Component\HttpFoundation\Request;
20+
use Symfony\Component\HttpFoundation\Response;
21+
use Symfony\Component\HttpKernel\Event\RequestEvent;
22+
use Symfony\Component\HttpKernel\KernelEvents;
1923
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
2024
use Symfony\Component\Security\Core\User\InMemoryUser;
2125
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
2226
use Symfony\Component\Security\Core\User\UserInterface;
23-
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
2427

2528
class SecurityTest extends AbstractWebTestCase
2629
{
@@ -38,8 +41,10 @@ public function testServiceIsFunctional()
3841
$security = $container->get('functional_test.security.helper');
3942
$this->assertTrue($security->isGranted('ROLE_USER'));
4043
$this->assertSame($token, $security->getToken());
41-
$this->assertInstanceOf(FirewallConfig::class, $firewallConfig = $security->getFirewallConfig(new Request()));
42-
$this->assertSame('default', $firewallConfig->getName());
44+
$request = new Request();
45+
$request->server->set('REQUEST_URI', '/main/foo');
46+
$this->assertInstanceOf(FirewallConfig::class, $firewallConfig = $security->getFirewallConfig($request));
47+
$this->assertSame('main', $firewallConfig->getName());
4348
}
4449

4550
/**
@@ -86,14 +91,14 @@ public function userWillBeMarkedAsChangedIfRolesHasChangedProvider()
8691
}
8792

8893
/**
89-
* @testWith ["json_login"]
94+
* @testWith ["form_login"]
9095
* ["Symfony\\Bundle\\SecurityBundle\\Tests\\Functional\\Bundle\\AuthenticatorBundle\\ApiAuthenticator"]
9196
*/
9297
public function testLogin(string $authenticator)
9398
{
94-
$client = $this->createClient(['test_case' => 'SecurityHelper', 'root_config' => 'config.yml']);
95-
static::getContainer()->get(WelcomeController::class)->authenticator = $authenticator;
96-
$client->request('GET', '/welcome');
99+
$client = $this->createClient(['test_case' => 'SecurityHelper', 'root_config' => 'config.yml', 'debug' > true]);
100+
static::getContainer()->get(ForceLoginController::class)->authenticator = $authenticator;
101+
$client->request('GET', '/main/force-login');
97102
$response = $client->getResponse();
98103

99104
$this->assertInstanceOf(JsonResponse::class, $response);
@@ -104,8 +109,10 @@ public function testLogin(string $authenticator)
104109

105110
public function testLogout()
106111
{
107-
$client = $this->createClient(['test_case' => 'SecurityHelper', 'root_config' => 'config.yml']);
108-
$client->request('GET', '/force-logout');
112+
$client = $this->createClient(['test_case' => 'SecurityHelper', 'root_config' => 'config.yml', 'debug' => true]);
113+
$client->loginUser(new InMemoryUser('chalasr', 'the-password', ['ROLE_FOO']), 'main');
114+
115+
$client->request('GET', '/main/force-logout');
109116
$response = $client->getResponse();
110117

111118
$this->assertSame(200, $response->getStatusCode());
@@ -114,9 +121,38 @@ public function testLogout()
114121
}
115122

116123
public function testLogoutWithCsrf()
124+
{
125+
$client = $this->createClient(['test_case' => 'SecurityHelper', 'root_config' => 'config_logout_csrf.yml', 'debug' => true]);
126+
$client->loginUser(new InMemoryUser('chalasr', 'the-password', ['ROLE_FOO']), 'main');
127+
128+
/** @var EventDispatcherInterface $eventDispatcher */
129+
$eventDispatcher = static::getContainer()->get(EventDispatcherInterface::class);
130+
$setCsrfToken = function (RequestEvent $event) {
131+
static::getContainer()->get('security.csrf.token_storage')->setToken('logout', 'bar');
132+
$event->setResponse(new Response(''));
133+
};
134+
$eventDispatcher->addListener(KernelEvents::REQUEST, $setCsrfToken);
135+
try {
136+
$client->request('GET', '/'.uniqid('', true));
137+
} finally {
138+
$eventDispatcher->removeListener(KernelEvents::REQUEST, $setCsrfToken);
139+
}
140+
141+
static::getContainer()->get(LogoutController::class)->checkCsrf = true;
142+
$client->request('GET', '/main/force-logout', ['_csrf_token' => 'bar']);
143+
$response = $client->getResponse();
144+
145+
$this->assertSame(200, $response->getStatusCode());
146+
$this->assertNull(static::getContainer()->get('security.helper')->getUser());
147+
$this->assertSame(['message' => 'Logout successful'], json_decode($response->getContent(), true));
148+
}
149+
150+
public function testLogoutBypassCsrf()
117151
{
118152
$client = $this->createClient(['test_case' => 'SecurityHelper', 'root_config' => 'config_logout_csrf.yml']);
119-
$client->request('GET', '/force-logout');
153+
$client->loginUser(new InMemoryUser('chalasr', 'the-password', ['ROLE_FOO']), 'main');
154+
155+
$client->request('GET', '/main/force-logout');
120156
$response = $client->getResponse();
121157

122158
$this->assertSame(200, $response->getStatusCode());
@@ -232,17 +268,17 @@ public function eraseCredentials(): void
232268
}
233269
}
234270

235-
class WelcomeController
271+
class ForceLoginController
236272
{
237-
public $authenticator = 'json_login';
273+
public $authenticator = 'form_login';
238274

239275
public function __construct(private Security $security)
240276
{
241277
}
242278

243279
public function welcome()
244280
{
245-
$user = new InMemoryUser('chalasr', '', ['ROLE_USER']);
281+
$user = new InMemoryUser('chalasr', 'the-password', ['ROLE_FOO']);
246282
$this->security->login($user, $this->authenticator);
247283

248284
return new JsonResponse(['message' => sprintf('Welcome @%s!', $this->security->getUser()->getUserIdentifier())]);
@@ -251,18 +287,24 @@ public function welcome()
251287

252288
class LogoutController
253289
{
254-
public function __construct(private Security $security, private ?CsrfTokenManagerInterface $csrfTokenManager = null)
290+
public $checkCsrf = false;
291+
292+
public function __construct(private Security $security)
255293
{
256294
}
257295

258-
public function logout(Request $request)
296+
public function logout(UserInterface $user)
259297
{
260-
$this->security->login(new InMemoryUser('chalasr', '', ['ROLE_USER']), 'json_login', 'default');
261-
if ($this->csrfTokenManager) {
262-
$request->query->set('_csrf_token', (string) $this->csrfTokenManager->getToken('logout'));
263-
}
264-
$this->security->logout();
298+
$this->security->logout($this->checkCsrf);
265299

266300
return new JsonResponse(['message' => 'Logout successful']);
267301
}
268302
}
303+
304+
class LoggedInController
305+
{
306+
public function __invoke(UserInterface $user)
307+
{
308+
return new JsonResponse(['message' => sprintf('Welcome back @%s', $user->getUserIdentifier())]);
309+
}
310+
}

‎src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config.yml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config.yml
+17-9Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ imports:
22
- { resource: ./../config/framework.yml }
33

44
services:
5-
# alias the service so we can access it in the tests
65
functional_test.security.helper:
76
alias: security.helper
87
public: true
@@ -11,30 +10,39 @@ services:
1110
alias: security.token_storage
1211
public: true
1312

14-
Symfony\Bundle\SecurityBundle\Tests\Functional\WelcomeController:
13+
Symfony\Bundle\SecurityBundle\Tests\Functional\ForceLoginController:
1514
arguments: ['@security.helper']
1615
public: true
1716

1817
Symfony\Bundle\SecurityBundle\Tests\Functional\LogoutController:
1918
arguments: ['@security.helper']
2019
public: true
2120

21+
Symfony\Bundle\SecurityBundle\Tests\Functional\LoggedInController:
22+
arguments: ['@security.helper']
23+
public: true
24+
2225
Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ApiAuthenticator: ~
2326

2427
security:
2528
enable_authenticator_manager: true
29+
2630
providers:
27-
in_memory:
31+
main:
2832
memory:
29-
users: []
33+
users:
34+
chalasr: { password: the-password, roles: ['ROLE_FOO'] }
35+
no-role-username: { password: the-password, roles: [] }
3036

3137
firewalls:
32-
default:
33-
json_login:
34-
username_path: user.login
35-
password_path: user.password
38+
main:
39+
pattern: ^/main
40+
form_login:
41+
check_path: /main/login/check
3642
custom_authenticators:
3743
- 'Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ApiAuthenticator'
44+
provider: main
3845

3946
access_control:
40-
- { path: ^/foo, roles: PUBLIC_ACCESS }
47+
- { path: '^/main/login/check$', roles: IS_AUTHENTICATED_FULLY }
48+
- { path: '^/main/logged-in$', roles: IS_AUTHENTICATED_FULLY }

‎src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config_logout_csrf.yml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config_logout_csrf.yml
+18-12Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ imports:
22
- { resource: ./../config/framework.yml }
33

44
services:
5-
# alias the service so we can access it in the tests
65
functional_test.security.helper:
76
alias: security.helper
87
public: true
@@ -11,32 +10,39 @@ services:
1110
alias: security.token_storage
1211
public: true
1312

14-
Symfony\Bundle\SecurityBundle\Tests\Functional\WelcomeController:
13+
Symfony\Bundle\SecurityBundle\Tests\Functional\ForceLoginController:
1514
arguments: ['@security.helper']
1615
public: true
1716

1817
Symfony\Bundle\SecurityBundle\Tests\Functional\LogoutController:
19-
arguments: ['@security.helper', '@security.csrf.token_manager']
18+
arguments: ['@security.helper']
19+
public: true
20+
21+
Symfony\Bundle\SecurityBundle\Tests\Functional\LoggedInController:
22+
arguments: ['@security.helper']
2023
public: true
2124

2225
Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ApiAuthenticator: ~
2326

2427
security:
2528
enable_authenticator_manager: true
29+
2630
providers:
27-
in_memory:
31+
main:
2832
memory:
29-
users: []
33+
users:
34+
chalasr: { password: the-password, roles: ['ROLE_FOO'] }
35+
no-role-username: { password: the-password, roles: [] }
3036

3137
firewalls:
32-
default:
33-
json_login:
34-
username_path: user.login
35-
password_path: user.password
36-
logout:
37-
path: /regular-logout
38+
main:
39+
pattern: ^/main
40+
form_login:
41+
check_path: /main/login/check
3842
custom_authenticators:
3943
- 'Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ApiAuthenticator'
44+
provider: main
4045

4146
access_control:
42-
- { path: ^/foo, roles: PUBLIC_ACCESS }
47+
- { path: '^/main/logged-in$', roles: IS_AUTHENTICATED_FULLY }
48+
- { path: '^/main/force-logout$', roles: IS_AUTHENTICATED_FULLY }
+8-4Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
welcome:
2-
path: /welcome
3-
controller: Symfony\Bundle\SecurityBundle\Tests\Functional\WelcomeController::welcome
1+
force-login:
2+
path: /main/force-login
3+
controller: Symfony\Bundle\SecurityBundle\Tests\Functional\ForceLoginController::welcome
4+
5+
logged-in:
6+
path: /main/logged-in
7+
controller: Symfony\Bundle\SecurityBundle\Tests\Functional\LoggedInController
48

59
force-logout:
6-
path: /force-logout
10+
path: /main/force-logout
711
controller: Symfony\Bundle\SecurityBundle\Tests\Functional\LogoutController::logout

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.