Skip to content

Commit a3253f6

Browse files
ycerutofabpot
authored andcommitted
[SecurityBundle] Add user impersonation info and exit action to the profiler
1 parent b223241 commit a3253f6

File tree

11 files changed

+162
-40
lines changed

11 files changed

+162
-40
lines changed

src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
2121
use Symfony\Component\Security\Core\Role\RoleInterface;
2222
use Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener;
23+
use Symfony\Component\Security\Core\Role\SwitchUserRole;
24+
use Symfony\Component\Security\Http\Firewall\SwitchUserListener;
2325
use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator;
2426
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
2527
use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager;
@@ -73,6 +75,9 @@ public function collect(Request $request, Response $response, \Exception $except
7375
$this->data = array(
7476
'enabled' => false,
7577
'authenticated' => false,
78+
'impersonated' => false,
79+
'impersonator_user' => null,
80+
'impersonation_exit_path' => null,
7681
'token' => null,
7782
'token_class' => null,
7883
'logout_url' => null,
@@ -85,6 +90,9 @@ public function collect(Request $request, Response $response, \Exception $except
8590
$this->data = array(
8691
'enabled' => true,
8792
'authenticated' => false,
93+
'impersonated' => false,
94+
'impersonator_user' => null,
95+
'impersonation_exit_path' => null,
8896
'token' => null,
8997
'token_class' => null,
9098
'logout_url' => null,
@@ -97,6 +105,14 @@ public function collect(Request $request, Response $response, \Exception $except
97105
$inheritedRoles = array();
98106
$assignedRoles = $token->getRoles();
99107

108+
$impersonatorUser = null;
109+
foreach ($assignedRoles as $role) {
110+
if ($role instanceof SwitchUserRole) {
111+
$impersonatorUser = $role->getSource()->getUsername();
112+
break;
113+
}
114+
}
115+
100116
if (null !== $this->roleHierarchy) {
101117
$allRoles = $this->roleHierarchy->getReachableRoles($assignedRoles);
102118
foreach ($allRoles as $role) {
@@ -126,6 +142,9 @@ public function collect(Request $request, Response $response, \Exception $except
126142
$this->data = array(
127143
'enabled' => true,
128144
'authenticated' => $token->isAuthenticated(),
145+
'impersonated' => null !== $impersonatorUser,
146+
'impersonator_user' => $impersonatorUser,
147+
'impersonation_exit_path' => null,
129148
'token' => $token,
130149
'token_class' => $this->hasVarDumper ? new ClassStub(get_class($token)) : get_class($token),
131150
'logout_url' => $logoutUrl,
@@ -169,6 +188,15 @@ public function collect(Request $request, Response $response, \Exception $except
169188
'user_checker' => $firewallConfig->getUserChecker(),
170189
'listeners' => $firewallConfig->getListeners(),
171190
);
191+
192+
// generate exit impersonation path from current request
193+
if ($this->data['impersonated'] && null !== $switchUserConfig = $firewallConfig->getSwitchUser()) {
194+
$exitPath = $request->getRequestUri();
195+
$exitPath .= null === $request->getQueryString() ? '?' : '&';
196+
$exitPath .= sprintf('%s=%s', urlencode($switchUserConfig['parameter']), SwitchUserListener::EXIT_VALUE);
197+
198+
$this->data['impersonation_exit_path'] = $exitPath;
199+
}
172200
}
173201
}
174202

@@ -245,6 +273,21 @@ public function isAuthenticated()
245273
return $this->data['authenticated'];
246274
}
247275

276+
public function isImpersonated()
277+
{
278+
return $this->data['impersonated'];
279+
}
280+
281+
public function getImpersonatorUser()
282+
{
283+
return $this->data['impersonator_user'];
284+
}
285+
286+
public function getImpersonationExitPath()
287+
{
288+
return $this->data['impersonation_exit_path'];
289+
}
290+
248291
/**
249292
* Get the class name of the security token.
250293
*

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a
448448
}
449449

450450
$config->replaceArgument(10, $listenerKeys);
451+
$config->replaceArgument(11, isset($firewall['switch_user']) ? $firewall['switch_user'] : null);
451452

452453
return array($matcher, $listeners, $exceptionListener);
453454
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@
136136
<argument /> <!-- access_denied_handler -->
137137
<argument /> <!-- access_denied_url -->
138138
<argument type="collection" /> <!-- listeners -->
139+
<argument /> <!-- switch_user -->
139140
</service>
140141

141142
<service id="security.logout_url_generator" class="Symfony\Component\Security\Http\Logout\LogoutUrlGenerator">

src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/security.html.twig

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,47 +16,63 @@
1616
{% endset %}
1717

1818
{% set text %}
19-
{% if collector.enabled %}
20-
{% if collector.token %}
19+
{% if collector.impersonated %}
20+
<div class="sf-toolbar-info-group">
2121
<div class="sf-toolbar-info-piece">
22-
<b>Logged in as</b>
23-
<span>{{ collector.user }}</span>
22+
<b>Impersonator</b>
23+
<span>{{ collector.impersonatorUser }}</span>
2424
</div>
25+
</div>
26+
{% endif %}
2527

26-
<div class="sf-toolbar-info-piece">
27-
<b>Authenticated</b>
28-
<span class="sf-toolbar-status sf-toolbar-status-{{ is_authenticated ? 'green' : 'red' }}">{{ is_authenticated ? 'Yes' : 'No' }}</span>
29-
</div>
28+
<div class="sf-toolbar-info-group">
29+
{% if collector.enabled %}
30+
{% if collector.token %}
31+
<div class="sf-toolbar-info-piece">
32+
<b>Logged in as</b>
33+
<span>{{ collector.user }}</span>
34+
</div>
3035

31-
<div class="sf-toolbar-info-piece">
32-
<b>Token class</b>
33-
<span>{{ collector.tokenClass|abbr_class }}</span>
34-
</div>
35-
{% else %}
36-
<div class="sf-toolbar-info-piece">
37-
<b>Authenticated</b>
38-
<span class="sf-toolbar-status sf-toolbar-status-red">No</span>
39-
</div>
40-
{% endif %}
36+
<div class="sf-toolbar-info-piece">
37+
<b>Authenticated</b>
38+
<span class="sf-toolbar-status sf-toolbar-status-{{ is_authenticated ? 'green' : 'red' }}">{{ is_authenticated ? 'Yes' : 'No' }}</span>
39+
</div>
4140

42-
{% if collector.firewall %}
43-
<div class="sf-toolbar-info-piece">
44-
<b>Firewall name</b>
45-
<span>{{ collector.firewall.name }}</span>
46-
</div>
47-
{% endif %}
41+
<div class="sf-toolbar-info-piece">
42+
<b>Token class</b>
43+
<span>{{ collector.tokenClass|abbr_class }}</span>
44+
</div>
45+
{% else %}
46+
<div class="sf-toolbar-info-piece">
47+
<b>Authenticated</b>
48+
<span class="sf-toolbar-status sf-toolbar-status-red">No</span>
49+
</div>
50+
{% endif %}
51+
52+
{% if collector.firewall %}
53+
<div class="sf-toolbar-info-piece">
54+
<b>Firewall name</b>
55+
<span>{{ collector.firewall.name }}</span>
56+
</div>
57+
{% endif %}
4858

49-
{% if collector.token and collector.logoutUrl %}
59+
{% if collector.token and collector.logoutUrl %}
60+
<div class="sf-toolbar-info-piece">
61+
<b>Actions</b>
62+
<span>
63+
<a href="{{ collector.logoutUrl }}">Logout</a>
64+
{% if collector.impersonated and collector.impersonationExitPath %}
65+
| <a href="{{ collector.impersonationExitPath }}">Exit impersonation</a>
66+
{% endif %}
67+
</span>
68+
</div>
69+
{% endif %}
70+
{% else %}
5071
<div class="sf-toolbar-info-piece">
51-
<b>Actions</b>
52-
<span><a href="{{ collector.logoutUrl }}">Logout</a></span>
72+
<span>The security is disabled.</span>
5373
</div>
5474
{% endif %}
55-
{% else %}
56-
<div class="sf-toolbar-info-piece">
57-
<span>The security is disabled.</span>
58-
</div>
59-
{% endif %}
75+
</div>
6076
{% endset %}
6177

6278
{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url, status: color_code }) }}

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ final class FirewallConfig
2727
private $accessDeniedHandler;
2828
private $accessDeniedUrl;
2929
private $listeners;
30+
private $switchUser;
3031

3132
/**
3233
* @param string $name
@@ -40,8 +41,9 @@ final class FirewallConfig
4041
* @param string|null $accessDeniedHandler
4142
* @param string|null $accessDeniedUrl
4243
* @param string[] $listeners
44+
* @param array|null $switchUser
4345
*/
44-
public function __construct($name, $userChecker, $requestMatcher = null, $securityEnabled = true, $stateless = false, $provider = null, $context = null, $entryPoint = null, $accessDeniedHandler = null, $accessDeniedUrl = null, $listeners = array())
46+
public function __construct($name, $userChecker, $requestMatcher = null, $securityEnabled = true, $stateless = false, $provider = null, $context = null, $entryPoint = null, $accessDeniedHandler = null, $accessDeniedUrl = null, $listeners = array(), $switchUser = null)
4547
{
4648
$this->name = $name;
4749
$this->userChecker = $userChecker;
@@ -54,6 +56,7 @@ public function __construct($name, $userChecker, $requestMatcher = null, $securi
5456
$this->accessDeniedHandler = $accessDeniedHandler;
5557
$this->accessDeniedUrl = $accessDeniedUrl;
5658
$this->listeners = $listeners;
59+
$this->switchUser = $switchUser;
5760
}
5861

5962
public function getName()
@@ -140,4 +143,12 @@ public function getListeners()
140143
{
141144
return $this->listeners;
142145
}
146+
147+
/**
148+
* @return array|null The switch_user parameters if configured, null otherwise
149+
*/
150+
public function getSwitchUser()
151+
{
152+
return $this->switchUser;
153+
}
143154
}

src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use Symfony\Component\Security\Core\Role\Role;
2525
use Symfony\Component\Security\Core\Role\RoleHierarchy;
2626
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
27+
use Symfony\Component\Security\Core\Role\SwitchUserRole;
2728
use Symfony\Component\Security\Http\FirewallMapInterface;
2829
use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator;
2930

@@ -37,6 +38,9 @@ public function testCollectWhenSecurityIsDisabled()
3738
$this->assertSame('security', $collector->getName());
3839
$this->assertFalse($collector->isEnabled());
3940
$this->assertFalse($collector->isAuthenticated());
41+
$this->assertFalse($collector->isImpersonated());
42+
$this->assertNull($collector->getImpersonatorUser());
43+
$this->assertNull($collector->getImpersonationExitPath());
4044
$this->assertNull($collector->getTokenClass());
4145
$this->assertFalse($collector->supportsRoleHierarchy());
4246
$this->assertCount(0, $collector->getRoles());
@@ -53,6 +57,9 @@ public function testCollectWhenAuthenticationTokenIsNull()
5357

5458
$this->assertTrue($collector->isEnabled());
5559
$this->assertFalse($collector->isAuthenticated());
60+
$this->assertFalse($collector->isImpersonated());
61+
$this->assertNull($collector->getImpersonatorUser());
62+
$this->assertNull($collector->getImpersonationExitPath());
5663
$this->assertNull($collector->getTokenClass());
5764
$this->assertTrue($collector->supportsRoleHierarchy());
5865
$this->assertCount(0, $collector->getRoles());
@@ -73,13 +80,43 @@ public function testCollectAuthenticationTokenAndRoles(array $roles, array $norm
7380

7481
$this->assertTrue($collector->isEnabled());
7582
$this->assertTrue($collector->isAuthenticated());
83+
$this->assertFalse($collector->isImpersonated());
84+
$this->assertNull($collector->getImpersonatorUser());
85+
$this->assertNull($collector->getImpersonationExitPath());
7686
$this->assertSame('Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken', $collector->getTokenClass()->getValue());
7787
$this->assertTrue($collector->supportsRoleHierarchy());
7888
$this->assertSame($normalizedRoles, $collector->getRoles()->getValue(true));
7989
$this->assertSame($inheritedRoles, $collector->getInheritedRoles()->getValue(true));
8090
$this->assertSame('hhamon', $collector->getUser());
8191
}
8292

93+
public function testCollectImpersonatedToken()
94+
{
95+
$adminToken = new UsernamePasswordToken('yceruto', 'P4$$w0rD', 'provider', array('ROLE_ADMIN'));
96+
97+
$userRoles = array(
98+
'ROLE_USER',
99+
new SwitchUserRole('ROLE_PREVIOUS_ADMIN', $adminToken),
100+
);
101+
102+
$tokenStorage = new TokenStorage();
103+
$tokenStorage->setToken(new UsernamePasswordToken('hhamon', 'P4$$w0rD', 'provider', $userRoles));
104+
105+
$collector = new SecurityDataCollector($tokenStorage, $this->getRoleHierarchy());
106+
$collector->collect($this->getRequest(), $this->getResponse());
107+
$collector->lateCollect();
108+
109+
$this->assertTrue($collector->isEnabled());
110+
$this->assertTrue($collector->isAuthenticated());
111+
$this->assertTrue($collector->isImpersonated());
112+
$this->assertSame('yceruto', $collector->getImpersonatorUser());
113+
$this->assertSame('Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken', $collector->getTokenClass()->getValue());
114+
$this->assertTrue($collector->supportsRoleHierarchy());
115+
$this->assertSame(array('ROLE_USER', 'ROLE_PREVIOUS_ADMIN'), $collector->getRoles()->getValue(true));
116+
$this->assertSame(array(), $collector->getInheritedRoles()->getValue(true));
117+
$this->assertSame('hhamon', $collector->getUser());
118+
}
119+
83120
public function testGetFirewall()
84121
{
85122
$firewallConfig = new FirewallConfig('dummy', 'security.request_matcher.dummy', 'security.user_checker.dummy');

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ public function testFirewalls()
107107
'remember_me',
108108
'anonymous',
109109
),
110+
array(
111+
'parameter' => '_switch_user',
112+
'role' => 'ROLE_ALLOWED_TO_SWITCH',
113+
),
110114
),
111115
array(
112116
'host',
@@ -123,6 +127,7 @@ public function testFirewalls()
123127
'http_basic',
124128
'anonymous',
125129
),
130+
null,
126131
),
127132
array(
128133
'with_user_checker',
@@ -139,6 +144,7 @@ public function testFirewalls()
139144
'http_basic',
140145
'anonymous',
141146
),
147+
null,
142148
),
143149
), $configs);
144150

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

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

14+
use Symfony\Component\Security\Http\Firewall\SwitchUserListener;
15+
1416
class SwitchUserTest extends WebTestCase
1517
{
1618
/**
@@ -42,7 +44,7 @@ public function testSwitchedUserExit()
4244
$client = $this->createAuthenticatedClient('user_can_switch');
4345

4446
$client->request('GET', '/profile?_switch_user=user_cannot_switch_1');
45-
$client->request('GET', '/profile?_switch_user=_exit');
47+
$client->request('GET', '/profile?_switch_user='.SwitchUserListener::EXIT_VALUE);
4648

4749
$this->assertEquals(200, $client->getResponse()->getStatusCode());
4850
$this->assertEquals('user_can_switch', $client->getProfile()->getCollector('security')->getUser());

src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallConfigTest.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public function testGetters()
2929
'access_denied_url' => 'foo_access_denied_url',
3030
'access_denied_handler' => 'foo_access_denied_handler',
3131
'user_checker' => 'foo_user_checker',
32+
'switch_user' => array('provider' => null, 'parameter' => '_switch_user', 'role' => 'ROLE_ALLOWED_TO_SWITCH'),
3233
);
3334

3435
$config = new FirewallConfig(
@@ -42,7 +43,8 @@ public function testGetters()
4243
$options['entry_point'],
4344
$options['access_denied_handler'],
4445
$options['access_denied_url'],
45-
$listeners
46+
$listeners,
47+
$options['switch_user']
4648
);
4749

4850
$this->assertSame('foo_firewall', $config->getName());
@@ -57,5 +59,6 @@ public function testGetters()
5759
$this->assertSame($options['user_checker'], $config->getUserChecker());
5860
$this->assertTrue($config->allowsAnonymous());
5961
$this->assertSame($listeners, $config->getListeners());
62+
$this->assertSame($options['switch_user'], $config->getSwitchUser());
6063
}
6164
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy