From 2d3f2b7a74e70bec23dd0e7ff765698e447a4729 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Tue, 26 Feb 2019 19:23:16 +0100 Subject: [PATCH] undeprecate the RoleHierarchyInterface Instead of deprecating the interface it is sufficient to deprecate its getReachableRoles() method and add a new getReachableRoleNames() method in Symfony 5. --- UPGRADE-4.3.md | 5 ++- UPGRADE-5.0.md | 4 +-- .../DataCollector/SecurityDataCollector.php | 3 +- .../Resources/config/security.xml | 2 -- src/Symfony/Component/Security/CHANGELOG.md | 5 ++- .../Authorization/Voter/ExpressionVoter.php | 32 +++++++++---------- .../Voter/RoleHierarchyVoter.php | 9 +++--- .../Security/Core/Role/RoleHierarchy.php | 15 +++------ .../Core/Role/RoleHierarchyInterface.php | 16 +++------- .../Workflow/EventListener/GuardListener.php | 21 +++++++++--- 10 files changed, 52 insertions(+), 60 deletions(-) diff --git a/UPGRADE-4.3.md b/UPGRADE-4.3.md index 52165c10b5..f91bdb8bf6 100644 --- a/UPGRADE-4.3.md +++ b/UPGRADE-4.3.md @@ -88,9 +88,8 @@ Security * The `Role` and `SwitchUserRole` classes are deprecated and will be removed in 5.0. Use strings for roles instead. - * The `RoleHierarchyInterface` is deprecated and will be removed in 5.0. - * The `getReachableRoles()` method of the `RoleHierarchy` class is deprecated and will be removed in 5.0. - Use the `getReachableRoleNames()` method instead. + * The `getReachableRoles()` method of the `RoleHierarchyInterface` is deprecated and will be removed in 5.0. + Role hierarchies must implement the `getReachableRoleNames()` method instead and return roles as strings. * The `getRoles()` method of the `TokenInterface` is deprecated. Tokens must implement the `getRoleNames()` method instead and return roles as strings. * The `ListenerInterface` is deprecated, turn your listeners into callables instead. diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index 60b16b1c58..5757661bec 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -256,8 +256,8 @@ Security -------- * The `Role` and `SwitchUserRole` classes have been removed. - * The `RoleHierarchyInterface` has been removed. - * The `getReachableRoles()` method of the `RoleHierarchy` class has been removed. + * The `getReachableRoles()` method of the `RoleHierarchy` class has been removed. It has been replaced by the new + `getReachableRoleNames()` method. * The `getRoles()` method has been removed from the `TokenInterface`. It has been replaced by the new `getRoleNames()` method. * The `ContextListener::setLogoutOnUserChange()` method has been removed. diff --git a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php index d33d227ff1..0d122efe7f 100644 --- a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php +++ b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php @@ -23,7 +23,6 @@ use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager; use Symfony\Component\Security\Core\Authorization\Voter\TraceableVoter; use Symfony\Component\Security\Core\Role\Role; -use Symfony\Component\Security\Core\Role\RoleHierarchy; use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; use Symfony\Component\Security\Core\Role\SwitchUserRole; use Symfony\Component\Security\Http\Firewall\SwitchUserListener; @@ -113,7 +112,7 @@ class SecurityDataCollector extends DataCollector implements LateDataCollectorIn } if (null !== $this->roleHierarchy) { - if ($this->roleHierarchy instanceof RoleHierarchy) { + if (method_exists($this->roleHierarchy, 'getReachableRoleNames')) { $allRoles = $this->roleHierarchy->getReachableRoleNames($assignedRoles); } else { $allRoles = array_map(function (Role $role) { return (string) $role; }, $this->roleHierarchy->getReachableRoles($token->getRoles(false))); diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml index d4d6f1a2e3..1d2f0c4e50 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml @@ -98,8 +98,6 @@ %security.role_hierarchy.roles% - - diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index e29070f0cc..fc6b60a3a1 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -6,9 +6,8 @@ CHANGELOG * The `Role` and `SwitchUserRole` classes are deprecated and will be removed in 5.0. Use strings for roles instead. - * The `RoleHierarchyInterface` is deprecated and will be removed in 5.0. - * The `getReachableRoles()` method of the `RoleHierarchy` class is deprecated and will be removed in 5.0. - Use the `getReachableRoleNames()` method instead. + * The `getReachableRoles()` method of the `RoleHierarchyInterface` is deprecated and will be removed in 5.0. + Role hierarchies must implement the `getReachableRoleNames()` method instead and return roles as strings. * The `getRoles()` method of the `TokenInterface` is deprecated. Tokens must implement the `getRoleNames()` method instead and return roles as strings. * Made the `serialize()` and `unserialize()` methods of `AbstractToken` and diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php index c84fc5d9b4..e35583555d 100644 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php @@ -19,7 +19,6 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Authorization\ExpressionLanguage; use Symfony\Component\Security\Core\Role\Role; -use Symfony\Component\Security\Core\Role\RoleHierarchy; use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; /** @@ -43,6 +42,10 @@ class ExpressionVoter implements VoterInterface @trigger_error(sprintf('Passing a RoleHierarchyInterface to "%s()" is deprecated since Symfony 4.2. Pass an AuthorizationCheckerInterface instead.', __METHOD__), E_USER_DEPRECATED); $roleHierarchy = $authChecker; $authChecker = null; + + if (!method_exists($roleHierarchy, 'getReachableRoleNames')) { + @trigger_error(sprintf('Not implementing the getReachableRoleNames() method in %s which implements %s is deprecated since Symfony 4.3.', \get_class($this->roleHierarchy), RoleHierarchyInterface::class), E_USER_DEPRECATED); + } } elseif (null === $authChecker) { @trigger_error(sprintf('Argument 3 passed to "%s()" should be an instance of AuthorizationCheckerInterface, not passing it is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED); } elseif (!$authChecker instanceof AuthorizationCheckerInterface) { @@ -92,27 +95,23 @@ class ExpressionVoter implements VoterInterface private function getVariables(TokenInterface $token, $subject) { - if ($this->roleHierarchy instanceof RoleHierarchy) { - if (method_exists($token, 'getRoleNames')) { - $rolesFromToken = $token->getRoleNames(); - } else { - @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); - - $rolesFromToken = $token->getRoles(false); - } - - $roles = $this->roleHierarchy->getReachableRoleNames($rolesFromToken); - } elseif (null !== $this->roleHierarchy) { - $roles = $this->roleHierarchy->getReachableRoles($token->getRoles(false)); - } elseif (method_exists($token, 'getRoleNames')) { - $roles = $token->getRoleNames(); + if (method_exists($token, 'getRoleNames')) { + $roleNames = $token->getRoleNames(); + $roles = array_map(function (string $role) { return new Role($role, false); }, $roleNames); } else { @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); $roles = $token->getRoles(false); + $roleNames = array_map(function (Role $role) { return $role->getRole(); }, $roles); } - $roles = array_map(function ($role) { return $role instanceof Role ? $role->getRole() : $role; }, $roles); + if (null !== $this->roleHierarchy && method_exists($this->roleHierarchy, 'getReachableRoleNames')) { + $roleNames = $this->roleHierarchy->getReachableRoleNames($roleNames); + $roles = array_map(function (string $role) { return new Role($role, false); }, $roleNames); + } elseif (null !== $this->roleHierarchy) { + $roles = $this->roleHierarchy->getReachableRoles($roles); + $roleNames = array_map(function (Role $role) { return $role->getRole(); }, $roles); + } $variables = [ 'token' => $token, @@ -120,6 +119,7 @@ class ExpressionVoter implements VoterInterface 'object' => $subject, 'subject' => $subject, 'roles' => $roles, + 'role_names' => $roleNames, 'trust_resolver' => $this->trustResolver, 'auth_checker' => $this->authChecker, ]; diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/RoleHierarchyVoter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/RoleHierarchyVoter.php index 4a1de12cf7..d4524667c5 100644 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/RoleHierarchyVoter.php +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/RoleHierarchyVoter.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Core\Authorization\Voter; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Security\Core\Role\RoleHierarchy; use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; @@ -27,8 +28,8 @@ class RoleHierarchyVoter extends RoleVoter public function __construct(RoleHierarchyInterface $roleHierarchy, string $prefix = 'ROLE_') { - if (!$roleHierarchy instanceof RoleHierarchy) { - @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); + if (!method_exists($roleHierarchy, 'getReachableRoleNames')) { + @trigger_error(sprintf('Not implementing the getReachableRoleNames() method in %s which implements %s is deprecated since Symfony 4.3.', \get_class($roleHierarchy), RoleHierarchyInterface::class), E_USER_DEPRECATED); } $this->roleHierarchy = $roleHierarchy; @@ -41,13 +42,13 @@ class RoleHierarchyVoter extends RoleVoter */ protected function extractRoles(TokenInterface $token) { - if ($this->roleHierarchy instanceof RoleHierarchy) { + if (method_exists($this->roleHierarchy, 'getReachableRoleNames')) { if (method_exists($token, 'getRoleNames')) { $roles = $token->getRoleNames(); } else { @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); - $roles = $token->getRoles(false); + $roles = array_map(function (Role $role) { return $role->getRole(); }, $token->getRoles(false)); } return $this->roleHierarchy->getReachableRoleNames($roles); diff --git a/src/Symfony/Component/Security/Core/Role/RoleHierarchy.php b/src/Symfony/Component/Security/Core/Role/RoleHierarchy.php index d725000d1b..c1116cd2c3 100644 --- a/src/Symfony/Component/Security/Core/Role/RoleHierarchy.php +++ b/src/Symfony/Component/Security/Core/Role/RoleHierarchy.php @@ -36,7 +36,9 @@ class RoleHierarchy implements RoleHierarchyInterface */ public function getReachableRoles(array $roles) { - @trigger_error(sprintf('The %s() method is deprecated since Symfony 4.3 and will be removed in 5.0. Use roles as strings and the getReachableRoleNames() method instead.', __METHOD__), E_USER_DEPRECATED); + if (0 === \func_num_args() || func_get_arg(0)) { + @trigger_error(sprintf('The %s() method is deprecated since Symfony 4.3 and will be removed in 5.0. Use roles as strings and the getReachableRoleNames() method instead.', __METHOD__), E_USER_DEPRECATED); + } $reachableRoles = $roles; foreach ($roles as $role) { @@ -59,16 +61,7 @@ class RoleHierarchy implements RoleHierarchyInterface */ public function getReachableRoleNames(array $roles): array { - $reachableRoles = $roles = array_map( - function ($role) { - if ($role instanceof Role) { - return $role->getRole(); - } - - return $role; - }, - $roles - ); + $reachableRoles = $roles; foreach ($roles as $role) { if (!isset($this->map[$role])) { diff --git a/src/Symfony/Component/Security/Core/Role/RoleHierarchyInterface.php b/src/Symfony/Component/Security/Core/Role/RoleHierarchyInterface.php index 3a5112f7e5..9f54042db0 100644 --- a/src/Symfony/Component/Security/Core/Role/RoleHierarchyInterface.php +++ b/src/Symfony/Component/Security/Core/Role/RoleHierarchyInterface.php @@ -14,21 +14,13 @@ namespace Symfony\Component\Security\Core\Role; /** * RoleHierarchyInterface is the interface for a role hierarchy. * + * The getReachableRoles(Role[] $roles) method that returns an array of all reachable Role objects is deprecated + * since Symfony 4.3. + * * @author Fabien Potencier * - * @deprecated since Symfony 4.3, to be removed in 5.0. + * @method string[] getReachableRoleNames(string[] $roles) The associated roles - not implementing it is deprecated since Symfony 4.3 */ interface RoleHierarchyInterface { - /** - * Returns an array of all reachable roles by the given ones. - * - * Reachable roles are the roles directly assigned but also all roles that - * are transitively reachable from them in the role hierarchy. - * - * @param Role[] $roles An array of directly assigned roles - * - * @return Role[] An array of all reachable roles - */ - public function getReachableRoles(array $roles); } diff --git a/src/Symfony/Component/Workflow/EventListener/GuardListener.php b/src/Symfony/Component/Workflow/EventListener/GuardListener.php index 60cd00ed20..a1795b8f47 100644 --- a/src/Symfony/Component/Workflow/EventListener/GuardListener.php +++ b/src/Symfony/Component/Workflow/EventListener/GuardListener.php @@ -13,9 +13,9 @@ namespace Symfony\Component\Workflow\EventListener; use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Role\Role; -use Symfony\Component\Security\Core\Role\RoleHierarchy; use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Workflow\Event\GuardEvent; @@ -37,6 +37,10 @@ class GuardListener public function __construct(array $configuration, ExpressionLanguage $expressionLanguage, TokenStorageInterface $tokenStorage, AuthorizationCheckerInterface $authorizationChecker, AuthenticationTrustResolverInterface $trustResolver, RoleHierarchyInterface $roleHierarchy = null, ValidatorInterface $validator = null) { + if (null !== $roleHierarchy && !method_exists($roleHierarchy, 'getReachableRoleNames')) { + @trigger_error(sprintf('Not implementing the getReachableRoleNames() method in %s which implements %s is deprecated since Symfony 4.3.', \get_class($roleHierarchy), RoleHierarchyInterface::class), E_USER_DEPRECATED); + } + $this->configuration = $configuration; $this->expressionLanguage = $expressionLanguage; $this->tokenStorage = $tokenStorage; @@ -83,15 +87,21 @@ class GuardListener } if (method_exists($token, 'getRoleNames')) { - $roles = $token->getRoleNames(); + $roleNames = $token->getRoleNames(); + $roles = array_map(function (string $role) { return new Role($role, false); }, $roleNames); } else { - $roles = array_map(function (Role $role) { return $role->getRole(); }, $token->getRoles(false)); + @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); + + $roles = $token->getRoles(false); + $roleNames = array_map(function (Role $role) { return $role->getRole(); }, $roles); } - if ($this->roleHierarchy instanceof RoleHierarchy) { - $roles = $this->roleHierarchy->getReachableRoleNames($roles); + if (null !== $this->roleHierarchy && method_exists($this->roleHierarchy, 'getReachableRoleNames')) { + $roleNames = $this->roleHierarchy->getReachableRoleNames($roles); + $roles = array_map(function (string $role) { return new Role($role, false); }, $roleNames); } elseif (null !== $this->roleHierarchy) { $roles = $this->roleHierarchy->getReachableRoles($token->getRoles(false)); + $roleNames = array_map(function (Role $role) { return $role->getRole(); }, $roles); } $variables = [ @@ -99,6 +109,7 @@ class GuardListener 'user' => $token->getUser(), 'subject' => $event->getSubject(), 'roles' => $roles, + 'role_names' => $roleNames, // needed for the is_granted expression function 'auth_checker' => $this->authorizationChecker, // needed for the is_* expression function