Skip to content

Commit 18604f6

Browse files
dFayetDamien Fayet
authored andcommitted
Create impersonation_exit_path() and *_url() functions
1 parent 4ab612c commit 18604f6

File tree

8 files changed

+138
-8
lines changed

8 files changed

+138
-8
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
5.2.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
* added the `workflow_transition()` function to easily retrieve a specific transition object
89
* added support for translating `Translatable` objects
910
* added the `t()` function to easily create `Translatable` objects

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\Security\Acl\Voter\FieldVote;
1515
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
1616
use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
17+
use Symfony\Component\Security\Http\Impersonate\ImpersonateUrlGenerator;
1718
use Twig\Extension\AbstractExtension;
1819
use Twig\TwigFunction;
1920

@@ -26,9 +27,12 @@ final class SecurityExtension extends AbstractExtension
2627
{
2728
private $securityChecker;
2829

29-
public function __construct(AuthorizationCheckerInterface $securityChecker = null)
30+
private $impersonateUrlGenerator;
31+
32+
public function __construct(AuthorizationCheckerInterface $securityChecker = null, ImpersonateUrlGenerator $impersonateUrlGenerator = null)
3033
{
3134
$this->securityChecker = $securityChecker;
35+
$this->impersonateUrlGenerator = $impersonateUrlGenerator;
3236
}
3337

3438
/**
@@ -51,13 +55,33 @@ public function isGranted($role, $object = null, string $field = null): bool
5155
}
5256
}
5357

58+
public function getImpersonateExitUrl(string $exitTo = null): string
59+
{
60+
if (null === $this->impersonateUrlGenerator) {
61+
return '';
62+
}
63+
64+
return $this->impersonateUrlGenerator->generateExitUrl($exitTo);
65+
}
66+
67+
public function getImpersonateExitPath(string $exitTo = null): string
68+
{
69+
if (null === $this->impersonateUrlGenerator) {
70+
return '';
71+
}
72+
73+
return $this->impersonateUrlGenerator->generateExitPath($exitTo);
74+
}
75+
5476
/**
5577
* {@inheritdoc}
5678
*/
5779
public function getFunctions(): array
5880
{
5981
return [
6082
new TwigFunction('is_granted', [$this, 'isGranted']),
83+
new TwigFunction('impersonation_exit_url', [$this, 'getImpersonateExitUrl']),
84+
new TwigFunction('impersonation_exit_path', [$this, 'getImpersonateExitPath']),
6185
];
6286
}
6387
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
use Symfony\Component\Security\Http\Controller\UserValueResolver;
4949
use Symfony\Component\Security\Http\Firewall;
5050
use Symfony\Component\Security\Http\HttpUtils;
51+
use Symfony\Component\Security\Http\Impersonate\ImpersonateUrlGenerator;
5152
use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator;
5253
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy;
5354
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
@@ -160,6 +161,13 @@
160161
])
161162
->tag('security.voter', ['priority' => 245])
162163

