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 32fc7bb

Browse filesBrowse files
author
Robin Chalas
committed
[Security] Add user impersonation support for stateless authentication
1 parent bd3bc69 commit 32fc7bb
Copy full SHA for 32fc7bb

24 files changed

+120
-20
lines changed

‎UPGRADE-3.4.md

Copy file name to clipboardExpand all lines: UPGRADE-3.4.md
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,9 @@ SecurityBundle
302302

303303
* Deprecated the HTTP digest authentication: `HttpDigestFactory` will be removed in 4.0.
304304
Use another authentication system like `http_basic` instead.
305+
306+
* Deprecated setting the `switch_user.stateless` option to false when the firewall is `stateless`.
307+
Setting it to false will have no effect in 4.0.
305308

306309
Translation
307310
-----------

‎UPGRADE-4.0.md

Copy file name to clipboardExpand all lines: UPGRADE-4.0.md
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,8 @@ SecurityBundle
677677

678678
* Removed the HTTP digest authentication system. The `HttpDigestFactory` class
679679
has been removed. Use another authentication system like `http_basic` instead.
680+
681+
* The `switch_user.stateless` option is now always true if the firewall is stateless.
680682

681683
Serializer
682684
----------

‎src/Symfony/Bundle/SecurityBundle/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ CHANGELOG
1414
* deprecated HTTP digest authentication
1515
* deprecated command `acl:set` along with `SetAclCommand` class
1616
* deprecated command `init:acl` along with `InitAclCommand` class
17+
* added `stateless` option to the `switch_user` listener
1718

1819
3.3.0
1920
-----

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto
304304
->scalarNode('provider')->end()
305305
->scalarNode('parameter')->defaultValue('_switch_user')->end()
306306
->scalarNode('role')->defaultValue('ROLE_ALLOWED_TO_SWITCH')->end()
307+
->booleanNode('stateless')->defaultValue(false)->end()
307308
->end()
308309
->end()
309310
;

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
+8-2Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a
455455
// Switch user listener
456456
if (isset($firewall['switch_user'])) {
457457
$listenerKeys[] = 'switch_user';
458-
$listeners[] = new Reference($this->createSwitchUserListener($container, $id, $firewall['switch_user'], $defaultProvider));
458+
$listeners[] = new Reference($this->createSwitchUserListener($container, $id, $firewall['switch_user'], $defaultProvider, $firewall['stateless']));
459459
}
460460

461461
// Access listener
@@ -686,17 +686,23 @@ private function createExceptionListener($container, $config, $id, $defaultEntry
686686
return $exceptionListenerId;
687687
}
688688

689-
private function createSwitchUserListener($container, $id, $config, $defaultProvider)
689+
private function createSwitchUserListener($container, $id, $config, $defaultProvider, $stateless)
690690
{
691691
$userProvider = isset($config['provider']) ? $this->getUserProviderId($config['provider']) : $defaultProvider;
692692

693+
// in 4.0, deprecate and ignore the `switch_user.stateless` key, consider only "stateless" at firewall level and deprecate the `switch_user
694+
if ($stateless && false === $config['stateless']) {
695+
@trigger_error(sprintf('Firewall "%s" is configured as "stateless" but the "switch_user.stateless" key is set to false. Both should have the same value, the firewall\'s "stateless" value will be used as default value for the "switch_user.stateless" key in 4.0.', $id), E_USER_DEPRECATED);
696+
}
697+
693698
$switchUserListenerId = 'security.authentication.switchuser_listener.'.$id;
694699
$listener = $container->setDefinition($switchUserListenerId, new ChildDefinition('security.authentication.switchuser_listener'));
695700
$listener->replaceArgument(1, new Reference($userProvider));
696701
$listener->replaceArgument(2, new Reference('security.user_checker.'.$id));
697702
$listener->replaceArgument(3, $id);
698703
$listener->replaceArgument(6, $config['parameter']);
699704
$listener->replaceArgument(7, $config['role']);
705+
$listener->replaceArgument(9, $config['stateless']);
700706

701707
return $switchUserListenerId;
702708
}

‎src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@
241241
<argument>_switch_user</argument>
242242
<argument>ROLE_ALLOWED_TO_SWITCH</argument>
243243
<argument type="service" id="event_dispatcher" on-invalid="null"/>
244+
<argument>false</argument> <!-- Stateless -->
244245
</service>
245246

