Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7152540

Browse files
chalasrRobin Chalas
authored and
Robin Chalas
committed
[Security] Add user impersonation support for stateless authentication
1 parent bd3bc69 commit 7152540

24 files changed

+121
-20
lines changed

UPGRADE-3.4.md

Lines 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

Lines 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

Lines 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

Lines 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

Lines changed: 9 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,21 +686,28 @@ 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
}
703709

710+
704711
private function createExpression($container, $expression)
705712
{
706713
if (isset($this->expressions[$id = 'security.expression.'.ContainerBuilder::hash($expression)])) {

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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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(
Lines 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

Lines 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

Lines 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)