Skip to content

Commit e381eef

Browse files
committed
feature #22048 [Security] deprecate the Role and SwitchUserRole classes (xabbuh)
This PR was merged into the 4.3-dev branch. Discussion ---------- [Security] deprecate the Role and SwitchUserRole classes | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | no | BC breaks? | no | Deprecations? | yes | Tests pass? | yes | Fixed tickets | #20824 | License | MIT | Doc PR | symfony/symfony-docs#11047 In #20801, we deprecated the `RoleInterface`. The next logical step would be to also deprecate the `Role` class. However, we currently have the `SwitchUserRole` class (a sub-class of `Role`) that acts as an indicator to check whether or not the authenticated user switched to another user. This PR proposes an alternative solution to the usage of the special `SwitchUserRole` class by storing the original token inside the `UsernamePasswordToken`. This PR is not complete, but rather acts as a proof of concept of how we could get rid of the `Role` and the `SwitchUserRole` classes. Please share your opinions whether you think this is a valid approach and I will be happy to finalise the PR. Commits ------- d7aaa615b9 deprecate the Role and SwitchUserRole classes
2 parents 61c00f7 + d539927 commit e381eef

32 files changed

+493
-125
lines changed

Authentication/Provider/UserAuthenticationProvider.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\Security\Core\Authentication\Provider;
1313

14+
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
1415
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
1516
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
1617
use Symfony\Component\Security\Core\Exception\AuthenticationException;
@@ -87,7 +88,12 @@ public function authenticate(TokenInterface $token)
8788
throw $e;
8889
}
8990

90-
$authenticatedToken = new UsernamePasswordToken($user, $token->getCredentials(), $this->providerKey, $this->getRoles($user, $token));
91+
if ($token instanceof SwitchUserToken) {
92+
$authenticatedToken = new SwitchUserToken($user, $token->getCredentials(), $this->providerKey, $this->getRoles($user, $token), $token->getOriginalToken());
93+
} else {
94+
$authenticatedToken = new UsernamePasswordToken($user, $token->getCredentials(), $this->providerKey, $this->getRoles($user, $token));
95+
}
96+
9197
$authenticatedToken->setAttributes($token->getAttributes());
9298

