Skip to content

Commit e0c8ebb

Browse files
author
dFayet
committed
Create impersonation_exit_path() and *_url() functions
1 parent 24858f2 commit e0c8ebb

File tree

9 files changed

+146
-11
lines changed

9 files changed

+146
-11
lines changed

src/Symfony/Bridge/Twig/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
4.4.0
55
-----
66

7+
* added the `impersonation_exit_url()` and `impersonation_exit_path()` functions. They return a URL that allows to switch back to the original user.
78
* deprecated to pass `$rootDir` and `$fileLinkFormatter` as 5th and 6th argument respectively to the
89
`DebugCommand::__construct()` method, swap the variables position.
910

src/Symfony/Bridge/Twig/Extension/SecurityExtension.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111

1212
namespace Symfony\Bridge\Twig\Extension;
1313

14+
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
1415
use Symfony\Component\Security\Acl\Voter\FieldVote;
1516
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
1617
use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
18+
use Symfony\Component\Security\Http\Impersonate\ImpersonateUrlGenerator;
1719
use Twig\Extension\AbstractExtension;
1820
use Twig\TwigFunction;
1921

@@ -26,9 +28,12 @@ class SecurityExtension extends AbstractExtension
2628
{
2729
private $securityChecker;
2830

29-
public function __construct(AuthorizationCheckerInterface $securityChecker = null)
31+
private $impersonateUrlGenerator;
32+
33+
public function __construct(AuthorizationCheckerInterface $securityChecker = null, ImpersonateUrlGenerator $impersonateUrlGenerator = null)
3034
{
3135
$this->securityChecker = $securityChecker;
36+
$this->impersonateUrlGenerator = $impersonateUrlGenerator;
3237
}
3338

3439
public function isGranted($role, $object = null, $field = null)
@@ -48,13 +53,33 @@ public function isGranted($role, $object = null, $field = null)
4853
}
4954
}
5055

56+
public function getImpersonateExitUrl(string $exitTo = null)
57+
{
58+
if (null === $this->impersonateUrlGenerator) {
59+
return '';
60+
}
61+
62+
return $this->impersonateUrlGenerator->generateImpersonateExitUrl($exitTo);
63+
}
64+
65+
public function getImpersonateExitPath(string $exitTo = null)
66+
{
67+
if (null === $this->impersonateUrlGenerator) {
68+
return '';
69+
}
70+
71+
return $this->impersonateUrlGenerator->generateImpersonateExitUrl($exitTo, UrlGeneratorInterface::ABSOLUTE_PATH);
72+
}
73+
5174
/**
5275
* {@inheritdoc}
5376
*/
5477
public function getFunctions()
5578
{
5679
return [
5780
new TwigFunction('is_granted', [$this, 'isGranted']),
81+
new TwigFunction('impersonation_exit_url', [$this, 'getImpersonateExitUrl']),
82+
new TwigFunction('impersonation_exit_path', [$this, 'getImpersonateExitPath']),
5883
];
5984
}
6085

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,12 @@
159159
<argument /> <!-- switch_user -->
160160
</service>
161161

162+
<service id="security.impersonate_url_generator" class="Symfony\Component\Security\Http\Impersonate\ImpersonateUrlGenerator">
163+
<argument type="service" id="request_stack" />
164+
<argument type="service" id="security.firewall.map" />
165+
<argument type="service" id="security.token_storage" />
166+
</service>
167+
162168
<service id="security.logout_url_generator" class="Symfony\Component\Security\Http\Logout\LogoutUrlGenerator">
163169
<argument type="service" id="request_stack" on-invalid="null" />
164170
<argument type="service" id="router" on-invalid="null" />

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<service id="twig.extension.security" class="Symfony\Bridge\Twig\Extension\SecurityExtension">
1616
<tag name="twig.extension" />
1717
<argument type="service" id="security.authorization_checker" on-invalid="ignore" />
18+
<argument type="service" id="security.impersonate_url_generator" on-invalid="ignore" />
1819
</service>
1920
</services>
2021
</container>