246247
<service id="security.access_listener" class="Symfony\Component\Security\Http\Firewall\AccessListener">

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ public function testFirewalls()
129129
array(
130130
'parameter' => '_switch_user',
131131
'role' => 'ROLE_ALLOWED_TO_SWITCH',
132+
'stateless' => true,
132133
),
133134
),
134135
array(
@@ -255,6 +256,7 @@ public function testFirewallsWithDigest()
255256
array(
256257
'parameter' => '_switch_user',
257258
'role' => 'ROLE_ALLOWED_TO_SWITCH',
259+
'stateless' => true,
258260
),
259261
),
260262
array(

‎src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
'http_basic' => true,
6666
'form_login' => true,
6767
'anonymous' => true,
68-
'switch_user' => true,
68+
'switch_user' => array('stateless' => true),
6969
'x509' => true,
7070
'remote_user' => true,
7171
'logout' => true,

‎src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1_with_acl.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1_with_acl.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
'http_digest' => array('secret' => 'TheSecret'),
6868
'form_login' => true,
6969
'anonymous' => true,
70-
'switch_user' => true,
70+
'switch_user' => array('stateless' => true),
7171
'x509' => true,
7272
'remote_user' => true,
7373
'logout' => true,

‎src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1_with_digest.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1_with_digest.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
'http_digest' => array('secret' => 'TheSecret'),
6868
'form_login' => true,
6969
'anonymous' => true,
70-
'switch_user' => true,
70+
'switch_user' => array('stateless' => true),
7171
'x509' => true,
7272
'remote_user' => true,
7373
'logout' => true,

‎src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/no_custom_user_checker.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/no_custom_user_checker.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
'http_basic' => true,
1818
'form_login' => true,
1919
'anonymous' => true,
20-
'switch_user' => true,
20+
'switch_user' => array('stateless' => true),
2121
'x509' => true,
2222
'remote_user' => true,
2323
'logout' => true,

‎src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
<http-basic />
5050
<form-login />
5151
<anonymous />
52-
<switch-user />
52+
<switch-user stateless="true" />
5353
<x509 />
5454
<remote-user />
5555
<user-checker />

‎src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1_with_acl.xml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1_with_acl.xml
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
<http-digest secret="TheSecret" />
5252
<form-login />
5353
<anonymous />
54-
<switch-user />
54+
<switch-user stateless="true" />
5555
<x509 />
5656
<remote-user />
5757
<user-checker />

‎src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1_with_digest.xml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1_with_digest.xml
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
<http-digest secret="TheSecret" />
5353
<form-login />
5454
<anonymous />
55-
<switch-user />
55+
<switch-user stateless="true" />
5656
<x509 />
5757
<remote-user />
5858
<user-checker />

‎src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/no_custom_user_checker.xml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/no_custom_user_checker.xml
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<http-basic />
1818
<form-login />
1919
<anonymous />
20-
<switch-user />
20+
<switch-user stateless="true" />
2121
<x509 />
2222
<remote-user />
2323
<user-checker />

‎src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ security:
4747
http_basic: true
4848
form_login: true
4949
anonymous: true
50-
switch_user: true
50+
switch_user:
51+
stateless: true
5152
x509: true
5253
remote_user: true
5354
logout: true

‎src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1_with_acl.yml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1_with_acl.yml
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ security:
5050
secret: TheSecret
5151
form_login: true
5252
anonymous: true
53-
switch_user: true
53+
switch_user:
54+
stateless: true
5455
x509: true
5556
remote_user: true
5657
logout: true

‎src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1_with_digest.yml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1_with_digest.yml
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ security:
5050
secret: TheSecret
5151
form_login: true
5252
anonymous: true
53-
switch_user: true
53+
switch_user:
54+
stateless: true
5455
x509: true
5556
remote_user: true
5657
logout: true

‎src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/no_custom_user_checker.yml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/no_custom_user_checker.yml
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ security:
1212
http_basic: true
1313
form_login: true
1414
anonymous: true
15-
switch_user: true
15+
switch_user:
16+
stateless: true
1617
x509: true
1718
remote_user: true
1819
logout: true

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php
+26Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,32 @@ public function testDeprecationForUserLogout()
148148
$container->compile();
149149
}
150150

151+
/**
152+
* @group legacy
153+
* @expectedDeprecation Firewall "some_firewall" is configured as "stateless" but the "switch_user.stateless" key is set to false. Both should have the same value, the firewall's "stateless" value will be used as default value for the "switch_user.stateless" key in 4.0.
154+
*/
155+
public function testSwitchUserNotStatelessOnStatelessFirewall()
156+
{
157+
$container = $this->getRawContainer();
158+
159+
$container->loadFromExtension('security', array(
160+
'providers' => array(
161+
'default' => array('id' => 'foo'),
162+
),
163+
164+
'firewalls' => array(
165+
'some_firewall' => array(
166+
'stateless' => true,
167+
'http_basic' => null,
168+
'switch_user' => array('stateless' => false),
169+
'logout_on_user_change' => true,
170+
),
171+
),
172+
));
173+
174+
$container->compile();
175+
}
176+
151177
protected function getRawContainer()
152178
{
153179
$container = new ContainerBuilder();

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/Functional/SwitchUserTest.php
+13Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Bundle\SecurityBundle\Tests\Functional;
1313

14+
use Symfony\Component\HttpFoundation\JsonResponse;
1415
use Symfony\Component\Security\Http\Firewall\SwitchUserListener;
1516

1617
class SwitchUserTest extends WebTestCase
@@ -50,6 +51,18 @@ public function testSwitchedUserExit()
5051
$this->assertEquals('user_can_switch', $client->getProfile()->getCollector('security')->getUser());
5152
}
5253