9399
return $authenticatedToken;
@@ -110,7 +116,7 @@ private function getRoles(UserInterface $user, TokenInterface $token)
110116
{
111117
$roles = $user->getRoles();
112118

113-
foreach ($token->getRoles() as $role) {
119+
foreach ($token->getRoles(false) as $role) {
114120
if ($role instanceof SwitchUserRole) {
115121
$roles[] = $role;
116122

Authentication/Token/AbstractToken.php

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,32 +26,43 @@ abstract class AbstractToken implements TokenInterface
2626
{
2727
private $user;
2828
private $roles = [];
29+
private $roleNames = [];
2930
private $authenticated = false;
3031
private $attributes = [];
3132

3233
/**
33-
* @param (Role|string)[] $roles An array of roles
34+
* @param string[] $roles An array of roles
3435
*
3536
* @throws \InvalidArgumentException
3637
*/
3738
public function __construct(array $roles = [])
3839
{
3940
foreach ($roles as $role) {
4041
if (\is_string($role)) {
41-
$role = new Role($role);
42+
$role = new Role($role, false);
4243
} elseif (!$role instanceof Role) {
4344
throw new \InvalidArgumentException(sprintf('$roles must be an array of strings, or Role instances, but got %s.', \gettype($role)));
4445
}
4546

4647
$this->roles[] = $role;
48+
$this->roleNames[] = (string) $role;
4749
}
4850
}
4951

52+
public function getRoleNames(): array
53+
{
54+
return $this->roleNames;
55+
}
56+
5057
/**
5158
* {@inheritdoc}
5259
*/
5360
public function getRoles()
5461
{
62+
if (0 === \func_num_args() || func_get_arg(0)) {
63+
@trigger_error(sprintf('The %s() method is deprecated since Symfony 4.3. Use the getRoleNames() method instead.', __METHOD__), E_USER_DEPRECATED);
64+
}
65+
5566
return $this->roles;
5667
}
5768

@@ -172,7 +183,7 @@ public function unserialize($serialized)
172183
*/
173184
protected function getState(): array
174185
{
175-
return [$this->user, $this->authenticated, $this->roles, $this->attributes];
186+
return [$this->user, $this->authenticated, $this->roles, $this->attributes, $this->roleNames];
176187
}
177188

178189
/**
@@ -193,7 +204,7 @@ protected function getState(): array
193204
*/
194205
protected function setState(array $data)
195206
{
196-
[$this->user, $this->authenticated, $this->roles, $this->attributes] = $data;
207+
[$this->user, $this->authenticated, $this->roles, $this->attributes, $this->roleNames] = $data;
197208
}
198209

199210
/**

Authentication/Token/AnonymousToken.php

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

1212
namespace Symfony\Component\Security\Core\Authentication\Token;
1313

14-
use Symfony\Component\Security\Core\Role\Role;
15-
1614
/**
1715
* AnonymousToken represents an anonymous token.
1816
*
@@ -25,7 +23,7 @@ class AnonymousToken extends AbstractToken
2523
/**
2624
* @param string $secret A secret used to make sure the token is created by the app and not by a malicious client
2725
* @param string|object $user The user can be a UserInterface instance, or an object implementing a __toString method or the username as a regular string
28-
* @param Role[] $roles An array of roles
26+
* @param string[] $roles An array of roles
2927
*/
3028
public function __construct(string $secret, $user, array $roles = [])
3129
{

Authentication/Token/PreAuthenticatedToken.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111

1212
namespace Symfony\Component\Security\Core\Authentication\Token;
1313

14-
use Symfony\Component\Security\Core\Role\Role;
15-
1614
/**
1715
* PreAuthenticatedToken implements a pre-authenticated token.
1816
*
@@ -24,10 +22,10 @@ class PreAuthenticatedToken extends AbstractToken
2422
private $providerKey;
2523

2624
/**
27-
* @param string|object $user The user can be a UserInterface instance, or an object implementing a __toString method or the username as a regular string
28-
* @param mixed $credentials The user credentials
29-
* @param string $providerKey The provider key
30-
* @param (Role|string)[] $roles An array of roles
25+
* @param string|object $user The user can be a UserInterface instance, or an object implementing a __toString method or the username as a regular string
26+
* @param mixed $credentials The user credentials
27+
* @param string $providerKey The provider key
28+
* @param string[] $roles An array of roles
3129
*/
3230
public function __construct($user, $credentials, string $providerKey, array $roles = [])
3331
{

Authentication/Token/Storage/TokenStorage.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ public function getToken()
3939
*/
4040
public function setToken(TokenInterface $token = null)
4141
{
42+
if (null !== $token && !method_exists($token, 'getRoleNames')) {
43+
@trigger_error(sprintf('Not implementing the getRoleNames() method in %s which implements %s is deprecated since Symfony 4.3.', \get_class($token), TokenInterface::class), E_USER_DEPRECATED);
44+
}
45+
4246
$this->token = $token;
4347
}
4448

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Core\Authentication\Token;
13+
14+
/**
15+
* Token representing a user who temporarily impersonates another one.
16+
*
17+
* @author Christian Flothmann <[email protected]>
18+
*/
19+
class SwitchUserToken extends UsernamePasswordToken
20+
{
21+
private $originalToken;
22+
23+
/**
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
28+
* @param TokenInterface $originalToken The token of the user who switched to the current user
29+
*
30+
* @throws \InvalidArgumentException
31+
*/
32+
public function __construct($user, $credentials, string $providerKey, array $roles = [], TokenInterface $originalToken)
33+
{
34+
parent::__construct($user, $credentials, $providerKey, $roles);
35+
36+
$this->originalToken = $originalToken;
37+
}
38+
39+
public function getOriginalToken(): TokenInterface
40+
{
41+
return $this->originalToken;
42+
}
43+
44+
/**
45+
* {@inheritdoc}
46+
*/
47+
protected function getState(): array
48+
{
49+
return [$this->originalToken, parent::getState()];
50+
}
51+
52+
/**
53+
* {@inheritdoc}
54+
*/
55+
protected function setState(array $data)
56+
{
57+
[$this->originalToken, $parentData] = $data;
58+
parent::setState($parentData);
59+
}
60+
}

Authentication/Token/TokenInterface.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
*
1919
* @author Fabien Potencier <[email protected]>
2020
* @author Johannes M. Schmitt <[email protected]>
21+
*
22+
* @method string[] getRoleNames() The associated roles - not implementing it is deprecated since Symfony 4.3
2123
*/
2224
interface TokenInterface extends \Serializable
2325
{
@@ -34,6 +36,8 @@ public function __toString();
3436
* Returns the user roles.
3537
*
3638
* @return Role[] An array of Role instances
39+
*
40+
* @deprecated since Symfony 4.3, use the getRoleNames() method instead
3741
*/
3842
public function getRoles();
3943

Authentication/Token/UsernamePasswordToken.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111

1212
namespace Symfony\Component\Security\Core\Authentication\Token;
1313

14-
use Symfony\Component\Security\Core\Role\Role;
15-
1614
/**
1715
* UsernamePasswordToken implements a username and password token.
1816
*
@@ -24,10 +22,10 @@ class UsernamePasswordToken extends AbstractToken
2422
private $providerKey;
2523

2624
/**
27-
* @param string|object $user The username (like a nickname, email address, etc.), or a UserInterface instance or an object implementing a __toString method
28-
* @param mixed $credentials This usually is the password of the user
29-
* @param string $providerKey The provider key
30-
* @param (Role|string)[] $roles An array of roles
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 $providerKey The provider key
28+
* @param string[] $roles An array of roles
3129
*
3230
* @throws \InvalidArgumentException
3331
*/

Authorization/Voter/ExpressionVoter.php

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
1919
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
2020
use Symfony\Component\Security\Core\Authorization\ExpressionLanguage;
21+
use Symfony\Component\Security\Core\Role\Role;
22+
use Symfony\Component\Security\Core\Role\RoleHierarchy;
2123
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
2224

2325
/**
@@ -90,18 +92,34 @@ public function vote(TokenInterface $token, $subject, array $attributes)
9092

9193
private function getVariables(TokenInterface $token, $subject)
9294
{
93-
if (null !== $this->roleHierarchy) {
94-
$roles = $this->roleHierarchy->getReachableRoles($token->getRoles());
95+
if ($this->roleHierarchy instanceof RoleHierarchy) {
96+
if (method_exists($token, 'getRoleNames')) {
97+
$rolesFromToken = $token->getRoleNames();
98+
} else {
99+
@trigger_error(sprintf('Not implementing the getRoleNames() method in %s which implements %s is deprecated since Symfony 4.3.', \get_class($token), TokenInterface::class), E_USER_DEPRECATED);
100+
101+
$rolesFromToken = $token->getRoles(false);
102+
}
103+
104+
$roles = $this->roleHierarchy->getReachableRoleNames($rolesFromToken);
105+
} elseif (null !== $this->roleHierarchy) {
106+
$roles = $this->roleHierarchy->getReachableRoles($token->getRoles(false));
107+
} elseif (method_exists($token, 'getRoleNames')) {
108+
$roles = $token->getRoleNames();
95109
} else {
96-
$roles = $token->getRoles();
110+
@trigger_error(sprintf('Not implementing the getRoleNames() method in %s which implements %s is deprecated since Symfony 4.3.', \get_class($token), TokenInterface::class), E_USER_DEPRECATED);
111+
112+
$roles = $token->getRoles(false);
97113
}
98114

115+
$roles = array_map(function ($role) { return $role instanceof Role ? $role->getRole() : $role; }, $roles);
116+
99117
$variables = [
100118
'token' => $token,
101119
'user' => $token->getUser(),
102120
'object' => $subject,
103121
'subject' => $subject,
104-
'roles' => array_map(function ($role) { return $role->getRole(); }, $roles),
122+
'roles' => $roles,
105123
'trust_resolver' => $this->trustResolver,
106124
'auth_checker' => $this->authChecker,
107125
];

Authorization/Voter/RoleHierarchyVoter.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Security\Core\Authorization\Voter;
1313

1414
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
15+
use Symfony\Component\Security\Core\Role\RoleHierarchy;
1516
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
1617

1718
/**
@@ -26,6 +27,10 @@ class RoleHierarchyVoter extends RoleVoter
2627

2728
public function __construct(RoleHierarchyInterface $roleHierarchy, string $prefix = 'ROLE_')
2829
{
30+
if (!$roleHierarchy instanceof RoleHierarchy) {
31+
@trigger_error(sprintf('Passing a role hierarchy to "%s" that is not an instance of "%s" is deprecated since Symfony 4.3 and support for it will be dropped in Symfony 5.0 ("%s" given).', __CLASS__, RoleHierarchy::class, \get_class($roleHierarchy)), E_USER_DEPRECATED);
32+
}
33+
2934
$this->roleHierarchy = $roleHierarchy;
3035

3136
parent::__construct($prefix);
@@ -36,6 +41,18 @@ public function __construct(RoleHierarchyInterface $roleHierarchy, string $prefi
3641
*/
3742
protected function extractRoles(TokenInterface $token)
3843
{
39-
return $this->roleHierarchy->getReachableRoles($token->getRoles());
44+
if ($this->roleHierarchy instanceof RoleHierarchy) {
45+
if (method_exists($token, 'getRoleNames')) {
46+
$roles = $token->getRoleNames();
47+
} else {
48+
@trigger_error(sprintf('Not implementing the getRoleNames() method in %s which implements %s is deprecated since Symfony 4.3.', \get_class($token), TokenInterface::class), E_USER_DEPRECATED);
49+
50+
$roles = $token->getRoles(false);
51+
}
52+
53+
return $this->roleHierarchy->getReachableRoleNames($roles);
54+
}
55+
56+
return $this->roleHierarchy->getReachableRoles($token->getRoles(false));
4057
}
4158
}

Authorization/Voter/RoleVoter.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public function vote(TokenInterface $token, $subject, array $attributes)
4747

4848
$result = VoterInterface::ACCESS_DENIED;
4949
foreach ($roles as $role) {
50-
if ($attribute === $role->getRole()) {
50+
if ($attribute === $role) {
5151
return VoterInterface::ACCESS_GRANTED;
5252
}
5353
}
@@ -58,6 +58,12 @@ public function vote(TokenInterface $token, $subject, array $attributes)
5858

5959
protected function extractRoles(TokenInterface $token)
6060
{
61-
return $token->getRoles();
61+
if (method_exists($token, 'getRoleNames')) {
62+
return $token->getRoleNames();
63+
}
64+
65+
@trigger_error(sprintf('Not implementing the getRoleNames() method in %s which implements %s is deprecated since Symfony 4.3.', \get_class($token), TokenInterface::class), E_USER_DEPRECATED);
66+
67+
return array_map(function (Role $role) { return $role->getRole(); }, $token->getRoles(false));
6268
}
6369
}

Role/Role.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,19 @@
1515
* Role is a simple implementation representing a role identified by a string.
1616
*
1717
* @author Fabien Potencier <[email protected]>
18+
*
19+
* @deprecated since Symfony 4.3, to be removed in 5.0. Use strings as roles instead.
1820
*/
1921
class Role
2022
{
2123
private $role;
2224

2325
public function __construct(string $role)
2426
{
27+
if (\func_num_args() < 2 || func_get_arg(1)) {
28+
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3 and will be removed in 5.0. Use strings as roles instead.', __CLASS__), E_USER_DEPRECATED);
29+
}
30+
2531
$this->role = $role;
2632
}
2733

@@ -34,4 +40,9 @@ public function getRole()
3440
{
3541
return $this->role;
3642
}
43+
44+
public function __toString(): string
45+
{
46+
return $this->role;
47+
}
3748
}

0 commit comments

Comments
 (0)