src/Symfony/Bundle/SecurityBundle/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"symfony/security-core": "^4.3",
2525
"symfony/security-csrf": "^4.2|^5.0",
2626
"symfony/security-guard": "^4.2|^5.0",
27-
"symfony/security-http": "^4.3"
27+
"symfony/security-http": "^4.4"
2828
},
2929
"require-dev": {
3030
"symfony/asset": "^3.4|^4.0|^5.0",

src/Symfony/Component/Security/Core/Authentication/Token/SwitchUserToken.php

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,40 +20,49 @@ class SwitchUserToken extends UsernamePasswordToken
2020
{
2121
private $originalToken;
2222

23+
private $impersonatedFromUri;
24+
2325
/**
24-
* @param string|object $user The username (like a nickname, email address, etc.), or a UserInterface instance or an object implementing a __toString method
25-
* @param mixed $credentials This usually is the password of the user
26-
* @param string $providerKey The provider key
27-
* @param string[] $roles An array of roles
26+
* @param string|object $user The username (like a nickname, email address, etc.), or a UserInterface instance or an object implementing a __toString method
27+
* @param mixed $credentials This usually is the password of the user
28+
* @param string $providerKey The provider key
29+
* @param string[] $roles An array of roles
30+
* @param string|null $impersonatedFromUri The Url where was the user at the switch
2831
*
2932
* @throws \InvalidArgumentException
3033
*/
31-
public function __construct($user, $credentials, string $providerKey, array $roles = [], TokenInterface $originalToken)
34+
public function __construct($user, $credentials, string $providerKey, array $roles = [], TokenInterface $originalToken, string $impersonatedFromUri = null)
3235
{
3336
parent::__construct($user, $credentials, $providerKey, $roles);
3437

3538
$this->originalToken = $originalToken;
39+
$this->impersonatedFromUri = $impersonatedFromUri;
3640
}
3741

3842
public function getOriginalToken(): TokenInterface
3943
{
4044
return $this->originalToken;
4145
}
4246

47+
public function getImpersonatedFromUri(): ?string
48+
{
49+
return $this->impersonatedFromUri;
50+
}
51+
4352
/**
4453
* {@inheritdoc}
4554
*/
4655
public function __serialize(): array
4756
{
48-
return [$this->originalToken, parent::__serialize()];
57+
return [$this->originalToken, $this->impersonatedFromUri, parent::__serialize()];
4958
}
5059

5160
/**
5261
* {@inheritdoc}
5362
*/
5463
public function __unserialize(array $data): void
5564
{
56-
[$this->originalToken, $parentData] = $data;
65+
[$this->originalToken, $this->impersonatedFromUri, $parentData] = $data;
5766
parent::__unserialize($parentData);
5867
}
5968
}

src/Symfony/Component/Security/Core/Tests/Authentication/Token/SwitchUserTokenTest.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class SwitchUserTokenTest extends TestCase
2020
public function testSerialize()
2121
{
2222
$originalToken = new UsernamePasswordToken('user', 'foo', 'provider-key', ['ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH']);
23-
$token = new SwitchUserToken('admin', 'bar', 'provider-key', ['ROLE_USER'], $originalToken);
23+
$token = new SwitchUserToken('admin', 'bar', 'provider-key', ['ROLE_USER'], $originalToken, 'https://symfony.com/blog');
2424

2525
$unserializedToken = unserialize(serialize($token));
2626

@@ -29,6 +29,7 @@ public function testSerialize()
2929
$this->assertSame('bar', $unserializedToken->getCredentials());
3030
$this->assertSame('provider-key', $unserializedToken->getProviderKey());
3131
$this->assertEquals(['ROLE_USER'], $unserializedToken->getRoleNames());
32+
$this->assertSame('https://symfony.com/blog', $unserializedToken->getImpersonatedFromUri());
3233

3334
$unserializedOriginalToken = $unserializedToken->getOriginalToken();
3435

@@ -38,4 +39,14 @@ public function testSerialize()
3839
$this->assertSame('provider-key', $unserializedOriginalToken->getProviderKey());
3940
$this->assertEquals(['ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH'], $unserializedOriginalToken->getRoleNames());
4041
}
42+
43+
public function testSerializeNullImpersonateUrl()
44+
{
45+
$originalToken = new UsernamePasswordToken('user', 'foo', 'provider-key', ['ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH']);
46+
$token = new SwitchUserToken('admin', 'bar', 'provider-key', ['ROLE_USER'], $originalToken);
47+
48+
$unserializedToken = unserialize(serialize($token));
49+
50+
$this->assertNull($unserializedToken->getImpersonatedFromUri());
51+
}
4152
}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,14 @@ private function attemptSwitchUser(Request $request, string $username): ?TokenIn
148148
$roles = $user->getRoles();
149149
$roles[] = new SwitchUserRole('ROLE_PREVIOUS_ADMIN', $this->tokenStorage->getToken(), false);
150150

