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

[SecurityBundle] Add user impersonation info and exit action to the profiler #23026

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
use Symfony\Component\Security\Core\Role\RoleInterface;
use Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener;
use Symfony\Component\Security\Core\Role\SwitchUserRole;
use Symfony\Component\Security\Http\Firewall\SwitchUserListener;
use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager;
Expand Down Expand Up @@ -73,6 +75,9 @@ public function collect(Request $request, Response $response, \Exception $except
$this->data = array(
'enabled' => false,
'authenticated' => false,
'impersonated' => false,
'impersonator_user' => null,
'impersonation_exit_path' => null,
'token' => null,
'token_class' => null,
'logout_url' => null,
Expand All @@ -85,6 +90,9 @@ public function collect(Request $request, Response $response, \Exception $except
$this->data = array(
'enabled' => true,
'authenticated' => false,
'impersonated' => false,
'impersonator_user' => null,
'impersonation_exit_path' => null,
'token' => null,
'token_class' => null,
'logout_url' => null,
Expand All @@ -97,6 +105,14 @@ public function collect(Request $request, Response $response, \Exception $except
$inheritedRoles = array();
$assignedRoles = $token->getRoles();

$impersonatorUser = null;
foreach ($assignedRoles as $role) {
if ($role instanceof SwitchUserRole) {
$impersonatorUser = $role->getSource()->getUsername();
break;
}
}

if (null !== $this->roleHierarchy) {
$allRoles = $this->roleHierarchy->getReachableRoles($assignedRoles);
foreach ($allRoles as $role) {
Expand Down Expand Up @@ -126,6 +142,9 @@ public function collect(Request $request, Response $response, \Exception $except
$this->data = array(
'enabled' => true,
'authenticated' => $token->isAuthenticated(),
'impersonated' => null !== $impersonatorUser,
'impersonator_user' => $impersonatorUser,
'impersonation_exit_path' => null,
'token' => $token,
'token_class' => $this->hasVarDumper ? new ClassStub(get_class($token)) : get_class($token),
'logout_url' => $logoutUrl,
Expand Down Expand Up @@ -169,6 +188,15 @@ public function collect(Request $request, Response $response, \Exception $except
'user_checker' => $firewallConfig->getUserChecker(),
'listeners' => $firewallConfig->getListeners(),
);

// generate exit impersonation path from current request
if ($this->data['impersonated'] && null !== $switchUserConfig = $firewallConfig->getSwitchUser()) {
$exitPath = $request->getRequestUri();
$exitPath .= null === $request->getQueryString() ? '?' : '&';
$exitPath .= sprintf('%s=%s', urlencode($switchUserConfig['parameter']), SwitchUserListener::EXIT_VALUE);

$this->data['impersonation_exit_path'] = $exitPath;
}
}
}

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

public function isImpersonated()
{
return $this->data['impersonated'];
}

public function getImpersonatorUser()
{
return $this->data['impersonator_user'];
}

public function getImpersonationExitPath()
{
return $this->data['impersonation_exit_path'];
}

/**
* Get the class name of the security token.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a
}

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

return array($matcher, $listeners, $exceptionListener);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
<argument /> <!-- access_denied_handler -->
<argument /> <!-- access_denied_url -->
<argument type="collection" /> <!-- listeners -->
<argument /> <!-- switch_user -->
</service>

<service id="security.logout_url_generator" class="Symfony\Component\Security\Http\Logout\LogoutUrlGenerator">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,47 +16,63 @@
{% endset %}

{% set text %}
{% if collector.enabled %}
{% if collector.token %}
{% if collector.impersonated %}
<div class="sf-toolbar-info-group">
<div class="sf-toolbar-info-piece">
<b>Logged in as</b>
<span>{{ collector.user }}</span>
<b>Impersonator</b>
<span>{{ collector.impersonatorUser }}</span>
</div>
</div>
{% endif %}

<div class="sf-toolbar-info-piece">
<b>Authenticated</b>
<span class="sf-toolbar-status sf-toolbar-status-{{ is_authenticated ? 'green' : 'red' }}">{{ is_authenticated ? 'Yes' : 'No' }}</span>
</div>
<div class="sf-toolbar-info-group">
{% if collector.enabled %}
{% if collector.token %}
<div class="sf-toolbar-info-piece">
<b>Logged in as</b>
<span>{{ collector.user }}</span>
</div>

<div class="sf-toolbar-info-piece">
<b>Token class</b>
<span>{{ collector.tokenClass|abbr_class }}</span>
</div>
{% else %}
<div class="sf-toolbar-info-piece">
<b>Authenticated</b>
<span class="sf-toolbar-status sf-toolbar-status-red">No</span>
</div>
{% endif %}
<div class="sf-toolbar-info-piece">
<b>Authenticated</b>
<span class="sf-toolbar-status sf-toolbar-status-{{ is_authenticated ? 'green' : 'red' }}">{{ is_authenticated ? 'Yes' : 'No' }}</span>
</div>

{% if collector.firewall %}
<div class="sf-toolbar-info-piece">
<b>Firewall name</b>
<span>{{ collector.firewall.name }}</span>
</div>
{% endif %}
<div class="sf-toolbar-info-piece">
<b>Token class</b>
<span>{{ collector.tokenClass|abbr_class }}</span>
</div>
{% else %}
<div class="sf-toolbar-info-piece">
<b>Authenticated</b>
<span class="sf-toolbar-status sf-toolbar-status-red">No</span>
</div>
{% endif %}

{% if collector.firewall %}
<div class="sf-toolbar-info-piece">
<b>Firewall name</b>
<span>{{ collector.firewall.name }}</span>
</div>
{% endif %}

{% if collector.token and collector.logoutUrl %}
{% if collector.token and collector.logoutUrl %}
<div class="sf-toolbar-info-piece">
<b>Actions</b>
<span>
<a href="{{ collector.logoutUrl }}">Logout</a>
{% if collector.impersonated and collector.impersonationExitPath %}
| <a href="{{ collector.impersonationExitPath }}">Exit impersonation</a>
{% endif %}
</span>
</div>
{% endif %}
{% else %}
<div class="sf-toolbar-info-piece">
<b>Actions</b>
<span><a href="{{ collector.logoutUrl }}">Logout</a></span>
<span>The security is disabled.</span>
</div>
{% endif %}
{% else %}
<div class="sf-toolbar-info-piece">
<span>The security is disabled.</span>
</div>
{% endif %}
</div>
{% endset %}

{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url, status: color_code }) }}
Expand Down
13 changes: 12 additions & 1 deletion 13 src/Symfony/Bundle/SecurityBundle/Security/FirewallConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ final class FirewallConfig
private $accessDeniedHandler;
private $accessDeniedUrl;
private $listeners;
private $switchUser;

/**
* @param string $name
Expand All @@ -40,8 +41,9 @@ final class FirewallConfig
* @param string|null $accessDeniedHandler
* @param string|null $accessDeniedUrl
* @param string[] $listeners
* @param array|null $switchUser
*/
public function __construct($name, $userChecker, $requestMatcher = null, $securityEnabled = true, $stateless = false, $provider = null, $context = null, $entryPoint = null, $accessDeniedHandler = null, $accessDeniedUrl = null, $listeners = array())
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)
{
$this->name = $name;
$this->userChecker = $userChecker;
Expand All @@ -54,6 +56,7 @@ public function __construct($name, $userChecker, $requestMatcher = null, $securi
$this->accessDeniedHandler = $accessDeniedHandler;
$this->accessDeniedUrl = $accessDeniedUrl;
$this->listeners = $listeners;
$this->switchUser = $switchUser;
}

public function getName()
Expand Down Expand Up @@ -140,4 +143,12 @@ public function getListeners()
{
return $this->listeners;
}

/**
* @return array|null The switch_user parameters if configured, null otherwise
*/
public function getSwitchUser()
{
return $this->switchUser;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Security\Core\Role\RoleHierarchy;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
use Symfony\Component\Security\Core\Role\SwitchUserRole;
use Symfony\Component\Security\Http\FirewallMapInterface;
use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator;

Expand All @@ -37,6 +38,9 @@ public function testCollectWhenSecurityIsDisabled()
$this->assertSame('security', $collector->getName());
$this->assertFalse($collector->isEnabled());
$this->assertFalse($collector->isAuthenticated());
$this->assertFalse($collector->isImpersonated());
$this->assertNull($collector->getImpersonatorUser());
$this->assertNull($collector->getImpersonationExitPath());
$this->assertNull($collector->getTokenClass());
$this->assertFalse($collector->supportsRoleHierarchy());
$this->assertCount(0, $collector->getRoles());
Expand All @@ -53,6 +57,9 @@ public function testCollectWhenAuthenticationTokenIsNull()

$this->assertTrue($collector->isEnabled());
$this->assertFalse($collector->isAuthenticated());
$this->assertFalse($collector->isImpersonated());
$this->assertNull($collector->getImpersonatorUser());
$this->assertNull($collector->getImpersonationExitPath());
$this->assertNull($collector->getTokenClass());
$this->assertTrue($collector->supportsRoleHierarchy());
$this->assertCount(0, $collector->getRoles());
Expand All @@ -73,13 +80,43 @@ public function testCollectAuthenticationTokenAndRoles(array $roles, array $norm

$this->assertTrue($collector->isEnabled());
$this->assertTrue($collector->isAuthenticated());
$this->assertFalse($collector->isImpersonated());
$this->assertNull($collector->getImpersonatorUser());
$this->assertNull($collector->getImpersonationExitPath());
$this->assertSame('Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken', $collector->getTokenClass()->getValue());
$this->assertTrue($collector->supportsRoleHierarchy());
$this->assertSame($normalizedRoles, $collector->getRoles()->getValue(true));
$this->assertSame($inheritedRoles, $collector->getInheritedRoles()->getValue(true));
$this->assertSame('hhamon', $collector->getUser());
}

public function testCollectImpersonatedToken()
{
$adminToken = new UsernamePasswordToken('yceruto', 'P4$$w0rD', 'provider', array('ROLE_ADMIN'));

$userRoles = array(
'ROLE_USER',
new SwitchUserRole('ROLE_PREVIOUS_ADMIN', $adminToken),
);

$tokenStorage = new TokenStorage();
$tokenStorage->setToken(new UsernamePasswordToken('hhamon', 'P4$$w0rD', 'provider', $userRoles));

$collector = new SecurityDataCollector($tokenStorage, $this->getRoleHierarchy());
$collector->collect($this->getRequest(), $this->getResponse());
$collector->lateCollect();

$this->assertTrue($collector->isEnabled());
$this->assertTrue($collector->isAuthenticated());
$this->assertTrue($collector->isImpersonated());
$this->assertSame('yceruto', $collector->getImpersonatorUser());
$this->assertSame('Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken', $collector->getTokenClass()->getValue());
$this->assertTrue($collector->supportsRoleHierarchy());
$this->assertSame(array('ROLE_USER', 'ROLE_PREVIOUS_ADMIN'), $collector->getRoles()->getValue(true));
$this->assertSame(array(), $collector->getInheritedRoles()->getValue(true));
$this->assertSame('hhamon', $collector->getUser());
}

public function testGetFirewall()
{
$firewallConfig = new FirewallConfig('dummy', 'security.request_matcher.dummy', 'security.user_checker.dummy');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ public function testFirewalls()
'remember_me',
'anonymous',
),
array(
'parameter' => '_switch_user',
'role' => 'ROLE_ALLOWED_TO_SWITCH',
),
),
array(
'host',
Expand All @@ -123,6 +127,7 @@ public function testFirewalls()
'http_basic',
'anonymous',
),
null,
),
array(
'with_user_checker',
Expand All @@ -139,6 +144,7 @@ public function testFirewalls()
'http_basic',
'anonymous',
),
null,
),
), $configs);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

namespace Symfony\Bundle\SecurityBundle\Tests\Functional;

use Symfony\Component\Security\Http\Firewall\SwitchUserListener;

class SwitchUserTest extends WebTestCase
{
/**
Expand Down Expand Up @@ -42,7 +44,7 @@ public function testSwitchedUserExit()
$client = $this->createAuthenticatedClient('user_can_switch');

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

$this->assertEquals(200, $client->getResponse()->getStatusCode());
$this->assertEquals('user_can_switch', $client->getProfile()->getCollector('security')->getUser());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public function testGetters()
'access_denied_url' => 'foo_access_denied_url',
'access_denied_handler' => 'foo_access_denied_handler',
'user_checker' => 'foo_user_checker',
'switch_user' => array('provider' => null, 'parameter' => '_switch_user', 'role' => 'ROLE_ALLOWED_TO_SWITCH'),
);

$config = new FirewallConfig(
Expand All @@ -42,7 +43,8 @@ public function testGetters()
$options['entry_point'],
$options['access_denied_handler'],
$options['access_denied_url'],
$listeners
$listeners,
$options['switch_user']
);

$this->assertSame('foo_firewall', $config->getName());
Expand All @@ -57,5 +59,6 @@ public function testGetters()
$this->assertSame($options['user_checker'], $config->getUserChecker());
$this->assertTrue($config->allowsAnonymous());
$this->assertSame($listeners, $config->getListeners());
$this->assertSame($options['switch_user'], $config->getSwitchUser());
}
}
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.