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 c300eef

Browse filesBrowse files
committed
Add CSRF protection
1 parent 2a9b2db commit c300eef
Copy full SHA for c300eef

File tree

9 files changed

+192
-88
lines changed
Filter options

9 files changed

+192
-88
lines changed

‎src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,8 @@ private function createFirewall(ContainerBuilder $container, string $id, array $
455455
false === $firewall['stateless'] && isset($firewall['context']) ? $firewall['context'] : null,
456456
])
457457
;
458+
459+
$config->replaceArgument(12, $firewall['logout']);
458460
}
459461

460462
// Determine default entry point

‎src/Symfony/Bundle/SecurityBundle/Resources/config/security.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Resources/config/security.php
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
'security.firewall.map' => service('security.firewall.map'),
8888
'security.user_checker' => service('security.user_checker'),
8989
'security.firewall.event_dispatcher_locator' => service('security.firewall.event_dispatcher_locator'),
90+
'security.csrf.token_manager' => service('security.csrf.token_manager')->ignoreOnInvalid(),
9091
]),
9192
abstract_arg('authenticators'),
9293
])
@@ -207,6 +208,7 @@
207208
null,
208209
[], // listeners
209210
null, // switch_user
211+
null, // logout
210212
])
211213

212214
->set('security.logout_url_generator', LogoutUrlGenerator::class)

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Security/FirewallConfig.php
+20-27Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,33 +16,21 @@
1616
*/
1717
final class FirewallConfig
1818
{
19-
private string $name;
20-
private string $userChecker;
21-
private ?string $requestMatcher;
22-
private bool $securityEnabled;
23-
private bool $stateless;
24-
private ?string $provider;
25-
private ?string $context;
26-
private ?string $entryPoint;
27-
private ?string $accessDeniedHandler;
28-
private ?string $accessDeniedUrl;
29-
private array $authenticators;
30-
private ?array $switchUser;
31-
32-
public function __construct(string $name, string $userChecker, string $requestMatcher = null, bool $securityEnabled = true, bool $stateless = false, string $provider = null, string $context = null, string $entryPoint = null, string $accessDeniedHandler = null, string $accessDeniedUrl = null, array $authenticators = [], array $switchUser = null)
33-
{
34-
$this->name = $name;
35-
$this->userChecker = $userChecker;
36-
$this->requestMatcher = $requestMatcher;
37-
$this->securityEnabled = $securityEnabled;
38-
$this->stateless = $stateless;
39-
$this->provider = $provider;
40-
$this->context = $context;
41-
$this->entryPoint = $entryPoint;
42-
$this->accessDeniedHandler = $accessDeniedHandler;
43-
$this->accessDeniedUrl = $accessDeniedUrl;
44-
$this->authenticators = $authenticators;
45-
$this->switchUser = $switchUser;
19+
public function __construct(
20+
private readonly string $name,
21+
private readonly string $userChecker,
22+
private readonly ?string $requestMatcher = null,
23+
private readonly bool $securityEnabled = true,
24+
private readonly bool $stateless = false,
25+
private readonly ?string $provider = null,
26+
private readonly ?string $context = null,
27+
private readonly ?string $entryPoint = null,
28+
private readonly ?string $accessDeniedHandler = null,
29+
private readonly ?string $accessDeniedUrl = null,
30+
private readonly array $authenticators = [],
31+
private readonly ?array $switchUser = null,
32+
private readonly ?array $logout = null
33+
) {
4634
}
4735

4836
public function getName(): string
@@ -111,4 +99,9 @@ public function getSwitchUser(): ?array
11199
{
112100
return $this->switchUser;
113101
}
102+
103+
public function getLogout(): ?array
104+
{
105+
return $this->logout;
106+
}
114107
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Security/Security.php
+24-7Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,15 @@
1414
use Psr\Container\ContainerInterface;
1515
use Symfony\Component\HttpFoundation\Request;
1616
use Symfony\Component\HttpFoundation\Response;
17+
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
1718
use Symfony\Component\Security\Core\Exception\LogicException;
19+
use Symfony\Component\Security\Core\Exception\LogoutException;
1820
use Symfony\Component\Security\Core\Security as LegacySecurity;
1921
use Symfony\Component\Security\Core\User\UserInterface;
22+
use Symfony\Component\Security\Csrf\CsrfToken;
2023
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
2124
use Symfony\Component\Security\Http\Event\LogoutEvent;
25+
use Symfony\Component\Security\Http\ParameterBagUtils;
2226
use Symfony\Contracts\Service\ServiceProviderInterface;
2327

2428
/**
@@ -69,17 +73,30 @@ public function login(UserInterface $user, string $authenticatorName = null, str
6973
*/
7074
public function logout(): ?Response
7175
{
76+
/** @var TokenStorageInterface $tokenStorage */
77+
$tokenStorage = $this->container->get('security.token_storage');
78+
79+
if (!($token = $tokenStorage->getToken()) || !$token->getUser()) {
80+
throw new LogicException('Unable to logout as there is no logged-in user.');
81+
}
82+
7283
$request = $this->container->get('request_stack')->getMainRequest();
73-
$logoutEvent = new LogoutEvent($request, $this->container->get('security.token_storage')->getToken());
74-
$firewallConfig = $this->container->get('security.firewall.map')->getFirewallConfig($request);
7584

76-
if (!$firewallConfig) {
77-
throw new LogicException('It is not possible to logout, as the request is not behind a firewall.');
85+
if (!$firewallConfig = $this->container->get('security.firewall.map')->getFirewallConfig($request)) {
86+
throw new LogicException('Unable to logout as the request is not behind a firewall.');
7887
}
79-
$firewallName = $firewallConfig->getName();
8088

81-
$this->container->get('security.firewall.event_dispatcher_locator')->get($firewallName)->dispatch($logoutEvent);
82-
$this->container->get('security.token_storage')->setToken();
89+
if ($this->container->has('security.csrf.token_manager') && $logoutConfig = $firewallConfig->getLogout()) {
90+
$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))) {
92+
throw new LogoutException('Invalid CSRF token.');
93+
}
94+
}
95+
96+
$logoutEvent = new LogoutEvent($request, $token);
97+
$this->container->get('security.firewall.event_dispatcher_locator')->get($firewallConfig->getName())->dispatch($logoutEvent);
98+
99+
$tokenStorage->setToken();
83100

84101
return $logoutEvent->getResponse();
85102
}

‎src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php
+11Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ public function testFirewalls()
141141
'',
142142
[],
143143
null,
144+
null,
144145
],
145146
[
146147
'secure',
@@ -165,6 +166,14 @@ public function testFirewalls()
165166
'parameter' => '_switch_user',
166167
'role' => 'ROLE_ALLOWED_TO_SWITCH',
167168
],
169+
[
170+
'csrf_parameter' => '_csrf_token',
171+
'csrf_token_id' => 'logout',
172+
'path' => '/logout',
173+
'target' => '/',
174+
'invalidate_session' => true,
175+
'delete_cookies' => [],
176+
],
168177
],
169178
[
170179
'host',
@@ -181,6 +190,7 @@ public function testFirewalls()
181190
'http_basic',
182191
],
183192
null,
193+
null,
184194
],
185195
[
186196
'with_user_checker',
@@ -197,6 +207,7 @@ public function testFirewalls()
197207
'http_basic',
198208
],
199209
null,
210+
null,
200211
],
201212
], $configs);
202213

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php
+24-14Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Symfony\Component\Security\Core\User\InMemoryUser;
2121
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
2222
use Symfony\Component\Security\Core\User\UserInterface;
23+
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
2324

2425
class SecurityTest extends AbstractWebTestCase
2526
{
@@ -88,31 +89,36 @@ public function userWillBeMarkedAsChangedIfRolesHasChangedProvider()
8889
* @testWith ["json_login"]
8990
* ["Symfony\\Bundle\\SecurityBundle\\Tests\\Functional\\Bundle\\AuthenticatorBundle\\ApiAuthenticator"]
9091
*/
91-
public function testLoginWithBuiltInAuthenticator(string $authenticator)
92+
public function testLogin(string $authenticator)
9293
{
93-
$client = $this->createClient(['test_case' => 'SecurityHelper', 'root_config' => 'config.yml', 'debug' => true]);
94+
$client = $this->createClient(['test_case' => 'SecurityHelper', 'root_config' => 'config.yml']);
9495
static::getContainer()->get(WelcomeController::class)->authenticator = $authenticator;
9596
$client->request('GET', '/welcome');
9697
$response = $client->getResponse();
9798

9899
$this->assertInstanceOf(JsonResponse::class, $response);
99100
$this->assertSame(200, $response->getStatusCode());
100101
$this->assertSame(['message' => 'Welcome @chalasr!'], json_decode($response->getContent(), true));
102+
$this->assertSame('chalasr', static::getContainer()->get('security.helper')->getUser()->getUserIdentifier());
101103
}
102104

103-
/**
104-
* @testWith ["json_login"]
105-
* ["Symfony\\Bundle\\SecurityBundle\\Tests\\Functional\\Bundle\\AuthenticatorBundle\\ApiAuthenticator"]
106-
*/
107-
public function testLogout(string $authenticator)
105+
public function testLogout()
108106
{
109-
$client = $this->createClient(['test_case' => 'SecurityHelper', 'root_config' => 'config.yml', 'debug' => true]);
110-
static::getContainer()->get(WelcomeController::class)->authenticator = $authenticator;
111-
$client->request('GET', '/welcome');
112-
$this->assertEquals('chalasr', static::getContainer()->get('security.helper')->getUser()->getUserIdentifier());
107+
$client = $this->createClient(['test_case' => 'SecurityHelper', 'root_config' => 'config.yml']);
108+
$client->request('GET', '/force-logout');
109+
$response = $client->getResponse();
110+
111+
$this->assertSame(200, $response->getStatusCode());
112+
$this->assertNull(static::getContainer()->get('security.helper')->getUser());
113+
$this->assertSame(['message' => 'Logout successful'], json_decode($response->getContent(), true));
114+
}
113115

114-
$client->request('GET', '/auto-logout');
116+
public function testLogoutWithCsrf()
117+
{
118+
$client = $this->createClient(['test_case' => 'SecurityHelper', 'root_config' => 'config_logout_csrf.yml']);
119+
$client->request('GET', '/force-logout');
115120
$response = $client->getResponse();
121+
116122
$this->assertSame(200, $response->getStatusCode());
117123
$this->assertNull(static::getContainer()->get('security.helper')->getUser());
118124
$this->assertSame(['message' => 'Logout successful'], json_decode($response->getContent(), true));
@@ -245,12 +251,16 @@ public function welcome()
245251

246252
class LogoutController
247253
{
248-
public function __construct(private Security $security)
254+
public function __construct(private Security $security, private ?CsrfTokenManagerInterface $csrfTokenManager = null)
249255
{
250256
}
251257

252-
public function logout()
258+
public function logout(Request $request)
253259
{
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+
}
254264
$this->security->logout();
255265

256266
return new JsonResponse(['message' => 'Logout successful']);
+42Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
imports:
2+
- { resource: ./../config/framework.yml }
3+
4+
services:
5+
# alias the service so we can access it in the tests
6+
functional_test.security.helper:
7+
alias: security.helper
8+
public: true
9+
10+
functional.test.security.token_storage:
11+
alias: security.token_storage
12+
public: true
13+
14+
Symfony\Bundle\SecurityBundle\Tests\Functional\WelcomeController:
15+
arguments: ['@security.helper']
16+
public: true
17+
18+
Symfony\Bundle\SecurityBundle\Tests\Functional\LogoutController:
19+
arguments: ['@security.helper', '@security.csrf.token_manager']
20+
public: true
21+
22+
Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ApiAuthenticator: ~
23+
24+
security:
25+
enable_authenticator_manager: true
26+
providers:
27+
in_memory:
28+
memory:
29+
users: []
30+
31+
firewalls:
32+
default:
33+
json_login:
34+
username_path: user.login
35+
password_path: user.password
36+
logout:
37+
path: /regular-logout
38+
custom_authenticators:
39+
- 'Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ApiAuthenticator'
40+
41+
access_control:
42+
- { path: ^/foo, roles: PUBLIC_ACCESS }

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/routing.yml
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ welcome:
22
path: /welcome
33
controller: Symfony\Bundle\SecurityBundle\Tests\Functional\WelcomeController::welcome
44

5-
logout:
6-
path: /auto-logout
5+
force-logout:
6+
path: /force-logout
77
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.