164+
->set('security.impersonate_url_generator', ImpersonateUrlGenerator::class)
165+
->args([
166+
service('request_stack'),
167+
service('security.firewall.map'),
168+
service('security.token_storage'),
169+
])
170+
163171
// Firewall related services
164172
->set('security.firewall', FirewallListener::class)
165173
->args([

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
->set('twig.extension.security', SecurityExtension::class)
2626
->args([
2727
service('security.authorization_checker')->ignoreOnInvalid(),
28+
service('security.impersonate_url_generator')->ignoreOnInvalid(),
2829
])
2930
->tag('twig.extension')
3031
;

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,39 +19,47 @@
1919
class SwitchUserToken extends UsernamePasswordToken
2020
{
2121
private $originalToken;
22+
private $originatedFromUri;
2223

2324
/**
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
25+
* @param string|object $user The username (like a nickname, email address, etc.), or a UserInterface instance or an object implementing a __toString method
26+
* @param mixed $credentials This usually is the password of the user
27+
* @param string|null $originatedFromUri The URI where was the user at the switch
2628
*
2729
* @throws \InvalidArgumentException
2830
*/
29-
public function __construct($user, $credentials, string $firewallName, array $roles, TokenInterface $originalToken)
31+
public function __construct($user, $credentials, string $firewallName, array $roles, TokenInterface $originalToken, string $originatedFromUri = null)
3032
{
3133
parent::__construct($user, $credentials, $firewallName, $roles);
3234

3335
$this->originalToken = $originalToken;
36+
$this->originatedFromUri = $originatedFromUri;
3437
}
3538

3639
public function getOriginalToken(): TokenInterface
3740
{
3841
return $this->originalToken;
3942
}
4043

44+
public function getOriginatedFromUri(): ?string
45+
{
46+
return $this->originatedFromUri;
47+
}
48+
4149
/**
4250
* {@inheritdoc}
4351
*/
4452
public function __serialize(): array
4553
{
46-
return [$this->originalToken, parent::__serialize()];
54+
return [$this->originalToken, $this->originatedFromUri, parent::__serialize()];
4755
}
4856

4957
/**
5058
* {@inheritdoc}
5159
*/
5260
public function __unserialize(array $data): void
5361
{
54-
[$this->originalToken, $parentData] = $data;
62+
[$this->originalToken, $this->originatedFromUri, $parentData] = $data;
5563
$parentData = \is_array($parentData) ? $parentData : unserialize($parentData);
5664
parent::__unserialize($parentData);
5765
}

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

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

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

@@ -30,6 +30,7 @@ public function testSerialize()
3030
$this->assertSame('bar', $unserializedToken->getCredentials());
3131
$this->assertSame('provider-key', $unserializedToken->getFirewallName());
3232
$this->assertEquals(['ROLE_USER'], $unserializedToken->getRoleNames());
33+
$this->assertSame('https://symfony.com/blog', $unserializedToken->getImpersonatedFromUri());
3334

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

@@ -73,4 +74,14 @@ public function getSalt()
7374
$token->setUser($impersonated);
7475
$this->assertTrue($token->isAuthenticated());
7576
}
77+
78+
public function testSerializeNullImpersonateUrl()
79+
{
80+
$originalToken = new UsernamePasswordToken('user', 'foo', 'provider-key', ['ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH']);
81+
$token = new SwitchUserToken('admin', 'bar', 'provider-key', ['ROLE_USER'], $originalToken);
82+
83+
$unserializedToken = unserialize(serialize($token));
84+
85+
$this->assertNull($unserializedToken->getImpersonatedFromUri());
86+
}
7687
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,8 @@ private function attemptSwitchUser(Request $request, string $username): ?TokenIn
181181

182182
$roles = $user->getRoles();
183183
$roles[] = 'ROLE_PREVIOUS_ADMIN';
184-
$token = new SwitchUserToken($user, $user->getPassword(), $this->firewallName, $roles, $token);
184+
$originatedFromUri = str_replace('/&', '/?', preg_replace('#[&?]'.$this->usernameParameter.'=[^&]*#', '', $request->getRequestUri()));
185+
$token = new SwitchUserToken($user, $user->getPassword(), $this->firewallName, $roles, $token, $originatedFromUri);
185186

186187
if (null !== $this->dispatcher) {
187188
$switchEvent = new SwitchUserEvent($request, $token->getUser(), $token);
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
17+
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
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+
private $tokenStorage;
30+
private $firewallMap;
31+
32+
public function __construct(RequestStack $requestStack, FirewallMap $firewallMap, TokenStorageInterface $tokenStorage)
33+
{
34+
$this->requestStack = $requestStack;
35+
$this->tokenStorage = $tokenStorage;
36+
$this->firewallMap = $firewallMap;
37+
}
38+
39+
public function generateExitPath(string $targetUri = null): string
40+
{
41+
return $this->buildExitPath($targetUri);
42+
}
43+
44+
public function generateExitUrl(string $targetUri = null): string
45+
{
46+
if (null === $request = $this->requestStack->getCurrentRequest()) {
47+
return '';
48+
}
49+
50+
return $request->getUriForPath($this->buildExitPath($targetUri));
51+
}
52+
53+
private function isImpersonatedUser(): bool
54+
{
55+
return $this->tokenStorage->getToken() instanceof SwitchUserToken;
56+
}
57+
58+
private function buildExitPath(string $targetUri = null): string
59+
{
60+
if (null === ($request = $this->requestStack->getCurrentRequest()) || !$this->isImpersonatedUser()) {
61+
return '';
62+
}
63+
64+
if (null === $switchUserConfig = $this->firewallMap->getFirewallConfig($request)->getSwitchUser()) {
65+
throw new \LogicException('Unable to generate the impersonate exit URL without a firewall configured for the user switch.');
66+
}
67+
68+
if (null === $targetUri) {
69+
$targetUri = $request->getRequestUri();
70+
}
71+
72+
$targetUri .= (parse_url($targetUri, \PHP_URL_QUERY) ? '&' : '?').http_build_query([$switchUserConfig['parameter'] => SwitchUserListener::EXIT_VALUE]);
73+
74+
return $targetUri;
75+
}
76+
}

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