diff --git a/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolver.php b/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolver.php index cbc411fad730b..6faebe6d24890 100644 --- a/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolver.php +++ b/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolver.php @@ -14,19 +14,31 @@ use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\AuthorizationChecker; /** * The default implementation of the authentication trust resolver. * * @author Johannes M. Schmitt + * + * @deprecated since symfony/security-core 5.1 */ class AuthenticationTrustResolver implements AuthenticationTrustResolverInterface { + public function __construct(bool $triggerDeprecation = true) + { + if ($triggerDeprecation) { + trigger_deprecation('symfony/security-core', '5.1', '%s is deprecated.', __CLASS__); + } + } + /** * {@inheritdoc} */ public function isAnonymous(TokenInterface $token = null) { + trigger_deprecation('symfony/security-core', '5.1', 'The %s is deprecated, use %s::isGranted("IS_ANONYMOUS") instead.', __CLASS__, AuthorizationChecker::class); + if (null === $token) { return false; } @@ -39,6 +51,8 @@ public function isAnonymous(TokenInterface $token = null) */ public function isRememberMe(TokenInterface $token = null) { + trigger_deprecation('symfony/security-core', '5.1', 'The %s is deprecated, use %s::isGranted("IS_REMEMBERED") instead.', __CLASS__, AuthorizationChecker::class); + if (null === $token) { return false; } @@ -51,6 +65,8 @@ public function isRememberMe(TokenInterface $token = null) */ public function isFullFledged(TokenInterface $token = null) { + trigger_deprecation('symfony/security-core', '5.1', 'The %s is deprecated, use %s::isGranted("IS_AUTHENTICATED_FULLY") instead.', __CLASS__, AuthorizationChecker::class); + if (null === $token) { return false; } diff --git a/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolverInterface.php b/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolverInterface.php index 6c9c4cbb37efa..1874534e5e8dc 100644 --- a/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolverInterface.php +++ b/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolverInterface.php @@ -17,6 +17,8 @@ * Interface for resolving the authentication status of a given token. * * @author Johannes M. Schmitt + * + * @deprecated since symfony/security-core 5.1 */ interface AuthenticationTrustResolverInterface { diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/AuthenticatedVoter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/AuthenticatedVoter.php index d571a7e9379b3..5432b82c8208e 100644 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/AuthenticatedVoter.php +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/AuthenticatedVoter.php @@ -12,6 +12,9 @@ namespace Symfony\Component\Security\Core\Authorization\Voter; use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; +use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverTrait; +use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; +use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; @@ -35,9 +38,13 @@ class AuthenticatedVoter implements VoterInterface private $authenticationTrustResolver; - public function __construct(AuthenticationTrustResolverInterface $authenticationTrustResolver) + public function __construct(AuthenticationTrustResolverInterface $authenticationTrustResolver = null) { - $this->authenticationTrustResolver = $authenticationTrustResolver; + if (null !== $authenticationTrustResolver) { + trigger_deprecation('symfony/security-core', '5.1', 'Passing instance of "%s" as first argument to "%s" is deprecated.', AuthenticationTrustResolverInterface::class, __CLASS__); + + $this->authenticationTrustResolver = $authenticationTrustResolver; + } } /** @@ -46,6 +53,7 @@ public function __construct(AuthenticationTrustResolverInterface $authentication public function vote(TokenInterface $token, $subject, array $attributes) { $result = VoterInterface::ACCESS_ABSTAIN; + foreach ($attributes as $attribute) { if (null === $attribute || (self::IS_AUTHENTICATED_FULLY !== $attribute && self::IS_AUTHENTICATED_REMEMBERED !== $attribute @@ -57,30 +65,33 @@ public function vote(TokenInterface $token, $subject, array $attributes) } $result = VoterInterface::ACCESS_DENIED; + if (null === $token) { + return $result; + } if (self::IS_AUTHENTICATED_FULLY === $attribute - && $this->authenticationTrustResolver->isFullFledged($token)) { + && (null !== $this->authenticationTrustResolver ? $this->authenticationTrustResolver->isFullFledged($token) : $this->isFullyFledged($token))) { return VoterInterface::ACCESS_GRANTED; } if (self::IS_AUTHENTICATED_REMEMBERED === $attribute - && ($this->authenticationTrustResolver->isRememberMe($token) - || $this->authenticationTrustResolver->isFullFledged($token))) { + && (null !== $this->authenticationTrustResolver ? ($this->authenticationTrustResolver->isRememberMe($token) + || $this->authenticationTrustResolver->isFullFledged($token)) : ($token instanceof RememberMeToken || $this->isFullyFledged($token)))) { return VoterInterface::ACCESS_GRANTED; } if (self::IS_AUTHENTICATED_ANONYMOUSLY === $attribute - && ($this->authenticationTrustResolver->isAnonymous($token) + && (null !== $this->authenticationTrustResolver ? ($this->authenticationTrustResolver->isAnonymous($token) || $this->authenticationTrustResolver->isRememberMe($token) - || $this->authenticationTrustResolver->isFullFledged($token))) { + || $this->authenticationTrustResolver->isFullFledged($token)) : ($token instanceof AnonymousToken || $token instanceof RememberMeToken || $this->isFullyFledged($token)))) { return VoterInterface::ACCESS_GRANTED; } - if (self::IS_REMEMBERED === $attribute && $this->authenticationTrustResolver->isRememberMe($token)) { + if (self::IS_REMEMBERED === $attribute && (null !== $this->authenticationTrustResolver ? $this->authenticationTrustResolver->isRememberMe($token) : $token instanceof RememberMeToken)) { return VoterInterface::ACCESS_GRANTED; } - if (self::IS_ANONYMOUS === $attribute && $this->authenticationTrustResolver->isAnonymous($token)) { + if (self::IS_ANONYMOUS === $attribute && (null !== $this->authenticationTrustResolver ? $this->authenticationTrustResolver->isAnonymous($token) : $token instanceof AnonymousToken)) { return VoterInterface::ACCESS_GRANTED; } @@ -91,4 +102,9 @@ public function vote(TokenInterface $token, $subject, array $attributes) return $result; } + + private function isFullyFledged(TokenInterface $token): bool + { + return !$token instanceof AnonymousToken && !$token instanceof RememberMeToken; + } } diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php index f02c42460ec37..d0ea2283b0b1a 100644 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php @@ -13,6 +13,7 @@ use Symfony\Component\ExpressionLanguage\Expression; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; @@ -31,8 +32,28 @@ class ExpressionVoter implements VoterInterface private $authChecker; private $roleHierarchy; - public function __construct(ExpressionLanguage $expressionLanguage, AuthenticationTrustResolverInterface $trustResolver, AuthorizationCheckerInterface $authChecker, RoleHierarchyInterface $roleHierarchy = null) + public function __construct(ExpressionLanguage $expressionLanguage, /*AuthenticationTrustResolverInterface */$trustResolver, /*AuthorizationCheckerInterface */$authChecker = null, /*RoleHierarchyInterface */$roleHierarchy = null) { + // $expr, $trust, $auth, ?$role + // $expr, $auth, ?$role + if ($trustResolver instanceof AuthenticationTrustResolverInterface) { + // old signature + trigger_deprecation('symfony/security-core', '5.1', 'Passing an %s as second argument to %s is deprecated.', AuthenticationTrustResolverInterface::class, __CLASS__); + } else { + // new signature: ExpressionLanguage $expressionLanguage, AuthorizationCheckerInterface $authChecker, RoleHierarchyInterface $roleHierarchy = null + $roleHierarchy = $authChecker; + $authChecker = $trustResolver; + $trustResolver = new AuthenticationTrustResolver(false); + } + + if (!$authChecker instanceof AuthorizationCheckerInterface) { + throw new \InvalidArgumentException(sprintf('Argument 2 of %s must be an instance of %s, %s given.', __METHOD__, AuthorizationCheckerInterface::class, is_object($authChecker) ? get_class($authChecker) : gettype($authChecker))); + } + + if (null !== $roleHierarchy && !$roleHierarchy instanceof RoleHierarchyInterface) { + throw new \InvalidArgumentException(sprintf('Argument 3 of %s must be an instance of %s or null, %s given.', __METHOD__, RoleHierarchyInterface::class, is_object($roleHierarchy) ? get_class($roleHierarchy) : gettype($roleHierarchy))); + } + $this->expressionLanguage = $expressionLanguage; $this->trustResolver = $trustResolver; $this->authChecker = $authChecker; diff --git a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php index 6e8908819746d..bc2b94cf03100 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php @@ -57,8 +57,21 @@ class ContextListener extends AbstractListener /** * @param iterable|UserProviderInterface[] $userProviders */ - public function __construct(TokenStorageInterface $tokenStorage, iterable $userProviders, string $contextKey, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, AuthenticationTrustResolverInterface $trustResolver = null, callable $sessionTrackerEnabler = null) + public function __construct(TokenStorageInterface $tokenStorage, iterable $userProviders, string $contextKey, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, /*AuthenticationTrustResolverInterface */$trustResolver = null, callable $sessionTrackerEnabler = null) { + if ($trustResolver instanceof AuthenticationTrustResolverInterface) { + // old signature + trigger_deprecation('symfony/security-core', '5.1', 'Passing an %s as second argument to %s is deprecated.', AuthenticationTrustResolverInterface::class, __CLASS__); + } else { + // new signature + $sessionTrackerEnabler = $trustResolver; + $trustResolver = null; + } + + if (null !== $sessionTrackerEnabler && !is_callable($sessionTrackerEnabler)) { + throw new \InvalidArgumentException(sprintf('Argument 2 of %s must be a callable, %s given.', __METHOD__, is_object($sessionTrackerEnabler) ? get_class($sessionTrackerEnabler) : gettype($sessionTrackerEnabler))); + } + if (empty($contextKey)) { throw new \InvalidArgumentException('$contextKey must not be empty.'); } @@ -69,7 +82,7 @@ public function __construct(TokenStorageInterface $tokenStorage, iterable $userP $this->logger = $logger; $this->dispatcher = class_exists(Event::class) ? LegacyEventDispatcherProxy::decorate($dispatcher) : $dispatcher; - $this->trustResolver = $trustResolver ?: new AuthenticationTrustResolver(AnonymousToken::class, RememberMeToken::class); + $this->trustResolver = $trustResolver; $this->sessionTrackerEnabler = $sessionTrackerEnabler; } @@ -166,7 +179,7 @@ public function onKernelResponse(ResponseEvent $event) $usageIndexValue = $session instanceof Session ? $usageIndexReference = &$session->getUsageIndex() : null; $token = $this->tokenStorage->getToken(); - if (null === $token || $this->trustResolver->isAnonymous($token)) { + if (null === $token || (null !== $this->trustResolver ? $this->trustResolver->isAnonymous($token) : (null !== $token && $token instanceof AnonymousToken))) { if ($request->hasPreviousSession()) { $session->remove($this->sessionKey); } diff --git a/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php b/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php index e9dbfb2352ff1..66de0d47b516b 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php @@ -21,7 +21,10 @@ use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; +use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; +use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Exception\AccountStatusException; use Symfony\Component\Security\Core\Exception\AuthenticationException; @@ -56,8 +59,44 @@ class ExceptionListener private $httpUtils; private $stateless; - public function __construct(TokenStorageInterface $tokenStorage, AuthenticationTrustResolverInterface $trustResolver, HttpUtils $httpUtils, string $providerKey, AuthenticationEntryPointInterface $authenticationEntryPoint = null, string $errorPage = null, AccessDeniedHandlerInterface $accessDeniedHandler = null, LoggerInterface $logger = null, bool $stateless = false) + public function __construct(TokenStorageInterface $tokenStorage, /*AuthenticationTrustResolverInterface */$trustResolver, /*HttpUtils */$httpUtils, /*string */$providerKey = null, /*AuthenticationEntryPointInterface */$authenticationEntryPoint = null, /*string */$errorPage = null, /*AccessDeniedHandlerInterface */$accessDeniedHandler = null, /*LoggerInterface */$logger = null, /*bool */$stateless = false) { + if ($trustResolver instanceof AuthenticationTrustResolverInterface) { + // old signature + trigger_deprecation('symfony/security-core', '5.1', 'Passing an %s as second argument to %s is deprecated.', AuthenticationTrustResolverInterface::class, __CLASS__); + } else { + // new signature + $stateless = $logger ?? false; + $logger = $accessDeniedHandler; + $accessDeniedHandler = $errorPage; + $errorPage = $authenticationEntryPoint; + $authenticationEntryPoint = $providerKey; + $providerKey = $httpUtils; + $httpUtils = $trustResolver; + } + + if (!$httpUtils instanceof HttpUtils) { + throw new \InvalidArgumentException(sprintf('Argument 2 of %s must be an instance of %s, %s given.', __METHOD__, HttpUtils::class, is_object($httpUtils) ? get_class($httpUtils) : gettype($httpUtils))); + } + if (!\is_string($providerKey)) { + throw new \InvalidArgumentException(sprintf('Argument 3 of %s must be a string, %s given.', __METHOD__, is_object($providerKey) ? get_class($providerKey) : gettype($providerKey))); + } + if (null !== $authenticationEntryPoint && !$authenticationEntryPoint instanceof AuthenticationEntryPointInterface) { + throw new \InvalidArgumentException(sprintf('Argument 4 of %s must be an instance of %s, %s given.', __METHOD__, AuthenticationEntryPointInterface::class, is_object($authenticationEntryPoint) ? get_class($authenticationEntryPoint) : gettype($authenticationEntryPoint))); + } + if (null !== $errorPage && !\is_string($errorPage)) { + throw new \InvalidArgumentException(sprintf('Argument 5 of %s must be a string, %s given.', __METHOD__, is_object($errorPage) ? get_class($errorPage) : gettype($errorPage))); + } + if (null !== $accessDeniedHandler && !$accessDeniedHandler instanceof AccessDeniedHandlerInterface) { + throw new \InvalidArgumentException(sprintf('Argument 6 of %s must be an instance of %s, %s given.', __METHOD__, AccessDeniedHandlerInterface::class, is_object($accessDeniedHandler) ? get_class($accessDeniedHandler) : gettype($accessDeniedHandler))); + } + if (null !== $logger && !$logger instanceof LoggerInterface) { + throw new \InvalidArgumentException(sprintf('Argument 7 of %s must be an instance of %s, %s given.', __METHOD__, LoggerInterface::class, is_object($logger) ? get_class($logger) : gettype($logger))); + } + if (!\is_bool($stateless)) { + throw new \InvalidArgumentException(sprintf('Argument 8 of %s must be a boolean, %s given.', __METHOD__, is_object($stateless) ? get_class($stateless) : gettype($stateless))); + } + $this->tokenStorage = $tokenStorage; $this->accessDeniedHandler = $accessDeniedHandler; $this->httpUtils = $httpUtils; @@ -137,7 +176,7 @@ private function handleAccessDeniedException(ExceptionEvent $event, AccessDenied $event->setThrowable(new AccessDeniedHttpException($exception->getMessage(), $exception)); $token = $this->tokenStorage->getToken(); - if (!$this->authenticationTrustResolver->isFullFledged($token)) { + if (!$this->isFullFledged($token)) { if (null !== $this->logger) { $this->logger->debug('Access denied, the user is not fully authenticated; redirecting to authentication entry point.', ['exception' => $exception]); } @@ -181,6 +220,11 @@ private function handleAccessDeniedException(ExceptionEvent $event, AccessDenied } } + private function isFullFledged(?TokenInterface $token): bool + { + return null !== $this->authenticationTrustResolver ? $this->authenticationTrustResolver->isFullFledged($token) : (null !== $token && !$token instanceof RememberMeToken && !$token instanceof AnonymousToken); + } + private function handleLogoutException(LogoutException $exception): void { if (null !== $this->logger) { diff --git a/src/Symfony/Component/Workflow/EventListener/GuardListener.php b/src/Symfony/Component/Workflow/EventListener/GuardListener.php index 4badb75afbd31..42fa16db9b8fc 100644 --- a/src/Symfony/Component/Workflow/EventListener/GuardListener.php +++ b/src/Symfony/Component/Workflow/EventListener/GuardListener.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Workflow\EventListener; +use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; @@ -33,8 +34,26 @@ class GuardListener private $roleHierarchy; private $validator; - public function __construct(array $configuration, ExpressionLanguage $expressionLanguage, TokenStorageInterface $tokenStorage, AuthorizationCheckerInterface $authorizationChecker, AuthenticationTrustResolverInterface $trustResolver, RoleHierarchyInterface $roleHierarchy = null, ValidatorInterface $validator = null) + public function __construct(array $configuration, ExpressionLanguage $expressionLanguage, TokenStorageInterface $tokenStorage, AuthorizationCheckerInterface $authorizationChecker, /*AuthenticationTrustResolverInterface */$trustResolver = null, /*RoleHierarchyInterface */$roleHierarchy = null, /*ValidatorInterface */$validator = null) { + if ($trustResolver instanceof AuthenticationTrustResolverInterface) { + // old signature + trigger_deprecation('symfony/security-core', '5.1', 'Passing an %s as second argument to %s is deprecated.', AuthenticationTrustResolverInterface::class, __CLASS__); + } else { + // new signature + $validator = $roleHierarchy; + $roleHierarchy = $trustResolver; + $trustResolver = new AuthenticationTrustResolver(false); + } + + if (null !== $roleHierarchy && !$roleHierarchy instanceof RoleHierarchyInterface) { + throw new \InvalidArgumentException(sprintf('Argument 5 of %s must be an instance of %s or null, %s given.', __METHOD__, RoleHierarchyInterface::class, is_object($roleHierarchy) ? get_class($roleHierarchy) : gettype($roleHierarchy))); + } + + if (null !== $validator && !$validator instanceof ValidatorInterface) { + throw new \InvalidArgumentException(sprintf('Argument 6 of %s must be an instance of %s or null, %s given.', __METHOD__, ValidatorInterface::class, is_object($validator) ? get_class($validator) : gettype($validator))); + } + $this->configuration = $configuration; $this->expressionLanguage = $expressionLanguage; $this->tokenStorage = $tokenStorage;