54+
public function testSwitchUserStateless()
55+
{
56+
$client = $this->createClient(array('test_case' => 'JsonLogin', 'root_config' => 'switchuser_stateless.yml'));
57+
$client->request('POST', '/chk', array('_switch_user' => 'dunglas'), array(), array('CONTENT_TYPE' => 'application/json'), '{"user": {"login": "user_can_switch", "password": "test"}}');
58+
$response = $client->getResponse();
59+
60+
$this->assertInstanceOf(JsonResponse::class, $response);
61+
$this->assertSame(200, $response->getStatusCode());
62+
$this->assertSame(array('message' => 'Welcome @dunglas!'), json_decode($response->getContent(), true));
63+
$this->assertSame('dunglas', $client->getProfile()->getCollector('security')->getUser());
64+
}
65+
5366
public function getTestParameters()
5467
{
5568
return array(
+13Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
imports:
2+
- { resource: ./config.yml }
3+
4+
security:
5+
providers:
6+
in_memory:
7+
memory:
8+
users:
9+
user_can_switch: { password: test, roles: [ROLE_USER, ROLE_ALLOWED_TO_SWITCH] }
10+
firewalls:
11+
main:
12+
switch_user:
13+
stateless: true

‎src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php
+9-6Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,9 @@ class SwitchUserListener implements ListenerInterface
4949
private $role;
5050
private $logger;
5151
private $dispatcher;
52+
private $stateless;
5253

53-
public function __construct(TokenStorageInterface $tokenStorage, UserProviderInterface $provider, UserCheckerInterface $userChecker, $providerKey, AccessDecisionManagerInterface $accessDecisionManager, LoggerInterface $logger = null, $usernameParameter = '_switch_user', $role = 'ROLE_ALLOWED_TO_SWITCH', EventDispatcherInterface $dispatcher = null)
54+
public function __construct(TokenStorageInterface $tokenStorage, UserProviderInterface $provider, UserCheckerInterface $userChecker, $providerKey, AccessDecisionManagerInterface $accessDecisionManager, LoggerInterface $logger = null, $usernameParameter = '_switch_user', $role = 'ROLE_ALLOWED_TO_SWITCH', EventDispatcherInterface $dispatcher = null, $stateless = false)
5455
{
5556
if (empty($providerKey)) {
5657
throw new \InvalidArgumentException('$providerKey must not be empty.');
@@ -65,6 +66,7 @@ public function __construct(TokenStorageInterface $tokenStorage, UserProviderInt
6566
$this->role = $role;
6667
$this->logger = $logger;
6768
$this->dispatcher = $dispatcher;
69+
$this->stateless = $stateless;
6870
}
6971

7072
/**
@@ -92,12 +94,13 @@ public function handle(GetResponseEvent $event)
9294
}
9395
}
9496

95-
$request->query->remove($this->usernameParameter);
96-
$request->server->set('QUERY_STRING', http_build_query($request->query->all()));
97+
if (!$this->stateless) {
98+
$request->query->remove($this->usernameParameter);
99+
$request->server->set('QUERY_STRING', http_build_query($request->query->all()));
100+
$response = new RedirectResponse($request->getUri(), 302);
97101

98-
$response = new RedirectResponse($request->getUri(), 302);
99-
100-
$event->setResponse($response);
102+
$event->setResponse($response);
103+
}
101104
}
102105

103106
/**

‎src/Symfony/Component/Security/Http/Tests/Firewall/SwitchUserListenerTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Security/Http/Tests/Firewall/SwitchUserListenerTest.php
+25Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,4 +266,29 @@ public function testSwitchUserWithReplacedToken()
266266

267267
$this->assertSame($replacedToken, $this->tokenStorage->getToken());
268268
}
269+
270+
public function testSwitchUserStateless()
271+
{
272+
$token = new UsernamePasswordToken('username', '', 'key', array('ROLE_FOO'));
273+
$user = new User('username', 'password', array());
274+
275+
$this->tokenStorage->setToken($token);
276+
$this->request->query->set('_switch_user', 'kuba');
277+
278+
$this->accessDecisionManager->expects($this->once())
279+
->method('decide')->with($token, array('ROLE_ALLOWED_TO_SWITCH'))
280+
->will($this->returnValue(true));
281+
282+
$this->userProvider->expects($this->once())
283+
->method('loadUserByUsername')->with('kuba')
284+
->will($this->returnValue($user));
285+
$this->userChecker->expects($this->once())
286+
->method('checkPostAuth')->with($user);
287+
288+
$listener = new SwitchUserListener($this->tokenStorage, $this->userProvider, $this->userChecker, 'provider123', $this->accessDecisionManager, null, '_switch_user', 'ROLE_ALLOWED_TO_SWITCH', null, true);
289+
$listener->handle($this->event);
290+
291+
$this->assertInstanceOf('Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken', $this->tokenStorage->getToken());
292+
$this->assertFalse($this->event->hasResponse());
293+
}
269294
}

0 commit comments

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