151-
$token = new SwitchUserToken($user, $user->getPassword(), $this->providerKey, $roles, $token);
151+
$token = new SwitchUserToken(
152+
$user,
153+
$user->getPassword(),
154+
$this->providerKey,
155+
$roles,
156+
$token,
157+
str_replace('/&', '/?', preg_replace('#[&?]'.$this->usernameParameter.'=[^&]*#', '', $request->getRequestUri()))
158+
);
152159

153160
if (null !== $this->dispatcher) {
154161
$switchEvent = new SwitchUserEvent($request, $token->getUser(), $token);
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Http\Impersonate;
13+
14+
use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
15+
use Symfony\Component\HttpFoundation\RequestStack;
16+
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
17+
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
18+
use Symfony\Component\Security\Http\Firewall\SwitchUserListener;
19+
20+
/**
21+
* Provides generator functions for the impersonate url exit.
22+
*
23+
* @author Amrouche Hamza <hamza.simperfit@gmail.com>
24+
* @author Damien Fayet <damienf1521@gmail.com>
25+
*/
26+
class ImpersonateUrlGenerator
27+
{
28+
private $requestStack;
29+
30+
private $tokenStorage;
31+
32+
private $firewallMap;
33+
34+
public function __construct(RequestStack $requestStack, FirewallMap $firewallMap, TokenStorageInterface $tokenStorage)
35+
{
36+
$this->requestStack = $requestStack;
37+
$this->tokenStorage = $tokenStorage;
38+
$this->firewallMap = $firewallMap;
39+
}
40+
41+
public function generateImpersonateExitUrl(string $exitTo = null, $referenceType = UrlGeneratorInterface::ABSOLUTE_URL): string
42+
{
43+
$request = $this->requestStack->getCurrentRequest();
44+
45+
if (!$this->isImpersonatedUser()) {
46+
return '';
47+
}
48+
49+
if (null === $switchUserConfig = $this->firewallMap->getFirewallConfig($request)->getSwitchUser()) {
50+
throw new \LogicException('Unable to generate the impersonate exit URL without a firewall configured for the user switch.');
51+
}
52+
53+
if ('_use_impersonated_from_url' === $exitTo) {
54+
if (null === ($impersonatedFromUri = $this->tokenStorage->getToken()->getImpersonatedFromUri())) {
55+
throw new \LogicException('Unable to generate the impersonate exit URL to the origin, as the origin is unknown.');
56+
}
57+
$exitTo = $impersonatedFromUri;
58+
} elseif (null === $exitTo) {
59+
$exitTo = $request->getRequestUri();
60+
}
61+
62+
$exitTo .= (parse_url($exitTo, PHP_URL_QUERY) ? '&' : '?').http_build_query([$switchUserConfig['parameter'] => SwitchUserListener::EXIT_VALUE]);
63+
64+
return UrlGeneratorInterface::ABSOLUTE_URL === $referenceType ? $request->getUriForPath($exitTo) : $exitTo;
65+
}
66+
67+
private function isImpersonatedUser(): bool
68+
{
69+
if (null === $token = $this->tokenStorage->getToken()) {
70+
return false;
71+
}
72+
73+
return false !== \in_array('ROLE_PREVIOUS_ADMIN', $token->getRoleNames(), true);
74+
}
75+
}

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