diff --git a/Authentication/AuthenticationManagerInterface.php b/Authentication/AuthenticationManagerInterface.php deleted file mode 100644 index 151b4f44..00000000 --- a/Authentication/AuthenticationManagerInterface.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Authentication; - -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Exception\AuthenticationException; - -/** - * AuthenticationManagerInterface is the interface for authentication managers, - * which process Token authentication. - * - * @author Fabien Potencier - * - * @internal since Symfony 5.3 - */ -interface AuthenticationManagerInterface -{ - /** - * Attempts to authenticate a TokenInterface object. - * - * @return TokenInterface - * - * @throws AuthenticationException if the authentication fails - */ - public function authenticate(TokenInterface $token); -} diff --git a/Authentication/AuthenticationProviderManager.php b/Authentication/AuthenticationProviderManager.php deleted file mode 100644 index f8da8478..00000000 --- a/Authentication/AuthenticationProviderManager.php +++ /dev/null @@ -1,133 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Authentication; - -use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; -use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\AuthenticationEvents; -use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent; -use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent; -use Symfony\Component\Security\Core\Exception\AccountStatusException; -use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; -use Symfony\Component\Security\Core\Exception\ProviderNotFoundException; -use Symfony\Component\Security\Core\User\UserInterface; -use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', AuthenticationProviderManager::class); - -// Help opcache.preload discover always-needed symbols -class_exists(AuthenticationEvents::class); -class_exists(AuthenticationFailureEvent::class); -class_exists(AuthenticationSuccessEvent::class); - -/** - * AuthenticationProviderManager uses a list of AuthenticationProviderInterface - * instances to authenticate a Token. - * - * @author Fabien Potencier - * @author Johannes M. Schmitt - * - * @deprecated since Symfony 5.3, use the new authenticator system instead - */ -class AuthenticationProviderManager implements AuthenticationManagerInterface -{ - private $providers; - private $eraseCredentials; - private $eventDispatcher; - - /** - * @param iterable $providers An iterable with AuthenticationProviderInterface instances as values - * @param bool $eraseCredentials Whether to erase credentials after authentication or not - * - * @throws \InvalidArgumentException - */ - public function __construct(iterable $providers, bool $eraseCredentials = true) - { - if (!$providers) { - throw new \InvalidArgumentException('You must at least add one authentication provider.'); - } - - $this->providers = $providers; - $this->eraseCredentials = $eraseCredentials; - } - - public function setEventDispatcher(EventDispatcherInterface $dispatcher) - { - $this->eventDispatcher = $dispatcher; - } - - /** - * {@inheritdoc} - */ - public function authenticate(TokenInterface $token) - { - $lastException = null; - $result = null; - - foreach ($this->providers as $provider) { - if (!$provider instanceof AuthenticationProviderInterface) { - throw new \InvalidArgumentException(sprintf('Provider "%s" must implement the AuthenticationProviderInterface.', get_debug_type($provider))); - } - - if (!$provider->supports($token)) { - continue; - } - - try { - $result = $provider->authenticate($token); - - if (null !== $result) { - break; - } - } catch (AccountStatusException $e) { - $lastException = $e; - - break; - } catch (AuthenticationException $e) { - $lastException = $e; - } catch (InvalidPasswordException $e) { - $lastException = new BadCredentialsException('Bad credentials.', 0, $e); - } - } - - if (null !== $result) { - if (true === $this->eraseCredentials) { - $result->eraseCredentials(); - } - - if (null !== $this->eventDispatcher) { - $this->eventDispatcher->dispatch(new AuthenticationSuccessEvent($result), AuthenticationEvents::AUTHENTICATION_SUCCESS); - } - - // @deprecated since Symfony 5.3 - if ($result->getUser() instanceof UserInterface && !method_exists($result->getUser(), 'getUserIdentifier')) { - trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "getUserIdentifier(): string" in user class "%s" is deprecated. This method will replace "getUsername()" in Symfony 6.0.', get_debug_type($result->getUser())); - } - - return $result; - } - - if (null === $lastException) { - $lastException = new ProviderNotFoundException(sprintf('No Authentication Provider found for token of class "%s".', \get_class($token))); - } - - if (null !== $this->eventDispatcher) { - $this->eventDispatcher->dispatch(new AuthenticationFailureEvent($token, $lastException), AuthenticationEvents::AUTHENTICATION_FAILURE); - } - - $lastException->setToken($token); - - throw $lastException; - } -} diff --git a/Authentication/AuthenticationTrustResolver.php b/Authentication/AuthenticationTrustResolver.php index e95b360b..513f0d5e 100644 --- a/Authentication/AuthenticationTrustResolver.php +++ b/Authentication/AuthenticationTrustResolver.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Security\Core\Authentication; -use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; @@ -24,36 +23,16 @@ class AuthenticationTrustResolver implements AuthenticationTrustResolverInterfac { public function isAuthenticated(?TokenInterface $token = null): bool { - return $token && $token->getUser() - // @deprecated since Symfony 5.4, TokenInterface::isAuthenticated() and AnonymousToken no longer exists in 6.0 - && !$token instanceof AnonymousToken && (!method_exists($token, 'isAuthenticated') || $token->isAuthenticated(false)); + return $token && $token->getUser(); } - /** - * {@inheritdoc} - */ - public function isAnonymous(?TokenInterface $token = null/* , $deprecation = true */) - { - if (1 === \func_num_args() || false !== func_get_arg(1)) { - trigger_deprecation('symfony/security-core', '5.4', 'The "%s()" method is deprecated, use "isAuthenticated()" or "isFullFledged()" if you want to check if the request is (fully) authenticated.', __METHOD__); - } - - return $token instanceof AnonymousToken || ($token && !$token->getUser()); - } - - /** - * {@inheritdoc} - */ - public function isRememberMe(?TokenInterface $token = null) + public function isRememberMe(?TokenInterface $token = null): bool { return $token && $token instanceof RememberMeToken; } - /** - * {@inheritdoc} - */ - public function isFullFledged(?TokenInterface $token = null) + public function isFullFledged(?TokenInterface $token = null): bool { - return $token && !$this->isAnonymous($token, false) && !$this->isRememberMe($token); + return $this->isAuthenticated($token) && !$this->isRememberMe($token); } } diff --git a/Authentication/AuthenticationTrustResolverInterface.php b/Authentication/AuthenticationTrustResolverInterface.php index 0e63d2fa..b508290c 100644 --- a/Authentication/AuthenticationTrustResolverInterface.php +++ b/Authentication/AuthenticationTrustResolverInterface.php @@ -17,35 +17,22 @@ * Interface for resolving the authentication status of a given token. * * @author Johannes M. Schmitt - * - * @method bool isAuthenticated(?TokenInterface $token = null) */ interface AuthenticationTrustResolverInterface { /** - * Resolves whether the passed token implementation is authenticated - * anonymously. - * - * If null is passed, the method must return false. - * - * @return bool - * - * @deprecated since Symfony 5.4, use !isAuthenticated() instead + * Resolves whether the passed token implementation is authenticated. */ - public function isAnonymous(?TokenInterface $token = null); + public function isAuthenticated(?TokenInterface $token = null): bool; /** * Resolves whether the passed token implementation is authenticated * using remember-me capabilities. - * - * @return bool */ - public function isRememberMe(?TokenInterface $token = null); + public function isRememberMe(?TokenInterface $token = null): bool; /** * Resolves whether the passed token implementation is fully authenticated. - * - * @return bool */ - public function isFullFledged(?TokenInterface $token = null); + public function isFullFledged(?TokenInterface $token = null): bool; } diff --git a/Authentication/Provider/AnonymousAuthenticationProvider.php b/Authentication/Provider/AnonymousAuthenticationProvider.php deleted file mode 100644 index 53f8cf18..00000000 --- a/Authentication/Provider/AnonymousAuthenticationProvider.php +++ /dev/null @@ -1,69 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Authentication\Provider; - -use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', AnonymousAuthenticationProvider::class); - -/** - * AnonymousAuthenticationProvider validates AnonymousToken instances. - * - * @author Fabien Potencier - * - * @deprecated since Symfony 5.3, use the new authenticator system instead - */ -class AnonymousAuthenticationProvider implements AuthenticationProviderInterface -{ - /** - * Used to determine if the token is created by the application - * instead of a malicious client. - * - * @var string - */ - private $secret; - - /** - * @param string $secret The secret shared with the AnonymousToken - */ - public function __construct(string $secret) - { - $this->secret = $secret; - } - - /** - * {@inheritdoc} - */ - public function authenticate(TokenInterface $token) - { - if (!$this->supports($token)) { - throw new AuthenticationException('The token is not supported by this authentication provider.'); - } - - if ($this->secret !== $token->getSecret()) { - throw new BadCredentialsException('The Token does not contain the expected key.'); - } - - return $token; - } - - /** - * {@inheritdoc} - */ - public function supports(TokenInterface $token) - { - return $token instanceof AnonymousToken; - } -} diff --git a/Authentication/Provider/AuthenticationProviderInterface.php b/Authentication/Provider/AuthenticationProviderInterface.php deleted file mode 100644 index fb57ed80..00000000 --- a/Authentication/Provider/AuthenticationProviderInterface.php +++ /dev/null @@ -1,44 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Authentication\Provider; - -use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" interface is deprecated, use the new authenticator system instead.', AuthenticationProviderInterface::class); - -/** - * AuthenticationProviderInterface is the interface for all authentication - * providers. - * - * Concrete implementations processes specific Token instances. - * - * @author Fabien Potencier - * - * @deprecated since Symfony 5.3, use the new authenticator system instead - */ -interface AuthenticationProviderInterface extends AuthenticationManagerInterface -{ - /** - * Use this constant for not provided username. - * - * @var string - */ - public const USERNAME_NONE_PROVIDED = 'NONE_PROVIDED'; - - /** - * Checks whether this provider supports the given token. - * - * @return bool - */ - public function supports(TokenInterface $token); -} diff --git a/Authentication/Provider/DaoAuthenticationProvider.php b/Authentication/Provider/DaoAuthenticationProvider.php deleted file mode 100644 index a0e01da4..00000000 --- a/Authentication/Provider/DaoAuthenticationProvider.php +++ /dev/null @@ -1,146 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Authentication\Provider; - -use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; -use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; -use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; -use Symfony\Component\Security\Core\Exception\AuthenticationServiceException; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; -use Symfony\Component\Security\Core\Exception\UserNotFoundException; -use Symfony\Component\Security\Core\User\LegacyPasswordAuthenticatedUserInterface; -use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; -use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; -use Symfony\Component\Security\Core\User\UserCheckerInterface; -use Symfony\Component\Security\Core\User\UserInterface; -use Symfony\Component\Security\Core\User\UserProviderInterface; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', DaoAuthenticationProvider::class); - -/** - * DaoAuthenticationProvider uses a UserProviderInterface to retrieve the user - * for a UsernamePasswordToken. - * - * @author Fabien Potencier - * - * @deprecated since Symfony 5.3, use the new authenticator system instead - */ -class DaoAuthenticationProvider extends UserAuthenticationProvider -{ - private $hasherFactory; - private $userProvider; - - /** - * @param PasswordHasherFactoryInterface $hasherFactory - */ - public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, string $providerKey, $hasherFactory, bool $hideUserNotFoundExceptions = true) - { - parent::__construct($userChecker, $providerKey, $hideUserNotFoundExceptions); - - if ($hasherFactory instanceof EncoderFactoryInterface) { - trigger_deprecation('symfony/security-core', '5.3', 'Passing a "%s" instance to the "%s" constructor is deprecated, use "%s" instead.', EncoderFactoryInterface::class, __CLASS__, PasswordHasherFactoryInterface::class); - } - - $this->hasherFactory = $hasherFactory; - $this->userProvider = $userProvider; - } - - /** - * {@inheritdoc} - */ - protected function checkAuthentication(UserInterface $user, UsernamePasswordToken $token) - { - $currentUser = $token->getUser(); - if ($currentUser instanceof UserInterface) { - if ($currentUser->getPassword() !== $user->getPassword()) { - throw new BadCredentialsException('The credentials were changed from another session.'); - } - } else { - if ('' === ($presentedPassword = $token->getCredentials())) { - throw new BadCredentialsException('The presented password cannot be empty.'); - } - - if (null === $user->getPassword()) { - throw new BadCredentialsException('The presented password is invalid.'); - } - - if (!$user instanceof PasswordAuthenticatedUserInterface) { - trigger_deprecation('symfony/security-core', '5.3', 'Using password-based authentication listeners while not implementing "%s" interface from class "%s" is deprecated.', PasswordAuthenticatedUserInterface::class, get_debug_type($user)); - } - - $salt = $user->getSalt(); - if ($salt && !$user instanceof LegacyPasswordAuthenticatedUserInterface) { - trigger_deprecation('symfony/security-core', '5.3', 'Returning a string from "getSalt()" without implementing the "%s" interface is deprecated, the "%s" class should implement it.', LegacyPasswordAuthenticatedUserInterface::class, get_debug_type($user)); - } - - // deprecated since Symfony 5.3 - if ($this->hasherFactory instanceof EncoderFactoryInterface) { - $encoder = $this->hasherFactory->getEncoder($user); - - if (!$encoder->isPasswordValid($user->getPassword(), $presentedPassword, $salt)) { - throw new BadCredentialsException('The presented password is invalid.'); - } - - if ($this->userProvider instanceof PasswordUpgraderInterface && method_exists($encoder, 'needsRehash') && $encoder->needsRehash($user->getPassword())) { - $this->userProvider->upgradePassword($user, $encoder->encodePassword($presentedPassword, $user->getSalt())); - } - - return; - } - - $hasher = $this->hasherFactory->getPasswordHasher($user); - - if (!$hasher->verify($user->getPassword(), $presentedPassword, $salt)) { - throw new BadCredentialsException('The presented password is invalid.'); - } - - if ($this->userProvider instanceof PasswordUpgraderInterface && $hasher->needsRehash($user->getPassword())) { - $this->userProvider->upgradePassword($user, $hasher->hash($presentedPassword, $salt)); - } - } - } - - /** - * {@inheritdoc} - */ - protected function retrieveUser(string $userIdentifier, UsernamePasswordToken $token) - { - $user = $token->getUser(); - if ($user instanceof UserInterface) { - return $user; - } - - try { - // @deprecated since Symfony 5.3, change to $this->userProvider->loadUserByIdentifier() in 6.0 - if (method_exists($this->userProvider, 'loadUserByIdentifier')) { - $user = $this->userProvider->loadUserByIdentifier($userIdentifier); - } else { - trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', get_debug_type($this->userProvider)); - - $user = $this->userProvider->loadUserByUsername($userIdentifier); - } - - if (!$user instanceof UserInterface) { - throw new AuthenticationServiceException('The user provider must return a UserInterface object.'); - } - - return $user; - } catch (UserNotFoundException $e) { - $e->setUserIdentifier($userIdentifier); - throw $e; - } catch (\Exception $e) { - $e = new AuthenticationServiceException($e->getMessage(), 0, $e); - $e->setToken($token); - throw $e; - } - } -} diff --git a/Authentication/Provider/LdapBindAuthenticationProvider.php b/Authentication/Provider/LdapBindAuthenticationProvider.php deleted file mode 100644 index ec2bc569..00000000 --- a/Authentication/Provider/LdapBindAuthenticationProvider.php +++ /dev/null @@ -1,121 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Authentication\Provider; - -use Symfony\Component\Ldap\Exception\ConnectionException; -use Symfony\Component\Ldap\LdapInterface; -use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; -use Symfony\Component\Security\Core\Exception\LogicException; -use Symfony\Component\Security\Core\Exception\UserNotFoundException; -use Symfony\Component\Security\Core\User\UserCheckerInterface; -use Symfony\Component\Security\Core\User\UserInterface; -use Symfony\Component\Security\Core\User\UserProviderInterface; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', LdapBindAuthenticationProvider::class); - -/** - * LdapBindAuthenticationProvider authenticates a user against an LDAP server. - * - * The only way to check user credentials is to try to connect the user with its - * credentials to the ldap. - * - * @author Charles Sarrazin - * - * @deprecated since Symfony 5.3, use the new authenticator system instead - */ -class LdapBindAuthenticationProvider extends UserAuthenticationProvider -{ - private $userProvider; - private $ldap; - private $dnString; - private $queryString; - private $searchDn; - private $searchPassword; - - public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, string $providerKey, LdapInterface $ldap, string $dnString = '{user_identifier}', bool $hideUserNotFoundExceptions = true, string $searchDn = '', string $searchPassword = '') - { - parent::__construct($userChecker, $providerKey, $hideUserNotFoundExceptions); - - $this->userProvider = $userProvider; - $this->ldap = $ldap; - $this->dnString = $dnString; - $this->searchDn = $searchDn; - $this->searchPassword = $searchPassword; - } - - /** - * Set a query string to use in order to find a DN for the user identifier. - */ - public function setQueryString(string $queryString) - { - $this->queryString = $queryString; - } - - /** - * {@inheritdoc} - */ - protected function retrieveUser(string $userIdentifier, UsernamePasswordToken $token) - { - if (AuthenticationProviderInterface::USERNAME_NONE_PROVIDED === $userIdentifier) { - throw new UserNotFoundException('User identifier cannot be null.'); - } - - // @deprecated since Symfony 5.3, change to $this->userProvider->loadUserByIdentifier() in 6.0 - if (method_exists($this->userProvider, 'loadUserByIdentifier')) { - return $this->userProvider->loadUserByIdentifier($userIdentifier); - } else { - trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', get_debug_type($this->userProvider)); - - return $this->userProvider->loadUserByUsername($userIdentifier); - } - } - - /** - * {@inheritdoc} - */ - protected function checkAuthentication(UserInterface $user, UsernamePasswordToken $token) - { - // @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0 - $userIdentifier = method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(); - $password = $token->getCredentials(); - - if ('' === (string) $password) { - throw new BadCredentialsException('The presented password must not be empty.'); - } - - try { - if ($this->queryString) { - if ('' !== $this->searchDn && '' !== $this->searchPassword) { - $this->ldap->bind($this->searchDn, $this->searchPassword); - } else { - throw new LogicException('Using the "query_string" config without using a "search_dn" and a "search_password" is not supported.'); - } - $userIdentifier = $this->ldap->escape($userIdentifier, '', LdapInterface::ESCAPE_FILTER); - $query = str_replace(['{username}', '{user_identifier}'], $userIdentifier, $this->queryString); - $result = $this->ldap->query($this->dnString, $query)->execute(); - if (1 !== $result->count()) { - throw new BadCredentialsException('The presented username is invalid.'); - } - - $dn = $result[0]->getDn(); - } else { - $userIdentifier = $this->ldap->escape($userIdentifier, '', LdapInterface::ESCAPE_DN); - $dn = str_replace(['{username}', '{user_identifier}'], $userIdentifier, $this->dnString); - } - - $this->ldap->bind($dn, $password); - } catch (ConnectionException $e) { - throw new BadCredentialsException('The presented password is invalid.'); - } - } -} diff --git a/Authentication/Provider/PreAuthenticatedAuthenticationProvider.php b/Authentication/Provider/PreAuthenticatedAuthenticationProvider.php deleted file mode 100644 index d81e31bb..00000000 --- a/Authentication/Provider/PreAuthenticatedAuthenticationProvider.php +++ /dev/null @@ -1,86 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Authentication\Provider; - -use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; -use Symfony\Component\Security\Core\User\UserCheckerInterface; -use Symfony\Component\Security\Core\User\UserProviderInterface; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', PreAuthenticatedAuthenticationProvider::class); - -/** - * Processes a pre-authenticated authentication request. - * - * This authentication provider will not perform any checks on authentication - * requests, as they should already be pre-authenticated. However, the - * UserProviderInterface implementation may still throw a - * UserNotFoundException, for example. - * - * @author Fabien Potencier - * - * @deprecated since Symfony 5.3, use the new authenticator system instead - */ -class PreAuthenticatedAuthenticationProvider implements AuthenticationProviderInterface -{ - private $userProvider; - private $userChecker; - private $providerKey; - - public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, string $providerKey) - { - $this->userProvider = $userProvider; - $this->userChecker = $userChecker; - $this->providerKey = $providerKey; - } - - /** - * {@inheritdoc} - */ - public function authenticate(TokenInterface $token) - { - if (!$this->supports($token)) { - throw new AuthenticationException('The token is not supported by this authentication provider.'); - } - - if (!$user = $token->getUser()) { - throw new BadCredentialsException('No pre-authenticated principal found in request.'); - } - - $userIdentifier = method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(); - // @deprecated since Symfony 5.3, change to $this->userProvider->loadUserByIdentifier() in 6.0 - if (method_exists($this->userProvider, 'loadUserByIdentifier')) { - $user = $this->userProvider->loadUserByIdentifier($userIdentifier); - } else { - trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', get_debug_type($this->userProvider)); - - $user = $this->userProvider->loadUserByUsername($userIdentifier); - } - - $this->userChecker->checkPostAuth($user); - - $authenticatedToken = new PreAuthenticatedToken($user, $token->getCredentials(), $this->providerKey, $user->getRoles()); - $authenticatedToken->setAttributes($token->getAttributes()); - - return $authenticatedToken; - } - - /** - * {@inheritdoc} - */ - public function supports(TokenInterface $token) - { - return $token instanceof PreAuthenticatedToken && $this->providerKey === $token->getFirewallName(); - } -} diff --git a/Authentication/Provider/RememberMeAuthenticationProvider.php b/Authentication/Provider/RememberMeAuthenticationProvider.php deleted file mode 100644 index 2fd52f2d..00000000 --- a/Authentication/Provider/RememberMeAuthenticationProvider.php +++ /dev/null @@ -1,79 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Authentication\Provider; - -use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; -use Symfony\Component\Security\Core\Exception\LogicException; -use Symfony\Component\Security\Core\User\UserCheckerInterface; -use Symfony\Component\Security\Core\User\UserInterface; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', RememberMeAuthenticationProvider::class); - -/** - * @deprecated since Symfony 5.3, use the new authenticator system instead - */ -class RememberMeAuthenticationProvider implements AuthenticationProviderInterface -{ - private $userChecker; - private $secret; - private $providerKey; - - /** - * @param string $secret A secret - * @param string $providerKey A provider secret - */ - public function __construct(UserCheckerInterface $userChecker, string $secret, string $providerKey) - { - $this->userChecker = $userChecker; - $this->secret = $secret; - $this->providerKey = $providerKey; - } - - /** - * {@inheritdoc} - */ - public function authenticate(TokenInterface $token) - { - if (!$this->supports($token)) { - throw new AuthenticationException('The token is not supported by this authentication provider.'); - } - - if ($this->secret !== $token->getSecret()) { - throw new BadCredentialsException('The presented secret does not match.'); - } - - $user = $token->getUser(); - - if (!$user instanceof UserInterface) { - throw new LogicException(sprintf('Method "%s::getUser()" must return a "%s" instance, "%s" returned.', get_debug_type($token), UserInterface::class, get_debug_type($user))); - } - - $this->userChecker->checkPreAuth($user); - $this->userChecker->checkPostAuth($user); - - $authenticatedToken = new RememberMeToken($user, $this->providerKey, $this->secret); - $authenticatedToken->setAttributes($token->getAttributes()); - - return $authenticatedToken; - } - - /** - * {@inheritdoc} - */ - public function supports(TokenInterface $token) - { - return $token instanceof RememberMeToken && $token->getFirewallName() === $this->providerKey; - } -} diff --git a/Authentication/Provider/UserAuthenticationProvider.php b/Authentication/Provider/UserAuthenticationProvider.php deleted file mode 100644 index 81731e8d..00000000 --- a/Authentication/Provider/UserAuthenticationProvider.php +++ /dev/null @@ -1,134 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Authentication\Provider; - -use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; -use Symfony\Component\Security\Core\Exception\AccountStatusException; -use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Component\Security\Core\Exception\AuthenticationServiceException; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; -use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException; -use Symfony\Component\Security\Core\Exception\UserNotFoundException; -use Symfony\Component\Security\Core\User\UserCheckerInterface; -use Symfony\Component\Security\Core\User\UserInterface; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', UserAuthenticationProvider::class); - -/** - * UserProviderInterface retrieves users for UsernamePasswordToken tokens. - * - * @author Fabien Potencier - * - * @deprecated since Symfony 5.3, use the new authenticator system instead - */ -abstract class UserAuthenticationProvider implements AuthenticationProviderInterface -{ - private $hideUserNotFoundExceptions; - private $userChecker; - private $providerKey; - - /** - * @throws \InvalidArgumentException - */ - public function __construct(UserCheckerInterface $userChecker, string $providerKey, bool $hideUserNotFoundExceptions = true) - { - if (empty($providerKey)) { - throw new \InvalidArgumentException('$providerKey must not be empty.'); - } - - $this->userChecker = $userChecker; - $this->providerKey = $providerKey; - $this->hideUserNotFoundExceptions = $hideUserNotFoundExceptions; - } - - /** - * {@inheritdoc} - */ - public function authenticate(TokenInterface $token) - { - if (!$this->supports($token)) { - throw new AuthenticationException('The token is not supported by this authentication provider.'); - } - - $username = method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(); - if ('' === $username || null === $username) { - $username = AuthenticationProviderInterface::USERNAME_NONE_PROVIDED; - } - - try { - $user = $this->retrieveUser($username, $token); - } catch (UserNotFoundException $e) { - if ($this->hideUserNotFoundExceptions) { - throw new BadCredentialsException('Bad credentials.', 0, $e); - } - $e->setUserIdentifier($username); - - throw $e; - } - - if (!$user instanceof UserInterface) { - throw new AuthenticationServiceException('retrieveUser() must return a UserInterface.'); - } - - try { - $this->userChecker->checkPreAuth($user); - $this->checkAuthentication($user, $token); - $this->userChecker->checkPostAuth($user); - } catch (AccountStatusException|BadCredentialsException $e) { - if ($this->hideUserNotFoundExceptions && !$e instanceof CustomUserMessageAccountStatusException) { - throw new BadCredentialsException('Bad credentials.', 0, $e); - } - - throw $e; - } - - if ($token instanceof SwitchUserToken) { - $roles = $user->getRoles(); - $roles[] = 'ROLE_PREVIOUS_ADMIN'; - - $authenticatedToken = new SwitchUserToken($user, $token->getCredentials(), $this->providerKey, $roles, $token->getOriginalToken()); - } else { - $authenticatedToken = new UsernamePasswordToken($user, $token->getCredentials(), $this->providerKey, $user->getRoles()); - } - - $authenticatedToken->setAttributes($token->getAttributes()); - - return $authenticatedToken; - } - - /** - * {@inheritdoc} - */ - public function supports(TokenInterface $token) - { - return $token instanceof UsernamePasswordToken && $this->providerKey === $token->getFirewallName(); - } - - /** - * Retrieves the user from an implementation-specific location. - * - * @return UserInterface - * - * @throws AuthenticationException if the credentials could not be validated - */ - abstract protected function retrieveUser(string $username, UsernamePasswordToken $token); - - /** - * Does additional checks on the user and token (like validating the - * credentials). - * - * @throws AuthenticationException if the credentials could not be validated - */ - abstract protected function checkAuthentication(UserInterface $user, UsernamePasswordToken $token); -} diff --git a/Authentication/RememberMe/CacheTokenVerifier.php b/Authentication/RememberMe/CacheTokenVerifier.php index 340bc87c..3930ac8d 100644 --- a/Authentication/RememberMe/CacheTokenVerifier.php +++ b/Authentication/RememberMe/CacheTokenVerifier.php @@ -18,27 +18,20 @@ */ class CacheTokenVerifier implements TokenVerifierInterface { - private $cache; - private $outdatedTokenTtl; - private $cacheKeyPrefix; - /** * @param int $outdatedTokenTtl How long the outdated token should still be considered valid. Defaults * to 60, which matches how often the PersistentRememberMeHandler will at * most refresh tokens. Increasing to more than that is not recommended, * but you may use a lower value. */ - public function __construct(CacheItemPoolInterface $cache, int $outdatedTokenTtl = 60, string $cacheKeyPrefix = 'rememberme-stale-') - { - $this->cache = $cache; - $this->outdatedTokenTtl = $outdatedTokenTtl; - $this->cacheKeyPrefix = $cacheKeyPrefix; + public function __construct( + private CacheItemPoolInterface $cache, + private int $outdatedTokenTtl = 60, + private string $cacheKeyPrefix = 'rememberme-stale-', + ) { } - /** - * {@inheritdoc} - */ - public function verifyToken(PersistentTokenInterface $token, string $tokenValue): bool + public function verifyToken(PersistentTokenInterface $token, #[\SensitiveParameter] string $tokenValue): bool { if (hash_equals($token->getTokenValue(), $tokenValue)) { return true; @@ -55,10 +48,7 @@ public function verifyToken(PersistentTokenInterface $token, string $tokenValue) return hash_equals($outdatedToken, $tokenValue); } - /** - * {@inheritdoc} - */ - public function updateExistingToken(PersistentTokenInterface $token, string $tokenValue, \DateTimeInterface $lastUsed): void + public function updateExistingToken(PersistentTokenInterface $token, #[\SensitiveParameter] string $tokenValue, \DateTimeInterface $lastUsed): void { // When a token gets updated, persist the outdated token for $outdatedTokenTtl seconds so we can // still accept it as valid in verifyToken diff --git a/Authentication/RememberMe/InMemoryTokenProvider.php b/Authentication/RememberMe/InMemoryTokenProvider.php index 571bbe02..d7fbb9c7 100644 --- a/Authentication/RememberMe/InMemoryTokenProvider.php +++ b/Authentication/RememberMe/InMemoryTokenProvider.php @@ -18,14 +18,11 @@ * * @author Johannes M. Schmitt */ -class InMemoryTokenProvider implements TokenProviderInterface +final class InMemoryTokenProvider implements TokenProviderInterface { - private $tokens = []; + private array $tokens = []; - /** - * {@inheritdoc} - */ - public function loadTokenBySeries(string $series) + public function loadTokenBySeries(string $series): PersistentTokenInterface { if (!isset($this->tokens[$series])) { throw new TokenNotFoundException('No token found.'); @@ -34,10 +31,7 @@ public function loadTokenBySeries(string $series) return $this->tokens[$series]; } - /** - * {@inheritdoc} - */ - public function updateToken(string $series, string $tokenValue, \DateTime $lastUsed) + public function updateToken(string $series, #[\SensitiveParameter] string $tokenValue, \DateTimeInterface $lastUsed): void { if (!isset($this->tokens[$series])) { throw new TokenNotFoundException('No token found.'); @@ -45,7 +39,7 @@ public function updateToken(string $series, string $tokenValue, \DateTime $lastU $token = new PersistentToken( $this->tokens[$series]->getClass(), - method_exists($this->tokens[$series], 'getUserIdentifier') ? $this->tokens[$series]->getUserIdentifier() : $this->tokens[$series]->getUsername(), + $this->tokens[$series]->getUserIdentifier(), $series, $tokenValue, $lastUsed @@ -53,18 +47,12 @@ public function updateToken(string $series, string $tokenValue, \DateTime $lastU $this->tokens[$series] = $token; } - /** - * {@inheritdoc} - */ - public function deleteTokenBySeries(string $series) + public function deleteTokenBySeries(string $series): void { unset($this->tokens[$series]); } - /** - * {@inheritdoc} - */ - public function createNewToken(PersistentTokenInterface $token) + public function createNewToken(PersistentTokenInterface $token): void { $this->tokens[$token->getSeries()] = $token; } diff --git a/Authentication/RememberMe/PersistentToken.php b/Authentication/RememberMe/PersistentToken.php index b8337adf..0f391c23 100644 --- a/Authentication/RememberMe/PersistentToken.php +++ b/Authentication/RememberMe/PersistentToken.php @@ -18,78 +18,53 @@ */ final class PersistentToken implements PersistentTokenInterface { - private $class; - private $userIdentifier; - private $series; - private $tokenValue; - private $lastUsed; + private \DateTimeImmutable $lastUsed; - public function __construct(string $class, string $userIdentifier, string $series, string $tokenValue, \DateTime $lastUsed) - { - if (empty($class)) { + public function __construct( + private string $class, + private string $userIdentifier, + private string $series, + #[\SensitiveParameter] private string $tokenValue, + \DateTimeInterface $lastUsed, + ) { + if (!$class) { throw new \InvalidArgumentException('$class must not be empty.'); } if ('' === $userIdentifier) { throw new \InvalidArgumentException('$userIdentifier must not be empty.'); } - if (empty($series)) { + if (!$series) { throw new \InvalidArgumentException('$series must not be empty.'); } - if (empty($tokenValue)) { + if (!$tokenValue) { throw new \InvalidArgumentException('$tokenValue must not be empty.'); } - $this->class = $class; - $this->userIdentifier = $userIdentifier; - $this->series = $series; - $this->tokenValue = $tokenValue; - $this->lastUsed = $lastUsed; + $this->lastUsed = \DateTimeImmutable::createFromInterface($lastUsed); } - /** - * {@inheritdoc} - */ public function getClass(): string { return $this->class; } - /** - * {@inheritdoc} - */ - public function getUsername(): string - { - trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use getUserIdentifier() instead.', __METHOD__); - - return $this->userIdentifier; - } - public function getUserIdentifier(): string { return $this->userIdentifier; } - /** - * {@inheritdoc} - */ public function getSeries(): string { return $this->series; } - /** - * {@inheritdoc} - */ public function getTokenValue(): string { return $this->tokenValue; } - /** - * {@inheritdoc} - */ public function getLastUsed(): \DateTime { - return $this->lastUsed; + return \DateTime::createFromImmutable($this->lastUsed); } } diff --git a/Authentication/RememberMe/PersistentTokenInterface.php b/Authentication/RememberMe/PersistentTokenInterface.php index e2bf2fe1..f5c06175 100644 --- a/Authentication/RememberMe/PersistentTokenInterface.php +++ b/Authentication/RememberMe/PersistentTokenInterface.php @@ -15,44 +15,34 @@ * Interface to be implemented by persistent token classes (such as * Doctrine entities representing a remember-me token). * - * @method string getUserIdentifier() returns the identifier used to authenticate (e.g. their email address or username) - * * @author Johannes M. Schmitt */ interface PersistentTokenInterface { /** * Returns the class of the user. - * - * @return string */ - public function getClass(); + public function getClass(): string; /** * Returns the series. - * - * @return string */ - public function getSeries(); + public function getSeries(): string; /** * Returns the token value. - * - * @return string */ - public function getTokenValue(); + public function getTokenValue(): string; /** * Returns the time the token was last used. * - * @return \DateTime + * Each call SHOULD return a new distinct DateTime instance. */ - public function getLastUsed(); + public function getLastUsed(): \DateTime; /** - * @return string - * - * @deprecated since Symfony 5.3, use getUserIdentifier() instead + * Returns the identifier used to authenticate (e.g. their email address or username). */ - public function getUsername(); + public function getUserIdentifier(): string; } diff --git a/Authentication/RememberMe/TokenProviderInterface.php b/Authentication/RememberMe/TokenProviderInterface.php index eda47300..bfe49015 100644 --- a/Authentication/RememberMe/TokenProviderInterface.php +++ b/Authentication/RememberMe/TokenProviderInterface.php @@ -31,18 +31,24 @@ public function loadTokenBySeries(string $series); /** * Deletes all tokens belonging to series. + * + * @return void */ public function deleteTokenBySeries(string $series); /** * Updates the token according to this data. * + * @return void + * * @throws TokenNotFoundException if the token is not found */ - public function updateToken(string $series, string $tokenValue, \DateTime $lastUsed); + public function updateToken(string $series, #[\SensitiveParameter] string $tokenValue, \DateTimeInterface $lastUsed); /** * Creates a new token. + * + * @return void */ public function createNewToken(PersistentTokenInterface $token); } diff --git a/Authentication/RememberMe/TokenVerifierInterface.php b/Authentication/RememberMe/TokenVerifierInterface.php index 57278d9e..a3231750 100644 --- a/Authentication/RememberMe/TokenVerifierInterface.php +++ b/Authentication/RememberMe/TokenVerifierInterface.php @@ -23,10 +23,10 @@ interface TokenVerifierInterface * * Do not forget to implement token comparisons using hash_equals for a secure implementation. */ - public function verifyToken(PersistentTokenInterface $token, string $tokenValue): bool; + public function verifyToken(PersistentTokenInterface $token, #[\SensitiveParameter] string $tokenValue): bool; /** * Updates an existing token with a new token value and lastUsed time. */ - public function updateExistingToken(PersistentTokenInterface $token, string $tokenValue, \DateTimeInterface $lastUsed): void; + public function updateExistingToken(PersistentTokenInterface $token, #[\SensitiveParameter] string $tokenValue, \DateTimeInterface $lastUsed): void; } diff --git a/Authentication/Token/AbstractToken.php b/Authentication/Token/AbstractToken.php index efe3318a..67d992ce 100644 --- a/Authentication/Token/AbstractToken.php +++ b/Authentication/Token/AbstractToken.php @@ -11,8 +11,7 @@ namespace Symfony\Component\Security\Core\Authentication\Token; -use Symfony\Component\Security\Core\User\EquatableInterface; -use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; +use Symfony\Component\Security\Core\User\InMemoryUser; use Symfony\Component\Security\Core\User\UserInterface; /** @@ -21,12 +20,11 @@ * @author Fabien Potencier * @author Johannes M. Schmitt */ -abstract class AbstractToken implements TokenInterface +abstract class AbstractToken implements TokenInterface, \Serializable { - private $user; - private $roleNames = []; - private $authenticated = false; - private $attributes = []; + private ?UserInterface $user = null; + private array $roleNames = []; + private array $attributes = []; /** * @param string[] $roles An array of roles @@ -40,129 +38,27 @@ public function __construct(array $roles = []) } } - /** - * {@inheritdoc} - */ public function getRoleNames(): array { return $this->roleNames; } - /** - * {@inheritdoc} - */ - public function getUsername(/* $legacy = true */) - { - if (1 === \func_num_args() && false === func_get_arg(0)) { - return null; - } - - trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use getUserIdentifier() instead.', __METHOD__); - - if ($this->user instanceof UserInterface) { - return method_exists($this->user, 'getUserIdentifier') ? $this->user->getUserIdentifier() : $this->user->getUsername(); - } - - return (string) $this->user; - } - - /** - * {@inheritdoc} - */ public function getUserIdentifier(): string { - // method returns "null" in non-legacy mode if not overridden - $username = $this->getUsername(false); - if (null !== $username) { - trigger_deprecation('symfony/security-core', '5.3', 'Method "%s::getUsername()" is deprecated, override "getUserIdentifier()" instead.', get_debug_type($this)); - } - - if ($this->user instanceof UserInterface) { - // @deprecated since Symfony 5.3, change to $user->getUserIdentifier() in 6.0 - return method_exists($this->user, 'getUserIdentifier') ? $this->user->getUserIdentifier() : $this->user->getUsername(); - } - - return (string) $this->user; + return $this->user ? $this->user->getUserIdentifier() : ''; } - /** - * {@inheritdoc} - */ - public function getUser() + public function getUser(): ?UserInterface { return $this->user; } - /** - * {@inheritdoc} - */ - public function setUser($user) + public function setUser(UserInterface $user): void { - if (!($user instanceof UserInterface || (\is_object($user) && method_exists($user, '__toString')) || \is_string($user))) { - throw new \InvalidArgumentException('$user must be an instanceof UserInterface, an object implementing a __toString method, or a primitive string.'); - } - - if (!$user instanceof UserInterface) { - trigger_deprecation('symfony/security-core', '5.4', 'Using an object that is not an instance of "%s" as $user in "%s" is deprecated.', UserInterface::class, static::class); - } - - // @deprecated since Symfony 5.4, remove the whole block if/elseif/else block in 6.0 - if (1 < \func_num_args() && !func_get_arg(1)) { - // ContextListener checks if the user has changed on its own and calls `setAuthenticated()` subsequently, - // avoid doing the same checks twice - $changed = false; - } elseif (null === $this->user) { - $changed = false; - } elseif ($this->user instanceof UserInterface) { - if (!$user instanceof UserInterface) { - $changed = true; - } else { - $changed = $this->hasUserChanged($user); - } - } elseif ($user instanceof UserInterface) { - $changed = true; - } else { - $changed = (string) $this->user !== (string) $user; - } - - // @deprecated since Symfony 5.4 - if ($changed) { - $this->setAuthenticated(false, false); - } - $this->user = $user; } - /** - * {@inheritdoc} - * - * @deprecated since Symfony 5.4 - */ - public function isAuthenticated() - { - if (1 > \func_num_args() || func_get_arg(0)) { - trigger_deprecation('symfony/security-core', '5.4', 'Method "%s()" is deprecated, return null from "getUser()" instead when a token is not authenticated.', __METHOD__); - } - - return $this->authenticated; - } - - /** - * {@inheritdoc} - */ - public function setAuthenticated(bool $authenticated) - { - if (2 > \func_num_args() || func_get_arg(1)) { - trigger_deprecation('symfony/security-core', '5.4', 'Method "%s()" is deprecated', __METHOD__); - } - - $this->authenticated = $authenticated; - } - - /** - * {@inheritdoc} - */ - public function eraseCredentials() + public function eraseCredentials(): void { if ($this->getUser() instanceof UserInterface) { $this->getUser()->eraseCredentials(); @@ -186,7 +82,7 @@ public function eraseCredentials() */ public function __serialize(): array { - return [$this->user, $this->authenticated, null, $this->attributes, $this->roleNames]; + return [$this->user, true, null, $this->attributes, $this->roleNames]; } /** @@ -207,57 +103,40 @@ public function __serialize(): array */ public function __unserialize(array $data): void { - [$this->user, $this->authenticated, , $this->attributes, $this->roleNames] = $data; + [$user, , , $this->attributes, $this->roleNames] = $data; + $this->user = \is_string($user) ? new InMemoryUser($user, '', $this->roleNames, false) : $user; } - /** - * {@inheritdoc} - */ - public function getAttributes() + public function getAttributes(): array { return $this->attributes; } - /** - * {@inheritdoc} - */ - public function setAttributes(array $attributes) + public function setAttributes(array $attributes): void { $this->attributes = $attributes; } - /** - * {@inheritdoc} - */ - public function hasAttribute(string $name) + public function hasAttribute(string $name): bool { return \array_key_exists($name, $this->attributes); } - /** - * {@inheritdoc} - */ - public function getAttribute(string $name) + public function getAttribute(string $name): mixed { if (!\array_key_exists($name, $this->attributes)) { - throw new \InvalidArgumentException(sprintf('This token has no "%s" attribute.', $name)); + throw new \InvalidArgumentException(\sprintf('This token has no "%s" attribute.', $name)); } return $this->attributes[$name]; } - /** - * {@inheritdoc} - */ - public function setAttribute(string $name, $value) + public function setAttribute(string $name, mixed $value): void { $this->attributes[$name] = $value; } - /** - * {@inheritdoc} - */ - public function __toString() + public function __toString(): string { $class = static::class; $class = substr($class, strrpos($class, '\\') + 1); @@ -267,7 +146,7 @@ public function __toString() $roles[] = $role; } - return sprintf('%s(user="%s", authenticated=%s, roles="%s")', $class, $this->getUserIdentifier(), json_encode($this->authenticated), implode(', ', $roles)); + return \sprintf('%s(user="%s", roles="%s")', $class, $this->getUserIdentifier(), implode(', ', $roles)); } /** @@ -275,58 +154,14 @@ public function __toString() */ final public function serialize(): string { - return serialize($this->__serialize()); + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); } /** * @internal */ - final public function unserialize($serialized) + final public function unserialize(string $serialized): void { - $this->__unserialize(\is_array($serialized) ? $serialized : unserialize($serialized)); - } - - /** - * @deprecated since Symfony 5.4 - */ - private function hasUserChanged(UserInterface $user): bool - { - if (!($this->user instanceof UserInterface)) { - throw new \BadMethodCallException('Method "hasUserChanged" should be called when current user class is instance of "UserInterface".'); - } - - if ($this->user instanceof EquatableInterface) { - return !(bool) $this->user->isEqualTo($user); - } - - // @deprecated since Symfony 5.3, check for PasswordAuthenticatedUserInterface on both user objects before comparing passwords - if ($this->user->getPassword() !== $user->getPassword()) { - return true; - } - - // @deprecated since Symfony 5.3, check for LegacyPasswordAuthenticatedUserInterface on both user objects before comparing salts - if ($this->user->getSalt() !== $user->getSalt()) { - return true; - } - - $userRoles = array_map('strval', (array) $user->getRoles()); - - if ($this instanceof SwitchUserToken) { - $userRoles[] = 'ROLE_PREVIOUS_ADMIN'; - } - - if (\count($userRoles) !== \count($this->getRoleNames()) || \count($userRoles) !== \count(array_intersect($userRoles, $this->getRoleNames()))) { - return true; - } - - // @deprecated since Symfony 5.3, drop getUsername() in 6.0 - $userIdentifier = function ($user) { - return method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(); - }; - if ($userIdentifier($this->user) !== $userIdentifier($user)) { - return true; - } - - return false; + $this->__unserialize(unserialize($serialized)); } } diff --git a/Authentication/Token/AnonymousToken.php b/Authentication/Token/AnonymousToken.php deleted file mode 100644 index 5d585fe1..00000000 --- a/Authentication/Token/AnonymousToken.php +++ /dev/null @@ -1,79 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Authentication\Token; - -use Symfony\Component\Security\Core\User\UserInterface; - -/** - * AnonymousToken represents an anonymous token. - * - * @author Fabien Potencier - * - * @deprecated since 5.4, anonymous is now represented by the absence of a token - */ -class AnonymousToken extends AbstractToken -{ - private $secret; - - /** - * @param string $secret A secret used to make sure the token is created by the app and not by a malicious client - * @param string|\Stringable|UserInterface $user - * @param string[] $roles - */ - public function __construct(string $secret, $user, array $roles = []) - { - trigger_deprecation('symfony/security-core', '5.4', 'The "%s" class is deprecated.', __CLASS__); - - parent::__construct($roles); - - $this->secret = $secret; - $this->setUser($user); - // @deprecated since Symfony 5.4 - $this->setAuthenticated(true, false); - } - - /** - * {@inheritdoc} - */ - public function getCredentials() - { - return ''; - } - - /** - * Returns the secret. - * - * @return string - */ - public function getSecret() - { - return $this->secret; - } - - /** - * {@inheritdoc} - */ - public function __serialize(): array - { - return [$this->secret, parent::__serialize()]; - } - - /** - * {@inheritdoc} - */ - public function __unserialize(array $data): void - { - [$this->secret, $parentData] = $data; - $parentData = \is_array($parentData) ? $parentData : unserialize($parentData); - parent::__unserialize($parentData); - } -} diff --git a/Authentication/Token/NullToken.php b/Authentication/Token/NullToken.php index 52187bb7..9c2e4892 100644 --- a/Authentication/Token/NullToken.php +++ b/Authentication/Token/NullToken.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Security\Core\Authentication\Token; +use Symfony\Component\Security\Core\User\UserInterface; + /** * @author Wouter de Jong */ @@ -26,78 +28,46 @@ public function getRoleNames(): array return []; } - public function getCredentials() - { - return ''; - } - - public function getUser() + public function getUser(): ?UserInterface { return null; } - public function setUser($user) + public function setUser(UserInterface $user): never { throw new \BadMethodCallException('Cannot set user on a NullToken.'); } - public function getUsername() - { - trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use getUserIdentifier() instead.', __METHOD__); - - return ''; - } - public function getUserIdentifier(): string { return ''; } - /** - * @deprecated since Symfony 5.4 - */ - public function isAuthenticated() - { - if (0 === \func_num_args() || func_get_arg(0)) { - trigger_deprecation('symfony/security-core', '5.4', 'Method "%s()" is deprecated, return null from "getUser()" instead when a token is not authenticated.', __METHOD__); - } - - return true; - } - - /** - * @deprecated since Symfony 5.4 - */ - public function setAuthenticated(bool $isAuthenticated) - { - throw new \BadMethodCallException('Cannot change authentication state of NullToken.'); - } - - public function eraseCredentials() + public function eraseCredentials(): void { } - public function getAttributes() + public function getAttributes(): array { return []; } - public function setAttributes(array $attributes) + public function setAttributes(array $attributes): never { throw new \BadMethodCallException('Cannot set attributes of NullToken.'); } - public function hasAttribute(string $name) + public function hasAttribute(string $name): bool { return false; } - public function getAttribute(string $name) + public function getAttribute(string $name): mixed { return null; } - public function setAttribute(string $name, $value) + public function setAttribute(string $name, mixed $value): never { throw new \BadMethodCallException('Cannot add attribute to NullToken.'); } @@ -110,27 +80,4 @@ public function __serialize(): array public function __unserialize(array $data): void { } - - /** - * @return string - * - * @internal in 5.3 - * - * @final in 5.3 - */ - public function serialize() - { - return ''; - } - - /** - * @return void - * - * @internal in 5.3 - * - * @final in 5.3 - */ - public function unserialize($serialized) - { - } } diff --git a/Authentication/Token/PreAuthenticatedToken.php b/Authentication/Token/PreAuthenticatedToken.php index 329d61cf..5c092404 100644 --- a/Authentication/Token/PreAuthenticatedToken.php +++ b/Authentication/Token/PreAuthenticatedToken.php @@ -20,24 +20,14 @@ */ class PreAuthenticatedToken extends AbstractToken { - private $credentials; - private $firewallName; - /** - * @param UserInterface $user - * @param string $firewallName - * @param string[] $roles + * @param string[] $roles */ - public function __construct($user, /* string */ $firewallName, /* array */ $roles = []) - { - if (\is_string($roles)) { - trigger_deprecation('symfony/security-core', '5.4', 'Argument $credentials of "%s()" is deprecated.', __METHOD__); - - $credentials = $firewallName; - $firewallName = $roles; - $roles = \func_num_args() > 3 ? func_get_arg(3) : []; - } - + public function __construct( + UserInterface $user, + private string $firewallName, + array $roles = [], + ) { parent::__construct($roles); if ('' === $firewallName) { @@ -45,69 +35,21 @@ public function __construct($user, /* string */ $firewallName, /* array */ $role } $this->setUser($user); - $this->credentials = $credentials ?? null; - $this->firewallName = $firewallName; - - if ($roles) { - $this->setAuthenticated(true, false); - } - } - - /** - * Returns the provider key. - * - * @return string The provider key - * - * @deprecated since Symfony 5.2, use getFirewallName() instead - */ - public function getProviderKey() - { - if (1 !== \func_num_args() || true !== func_get_arg(0)) { - trigger_deprecation('symfony/security-core', '5.2', 'Method "%s()" is deprecated, use "getFirewallName()" instead.', __METHOD__); - } - - return $this->firewallName; } public function getFirewallName(): string { - return $this->getProviderKey(true); - } - - /** - * {@inheritdoc} - */ - public function getCredentials() - { - trigger_deprecation('symfony/security-core', '5.4', 'Method "%s()" is deprecated.', __METHOD__); - - return $this->credentials; - } - - /** - * {@inheritdoc} - */ - public function eraseCredentials() - { - parent::eraseCredentials(); - - $this->credentials = null; + return $this->firewallName; } - /** - * {@inheritdoc} - */ public function __serialize(): array { - return [$this->credentials, $this->firewallName, parent::__serialize()]; + return [null, $this->firewallName, parent::__serialize()]; } - /** - * {@inheritdoc} - */ public function __unserialize(array $data): void { - [$this->credentials, $this->firewallName, $parentData] = $data; + [, $this->firewallName, $parentData] = $data; $parentData = \is_array($parentData) ? $parentData : unserialize($parentData); parent::__unserialize($parentData); } diff --git a/Authentication/Token/RememberMeToken.php b/Authentication/Token/RememberMeToken.php index f74ef009..dfbe20ec 100644 --- a/Authentication/Token/RememberMeToken.php +++ b/Authentication/Token/RememberMeToken.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Security\Core\Authentication\Token; +use Symfony\Component\Security\Core\Exception\InvalidArgumentException; use Symfony\Component\Security\Core\User\UserInterface; /** @@ -20,95 +21,50 @@ */ class RememberMeToken extends AbstractToken { - private $secret; - private $firewallName; + private ?string $secret = null; /** - * @param string $secret A secret used to make sure the token is created by the app and not by a malicious client - * * @throws \InvalidArgumentException */ - public function __construct(UserInterface $user, string $firewallName, string $secret) - { + public function __construct( + UserInterface $user, + private string $firewallName, + ) { parent::__construct($user->getRoles()); - if (empty($secret)) { - throw new \InvalidArgumentException('$secret must not be empty.'); + if (\func_num_args() > 2) { + trigger_deprecation('symfony/security-core', '7.2', 'The "$secret" argument of "%s()" is deprecated.', __METHOD__); + $this->secret = func_get_arg(2); } - if ('' === $firewallName) { - throw new \InvalidArgumentException('$firewallName must not be empty.'); + if (!$firewallName) { + throw new InvalidArgumentException('$firewallName must not be empty.'); } - $this->firewallName = $firewallName; - $this->secret = $secret; - $this->setUser($user); - parent::setAuthenticated(true, false); - } - - /** - * {@inheritdoc} - */ - public function setAuthenticated(bool $authenticated) - { - if ($authenticated) { - throw new \LogicException('You cannot set this token to authenticated after creation.'); - } - - parent::setAuthenticated(false, false); - } - - /** - * Returns the provider secret. - * - * @return string The provider secret - * - * @deprecated since Symfony 5.2, use getFirewallName() instead - */ - public function getProviderKey() - { - if (1 !== \func_num_args() || true !== func_get_arg(0)) { - trigger_deprecation('symfony/security-core', '5.2', 'Method "%s()" is deprecated, use "getFirewallName()" instead.', __METHOD__); - } - - return $this->firewallName; } public function getFirewallName(): string { - return $this->getProviderKey(true); - } - - /** - * @return string - */ - public function getSecret() - { - return $this->secret; + return $this->firewallName; } /** - * {@inheritdoc} + * @deprecated since Symfony 7.2 */ - public function getCredentials() + public function getSecret(): string { - trigger_deprecation('symfony/security-core', '5.4', 'Method "%s()" is deprecated.', __METHOD__); + trigger_deprecation('symfony/security-core', '7.2', 'The "%s()" method is deprecated.', __METHOD__); - return ''; + return $this->secret ??= base64_encode(random_bytes(8)); } - /** - * {@inheritdoc} - */ public function __serialize(): array { + // $this->firewallName should be kept at index 1 for compatibility with payloads generated before Symfony 8 return [$this->secret, $this->firewallName, parent::__serialize()]; } - /** - * {@inheritdoc} - */ public function __unserialize(array $data): void { [$this->secret, $this->firewallName, $parentData] = $data; diff --git a/Authentication/Token/Storage/TokenStorage.php b/Authentication/Token/Storage/TokenStorage.php index 9050e1cf..42234f86 100644 --- a/Authentication/Token/Storage/TokenStorage.php +++ b/Authentication/Token/Storage/TokenStorage.php @@ -24,13 +24,10 @@ */ class TokenStorage implements TokenStorageInterface, ResetInterface { - private $token; - private $initializer; + private ?TokenInterface $token = null; + private ?\Closure $initializer = null; - /** - * {@inheritdoc} - */ - public function getToken() + public function getToken(): ?TokenInterface { if ($initializer = $this->initializer) { $this->initializer = null; @@ -40,19 +37,11 @@ public function getToken() return $this->token; } - /** - * {@inheritdoc} - */ - public function setToken(?TokenInterface $token = null) + public function setToken(?TokenInterface $token): void { if ($token) { // ensure any initializer is called $this->getToken(); - - // @deprecated since Symfony 5.3 - if (!method_exists($token, 'getUserIdentifier')) { - trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "getUserIdentifier(): string" in token class "%s" is deprecated. This method will replace "getUsername()" in Symfony 6.0.', get_debug_type($token)); - } } $this->initializer = null; @@ -61,10 +50,10 @@ public function setToken(?TokenInterface $token = null) public function setInitializer(?callable $initializer): void { - $this->initializer = $initializer; + $this->initializer = null === $initializer ? null : $initializer(...); } - public function reset() + public function reset(): void { $this->setToken(null); } diff --git a/Authentication/Token/Storage/TokenStorageInterface.php b/Authentication/Token/Storage/TokenStorageInterface.php index 1b40c2d0..0f611ca4 100644 --- a/Authentication/Token/Storage/TokenStorageInterface.php +++ b/Authentication/Token/Storage/TokenStorageInterface.php @@ -22,15 +22,13 @@ interface TokenStorageInterface { /** * Returns the current security token. - * - * @return TokenInterface|null */ - public function getToken(); + public function getToken(): ?TokenInterface; /** * Sets the authentication token. * * @param TokenInterface|null $token A TokenInterface token, or null if no further authentication information should be stored */ - public function setToken(?TokenInterface $token = null); + public function setToken(?TokenInterface $token): void; } diff --git a/Authentication/Token/Storage/UsageTrackingTokenStorage.php b/Authentication/Token/Storage/UsageTrackingTokenStorage.php index 698c3be2..4255491d 100644 --- a/Authentication/Token/Storage/UsageTrackingTokenStorage.php +++ b/Authentication/Token/Storage/UsageTrackingTokenStorage.php @@ -24,19 +24,14 @@ */ final class UsageTrackingTokenStorage implements TokenStorageInterface, ServiceSubscriberInterface { - private $storage; - private $container; - private $enableUsageTracking = false; + private bool $enableUsageTracking = false; - public function __construct(TokenStorageInterface $storage, ContainerInterface $container) - { - $this->storage = $storage; - $this->container = $container; + public function __construct( + private TokenStorageInterface $storage, + private ContainerInterface $container, + ) { } - /** - * {@inheritdoc} - */ public function getToken(): ?TokenInterface { if ($this->shouldTrackUsage()) { @@ -47,9 +42,6 @@ public function getToken(): ?TokenInterface return $this->storage->getToken(); } - /** - * {@inheritdoc} - */ public function setToken(?TokenInterface $token = null): void { $this->storage->setToken($token); @@ -79,31 +71,11 @@ public static function getSubscribedServices(): array private function getSession(): SessionInterface { - // BC for symfony/security-bundle < 5.3 - if ($this->container->has('session')) { - trigger_deprecation('symfony/security-core', '5.3', 'Injecting the "session" in "%s" is deprecated, inject the "request_stack" instead.', __CLASS__); - - return $this->container->get('session'); - } - return $this->container->get('request_stack')->getSession(); } private function shouldTrackUsage(): bool { - if (!$this->enableUsageTracking) { - return false; - } - - // BC for symfony/security-bundle < 5.3 - if ($this->container->has('session')) { - return true; - } - - if (!$this->container->get('request_stack')->getMainRequest()) { - return false; - } - - return true; + return $this->enableUsageTracking && $this->container->get('request_stack')->getMainRequest(); } } diff --git a/Authentication/Token/SwitchUserToken.php b/Authentication/Token/SwitchUserToken.php index c5b0991e..c4e69766 100644 --- a/Authentication/Token/SwitchUserToken.php +++ b/Authentication/Token/SwitchUserToken.php @@ -20,35 +20,23 @@ */ class SwitchUserToken extends UsernamePasswordToken { - private $originalToken; - private $originatedFromUri; + private ?string $originatedFromUri = null; /** - * @param UserInterface $user - * @param string|null $originatedFromUri The URI where was the user at the switch + * @param $user The username (like a nickname, email address, etc.), or a UserInterface instance or an object implementing a __toString method + * @param $originatedFromUri The URI where was the user at the switch * * @throws \InvalidArgumentException */ - public function __construct($user, /* string */ $firewallName, /* array */ $roles, /* TokenInterface */ $originalToken, /* string */ $originatedFromUri = null) - { - if (\is_string($roles)) { - // @deprecated since 5.4, deprecation is triggered by UsernamePasswordToken::__construct() - $credentials = $firewallName; - $firewallName = $roles; - $roles = $originalToken; - $originalToken = $originatedFromUri; - $originatedFromUri = \func_num_args() > 5 ? func_get_arg(5) : null; - - parent::__construct($user, $credentials, $firewallName, $roles); - } else { - parent::__construct($user, $firewallName, $roles); - } - - if (!$originalToken instanceof TokenInterface) { - throw new \TypeError(sprintf('Argument $originalToken of "%s" must be an instance of "%s", "%s" given.', __METHOD__, TokenInterface::class, get_debug_type($originalToken))); - } + public function __construct( + UserInterface $user, + string $firewallName, + array $roles, + private TokenInterface $originalToken, + ?string $originatedFromUri = null, + ) { + parent::__construct($user, $firewallName, $roles); - $this->originalToken = $originalToken; $this->originatedFromUri = $originatedFromUri; } @@ -62,17 +50,11 @@ public function getOriginatedFromUri(): ?string return $this->originatedFromUri; } - /** - * {@inheritdoc} - */ public function __serialize(): array { return [$this->originalToken, $this->originatedFromUri, parent::__serialize()]; } - /** - * {@inheritdoc} - */ public function __unserialize(array $data): void { if (3 > \count($data)) { diff --git a/Authentication/Token/TokenInterface.php b/Authentication/Token/TokenInterface.php index ac4bab67..1e67b1e5 100644 --- a/Authentication/Token/TokenInterface.php +++ b/Authentication/Token/TokenInterface.php @@ -16,103 +16,64 @@ /** * TokenInterface is the interface for the user authentication information. * - * @method string getUserIdentifier() returns the user identifier used during authentication (e.g. a user's email address or username) - * * @author Fabien Potencier * @author Johannes M. Schmitt */ -interface TokenInterface extends \Serializable +interface TokenInterface extends \Stringable { /** * Returns a string representation of the Token. * * This is only to be used for debugging purposes. - * - * @return string */ - public function __toString(); + public function __toString(): string; /** - * Returns the user roles. - * - * @return string[] + * Returns the user identifier used during authentication (e.g. a user's email address or username). */ - public function getRoleNames(): array; + public function getUserIdentifier(): string; /** - * Returns the user credentials. - * - * @return mixed + * Returns the user roles. * - * @deprecated since Symfony 5.4 + * @return string[] */ - public function getCredentials(); + public function getRoleNames(): array; /** * Returns a user representation. * - * @return UserInterface|null - * * @see AbstractToken::setUser() */ - public function getUser(); + public function getUser(): ?UserInterface; /** * Sets the authenticated user in the token. * - * @param UserInterface $user - * * @throws \InvalidArgumentException */ - public function setUser($user); - - /** - * Returns whether the user is authenticated or not. - * - * @return bool true if the token has been authenticated, false otherwise - * - * @deprecated since Symfony 5.4, return null from "getUser()" instead when a token is not authenticated - */ - public function isAuthenticated(); - - /** - * Sets the authenticated flag. - * - * @deprecated since Symfony 5.4 - */ - public function setAuthenticated(bool $isAuthenticated); + public function setUser(UserInterface $user): void; /** * Removes sensitive information from the token. */ - public function eraseCredentials(); + public function eraseCredentials(): void; - /** - * @return array - */ - public function getAttributes(); + public function getAttributes(): array; /** * @param array $attributes The token attributes */ - public function setAttributes(array $attributes); + public function setAttributes(array $attributes): void; - /** - * @return bool - */ - public function hasAttribute(string $name); + public function hasAttribute(string $name): bool; /** - * @return mixed - * * @throws \InvalidArgumentException When attribute doesn't exist for this token */ - public function getAttribute(string $name); + public function getAttribute(string $name): mixed; - /** - * @param mixed $value The attribute value - */ - public function setAttribute(string $name, $value); + public function setAttribute(string $name, mixed $value): void; /** * Returns all the necessary state of the object for serialization purposes. @@ -123,11 +84,4 @@ public function __serialize(): array; * Restores the object state from an array given by __serialize(). */ public function __unserialize(array $data): void; - - /** - * @return string - * - * @deprecated since Symfony 5.3, use getUserIdentifier() instead - */ - public function getUsername(); } diff --git a/Authentication/Token/UsernamePasswordToken.php b/Authentication/Token/UsernamePasswordToken.php index 34bcc4ad..40beb003 100644 --- a/Authentication/Token/UsernamePasswordToken.php +++ b/Authentication/Token/UsernamePasswordToken.php @@ -20,25 +20,11 @@ */ class UsernamePasswordToken extends AbstractToken { - private $credentials; - private $firewallName; - - /** - * @param UserInterface $user - * @param string[] $roles - * - * @throws \InvalidArgumentException - */ - public function __construct($user, /* string */ $firewallName, /* array */ $roles = []) - { - if (\is_string($roles)) { - trigger_deprecation('symfony/security-core', '5.4', 'The $credentials argument of "%s" is deprecated.', static::class.'::__construct'); - - $credentials = $firewallName; - $firewallName = $roles; - $roles = \func_num_args() > 3 ? func_get_arg(3) : []; - } - + public function __construct( + UserInterface $user, + private string $firewallName, + array $roles = [], + ) { parent::__construct($roles); if ('' === $firewallName) { @@ -46,79 +32,21 @@ public function __construct($user, /* string */ $firewallName, /* array */ $role } $this->setUser($user); - $this->credentials = $credentials ?? null; - $this->firewallName = $firewallName; - - parent::setAuthenticated(\count($roles) > 0, false); - } - - /** - * {@inheritdoc} - */ - public function setAuthenticated(bool $isAuthenticated) - { - if ($isAuthenticated) { - throw new \LogicException('Cannot set this token to trusted after instantiation.'); - } - - parent::setAuthenticated(false, false); - } - - /** - * {@inheritdoc} - */ - public function getCredentials() - { - trigger_deprecation('symfony/security-core', '5.4', 'Method "%s" is deprecated.', __METHOD__); - - return $this->credentials; - } - - /** - * Returns the provider key. - * - * @return string The provider key - * - * @deprecated since Symfony 5.2, use getFirewallName() instead - */ - public function getProviderKey() - { - if (1 !== \func_num_args() || true !== func_get_arg(0)) { - trigger_deprecation('symfony/security-core', '5.2', 'Method "%s" is deprecated, use "getFirewallName()" instead.', __METHOD__); - } - - return $this->firewallName; } public function getFirewallName(): string { - return $this->getProviderKey(true); - } - - /** - * {@inheritdoc} - */ - public function eraseCredentials() - { - parent::eraseCredentials(); - - $this->credentials = null; + return $this->firewallName; } - /** - * {@inheritdoc} - */ public function __serialize(): array { - return [$this->credentials, $this->firewallName, parent::__serialize()]; + return [null, $this->firewallName, parent::__serialize()]; } - /** - * {@inheritdoc} - */ public function __unserialize(array $data): void { - [$this->credentials, $this->firewallName, $parentData] = $data; + [, $this->firewallName, $parentData] = $data; $parentData = \is_array($parentData) ? $parentData : unserialize($parentData); parent::__unserialize($parentData); } diff --git a/AuthenticationEvents.php b/AuthenticationEvents.php index fc286d2a..a1c3e5dd 100644 --- a/AuthenticationEvents.php +++ b/AuthenticationEvents.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Security\Core; -use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent; use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent; final class AuthenticationEvents @@ -24,16 +23,6 @@ final class AuthenticationEvents */ public const AUTHENTICATION_SUCCESS = 'security.authentication.success'; - /** - * The AUTHENTICATION_FAILURE event occurs after a user cannot be - * authenticated by any of the providers. - * - * @Event("Symfony\Component\Security\Core\Event\AuthenticationFailureEvent") - * - * @deprecated since Symfony 5.4, use {@see Event\LoginFailureEvent} instead - */ - public const AUTHENTICATION_FAILURE = 'security.authentication.failure'; - /** * Event aliases. * @@ -41,6 +30,5 @@ final class AuthenticationEvents */ public const ALIASES = [ AuthenticationSuccessEvent::class => self::AUTHENTICATION_SUCCESS, - AuthenticationFailureEvent::class => self::AUTHENTICATION_FAILURE, ]; } diff --git a/Authorization/AccessDecisionManager.php b/Authorization/AccessDecisionManager.php index cc59035b..3e42c4bf 100644 --- a/Authorization/AccessDecisionManager.php +++ b/Authorization/AccessDecisionManager.php @@ -14,9 +14,6 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Strategy\AccessDecisionStrategyInterface; use Symfony\Component\Security\Core\Authorization\Strategy\AffirmativeStrategy; -use Symfony\Component\Security\Core\Authorization\Strategy\ConsensusStrategy; -use Symfony\Component\Security\Core\Authorization\Strategy\PriorityStrategy; -use Symfony\Component\Security\Core\Authorization\Strategy\UnanimousStrategy; use Symfony\Component\Security\Core\Authorization\Voter\CacheableVoterInterface; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; use Symfony\Component\Security\Core\Exception\InvalidArgumentException; @@ -26,76 +23,37 @@ * that use decision voters. * * @author Fabien Potencier - * - * @final since Symfony 5.4 */ -class AccessDecisionManager implements AccessDecisionManagerInterface +final class AccessDecisionManager implements AccessDecisionManagerInterface { - /** - * @deprecated use {@see AffirmativeStrategy} instead - */ - public const STRATEGY_AFFIRMATIVE = 'affirmative'; - - /** - * @deprecated use {@see ConsensusStrategy} instead - */ - public const STRATEGY_CONSENSUS = 'consensus'; - - /** - * @deprecated use {@see UnanimousStrategy} instead - */ - public const STRATEGY_UNANIMOUS = 'unanimous'; - - /** - * @deprecated use {@see PriorityStrategy} instead - */ - public const STRATEGY_PRIORITY = 'priority'; - private const VALID_VOTES = [ VoterInterface::ACCESS_GRANTED => true, VoterInterface::ACCESS_DENIED => true, VoterInterface::ACCESS_ABSTAIN => true, ]; - private $voters; - private $votersCacheAttributes; - private $votersCacheObject; - private $strategy; + private array $votersCacheAttributes = []; + private array $votersCacheObject = []; + private AccessDecisionStrategyInterface $strategy; /** - * @param iterable $voters An array or an iterator of VoterInterface instances - * @param AccessDecisionStrategyInterface|null $strategy The vote strategy - * - * @throws \InvalidArgumentException + * @param iterable $voters An array or an iterator of VoterInterface instances */ - public function __construct(iterable $voters = [], /* ?AccessDecisionStrategyInterface */ $strategy = null) - { - $this->voters = $voters; - if (\is_string($strategy)) { - trigger_deprecation('symfony/security-core', '5.4', 'Passing the access decision strategy as a string is deprecated, pass an instance of "%s" instead.', AccessDecisionStrategyInterface::class); - $allowIfAllAbstainDecisions = 3 <= \func_num_args() && func_get_arg(2); - $allowIfEqualGrantedDeniedDecisions = 4 > \func_num_args() || func_get_arg(3); - - $strategy = $this->createStrategy($strategy, $allowIfAllAbstainDecisions, $allowIfEqualGrantedDeniedDecisions); - } elseif (null !== $strategy && !$strategy instanceof AccessDecisionStrategyInterface) { - throw new \TypeError(sprintf('"%s": Parameter #2 ($strategy) is expected to be an instance of "%s" or null, "%s" given.', __METHOD__, AccessDecisionStrategyInterface::class, get_debug_type($strategy))); - } - + public function __construct( + private iterable $voters = [], + ?AccessDecisionStrategyInterface $strategy = null, + ) { $this->strategy = $strategy ?? new AffirmativeStrategy(); } /** * @param bool $allowMultipleAttributes Whether to allow passing multiple values to the $attributes array - * - * {@inheritdoc} */ - public function decide(TokenInterface $token, array $attributes, $object = null/* , bool $allowMultipleAttributes = false */) + public function decide(TokenInterface $token, array $attributes, mixed $object = null, bool $allowMultipleAttributes = false): bool { - $allowMultipleAttributes = 3 < \func_num_args() && func_get_arg(3); - // Special case for AccessListener, do not remove the right side of the condition before 6.0 if (\count($attributes) > 1 && !$allowMultipleAttributes) { - throw new InvalidArgumentException(sprintf('Passing more than one Security attribute to "%s()" is not supported.', __METHOD__)); + throw new InvalidArgumentException(\sprintf('Passing more than one Security attribute to "%s()" is not supported.', __METHOD__)); } return $this->strategy->decide( @@ -104,41 +62,20 @@ public function decide(TokenInterface $token, array $attributes, $object = null/ } /** - * @param mixed $object - * * @return \Traversable */ - private function collectResults(TokenInterface $token, array $attributes, $object): \Traversable + private function collectResults(TokenInterface $token, array $attributes, mixed $object): \Traversable { foreach ($this->getVoters($attributes, $object) as $voter) { $result = $voter->vote($token, $object, $attributes); if (!\is_int($result) || !(self::VALID_VOTES[$result] ?? false)) { - trigger_deprecation('symfony/security-core', '5.3', 'Returning "%s" in "%s::vote()" is deprecated, return one of "%s" constants: "ACCESS_GRANTED", "ACCESS_DENIED" or "ACCESS_ABSTAIN".', var_export($result, true), get_debug_type($voter), VoterInterface::class); + throw new \LogicException(\sprintf('"%s::vote()" must return one of "%s" constants ("ACCESS_GRANTED", "ACCESS_DENIED" or "ACCESS_ABSTAIN"), "%s" returned.', get_debug_type($voter), VoterInterface::class, var_export($result, true))); } yield $result; } } - /** - * @throws \InvalidArgumentException if the $strategy is invalid - */ - private function createStrategy(string $strategy, bool $allowIfAllAbstainDecisions, bool $allowIfEqualGrantedDeniedDecisions): AccessDecisionStrategyInterface - { - switch ($strategy) { - case self::STRATEGY_AFFIRMATIVE: - return new AffirmativeStrategy($allowIfAllAbstainDecisions); - case self::STRATEGY_CONSENSUS: - return new ConsensusStrategy($allowIfAllAbstainDecisions, $allowIfEqualGrantedDeniedDecisions); - case self::STRATEGY_UNANIMOUS: - return new UnanimousStrategy($allowIfAllAbstainDecisions); - case self::STRATEGY_PRIORITY: - return new PriorityStrategy($allowIfAllAbstainDecisions); - } - - throw new \InvalidArgumentException(sprintf('The strategy "%s" is not supported.', $strategy)); - } - /** * @return iterable */ @@ -149,7 +86,7 @@ private function getVoters(array $attributes, $object = null): iterable $keyAttributes[] = \is_string($attribute) ? $attribute : null; } // use `get_class` to handle anonymous classes - $keyObject = \is_object($object) ? \get_class($object) : get_debug_type($object); + $keyObject = \is_object($object) ? $object::class : get_debug_type($object); foreach ($this->voters as $key => $voter) { if (!$voter instanceof CacheableVoterInterface) { yield $voter; diff --git a/Authorization/AccessDecisionManagerInterface.php b/Authorization/AccessDecisionManagerInterface.php index b8078611..f25c7e1b 100644 --- a/Authorization/AccessDecisionManagerInterface.php +++ b/Authorization/AccessDecisionManagerInterface.php @@ -25,8 +25,6 @@ interface AccessDecisionManagerInterface * * @param array $attributes An array of attributes associated with the method being invoked * @param mixed $object The object to secure - * - * @return bool */ - public function decide(TokenInterface $token, array $attributes, $object = null); + public function decide(TokenInterface $token, array $attributes, mixed $object = null): bool; } diff --git a/Authorization/AuthorizationChecker.php b/Authorization/AuthorizationChecker.php index 82441c08..c748697c 100644 --- a/Authorization/AuthorizationChecker.php +++ b/Authorization/AuthorizationChecker.php @@ -11,10 +11,8 @@ namespace Symfony\Component\Security\Core\Authorization; -use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\NullToken; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; -use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; /** * AuthorizationChecker is the main authorization point of the Security component. @@ -26,64 +24,18 @@ */ class AuthorizationChecker implements AuthorizationCheckerInterface { - private $tokenStorage; - private $accessDecisionManager; - private $authenticationManager; - private $alwaysAuthenticate; - private $exceptionOnNoToken; - - public function __construct(TokenStorageInterface $tokenStorage, /* AccessDecisionManagerInterface */ $accessDecisionManager, /* bool */ $alwaysAuthenticate = false, /* bool */ $exceptionOnNoToken = true) - { - if ($accessDecisionManager instanceof AuthenticationManagerInterface) { - trigger_deprecation('symfony/security-core', '5.4', 'The $autenticationManager argument of "%s" is deprecated.', __METHOD__); - - $this->authenticationManager = $accessDecisionManager; - $accessDecisionManager = $alwaysAuthenticate; - $alwaysAuthenticate = $exceptionOnNoToken; - $exceptionOnNoToken = \func_num_args() > 4 ? func_get_arg(4) : true; - } - - if (false !== $alwaysAuthenticate) { - trigger_deprecation('symfony/security-core', '5.4', 'Not setting the 4th argument of "%s" to "false" is deprecated.', __METHOD__); - } - if (false !== $exceptionOnNoToken) { - trigger_deprecation('symfony/security-core', '5.4', 'Not setting the 5th argument of "%s" to "false" is deprecated.', __METHOD__); - } - - if (!$accessDecisionManager instanceof AccessDecisionManagerInterface) { - throw new \TypeError(sprintf('Argument 2 of "%s" must be instance of "%s", "%s" given.', __METHOD__, AccessDecisionManagerInterface::class, get_debug_type($accessDecisionManager))); - } - - $this->tokenStorage = $tokenStorage; - $this->accessDecisionManager = $accessDecisionManager; - $this->alwaysAuthenticate = $alwaysAuthenticate; - $this->exceptionOnNoToken = $exceptionOnNoToken; + public function __construct( + private TokenStorageInterface $tokenStorage, + private AccessDecisionManagerInterface $accessDecisionManager, + ) { } - /** - * {@inheritdoc} - * - * @throws AuthenticationCredentialsNotFoundException when the token storage has no authentication token and $exceptionOnNoToken is set to true - */ - final public function isGranted($attribute, $subject = null): bool + final public function isGranted(mixed $attribute, mixed $subject = null): bool { $token = $this->tokenStorage->getToken(); if (!$token || !$token->getUser()) { - if ($this->exceptionOnNoToken) { - throw new AuthenticationCredentialsNotFoundException('The token storage contains no authentication token. One possible reason may be that there is no firewall configured for this URL.'); - } - $token = new NullToken(); - } else { - $authenticated = true; - // @deprecated since Symfony 5.4 - if ($this->alwaysAuthenticate || !$authenticated = $token->isAuthenticated(false)) { - if (!($authenticated ?? true)) { - trigger_deprecation('symfony/core', '5.4', 'Returning false from "%s::isAuthenticated()" is deprecated, return null from "getUser()" instead.', get_debug_type($token)); - } - $this->tokenStorage->setToken($token = $this->authenticationManager->authenticate($token)); - } } return $this->accessDecisionManager->decide($token, [$attribute], $subject); diff --git a/Authorization/AuthorizationCheckerInterface.php b/Authorization/AuthorizationCheckerInterface.php index f60c80b7..6f5a6022 100644 --- a/Authorization/AuthorizationCheckerInterface.php +++ b/Authorization/AuthorizationCheckerInterface.php @@ -22,9 +22,6 @@ interface AuthorizationCheckerInterface * Checks if the attribute is granted against the current authentication token and optionally supplied subject. * * @param mixed $attribute A single attribute to vote on (can be of any type, string and instance of Expression are supported by the core) - * @param mixed $subject - * - * @return bool */ - public function isGranted($attribute, $subject = null); + public function isGranted(mixed $attribute, mixed $subject = null): bool; } diff --git a/Authorization/ExpressionLanguage.php b/Authorization/ExpressionLanguage.php index d92df677..846d2cf6 100644 --- a/Authorization/ExpressionLanguage.php +++ b/Authorization/ExpressionLanguage.php @@ -15,7 +15,7 @@ use Symfony\Component\ExpressionLanguage\ExpressionLanguage as BaseExpressionLanguage; if (!class_exists(BaseExpressionLanguage::class)) { - throw new \LogicException(sprintf('The "%s" class requires the "ExpressionLanguage" component. Try running "composer require symfony/expression-language".', ExpressionLanguage::class)); + throw new \LogicException(\sprintf('The "%s" class requires the "ExpressionLanguage" component. Try running "composer require symfony/expression-language".', ExpressionLanguage::class)); } else { // Help opcache.preload discover always-needed symbols class_exists(ExpressionLanguageProvider::class); @@ -29,9 +29,6 @@ class_exists(ExpressionLanguageProvider::class); */ class ExpressionLanguage extends BaseExpressionLanguage { - /** - * {@inheritdoc} - */ public function __construct(?CacheItemPoolInterface $cache = null, array $providers = []) { // prepend the default provider to let users override it easily diff --git a/Authorization/ExpressionLanguageProvider.php b/Authorization/ExpressionLanguageProvider.php index ba2ba264..2e558c21 100644 --- a/Authorization/ExpressionLanguageProvider.php +++ b/Authorization/ExpressionLanguageProvider.php @@ -13,7 +13,6 @@ use Symfony\Component\ExpressionLanguage\ExpressionFunction; use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; -use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter; /** * Define some ExpressionLanguage functions. @@ -22,41 +21,16 @@ */ class ExpressionLanguageProvider implements ExpressionFunctionProviderInterface { - public function getFunctions() + public function getFunctions(): array { return [ - new ExpressionFunction('is_anonymous', function () { - return 'trigger_deprecation("symfony/security-core", "5.4", "The \"is_anonymous()\" expression function is deprecated.") || ($token && $auth_checker->isGranted("IS_ANONYMOUS"))'; - }, function (array $variables) { - trigger_deprecation('symfony/security-core', '5.4', 'The "is_anonymous()" expression function is deprecated.'); + new ExpressionFunction('is_authenticated', fn () => '$auth_checker->isGranted("IS_AUTHENTICATED")', fn (array $variables) => $variables['auth_checker']->isGranted('IS_AUTHENTICATED')), - return $variables['token'] && $variables['auth_checker']->isGranted('IS_ANONYMOUS'); - }), + new ExpressionFunction('is_fully_authenticated', fn () => '$token && $auth_checker->isGranted("IS_AUTHENTICATED_FULLY")', fn (array $variables) => $variables['token'] && $variables['auth_checker']->isGranted('IS_AUTHENTICATED_FULLY')), - // @deprecated remove the ternary and always use IS_AUTHENTICATED in 6.0 - new ExpressionFunction('is_authenticated', function () { - return 'defined("'.AuthenticatedVoter::class.'::IS_AUTHENTICATED") ? $auth_checker->isGranted("IS_AUTHENTICATED") : ($token && !$auth_checker->isGranted("IS_ANONYMOUS"))'; - }, function (array $variables) { - return \defined(AuthenticatedVoter::class.'::IS_AUTHENTICATED') ? $variables['auth_checker']->isGranted('IS_AUTHENTICATED') : ($variables['token'] && !$variables['auth_checker']->isGranted('IS_ANONYMOUS')); - }), + new ExpressionFunction('is_granted', fn ($attributes, $object = 'null') => \sprintf('$auth_checker->isGranted(%s, %s)', $attributes, $object), fn (array $variables, $attributes, $object = null) => $variables['auth_checker']->isGranted($attributes, $object)), - new ExpressionFunction('is_fully_authenticated', function () { - return '$token && $auth_checker->isGranted("IS_AUTHENTICATED_FULLY")'; - }, function (array $variables) { - return $variables['token'] && $variables['auth_checker']->isGranted('IS_AUTHENTICATED_FULLY'); - }), - - new ExpressionFunction('is_granted', function ($attributes, $object = 'null') { - return sprintf('$auth_checker->isGranted(%s, %s)', $attributes, $object); - }, function (array $variables, $attributes, $object = null) { - return $variables['auth_checker']->isGranted($attributes, $object); - }), - - new ExpressionFunction('is_remember_me', function () { - return '$token && $auth_checker->isGranted("IS_REMEMBERED")'; - }, function (array $variables) { - return $variables['token'] && $variables['auth_checker']->isGranted('IS_REMEMBERED'); - }), + new ExpressionFunction('is_remember_me', fn () => '$token && $auth_checker->isGranted("IS_REMEMBERED")', fn (array $variables) => $variables['token'] && $variables['auth_checker']->isGranted('IS_REMEMBERED')), ]; } } diff --git a/Authorization/Strategy/AffirmativeStrategy.php b/Authorization/Strategy/AffirmativeStrategy.php index 09011999..fb316fd3 100644 --- a/Authorization/Strategy/AffirmativeStrategy.php +++ b/Authorization/Strategy/AffirmativeStrategy.php @@ -24,19 +24,11 @@ */ final class AffirmativeStrategy implements AccessDecisionStrategyInterface, \Stringable { - /** - * @var bool - */ - private $allowIfAllAbstainDecisions; - - public function __construct(bool $allowIfAllAbstainDecisions = false) - { - $this->allowIfAllAbstainDecisions = $allowIfAllAbstainDecisions; + public function __construct( + private bool $allowIfAllAbstainDecisions = false, + ) { } - /** - * {@inheritdoc} - */ public function decide(\Traversable $results): bool { $deny = 0; diff --git a/Authorization/Strategy/ConsensusStrategy.php b/Authorization/Strategy/ConsensusStrategy.php index 0f617801..bff56513 100644 --- a/Authorization/Strategy/ConsensusStrategy.php +++ b/Authorization/Strategy/ConsensusStrategy.php @@ -32,13 +32,10 @@ */ final class ConsensusStrategy implements AccessDecisionStrategyInterface, \Stringable { - private $allowIfAllAbstainDecisions; - private $allowIfEqualGrantedDeniedDecisions; - - public function __construct(bool $allowIfAllAbstainDecisions = false, bool $allowIfEqualGrantedDeniedDecisions = true) - { - $this->allowIfAllAbstainDecisions = $allowIfAllAbstainDecisions; - $this->allowIfEqualGrantedDeniedDecisions = $allowIfEqualGrantedDeniedDecisions; + public function __construct( + private bool $allowIfAllAbstainDecisions = false, + private bool $allowIfEqualGrantedDeniedDecisions = true, + ) { } public function decide(\Traversable $results): bool diff --git a/Authorization/Strategy/PriorityStrategy.php b/Authorization/Strategy/PriorityStrategy.php index b2b416d7..d7f566ad 100644 --- a/Authorization/Strategy/PriorityStrategy.php +++ b/Authorization/Strategy/PriorityStrategy.php @@ -25,16 +25,11 @@ */ final class PriorityStrategy implements AccessDecisionStrategyInterface, \Stringable { - private $allowIfAllAbstainDecisions; - - public function __construct(bool $allowIfAllAbstainDecisions = false) - { - $this->allowIfAllAbstainDecisions = $allowIfAllAbstainDecisions; + public function __construct( + private bool $allowIfAllAbstainDecisions = false, + ) { } - /** - * {@inheritdoc} - */ public function decide(\Traversable $results): bool { foreach ($results as $result) { diff --git a/Authorization/Strategy/UnanimousStrategy.php b/Authorization/Strategy/UnanimousStrategy.php index b2289f96..d47d8994 100644 --- a/Authorization/Strategy/UnanimousStrategy.php +++ b/Authorization/Strategy/UnanimousStrategy.php @@ -24,16 +24,11 @@ */ final class UnanimousStrategy implements AccessDecisionStrategyInterface, \Stringable { - private $allowIfAllAbstainDecisions; - - public function __construct(bool $allowIfAllAbstainDecisions = false) - { - $this->allowIfAllAbstainDecisions = $allowIfAllAbstainDecisions; + public function __construct( + private bool $allowIfAllAbstainDecisions = false, + ) { } - /** - * {@inheritdoc} - */ public function decide(\Traversable $results): bool { $grant = 0; diff --git a/Authorization/TraceableAccessDecisionManager.php b/Authorization/TraceableAccessDecisionManager.php index 7ceea1c9..0b82eb3a 100644 --- a/Authorization/TraceableAccessDecisionManager.php +++ b/Authorization/TraceableAccessDecisionManager.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Core\Authorization; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Strategy\AccessDecisionStrategyInterface; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; /** @@ -24,34 +25,27 @@ */ class TraceableAccessDecisionManager implements AccessDecisionManagerInterface { - private $manager; - private $strategy; + private ?AccessDecisionStrategyInterface $strategy = null; /** @var iterable */ - private $voters = []; - private $decisionLog = []; // All decision logs - private $currentLog = []; // Logs being filled in - - public function __construct(AccessDecisionManagerInterface $manager) - { - $this->manager = $manager; - - if ($this->manager instanceof AccessDecisionManager) { - // The strategy and voters are stored in a private properties of the decorated service - $reflection = new \ReflectionProperty(AccessDecisionManager::class, 'strategy'); - $reflection->setAccessible(true); + private iterable $voters = []; + private array $decisionLog = []; // All decision logs + private array $currentLog = []; // Logs being filled in + + public function __construct( + private AccessDecisionManagerInterface $manager, + ) { + // The strategy and voters are stored in a private properties of the decorated service + if (property_exists($manager, 'strategy')) { + $reflection = new \ReflectionProperty($manager::class, 'strategy'); $this->strategy = $reflection->getValue($manager); - $reflection = new \ReflectionProperty(AccessDecisionManager::class, 'voters'); - $reflection->setAccessible(true); + } + if (property_exists($manager, 'voters')) { + $reflection = new \ReflectionProperty($manager::class, 'voters'); $this->voters = $reflection->getValue($manager); } } - /** - * {@inheritdoc} - * - * @param bool $allowMultipleAttributes Whether to allow passing multiple values to the $attributes array - */ - public function decide(TokenInterface $token, array $attributes, $object = null/* , bool $allowMultipleAttributes = false */): bool + public function decide(TokenInterface $token, array $attributes, mixed $object = null, bool $allowMultipleAttributes = false): bool { $currentDecisionLog = [ 'attributes' => $attributes, @@ -61,7 +55,7 @@ public function decide(TokenInterface $token, array $attributes, $object = null/ $this->currentLog[] = &$currentDecisionLog; - $result = $this->manager->decide($token, $attributes, $object, 3 < \func_num_args() && func_get_arg(3)); + $result = $this->manager->decide($token, $attributes, $object, $allowMultipleAttributes); $currentDecisionLog['result'] = $result; @@ -76,7 +70,7 @@ public function decide(TokenInterface $token, array $attributes, $object = null/ * @param array $attributes attributes used for the vote * @param int $vote vote of the voter */ - public function addVoterVote(VoterInterface $voter, array $attributes, int $vote) + public function addVoterVote(VoterInterface $voter, array $attributes, int $vote): void { $currentLogIndex = \count($this->currentLog) - 1; $this->currentLog[$currentLogIndex]['voterDetails'][] = [ @@ -91,7 +85,7 @@ public function getStrategy(): string if (null === $this->strategy) { return '-'; } - if (method_exists($this->strategy, '__toString')) { + if ($this->strategy instanceof \Stringable) { return (string) $this->strategy; } @@ -111,7 +105,3 @@ public function getDecisionLog(): array return $this->decisionLog; } } - -if (!class_exists(DebugAccessDecisionManager::class, false)) { - class_alias(TraceableAccessDecisionManager::class, DebugAccessDecisionManager::class); -} diff --git a/Authorization/Voter/AuthenticatedVoter.php b/Authorization/Voter/AuthenticatedVoter.php index ae78927c..a0011868 100644 --- a/Authorization/Voter/AuthenticatedVoter.php +++ b/Authorization/Voter/AuthenticatedVoter.php @@ -28,30 +28,17 @@ class AuthenticatedVoter implements CacheableVoterInterface { public const IS_AUTHENTICATED_FULLY = 'IS_AUTHENTICATED_FULLY'; public const IS_AUTHENTICATED_REMEMBERED = 'IS_AUTHENTICATED_REMEMBERED'; - /** - * @deprecated since Symfony 5.4 - */ - public const IS_AUTHENTICATED_ANONYMOUSLY = 'IS_AUTHENTICATED_ANONYMOUSLY'; - /** - * @deprecated since Symfony 5.4 - */ - public const IS_ANONYMOUS = 'IS_ANONYMOUS'; public const IS_AUTHENTICATED = 'IS_AUTHENTICATED'; public const IS_IMPERSONATOR = 'IS_IMPERSONATOR'; public const IS_REMEMBERED = 'IS_REMEMBERED'; public const PUBLIC_ACCESS = 'PUBLIC_ACCESS'; - private $authenticationTrustResolver; - - public function __construct(AuthenticationTrustResolverInterface $authenticationTrustResolver) - { - $this->authenticationTrustResolver = $authenticationTrustResolver; + public function __construct( + private AuthenticationTrustResolverInterface $authenticationTrustResolver, + ) { } - /** - * {@inheritdoc} - */ - public function vote(TokenInterface $token, $subject, array $attributes) + public function vote(TokenInterface $token, mixed $subject, array $attributes): int { if ($attributes === [self::PUBLIC_ACCESS]) { return VoterInterface::ACCESS_GRANTED; @@ -61,9 +48,7 @@ public function vote(TokenInterface $token, $subject, array $attributes) foreach ($attributes as $attribute) { if (null === $attribute || (self::IS_AUTHENTICATED_FULLY !== $attribute && self::IS_AUTHENTICATED_REMEMBERED !== $attribute - && self::IS_AUTHENTICATED_ANONYMOUSLY !== $attribute && self::IS_AUTHENTICATED !== $attribute - && self::IS_ANONYMOUS !== $attribute && self::IS_IMPERSONATOR !== $attribute && self::IS_REMEMBERED !== $attribute)) { continue; @@ -82,20 +67,7 @@ public function vote(TokenInterface $token, $subject, array $attributes) return VoterInterface::ACCESS_GRANTED; } - if (self::IS_AUTHENTICATED_ANONYMOUSLY === $attribute - && ($this->authenticationTrustResolver->isAnonymous($token) - || $this->authenticationTrustResolver->isRememberMe($token) - || $this->authenticationTrustResolver->isFullFledged($token))) { - trigger_deprecation('symfony/security-core', '5.4', 'The "IS_AUTHENTICATED_ANONYMOUSLY" security attribute is deprecated, use "PUBLIC_ACCESS" for public resources, otherwise use "IS_AUTHENTICATED" or "IS_AUTHENTICATED_FULLY" instead if you want to check if the request is (fully) authenticated.'); - - return VoterInterface::ACCESS_GRANTED; - } - - // @deprecated $this->authenticationTrustResolver must implement isAuthenticated() in 6.0 - if (self::IS_AUTHENTICATED === $attribute - && (method_exists($this->authenticationTrustResolver, 'isAuthenticated') - ? $this->authenticationTrustResolver->isAuthenticated($token) - : ($token && $token->getUser()))) { + if (self::IS_AUTHENTICATED === $attribute && $this->authenticationTrustResolver->isAuthenticated($token)) { return VoterInterface::ACCESS_GRANTED; } @@ -103,12 +75,6 @@ public function vote(TokenInterface $token, $subject, array $attributes) return VoterInterface::ACCESS_GRANTED; } - if (self::IS_ANONYMOUS === $attribute && $this->authenticationTrustResolver->isAnonymous($token)) { - trigger_deprecation('symfony/security-core', '5.4', 'The "IS_ANONYMOUSLY" security attribute is deprecated, anonymous no longer exists in version 6.'); - - return VoterInterface::ACCESS_GRANTED; - } - if (self::IS_IMPERSONATOR === $attribute && $token instanceof SwitchUserToken) { return VoterInterface::ACCESS_GRANTED; } @@ -122,9 +88,7 @@ public function supportsAttribute(string $attribute): bool return \in_array($attribute, [ self::IS_AUTHENTICATED_FULLY, self::IS_AUTHENTICATED_REMEMBERED, - self::IS_AUTHENTICATED_ANONYMOUSLY, self::IS_AUTHENTICATED, - self::IS_ANONYMOUS, self::IS_IMPERSONATOR, self::IS_REMEMBERED, self::PUBLIC_ACCESS, diff --git a/Authorization/Voter/ExpressionVoter.php b/Authorization/Voter/ExpressionVoter.php index c22def8b..bab32830 100644 --- a/Authorization/Voter/ExpressionVoter.php +++ b/Authorization/Voter/ExpressionVoter.php @@ -26,17 +26,12 @@ */ class ExpressionVoter implements CacheableVoterInterface { - private $expressionLanguage; - private $trustResolver; - private $authChecker; - private $roleHierarchy; - - public function __construct(ExpressionLanguage $expressionLanguage, AuthenticationTrustResolverInterface $trustResolver, AuthorizationCheckerInterface $authChecker, ?RoleHierarchyInterface $roleHierarchy = null) - { - $this->expressionLanguage = $expressionLanguage; - $this->trustResolver = $trustResolver; - $this->authChecker = $authChecker; - $this->roleHierarchy = $roleHierarchy; + public function __construct( + private ExpressionLanguage $expressionLanguage, + private AuthenticationTrustResolverInterface $trustResolver, + private AuthorizationCheckerInterface $authChecker, + private ?RoleHierarchyInterface $roleHierarchy = null, + ) { } public function supportsAttribute(string $attribute): bool @@ -49,10 +44,7 @@ public function supportsType(string $subjectType): bool return true; } - /** - * {@inheritdoc} - */ - public function vote(TokenInterface $token, $subject, array $attributes) + public function vote(TokenInterface $token, mixed $subject, array $attributes): int { $result = VoterInterface::ACCESS_ABSTAIN; $variables = null; @@ -61,9 +53,7 @@ public function vote(TokenInterface $token, $subject, array $attributes) continue; } - if (null === $variables) { - $variables = $this->getVariables($token, $subject); - } + $variables ??= $this->getVariables($token, $subject); $result = VoterInterface::ACCESS_DENIED; if ($this->expressionLanguage->evaluate($attribute, $variables)) { @@ -74,7 +64,7 @@ public function vote(TokenInterface $token, $subject, array $attributes) return $result; } - private function getVariables(TokenInterface $token, $subject): array + private function getVariables(TokenInterface $token, mixed $subject): array { $roleNames = $token->getRoleNames(); diff --git a/Authorization/Voter/RoleHierarchyVoter.php b/Authorization/Voter/RoleHierarchyVoter.php index d8f2b34c..110faa03 100644 --- a/Authorization/Voter/RoleHierarchyVoter.php +++ b/Authorization/Voter/RoleHierarchyVoter.php @@ -22,19 +22,14 @@ */ class RoleHierarchyVoter extends RoleVoter { - private $roleHierarchy; - - public function __construct(RoleHierarchyInterface $roleHierarchy, string $prefix = 'ROLE_') - { - $this->roleHierarchy = $roleHierarchy; - + public function __construct( + private RoleHierarchyInterface $roleHierarchy, + string $prefix = 'ROLE_', + ) { parent::__construct($prefix); } - /** - * {@inheritdoc} - */ - protected function extractRoles(TokenInterface $token) + protected function extractRoles(TokenInterface $token): array { return $this->roleHierarchy->getReachableRoleNames($token->getRoleNames()); } diff --git a/Authorization/Voter/RoleVoter.php b/Authorization/Voter/RoleVoter.php index f1d993a6..3c65fb63 100644 --- a/Authorization/Voter/RoleVoter.php +++ b/Authorization/Voter/RoleVoter.php @@ -20,17 +20,12 @@ */ class RoleVoter implements CacheableVoterInterface { - private $prefix; - - public function __construct(string $prefix = 'ROLE_') - { - $this->prefix = $prefix; + public function __construct( + private string $prefix = 'ROLE_', + ) { } - /** - * {@inheritdoc} - */ - public function vote(TokenInterface $token, $subject, array $attributes) + public function vote(TokenInterface $token, mixed $subject, array $attributes): int { $result = VoterInterface::ACCESS_ABSTAIN; $roles = $this->extractRoles($token); @@ -40,15 +35,9 @@ public function vote(TokenInterface $token, $subject, array $attributes) continue; } - if ('ROLE_PREVIOUS_ADMIN' === $attribute) { - trigger_deprecation('symfony/security-core', '5.1', 'The ROLE_PREVIOUS_ADMIN role is deprecated and will be removed in version 6.0, use the IS_IMPERSONATOR attribute instead.'); - } - $result = VoterInterface::ACCESS_DENIED; - foreach ($roles as $role) { - if ($attribute === $role) { - return VoterInterface::ACCESS_GRANTED; - } + if (\in_array($attribute, $roles, true)) { + return VoterInterface::ACCESS_GRANTED; } } @@ -65,7 +54,7 @@ public function supportsType(string $subjectType): bool return true; } - protected function extractRoles(TokenInterface $token) + protected function extractRoles(TokenInterface $token): array { return $token->getRoleNames(); } diff --git a/Authorization/Voter/TraceableVoter.php b/Authorization/Voter/TraceableVoter.php index f606f1d2..1abc7c70 100644 --- a/Authorization/Voter/TraceableVoter.php +++ b/Authorization/Voter/TraceableVoter.php @@ -24,16 +24,13 @@ */ class TraceableVoter implements CacheableVoterInterface { - private $voter; - private $eventDispatcher; - - public function __construct(VoterInterface $voter, EventDispatcherInterface $eventDispatcher) - { - $this->voter = $voter; - $this->eventDispatcher = $eventDispatcher; + public function __construct( + private VoterInterface $voter, + private EventDispatcherInterface $eventDispatcher, + ) { } - public function vote(TokenInterface $token, $subject, array $attributes): int + public function vote(TokenInterface $token, mixed $subject, array $attributes): int { $result = $this->voter->vote($token, $subject, $attributes); diff --git a/Authorization/Voter/Voter.php b/Authorization/Voter/Voter.php index 6ca76eca..1f76a42e 100644 --- a/Authorization/Voter/Voter.php +++ b/Authorization/Voter/Voter.php @@ -18,13 +18,13 @@ * * @author Roman Marintšenko * @author Grégoire Pineau + * + * @template TAttribute of string + * @template TSubject of mixed */ abstract class Voter implements VoterInterface, CacheableVoterInterface { - /** - * {@inheritdoc} - */ - public function vote(TokenInterface $token, $subject, array $attributes) + public function vote(TokenInterface $token, mixed $subject, array $attributes): int { // abstain vote by default in case none of the attributes are supported $vote = self::ACCESS_ABSTAIN; @@ -35,12 +35,7 @@ public function vote(TokenInterface $token, $subject, array $attributes) continue; } } catch (\TypeError $e) { - if (\PHP_VERSION_ID < 80000) { - if (0 === strpos($e->getMessage(), 'Argument 1 passed to') - && false !== strpos($e->getMessage(), '::supports() must be of the type string')) { - continue; - } - } elseif (false !== strpos($e->getMessage(), 'supports(): Argument #1')) { + if (str_contains($e->getMessage(), 'supports(): Argument #1')) { continue; } @@ -82,20 +77,19 @@ public function supportsType(string $subjectType): bool /** * Determines if the attribute and subject are supported by this voter. * - * @param string $attribute An attribute - * @param mixed $subject The subject to secure, e.g. an object the user wants to access or any other PHP type + * @param mixed $subject The subject to secure, e.g. an object the user wants to access or any other PHP type * - * @return bool + * @psalm-assert-if-true TSubject $subject + * @psalm-assert-if-true TAttribute $attribute */ - abstract protected function supports(string $attribute, $subject); + abstract protected function supports(string $attribute, mixed $subject): bool; /** * Perform a single access check operation on a given attribute, subject and token. * It is safe to assume that $attribute and $subject already passed the "supports()" method check. * - * @param mixed $subject - * - * @return bool + * @param TAttribute $attribute + * @param TSubject $subject */ - abstract protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token); + abstract protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool; } diff --git a/Authorization/Voter/VoterInterface.php b/Authorization/Voter/VoterInterface.php index a50af88e..5255c88e 100644 --- a/Authorization/Voter/VoterInterface.php +++ b/Authorization/Voter/VoterInterface.php @@ -33,7 +33,7 @@ interface VoterInterface * @param mixed $subject The subject to secure * @param array $attributes An array of attributes associated with the method being invoked * - * @return int either ACCESS_GRANTED, ACCESS_ABSTAIN, or ACCESS_DENIED + * @return self::ACCESS_* */ - public function vote(TokenInterface $token, $subject, array $attributes); + public function vote(TokenInterface $token, mixed $subject, array $attributes): int; } diff --git a/CHANGELOG.md b/CHANGELOG.md index d466a99e..7cf09c70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,50 @@ CHANGELOG ========= +7.2 +--- + + * Make `AccessDecisionStrategyTestCase` compatible with PHPUnit 10+ + * Add `$token` argument to `UserCheckerInterface::checkPostAuth()` + * Deprecate argument `$secret` of `RememberMeToken` + * Deprecate returning an empty string in `UserInterface::getUserIdentifier()` + +7.0 +--- + + * Remove the `Security` class, use `Symfony\Bundle\SecurityBundle\Security` instead + * Require explicit argument when calling `TokenStorage::setToken()` + * Change argument `$lastUsed` of `TokenProviderInterface::updateToken()` to accept `DateTimeInterface` + +6.4 +--- + + * Make `PersistentToken` immutable + * Deprecate accepting only `DateTime` for `TokenProviderInterface::updateToken()`, use `DateTimeInterface` instead + +6.3 +--- + + * Add `AttributesBasedUserProviderInterface` to allow `$attributes` optional argument on `loadUserByIdentifier` + * Add `OidcUser` with OIDC support for `OidcUserInfoTokenHandler` + +6.2 +--- + + * Deprecate the `Security` class, use `Symfony\Bundle\SecurityBundle\Security` instead + * Change the signature of `TokenStorageInterface::setToken()` to `setToken(?TokenInterface $token)` + * Deprecate calling `TokenStorage::setToken()` without arguments + * Add a `ChainUserChecker` to allow calling multiple user checkers for a firewall + +6.0 +--- + + * `TokenInterface` does not extend `Serializable` anymore + * Remove all classes in the `Core\Encoder\` sub-namespace, use the `PasswordHasher` component instead + * Remove methods `getPassword()` and `getSalt()` from `UserInterface`, use `PasswordAuthenticatedUserInterface` + or `LegacyPasswordAuthenticatedUserInterface` instead +* `AccessDecisionManager` requires the strategy to be passed as in instance of `AccessDecisionStrategyInterface` + 5.4.21 ------ diff --git a/Encoder/BasePasswordEncoder.php b/Encoder/BasePasswordEncoder.php deleted file mode 100644 index 9267ad94..00000000 --- a/Encoder/BasePasswordEncoder.php +++ /dev/null @@ -1,102 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Encoder; - -use Symfony\Component\PasswordHasher\Hasher\CheckPasswordLengthTrait; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', BasePasswordEncoder::class, CheckPasswordLengthTrait::class); - -/** - * BasePasswordEncoder is the base class for all password encoders. - * - * @author Fabien Potencier - * - * @deprecated since Symfony 5.3, use CheckPasswordLengthTrait instead - */ -abstract class BasePasswordEncoder implements PasswordEncoderInterface -{ - public const MAX_PASSWORD_LENGTH = 4096; - - /** - * {@inheritdoc} - */ - public function needsRehash(string $encoded): bool - { - return false; - } - - /** - * Demerges a merge password and salt string. - * - * @return array An array where the first element is the password and the second the salt - */ - protected function demergePasswordAndSalt(string $mergedPasswordSalt) - { - if (empty($mergedPasswordSalt)) { - return ['', '']; - } - - $password = $mergedPasswordSalt; - $salt = ''; - $saltBegins = strrpos($mergedPasswordSalt, '{'); - - if (false !== $saltBegins && $saltBegins + 1 < \strlen($mergedPasswordSalt)) { - $salt = substr($mergedPasswordSalt, $saltBegins + 1, -1); - $password = substr($mergedPasswordSalt, 0, $saltBegins); - } - - return [$password, $salt]; - } - - /** - * Merges a password and a salt. - * - * @return string - * - * @throws \InvalidArgumentException - */ - protected function mergePasswordAndSalt(string $password, ?string $salt) - { - if (empty($salt)) { - return $password; - } - - if (false !== strrpos($salt, '{') || false !== strrpos($salt, '}')) { - throw new \InvalidArgumentException('Cannot use { or } in salt.'); - } - - return $password.'{'.$salt.'}'; - } - - /** - * Compares two passwords. - * - * This method implements a constant-time algorithm to compare passwords to - * avoid (remote) timing attacks. - * - * @return bool - */ - protected function comparePasswords(string $password1, string $password2) - { - return hash_equals($password1, $password2); - } - - /** - * Checks if the password is too long. - * - * @return bool - */ - protected function isPasswordTooLong(string $password) - { - return \strlen($password) > static::MAX_PASSWORD_LENGTH; - } -} diff --git a/Encoder/EncoderAwareInterface.php b/Encoder/EncoderAwareInterface.php deleted file mode 100644 index 70231e2c..00000000 --- a/Encoder/EncoderAwareInterface.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Encoder; - -use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface; - -/** - * @author Christophe Coevoet - * - * @deprecated since Symfony 5.3, use {@link PasswordHasherAwareInterface} instead. - */ -interface EncoderAwareInterface -{ - /** - * Gets the name of the encoder used to encode the password. - * - * If the method returns null, the standard way to retrieve the encoder - * will be used instead. - * - * @return string|null - */ - public function getEncoderName(); -} diff --git a/Encoder/EncoderFactory.php b/Encoder/EncoderFactory.php deleted file mode 100644 index 526c461e..00000000 --- a/Encoder/EncoderFactory.php +++ /dev/null @@ -1,227 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Encoder; - -use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface; -use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory; -use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; -use Symfony\Component\PasswordHasher\PasswordHasherInterface; -use Symfony\Component\Security\Core\Exception\LogicException; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', EncoderFactory::class, PasswordHasherFactory::class); - -/** - * A generic encoder factory implementation. - * - * @author Johannes M. Schmitt - * - * @deprecated since Symfony 5.3, use {@link PasswordHasherFactory} instead - */ -class EncoderFactory implements EncoderFactoryInterface -{ - private $encoders; - - public function __construct(array $encoders) - { - $this->encoders = $encoders; - } - - /** - * {@inheritdoc} - */ - public function getEncoder($user) - { - $encoderKey = null; - - if (($user instanceof PasswordHasherAwareInterface && null !== $encoderName = $user->getPasswordHasherName()) || ($user instanceof EncoderAwareInterface && null !== $encoderName = $user->getEncoderName())) { - if (!\array_key_exists($encoderName, $this->encoders)) { - throw new \RuntimeException(sprintf('The encoder "%s" was not configured.', $encoderName)); - } - - $encoderKey = $encoderName; - } else { - foreach ($this->encoders as $class => $encoder) { - if ((\is_object($user) && $user instanceof $class) || (!\is_object($user) && (is_subclass_of($user, $class) || $user == $class))) { - $encoderKey = $class; - break; - } - } - } - - if (null === $encoderKey) { - throw new \RuntimeException(sprintf('No encoder has been configured for account "%s".', \is_object($user) ? get_debug_type($user) : $user)); - } - - if (!$this->encoders[$encoderKey] instanceof PasswordEncoderInterface) { - if ($this->encoders[$encoderKey] instanceof LegacyPasswordHasherInterface) { - $this->encoders[$encoderKey] = new LegacyPasswordHasherEncoder($this->encoders[$encoderKey]); - } elseif ($this->encoders[$encoderKey] instanceof PasswordHasherInterface) { - $this->encoders[$encoderKey] = new PasswordHasherEncoder($this->encoders[$encoderKey]); - } else { - $this->encoders[$encoderKey] = $this->createEncoder($this->encoders[$encoderKey]); - } - } - - return $this->encoders[$encoderKey]; - } - - /** - * Creates the actual encoder instance. - * - * @throws \InvalidArgumentException - */ - private function createEncoder(array $config, bool $isExtra = false): PasswordEncoderInterface - { - if (isset($config['algorithm'])) { - $rawConfig = $config; - $config = $this->getEncoderConfigFromAlgorithm($config); - } - if (!isset($config['class'])) { - throw new \InvalidArgumentException('"class" must be set in '.json_encode($config)); - } - if (!isset($config['arguments'])) { - throw new \InvalidArgumentException('"arguments" must be set in '.json_encode($config)); - } - - $encoder = new $config['class'](...$config['arguments']); - - if ($isExtra || !\in_array($config['class'], [NativePasswordEncoder::class, SodiumPasswordEncoder::class], true)) { - return $encoder; - } - - if ($rawConfig ?? null) { - $extraEncoders = array_map(function (string $algo) use ($rawConfig): PasswordEncoderInterface { - $rawConfig['algorithm'] = $algo; - - return $this->createEncoder($rawConfig); - }, ['pbkdf2', $rawConfig['hash_algorithm'] ?? 'sha512']); - } else { - $extraEncoders = [new Pbkdf2PasswordEncoder(), new MessageDigestPasswordEncoder()]; - } - - return new MigratingPasswordEncoder($encoder, ...$extraEncoders); - } - - private function getEncoderConfigFromAlgorithm(array $config): array - { - if ('auto' === $config['algorithm']) { - $encoderChain = []; - // "plaintext" is not listed as any leaked hashes could then be used to authenticate directly - foreach ([SodiumPasswordEncoder::isSupported() ? 'sodium' : 'native', 'pbkdf2', $config['hash_algorithm']] as $algo) { - $config['algorithm'] = $algo; - $encoderChain[] = $this->createEncoder($config, true); - } - - return [ - 'class' => MigratingPasswordEncoder::class, - 'arguments' => $encoderChain, - ]; - } - - if ($fromEncoders = ($config['migrate_from'] ?? false)) { - unset($config['migrate_from']); - $encoderChain = [$this->createEncoder($config, true)]; - - foreach ($fromEncoders as $name) { - if ($encoder = $this->encoders[$name] ?? false) { - $encoder = $encoder instanceof PasswordEncoderInterface ? $encoder : $this->createEncoder($encoder, true); - } else { - $encoder = $this->createEncoder(['algorithm' => $name], true); - } - - $encoderChain[] = $encoder; - } - - return [ - 'class' => MigratingPasswordEncoder::class, - 'arguments' => $encoderChain, - ]; - } - - switch ($config['algorithm']) { - case 'plaintext': - return [ - 'class' => PlaintextPasswordEncoder::class, - 'arguments' => [$config['ignore_case']], - ]; - - case 'pbkdf2': - return [ - 'class' => Pbkdf2PasswordEncoder::class, - 'arguments' => [ - $config['hash_algorithm'] ?? 'sha512', - $config['encode_as_base64'] ?? true, - $config['iterations'] ?? 1000, - $config['key_length'] ?? 40, - ], - ]; - - case 'bcrypt': - $config['algorithm'] = 'native'; - $config['native_algorithm'] = \PASSWORD_BCRYPT; - - return $this->getEncoderConfigFromAlgorithm($config); - - case 'native': - return [ - 'class' => NativePasswordEncoder::class, - 'arguments' => [ - $config['time_cost'] ?? null, - (($config['memory_cost'] ?? 0) << 10) ?: null, - $config['cost'] ?? null, - ] + (isset($config['native_algorithm']) ? [3 => $config['native_algorithm']] : []), - ]; - - case 'sodium': - return [ - 'class' => SodiumPasswordEncoder::class, - 'arguments' => [ - $config['time_cost'] ?? null, - (($config['memory_cost'] ?? 0) << 10) ?: null, - ], - ]; - - case 'argon2i': - if (SodiumPasswordEncoder::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) { - $config['algorithm'] = 'sodium'; - } elseif (\defined('PASSWORD_ARGON2I')) { - $config['algorithm'] = 'native'; - $config['native_algorithm'] = \PASSWORD_ARGON2I; - } else { - throw new LogicException(sprintf('Algorithm "argon2i" is not available. Either use %s"auto" or upgrade to PHP 7.2+ instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? '"argon2id", ' : '')); - } - - return $this->getEncoderConfigFromAlgorithm($config); - - case 'argon2id': - if (($hasSodium = SodiumPasswordEncoder::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) { - $config['algorithm'] = 'sodium'; - } elseif (\defined('PASSWORD_ARGON2ID')) { - $config['algorithm'] = 'native'; - $config['native_algorithm'] = \PASSWORD_ARGON2ID; - } else { - throw new LogicException(sprintf('Algorithm "argon2id" is not available. Either use %s"auto", upgrade to PHP 7.3+ or use libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? '"argon2i", ' : '')); - } - - return $this->getEncoderConfigFromAlgorithm($config); - } - - return [ - 'class' => MessageDigestPasswordEncoder::class, - 'arguments' => [ - $config['algorithm'], - $config['encode_as_base64'] ?? true, - $config['iterations'] ?? 5000, - ], - ]; - } -} diff --git a/Encoder/EncoderFactoryInterface.php b/Encoder/EncoderFactoryInterface.php deleted file mode 100644 index 83dea6c7..00000000 --- a/Encoder/EncoderFactoryInterface.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Encoder; - -use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; -use Symfony\Component\Security\Core\User\UserInterface; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', EncoderFactoryInterface::class, PasswordHasherFactoryInterface::class); - -/** - * EncoderFactoryInterface to support different encoders for different accounts. - * - * @author Johannes M. Schmitt - * - * @deprecated since Symfony 5.3, use {@link PasswordHasherFactoryInterface} instead - */ -interface EncoderFactoryInterface -{ - /** - * Returns the password encoder to use for the given account. - * - * @param UserInterface|string $user A UserInterface instance or a class name - * - * @return PasswordEncoderInterface - * - * @throws \RuntimeException when no password encoder could be found for the user - */ - public function getEncoder($user); -} diff --git a/Encoder/LegacyEncoderTrait.php b/Encoder/LegacyEncoderTrait.php deleted file mode 100644 index d1263213..00000000 --- a/Encoder/LegacyEncoderTrait.php +++ /dev/null @@ -1,56 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Encoder; - -use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; -use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; -use Symfony\Component\PasswordHasher\PasswordHasherInterface; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; - -/** - * @internal - */ -trait LegacyEncoderTrait -{ - /** - * @var PasswordHasherInterface|LegacyPasswordHasherInterface - */ - private $hasher; - - /** - * {@inheritdoc} - */ - public function encodePassword(string $raw, ?string $salt): string - { - try { - return $this->hasher->hash($raw, $salt); - } catch (InvalidPasswordException $e) { - throw new BadCredentialsException('Bad credentials.'); - } - } - - /** - * {@inheritdoc} - */ - public function isPasswordValid(string $encoded, string $raw, ?string $salt): bool - { - return $this->hasher->verify($encoded, $raw, $salt); - } - - /** - * {@inheritdoc} - */ - public function needsRehash(string $encoded): bool - { - return $this->hasher->needsRehash($encoded); - } -} diff --git a/Encoder/LegacyPasswordHasherEncoder.php b/Encoder/LegacyPasswordHasherEncoder.php deleted file mode 100644 index 7e57ff23..00000000 --- a/Encoder/LegacyPasswordHasherEncoder.php +++ /dev/null @@ -1,52 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Encoder; - -use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; -use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; - -/** - * Forward compatibility for new new PasswordHasher component. - * - * @author Alexander M. Turek - * - * @internal To be removed in Symfony 6 - */ -final class LegacyPasswordHasherEncoder implements PasswordEncoderInterface -{ - private $passwordHasher; - - public function __construct(LegacyPasswordHasherInterface $passwordHasher) - { - $this->passwordHasher = $passwordHasher; - } - - public function encodePassword(string $raw, ?string $salt): string - { - try { - return $this->passwordHasher->hash($raw, $salt); - } catch (InvalidPasswordException $e) { - throw new BadCredentialsException($e->getMessage(), $e->getCode(), $e); - } - } - - public function isPasswordValid(string $encoded, string $raw, ?string $salt): bool - { - return $this->passwordHasher->verify($encoded, $raw, $salt); - } - - public function needsRehash(string $encoded): bool - { - return $this->passwordHasher->needsRehash($encoded); - } -} diff --git a/Encoder/MessageDigestPasswordEncoder.php b/Encoder/MessageDigestPasswordEncoder.php deleted file mode 100644 index f98339fd..00000000 --- a/Encoder/MessageDigestPasswordEncoder.php +++ /dev/null @@ -1,87 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Encoder; - -use Symfony\Component\PasswordHasher\Hasher\MessageDigestPasswordHasher; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', MessageDigestPasswordEncoder::class, MessageDigestPasswordHasher::class); - -/** - * MessageDigestPasswordEncoder uses a message digest algorithm. - * - * @author Fabien Potencier - * - * @deprecated since Symfony 5.3, use {@link MessageDigestPasswordHasher} instead - */ -class MessageDigestPasswordEncoder extends BasePasswordEncoder -{ - private $algorithm; - private $encodeHashAsBase64; - private $iterations = 1; - private $encodedLength = -1; - - /** - * @param string $algorithm The digest algorithm to use - * @param bool $encodeHashAsBase64 Whether to base64 encode the password hash - * @param int $iterations The number of iterations to use to stretch the password hash - */ - public function __construct(string $algorithm = 'sha512', bool $encodeHashAsBase64 = true, int $iterations = 5000) - { - $this->algorithm = $algorithm; - $this->encodeHashAsBase64 = $encodeHashAsBase64; - - try { - $this->encodedLength = \strlen($this->encodePassword('', 'salt')); - } catch (\LogicException $e) { - // ignore algorithm not supported - } - - $this->iterations = $iterations; - } - - /** - * {@inheritdoc} - */ - public function encodePassword(string $raw, ?string $salt) - { - if ($this->isPasswordTooLong($raw)) { - throw new BadCredentialsException('Invalid password.'); - } - - if (!\in_array($this->algorithm, hash_algos(), true)) { - throw new \LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm)); - } - - $salted = $this->mergePasswordAndSalt($raw, $salt); - $digest = hash($this->algorithm, $salted, true); - - // "stretch" hash - for ($i = 1; $i < $this->iterations; ++$i) { - $digest = hash($this->algorithm, $digest.$salted, true); - } - - return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest); - } - - /** - * {@inheritdoc} - */ - public function isPasswordValid(string $encoded, string $raw, ?string $salt) - { - if (\strlen($encoded) !== $this->encodedLength || str_contains($encoded, '$')) { - return false; - } - - return !$this->isPasswordTooLong($raw) && $this->comparePasswords($encoded, $this->encodePassword($raw, $salt)); - } -} diff --git a/Encoder/MigratingPasswordEncoder.php b/Encoder/MigratingPasswordEncoder.php deleted file mode 100644 index 53d3a58d..00000000 --- a/Encoder/MigratingPasswordEncoder.php +++ /dev/null @@ -1,77 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Encoder; - -use Symfony\Component\PasswordHasher\Hasher\MigratingPasswordHasher; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', MigratingPasswordEncoder::class, MigratingPasswordHasher::class); - -/** - * Hashes passwords using the best available encoder. - * Validates them using a chain of encoders. - * - * /!\ Don't put a PlaintextPasswordEncoder in the list as that'd mean a leaked hash - * could be used to authenticate successfully without knowing the cleartext password. - * - * @author Nicolas Grekas - * - * @deprecated since Symfony 5.3, use {@link MigratingPasswordHasher} instead - */ -final class MigratingPasswordEncoder extends BasePasswordEncoder implements SelfSaltingEncoderInterface -{ - private $bestEncoder; - private $extraEncoders; - - public function __construct(PasswordEncoderInterface $bestEncoder, PasswordEncoderInterface ...$extraEncoders) - { - $this->bestEncoder = $bestEncoder; - $this->extraEncoders = $extraEncoders; - } - - /** - * {@inheritdoc} - */ - public function encodePassword(string $raw, ?string $salt): string - { - return $this->bestEncoder->encodePassword($raw, $salt); - } - - /** - * {@inheritdoc} - */ - public function isPasswordValid(string $encoded, string $raw, ?string $salt): bool - { - if ($this->bestEncoder->isPasswordValid($encoded, $raw, $salt)) { - return true; - } - - if (!$this->bestEncoder->needsRehash($encoded)) { - return false; - } - - foreach ($this->extraEncoders as $encoder) { - if ($encoder->isPasswordValid($encoded, $raw, $salt)) { - return true; - } - } - - return false; - } - - /** - * {@inheritdoc} - */ - public function needsRehash(string $encoded): bool - { - return $this->bestEncoder->needsRehash($encoded); - } -} diff --git a/Encoder/NativePasswordEncoder.php b/Encoder/NativePasswordEncoder.php deleted file mode 100644 index eef5e62b..00000000 --- a/Encoder/NativePasswordEncoder.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Encoder; - -use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', NativePasswordEncoder::class, NativePasswordHasher::class); - -/** - * Hashes passwords using password_hash(). - * - * @author Elnur Abdurrakhimov - * @author Terje Bråten - * @author Nicolas Grekas - * - * @deprecated since Symfony 5.3, use {@link NativePasswordHasher} instead - */ -final class NativePasswordEncoder implements PasswordEncoderInterface, SelfSaltingEncoderInterface -{ - use LegacyEncoderTrait; - - /** - * @param string|null $algo An algorithm supported by password_hash() or null to use the stronger available algorithm - */ - public function __construct(?int $opsLimit = null, ?int $memLimit = null, ?int $cost = null, ?string $algo = null) - { - $this->hasher = new NativePasswordHasher($opsLimit, $memLimit, $cost, $algo); - } -} diff --git a/Encoder/PasswordEncoderInterface.php b/Encoder/PasswordEncoderInterface.php deleted file mode 100644 index 9c1524d6..00000000 --- a/Encoder/PasswordEncoderInterface.php +++ /dev/null @@ -1,55 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Encoder; - -use Symfony\Component\PasswordHasher\PasswordHasherInterface; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', PasswordEncoderInterface::class, PasswordHasherInterface::class); - -/** - * PasswordEncoderInterface is the interface for all encoders. - * - * @author Fabien Potencier - * - * @deprecated since Symfony 5.3, use {@link PasswordHasherInterface} instead - */ -interface PasswordEncoderInterface -{ - /** - * Encodes the raw password. - * - * @return string - * - * @throws BadCredentialsException If the raw password is invalid, e.g. excessively long - * @throws \InvalidArgumentException If the salt is invalid - */ - public function encodePassword(string $raw, ?string $salt); - - /** - * Checks a raw password against an encoded password. - * - * @param string $encoded An encoded password - * @param string $raw A raw password - * @param string|null $salt The salt - * - * @return bool - * - * @throws \InvalidArgumentException If the salt is invalid - */ - public function isPasswordValid(string $encoded, string $raw, ?string $salt); - - /** - * Checks if an encoded password would benefit from rehashing. - */ - public function needsRehash(string $encoded): bool; -} diff --git a/Encoder/PasswordHasherAdapter.php b/Encoder/PasswordHasherAdapter.php deleted file mode 100644 index 4a4b9c0b..00000000 --- a/Encoder/PasswordHasherAdapter.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Encoder; - -use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; - -/** - * Forward compatibility for new new PasswordHasher component. - * - * @author Alexander M. Turek - * - * @internal To be removed in Symfony 6 - */ -final class PasswordHasherAdapter implements LegacyPasswordHasherInterface -{ - private $passwordEncoder; - - public function __construct(PasswordEncoderInterface $passwordEncoder) - { - $this->passwordEncoder = $passwordEncoder; - } - - public function hash(string $plainPassword, ?string $salt = null): string - { - return $this->passwordEncoder->encodePassword($plainPassword, $salt); - } - - public function verify(string $hashedPassword, string $plainPassword, ?string $salt = null): bool - { - return $this->passwordEncoder->isPasswordValid($hashedPassword, $plainPassword, $salt); - } - - public function needsRehash(string $hashedPassword): bool - { - return $this->passwordEncoder->needsRehash($hashedPassword); - } -} diff --git a/Encoder/PasswordHasherEncoder.php b/Encoder/PasswordHasherEncoder.php deleted file mode 100644 index d37875dc..00000000 --- a/Encoder/PasswordHasherEncoder.php +++ /dev/null @@ -1,60 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Encoder; - -use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; -use Symfony\Component\PasswordHasher\PasswordHasherInterface; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; - -/** - * Forward compatibility for new new PasswordHasher component. - * - * @author Alexander M. Turek - * - * @internal To be removed in Symfony 6 - */ -final class PasswordHasherEncoder implements PasswordEncoderInterface, SelfSaltingEncoderInterface -{ - private $passwordHasher; - - public function __construct(PasswordHasherInterface $passwordHasher) - { - $this->passwordHasher = $passwordHasher; - } - - public function encodePassword(string $raw, ?string $salt): string - { - if (null !== $salt) { - throw new \InvalidArgumentException('This password hasher does not support passing a salt.'); - } - - try { - return $this->passwordHasher->hash($raw); - } catch (InvalidPasswordException $e) { - throw new BadCredentialsException($e->getMessage(), $e->getCode(), $e); - } - } - - public function isPasswordValid(string $encoded, string $raw, ?string $salt): bool - { - if (null !== $salt) { - throw new \InvalidArgumentException('This password hasher does not support passing a salt.'); - } - - return $this->passwordHasher->verify($encoded, $raw); - } - - public function needsRehash(string $encoded): bool - { - return $this->passwordHasher->needsRehash($encoded); - } -} diff --git a/Encoder/Pbkdf2PasswordEncoder.php b/Encoder/Pbkdf2PasswordEncoder.php deleted file mode 100644 index d92c12fc..00000000 --- a/Encoder/Pbkdf2PasswordEncoder.php +++ /dev/null @@ -1,47 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Encoder; - -use Symfony\Component\PasswordHasher\Hasher\Pbkdf2PasswordHasher; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', Pbkdf2PasswordEncoder::class, Pbkdf2PasswordHasher::class); - -/** - * Pbkdf2PasswordEncoder uses the PBKDF2 (Password-Based Key Derivation Function 2). - * - * Providing a high level of Cryptographic security, - * PBKDF2 is recommended by the National Institute of Standards and Technology (NIST). - * - * But also warrants a warning, using PBKDF2 (with a high number of iterations) slows down the process. - * PBKDF2 should be used with caution and care. - * - * @author Sebastiaan Stok - * @author Andrew Johnson - * @author Fabien Potencier - * - * @deprecated since Symfony 5.3, use {@link Pbkdf2PasswordHasher} instead - */ -class Pbkdf2PasswordEncoder extends BasePasswordEncoder -{ - use LegacyEncoderTrait; - - /** - * @param string $algorithm The digest algorithm to use - * @param bool $encodeHashAsBase64 Whether to base64 encode the password hash - * @param int $iterations The number of iterations to use to stretch the password hash - * @param int $length Length of derived key to create - */ - public function __construct(string $algorithm = 'sha512', bool $encodeHashAsBase64 = true, int $iterations = 1000, int $length = 40) - { - $this->hasher = new Pbkdf2PasswordHasher($algorithm, $encodeHashAsBase64, $iterations, $length); - } -} diff --git a/Encoder/PlaintextPasswordEncoder.php b/Encoder/PlaintextPasswordEncoder.php deleted file mode 100644 index 497e9f19..00000000 --- a/Encoder/PlaintextPasswordEncoder.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Encoder; - -use Symfony\Component\PasswordHasher\Hasher\PlaintextPasswordHasher; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', PlaintextPasswordEncoder::class, PlaintextPasswordHasher::class); - -/** - * PlaintextPasswordEncoder does not do any encoding but is useful in testing environments. - * - * As this encoder is not cryptographically secure, usage of it in production environments is discouraged. - * - * @author Fabien Potencier - * - * @deprecated since Symfony 5.3, use {@link PlaintextPasswordHasher} instead - */ -class PlaintextPasswordEncoder extends BasePasswordEncoder -{ - use LegacyEncoderTrait; - - /** - * @param bool $ignorePasswordCase Compare password case-insensitive - */ - public function __construct(bool $ignorePasswordCase = false) - { - $this->hasher = new PlaintextPasswordHasher($ignorePasswordCase); - } -} diff --git a/Encoder/SelfSaltingEncoderInterface.php b/Encoder/SelfSaltingEncoderInterface.php deleted file mode 100644 index b8740bc9..00000000 --- a/Encoder/SelfSaltingEncoderInterface.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Encoder; - -use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" interface is deprecated, use "%s" on hasher implementations that deal with salts instead.', SelfSaltingEncoderInterface::class, LegacyPasswordHasherInterface::class); - -/** - * SelfSaltingEncoderInterface is a marker interface for encoders that do not - * require a user-generated salt. - * - * @author Zan Baldwin - * - * @deprecated since Symfony 5.3, use {@link LegacyPasswordHasherInterface} instead - */ -interface SelfSaltingEncoderInterface -{ -} diff --git a/Encoder/SodiumPasswordEncoder.php b/Encoder/SodiumPasswordEncoder.php deleted file mode 100644 index d63f5450..00000000 --- a/Encoder/SodiumPasswordEncoder.php +++ /dev/null @@ -1,40 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Encoder; - -use Symfony\Component\PasswordHasher\Hasher\SodiumPasswordHasher; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', SodiumPasswordEncoder::class, SodiumPasswordHasher::class); - -/** - * Hashes passwords using libsodium. - * - * @author Robin Chalas - * @author Zan Baldwin - * @author Dominik Müller - * - * @deprecated since Symfony 5.3, use {@link SodiumPasswordHasher} instead - */ -final class SodiumPasswordEncoder implements PasswordEncoderInterface, SelfSaltingEncoderInterface -{ - use LegacyEncoderTrait; - - public function __construct(?int $opsLimit = null, ?int $memLimit = null) - { - $this->hasher = new SodiumPasswordHasher($opsLimit, $memLimit); - } - - public static function isSupported(): bool - { - return SodiumPasswordHasher::isSupported(); - } -} diff --git a/Encoder/UserPasswordEncoder.php b/Encoder/UserPasswordEncoder.php deleted file mode 100644 index 7b29918c..00000000 --- a/Encoder/UserPasswordEncoder.php +++ /dev/null @@ -1,83 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Encoder; - -use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher; -use Symfony\Component\Security\Core\User\LegacyPasswordAuthenticatedUserInterface; -use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; -use Symfony\Component\Security\Core\User\UserInterface; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', UserPasswordEncoder::class, UserPasswordHasher::class); - -/** - * A generic password encoder. - * - * @author Ariel Ferrandini - * - * @deprecated since Symfony 5.3, use {@link UserPasswordHasher} instead - */ -class UserPasswordEncoder implements UserPasswordEncoderInterface -{ - private $encoderFactory; - - public function __construct(EncoderFactoryInterface $encoderFactory) - { - $this->encoderFactory = $encoderFactory; - } - - /** - * {@inheritdoc} - */ - public function encodePassword(UserInterface $user, string $plainPassword) - { - $encoder = $this->encoderFactory->getEncoder($user); - - if (!$user instanceof PasswordAuthenticatedUserInterface) { - trigger_deprecation('symfony/password-hasher', '5.3', 'Not implementing the "%s" interface while using "%s" is deprecated, the "%s" class should implement it.', PasswordAuthenticatedUserInterface::class, __CLASS__, get_debug_type($user)); - } - - $salt = $user->getSalt(); - if ($salt && !$user instanceof LegacyPasswordAuthenticatedUserInterface) { - trigger_deprecation('symfony/password-hasher', '5.3', 'Returning a string from "getSalt()" without implementing the "%s" interface is deprecated, the "%s" class should implement it.', LegacyPasswordAuthenticatedUserInterface::class, get_debug_type($user)); - } - - return $encoder->encodePassword($plainPassword, $user->getSalt()); - } - - /** - * {@inheritdoc} - */ - public function isPasswordValid(UserInterface $user, string $raw) - { - if (null === $user->getPassword()) { - return false; - } - - $encoder = $this->encoderFactory->getEncoder($user); - - return $encoder->isPasswordValid($user->getPassword(), $raw, $user->getSalt()); - } - - /** - * {@inheritdoc} - */ - public function needsRehash(UserInterface $user): bool - { - if (null === $user->getPassword()) { - return false; - } - - $encoder = $this->encoderFactory->getEncoder($user); - - return $encoder->needsRehash($user->getPassword()); - } -} diff --git a/Encoder/UserPasswordEncoderInterface.php b/Encoder/UserPasswordEncoderInterface.php deleted file mode 100644 index 894ba401..00000000 --- a/Encoder/UserPasswordEncoderInterface.php +++ /dev/null @@ -1,44 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Encoder; - -use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; -use Symfony\Component\Security\Core\User\UserInterface; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" interface is deprecated, use "%s" instead.', UserPasswordEncoderInterface::class, UserPasswordHasherInterface::class); - -/** - * UserPasswordEncoderInterface is the interface for the password encoder service. - * - * @author Ariel Ferrandini - * - * @deprecated since Symfony 5.3, use {@link UserPasswordHasherInterface} instead - */ -interface UserPasswordEncoderInterface -{ - /** - * Encodes the plain password. - * - * @return string - */ - public function encodePassword(UserInterface $user, string $plainPassword); - - /** - * @return bool - */ - public function isPasswordValid(UserInterface $user, string $raw); - - /** - * Checks if an encoded password would benefit from rehashing. - */ - public function needsRehash(UserInterface $user): bool; -} diff --git a/Event/AuthenticationEvent.php b/Event/AuthenticationEvent.php index 4fc15196..f1683558 100644 --- a/Event/AuthenticationEvent.php +++ b/Event/AuthenticationEvent.php @@ -21,15 +21,13 @@ */ class AuthenticationEvent extends Event { - private $authenticationToken; - - public function __construct(TokenInterface $token) - { - $this->authenticationToken = $token; + public function __construct( + private TokenInterface $token, + ) { } - public function getAuthenticationToken() + public function getAuthenticationToken(): TokenInterface { - return $this->authenticationToken; + return $this->token; } } diff --git a/Event/AuthenticationFailureEvent.php b/Event/AuthenticationFailureEvent.php deleted file mode 100644 index 4e9562c2..00000000 --- a/Event/AuthenticationFailureEvent.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Event; - -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Component\Security\Http\Event\LoginFailureEvent; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" with the new authenticator system instead.', AuthenticationFailureEvent::class, LoginFailureEvent::class); - -/** - * This event is dispatched on authentication failure. - * - * @author Johannes M. Schmitt - * - * @deprecated since Symfony 5.3, use LoginFailureEvent with the new authenticator system instead - */ -final class AuthenticationFailureEvent extends AuthenticationEvent -{ - private $authenticationException; - - public function __construct(TokenInterface $token, AuthenticationException $ex) - { - parent::__construct($token); - - $this->authenticationException = $ex; - } - - public function getAuthenticationException(): AuthenticationException - { - return $this->authenticationException; - } -} diff --git a/Event/VoteEvent.php b/Event/VoteEvent.php index 78ac2f90..edc66b36 100644 --- a/Event/VoteEvent.php +++ b/Event/VoteEvent.php @@ -23,17 +23,12 @@ */ final class VoteEvent extends Event { - private $voter; - private $subject; - private $attributes; - private $vote; - - public function __construct(VoterInterface $voter, $subject, array $attributes, int $vote) - { - $this->voter = $voter; - $this->subject = $subject; - $this->attributes = $attributes; - $this->vote = $vote; + public function __construct( + private VoterInterface $voter, + private mixed $subject, + private array $attributes, + private int $vote, + ) { } public function getVoter(): VoterInterface @@ -41,7 +36,7 @@ public function getVoter(): VoterInterface return $this->voter; } - public function getSubject() + public function getSubject(): mixed { return $this->subject; } diff --git a/Exception/AccessDeniedException.php b/Exception/AccessDeniedException.php index f07cbfc0..93c38694 100644 --- a/Exception/AccessDeniedException.php +++ b/Exception/AccessDeniedException.php @@ -11,49 +11,40 @@ namespace Symfony\Component\Security\Core\Exception; +use Symfony\Component\HttpKernel\Attribute\WithHttpStatus; + /** * AccessDeniedException is thrown when the account has not the required role. * * @author Fabien Potencier */ +#[WithHttpStatus(403)] class AccessDeniedException extends RuntimeException { - private $attributes = []; - private $subject; + private array $attributes = []; + private mixed $subject = null; - public function __construct(string $message = 'Access Denied.', ?\Throwable $previous = null) + public function __construct(string $message = 'Access Denied.', ?\Throwable $previous = null, int $code = 403) { - parent::__construct($message, 403, $previous); + parent::__construct($message, $code, $previous); } - /** - * @return array - */ - public function getAttributes() + public function getAttributes(): array { return $this->attributes; } - /** - * @param array|string $attributes - */ - public function setAttributes($attributes) + public function setAttributes(array|string $attributes): void { $this->attributes = (array) $attributes; } - /** - * @return mixed - */ - public function getSubject() + public function getSubject(): mixed { return $this->subject; } - /** - * @param mixed $subject - */ - public function setSubject($subject) + public function setSubject(mixed $subject): void { $this->subject = $subject; } diff --git a/Exception/AccountExpiredException.php b/Exception/AccountExpiredException.php index 4a712637..91ea122e 100644 --- a/Exception/AccountExpiredException.php +++ b/Exception/AccountExpiredException.php @@ -19,10 +19,7 @@ */ class AccountExpiredException extends AccountStatusException { - /** - * {@inheritdoc} - */ - public function getMessageKey() + public function getMessageKey(): string { return 'Account has expired.'; } diff --git a/Exception/AccountStatusException.php b/Exception/AccountStatusException.php index 880ad3de..c0176e08 100644 --- a/Exception/AccountStatusException.php +++ b/Exception/AccountStatusException.php @@ -22,34 +22,26 @@ */ abstract class AccountStatusException extends AuthenticationException { - private $user; + private ?UserInterface $user = null; /** * Get the user. - * - * @return UserInterface|null */ - public function getUser() + public function getUser(): ?UserInterface { return $this->user; } - public function setUser(UserInterface $user) + public function setUser(UserInterface $user): void { $this->user = $user; } - /** - * {@inheritdoc} - */ public function __serialize(): array { return [$this->user, parent::__serialize()]; } - /** - * {@inheritdoc} - */ public function __unserialize(array $data): void { [$this->user, $parentData] = $data; diff --git a/Exception/AuthenticationCredentialsNotFoundException.php b/Exception/AuthenticationCredentialsNotFoundException.php index 8595bed8..fc28e4e5 100644 --- a/Exception/AuthenticationCredentialsNotFoundException.php +++ b/Exception/AuthenticationCredentialsNotFoundException.php @@ -20,10 +20,7 @@ */ class AuthenticationCredentialsNotFoundException extends AuthenticationException { - /** - * {@inheritdoc} - */ - public function getMessageKey() + public function getMessageKey(): string { return 'Authentication credentials could not be found.'; } diff --git a/Exception/AuthenticationException.php b/Exception/AuthenticationException.php index 6e038f2e..5d4c4431 100644 --- a/Exception/AuthenticationException.php +++ b/Exception/AuthenticationException.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Security\Core\Exception; +use Symfony\Component\HttpKernel\Attribute\WithHttpStatus; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; /** @@ -19,28 +20,17 @@ * @author Fabien Potencier * @author Alexander */ +#[WithHttpStatus(401)] class AuthenticationException extends RuntimeException { - /** @internal */ - protected $serialized; + private ?TokenInterface $token = null; - private $token; - - public function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null) - { - unset($this->serialized); - parent::__construct($message, $code, $previous); - } - - /** - * @return TokenInterface|null - */ - public function getToken() + public function getToken(): ?TokenInterface { return $this->token; } - public function setToken(TokenInterface $token) + public function setToken(TokenInterface $token): void { $this->token = $token; } @@ -88,40 +78,17 @@ public function __unserialize(array $data): void /** * Message key to be used by the translation component. - * - * @return string */ - public function getMessageKey() + public function getMessageKey(): string { return 'An authentication exception occurred.'; } /** * Message data to be used by the translation component. - * - * @return array */ - public function getMessageData() + public function getMessageData(): array { return []; } - - /** - * @internal - */ - public function __sleep(): array - { - $this->serialized = $this->__serialize(); - - return ['serialized']; - } - - /** - * @internal - */ - public function __wakeup(): void - { - $this->__unserialize($this->serialized); - unset($this->serialized); - } } diff --git a/Exception/AuthenticationExpiredException.php b/Exception/AuthenticationExpiredException.php index e3fce37b..1d04c5e8 100644 --- a/Exception/AuthenticationExpiredException.php +++ b/Exception/AuthenticationExpiredException.php @@ -21,10 +21,7 @@ */ class AuthenticationExpiredException extends AccountStatusException { - /** - * {@inheritdoc} - */ - public function getMessageKey() + public function getMessageKey(): string { return 'Authentication expired because your account information has changed.'; } diff --git a/Exception/AuthenticationServiceException.php b/Exception/AuthenticationServiceException.php index 66f051d0..fa5042e1 100644 --- a/Exception/AuthenticationServiceException.php +++ b/Exception/AuthenticationServiceException.php @@ -19,10 +19,7 @@ */ class AuthenticationServiceException extends AuthenticationException { - /** - * {@inheritdoc} - */ - public function getMessageKey() + public function getMessageKey(): string { return 'Authentication request could not be processed due to a system problem.'; } diff --git a/Exception/BadCredentialsException.php b/Exception/BadCredentialsException.php index be061c7b..6aeed7b0 100644 --- a/Exception/BadCredentialsException.php +++ b/Exception/BadCredentialsException.php @@ -19,10 +19,7 @@ */ class BadCredentialsException extends AuthenticationException { - /** - * {@inheritdoc} - */ - public function getMessageKey() + public function getMessageKey(): string { return 'Invalid credentials.'; } diff --git a/Exception/CookieTheftException.php b/Exception/CookieTheftException.php index af971684..a32f30d5 100644 --- a/Exception/CookieTheftException.php +++ b/Exception/CookieTheftException.php @@ -20,10 +20,7 @@ */ class CookieTheftException extends AuthenticationException { - /** - * {@inheritdoc} - */ - public function getMessageKey() + public function getMessageKey(): string { return 'Cookie has already been used by someone else.'; } diff --git a/Exception/CredentialsExpiredException.php b/Exception/CredentialsExpiredException.php index bcc1267a..50183772 100644 --- a/Exception/CredentialsExpiredException.php +++ b/Exception/CredentialsExpiredException.php @@ -19,10 +19,7 @@ */ class CredentialsExpiredException extends AccountStatusException { - /** - * {@inheritdoc} - */ - public function getMessageKey() + public function getMessageKey(): string { return 'Credentials have expired.'; } diff --git a/Exception/CustomUserMessageAccountStatusException.php b/Exception/CustomUserMessageAccountStatusException.php index f3b752b7..f59eff9b 100644 --- a/Exception/CustomUserMessageAccountStatusException.php +++ b/Exception/CustomUserMessageAccountStatusException.php @@ -23,9 +23,8 @@ */ class CustomUserMessageAccountStatusException extends AccountStatusException { - private $messageKey; - - private $messageData = []; + private string $messageKey; + private array $messageData = []; public function __construct(string $message = '', array $messageData = [], int $code = 0, ?\Throwable $previous = null) { @@ -40,33 +39,27 @@ public function __construct(string $message = '', array $messageData = [], int $ * @param string $messageKey The message or message key * @param array $messageData Data to be passed into the translator */ - public function setSafeMessage(string $messageKey, array $messageData = []) + public function setSafeMessage(string $messageKey, array $messageData = []): void { $this->messageKey = $messageKey; $this->messageData = $messageData; } - public function getMessageKey() + public function getMessageKey(): string { return $this->messageKey; } - public function getMessageData() + public function getMessageData(): array { return $this->messageData; } - /** - * {@inheritdoc} - */ public function __serialize(): array { return [parent::__serialize(), $this->messageKey, $this->messageData]; } - /** - * {@inheritdoc} - */ public function __unserialize(array $data): void { [$parentData, $this->messageKey, $this->messageData] = $data; diff --git a/Exception/CustomUserMessageAuthenticationException.php b/Exception/CustomUserMessageAuthenticationException.php index 193b5461..eae66c41 100644 --- a/Exception/CustomUserMessageAuthenticationException.php +++ b/Exception/CustomUserMessageAuthenticationException.php @@ -22,9 +22,8 @@ */ class CustomUserMessageAuthenticationException extends AuthenticationException { - private $messageKey; - - private $messageData = []; + private string $messageKey; + private array $messageData = []; public function __construct(string $message = '', array $messageData = [], int $code = 0, ?\Throwable $previous = null) { @@ -39,33 +38,27 @@ public function __construct(string $message = '', array $messageData = [], int $ * @param string $messageKey The message or message key * @param array $messageData Data to be passed into the translator */ - public function setSafeMessage(string $messageKey, array $messageData = []) + public function setSafeMessage(string $messageKey, array $messageData = []): void { $this->messageKey = $messageKey; $this->messageData = $messageData; } - public function getMessageKey() + public function getMessageKey(): string { return $this->messageKey; } - public function getMessageData() + public function getMessageData(): array { return $this->messageData; } - /** - * {@inheritdoc} - */ public function __serialize(): array { return [parent::__serialize(), $this->messageKey, $this->messageData]; } - /** - * {@inheritdoc} - */ public function __unserialize(array $data): void { [$parentData, $this->messageKey, $this->messageData] = $data; diff --git a/Exception/DisabledException.php b/Exception/DisabledException.php index e9b784fb..b82067cc 100644 --- a/Exception/DisabledException.php +++ b/Exception/DisabledException.php @@ -19,10 +19,7 @@ */ class DisabledException extends AccountStatusException { - /** - * {@inheritdoc} - */ - public function getMessageKey() + public function getMessageKey(): string { return 'Account is disabled.'; } diff --git a/Exception/InsufficientAuthenticationException.php b/Exception/InsufficientAuthenticationException.php index e33ef6ac..0221dfd2 100644 --- a/Exception/InsufficientAuthenticationException.php +++ b/Exception/InsufficientAuthenticationException.php @@ -21,10 +21,7 @@ */ class InsufficientAuthenticationException extends AuthenticationException { - /** - * {@inheritdoc} - */ - public function getMessageKey() + public function getMessageKey(): string { return 'Not privileged to request the resource.'; } diff --git a/Exception/InvalidCsrfTokenException.php b/Exception/InvalidCsrfTokenException.php index 84be8556..2041cf6b 100644 --- a/Exception/InvalidCsrfTokenException.php +++ b/Exception/InvalidCsrfTokenException.php @@ -19,10 +19,7 @@ */ class InvalidCsrfTokenException extends AuthenticationException { - /** - * {@inheritdoc} - */ - public function getMessageKey() + public function getMessageKey(): string { return 'Invalid CSRF token.'; } diff --git a/Exception/LazyResponseException.php b/Exception/LazyResponseException.php index 8edc248a..a354e68e 100644 --- a/Exception/LazyResponseException.php +++ b/Exception/LazyResponseException.php @@ -20,11 +20,9 @@ */ class LazyResponseException extends \Exception implements ExceptionInterface { - private $response; - - public function __construct(Response $response) - { - $this->response = $response; + public function __construct( + private Response $response, + ) { } public function getResponse(): Response diff --git a/Exception/LockedException.php b/Exception/LockedException.php index fffae74d..fb81cb05 100644 --- a/Exception/LockedException.php +++ b/Exception/LockedException.php @@ -19,10 +19,7 @@ */ class LockedException extends AccountStatusException { - /** - * {@inheritdoc} - */ - public function getMessageKey() + public function getMessageKey(): string { return 'Account is locked.'; } diff --git a/Exception/ProviderNotFoundException.php b/Exception/ProviderNotFoundException.php index af2e1b57..e4daf4ef 100644 --- a/Exception/ProviderNotFoundException.php +++ b/Exception/ProviderNotFoundException.php @@ -20,10 +20,7 @@ */ class ProviderNotFoundException extends AuthenticationException { - /** - * {@inheritdoc} - */ - public function getMessageKey() + public function getMessageKey(): string { return 'No authentication provider found to support the authentication token.'; } diff --git a/Exception/SessionUnavailableException.php b/Exception/SessionUnavailableException.php index 90b858a7..eec069c5 100644 --- a/Exception/SessionUnavailableException.php +++ b/Exception/SessionUnavailableException.php @@ -25,10 +25,7 @@ */ class SessionUnavailableException extends AuthenticationException { - /** - * {@inheritdoc} - */ - public function getMessageKey() + public function getMessageKey(): string { return 'No session available, it either timed out or cookies are not enabled.'; } diff --git a/Exception/TokenNotFoundException.php b/Exception/TokenNotFoundException.php index b050302a..a18f0d08 100644 --- a/Exception/TokenNotFoundException.php +++ b/Exception/TokenNotFoundException.php @@ -19,10 +19,7 @@ */ class TokenNotFoundException extends AuthenticationException { - /** - * {@inheritdoc} - */ - public function getMessageKey() + public function getMessageKey(): string { return 'No token could be found.'; } diff --git a/Exception/TooManyLoginAttemptsAuthenticationException.php b/Exception/TooManyLoginAttemptsAuthenticationException.php index b6981ecd..7bb74d64 100644 --- a/Exception/TooManyLoginAttemptsAuthenticationException.php +++ b/Exception/TooManyLoginAttemptsAuthenticationException.php @@ -19,16 +19,11 @@ */ class TooManyLoginAttemptsAuthenticationException extends AuthenticationException { - private $threshold; - - public function __construct(?int $threshold = null) - { - $this->threshold = $threshold; + public function __construct( + private ?int $threshold = null, + ) { } - /** - * {@inheritdoc} - */ public function getMessageData(): array { return [ @@ -37,25 +32,16 @@ public function getMessageData(): array ]; } - /** - * {@inheritdoc} - */ public function getMessageKey(): string { return 'Too many failed login attempts, please try again '.($this->threshold ? 'in %minutes% minute'.($this->threshold > 1 ? 's' : '').'.' : 'later.'); } - /** - * {@inheritdoc} - */ public function __serialize(): array { return [$this->threshold, parent::__serialize()]; } - /** - * {@inheritdoc} - */ public function __unserialize(array $data): void { [$this->threshold, $parentData] = $data; diff --git a/Exception/UserNotFoundException.php b/Exception/UserNotFoundException.php index 6ed9a5c6..6cd9b712 100644 --- a/Exception/UserNotFoundException.php +++ b/Exception/UserNotFoundException.php @@ -19,12 +19,9 @@ */ class UserNotFoundException extends AuthenticationException { - private $identifier; + private ?string $identifier = null; - /** - * {@inheritdoc} - */ - public function getMessageKey() + public function getMessageKey(): string { return 'Username could not be found.'; } @@ -37,18 +34,6 @@ public function getUserIdentifier(): ?string return $this->identifier; } - /** - * @return string - * - * @deprecated - */ - public function getUsername() - { - trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use getUserIdentifier() instead.', __METHOD__); - - return $this->identifier; - } - /** * Set the user identifier (e.g. username or email address). */ @@ -57,35 +42,16 @@ public function setUserIdentifier(string $identifier): void $this->identifier = $identifier; } - /** - * @deprecated - */ - public function setUsername(string $username) - { - trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use setUserIdentifier() instead.', __METHOD__); - - $this->identifier = $username; - } - - /** - * {@inheritdoc} - */ - public function getMessageData() + public function getMessageData(): array { return ['{{ username }}' => $this->identifier, '{{ user_identifier }}' => $this->identifier]; } - /** - * {@inheritdoc} - */ public function __serialize(): array { return [$this->identifier, parent::__serialize()]; } - /** - * {@inheritdoc} - */ public function __unserialize(array $data): void { [$this->identifier, $parentData] = $data; @@ -93,7 +59,3 @@ public function __unserialize(array $data): void parent::__unserialize($parentData); } } - -if (!class_exists(UsernameNotFoundException::class, false)) { - class_alias(UserNotFoundException::class, UsernameNotFoundException::class); -} diff --git a/Exception/UsernameNotFoundException.php b/Exception/UsernameNotFoundException.php deleted file mode 100644 index e0d2d4a2..00000000 --- a/Exception/UsernameNotFoundException.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Exception; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', UsernameNotFoundException::class, UserNotFoundException::class); - -class_exists(UserNotFoundException::class); - -if (false) { - /** - * @deprecated since Symfony 5.3 to be removed in 6.0, use UserNotFoundException instead. - */ - class UsernameNotFoundException extends AuthenticationException - { - } -} diff --git a/README.md b/README.md index 6e31770c..fc50dcc6 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ so called user providers that hold the users credentials. Getting Started --------------- -``` -$ composer require symfony/security-core +```bash +composer require symfony/security-core ``` ```php @@ -41,7 +41,7 @@ if (!$accessDecisionManager->decide($token, ['ROLE_ADMIN'])) { Sponsor ------- -The Security component for Symfony 5.4/6.0 is [backed][1] by [SymfonyCasts][2]. +The Security component for Symfony 7.1 is [backed][1] by [SymfonyCasts][2]. Learn Symfony faster by watching real projects being built and actively coding along with them. SymfonyCasts bridges that learning gap, bringing you video diff --git a/Resources/translations/security.he.xlf b/Resources/translations/security.he.xlf index b1d6afd4..1cf02a4e 100644 --- a/Resources/translations/security.he.xlf +++ b/Resources/translations/security.he.xlf @@ -4,15 +4,15 @@ An authentication exception occurred. - שגיאה באימות + התרחשה שגיאה באימות. Authentication credentials could not be found. - פרטי זיהוי לא נמצאו. + פרטי הזיהוי לא נמצאו. Authentication request could not be processed due to a system problem. - לא ניתן היה לעבד את בקשת אימות בגלל בעיית מערכת. + לא ניתן היה לעבד את בקשת האימות בגלל בעיית מערכת. Invalid credentials. @@ -20,7 +20,7 @@ Cookie has already been used by someone else. - עוגיה כבר שומשה. + עוגיה כבר שומשה על ידי מישהו אחר. Not privileged to request the resource. @@ -32,15 +32,15 @@ No authentication provider found to support the authentication token. - לא נמצא ספק אימות המתאימה לבקשה. + לא נמצא ספק אימות המתאים לבקשה. No session available, it either timed out or cookies are not enabled. - אין סיישן זמין, או שתם הזמן הקצוב או העוגיות אינן מופעלות. + אין מפגש זמין, תם הזמן הקצוב או שהעוגיות אינן מופעלות. No token could be found. - הטוקן לא נמצא. + אסימון לא נמצא. Username could not be found. @@ -72,11 +72,11 @@ Too many failed login attempts, please try again in %minutes% minute. - יותר מדי ניסיונות כניסה כושלים, אנא נסה שוב בוד %minutes% דקה. + יותר מדי ניסיונות כניסה כושלים, אנא נסה שוב בעוד %minutes% דקה. Too many failed login attempts, please try again in %minutes% minutes. - יותר מדי ניסיונות כניסה כושלים, אנא נסה שוב בעוד %minutes% דקות. + יותר מדי ניסיונות כניסה כושלים, אנא נסה שוב בעוד %minutes% דקות. diff --git a/Resources/translations/security.sk.xlf b/Resources/translations/security.sk.xlf index b08757de..3820bdcc 100644 --- a/Resources/translations/security.sk.xlf +++ b/Resources/translations/security.sk.xlf @@ -76,7 +76,7 @@ Too many failed login attempts, please try again in %minutes% minutes. - Príliš veľa neúspešných pokusov o prihlásenie, skúste to prosím znova o %minutes% minútu.|Príliš veľa neúspešných pokusov o prihlásenie, skúste to prosím znova o %minutes% minúty.|Príliš veľa neúspešných pokusov o prihlásenie, skúste to prosím znova o %minutes% minút. + Príliš veľa neúspešných pokusov o prihlásenie, skúste to prosím znova o %minutes% minútu.|Príliš veľa neúspešných pokusov o prihlásenie, skúste to prosím znova o %minutes% minúty.|Príliš veľa neúspešných pokusov o prihlásenie, skúste to prosím znova o %minutes% minút. diff --git a/Resources/translations/security.sl.xlf b/Resources/translations/security.sl.xlf index 7d051400..2b7a592b 100644 --- a/Resources/translations/security.sl.xlf +++ b/Resources/translations/security.sl.xlf @@ -76,7 +76,7 @@ Too many failed login attempts, please try again in %minutes% minutes. - Preveč neuspešnih poskusov prijave, poskusite znova čez %minutes% minuto.|Preveč neuspešnih poskusov prijave, poskusite znova čez %minutes% minut. + Preveč neuspešnih poskusov prijave, poskusite znova čez %minutes% minuto.|Preveč neuspešnih poskusov prijave, poskusite znova čez %minutes% minuti.|Preveč neuspešnih poskusov prijave, poskusite znova čez %minutes% minute.|Preveč neuspešnih poskusov prijave, poskusite znova čez %minutes% minut. diff --git a/Resources/translations/security.tl.xlf b/Resources/translations/security.tl.xlf index c02222de..aa47f179 100644 --- a/Resources/translations/security.tl.xlf +++ b/Resources/translations/security.tl.xlf @@ -72,11 +72,11 @@ Too many failed login attempts, please try again in %minutes% minute. - Napakaraming nabigong mga pagtatangka sa pag-login, pakisubukan ulit sa% minuto% minuto. + Napakaraming nabigong mga pagtatangka sa pag-login, pakisubukan ulit matapos ang %minutes% minuto. Too many failed login attempts, please try again in %minutes% minutes. - Napakaraming nabigong pagtatangka ng pag-login, mangyaring subukang muli sa loob ng %minutes% minuto.|Napakaraming nabigong pagtatangka ng pag-login, mangyaring subukang muli sa loob ng %minutes% minuto. + Napakaraming nabigong mga pagtatangka sa pag-login, pakisubukan ulit matapos ang %minutes% minuto. diff --git a/Role/RoleHierarchy.php b/Role/RoleHierarchy.php index d7960d48..a2a58457 100644 --- a/Role/RoleHierarchy.php +++ b/Role/RoleHierarchy.php @@ -18,23 +18,18 @@ */ class RoleHierarchy implements RoleHierarchyInterface { - private $hierarchy; /** @var array> */ - protected $map; + protected array $map; /** * @param array> $hierarchy */ - public function __construct(array $hierarchy) - { - $this->hierarchy = $hierarchy; - + public function __construct( + private array $hierarchy, + ) { $this->buildRoleMap(); } - /** - * {@inheritdoc} - */ public function getReachableRoleNames(array $roles): array { $reachableRoles = $roles; @@ -52,7 +47,7 @@ public function getReachableRoleNames(array $roles): array return array_values(array_unique($reachableRoles)); } - protected function buildRoleMap() + protected function buildRoleMap(): void { $this->map = []; foreach ($this->hierarchy as $main => $roles) { diff --git a/Security.php b/Security.php deleted file mode 100644 index 0e0d4e5b..00000000 --- a/Security.php +++ /dev/null @@ -1,70 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core; - -use Psr\Container\ContainerInterface; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; -use Symfony\Component\Security\Core\User\UserInterface; - -/** - * Helper class for commonly-needed security tasks. - * - * @final - */ -class Security implements AuthorizationCheckerInterface -{ - public const ACCESS_DENIED_ERROR = '_security.403_error'; - public const AUTHENTICATION_ERROR = '_security.last_error'; - public const LAST_USERNAME = '_security.last_username'; - public const MAX_USERNAME_LENGTH = 4096; - - private $container; - - public function __construct(ContainerInterface $container) - { - $this->container = $container; - } - - public function getUser(): ?UserInterface - { - if (!$token = $this->getToken()) { - return null; - } - - $user = $token->getUser(); - - // @deprecated since Symfony 5.4, $user will always be a UserInterface instance - if (!$user instanceof UserInterface) { - return null; - } - - return $user; - } - - /** - * Checks if the attributes are granted against the current authentication token and optionally supplied subject. - * - * @param mixed $attributes - * @param mixed $subject - */ - public function isGranted($attributes, $subject = null): bool - { - return $this->container->get('security.authorization_checker') - ->isGranted($attributes, $subject); - } - - public function getToken(): ?TokenInterface - { - return $this->container->get('security.token_storage')->getToken(); - } -} diff --git a/Signature/ExpiredSignatureStorage.php b/Signature/ExpiredSignatureStorage.php index 9861c158..62026644 100644 --- a/Signature/ExpiredSignatureStorage.php +++ b/Signature/ExpiredSignatureStorage.php @@ -18,13 +18,10 @@ */ final class ExpiredSignatureStorage { - private $cache; - private $lifetime; - - public function __construct(CacheItemPoolInterface $cache, int $lifetime) - { - $this->cache = $cache; - $this->lifetime = $lifetime; + public function __construct( + private CacheItemPoolInterface $cache, + private int $lifetime, + ) { } public function countUsages(string $hash): int diff --git a/Signature/SignatureHasher.php b/Signature/SignatureHasher.php index da4bfdbd..903f5b34 100644 --- a/Signature/SignatureHasher.php +++ b/Signature/SignatureHasher.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Core\Signature; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; +use Symfony\Component\Security\Core\Exception\InvalidArgumentException; use Symfony\Component\Security\Core\Signature\Exception\ExpiredSignatureException; use Symfony\Component\Security\Core\Signature\Exception\InvalidSignatureException; use Symfony\Component\Security\Core\User\UserInterface; @@ -24,24 +25,21 @@ */ class SignatureHasher { - private $propertyAccessor; - private $signatureProperties; - private $secret; - private $expiredSignaturesStorage; - private $maxUses; - /** * @param array $signatureProperties Properties of the User; the hash is invalidated if these properties change * @param ExpiredSignatureStorage|null $expiredSignaturesStorage If provided, secures a sequence of hashes that are expired * @param int|null $maxUses Used together with $expiredSignatureStorage to allow a maximum usage of a hash */ - public function __construct(PropertyAccessorInterface $propertyAccessor, array $signatureProperties, string $secret, ?ExpiredSignatureStorage $expiredSignaturesStorage = null, ?int $maxUses = null) - { - $this->propertyAccessor = $propertyAccessor; - $this->signatureProperties = $signatureProperties; - $this->secret = $secret; - $this->expiredSignaturesStorage = $expiredSignaturesStorage; - $this->maxUses = $maxUses; + public function __construct( + private PropertyAccessorInterface $propertyAccessor, + private array $signatureProperties, + #[\SensitiveParameter] private string $secret, + private ?ExpiredSignatureStorage $expiredSignaturesStorage = null, + private ?int $maxUses = null, + ) { + if (!$secret) { + throw new InvalidArgumentException('A non-empty secret is required.'); + } } /** @@ -89,7 +87,7 @@ public function verifySignatureHash(UserInterface $user, int $expires, string $h if ($this->expiredSignaturesStorage && $this->maxUses) { if ($this->expiredSignaturesStorage->countUsages($hash) >= $this->maxUses) { - throw new ExpiredSignatureException(sprintf('Signature can only be used "%d" times.', $this->maxUses)); + throw new ExpiredSignatureException(\sprintf('Signature can only be used "%d" times.', $this->maxUses)); } $this->expiredSignaturesStorage->incrementUsages($hash); @@ -103,7 +101,7 @@ public function verifySignatureHash(UserInterface $user, int $expires, string $h */ public function computeSignatureHash(UserInterface $user, int $expires): string { - $userIdentifier = method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(); + $userIdentifier = $user->getUserIdentifier(); $fieldsHash = hash_init('sha256'); foreach ($this->signatureProperties as $property) { @@ -112,8 +110,8 @@ public function computeSignatureHash(UserInterface $user, int $expires): string $value = $value->format('c'); } - if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { - throw new \InvalidArgumentException(sprintf('The property path "%s" on the user object "%s" must return a value that can be cast to a string, but "%s" was returned.', $property, \get_class($user), get_debug_type($value))); + if (!\is_scalar($value) && !$value instanceof \Stringable) { + throw new \InvalidArgumentException(\sprintf('The property path "%s" on the user object "%s" must return a value that can be cast to a string, but "%s" was returned.', $property, $user::class, get_debug_type($value))); } hash_update($fieldsHash, ':'.base64_encode($value)); } diff --git a/Test/AccessDecisionStrategyTestCase.php b/Test/AccessDecisionStrategyTestCase.php index d542588f..792e7779 100644 --- a/Test/AccessDecisionStrategyTestCase.php +++ b/Test/AccessDecisionStrategyTestCase.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Security\Core\Test; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; @@ -29,6 +30,7 @@ abstract class AccessDecisionStrategyTestCase extends TestCase * * @param VoterInterface[] $voters */ + #[DataProvider('provideStrategyTests')] final public function testDecide(AccessDecisionStrategyInterface $strategy, array $voters, bool $expected) { $token = $this->createMock(TokenInterface::class); @@ -64,11 +66,9 @@ final protected static function getVoters(int $grants, int $denies, int $abstain final protected static function getVoter(int $vote): VoterInterface { return new class($vote) implements VoterInterface { - private $vote; - - public function __construct(int $vote) - { - $this->vote = $vote; + public function __construct( + private int $vote, + ) { } public function vote(TokenInterface $token, $subject, array $attributes): int diff --git a/Tests/Authentication/AuthenticationProviderManagerTest.php b/Tests/Authentication/AuthenticationProviderManagerTest.php deleted file mode 100644 index 11f36629..00000000 --- a/Tests/Authentication/AuthenticationProviderManagerTest.php +++ /dev/null @@ -1,210 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Tests\Authentication; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager; -use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; -use Symfony\Component\Security\Core\AuthenticationEvents; -use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent; -use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent; -use Symfony\Component\Security\Core\Exception\AccountStatusException; -use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Component\Security\Core\Exception\ProviderNotFoundException; -use Symfony\Component\Security\Core\User\InMemoryUser; - -/** - * @group legacy - */ -class AuthenticationProviderManagerTest extends TestCase -{ - public function testAuthenticateWithoutProviders() - { - $this->expectException(\InvalidArgumentException::class); - new AuthenticationProviderManager([]); - } - - public function testAuthenticateWithProvidersWithIncorrectInterface() - { - $this->expectException(\InvalidArgumentException::class); - (new AuthenticationProviderManager([ - new \stdClass(), - ]))->authenticate($this->createMock(TokenInterface::class)); - } - - public function testAuthenticateWhenNoProviderSupportsToken() - { - $manager = new AuthenticationProviderManager([ - $this->getAuthenticationProvider(false), - ]); - - try { - $manager->authenticate($token = $this->createMock(TokenInterface::class)); - $this->fail(); - } catch (ProviderNotFoundException $e) { - $this->assertSame($token, $e->getToken()); - } - } - - public function testAuthenticateWhenProviderReturnsAccountStatusException() - { - $secondAuthenticationProvider = $this->createMock(AuthenticationProviderInterface::class); - - $manager = new AuthenticationProviderManager([ - $this->getAuthenticationProvider(true, null, AccountStatusException::class), - $secondAuthenticationProvider, - ]); - - // AccountStatusException stops authentication - $secondAuthenticationProvider->expects($this->never())->method('supports'); - - try { - $manager->authenticate($token = $this->createMock(TokenInterface::class)); - $this->fail(); - } catch (AccountStatusException $e) { - $this->assertSame($token, $e->getToken()); - } - } - - public function testAuthenticateWhenProviderReturnsAuthenticationException() - { - $manager = new AuthenticationProviderManager([ - $this->getAuthenticationProvider(true, null, AuthenticationException::class), - ]); - - try { - $manager->authenticate($token = $this->createMock(TokenInterface::class)); - $this->fail(); - } catch (AuthenticationException $e) { - $this->assertSame($token, $e->getToken()); - } - } - - public function testAuthenticateWhenOneReturnsAuthenticationExceptionButNotAll() - { - $expected = $this->createMock(TokenInterface::class); - $expected->expects($this->any())->method('getUser')->willReturn(new InMemoryUser('wouter', null)); - - $manager = new AuthenticationProviderManager([ - $this->getAuthenticationProvider(true, null, AuthenticationException::class), - $this->getAuthenticationProvider(true, $expected), - ]); - - $token = $manager->authenticate($this->createMock(TokenInterface::class)); - $this->assertSame($expected, $token); - } - - public function testAuthenticateReturnsTokenOfTheFirstMatchingProvider() - { - $second = $this->createMock(AuthenticationProviderInterface::class); - $second - ->expects($this->never()) - ->method('supports') - ; - $expected = $this->createMock(TokenInterface::class); - $expected->expects($this->any())->method('getUser')->willReturn(new InMemoryUser('wouter', null)); - $manager = new AuthenticationProviderManager([ - $this->getAuthenticationProvider(true, $expected), - $second, - ]); - - $token = $manager->authenticate($this->createMock(TokenInterface::class)); - $this->assertSame($expected, $token); - } - - public function testEraseCredentialFlag() - { - $manager = new AuthenticationProviderManager([ - $this->getAuthenticationProvider(true, $token = new UsernamePasswordToken('foo', 'bar', 'key')), - ]); - - $token = $manager->authenticate($this->createMock(TokenInterface::class)); - $this->assertEquals('', $token->getCredentials()); - - $manager = new AuthenticationProviderManager([ - $this->getAuthenticationProvider(true, $token = new UsernamePasswordToken('foo', 'bar', 'key')), - ], false); - - $token = $manager->authenticate($this->createMock(TokenInterface::class)); - $this->assertEquals('bar', $token->getCredentials()); - } - - public function testAuthenticateDispatchesAuthenticationFailureEvent() - { - $token = new UsernamePasswordToken('foo', 'bar', 'key'); - $provider = $this->createMock(AuthenticationProviderInterface::class); - $provider->expects($this->once())->method('supports')->willReturn(true); - $provider->expects($this->once())->method('authenticate')->willThrowException($exception = new AuthenticationException()); - - $dispatcher = $this->createMock(EventDispatcherInterface::class); - $dispatcher - ->expects($this->once()) - ->method('dispatch') - ->with($this->equalTo(new AuthenticationFailureEvent($token, $exception)), AuthenticationEvents::AUTHENTICATION_FAILURE); - - $manager = new AuthenticationProviderManager([$provider]); - $manager->setEventDispatcher($dispatcher); - - try { - $manager->authenticate($token); - $this->fail('->authenticate() should rethrow exceptions'); - } catch (AuthenticationException $e) { - $this->assertSame($token, $exception->getToken()); - } - } - - public function testAuthenticateDispatchesAuthenticationSuccessEvent() - { - $token = new UsernamePasswordToken('foo', 'bar', 'key'); - - $provider = $this->createMock(AuthenticationProviderInterface::class); - $provider->expects($this->once())->method('supports')->willReturn(true); - $provider->expects($this->once())->method('authenticate')->willReturn($token); - - $dispatcher = $this->createMock(EventDispatcherInterface::class); - $dispatcher - ->expects($this->once()) - ->method('dispatch') - ->with($this->equalTo(new AuthenticationSuccessEvent($token)), AuthenticationEvents::AUTHENTICATION_SUCCESS); - - $manager = new AuthenticationProviderManager([$provider]); - $manager->setEventDispatcher($dispatcher); - - $this->assertSame($token, $manager->authenticate($token)); - } - - protected function getAuthenticationProvider($supports, $token = null, $exception = null) - { - $provider = $this->createMock(AuthenticationProviderInterface::class); - $provider->expects($this->once()) - ->method('supports') - ->willReturn($supports) - ; - - if (null !== $token) { - $provider->expects($this->once()) - ->method('authenticate') - ->willReturn($token) - ; - } elseif (null !== $exception) { - $provider->expects($this->once()) - ->method('authenticate') - ->willThrowException($this->getMockBuilder($exception)->onlyMethods([])->getMock()) - ; - } - - return $provider; - } -} diff --git a/Tests/Authentication/AuthenticationTrustResolverTest.php b/Tests/Authentication/AuthenticationTrustResolverTest.php index d7845c2b..fc559983 100644 --- a/Tests/Authentication/AuthenticationTrustResolverTest.php +++ b/Tests/Authentication/AuthenticationTrustResolverTest.php @@ -13,7 +13,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; -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\User\InMemoryUser; @@ -21,17 +20,6 @@ class AuthenticationTrustResolverTest extends TestCase { - /** - * @group legacy - */ - public function testIsAnonymous() - { - $resolver = new AuthenticationTrustResolver(); - $this->assertFalse($resolver->isAnonymous(null)); - $this->assertFalse($resolver->isAnonymous($this->getRememberMeToken())); - $this->assertFalse($resolver->isAnonymous(new FakeCustomToken())); - } - public function testIsRememberMe() { $resolver = new AuthenticationTrustResolver(); @@ -60,21 +48,9 @@ public function testIsAuthenticated() $this->assertTrue($resolver->isAuthenticated(new FakeCustomToken())); } - /** - * @group legacy - */ - public function testIsAnonymousWithClassAsConstructorButStillExtending() - { - $resolver = $this->getResolver(); - - $this->assertFalse($resolver->isAnonymous(null)); - $this->assertFalse($resolver->isAnonymous(new FakeCustomToken())); - $this->assertFalse($resolver->isAnonymous($this->getRememberMeToken())); - } - public function testIsRememberMeWithClassAsConstructorButStillExtending() { - $resolver = $this->getResolver(); + $resolver = new AuthenticationTrustResolver(); $this->assertFalse($resolver->isRememberMe(null)); $this->assertFalse($resolver->isRememberMe(new FakeCustomToken())); @@ -84,7 +60,7 @@ public function testIsRememberMeWithClassAsConstructorButStillExtending() public function testisFullFledgedWithClassAsConstructorButStillExtending() { - $resolver = $this->getResolver(); + $resolver = new AuthenticationTrustResolver(); $this->assertFalse($resolver->isFullFledged(null)); $this->assertFalse($resolver->isFullFledged($this->getRememberMeToken())); @@ -92,49 +68,11 @@ public function testisFullFledgedWithClassAsConstructorButStillExtending() $this->assertTrue($resolver->isFullFledged(new FakeCustomToken())); } - /** - * @group legacy - */ - public function testLegacy() - { - $resolver = $this->getResolver(); - - $this->assertTrue($resolver->isAnonymous($this->getAnonymousToken())); - $this->assertTrue($resolver->isAnonymous($this->getRealCustomAnonymousToken())); - - $this->assertFalse($resolver->isRememberMe($this->getAnonymousToken())); - - $this->assertFalse($resolver->isFullFledged($this->getAnonymousToken())); - $this->assertFalse($resolver->isFullFledged($this->getRealCustomAnonymousToken())); - } - - protected function getAnonymousToken() - { - return new AnonymousToken('secret', 'anon.'); - } - - private function getRealCustomAnonymousToken() - { - return new class() extends AnonymousToken { - public function __construct() - { - } - }; - } - protected function getRememberMeToken() { $user = new InMemoryUser('wouter', '', ['ROLE_USER']); - return new RememberMeToken($user, 'main', 'secret'); - } - - protected function getResolver() - { - return new AuthenticationTrustResolver( - AnonymousToken::class, - RememberMeToken::class - ); + return new RememberMeToken($user, 'main'); } } @@ -164,7 +102,7 @@ public function getRoleNames(): array { } - public function getCredentials() + public function getCredentials(): mixed { } @@ -173,11 +111,7 @@ public function getUser(): UserInterface return new InMemoryUser('wouter', '', ['ROLE_USER']); } - public function setUser($user) - { - } - - public function getUsername(): string + public function setUser($user): void { } @@ -185,16 +119,7 @@ public function getUserIdentifier(): string { } - public function isAuthenticated(): bool - { - return true; - } - - public function setAuthenticated(bool $isAuthenticated) - { - } - - public function eraseCredentials() + public function eraseCredentials(): void { } @@ -202,7 +127,7 @@ public function getAttributes(): array { } - public function setAttributes(array $attributes) + public function setAttributes(array $attributes): void { } @@ -210,11 +135,11 @@ public function hasAttribute(string $name): bool { } - public function getAttribute(string $name) + public function getAttribute(string $name): mixed { } - public function setAttribute(string $name, $value) + public function setAttribute(string $name, $value): void { } } diff --git a/Tests/Authentication/Provider/AnonymousAuthenticationProviderTest.php b/Tests/Authentication/Provider/AnonymousAuthenticationProviderTest.php deleted file mode 100644 index 3dbc3898..00000000 --- a/Tests/Authentication/Provider/AnonymousAuthenticationProviderTest.php +++ /dev/null @@ -1,74 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Tests\Authentication\Provider; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Security\Core\Authentication\Provider\AnonymousAuthenticationProvider; -use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; - -/** - * @group legacy - */ -class AnonymousAuthenticationProviderTest extends TestCase -{ - public function testSupports() - { - $provider = $this->getProvider('foo'); - - $this->assertTrue($provider->supports($this->getSupportedToken('foo'))); - $this->assertFalse($provider->supports($this->createMock(TokenInterface::class))); - } - - public function testAuthenticateWhenTokenIsNotSupported() - { - $this->expectException(AuthenticationException::class); - $this->expectExceptionMessage('The token is not supported by this authentication provider.'); - $provider = $this->getProvider('foo'); - - $provider->authenticate($this->createMock(TokenInterface::class)); - } - - public function testAuthenticateWhenSecretIsNotValid() - { - $this->expectException(BadCredentialsException::class); - $provider = $this->getProvider('foo'); - - $provider->authenticate($this->getSupportedToken('bar')); - } - - public function testAuthenticate() - { - $provider = $this->getProvider('foo'); - $token = $this->getSupportedToken('foo'); - - $this->assertSame($token, $provider->authenticate($token)); - } - - protected function getSupportedToken($secret) - { - $token = $this->getMockBuilder(AnonymousToken::class)->onlyMethods(['getSecret'])->disableOriginalConstructor()->getMock(); - $token->expects($this->any()) - ->method('getSecret') - ->willReturn($secret) - ; - - return $token; - } - - protected function getProvider($secret) - { - return new AnonymousAuthenticationProvider($secret); - } -} diff --git a/Tests/Authentication/Provider/DaoAuthenticationProviderTest.php b/Tests/Authentication/Provider/DaoAuthenticationProviderTest.php deleted file mode 100644 index a40c27f2..00000000 --- a/Tests/Authentication/Provider/DaoAuthenticationProviderTest.php +++ /dev/null @@ -1,395 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Tests\Authentication\Provider; - -use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; -use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; -use Symfony\Component\PasswordHasher\Hasher\PlaintextPasswordHasher; -use Symfony\Component\PasswordHasher\PasswordHasherInterface; -use Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider; -use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; -use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; -use Symfony\Component\Security\Core\Exception\AuthenticationServiceException; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; -use Symfony\Component\Security\Core\Exception\UserNotFoundException; -use Symfony\Component\Security\Core\User\InMemoryUser; -use Symfony\Component\Security\Core\User\InMemoryUserProvider; -use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; -use Symfony\Component\Security\Core\User\UserCheckerInterface; -use Symfony\Component\Security\Core\User\UserInterface; -use Symfony\Component\Security\Core\User\UserProviderInterface; - -/** - * @group legacy - */ -class DaoAuthenticationProviderTest extends TestCase -{ - use ExpectDeprecationTrait; - - public function testRetrieveUserWhenProviderDoesNotReturnAnUserInterface() - { - $this->expectException(AuthenticationServiceException::class); - $userProvider = $this->createMock(DaoAuthenticationProviderTest_UserProvider::class); - $userProvider->expects($this->once()) - ->method('loadUserByUsername') - ->willReturn('fabien') - ; - $provider = $this->getProvider(null, null, null, $userProvider); - $method = new \ReflectionMethod($provider, 'retrieveUser'); - $method->setAccessible(true); - - $this->expectDeprecation('Since symfony/security-core 5.3: Not implementing method "loadUserByIdentifier()" in user provider "'.get_debug_type($userProvider).'" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.'); - - $method->invoke($provider, 'fabien', $this->getSupportedToken()); - } - - public function testRetrieveUserWhenUsernameIsNotFoundWithLegacyEncoderFactory() - { - $this->expectException(UserNotFoundException::class); - $userProvider = new InMemoryUserProvider(); - - $provider = new DaoAuthenticationProvider($userProvider, $this->createMock(UserCheckerInterface::class), 'key', $this->createMock(EncoderFactoryInterface::class)); - $method = new \ReflectionMethod($provider, 'retrieveUser'); - $method->setAccessible(true); - - $method->invoke($provider, 'fabien', $this->getSupportedToken()); - } - - public function testRetrieveUserWhenUsernameIsNotFound() - { - $this->expectException(UserNotFoundException::class); - $userProvider = new InMemoryUserProvider(); - - $provider = new DaoAuthenticationProvider($userProvider, $this->createMock(UserCheckerInterface::class), 'key', $this->createMock(PasswordHasherFactoryInterface::class)); - $method = new \ReflectionMethod($provider, 'retrieveUser'); - $method->setAccessible(true); - - $method->invoke($provider, 'fabien', $this->getSupportedToken()); - } - - public function testRetrieveUserWhenAnExceptionOccurs() - { - $this->expectException(AuthenticationServiceException::class); - $userProvider = $this->createMock(InMemoryUserProvider::class); - $userProvider->expects($this->once()) - ->method('loadUserByIdentifier') - ->willThrowException(new \RuntimeException()) - ; - - $provider = new DaoAuthenticationProvider($userProvider, $this->createMock(UserCheckerInterface::class), 'key', $this->createMock(PasswordHasherFactoryInterface::class)); - $method = new \ReflectionMethod($provider, 'retrieveUser'); - $method->setAccessible(true); - - $method->invoke($provider, 'fabien', $this->getSupportedToken()); - } - - public function testRetrieveUserReturnsUserFromTokenOnReauthentication() - { - $userProvider = $this->createMock(InMemoryUserProvider::class); - $userProvider->expects($this->never()) - ->method('loadUserByIdentifier') - ; - - $user = new TestUser(); - $token = $this->getSupportedToken(); - $token->expects($this->once()) - ->method('getUser') - ->willReturn($user) - ; - - $provider = new DaoAuthenticationProvider($userProvider, $this->createMock(UserCheckerInterface::class), 'key', $this->createMock(PasswordHasherFactoryInterface::class)); - $reflection = new \ReflectionMethod($provider, 'retrieveUser'); - $reflection->setAccessible(true); - $result = $reflection->invoke($provider, 'someUser', $token); - - $this->assertSame($user, $result); - } - - public function testRetrieveUser() - { - $userProvider = new InMemoryUserProvider(['fabien' => []]); - - $provider = new DaoAuthenticationProvider($userProvider, $this->createMock(UserCheckerInterface::class), 'key', $this->createMock(PasswordHasherFactoryInterface::class)); - $method = new \ReflectionMethod($provider, 'retrieveUser'); - $method->setAccessible(true); - - $this->assertEquals('fabien', $method->invoke($provider, 'fabien', $this->getSupportedToken())->getUserIdentifier()); - } - - public function testCheckAuthenticationWhenCredentialsAreEmpty() - { - $this->expectException(BadCredentialsException::class); - $hasher = $this->getMockBuilder(PasswordHasherInterface::class)->getMock(); - $hasher - ->expects($this->never()) - ->method('verify') - ; - - $provider = $this->getProvider(null, null, $hasher); - $method = new \ReflectionMethod($provider, 'checkAuthentication'); - $method->setAccessible(true); - - $token = $this->getSupportedToken(); - $token - ->expects($this->once()) - ->method('getCredentials') - ->willReturn('') - ; - - $method->invoke($provider, new TestUser(), $token); - } - - public function testCheckAuthenticationWhenCredentialsAre0() - { - $hasher = $this->createMock(PasswordHasherInterface::class); - $hasher - ->expects($this->once()) - ->method('verify') - ->willReturn(true) - ; - - $provider = $this->getProvider(null, null, $hasher); - $method = new \ReflectionMethod($provider, 'checkAuthentication'); - $method->setAccessible(true); - - $token = $this->getSupportedToken(); - $token - ->expects($this->once()) - ->method('getCredentials') - ->willReturn('0') - ; - - $method->invoke( - $provider, - new InMemoryUser('username', 'password'), - $token - ); - } - - public function testCheckAuthenticationWhenCredentialsAreNotValid() - { - $this->expectException(BadCredentialsException::class); - $hasher = $this->createMock(PasswordHasherInterface::class); - $hasher->expects($this->once()) - ->method('verify') - ->willReturn(false) - ; - - $provider = $this->getProvider(null, null, $hasher); - $method = new \ReflectionMethod($provider, 'checkAuthentication'); - $method->setAccessible(true); - - $token = $this->getSupportedToken(); - $token->expects($this->once()) - ->method('getCredentials') - ->willReturn('foo') - ; - - $method->invoke($provider, new InMemoryUser('username', 'password'), $token); - } - - public function testCheckAuthenticationDoesNotReauthenticateWhenPasswordHasChanged() - { - $this->expectException(BadCredentialsException::class); - $user = $this->createMock(UserInterface::class); - $user->expects($this->once()) - ->method('getPassword') - ->willReturn('foo') - ; - - $token = $this->getSupportedToken(); - $token->expects($this->once()) - ->method('getUser') - ->willReturn($user); - - $dbUser = $this->createMock(UserInterface::class); - $dbUser->expects($this->once()) - ->method('getPassword') - ->willReturn('newFoo') - ; - - $provider = $this->getProvider(); - $reflection = new \ReflectionMethod($provider, 'checkAuthentication'); - $reflection->setAccessible(true); - $reflection->invoke($provider, $dbUser, $token); - } - - public function testCheckAuthenticationWhenTokenNeedsReauthenticationWorksWithoutOriginalCredentials() - { - $user = $this->createMock(UserInterface::class); - $user->expects($this->once()) - ->method('getPassword') - ->willReturn('foo') - ; - - $token = $this->getSupportedToken(); - $token->expects($this->once()) - ->method('getUser') - ->willReturn($user); - - $dbUser = $this->createMock(UserInterface::class); - $dbUser->expects($this->once()) - ->method('getPassword') - ->willReturn('foo') - ; - - $provider = $this->getProvider(); - $reflection = new \ReflectionMethod($provider, 'checkAuthentication'); - $reflection->setAccessible(true); - $reflection->invoke($provider, $dbUser, $token); - } - - public function testCheckAuthentication() - { - $hasher = $this->createMock(PasswordHasherInterface::class); - $hasher->expects($this->once()) - ->method('verify') - ->willReturn(true) - ; - - $provider = $this->getProvider(null, null, $hasher); - $method = new \ReflectionMethod($provider, 'checkAuthentication'); - $method->setAccessible(true); - - $token = $this->getSupportedToken(); - $token->expects($this->once()) - ->method('getCredentials') - ->willReturn('foo') - ; - - $method->invoke($provider, new InMemoryUser('username', 'password'), $token); - } - - public function testPasswordUpgrades() - { - $user = new InMemoryUser('user', 'pwd'); - - $hasher = $this->createMock(PasswordHasherInterface::class); - $hasher->expects($this->once()) - ->method('verify') - ->willReturn(true) - ; - $hasher->expects($this->once()) - ->method('hash') - ->willReturn('foobar') - ; - $hasher->expects($this->once()) - ->method('needsRehash') - ->willReturn(true) - ; - - $provider = $this->getProvider(null, null, $hasher); - - $userProvider = ((array) $provider)[sprintf("\0%s\0userProvider", DaoAuthenticationProvider::class)]; - $userProvider->expects($this->once()) - ->method('upgradePassword') - ->with($user, 'foobar') - ; - - $method = new \ReflectionMethod($provider, 'checkAuthentication'); - $method->setAccessible(true); - - $token = $this->getSupportedToken(); - $token->expects($this->once()) - ->method('getCredentials') - ->willReturn('foo') - ; - - $method->invoke($provider, $user, $token); - } - - protected function getSupportedToken() - { - $mock = $this->getMockBuilder(UsernamePasswordToken::class)->onlyMethods(['getCredentials', 'getUser', 'getProviderKey'])->disableOriginalConstructor()->getMock(); - $mock - ->expects($this->any()) - ->method('getProviderKey') - ->willReturn('key') - ; - - return $mock; - } - - protected function getProvider($user = null, $userChecker = null, $passwordHasher = null, $userProvider = null) - { - if (null === $userProvider) { - $userProvider = $this->createMock(PasswordUpgraderProvider::class); - if (null !== $user) { - $userProvider->expects($this->once()) - ->method('loadUserByIdentifier') - ->willReturn($user) - ; - } - } - - if (null === $userChecker) { - $userChecker = $this->createMock(UserCheckerInterface::class); - } - - if (null === $passwordHasher) { - $passwordHasher = new PlaintextPasswordHasher(); - } - - $hasherFactory = $this->createMock(PasswordHasherFactoryInterface::class); - $hasherFactory - ->expects($this->any()) - ->method('getPasswordHasher') - ->willReturn($passwordHasher) - ; - - return new DaoAuthenticationProvider($userProvider, $userChecker, 'key', $hasherFactory); - } -} - -class TestUser implements UserInterface -{ - public function getRoles(): array - { - return []; - } - - public function getPassword(): ?string - { - return 'secret'; - } - - public function getSalt(): ?string - { - return null; - } - - public function getUsername(): string - { - return 'jane_doe'; - } - - public function getUserIdentifier(): string - { - return 'jane_doe'; - } - - public function eraseCredentials() - { - } -} -interface PasswordUpgraderProvider extends UserProviderInterface, PasswordUpgraderInterface -{ - public function upgradePassword($user, string $newHashedPassword): void; - - public function loadUserByIdentifier(string $identifier): UserInterface; -} - -interface DaoAuthenticationProviderTest_UserProvider extends UserProviderInterface -{ - public function loadUserByUsername($username): string; -} diff --git a/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php b/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php deleted file mode 100644 index 51c4e949..00000000 --- a/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php +++ /dev/null @@ -1,252 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Tests\Authentication\Provider; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Ldap\Adapter\CollectionInterface; -use Symfony\Component\Ldap\Adapter\QueryInterface; -use Symfony\Component\Ldap\Entry; -use Symfony\Component\Ldap\Exception\ConnectionException; -use Symfony\Component\Ldap\LdapInterface; -use Symfony\Component\Security\Core\Authentication\Provider\LdapBindAuthenticationProvider; -use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; -use Symfony\Component\Security\Core\User\InMemoryUser; -use Symfony\Component\Security\Core\User\InMemoryUserProvider; -use Symfony\Component\Security\Core\User\UserCheckerInterface; -use Symfony\Component\Security\Core\User\UserProviderInterface; - -/** - * @requires extension ldap - * - * @group legacy - */ -class LdapBindAuthenticationProviderTest extends TestCase -{ - public function testEmptyPasswordShouldThrowAnException() - { - $this->expectException(BadCredentialsException::class); - $this->expectExceptionMessage('The presented password must not be empty.'); - $userProvider = $this->createMock(UserProviderInterface::class); - $ldap = $this->createMock(LdapInterface::class); - $userChecker = $this->createMock(UserCheckerInterface::class); - - $provider = new LdapBindAuthenticationProvider($userProvider, $userChecker, 'key', $ldap); - $reflection = new \ReflectionMethod($provider, 'checkAuthentication'); - $reflection->setAccessible(true); - - $reflection->invoke($provider, new InMemoryUser('foo', null), new UsernamePasswordToken('foo', '', 'key')); - } - - public function testNullPasswordShouldThrowAnException() - { - $this->expectException(BadCredentialsException::class); - $this->expectExceptionMessage('The presented password must not be empty.'); - $userProvider = $this->createMock(UserProviderInterface::class); - $ldap = $this->createMock(LdapInterface::class); - $userChecker = $this->createMock(UserCheckerInterface::class); - - $provider = new LdapBindAuthenticationProvider($userProvider, $userChecker, 'key', $ldap); - $reflection = new \ReflectionMethod($provider, 'checkAuthentication'); - $reflection->setAccessible(true); - - $reflection->invoke($provider, new InMemoryUser('foo', null), new UsernamePasswordToken('foo', null, 'key')); - } - - public function testBindFailureShouldThrowAnException() - { - $this->expectException(BadCredentialsException::class); - $this->expectExceptionMessage('The presented password is invalid.'); - $userProvider = $this->createMock(UserProviderInterface::class); - $ldap = $this->createMock(LdapInterface::class); - $ldap - ->expects($this->once()) - ->method('bind') - ->willThrowException(new ConnectionException()) - ; - $ldap->method('escape')->willReturnArgument(0); - $userChecker = $this->createMock(UserCheckerInterface::class); - - $provider = new LdapBindAuthenticationProvider($userProvider, $userChecker, 'key', $ldap); - $reflection = new \ReflectionMethod($provider, 'checkAuthentication'); - $reflection->setAccessible(true); - - $reflection->invoke($provider, new InMemoryUser('foo', null), new UsernamePasswordToken('foo', 'bar', 'key')); - } - - public function testRetrieveUser() - { - $userProvider = $this->createMock(InMemoryUserProvider::class); - $userProvider - ->expects($this->once()) - ->method('loadUserByIdentifier') - ->with('foo') - ; - $ldap = $this->createMock(LdapInterface::class); - - $userChecker = $this->createMock(UserCheckerInterface::class); - - $provider = new LdapBindAuthenticationProvider($userProvider, $userChecker, 'key', $ldap); - $reflection = new \ReflectionMethod($provider, 'retrieveUser'); - $reflection->setAccessible(true); - - $reflection->invoke($provider, 'foo', new UsernamePasswordToken('foo', 'bar', 'key')); - } - - public function testQueryForDn() - { - $userProvider = $this->createMock(UserProviderInterface::class); - - $collection = new class([new Entry('')]) extends \ArrayObject implements CollectionInterface { - public function toArray(): array - { - return $this->getArrayCopy(); - } - }; - - $query = $this->createMock(QueryInterface::class); - $query - ->expects($this->once()) - ->method('execute') - ->willReturn($collection) - ; - - $ldap = $this->createMock(LdapInterface::class); - $ldap - ->method('bind') - ->willReturnCallback(function (...$args) { - static $series = [ - ['elsa', 'test1234A$'], - ['', 'bar'], - ]; - - $this->assertSame(array_shift($series), $args); - }) - ; - $ldap - ->expects($this->once()) - ->method('escape') - ->with('foo', '') - ->willReturn('foo') - ; - $ldap - ->expects($this->once()) - ->method('query') - ->with('{username}', 'foobar') - ->willReturn($query) - ; - $userChecker = $this->createMock(UserCheckerInterface::class); - - $provider = new LdapBindAuthenticationProvider($userProvider, $userChecker, 'key', $ldap, '{username}', true, 'elsa', 'test1234A$'); - $provider->setQueryString('{username}bar'); - $reflection = new \ReflectionMethod($provider, 'checkAuthentication'); - $reflection->setAccessible(true); - - $reflection->invoke($provider, new InMemoryUser('foo', null), new UsernamePasswordToken('foo', 'bar', 'key')); - } - - public function testQueryWithUserForDn() - { - $userProvider = $this->createMock(UserProviderInterface::class); - - $collection = new class([new Entry('')]) extends \ArrayObject implements CollectionInterface { - public function toArray(): array - { - return $this->getArrayCopy(); - } - }; - - $query = $this->createMock(QueryInterface::class); - $query - ->expects($this->once()) - ->method('execute') - ->willReturn($collection) - ; - - $ldap = $this->createMock(LdapInterface::class); - $ldap - ->method('bind') - ->willReturnCallback(function (...$args) { - static $series = [ - ['elsa', 'test1234A$'], - ['', 'bar'], - ]; - - $this->assertSame(array_shift($series), $args); - }) - ; - $ldap - ->expects($this->once()) - ->method('escape') - ->with('foo', '') - ->willReturn('foo') - ; - $ldap - ->expects($this->once()) - ->method('query') - ->with('{username}', 'foobar') - ->willReturn($query) - ; - - $userChecker = $this->createMock(UserCheckerInterface::class); - - $provider = new LdapBindAuthenticationProvider($userProvider, $userChecker, 'key', $ldap, '{username}', true, 'elsa', 'test1234A$'); - $provider->setQueryString('{username}bar'); - $reflection = new \ReflectionMethod($provider, 'checkAuthentication'); - $reflection->setAccessible(true); - - $reflection->invoke($provider, new InMemoryUser('foo', null), new UsernamePasswordToken('foo', 'bar', 'key')); - } - - public function testEmptyQueryResultShouldThrowAnException() - { - $this->expectException(BadCredentialsException::class); - $this->expectExceptionMessage('The presented username is invalid.'); - $userProvider = $this->createMock(UserProviderInterface::class); - - $collection = $this->createMock(CollectionInterface::class); - - $query = $this->createMock(QueryInterface::class); - $query - ->expects($this->once()) - ->method('execute') - ->willReturn($collection) - ; - - $ldap = $this->createMock(LdapInterface::class); - $ldap - ->method('bind') - ->willReturnCallback(function (...$args) { - static $series = [ - ['elsa', 'test1234A$'], - ['', 'bar'], - ]; - - $this->assertSame(array_shift($series), $args); - }) - ; - $ldap - ->expects($this->once()) - ->method('query') - ->willReturn($query) - ; - $ldap->method('escape')->willReturnArgument(0); - $userChecker = $this->createMock(UserCheckerInterface::class); - - $provider = new LdapBindAuthenticationProvider($userProvider, $userChecker, 'key', $ldap, '{username}', true, 'elsa', 'test1234A$'); - $provider->setQueryString('{username}bar'); - $reflection = new \ReflectionMethod($provider, 'checkAuthentication'); - $reflection->setAccessible(true); - - $reflection->invoke($provider, new InMemoryUser('foo', null), new UsernamePasswordToken('foo', 'bar', 'key')); - } -} diff --git a/Tests/Authentication/Provider/PreAuthenticatedAuthenticationProviderTest.php b/Tests/Authentication/Provider/PreAuthenticatedAuthenticationProviderTest.php deleted file mode 100644 index 341122ba..00000000 --- a/Tests/Authentication/Provider/PreAuthenticatedAuthenticationProviderTest.php +++ /dev/null @@ -1,140 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Tests\Authentication\Provider; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Security\Core\Authentication\Provider\PreAuthenticatedAuthenticationProvider; -use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; -use Symfony\Component\Security\Core\Exception\LockedException; -use Symfony\Component\Security\Core\User\InMemoryUserProvider; -use Symfony\Component\Security\Core\User\UserCheckerInterface; -use Symfony\Component\Security\Core\User\UserInterface; - -/** - * @group legacy - */ -class PreAuthenticatedAuthenticationProviderTest extends TestCase -{ - public function testSupports() - { - $provider = $this->getProvider(); - - $this->assertTrue($provider->supports($this->getSupportedToken())); - $this->assertFalse($provider->supports($this->createMock(TokenInterface::class))); - - $token = $this->createMock(PreAuthenticatedToken::class); - $token - ->expects($this->once()) - ->method('getFirewallName') - ->willReturn('foo') - ; - $this->assertFalse($provider->supports($token)); - } - - public function testAuthenticateWhenTokenIsNotSupported() - { - $this->expectException(AuthenticationException::class); - $this->expectExceptionMessage('The token is not supported by this authentication provider.'); - $provider = $this->getProvider(); - - $provider->authenticate($this->createMock(TokenInterface::class)); - } - - public function testAuthenticateWhenNoUserIsSet() - { - $this->expectException(BadCredentialsException::class); - $provider = $this->getProvider(); - $provider->authenticate($this->getSupportedToken('')); - } - - public function testAuthenticate() - { - $user = $this->createMock(UserInterface::class); - $user - ->expects($this->once()) - ->method('getRoles') - ->willReturn([]) - ; - $provider = $this->getProvider($user); - - $token = $provider->authenticate($this->getSupportedToken('fabien', 'pass')); - $this->assertInstanceOf(PreAuthenticatedToken::class, $token); - $this->assertEquals('pass', $token->getCredentials()); - $this->assertEquals('key', $token->getFirewallName()); - $this->assertEquals([], $token->getRoleNames()); - $this->assertEquals(['foo' => 'bar'], $token->getAttributes(), '->authenticate() copies token attributes'); - $this->assertSame($user, $token->getUser()); - } - - public function testAuthenticateWhenUserCheckerThrowsException() - { - $this->expectException(LockedException::class); - $user = $this->createMock(UserInterface::class); - - $userChecker = $this->createMock(UserCheckerInterface::class); - $userChecker->expects($this->once()) - ->method('checkPostAuth') - ->willThrowException(new LockedException()) - ; - - $provider = $this->getProvider($user, $userChecker); - - $provider->authenticate($this->getSupportedToken('fabien')); - } - - protected function getSupportedToken($user = false, $credentials = false) - { - $token = $this->getMockBuilder(PreAuthenticatedToken::class)->onlyMethods(['getUser', 'getCredentials', 'getFirewallName'])->disableOriginalConstructor()->getMock(); - if (false !== $user) { - $token->expects($this->once()) - ->method('getUser') - ->willReturn($user) - ; - } - if (false !== $credentials) { - $token->expects($this->once()) - ->method('getCredentials') - ->willReturn($credentials) - ; - } - - $token - ->expects($this->any()) - ->method('getFirewallName') - ->willReturn('key') - ; - - $token->setAttributes(['foo' => 'bar']); - - return $token; - } - - protected function getProvider($user = null, $userChecker = null) - { - $userProvider = $this->createMock(InMemoryUserProvider::class); - if (null !== $user) { - $userProvider->expects($this->once()) - ->method('loadUserByIdentifier') - ->willReturn($user) - ; - } - - if (null === $userChecker) { - $userChecker = $this->createMock(UserCheckerInterface::class); - } - - return new PreAuthenticatedAuthenticationProvider($userProvider, $userChecker, 'key'); - } -} diff --git a/Tests/Authentication/Provider/RememberMeAuthenticationProviderTest.php b/Tests/Authentication/Provider/RememberMeAuthenticationProviderTest.php deleted file mode 100644 index d8e5331d..00000000 --- a/Tests/Authentication/Provider/RememberMeAuthenticationProviderTest.php +++ /dev/null @@ -1,128 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Tests\Authentication\Provider; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Security\Core\Authentication\Provider\RememberMeAuthenticationProvider; -use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; -use Symfony\Component\Security\Core\Exception\DisabledException; -use Symfony\Component\Security\Core\Exception\LogicException; -use Symfony\Component\Security\Core\User\InMemoryUser; -use Symfony\Component\Security\Core\User\UserCheckerInterface; -use Symfony\Component\Security\Core\User\UserInterface; - -/** - * @group legacy - */ -class RememberMeAuthenticationProviderTest extends TestCase -{ - public function testSupports() - { - $provider = $this->getProvider(); - - $this->assertTrue($provider->supports($this->getSupportedToken())); - $this->assertFalse($provider->supports($this->createMock(TokenInterface::class))); - $this->assertFalse($provider->supports($this->createMock(RememberMeToken::class))); - } - - public function testAuthenticateWhenTokenIsNotSupported() - { - $this->expectException(AuthenticationException::class); - $this->expectExceptionMessage('The token is not supported by this authentication provider.'); - $provider = $this->getProvider(); - - $token = $this->createMock(TokenInterface::class); - $provider->authenticate($token); - } - - public function testAuthenticateWhenSecretsDoNotMatch() - { - $this->expectException(BadCredentialsException::class); - $provider = $this->getProvider(null, 'secret1'); - $token = $this->getSupportedToken(null, 'secret2'); - - $provider->authenticate($token); - } - - public function testAuthenticateThrowsOnNonUserInterfaceInstance() - { - $this->expectException(LogicException::class); - $this->expectExceptionMessage('Method "Symfony\Component\Security\Core\Authentication\Token\RememberMeToken::getUser()" must return a "Symfony\Component\Security\Core\User\UserInterface" instance, "string" returned.'); - - $provider = $this->getProvider(); - $token = new RememberMeToken(new InMemoryUser('dummyuser', null), 'foo', 'test'); - $token->setUser('stringish-user'); - $provider->authenticate($token); - } - - public function testAuthenticateWhenPreChecksFails() - { - $this->expectException(DisabledException::class); - $userChecker = $this->createMock(UserCheckerInterface::class); - $userChecker->expects($this->once()) - ->method('checkPreAuth') - ->willThrowException(new DisabledException()); - - $provider = $this->getProvider($userChecker); - - $provider->authenticate($this->getSupportedToken()); - } - - public function testAuthenticate() - { - $user = $this->createMock(UserInterface::class); - $user->expects($this->exactly(2)) - ->method('getRoles') - ->willReturn(['ROLE_FOO']); - - $provider = $this->getProvider(); - - $token = $this->getSupportedToken($user); - $authToken = $provider->authenticate($token); - - $this->assertInstanceOf(RememberMeToken::class, $authToken); - $this->assertSame($user, $authToken->getUser()); - $this->assertEquals(['ROLE_FOO'], $authToken->getRoleNames()); - $this->assertEquals('', $authToken->getCredentials()); - } - - protected function getSupportedToken($user = null, $secret = 'test') - { - if (null === $user) { - $user = $this->createMock(UserInterface::class); - $user - ->expects($this->any()) - ->method('getRoles') - ->willReturn([]); - } - - $token = $this->getMockBuilder(RememberMeToken::class)->onlyMethods(['getFirewallName'])->setConstructorArgs([$user, 'foo', $secret])->getMock(); - $token - ->expects($this->once()) - ->method('getFirewallName') - ->willReturn('foo'); - - return $token; - } - - protected function getProvider($userChecker = null, $key = 'test') - { - if (null === $userChecker) { - $userChecker = $this->createMock(UserCheckerInterface::class); - } - - return new RememberMeAuthenticationProvider($userChecker, $key, 'foo'); - } -} diff --git a/Tests/Authentication/Provider/UserAuthenticationProviderTest.php b/Tests/Authentication/Provider/UserAuthenticationProviderTest.php deleted file mode 100644 index 0527069e..00000000 --- a/Tests/Authentication/Provider/UserAuthenticationProviderTest.php +++ /dev/null @@ -1,261 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Tests\Authentication\Provider; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Security\Core\Authentication\Provider\UserAuthenticationProvider; -use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; -use Symfony\Component\Security\Core\Exception\AccountExpiredException; -use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Component\Security\Core\Exception\AuthenticationServiceException; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; -use Symfony\Component\Security\Core\Exception\CredentialsExpiredException; -use Symfony\Component\Security\Core\Exception\UserNotFoundException; -use Symfony\Component\Security\Core\Tests\Fixtures\MockableUsernamePasswordTokenWithRoles; -use Symfony\Component\Security\Core\User\InMemoryUser; -use Symfony\Component\Security\Core\User\UserCheckerInterface; -use Symfony\Component\Security\Core\User\UserInterface; - -/** - * @group legacy - */ -class UserAuthenticationProviderTest extends TestCase -{ - public function testSupports() - { - $provider = $this->getProvider(); - - $this->assertTrue($provider->supports($this->getSupportedToken())); - $this->assertFalse($provider->supports($this->createMock(TokenInterface::class))); - } - - public function testAuthenticateWhenTokenIsNotSupported() - { - $this->expectException(AuthenticationException::class); - $this->expectExceptionMessage('The token is not supported by this authentication provider.'); - $provider = $this->getProvider(); - - $provider->authenticate($this->createMock(TokenInterface::class)); - } - - public function testAuthenticateWhenUsernameIsNotFound() - { - $this->expectException(UserNotFoundException::class); - $provider = $this->getProvider(false, false); - $provider->expects($this->once()) - ->method('retrieveUser') - ->willThrowException(new UserNotFoundException()) - ; - - $provider->authenticate($this->getSupportedToken()); - } - - public function testAuthenticateWhenUsernameIsNotFoundAndHideIsTrue() - { - $this->expectException(BadCredentialsException::class); - $provider = $this->getProvider(false, true); - $provider->expects($this->once()) - ->method('retrieveUser') - ->willThrowException(new UserNotFoundException()) - ; - - $provider->authenticate($this->getSupportedToken()); - } - - public function testAuthenticateWhenCredentialsAreInvalidAndHideIsTrue() - { - $provider = $this->getProvider(); - $provider->expects($this->once()) - ->method('retrieveUser') - ->willReturn($this->createMock(UserInterface::class)) - ; - $provider->expects($this->once()) - ->method('checkAuthentication') - ->willThrowException(new BadCredentialsException()) - ; - - $this->expectException(BadCredentialsException::class); - $this->expectExceptionMessage('Bad credentials.'); - - $provider->authenticate($this->getSupportedToken()); - } - - public function testAuthenticateWhenProviderDoesNotReturnAnUserInterface() - { - $this->expectException(AuthenticationServiceException::class); - $provider = $this->getProvider(false, true); - $provider->expects($this->once()) - ->method('retrieveUser') - ->willReturn(null) - ; - - $provider->authenticate($this->getSupportedToken()); - } - - public function testAuthenticateWhenPreChecksFails() - { - $this->expectException(BadCredentialsException::class); - $userChecker = $this->createMock(UserCheckerInterface::class); - $userChecker->expects($this->once()) - ->method('checkPreAuth') - ->willThrowException(new CredentialsExpiredException()) - ; - - $provider = $this->getProvider($userChecker); - $provider->expects($this->once()) - ->method('retrieveUser') - ->willReturn($this->createMock(UserInterface::class)) - ; - - $provider->authenticate($this->getSupportedToken()); - } - - public function testAuthenticateWhenPostChecksFails() - { - $this->expectException(BadCredentialsException::class); - $userChecker = $this->createMock(UserCheckerInterface::class); - $userChecker->expects($this->once()) - ->method('checkPostAuth') - ->willThrowException(new AccountExpiredException()) - ; - - $provider = $this->getProvider($userChecker); - $provider->expects($this->once()) - ->method('retrieveUser') - ->willReturn($this->createMock(UserInterface::class)) - ; - - $provider->authenticate($this->getSupportedToken()); - } - - public function testAuthenticateWhenPostCheckAuthenticationFails() - { - $this->expectException(BadCredentialsException::class); - $this->expectExceptionMessage('Bad credentials'); - $provider = $this->getProvider(); - $provider->expects($this->once()) - ->method('retrieveUser') - ->willReturn($this->createMock(UserInterface::class)) - ; - $provider->expects($this->once()) - ->method('checkAuthentication') - ->willThrowException(new CredentialsExpiredException()) - ; - - $provider->authenticate($this->getSupportedToken()); - } - - public function testAuthenticateWhenPostCheckAuthenticationFailsWithHideFalse() - { - $this->expectException(BadCredentialsException::class); - $this->expectExceptionMessage('Foo'); - $provider = $this->getProvider(false, false); - $provider->expects($this->once()) - ->method('retrieveUser') - ->willReturn($this->createMock(UserInterface::class)) - ; - $provider->expects($this->once()) - ->method('checkAuthentication') - ->willThrowException(new BadCredentialsException('Foo')) - ; - - $provider->authenticate($this->getSupportedToken()); - } - - public function testAuthenticate() - { - $user = $this->createMock(UserInterface::class); - $user->expects($this->once()) - ->method('getRoles') - ->willReturn(['ROLE_FOO']) - ; - - $provider = $this->getProvider(); - $provider->expects($this->once()) - ->method('retrieveUser') - ->willReturn($user) - ; - - $token = $this->getSupportedToken(); - $token->expects($this->once()) - ->method('getCredentials') - ->willReturn('foo') - ; - - $authToken = $provider->authenticate($token); - - $this->assertInstanceOf(UsernamePasswordToken::class, $authToken); - $this->assertSame($user, $authToken->getUser()); - $this->assertEquals(['ROLE_FOO'], $authToken->getRoleNames()); - $this->assertEquals('foo', $authToken->getCredentials()); - $this->assertEquals(['foo' => 'bar'], $authToken->getAttributes(), '->authenticate() copies token attributes'); - } - - public function testAuthenticatePreservesOriginalToken() - { - $user = $this->createMock(UserInterface::class); - $user->expects($this->once()) - ->method('getRoles') - ->willReturn(['ROLE_FOO']) - ; - - $provider = $this->getProvider(); - $provider->expects($this->once()) - ->method('retrieveUser') - ->willReturn($user) - ; - - $originalToken = $this->createMock(TokenInterface::class); - $token = new SwitchUserToken(new InMemoryUser('wouter', null), 'foo', 'key', [], $originalToken); - $token->setAttributes(['foo' => 'bar']); - - $authToken = $provider->authenticate($token); - - $this->assertInstanceOf(SwitchUserToken::class, $authToken); - $this->assertSame($originalToken, $authToken->getOriginalToken()); - $this->assertSame($user, $authToken->getUser()); - $this->assertContains('ROLE_FOO', $authToken->getRoleNames()); - $this->assertContains('ROLE_PREVIOUS_ADMIN', $authToken->getRoleNames()); - $this->assertEquals('foo', $authToken->getCredentials()); - $this->assertEquals(['foo' => 'bar'], $authToken->getAttributes(), '->authenticate() copies token attributes'); - } - - protected function getSupportedToken() - { - $mock = $this->getMockBuilder(MockableUsernamePasswordTokenWithRoles::class) - ->onlyMethods(['getCredentials', 'getFirewallName', 'getRoles']) - ->disableOriginalConstructor()->getMock(); - $mock - ->expects($this->any()) - ->method('getFirewallName') - ->willReturn('key') - ; - - $mock->setAttributes(['foo' => 'bar']); - - return $mock; - } - - protected function getProvider($userChecker = false, $hide = true) - { - if (false === $userChecker) { - $userChecker = $this->createMock(UserCheckerInterface::class); - } - - return $this->getMockBuilder(UserAuthenticationProvider::class) - ->setConstructorArgs([$userChecker, 'key', $hide]) - ->onlyMethods(['retrieveUser', 'checkAuthentication']) - ->getMock(); - } -} diff --git a/Tests/Authentication/RememberMe/CacheTokenVerifierTest.php b/Tests/Authentication/RememberMe/CacheTokenVerifierTest.php index 996a42e4..4ac0588b 100644 --- a/Tests/Authentication/RememberMe/CacheTokenVerifierTest.php +++ b/Tests/Authentication/RememberMe/CacheTokenVerifierTest.php @@ -21,23 +21,23 @@ class CacheTokenVerifierTest extends TestCase public function testVerifyCurrentToken() { $verifier = new CacheTokenVerifier(new ArrayAdapter()); - $token = new PersistentToken('class', 'user', 'series1@special:chars=/', 'value', new \DateTime()); + $token = new PersistentToken('class', 'user', 'series1@special:chars=/', 'value', new \DateTimeImmutable()); $this->assertTrue($verifier->verifyToken($token, 'value')); } public function testVerifyFailsInvalidToken() { $verifier = new CacheTokenVerifier(new ArrayAdapter()); - $token = new PersistentToken('class', 'user', 'series1@special:chars=/', 'value', new \DateTime()); + $token = new PersistentToken('class', 'user', 'series1@special:chars=/', 'value', new \DateTimeImmutable()); $this->assertFalse($verifier->verifyToken($token, 'wrong-value')); } public function testVerifyOutdatedToken() { $verifier = new CacheTokenVerifier(new ArrayAdapter()); - $outdatedToken = new PersistentToken('class', 'user', 'series1@special:chars=/', 'value', new \DateTime()); - $newToken = new PersistentToken('class', 'user', 'series1@special:chars=/', 'newvalue', new \DateTime()); - $verifier->updateExistingToken($outdatedToken, 'newvalue', new \DateTime()); + $outdatedToken = new PersistentToken('class', 'user', 'series1@special:chars=/', 'value', new \DateTimeImmutable()); + $newToken = new PersistentToken('class', 'user', 'series1@special:chars=/', 'newvalue', new \DateTimeImmutable()); + $verifier->updateExistingToken($outdatedToken, 'newvalue', new \DateTimeImmutable()); $this->assertTrue($verifier->verifyToken($newToken, 'value')); } } diff --git a/Tests/Authentication/RememberMe/InMemoryTokenProviderTest.php b/Tests/Authentication/RememberMe/InMemoryTokenProviderTest.php index bbcbeb41..6fc2ab15 100644 --- a/Tests/Authentication/RememberMe/InMemoryTokenProviderTest.php +++ b/Tests/Authentication/RememberMe/InMemoryTokenProviderTest.php @@ -22,7 +22,7 @@ public function testCreateNewToken() { $provider = new InMemoryTokenProvider(); - $token = new PersistentToken('foo', 'foo', 'foo', 'foo', new \DateTime()); + $token = new PersistentToken('foo', 'foo', 'foo', 'foo', new \DateTimeImmutable()); $provider->createNewToken($token); $this->assertSame($provider->loadTokenBySeries('foo'), $token); @@ -31,31 +31,32 @@ public function testCreateNewToken() public function testLoadTokenBySeriesThrowsNotFoundException() { $this->expectException(TokenNotFoundException::class); - $provider = new InMemoryTokenProvider(); - $provider->loadTokenBySeries('foo'); + (new InMemoryTokenProvider())->loadTokenBySeries('foo'); } public function testUpdateToken() { $provider = new InMemoryTokenProvider(); - $token = new PersistentToken('foo', 'foo', 'foo', 'foo', new \DateTime()); + $token = new PersistentToken('foo', 'foo', 'foo', 'foo', new \DateTimeImmutable()); $provider->createNewToken($token); $provider->updateToken('foo', 'newFoo', $lastUsed = new \DateTime()); $token = $provider->loadTokenBySeries('foo'); $this->assertEquals('newFoo', $token->getTokenValue()); - $this->assertSame($token->getLastUsed(), $lastUsed); + $this->assertEquals($token->getLastUsed(), $lastUsed); } public function testDeleteToken() { - $this->expectException(TokenNotFoundException::class); $provider = new InMemoryTokenProvider(); - $token = new PersistentToken('foo', 'foo', 'foo', 'foo', new \DateTime()); + $token = new PersistentToken('foo', 'foo', 'foo', 'foo', new \DateTimeImmutable()); $provider->createNewToken($token); $provider->deleteTokenBySeries('foo'); + + $this->expectException(TokenNotFoundException::class); + $provider->loadTokenBySeries('foo'); } } diff --git a/Tests/Authentication/RememberMe/PersistentTokenTest.php b/Tests/Authentication/RememberMe/PersistentTokenTest.php index 9df545a4..b9701879 100644 --- a/Tests/Authentication/RememberMe/PersistentTokenTest.php +++ b/Tests/Authentication/RememberMe/PersistentTokenTest.php @@ -12,33 +12,27 @@ namespace Symfony\Component\Security\Core\Tests\Authentication\RememberMe; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken; class PersistentTokenTest extends TestCase { - use ExpectDeprecationTrait; - public function testConstructor() { - $lastUsed = new \DateTime(); + $lastUsed = new \DateTimeImmutable(); $token = new PersistentToken('fooclass', 'fooname', 'fooseries', 'footokenvalue', $lastUsed); $this->assertEquals('fooclass', $token->getClass()); $this->assertEquals('fooname', $token->getUserIdentifier()); $this->assertEquals('fooseries', $token->getSeries()); $this->assertEquals('footokenvalue', $token->getTokenValue()); - $this->assertSame($lastUsed, $token->getLastUsed()); + $this->assertEquals($lastUsed, $token->getLastUsed()); } - /** - * @group legacy - */ - public function testLegacyGetUsername() + public function testDateTime() { - $token = new PersistentToken('fooclass', 'fooname', 'fooseries', 'footokenvalue', new \DateTime()); + $lastUsed = new \DateTime(); + $token = new PersistentToken('fooclass', 'fooname', 'fooseries', 'footokenvalue', $lastUsed); - $this->expectDeprecation('Since symfony/security-core 5.3: Method "Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken::getUsername()" is deprecated, use getUserIdentifier() instead.'); - $this->assertEquals('fooname', $token->getUsername()); + $this->assertEquals($lastUsed, $token->getLastUsed()); } } diff --git a/Tests/Authentication/Token/AbstractTokenTest.php b/Tests/Authentication/Token/AbstractTokenTest.php index 88eb4d10..cc1357a1 100644 --- a/Tests/Authentication/Token/AbstractTokenTest.php +++ b/Tests/Authentication/Token/AbstractTokenTest.php @@ -12,57 +12,12 @@ namespace Symfony\Component\Security\Core\Tests\Authentication\Token; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; use Symfony\Component\Security\Core\User\InMemoryUser; use Symfony\Component\Security\Core\User\UserInterface; class AbstractTokenTest extends TestCase { - use ExpectDeprecationTrait; - - /** - * @group legacy - */ - public function testLegacyGetUsername() - { - $token = new ConcreteToken(['ROLE_FOO']); - $token->setUser('fabien'); - $this->assertEquals('fabien', $token->getUsername()); - - $token->setUser(new TestUser('fabien')); - $this->assertEquals('fabien', $token->getUsername()); - - $legacyUser = new class() implements UserInterface { - public function getUsername() - { - return 'fabien'; - } - - public function getRoles() - { - return []; - } - - public function getPassword() - { - } - - public function getSalt() - { - } - - public function eraseCredentials() - { - } - }; - $token->setUser($legacyUser); - $this->assertEquals('fabien', $token->getUsername()); - - $token->setUser($legacyUser); - $this->assertEquals('fabien', $token->getUserIdentifier()); - } - /** * @dataProvider provideUsers */ @@ -78,26 +33,6 @@ public static function provideUsers() yield [new InMemoryUser('fabien', null), 'fabien']; } - /** - * @dataProvider provideLegacyUsers - * - * @group legacy - */ - public function testLegacyGetUserIdentifier($user, string $username) - { - $token = new ConcreteToken(['ROLE_FOO']); - $token->setUser($user); - $this->assertEquals($username, $token->getUserIdentifier()); - } - - public static function provideLegacyUsers() - { - return [ - [new TestUser('fabien'), 'fabien'], - ['fabien', 'fabien'], - ]; - } - public function testEraseCredentials() { $token = new ConcreteToken(['ROLE_FOO']); @@ -126,21 +61,6 @@ public function testConstructor() $this->assertEquals(['ROLE_FOO'], $token->getRoleNames()); } - /** - * @group legacy - */ - public function testAuthenticatedFlag() - { - $token = new ConcreteToken(); - $this->assertFalse($token->isAuthenticated()); - - $token->setAuthenticated(true); - $this->assertTrue($token->isAuthenticated()); - - $token->setAuthenticated(false); - $this->assertFalse($token->isAuthenticated()); - } - public function testAttributes() { $attributes = ['foo' => 'bar']; @@ -172,192 +92,11 @@ public function testSetUser($user) $token->setUser($user); $this->assertSame($user, $token->getUser()); } - - /** - * @group legacy - * - * @dataProvider getUserChanges - */ - public function testSetUserSetsAuthenticatedToFalseWhenUserChanges($firstUser, $secondUser) - { - $token = new ConcreteToken(); - $token->setAuthenticated(true); - $this->assertTrue($token->isAuthenticated()); - - $token->setUser($firstUser); - $this->assertTrue($token->isAuthenticated()); - - $token->setUser($secondUser); - $this->assertFalse($token->isAuthenticated()); - } - - public static function getUserChanges() - { - $user = new DummyUser(); - - return [ - ['foo', 'bar'], - ['foo', new TestUser('bar')], - ['foo', $user], - [$user, 'foo'], - [$user, new TestUser('foo')], - [new TestUser('foo'), new TestUser('bar')], - [new TestUser('foo'), 'bar'], - [new TestUser('foo'), $user], - ]; - } - - /** - * @group legacy - * - * @dataProvider provideUsers - * @dataProvider provideLegacyUsers - */ - public function testSetUserDoesNotSetAuthenticatedToFalseWhenUserDoesNotChange($user) - { - $token = new ConcreteToken(); - $token->setAuthenticated(true); - $this->assertTrue($token->isAuthenticated()); - - $token->setUser($user); - $this->assertTrue($token->isAuthenticated()); - - $token->setUser($user); - $this->assertTrue($token->isAuthenticated()); - } - - /** - * @group legacy - */ - public function testIsUserChangedWhenSerializing() - { - $token = new ConcreteToken(['ROLE_ADMIN']); - $token->setAuthenticated(true); - $this->assertTrue($token->isAuthenticated()); - - $user = new SerializableUser('wouter', ['ROLE_ADMIN']); - $token->setUser($user); - $this->assertTrue($token->isAuthenticated()); - - $token = unserialize(serialize($token)); - $token->setUser($user); - $this->assertTrue($token->isAuthenticated()); - } -} - -class TestUser -{ - protected $name; - - public function __construct($name) - { - $this->name = $name; - } - - public function __toString(): string - { - return $this->name; - } -} - -class SerializableUser implements UserInterface, \Serializable -{ - private $roles; - private $name; - - public function __construct($name, array $roles = []) - { - $this->name = $name; - $this->roles = $roles; - } - - public function getUsername(): string - { - return $this->name; - } - - public function getUserIdentifier(): string - { - return $this->name; - } - - public function getPassword(): ?string - { - return '***'; - } - - public function getRoles(): array - { - if (empty($this->roles)) { - return ['ROLE_USER']; - } - - return $this->roles; - } - - public function eraseCredentials() - { - } - - public function getSalt(): ?string - { - return null; - } - - public function serialize(): string - { - return serialize($this->__serialize()); - } - - public function unserialize($serialized): void - { - $this->__unserialize(unserialize($serialized)); - } - - public function __serialize(): array - { - return ['name' => $this->name]; - } - - public function __unserialize(array $data): void - { - ['name' => $this->name] = $data; - } -} - -class DummyUser implements UserInterface -{ - public function getRoles(): array - { - return []; - } - - public function getPassword(): ?string - { - return null; - } - - public function getSalt(): ?string - { - return null; - } - - public function eraseCredentials(): void - { - } - - public function getUsername(): string - { - } - - public function getUserIdentifier(): string - { - } } class ConcreteToken extends AbstractToken { - private $credentials = 'credentials_value'; + private string $credentials = 'credentials_value'; public function __construct(array $roles = [], ?UserInterface $user = null) { @@ -379,7 +118,7 @@ public function __unserialize(array $data): void parent::__unserialize($parentState); } - public function getCredentials() + public function getCredentials(): mixed { } } diff --git a/Tests/Authentication/Token/AnonymousTokenTest.php b/Tests/Authentication/Token/AnonymousTokenTest.php deleted file mode 100644 index 678b2d76..00000000 --- a/Tests/Authentication/Token/AnonymousTokenTest.php +++ /dev/null @@ -1,51 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Tests\Authentication\Token; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; - -/** - * @group legacy - */ -class AnonymousTokenTest extends TestCase -{ - public function testConstructor() - { - $token = new AnonymousToken('foo', 'bar', ['ROLE_FOO']); - $this->assertEquals(['ROLE_FOO'], $token->getRoleNames()); - } - - public function testIsAuthenticated() - { - $token = new AnonymousToken('foo', 'bar'); - $this->assertTrue($token->isAuthenticated()); - } - - public function testGetKey() - { - $token = new AnonymousToken('foo', 'bar'); - $this->assertEquals('foo', $token->getSecret()); - } - - public function testGetCredentials() - { - $token = new AnonymousToken('foo', 'bar'); - $this->assertEquals('', $token->getCredentials()); - } - - public function testGetUser() - { - $token = new AnonymousToken('foo', 'bar'); - $this->assertEquals('bar', $token->getUser()); - } -} diff --git a/Tests/Authentication/Token/Fixtures/CustomUser.php b/Tests/Authentication/Token/Fixtures/CustomUser.php index 52fea7a3..99302032 100644 --- a/Tests/Authentication/Token/Fixtures/CustomUser.php +++ b/Tests/Authentication/Token/Fixtures/CustomUser.php @@ -17,11 +17,6 @@ public function __construct(string $username, array $roles) $this->roles = $roles; } - public function getUsername(): string - { - return $this->username; - } - public function getUserIdentifier(): string { return $this->username; diff --git a/Tests/Authentication/Token/Fixtures/switch-user-token-4.4.txt b/Tests/Authentication/Token/Fixtures/switch-user-token-4.4.txt index fc8af143..5103e3db 100644 Binary files a/Tests/Authentication/Token/Fixtures/switch-user-token-4.4.txt and b/Tests/Authentication/Token/Fixtures/switch-user-token-4.4.txt differ diff --git a/Tests/Authentication/Token/PreAuthenticatedTokenTest.php b/Tests/Authentication/Token/PreAuthenticatedTokenTest.php index e6da2964..dbbb5f70 100644 --- a/Tests/Authentication/Token/PreAuthenticatedTokenTest.php +++ b/Tests/Authentication/Token/PreAuthenticatedTokenTest.php @@ -24,47 +24,9 @@ public function testConstructor() $this->assertEquals('key', $token->getFirewallName()); } - /** - * @group legacy - */ - public function testLegacyConstructor() - { - $token = new PreAuthenticatedToken('foo', 'bar', 'key', ['ROLE_FOO']); - $this->assertEquals(['ROLE_FOO'], $token->getRoleNames()); - $this->assertEquals('key', $token->getFirewallName()); - } - - /** - * @group legacy - */ - public function testGetCredentials() - { - $token = new PreAuthenticatedToken('foo', 'bar', 'key'); - $this->assertEquals('bar', $token->getCredentials()); - } - public function testGetUser() { $token = new PreAuthenticatedToken($user = new InMemoryUser('foo', 'bar'), 'key'); $this->assertEquals($user, $token->getUser()); } - - /** - * @group legacy - */ - public function testEraseCredentials() - { - $token = new PreAuthenticatedToken('foo', 'bar', 'key'); - $token->eraseCredentials(); - $this->assertNull($token->getCredentials()); - } - - /** - * @group legacy - */ - public function testIsAuthenticated() - { - $token = new PreAuthenticatedToken('foo', 'bar', 'key'); - $this->assertFalse($token->isAuthenticated()); - } } diff --git a/Tests/Authentication/Token/RememberMeTokenTest.php b/Tests/Authentication/Token/RememberMeTokenTest.php index 42df2337..b0cdbaf1 100644 --- a/Tests/Authentication/Token/RememberMeTokenTest.php +++ b/Tests/Authentication/Token/RememberMeTokenTest.php @@ -20,10 +20,9 @@ class RememberMeTokenTest extends TestCase public function testConstructor() { $user = $this->getUser(); - $token = new RememberMeToken($user, 'fookey', 'foo'); + $token = new RememberMeToken($user, 'fookey'); $this->assertEquals('fookey', $token->getFirewallName()); - $this->assertEquals('foo', $token->getSecret()); $this->assertEquals(['ROLE_FOO'], $token->getRoleNames()); $this->assertSame($user, $token->getUser()); } @@ -31,21 +30,12 @@ public function testConstructor() /** * @group legacy */ - public function testIsAuthenticated() + public function testSecret() { $user = $this->getUser(); $token = new RememberMeToken($user, 'fookey', 'foo'); - $this->assertTrue($token->isAuthenticated()); - } - public function testConstructorSecretCannotBeEmptyString() - { - $this->expectException(\InvalidArgumentException::class); - new RememberMeToken( - $this->getUser(), - '', - '' - ); + $this->assertEquals('foo', $token->getSecret()); } protected function getUser($roles = ['ROLE_FOO']) diff --git a/Tests/Authentication/Token/Storage/TokenStorageTest.php b/Tests/Authentication/Token/Storage/TokenStorageTest.php index a08f90cd..26d20ae4 100644 --- a/Tests/Authentication/Token/Storage/TokenStorageTest.php +++ b/Tests/Authentication/Token/Storage/TokenStorageTest.php @@ -25,5 +25,7 @@ public function testGetSetToken() $token = new UsernamePasswordToken(new InMemoryUser('username', 'password'), 'provider'); $tokenStorage->setToken($token); $this->assertSame($token, $tokenStorage->getToken()); + $tokenStorage->setToken(null); + $this->assertNull($tokenStorage->getToken()); } } diff --git a/Tests/Authentication/Token/Storage/UsageTrackingTokenStorageTest.php b/Tests/Authentication/Token/Storage/UsageTrackingTokenStorageTest.php index 1009dab6..72e1665a 100644 --- a/Tests/Authentication/Token/Storage/UsageTrackingTokenStorageTest.php +++ b/Tests/Authentication/Token/Storage/UsageTrackingTokenStorageTest.php @@ -13,9 +13,10 @@ use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\Security\Core\Authentication\Token\NullToken; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorage; @@ -25,28 +26,14 @@ class UsageTrackingTokenStorageTest extends TestCase { public function testGetSetToken() { - $sessionAccess = 0; - $sessionLocator = new class(['request_stack' => function () use (&$sessionAccess) { - $session = $this->createMock(SessionInterface::class); - - $request = new Request(); - $request->setSession($session); - $requestStack = $this->getMockBuilder(RequestStack::class)->onlyMethods(['getSession'])->getMock(); - $requestStack->push($request); - $requestStack->expects($this->any())->method('getSession')->willReturnCallback(function () use ($session, &$sessionAccess) { - ++$sessionAccess; - - $session->expects($this->once()) - ->method('getMetadataBag'); - - return $session; - }); - - return $requestStack; - }]) implements ContainerInterface { - use ServiceLocatorTrait; - }; $tokenStorage = new TokenStorage(); + $session = new Session(); + $request = new Request(); + $request->setSession($session); + $requestStack = new RequestStack(); + $requestStack->push($request); + $sessionLocator = new ContainerBuilder(); + $sessionLocator->set('request_stack', $requestStack); $trackingStorage = new UsageTrackingTokenStorage($tokenStorage, $sessionLocator); $this->assertNull($trackingStorage->getToken()); @@ -55,22 +42,20 @@ public function testGetSetToken() $trackingStorage->setToken($token); $this->assertSame($token, $trackingStorage->getToken()); $this->assertSame($token, $tokenStorage->getToken()); - $this->assertSame(0, $sessionAccess); + $this->assertSame(0, $session->getUsageIndex()); $trackingStorage->enableUsageTracking(); $this->assertSame($token, $trackingStorage->getToken()); - $this->assertSame(1, $sessionAccess); + $this->assertSame(1, $session->getUsageIndex()); $trackingStorage->disableUsageTracking(); $this->assertSame($token, $trackingStorage->getToken()); - $this->assertSame(1, $sessionAccess); + $this->assertSame(1, $session->getUsageIndex()); } public function testWithoutMainRequest() { - $locator = new class(['request_stack' => function () { - return new RequestStack(); - }]) implements ContainerInterface { + $locator = new class(['request_stack' => fn () => new RequestStack()]) implements ContainerInterface { use ServiceLocatorTrait; }; $tokenStorage = new TokenStorage(); diff --git a/Tests/Authentication/Token/SwitchUserTokenTest.php b/Tests/Authentication/Token/SwitchUserTokenTest.php index f5c9f37c..2fc6e321 100644 --- a/Tests/Authentication/Token/SwitchUserTokenTest.php +++ b/Tests/Authentication/Token/SwitchUserTokenTest.php @@ -16,7 +16,6 @@ use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Tests\Authentication\Token\Fixtures\CustomUser; use Symfony\Component\Security\Core\User\InMemoryUser; -use Symfony\Component\Security\Core\User\UserInterface; class SwitchUserTokenTest extends TestCase { @@ -41,74 +40,6 @@ public function testSerialize() $this->assertEquals(['ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH'], $unserializedOriginalToken->getRoleNames()); } - /** - * @group legacy - */ - public function testLegacySerialize() - { - $originalToken = new UsernamePasswordToken('user', 'foo', 'provider-key', ['ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH']); - $token = new SwitchUserToken('admin', 'bar', 'provider-key', ['ROLE_USER'], $originalToken, 'https://symfony.com/blog'); - - $unserializedToken = unserialize(serialize($token)); - - $this->assertInstanceOf(SwitchUserToken::class, $unserializedToken); - $this->assertSame('admin', $unserializedToken->getUserIdentifier()); - $this->assertSame('bar', $unserializedToken->getCredentials()); - $this->assertSame('provider-key', $unserializedToken->getFirewallName()); - $this->assertEquals(['ROLE_USER'], $unserializedToken->getRoleNames()); - $this->assertSame('https://symfony.com/blog', $unserializedToken->getOriginatedFromUri()); - - $unserializedOriginalToken = $unserializedToken->getOriginalToken(); - - $this->assertInstanceOf(UsernamePasswordToken::class, $unserializedOriginalToken); - $this->assertSame('user', $unserializedOriginalToken->getUserIdentifier()); - $this->assertSame('foo', $unserializedOriginalToken->getCredentials()); - $this->assertSame('provider-key', $unserializedOriginalToken->getFirewallName()); - $this->assertEquals(['ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH'], $unserializedOriginalToken->getRoleNames()); - } - - /** - * @group legacy - */ - public function testSetUserDoesNotDeauthenticate() - { - $impersonated = new class() implements UserInterface { - public function getUsername() - { - return 'impersonated'; - } - - public function getUserIdentifier() - { - return 'impersonated'; - } - - public function getPassword() - { - return null; - } - - public function eraseCredentials() - { - } - - public function getRoles() - { - return ['ROLE_USER']; - } - - public function getSalt() - { - return null; - } - }; - - $originalToken = new UsernamePasswordToken(new InMemoryUser('impersonator', '', ['ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH']), 'foo', 'provider-key', ['ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH']); - $token = new SwitchUserToken($impersonated, 'bar', 'provider-key', ['ROLE_USER', 'ROLE_PREVIOUS_ADMIN'], $originalToken); - $token->setUser($impersonated); - $this->assertTrue($token->isAuthenticated()); - } - public function testSerializeNullImpersonateUrl() { $originalToken = new UsernamePasswordToken(new InMemoryUser('user', 'foo', ['ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH']), 'provider-key', ['ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH']); @@ -119,23 +50,10 @@ public function testSerializeNullImpersonateUrl() $this->assertNull($unserializedToken->getOriginatedFromUri()); } - /** - * @group legacy - */ - public function testLegacySerializeNullImpersonateUrl() - { - $originalToken = new UsernamePasswordToken('user', 'foo', 'provider-key', ['ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH']); - $token = new SwitchUserToken('admin', 'bar', 'provider-key', ['ROLE_USER'], $originalToken); - - $unserializedToken = unserialize(serialize($token)); - - $this->assertNull($unserializedToken->getOriginatedFromUri()); - } - /** * Tests if an old version of SwitchUserToken can still be unserialized. * - * The fixture was generated by running the following code with Symfony 4.4 and PHP 7.2. + * The fixture was generated by running the following code with Symfony 4.4 and PHP 8.0. * * serialize( * new SwitchUserToken( @@ -150,8 +68,6 @@ public function testLegacySerializeNullImpersonateUrl() * ) * ) * ) - * - * @group legacy */ public function testUnserializeOldToken() { @@ -162,7 +78,6 @@ public function testUnserializeOldToken() self::assertInstanceOf(UsernamePasswordToken::class, $token->getOriginalToken()); self::assertInstanceOf(CustomUser::class, $token->getUser()); self::assertSame('john', $token->getUserIdentifier()); - self::assertSame(['foo' => 'bar'], $token->getCredentials()); self::assertSame('main', $token->getFirewallName()); self::assertEquals(['ROLE_USER'], $token->getRoleNames()); self::assertNull($token->getOriginatedFromUri()); diff --git a/Tests/Authentication/Token/UsernamePasswordTokenTest.php b/Tests/Authentication/Token/UsernamePasswordTokenTest.php index 28b1a61f..91faed0c 100644 --- a/Tests/Authentication/Token/UsernamePasswordTokenTest.php +++ b/Tests/Authentication/Token/UsernamePasswordTokenTest.php @@ -24,62 +24,9 @@ public function testConstructor() $this->assertEquals('key', $token->getFirewallName()); } - /** - * @group legacy - */ - public function testLegacyConstructor() - { - $token = new UsernamePasswordToken('foo', 'bar', 'key', ['ROLE_FOO']); - $this->assertEquals(['ROLE_FOO'], $token->getRoleNames()); - $this->assertEquals('bar', $token->getCredentials()); - $this->assertEquals('key', $token->getFirewallName()); - } - - /** - * @group legacy - */ - public function testIsAuthenticated() - { - $token = new UsernamePasswordToken('foo', 'bar', 'key'); - $this->assertFalse($token->isAuthenticated()); - - $token = new UsernamePasswordToken('foo', 'bar', 'key', ['ROLE_FOO']); - $this->assertTrue($token->isAuthenticated()); - } - - /** - * @group legacy - */ - public function testSetAuthenticatedToTrue() - { - $this->expectException(\LogicException::class); - $token = new UsernamePasswordToken('foo', 'bar', 'key'); - $token->setAuthenticated(true); - } - - /** - * @group legacy - */ - public function testSetAuthenticatedToFalse() - { - $token = new UsernamePasswordToken('foo', 'bar', 'key'); - $token->setAuthenticated(false); - $this->assertFalse($token->isAuthenticated()); - } - - /** - * @group legacy - */ - public function testEraseCredentials() - { - $token = new UsernamePasswordToken('foo', 'bar', 'key'); - $token->eraseCredentials(); - $this->assertEquals('', $token->getCredentials()); - } - public function testToString() { $token = new UsernamePasswordToken(new InMemoryUser('foo', '', ['A', 'B']), 'foo', ['A', 'B']); - $this->assertEquals('UsernamePasswordToken(user="foo", authenticated=true, roles="A, B")', (string) $token); + $this->assertEquals('UsernamePasswordToken(user="foo", roles="A, B")', (string) $token); } } diff --git a/Tests/Authorization/AccessDecisionManagerTest.php b/Tests/Authorization/AccessDecisionManagerTest.php index 6aa99ef3..f0a49788 100644 --- a/Tests/Authorization/AccessDecisionManagerTest.php +++ b/Tests/Authorization/AccessDecisionManagerTest.php @@ -13,7 +13,6 @@ use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; use Symfony\Component\Security\Core\Authorization\Strategy\AccessDecisionStrategyInterface; @@ -22,57 +21,7 @@ class AccessDecisionManagerTest extends TestCase { - use ExpectDeprecationTrait; - - /** - * @group legacy - */ - public function testSetUnsupportedStrategy() - { - $this->expectException(\InvalidArgumentException::class); - new AccessDecisionManager([$this->getVoter(VoterInterface::ACCESS_GRANTED)], 'fooBar'); - } - - /** - * @group legacy - * - * @dataProvider getStrategyTests - */ - public function testStrategies($strategy, $voters, $allowIfAllAbstainDecisions, $allowIfEqualGrantedDeniedDecisions, $expected) - { - $token = $this->createMock(TokenInterface::class); - - $this->expectDeprecation('Since symfony/security-core 5.4: Passing the access decision strategy as a string is deprecated, pass an instance of "Symfony\Component\Security\Core\Authorization\Strategy\AccessDecisionStrategyInterface" instead.'); - $manager = new AccessDecisionManager($voters, $strategy, $allowIfAllAbstainDecisions, $allowIfEqualGrantedDeniedDecisions); - - $this->assertSame($expected, $manager->decide($token, ['ROLE_FOO'])); - } - - /** - * @dataProvider provideBadVoterResults - * - * @group legacy - */ - public function testDeprecatedVoter() - { - $token = $this->createMock(TokenInterface::class); - $strategy = new class() implements AccessDecisionStrategyInterface { - public function decide(\Traversable $results): bool - { - iterator_to_array($results); - - return true; - } - }; - - $manager = new AccessDecisionManager([$this->getVoter(3)], $strategy); - - $this->expectDeprecation('Since symfony/security-core 5.3: Returning "%s" in "%s::vote()" is deprecated, return one of "Symfony\Component\Security\Core\Authorization\Voter\VoterInterface" constants: "ACCESS_GRANTED", "ACCESS_DENIED" or "ACCESS_ABSTAIN".'); - - $manager->decide($token, ['ROLE_FOO']); - } - - public static function provideBadVoterResults(): array + public function provideBadVoterResults(): array { return [ [3], @@ -90,7 +39,7 @@ public function testVoterCalls() $this->getUnexpectedVoter(), ]; - $strategy = new class() implements AccessDecisionStrategyInterface { + $strategy = new class implements AccessDecisionStrategyInterface { public function decide(\Traversable $results): bool { $i = 0; @@ -116,66 +65,6 @@ public function decide(\Traversable $results): bool $this->assertTrue($manager->decide($token, ['ROLE_FOO'])); } - public static function getStrategyTests(): array - { - return [ - // affirmative - [AccessDecisionManager::STRATEGY_AFFIRMATIVE, self::getVoters(1, 0, 0), false, true, true], - [AccessDecisionManager::STRATEGY_AFFIRMATIVE, self::getVoters(1, 2, 0), false, true, true], - [AccessDecisionManager::STRATEGY_AFFIRMATIVE, self::getVoters(0, 1, 0), false, true, false], - [AccessDecisionManager::STRATEGY_AFFIRMATIVE, self::getVoters(0, 0, 1), false, true, false], - [AccessDecisionManager::STRATEGY_AFFIRMATIVE, self::getVoters(0, 0, 1), true, true, true], - - // consensus - [AccessDecisionManager::STRATEGY_CONSENSUS, self::getVoters(1, 0, 0), false, true, true], - [AccessDecisionManager::STRATEGY_CONSENSUS, self::getVoters(1, 2, 0), false, true, false], - [AccessDecisionManager::STRATEGY_CONSENSUS, self::getVoters(2, 1, 0), false, true, true], - - [AccessDecisionManager::STRATEGY_CONSENSUS, self::getVoters(0, 0, 1), false, true, false], - - [AccessDecisionManager::STRATEGY_CONSENSUS, self::getVoters(0, 0, 1), true, true, true], - - [AccessDecisionManager::STRATEGY_CONSENSUS, self::getVoters(2, 2, 0), false, true, true], - [AccessDecisionManager::STRATEGY_CONSENSUS, self::getVoters(2, 2, 1), false, true, true], - - [AccessDecisionManager::STRATEGY_CONSENSUS, self::getVoters(2, 2, 0), false, false, false], - [AccessDecisionManager::STRATEGY_CONSENSUS, self::getVoters(2, 2, 1), false, false, false], - - // unanimous - [AccessDecisionManager::STRATEGY_UNANIMOUS, self::getVoters(1, 0, 0), false, true, true], - [AccessDecisionManager::STRATEGY_UNANIMOUS, self::getVoters(1, 0, 1), false, true, true], - [AccessDecisionManager::STRATEGY_UNANIMOUS, self::getVoters(1, 1, 0), false, true, false], - - [AccessDecisionManager::STRATEGY_UNANIMOUS, self::getVoters(0, 0, 2), false, true, false], - [AccessDecisionManager::STRATEGY_UNANIMOUS, self::getVoters(0, 0, 2), true, true, true], - - // priority - [AccessDecisionManager::STRATEGY_PRIORITY, [ - self::getVoter(VoterInterface::ACCESS_ABSTAIN), - self::getVoter(VoterInterface::ACCESS_GRANTED), - self::getVoter(VoterInterface::ACCESS_DENIED), - self::getVoter(VoterInterface::ACCESS_DENIED), - ], true, true, true], - - [AccessDecisionManager::STRATEGY_PRIORITY, [ - self::getVoter(VoterInterface::ACCESS_ABSTAIN), - self::getVoter(VoterInterface::ACCESS_DENIED), - self::getVoter(VoterInterface::ACCESS_GRANTED), - self::getVoter(VoterInterface::ACCESS_GRANTED), - ], true, true, false], - - [AccessDecisionManager::STRATEGY_PRIORITY, [ - self::getVoter(VoterInterface::ACCESS_ABSTAIN), - self::getVoter(VoterInterface::ACCESS_ABSTAIN), - ], false, true, false], - - [AccessDecisionManager::STRATEGY_PRIORITY, [ - self::getVoter(VoterInterface::ACCESS_ABSTAIN), - self::getVoter(VoterInterface::ACCESS_ABSTAIN), - ], true, true, true], - ]; - } - public function testCacheableVoters() { $token = $this->createMock(TokenInterface::class); @@ -367,7 +256,7 @@ protected static function getVoters($grants, $denies, $abstains): array protected static function getVoter($vote) { return new class($vote) implements VoterInterface { - private $vote; + private int $vote; public function __construct(int $vote) { diff --git a/Tests/Authorization/AuthorizationCheckerTest.php b/Tests/Authorization/AuthorizationCheckerTest.php index 5ea3e6ee..36b048c8 100644 --- a/Tests/Authorization/AuthorizationCheckerTest.php +++ b/Tests/Authorization/AuthorizationCheckerTest.php @@ -11,97 +11,38 @@ namespace Symfony\Component\Security\Core\Tests\Authorization; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\NullToken; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationChecker; -use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; use Symfony\Component\Security\Core\User\InMemoryUser; class AuthorizationCheckerTest extends TestCase { - private $authenticationManager; - private $accessDecisionManager; - private $authorizationChecker; - private $tokenStorage; + private MockObject&AccessDecisionManagerInterface $accessDecisionManager; + private AuthorizationChecker $authorizationChecker; + private TokenStorage $tokenStorage; protected function setUp(): void { $this->accessDecisionManager = $this->createMock(AccessDecisionManagerInterface::class); $this->tokenStorage = new TokenStorage(); - $this->authorizationChecker = new AuthorizationChecker( - $this->tokenStorage, - $this->accessDecisionManager, - false, - false - ); + $this->authorizationChecker = new AuthorizationChecker($this->tokenStorage, $this->accessDecisionManager); } - /** - * @group legacy - */ - public function testVoteAuthenticatesTokenIfNecessary() - { - $token = new UsernamePasswordToken('username', 'password', 'provider'); - $this->tokenStorage->setToken($token); - - $newToken = new UsernamePasswordToken('username', 'password', 'provider'); - - $authenticationManager = $this->createMock(AuthenticationManagerInterface::class); - $this->authorizationChecker = new AuthorizationChecker($this->tokenStorage, $authenticationManager, $this->accessDecisionManager, false, false); - $authenticationManager - ->expects($this->once()) - ->method('authenticate') - ->with($this->equalTo($token)) - ->willReturn($newToken); - - // default with() isn't a strict check - $tokenComparison = function ($value) use ($newToken) { - // make sure that the new token is used in "decide()" and not the old one - return $value === $newToken; - }; - - $this->accessDecisionManager - ->expects($this->once()) - ->method('decide') - ->with($this->callback($tokenComparison)) - ->willReturn(true); - - // first run the token has not been re-authenticated yet, after isGranted is called, it should be equal - $this->assertNotSame($newToken, $this->tokenStorage->getToken()); - $this->assertTrue($this->authorizationChecker->isGranted('foo')); - $this->assertSame($newToken, $this->tokenStorage->getToken()); - } - - /** - * @group legacy - */ - public function testLegacyVoteWithoutAuthenticationToken() + public function testVoteWithoutAuthenticationToken() { $authorizationChecker = new AuthorizationChecker($this->tokenStorage, $this->accessDecisionManager); - $this->expectException(AuthenticationCredentialsNotFoundException::class); + $this->accessDecisionManager->expects($this->once())->method('decide')->with($this->isInstanceOf(NullToken::class))->willReturn(false); $authorizationChecker->isGranted('ROLE_FOO'); } - public function testVoteWithoutAuthenticationToken() - { - $authorizationChecker = new AuthorizationChecker($this->tokenStorage, $this->accessDecisionManager, false, false); - - $this->accessDecisionManager - ->expects($this->once()) - ->method('decide') - ->with($this->isInstanceOf(NullToken::class)) - ->willReturn(true); - - $this->assertTrue($authorizationChecker->isGranted('ANONYMOUS')); - } - /** * @dataProvider isGrantedProvider */ diff --git a/Tests/Authorization/ExpressionLanguageTest.php b/Tests/Authorization/ExpressionLanguageTest.php index 81c31d6d..1a4db41e 100644 --- a/Tests/Authorization/ExpressionLanguageTest.php +++ b/Tests/Authorization/ExpressionLanguageTest.php @@ -13,7 +13,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; -use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; @@ -36,7 +35,7 @@ public function testIsAuthenticated($token, $expression, $result) $tokenStorage = new TokenStorage(); $tokenStorage->setToken($token); $accessDecisionManager = new AccessDecisionManager([new RoleVoter(), new AuthenticatedVoter($trustResolver)]); - $authChecker = new AuthorizationChecker($tokenStorage, $accessDecisionManager, false, false); + $authChecker = new AuthorizationChecker($tokenStorage, $accessDecisionManager); $context = []; $context['auth_checker'] = $authChecker; @@ -51,7 +50,7 @@ public static function provider() $user = new InMemoryUser('username', 'password', $roles); $noToken = null; - $rememberMeToken = new RememberMeToken($user, 'firewall-name', 'firewall'); + $rememberMeToken = new RememberMeToken($user, 'firewall-name'); $usernamePasswordToken = new UsernamePasswordToken($user, 'firewall-name', $roles); return [ @@ -72,36 +71,4 @@ public static function provider() [$usernamePasswordToken, "is_granted('ROLE_USER')", true], ]; } - - /** - * @dataProvider legacyProvider - * - * @group legacy - */ - public function testLegacyIsAuthenticated($token, $expression, $result) - { - $this->testIsAuthenticated($token, $expression, $result); - } - - /** - * @group legacy - */ - public static function legacyProvider() - { - $roles = ['ROLE_USER', 'ROLE_ADMIN']; - $user = new InMemoryUser('username', 'password', $roles); - $anonymousToken = new AnonymousToken('firewall', 'anon.'); - - return [ - [$anonymousToken, 'is_anonymous()', true], - [$anonymousToken, 'is_authenticated()', false], - [$anonymousToken, 'is_fully_authenticated()', false], - [$anonymousToken, 'is_remember_me()', false], - [$anonymousToken, "is_granted('ROLE_USER')", false], - - [null, 'is_anonymous()', false], - [new RememberMeToken($user, 'firewall-name', 'firewall'), 'is_anonymous()', false], - [new UsernamePasswordToken($user, 'firewall-name', $roles), 'is_anonymous()', false], - ]; - } } diff --git a/Tests/Authorization/TraceableAccessDecisionManagerTest.php b/Tests/Authorization/TraceableAccessDecisionManagerTest.php index ebfda276..8797d74d 100644 --- a/Tests/Authorization/TraceableAccessDecisionManagerTest.php +++ b/Tests/Authorization/TraceableAccessDecisionManagerTest.php @@ -15,7 +15,6 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; -use Symfony\Component\Security\Core\Authorization\DebugAccessDecisionManager; use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; use Symfony\Component\Security\Core\Tests\Fixtures\DummyVoter; @@ -117,7 +116,7 @@ public static function provideObjectsAndLogs(): \Generator 'result' => true, 'voterDetails' => [], ]], - 'attributes' => [12], + [12], 12345, [], true, @@ -172,13 +171,6 @@ public static function provideObjectsAndLogs(): \Generator ]; } - public function testDebugAccessDecisionManagerAliasExistsForBC() - { - $adm = new TraceableAccessDecisionManager(new AccessDecisionManager()); - - $this->assertInstanceOf(DebugAccessDecisionManager::class, $adm, 'For BC, TraceableAccessDecisionManager must be an instance of DebugAccessDecisionManager'); - } - /** * Tests decision log returned when a voter call decide method of AccessDecisionManager. */ @@ -205,7 +197,7 @@ public function testAccessDecisionManagerCalledByVoter() ->expects($this->any()) ->method('vote') ->willReturnCallback(function (TokenInterface $token, $subject, array $attributes) use ($sut, $voter1) { - $vote = \in_array('attr1', $attributes) ? VoterInterface::ACCESS_GRANTED : VoterInterface::ACCESS_ABSTAIN; + $vote = \in_array('attr1', $attributes, true) ? VoterInterface::ACCESS_GRANTED : VoterInterface::ACCESS_ABSTAIN; $sut->addVoterVote($voter1, $attributes, $vote); return $vote; @@ -215,7 +207,7 @@ public function testAccessDecisionManagerCalledByVoter() ->expects($this->any()) ->method('vote') ->willReturnCallback(function (TokenInterface $token, $subject, array $attributes) use ($sut, $voter2) { - if (\in_array('attr2', $attributes)) { + if (\in_array('attr2', $attributes, true)) { $vote = null == $subject ? VoterInterface::ACCESS_GRANTED : VoterInterface::ACCESS_DENIED; } else { $vote = VoterInterface::ACCESS_ABSTAIN; @@ -230,7 +222,7 @@ public function testAccessDecisionManagerCalledByVoter() ->expects($this->any()) ->method('vote') ->willReturnCallback(function (TokenInterface $token, $subject, array $attributes) use ($sut, $voter3) { - if (\in_array('attr2', $attributes) && $subject) { + if (\in_array('attr2', $attributes, true) && $subject) { $vote = $sut->decide($token, $attributes) ? VoterInterface::ACCESS_GRANTED : VoterInterface::ACCESS_DENIED; } else { $vote = VoterInterface::ACCESS_ABSTAIN; @@ -275,4 +267,13 @@ public function testAccessDecisionManagerCalledByVoter() ], ], $sut->getDecisionLog()); } + + public function testCustomAccessDecisionManagerReturnsEmptyStrategy() + { + $admMock = $this->createMock(AccessDecisionManagerInterface::class); + + $adm = new TraceableAccessDecisionManager($admMock); + + $this->assertEquals('-', $adm->getStrategy()); + } } diff --git a/Tests/Authorization/Voter/AuthenticatedVoterTest.php b/Tests/Authorization/Voter/AuthenticatedVoterTest.php index 34255261..ed894b3a 100644 --- a/Tests/Authorization/Voter/AuthenticatedVoterTest.php +++ b/Tests/Authorization/Voter/AuthenticatedVoterTest.php @@ -53,37 +53,6 @@ public static function getVoteTests() ]; } - /** - * @group legacy - * - * @dataProvider getLegacyVoteTests - */ - public function testLegacyVote($authenticated, $attributes, $expected) - { - $this->testVote($authenticated, $attributes, $expected); - } - - public static function getLegacyVoteTests() - { - return [ - ['anonymously', [], VoterInterface::ACCESS_ABSTAIN], - ['anonymously', ['FOO'], VoterInterface::ACCESS_ABSTAIN], - ['anonymously', ['IS_AUTHENTICATED_ANONYMOUSLY'], VoterInterface::ACCESS_GRANTED], - ['anonymously', ['IS_AUTHENTICATED_REMEMBERED'], VoterInterface::ACCESS_DENIED], - ['anonymously', ['IS_AUTHENTICATED_FULLY'], VoterInterface::ACCESS_DENIED], - ['anonymously', ['IS_ANONYMOUS'], VoterInterface::ACCESS_GRANTED], - ['anonymously', ['IS_IMPERSONATOR'], VoterInterface::ACCESS_DENIED], - - ['fully', ['IS_ANONYMOUS'], VoterInterface::ACCESS_DENIED], - ['remembered', ['IS_ANONYMOUS'], VoterInterface::ACCESS_DENIED], - ['anonymously', ['IS_ANONYMOUS'], VoterInterface::ACCESS_GRANTED], - - ['fully', ['IS_AUTHENTICATED_ANONYMOUSLY'], VoterInterface::ACCESS_GRANTED], - ['remembered', ['IS_AUTHENTICATED_ANONYMOUSLY'], VoterInterface::ACCESS_GRANTED], - ['anonymously', ['IS_AUTHENTICATED_ANONYMOUSLY'], VoterInterface::ACCESS_GRANTED], - ]; - } - /** * @dataProvider provideAttributes */ @@ -98,8 +67,6 @@ public static function provideAttributes() { yield [AuthenticatedVoter::IS_AUTHENTICATED_FULLY, true]; yield [AuthenticatedVoter::IS_AUTHENTICATED_REMEMBERED, true]; - yield [AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY, true]; - yield [AuthenticatedVoter::IS_ANONYMOUS, true]; yield [AuthenticatedVoter::IS_AUTHENTICATED, true]; yield [AuthenticatedVoter::IS_IMPERSONATOR, true]; yield [AuthenticatedVoter::IS_REMEMBERED, true]; @@ -123,19 +90,18 @@ protected function getToken($authenticated) $user = new InMemoryUser('wouter', '', ['ROLE_USER']); if ('fully' === $authenticated) { - $token = new class() extends AbstractToken { + $token = new class extends AbstractToken { public function getCredentials() { } }; $token->setUser($user); - $token->setAuthenticated(true, false); return $token; } if ('remembered' === $authenticated) { - return new RememberMeToken($user, 'foo', 'bar'); + return new RememberMeToken($user, 'foo'); } if ('impersonated' === $authenticated) { diff --git a/Tests/Authorization/Voter/RoleVoterTest.php b/Tests/Authorization/Voter/RoleVoterTest.php index 59a7ab77..dfa05556 100644 --- a/Tests/Authorization/Voter/RoleVoterTest.php +++ b/Tests/Authorization/Voter/RoleVoterTest.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Security\Core\Tests\Authorization\Voter; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter; @@ -21,8 +20,6 @@ class RoleVoterTest extends TestCase { - use ExpectDeprecationTrait; - /** * @dataProvider getVoteTests */ @@ -49,17 +46,6 @@ public static function getVoteTests() ]; } - /** - * @group legacy - */ - public function testDeprecatedRolePreviousAdmin() - { - $this->expectDeprecation('Since symfony/security-core 5.1: The ROLE_PREVIOUS_ADMIN role is deprecated and will be removed in version 6.0, use the IS_IMPERSONATOR attribute instead.'); - $voter = new RoleVoter(); - - $voter->vote($this->getTokenWithRoleNames(['ROLE_USER', 'ROLE_PREVIOUS_ADMIN']), null, ['ROLE_PREVIOUS_ADMIN']); - } - /** * @dataProvider provideAttributes */ diff --git a/Tests/Authorization/Voter/VoterTest.php b/Tests/Authorization/Voter/VoterTest.php index 25aff8e1..602c61ab 100644 --- a/Tests/Authorization/Voter/VoterTest.php +++ b/Tests/Authorization/Voter/VoterTest.php @@ -18,7 +18,7 @@ class VoterTest extends TestCase { - protected $token; + protected TokenInterface $token; protected function setUp(): void { @@ -41,7 +41,7 @@ public static function getTests(): array [$voter, ['DELETE'], VoterInterface::ACCESS_ABSTAIN, new \stdClass(), 'ACCESS_ABSTAIN if no attribute is supported'], - [$voter, ['EDIT'], VoterInterface::ACCESS_ABSTAIN, new class() {}, 'ACCESS_ABSTAIN if class is not supported'], + [$voter, ['EDIT'], VoterInterface::ACCESS_ABSTAIN, new class {}, 'ACCESS_ABSTAIN if class is not supported'], [$voter, ['EDIT'], VoterInterface::ACCESS_ABSTAIN, null, 'ACCESS_ABSTAIN if object is null'], @@ -67,8 +67,7 @@ public function testVoteWithTypeError() { $this->expectException(\TypeError::class); $this->expectExceptionMessage('Should error'); - $voter = new TypeErrorVoterTest_Voter(); - $voter->vote($this->token, new \stdClass(), ['EDIT']); + (new TypeErrorVoterTest_Voter())->vote($this->token, new \stdClass(), ['EDIT']); } } diff --git a/Tests/Encoder/EncoderFactoryTest.php b/Tests/Encoder/EncoderFactoryTest.php deleted file mode 100644 index 7b05c9be..00000000 --- a/Tests/Encoder/EncoderFactoryTest.php +++ /dev/null @@ -1,272 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Tests\Encoder; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\PasswordHasher\Hasher\MessageDigestPasswordHasher; -use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; -use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface; -use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory; -use Symfony\Component\PasswordHasher\Hasher\PlaintextPasswordHasher; -use Symfony\Component\Security\Core\Encoder\EncoderAwareInterface; -use Symfony\Component\Security\Core\Encoder\EncoderFactory; -use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder; -use Symfony\Component\Security\Core\Encoder\MigratingPasswordEncoder; -use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder; -use Symfony\Component\Security\Core\Encoder\SelfSaltingEncoderInterface; -use Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder; -use Symfony\Component\Security\Core\User\User; -use Symfony\Component\Security\Core\User\UserInterface; - -/** - * @group legacy - */ -class EncoderFactoryTest extends TestCase -{ - public function testGetEncoderWithMessageDigestEncoder() - { - $factory = new EncoderFactory(['Symfony\Component\Security\Core\User\UserInterface' => [ - 'class' => 'Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder', - 'arguments' => ['sha512', true, 5], - ]]); - - $encoder = $factory->getEncoder($this->createMock(UserInterface::class)); - $expectedEncoder = new MessageDigestPasswordEncoder('sha512', true, 5); - - $this->assertEquals($expectedEncoder->encodePassword('foo', 'moo'), $encoder->encodePassword('foo', 'moo')); - } - - public function testGetEncoderWithService() - { - $factory = new EncoderFactory([ - 'Symfony\Component\Security\Core\User\UserInterface' => new MessageDigestPasswordEncoder('sha1'), - ]); - - $encoder = $factory->getEncoder($this->createMock(UserInterface::class)); - $expectedEncoder = new MessageDigestPasswordEncoder('sha1'); - $this->assertEquals($expectedEncoder->encodePassword('foo', ''), $encoder->encodePassword('foo', '')); - - $encoder = $factory->getEncoder(new User('user', 'pass')); - $expectedEncoder = new MessageDigestPasswordEncoder('sha1'); - $this->assertEquals($expectedEncoder->encodePassword('foo', ''), $encoder->encodePassword('foo', '')); - } - - public function testGetEncoderWithClassName() - { - $factory = new EncoderFactory([ - 'Symfony\Component\Security\Core\User\UserInterface' => new MessageDigestPasswordEncoder('sha1'), - ]); - - $encoder = $factory->getEncoder('Symfony\Component\Security\Core\Tests\Encoder\SomeChildUser'); - $expectedEncoder = new MessageDigestPasswordEncoder('sha1'); - $this->assertEquals($expectedEncoder->encodePassword('foo', ''), $encoder->encodePassword('foo', '')); - } - - public function testGetEncoderConfiguredForConcreteClassWithService() - { - $factory = new EncoderFactory([ - 'Symfony\Component\Security\Core\User\User' => new MessageDigestPasswordEncoder('sha1'), - ]); - - $encoder = $factory->getEncoder(new User('user', 'pass')); - $expectedEncoder = new MessageDigestPasswordEncoder('sha1'); - $this->assertEquals($expectedEncoder->encodePassword('foo', ''), $encoder->encodePassword('foo', '')); - } - - public function testGetEncoderConfiguredForConcreteClassWithClassName() - { - $factory = new EncoderFactory([ - 'Symfony\Component\Security\Core\Tests\Encoder\SomeUser' => new MessageDigestPasswordEncoder('sha1'), - ]); - - $encoder = $factory->getEncoder('Symfony\Component\Security\Core\Tests\Encoder\SomeChildUser'); - $expectedEncoder = new MessageDigestPasswordEncoder('sha1'); - $this->assertEquals($expectedEncoder->encodePassword('foo', ''), $encoder->encodePassword('foo', '')); - } - - public function testGetNamedEncoderForEncoderAware() - { - $factory = new EncoderFactory([ - 'Symfony\Component\Security\Core\Tests\Encoder\EncAwareUser' => new MessageDigestPasswordEncoder('sha256'), - 'encoder_name' => new MessageDigestPasswordEncoder('sha1'), - ]); - - $encoder = $factory->getEncoder(new EncAwareUser('user', 'pass')); - $expectedEncoder = new MessageDigestPasswordEncoder('sha1'); - $this->assertEquals($expectedEncoder->encodePassword('foo', ''), $encoder->encodePassword('foo', '')); - } - - public function testGetNullNamedEncoderForEncoderAware() - { - $factory = new EncoderFactory([ - 'Symfony\Component\Security\Core\Tests\Encoder\EncAwareUser' => new MessageDigestPasswordEncoder('sha1'), - 'encoder_name' => new MessageDigestPasswordEncoder('sha256'), - ]); - - $user = new EncAwareUser('user', 'pass'); - $user->encoderName = null; - $encoder = $factory->getEncoder($user); - $expectedEncoder = new MessageDigestPasswordEncoder('sha1'); - $this->assertEquals($expectedEncoder->encodePassword('foo', ''), $encoder->encodePassword('foo', '')); - } - - public function testGetInvalidNamedEncoderForEncoderAware() - { - $this->expectException(\RuntimeException::class); - $factory = new EncoderFactory([ - 'Symfony\Component\Security\Core\Tests\Encoder\EncAwareUser' => new MessageDigestPasswordEncoder('sha1'), - 'encoder_name' => new MessageDigestPasswordEncoder('sha256'), - ]); - - $user = new EncAwareUser('user', 'pass'); - $user->encoderName = 'invalid_encoder_name'; - $factory->getEncoder($user); - } - - public function testGetEncoderForEncoderAwareWithClassName() - { - $factory = new EncoderFactory([ - 'Symfony\Component\Security\Core\Tests\Encoder\EncAwareUser' => new MessageDigestPasswordEncoder('sha1'), - 'encoder_name' => new MessageDigestPasswordEncoder('sha256'), - ]); - - $encoder = $factory->getEncoder('Symfony\Component\Security\Core\Tests\Encoder\EncAwareUser'); - $expectedEncoder = new MessageDigestPasswordEncoder('sha1'); - $this->assertEquals($expectedEncoder->encodePassword('foo', ''), $encoder->encodePassword('foo', '')); - } - - public function testMigrateFrom() - { - if (!SodiumPasswordEncoder::isSupported()) { - $this->markTestSkipped('Sodium is not available'); - } - - $factory = new EncoderFactory([ - 'digest_encoder' => $digest = new MessageDigestPasswordEncoder('sha256'), - SomeUser::class => ['algorithm' => 'sodium', 'migrate_from' => ['bcrypt', 'digest_encoder']], - ]); - - $encoder = $factory->getEncoder(SomeUser::class); - $this->assertInstanceOf(MigratingPasswordEncoder::class, $encoder); - - $this->assertTrue($encoder->isPasswordValid((new SodiumPasswordEncoder())->encodePassword('foo', null), 'foo', null)); - $this->assertTrue($encoder->isPasswordValid((new NativePasswordEncoder(null, null, null, \PASSWORD_BCRYPT))->encodePassword('foo', null), 'foo', null)); - $this->assertTrue($encoder->isPasswordValid($digest->encodePassword('foo', null), 'foo', null)); - $this->assertStringStartsWith(\SODIUM_CRYPTO_PWHASH_STRPREFIX, $encoder->encodePassword('foo', null)); - } - - public function testDefaultMigratingEncoders() - { - $this->assertInstanceOf( - MigratingPasswordEncoder::class, - (new EncoderFactory([SomeUser::class => ['class' => NativePasswordEncoder::class, 'arguments' => []]]))->getEncoder(SomeUser::class) - ); - - $this->assertInstanceOf( - MigratingPasswordEncoder::class, - (new EncoderFactory([SomeUser::class => ['algorithm' => 'bcrypt', 'cost' => 11]]))->getEncoder(SomeUser::class) - ); - - if (!SodiumPasswordEncoder::isSupported()) { - return; - } - - $this->assertInstanceOf( - MigratingPasswordEncoder::class, - (new EncoderFactory([SomeUser::class => ['class' => SodiumPasswordEncoder::class, 'arguments' => []]]))->getEncoder(SomeUser::class) - ); - } - - public function testHasherAwareCompat() - { - $factory = new PasswordHasherFactory([ - 'encoder_name' => new MessageDigestPasswordHasher('sha1'), - ]); - - $encoder = $factory->getPasswordHasher(new HasherAwareUser('user', 'pass')); - $expectedEncoder = new MessageDigestPasswordHasher('sha1'); - $this->assertEquals($expectedEncoder->hash('foo', ''), $encoder->hash('foo', '')); - } - - public function testLegacyPasswordHasher() - { - $factory = new EncoderFactory([ - SomeUser::class => new PlaintextPasswordHasher(), - ]); - - $encoder = $factory->getEncoder(new SomeUser()); - self::assertNotInstanceOf(SelfSaltingEncoderInterface::class, $encoder); - self::assertSame('foo{bar}', $encoder->encodePassword('foo', 'bar')); - } - - public function testPasswordHasher() - { - $factory = new EncoderFactory([ - SomeUser::class => new NativePasswordHasher(), - ]); - - $encoder = $factory->getEncoder(new SomeUser()); - self::assertInstanceOf(SelfSaltingEncoderInterface::class, $encoder); - self::assertTrue($encoder->isPasswordValid($encoder->encodePassword('foo', null), 'foo', null)); - } -} - -class SomeUser implements UserInterface -{ - public function getRoles(): array - { - } - - public function getPassword(): ?string - { - } - - public function getSalt(): ?string - { - } - - public function getUsername(): string - { - } - - public function getUserIdentifier(): string - { - } - - public function eraseCredentials() - { - } -} - -class SomeChildUser extends SomeUser -{ -} - -class EncAwareUser extends SomeUser implements EncoderAwareInterface -{ - public $encoderName = 'encoder_name'; - - public function getEncoderName(): ?string - { - return $this->encoderName; - } -} - -class HasherAwareUser extends SomeUser implements PasswordHasherAwareInterface -{ - public $hasherName = 'encoder_name'; - - public function getPasswordHasherName(): ?string - { - return $this->hasherName; - } -} diff --git a/Tests/Encoder/Fixtures/MyMessageDigestPasswordEncoder.php b/Tests/Encoder/Fixtures/MyMessageDigestPasswordEncoder.php deleted file mode 100644 index 110541d5..00000000 --- a/Tests/Encoder/Fixtures/MyMessageDigestPasswordEncoder.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Tests\Encoder\Fixtures; - -use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder; - -final class MyMessageDigestPasswordEncoder extends MessageDigestPasswordEncoder -{ - public function __construct() - { - parent::__construct('sha512', true, 1); - } - - protected function mergePasswordAndSalt(string $password, ?string $salt): string - { - return json_encode(['password' => $password, 'salt' => $salt]); - } - - protected function demergePasswordAndSalt(string $mergedPasswordSalt): array - { - ['password' => $password, 'salt' => $salt] = json_decode($mergedPasswordSalt, true); - - return [$password, $salt]; - } -} diff --git a/Tests/Encoder/MessageDigestPasswordEncoderTest.php b/Tests/Encoder/MessageDigestPasswordEncoderTest.php deleted file mode 100644 index a9753957..00000000 --- a/Tests/Encoder/MessageDigestPasswordEncoderTest.php +++ /dev/null @@ -1,73 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Tests\Encoder; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; -use Symfony\Component\Security\Core\Tests\Encoder\Fixtures\MyMessageDigestPasswordEncoder; - -/** - * @group legacy - */ -class MessageDigestPasswordEncoderTest extends TestCase -{ - public function testIsPasswordValid() - { - $encoder = new MessageDigestPasswordEncoder('sha256', false, 1); - - $this->assertTrue($encoder->isPasswordValid(hash('sha256', 'password'), 'password', '')); - } - - public function testEncodePassword() - { - $encoder = new MessageDigestPasswordEncoder('sha256', false, 1); - $this->assertSame(hash('sha256', 'password'), $encoder->encodePassword('password', '')); - - $encoder = new MessageDigestPasswordEncoder('sha256', true, 1); - $this->assertSame(base64_encode(hash('sha256', 'password', true)), $encoder->encodePassword('password', '')); - - $encoder = new MessageDigestPasswordEncoder('sha256', false, 2); - $this->assertSame(hash('sha256', hash('sha256', 'password', true).'password'), $encoder->encodePassword('password', '')); - } - - public function testEncodePasswordAlgorithmDoesNotExist() - { - $this->expectException(\LogicException::class); - $encoder = new MessageDigestPasswordEncoder('foobar'); - $encoder->encodePassword('password', ''); - } - - public function testEncodePasswordLength() - { - $this->expectException(BadCredentialsException::class); - $encoder = new MessageDigestPasswordEncoder(); - - $encoder->encodePassword(str_repeat('a', 5000), 'salt'); - } - - public function testCheckPasswordLength() - { - $encoder = new MessageDigestPasswordEncoder(); - - $this->assertFalse($encoder->isPasswordValid('encoded', str_repeat('a', 5000), 'salt')); - } - - public function testCustomEncoder() - { - $encoder = new MyMessageDigestPasswordEncoder(); - $encodedPassword = $encoder->encodePassword('p4ssw0rd', 's417'); - - $this->assertSame(base64_encode(hash('sha512', '{"password":"p4ssw0rd","salt":"s417"}', true)), $encodedPassword); - $this->assertTrue($encoder->isPasswordValid($encodedPassword, 'p4ssw0rd', 's417')); - } -} diff --git a/Tests/Encoder/MigratingPasswordEncoderTest.php b/Tests/Encoder/MigratingPasswordEncoderTest.php deleted file mode 100644 index fbaf89b0..00000000 --- a/Tests/Encoder/MigratingPasswordEncoderTest.php +++ /dev/null @@ -1,70 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Tests\Encoder; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Security\Core\Encoder\MigratingPasswordEncoder; -use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder; - -/** - * @group legacy - */ -class MigratingPasswordEncoderTest extends TestCase -{ - public function testValidation() - { - $bestEncoder = new NativePasswordEncoder(4, 12000, 4); - - $extraEncoder = $this->createMock(TestPasswordEncoderInterface::class); - $extraEncoder->expects($this->never())->method('encodePassword'); - $extraEncoder->expects($this->never())->method('isPasswordValid'); - $extraEncoder->expects($this->never())->method('needsRehash'); - - $encoder = new MigratingPasswordEncoder($bestEncoder, $extraEncoder); - - $this->assertTrue($encoder->needsRehash('foo')); - - $hash = $encoder->encodePassword('foo', 'salt'); - $this->assertFalse($encoder->needsRehash($hash)); - - $this->assertTrue($encoder->isPasswordValid($hash, 'foo', 'salt')); - $this->assertFalse($encoder->isPasswordValid($hash, 'bar', 'salt')); - } - - public function testFallback() - { - $bestEncoder = new NativePasswordEncoder(4, 12000, 4); - - $extraEncoder1 = $this->createMock(TestPasswordEncoderInterface::class); - $extraEncoder1->expects($this->any()) - ->method('isPasswordValid') - ->with('abc', 'foo', 'salt') - ->willReturn(true); - - $encoder = new MigratingPasswordEncoder($bestEncoder, $extraEncoder1); - - $this->assertTrue($encoder->isPasswordValid('abc', 'foo', 'salt')); - - $extraEncoder2 = $this->createMock(TestPasswordEncoderInterface::class); - $extraEncoder2->expects($this->any()) - ->method('isPasswordValid') - ->willReturn(false); - - $encoder = new MigratingPasswordEncoder($bestEncoder, $extraEncoder2); - - $this->assertFalse($encoder->isPasswordValid('abc', 'foo', 'salt')); - - $encoder = new MigratingPasswordEncoder($bestEncoder, $extraEncoder2, $extraEncoder1); - - $this->assertTrue($encoder->isPasswordValid('abc', 'foo', 'salt')); - } -} diff --git a/Tests/Encoder/NativePasswordEncoderTest.php b/Tests/Encoder/NativePasswordEncoderTest.php deleted file mode 100644 index b50bfc6f..00000000 --- a/Tests/Encoder/NativePasswordEncoderTest.php +++ /dev/null @@ -1,107 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Tests\Encoder; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder; - -/** - * @author Elnur Abdurrakhimov - * - * @group legacy - */ -class NativePasswordEncoderTest extends TestCase -{ - public function testCostBelowRange() - { - $this->expectException(\InvalidArgumentException::class); - new NativePasswordEncoder(null, null, 3); - } - - public function testCostAboveRange() - { - $this->expectException(\InvalidArgumentException::class); - new NativePasswordEncoder(null, null, 32); - } - - /** - * @dataProvider validRangeData - */ - public function testCostInRange($cost) - { - $this->assertInstanceOf(NativePasswordEncoder::class, new NativePasswordEncoder(null, null, $cost)); - } - - public static function validRangeData() - { - $costs = range(4, 31); - array_walk($costs, function (&$cost) { $cost = [$cost]; }); - - return $costs; - } - - public function testValidation() - { - $encoder = new NativePasswordEncoder(); - $result = $encoder->encodePassword('password', null); - $this->assertTrue($encoder->isPasswordValid($result, 'password', null)); - $this->assertFalse($encoder->isPasswordValid($result, 'anotherPassword', null)); - $this->assertFalse($encoder->isPasswordValid($result, '', null)); - } - - public function testNonArgonValidation() - { - $encoder = new NativePasswordEncoder(); - $this->assertTrue($encoder->isPasswordValid('$5$abcdefgh$ZLdkj8mkc2XVSrPVjskDAgZPGjtj1VGVaa1aUkrMTU/', 'password', null)); - $this->assertFalse($encoder->isPasswordValid('$5$abcdefgh$ZLdkj8mkc2XVSrPVjskDAgZPGjtj1VGVaa1aUkrMTU/', 'anotherPassword', null)); - $this->assertTrue($encoder->isPasswordValid('$6$abcdefgh$yVfUwsw5T.JApa8POvClA1pQ5peiq97DUNyXCZN5IrF.BMSkiaLQ5kvpuEm/VQ1Tvh/KV2TcaWh8qinoW5dhA1', 'password', null)); - $this->assertFalse($encoder->isPasswordValid('$6$abcdefgh$yVfUwsw5T.JApa8POvClA1pQ5peiq97DUNyXCZN5IrF.BMSkiaLQ5kvpuEm/VQ1Tvh/KV2TcaWh8qinoW5dhA1', 'anotherPassword', null)); - } - - public function testConfiguredAlgorithm() - { - $encoder = new NativePasswordEncoder(null, null, null, \PASSWORD_BCRYPT); - $result = $encoder->encodePassword('password', null); - $this->assertTrue($encoder->isPasswordValid($result, 'password', null)); - $this->assertStringStartsWith('$2', $result); - } - - public function testConfiguredAlgorithmWithLegacyConstValue() - { - $encoder = new NativePasswordEncoder(null, null, null, '1'); - $result = $encoder->encodePassword('password', null); - $this->assertTrue($encoder->isPasswordValid($result, 'password', null)); - $this->assertStringStartsWith('$2', $result); - } - - public function testCheckPasswordLength() - { - $encoder = new NativePasswordEncoder(null, null, 4); - $result = password_hash(str_repeat('a', 72), \PASSWORD_BCRYPT, ['cost' => 4]); - - $this->assertFalse($encoder->isPasswordValid($result, str_repeat('a', 73), 'salt')); - $this->assertTrue($encoder->isPasswordValid($result, str_repeat('a', 72), 'salt')); - } - - public function testNeedsRehash() - { - $encoder = new NativePasswordEncoder(4, 11000, 4); - - $this->assertTrue($encoder->needsRehash('dummyhash')); - - $hash = $encoder->encodePassword('foo', 'salt'); - $this->assertFalse($encoder->needsRehash($hash)); - - $encoder = new NativePasswordEncoder(5, 11000, 5); - $this->assertTrue($encoder->needsRehash($hash)); - } -} diff --git a/Tests/Encoder/Pbkdf2PasswordEncoderTest.php b/Tests/Encoder/Pbkdf2PasswordEncoderTest.php deleted file mode 100644 index 000e07d6..00000000 --- a/Tests/Encoder/Pbkdf2PasswordEncoderTest.php +++ /dev/null @@ -1,63 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Tests\Encoder; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Security\Core\Encoder\Pbkdf2PasswordEncoder; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; - -/** - * @group legacy - */ -class Pbkdf2PasswordEncoderTest extends TestCase -{ - public function testIsPasswordValid() - { - $encoder = new Pbkdf2PasswordEncoder('sha256', false, 1, 40); - - $this->assertTrue($encoder->isPasswordValid('c1232f10f62715fda06ae7c0a2037ca19b33cf103b727ba56d870c11f290a2ab106974c75607c8a3', 'password', '')); - } - - public function testEncodePassword() - { - $encoder = new Pbkdf2PasswordEncoder('sha256', false, 1, 40); - $this->assertSame('c1232f10f62715fda06ae7c0a2037ca19b33cf103b727ba56d870c11f290a2ab106974c75607c8a3', $encoder->encodePassword('password', '')); - - $encoder = new Pbkdf2PasswordEncoder('sha256', true, 1, 40); - $this->assertSame('wSMvEPYnFf2gaufAogN8oZszzxA7cnulbYcMEfKQoqsQaXTHVgfIow==', $encoder->encodePassword('password', '')); - - $encoder = new Pbkdf2PasswordEncoder('sha256', false, 2, 40); - $this->assertSame('8bc2f9167a81cdcfad1235cd9047f1136271c1f978fcfcb35e22dbeafa4634f6fd2214218ed63ebb', $encoder->encodePassword('password', '')); - } - - public function testEncodePasswordAlgorithmDoesNotExist() - { - $this->expectException(\LogicException::class); - $encoder = new Pbkdf2PasswordEncoder('foobar'); - $encoder->encodePassword('password', ''); - } - - public function testEncodePasswordLength() - { - $this->expectException(BadCredentialsException::class); - $encoder = new Pbkdf2PasswordEncoder('foobar'); - - $encoder->encodePassword(str_repeat('a', 5000), 'salt'); - } - - public function testCheckPasswordLength() - { - $encoder = new Pbkdf2PasswordEncoder('foobar'); - - $this->assertFalse($encoder->isPasswordValid('encoded', str_repeat('a', 5000), 'salt')); - } -} diff --git a/Tests/Encoder/PlaintextPasswordEncoderTest.php b/Tests/Encoder/PlaintextPasswordEncoderTest.php deleted file mode 100644 index 39804403..00000000 --- a/Tests/Encoder/PlaintextPasswordEncoderTest.php +++ /dev/null @@ -1,59 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Tests\Encoder; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; - -/** - * @group legacy - */ -class PlaintextPasswordEncoderTest extends TestCase -{ - public function testIsPasswordValid() - { - $encoder = new PlaintextPasswordEncoder(); - - $this->assertTrue($encoder->isPasswordValid('foo', 'foo', '')); - $this->assertFalse($encoder->isPasswordValid('bar', 'foo', '')); - $this->assertFalse($encoder->isPasswordValid('FOO', 'foo', '')); - - $encoder = new PlaintextPasswordEncoder(true); - - $this->assertTrue($encoder->isPasswordValid('foo', 'foo', '')); - $this->assertFalse($encoder->isPasswordValid('bar', 'foo', '')); - $this->assertTrue($encoder->isPasswordValid('FOO', 'foo', '')); - } - - public function testEncodePassword() - { - $encoder = new PlaintextPasswordEncoder(); - - $this->assertSame('foo', $encoder->encodePassword('foo', '')); - } - - public function testEncodePasswordLength() - { - $this->expectException(BadCredentialsException::class); - $encoder = new PlaintextPasswordEncoder(); - - $encoder->encodePassword(str_repeat('a', 5000), 'salt'); - } - - public function testCheckPasswordLength() - { - $encoder = new PlaintextPasswordEncoder(); - - $this->assertFalse($encoder->isPasswordValid('encoded', str_repeat('a', 5000), 'salt')); - } -} diff --git a/Tests/Encoder/SodiumPasswordEncoderTest.php b/Tests/Encoder/SodiumPasswordEncoderTest.php deleted file mode 100644 index 4bae5f89..00000000 --- a/Tests/Encoder/SodiumPasswordEncoderTest.php +++ /dev/null @@ -1,88 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Tests\Encoder; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; - -/** - * @group legacy - */ -class SodiumPasswordEncoderTest extends TestCase -{ - protected function setUp(): void - { - if (!SodiumPasswordEncoder::isSupported()) { - $this->markTestSkipped('Libsodium is not available.'); - } - } - - public function testValidation() - { - $encoder = new SodiumPasswordEncoder(); - $result = $encoder->encodePassword('password', null); - $this->assertTrue($encoder->isPasswordValid($result, 'password', null)); - $this->assertFalse($encoder->isPasswordValid($result, 'anotherPassword', null)); - $this->assertFalse($encoder->isPasswordValid($result, '', null)); - } - - public function testBCryptValidation() - { - $encoder = new SodiumPasswordEncoder(); - $this->assertTrue($encoder->isPasswordValid('$2y$04$M8GDODMoGQLQRpkYCdoJh.lbiZPee3SZI32RcYK49XYTolDGwoRMm', 'abc', null)); - } - - public function testNonArgonValidation() - { - $encoder = new SodiumPasswordEncoder(); - $this->assertTrue($encoder->isPasswordValid('$5$abcdefgh$ZLdkj8mkc2XVSrPVjskDAgZPGjtj1VGVaa1aUkrMTU/', 'password', null)); - $this->assertFalse($encoder->isPasswordValid('$5$abcdefgh$ZLdkj8mkc2XVSrPVjskDAgZPGjtj1VGVaa1aUkrMTU/', 'anotherPassword', null)); - $this->assertTrue($encoder->isPasswordValid('$6$abcdefgh$yVfUwsw5T.JApa8POvClA1pQ5peiq97DUNyXCZN5IrF.BMSkiaLQ5kvpuEm/VQ1Tvh/KV2TcaWh8qinoW5dhA1', 'password', null)); - $this->assertFalse($encoder->isPasswordValid('$6$abcdefgh$yVfUwsw5T.JApa8POvClA1pQ5peiq97DUNyXCZN5IrF.BMSkiaLQ5kvpuEm/VQ1Tvh/KV2TcaWh8qinoW5dhA1', 'anotherPassword', null)); - } - - public function testEncodePasswordLength() - { - $this->expectException(BadCredentialsException::class); - $encoder = new SodiumPasswordEncoder(); - $encoder->encodePassword(str_repeat('a', 4097), 'salt'); - } - - public function testCheckPasswordLength() - { - $encoder = new SodiumPasswordEncoder(); - $result = $encoder->encodePassword(str_repeat('a', 4096), null); - $this->assertFalse($encoder->isPasswordValid($result, str_repeat('a', 4097), null)); - $this->assertTrue($encoder->isPasswordValid($result, str_repeat('a', 4096), null)); - } - - public function testUserProvidedSaltIsNotUsed() - { - $encoder = new SodiumPasswordEncoder(); - $result = $encoder->encodePassword('password', 'salt'); - $this->assertTrue($encoder->isPasswordValid($result, 'password', 'anotherSalt')); - } - - public function testNeedsRehash() - { - $encoder = new SodiumPasswordEncoder(4, 11000); - - $this->assertTrue($encoder->needsRehash('dummyhash')); - - $hash = $encoder->encodePassword('foo', 'salt'); - $this->assertFalse($encoder->needsRehash($hash)); - - $encoder = new SodiumPasswordEncoder(5, 11000); - $this->assertTrue($encoder->needsRehash($hash)); - } -} diff --git a/Tests/Encoder/TestPasswordEncoderInterface.php b/Tests/Encoder/TestPasswordEncoderInterface.php deleted file mode 100644 index 3764038e..00000000 --- a/Tests/Encoder/TestPasswordEncoderInterface.php +++ /dev/null @@ -1,22 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Tests\Encoder; - -use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface; - -/** - * @group legacy - */ -interface TestPasswordEncoderInterface extends PasswordEncoderInterface -{ - public function needsRehash(string $encoded): bool; -} diff --git a/Tests/Encoder/UserPasswordEncoderTest.php b/Tests/Encoder/UserPasswordEncoderTest.php deleted file mode 100644 index 9fca4150..00000000 --- a/Tests/Encoder/UserPasswordEncoderTest.php +++ /dev/null @@ -1,98 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Tests\Encoder; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; -use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder; -use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface; -use Symfony\Component\Security\Core\Encoder\UserPasswordEncoder; -use Symfony\Component\Security\Core\User\User; -use Symfony\Component\Security\Core\User\UserInterface; - -/** - * @group legacy - */ -class UserPasswordEncoderTest extends TestCase -{ - public function testEncodePassword() - { - $userMock = $this->createMock(UserInterface::class); - $userMock->expects($this->any()) - ->method('getSalt') - ->willReturn('userSalt'); - - $mockEncoder = $this->createMock(PasswordEncoderInterface::class); - $mockEncoder->expects($this->any()) - ->method('encodePassword') - ->with($this->equalTo('plainPassword'), $this->equalTo('userSalt')) - ->willReturn('encodedPassword'); - - $mockEncoderFactory = $this->createMock(EncoderFactoryInterface::class); - $mockEncoderFactory->expects($this->any()) - ->method('getEncoder') - ->with($this->equalTo($userMock)) - ->willReturn($mockEncoder); - - $passwordEncoder = new UserPasswordEncoder($mockEncoderFactory); - - $encoded = $passwordEncoder->encodePassword($userMock, 'plainPassword'); - $this->assertEquals('encodedPassword', $encoded); - } - - public function testIsPasswordValid() - { - $userMock = $this->createMock(UserInterface::class); - $userMock->expects($this->any()) - ->method('getSalt') - ->willReturn('userSalt'); - $userMock->expects($this->any()) - ->method('getPassword') - ->willReturn('encodedPassword'); - - $mockEncoder = $this->createMock(PasswordEncoderInterface::class); - $mockEncoder->expects($this->any()) - ->method('isPasswordValid') - ->with($this->equalTo('encodedPassword'), $this->equalTo('plainPassword'), $this->equalTo('userSalt')) - ->willReturn(true); - - $mockEncoderFactory = $this->createMock(EncoderFactoryInterface::class); - $mockEncoderFactory->expects($this->any()) - ->method('getEncoder') - ->with($this->equalTo($userMock)) - ->willReturn($mockEncoder); - - $passwordEncoder = new UserPasswordEncoder($mockEncoderFactory); - - $isValid = $passwordEncoder->isPasswordValid($userMock, 'plainPassword'); - $this->assertTrue($isValid); - } - - public function testNeedsRehash() - { - $user = new User('username', null); - $encoder = new NativePasswordEncoder(4, 20000, 4); - - $mockEncoderFactory = $this->createMock(EncoderFactoryInterface::class); - $mockEncoderFactory->expects($this->any()) - ->method('getEncoder') - ->with($user) - ->willReturn($encoder, $encoder, new NativePasswordEncoder(5, 20000, 5), $encoder); - - $passwordEncoder = new UserPasswordEncoder($mockEncoderFactory); - - $user->setPassword($passwordEncoder->encodePassword($user, 'foo', 'salt')); - $this->assertFalse($passwordEncoder->needsRehash($user)); - $this->assertTrue($passwordEncoder->needsRehash($user)); - $this->assertFalse($passwordEncoder->needsRehash($user)); - } -} diff --git a/Tests/Exception/CustomUserMessageAuthenticationExceptionTest.php b/Tests/Exception/CustomUserMessageAuthenticationExceptionTest.php index bd499b09..5555ce0b 100644 --- a/Tests/Exception/CustomUserMessageAuthenticationExceptionTest.php +++ b/Tests/Exception/CustomUserMessageAuthenticationExceptionTest.php @@ -46,7 +46,7 @@ public function testConstructWithSAfeMessage() public function testSharedSerializedData() { - $token = new UsernamePasswordToken(new InMemoryUser('foo', ''), 'bar'); + $token = new UsernamePasswordToken(new InMemoryUser('foo', 'bar', ['ROLE_USER']), 'main', ['ROLE_USER']); $exception = new CustomUserMessageAuthenticationException(); $exception->setToken($token); @@ -60,7 +60,7 @@ public function testSharedSerializedData() public function testSharedSerializedDataFromChild() { - $token = new UsernamePasswordToken(new InMemoryUser('foo', ''), 'bar'); + $token = new UsernamePasswordToken(new InMemoryUser('foo', 'bar', ['ROLE_USER']), 'main', ['ROLE_USER']); $exception = new ChildCustomUserMessageAuthenticationException(); $exception->childMember = $token; diff --git a/Tests/Exception/UserNotFoundExceptionTest.php b/Tests/Exception/UserNotFoundExceptionTest.php index 3d9de8f1..d0c812eb 100644 --- a/Tests/Exception/UserNotFoundExceptionTest.php +++ b/Tests/Exception/UserNotFoundExceptionTest.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Security\Core\Tests\Exception; use PHPUnit\Framework\TestCase; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; use Symfony\Component\Security\Core\Exception\UserNotFoundException; class UserNotFoundExceptionTest extends TestCase @@ -31,26 +30,4 @@ public function testUserIdentifierIsNotSetByDefault() $this->assertNull($exception->getUserIdentifier()); } - - /** - * @group legacy - */ - public function testUsernameIsNotSetByDefault() - { - $exception = new UserNotFoundException(); - - $this->assertNull($exception->getUsername()); - } - - /** - * @group legacy - */ - public function testUsernameNotFoundException() - { - $exception = new UsernameNotFoundException(); - $this->assertInstanceOf(UserNotFoundException::class, $exception); - - $exception->setUsername('username'); - $this->assertEquals('username', $exception->getUserIdentifier()); - } } diff --git a/Tests/Resources/TranslationFilesTest.php b/Tests/Resources/TranslationFilesTest.php index a3fb755e..695cdd98 100644 --- a/Tests/Resources/TranslationFilesTest.php +++ b/Tests/Resources/TranslationFilesTest.php @@ -26,7 +26,7 @@ public function testTranslationFileIsValid($filePath) $errors = XliffUtils::validateSchema($document); - $this->assertCount(0, $errors, sprintf('"%s" is invalid:%s', $filePath, \PHP_EOL.implode(\PHP_EOL, array_column($errors, 'message')))); + $this->assertCount(0, $errors, \sprintf('"%s" is invalid:%s', $filePath, \PHP_EOL.implode(\PHP_EOL, array_column($errors, 'message')))); } /** @@ -36,19 +36,16 @@ public function testTranslationFileIsValidWithoutEntityLoader($filePath) { $document = new \DOMDocument(); $document->loadXML(file_get_contents($filePath)); - if (\LIBXML_VERSION < 20900) { - libxml_disable_entity_loader(true); - } $errors = XliffUtils::validateSchema($document); - $this->assertCount(0, $errors, sprintf('"%s" is invalid:%s', $filePath, \PHP_EOL.implode(\PHP_EOL, array_column($errors, 'message')))); + $this->assertCount(0, $errors, \sprintf('"%s" is invalid:%s', $filePath, \PHP_EOL.implode(\PHP_EOL, array_column($errors, 'message')))); } public static function provideTranslationFiles() { return array_map( - function ($filePath) { return (array) $filePath; }, + fn ($filePath) => (array) $filePath, glob(\dirname(__DIR__, 2).'/Resources/translations/*.xlf') ); } diff --git a/Tests/SecurityTest.php b/Tests/SecurityTest.php deleted file mode 100644 index 55dad5a6..00000000 --- a/Tests/SecurityTest.php +++ /dev/null @@ -1,114 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Tests; - -use PHPUnit\Framework\TestCase; -use Psr\Container\ContainerInterface; -use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; -use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; -use Symfony\Component\Security\Core\Security; -use Symfony\Component\Security\Core\User\InMemoryUser; - -class SecurityTest extends TestCase -{ - public function testGetToken() - { - $token = new UsernamePasswordToken(new InMemoryUser('foo', 'bar'), 'provider'); - $tokenStorage = $this->createMock(TokenStorageInterface::class); - - $tokenStorage->expects($this->once()) - ->method('getToken') - ->willReturn($token); - - $container = $this->createContainer('security.token_storage', $tokenStorage); - - $security = new Security($container); - $this->assertSame($token, $security->getToken()); - } - - /** - * @dataProvider getUserTests - * @dataProvider getLegacyUserTests - */ - public function testGetUser($userInToken, $expectedUser) - { - $token = $this->createMock(TokenInterface::class); - $token->expects($this->any()) - ->method('getUser') - ->willReturn($userInToken); - $tokenStorage = $this->createMock(TokenStorageInterface::class); - - $tokenStorage->expects($this->once()) - ->method('getToken') - ->willReturn($token); - - $container = $this->createContainer('security.token_storage', $tokenStorage); - - $security = new Security($container); - $this->assertSame($expectedUser, $security->getUser()); - } - - public static function getUserTests() - { - yield [null, null]; - - $user = new InMemoryUser('nice_user', 'foo'); - yield [$user, $user]; - } - - /** - * @group legacy - */ - public static function getLegacyUserTests() - { - yield ['string_username', null]; - - yield [new StringishUser(), null]; - } - - public function testIsGranted() - { - $authorizationChecker = $this->createMock(AuthorizationCheckerInterface::class); - - $authorizationChecker->expects($this->once()) - ->method('isGranted') - ->with('SOME_ATTRIBUTE', 'SOME_SUBJECT') - ->willReturn(true); - - $container = $this->createContainer('security.authorization_checker', $authorizationChecker); - - $security = new Security($container); - $this->assertTrue($security->isGranted('SOME_ATTRIBUTE', 'SOME_SUBJECT')); - } - - private function createContainer($serviceId, $serviceObject) - { - $container = $this->createMock(ContainerInterface::class); - - $container->expects($this->atLeastOnce()) - ->method('get') - ->with($serviceId) - ->willReturn($serviceObject); - - return $container; - } -} - -class StringishUser -{ - public function __toString(): string - { - return 'stringish_user'; - } -} diff --git a/Tests/User/ChainUserCheckerTest.php b/Tests/User/ChainUserCheckerTest.php new file mode 100644 index 00000000..f55fa655 --- /dev/null +++ b/Tests/User/ChainUserCheckerTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Tests\User; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Security\Core\User\ChainUserChecker; +use Symfony\Component\Security\Core\User\UserCheckerInterface; +use Symfony\Component\Security\Core\User\UserInterface; + +final class ChainUserCheckerTest extends TestCase +{ + public function testForwardsPreAuthToAllUserCheckers() + { + $user = $this->createMock(UserInterface::class); + + $checker1 = $this->createMock(UserCheckerInterface::class); + $checker1->expects($this->once()) + ->method('checkPreAuth') + ->with($user); + + $checker2 = $this->createMock(UserCheckerInterface::class); + $checker2->expects($this->once()) + ->method('checkPreAuth') + ->with($user); + + $checker3 = $this->createMock(UserCheckerInterface::class); + $checker3->expects($this->once()) + ->method('checkPreAuth') + ->with($user); + + (new ChainUserChecker([$checker1, $checker2, $checker3]))->checkPreAuth($user); + } + + public function testForwardsPostAuthToAllUserCheckers() + { + $user = $this->createMock(UserInterface::class); + + $checker1 = $this->createMock(UserCheckerInterface::class); + $checker1->expects($this->once()) + ->method('checkPostAuth') + ->with($user); + + $checker2 = $this->createMock(UserCheckerInterface::class); + $checker2->expects($this->once()) + ->method('checkPostAuth') + ->with($user); + + $checker3 = $this->createMock(UserCheckerInterface::class); + $checker3->expects($this->once()) + ->method('checkPostAuth') + ->with($user); + + (new ChainUserChecker([$checker1, $checker2, $checker3]))->checkPostAuth($user); + } +} diff --git a/Tests/User/ChainUserProviderTest.php b/Tests/User/ChainUserProviderTest.php index c44402bd..3f6d223d 100644 --- a/Tests/User/ChainUserProviderTest.php +++ b/Tests/User/ChainUserProviderTest.php @@ -23,7 +23,7 @@ class ChainUserProviderTest extends TestCase { - public function testLoadUserByUsername() + public function testLoadUserByIdentifier() { $provider1 = $this->createMock(InMemoryUserProvider::class); $provider1 @@ -45,9 +45,8 @@ public function testLoadUserByUsername() $this->assertSame($account, $provider->loadUserByIdentifier('foo')); } - public function testLoadUserByUsernameThrowsUserNotFoundException() + public function testLoadUserByIdentifierThrowsUserNotFoundException() { - $this->expectException(UserNotFoundException::class); $provider1 = $this->createMock(InMemoryUserProvider::class); $provider1 ->expects($this->once()) @@ -65,6 +64,9 @@ public function testLoadUserByUsernameThrowsUserNotFoundException() ; $provider = new ChainUserProvider([$provider1, $provider2]); + + $this->expectException(UserNotFoundException::class); + $provider->loadUserByIdentifier('foo'); } @@ -141,7 +143,6 @@ public function testRefreshUserAgain() public function testRefreshUserThrowsUnsupportedUserException() { - $this->expectException(UnsupportedUserException::class); $provider1 = $this->createMock(InMemoryUserProvider::class); $provider1 ->expects($this->once()) @@ -169,6 +170,9 @@ public function testRefreshUserThrowsUnsupportedUserException() ; $provider = new ChainUserProvider([$provider1, $provider2]); + + $this->expectException(UnsupportedUserException::class); + $provider->refreshUser($this->createMock(UserInterface::class)); } diff --git a/Tests/User/InMemoryUserCheckerTest.php b/Tests/User/InMemoryUserCheckerTest.php index 8b01e5f0..25107723 100644 --- a/Tests/User/InMemoryUserCheckerTest.php +++ b/Tests/User/InMemoryUserCheckerTest.php @@ -35,7 +35,6 @@ public function testCheckPostAuthPass() public function testCheckPreAuthDisabled() { $this->expectException(DisabledException::class); - $checker = new InMemoryUserChecker(); - $checker->checkPreAuth(new InMemoryUser('John', 'password', [], false)); + (new InMemoryUserChecker())->checkPreAuth(new InMemoryUser('John', 'password', [], false)); } } diff --git a/Tests/User/InMemoryUserProviderTest.php b/Tests/User/InMemoryUserProviderTest.php index d4d4964c..98afb3b4 100644 --- a/Tests/User/InMemoryUserProviderTest.php +++ b/Tests/User/InMemoryUserProviderTest.php @@ -12,16 +12,12 @@ namespace Symfony\Component\Security\Core\Tests\User; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\InMemoryUser; use Symfony\Component\Security\Core\User\InMemoryUserProvider; -use Symfony\Component\Security\Core\User\User; class InMemoryUserProviderTest extends TestCase { - use ExpectDeprecationTrait; - public function testConstructor() { $provider = $this->createProvider(); @@ -44,22 +40,6 @@ public function testRefresh() $this->assertFalse($refreshedUser->isEnabled()); } - /** - * @group legacy - */ - public function testRefreshWithLegacyUser() - { - $user = new User('fabien', 'bar'); - - $provider = $this->createProvider(); - - $refreshedUser = $provider->refreshUser($user); - $this->assertEquals('foo', $refreshedUser->getPassword()); - $this->assertEquals(['ROLE_USER'], $refreshedUser->getRoles()); - $this->assertFalse($refreshedUser->isEnabled()); - $this->assertFalse($refreshedUser->isCredentialsNonExpired()); - } - protected function createProvider(): InMemoryUserProvider { return new InMemoryUserProvider([ @@ -82,16 +62,17 @@ public function testCreateUser() public function testCreateUserAlreadyExist() { - $this->expectException(\LogicException::class); $provider = new InMemoryUserProvider(); $provider->createUser(new InMemoryUser('fabien', 'foo')); + + $this->expectException(\LogicException::class); + $provider->createUser(new InMemoryUser('fabien', 'foo')); } - public function testLoadUserByUsernameDoesNotExist() + public function testLoadUserByIdentifierDoesNotExist() { $this->expectException(UserNotFoundException::class); - $provider = new InMemoryUserProvider(); - $provider->loadUserByIdentifier('fabien'); + (new InMemoryUserProvider())->loadUserByIdentifier('fabien'); } } diff --git a/Tests/User/InMemoryUserTest.php b/Tests/User/InMemoryUserTest.php index a5496ef3..0e64bce5 100644 --- a/Tests/User/InMemoryUserTest.php +++ b/Tests/User/InMemoryUserTest.php @@ -12,15 +12,11 @@ namespace Symfony\Component\Security\Core\Tests\User; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; -use Symfony\Component\Security\Core\User\EquatableInterface; use Symfony\Component\Security\Core\User\InMemoryUser; use Symfony\Component\Security\Core\User\UserInterface; class InMemoryUserTest extends TestCase { - use ExpectDeprecationTrait; - public function testConstructorException() { $this->expectException(\InvalidArgumentException::class); @@ -42,29 +38,12 @@ public function testGetPassword() $this->assertEquals('superpass', $user->getPassword()); } - /** - * @group legacy - */ - public function testGetUsername() - { - $user = new InMemoryUser('fabien', 'superpass'); - - $this->expectDeprecation('Since symfony/security-core 5.3: Method "Symfony\Component\Security\Core\User\User::getUsername()" is deprecated, use getUserIdentifier() instead.'); - $this->assertEquals('fabien', $user->getUsername()); - } - public function testGetUserIdentifier() { $user = new InMemoryUser('fabien', 'superpass'); $this->assertEquals('fabien', $user->getUserIdentifier()); } - public function testGetSalt() - { - $user = new InMemoryUser('fabien', 'superpass'); - $this->assertNull($user->getSalt()); - } - public function testIsEnabled() { $user = new InMemoryUser('mathilde', 'k'); @@ -90,9 +69,9 @@ public function testToString() /** * @dataProvider isEqualToData * - * @param bool $expectation - * @param EquatableInterface|UserInterface $a - * @param EquatableInterface|UserInterface $b + * @param bool $expectation + * @param UserInterface $a + * @param UserInterface $b */ public function testIsEqualTo($expectation, $a, $b) { diff --git a/Tests/User/OidcUserTest.php b/Tests/User/OidcUserTest.php new file mode 100644 index 00000000..79256283 --- /dev/null +++ b/Tests/User/OidcUserTest.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Tests\User; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Security\Core\User\OidcUser; + +class OidcUserTest extends TestCase +{ + public function testCannotCreateUserWithoutSubProperty() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The "sub" claim cannot be empty.'); + + new OidcUser(); + } + + public function testCreateFullUserWithAdditionalClaimsUsingPositionalParameters() + { + $this->assertEquals(new OidcUser( + userIdentifier: 'john.doe', + roles: ['ROLE_USER', 'ROLE_ADMIN'], + sub: 'e21bf182-1538-406e-8ccb-e25a17aba39f', + name: 'John DOE', + givenName: 'John', + familyName: 'DOE', + middleName: 'Fitzgerald', + nickname: 'Johnny', + preferredUsername: 'john.doe', + profile: 'https://www.example.com/john-doe', + picture: 'https://www.example.com/pics/john-doe.jpg', + website: 'https://www.example.com', + email: 'john.doe@example.com', + emailVerified: true, + gender: 'male', + birthdate: '1980-05-15', + zoneinfo: 'Europe/Paris', + locale: 'fr-FR', + phoneNumber: '+33 (0) 6 12 34 56 78', + phoneNumberVerified: false, + address: [ + 'formatted' => '1 Rue des Moulins 75000 Paris - France', + 'street_address' => '1 Rue des Moulins', + 'locality' => 'Paris', + 'region' => 'Île-de-France', + 'postal_code' => '75000', + 'country' => 'France', + ], + updatedAt: (new \DateTimeImmutable())->setTimestamp(1669628917), + additionalClaims: [ + 'impersonator' => [ + 'username' => 'jane.doe@example.com', + ], + 'customId' => 12345, + ], + ), new OidcUser(...[ + 'userIdentifier' => 'john.doe', + 'roles' => ['ROLE_USER', 'ROLE_ADMIN'], + 'sub' => 'e21bf182-1538-406e-8ccb-e25a17aba39f', + 'name' => 'John DOE', + 'givenName' => 'John', + 'familyName' => 'DOE', + 'middleName' => 'Fitzgerald', + 'nickname' => 'Johnny', + 'preferredUsername' => 'john.doe', + 'profile' => 'https://www.example.com/john-doe', + 'picture' => 'https://www.example.com/pics/john-doe.jpg', + 'website' => 'https://www.example.com', + 'email' => 'john.doe@example.com', + 'emailVerified' => true, + 'gender' => 'male', + 'birthdate' => '1980-05-15', + 'zoneinfo' => 'Europe/Paris', + 'locale' => 'fr-FR', + 'phoneNumber' => '+33 (0) 6 12 34 56 78', + 'phoneNumberVerified' => false, + 'address' => [ + 'formatted' => '1 Rue des Moulins 75000 Paris - France', + 'street_address' => '1 Rue des Moulins', + 'locality' => 'Paris', + 'region' => 'Île-de-France', + 'postal_code' => '75000', + 'country' => 'France', + ], + 'updatedAt' => (new \DateTimeImmutable())->setTimestamp(1669628917), + 'impersonator' => [ + 'username' => 'jane.doe@example.com', + ], + 'customId' => 12345, + ])); + } + + public function testCreateFullUserWithAdditionalClaims() + { + $this->assertEquals(new OidcUser( + userIdentifier: 'john.doe', + roles: ['ROLE_USER', 'ROLE_ADMIN'], + sub: 'e21bf182-1538-406e-8ccb-e25a17aba39f', + name: 'John DOE', + givenName: 'John', + familyName: 'DOE', + middleName: 'Fitzgerald', + nickname: 'Johnny', + preferredUsername: 'john.doe', + profile: 'https://www.example.com/john-doe', + picture: 'https://www.example.com/pics/john-doe.jpg', + website: 'https://www.example.com', + email: 'john.doe@example.com', + emailVerified: true, + gender: 'male', + birthdate: '1980-05-15', + zoneinfo: 'Europe/Paris', + locale: 'fr-FR', + phoneNumber: '+33 (0) 6 12 34 56 78', + phoneNumberVerified: false, + address: [ + 'formatted' => '1 Rue des Moulins 75000 Paris - France', + 'street_address' => '1 Rue des Moulins', + 'locality' => 'Paris', + 'region' => 'Île-de-France', + 'postal_code' => '75000', + 'country' => 'France', + ], + updatedAt: (new \DateTimeImmutable())->setTimestamp(1669628917), + additionalClaims: [ + [ + 'username' => 'jane.doe@example.com', + ], + 12345, + ], + ), new OidcUser( + 'john.doe', + ['ROLE_USER', 'ROLE_ADMIN'], + 'e21bf182-1538-406e-8ccb-e25a17aba39f', + 'John DOE', + 'John', + 'DOE', + 'Fitzgerald', + 'Johnny', + 'john.doe', + 'https://www.example.com/john-doe', + 'https://www.example.com/pics/john-doe.jpg', + 'https://www.example.com', + 'john.doe@example.com', + true, + 'male', + '1980-05-15', + 'Europe/Paris', + 'fr-FR', + '+33 (0) 6 12 34 56 78', + false, + [ + 'formatted' => '1 Rue des Moulins 75000 Paris - France', + 'street_address' => '1 Rue des Moulins', + 'locality' => 'Paris', + 'region' => 'Île-de-France', + 'postal_code' => '75000', + 'country' => 'France', + ], + (new \DateTimeImmutable())->setTimestamp(1669628917), + [ + 'username' => 'jane.doe@example.com', + ], + 12345 + )); + } +} diff --git a/Tests/User/UserCheckerTest.php b/Tests/User/UserCheckerTest.php deleted file mode 100644 index 728d935b..00000000 --- a/Tests/User/UserCheckerTest.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Tests\User; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Security\Core\Exception\AccountExpiredException; -use Symfony\Component\Security\Core\Exception\CredentialsExpiredException; -use Symfony\Component\Security\Core\Exception\DisabledException; -use Symfony\Component\Security\Core\Exception\LockedException; -use Symfony\Component\Security\Core\User\User; -use Symfony\Component\Security\Core\User\UserChecker; -use Symfony\Component\Security\Core\User\UserInterface; - -/** - * @group legacy - */ -class UserCheckerTest extends TestCase -{ - public function testCheckPostAuthNotAdvancedUserInterface() - { - $checker = new UserChecker(); - - $this->assertNull($checker->checkPostAuth($this->createMock(UserInterface::class))); - } - - public function testCheckPostAuthPass() - { - $checker = new UserChecker(); - $this->assertNull($checker->checkPostAuth(new User('John', 'password'))); - } - - public function testCheckPostAuthCredentialsExpired() - { - $this->expectException(CredentialsExpiredException::class); - $checker = new UserChecker(); - $checker->checkPostAuth(new User('John', 'password', [], true, true, false, true)); - } - - public function testCheckPreAuthAccountLocked() - { - $this->expectException(LockedException::class); - $checker = new UserChecker(); - $checker->checkPreAuth(new User('John', 'password', [], true, true, false, false)); - } - - public function testCheckPreAuthDisabled() - { - $this->expectException(DisabledException::class); - $checker = new UserChecker(); - $checker->checkPreAuth(new User('John', 'password', [], false, true, false, true)); - } - - public function testCheckPreAuthAccountExpired() - { - $this->expectException(AccountExpiredException::class); - $checker = new UserChecker(); - $checker->checkPreAuth(new User('John', 'password', [], true, false, true, true)); - } -} diff --git a/Tests/User/UserTest.php b/Tests/User/UserTest.php deleted file mode 100644 index 81b8705d..00000000 --- a/Tests/User/UserTest.php +++ /dev/null @@ -1,152 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Tests\User; - -use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; -use Symfony\Component\Security\Core\User\EquatableInterface; -use Symfony\Component\Security\Core\User\User; -use Symfony\Component\Security\Core\User\UserInterface; - -/** - * @group legacy - */ -class UserTest extends TestCase -{ - use ExpectDeprecationTrait; - - public function testConstructorException() - { - $this->expectException(\InvalidArgumentException::class); - new User('', 'superpass'); - } - - public function testGetRoles() - { - $user = new User('fabien', 'superpass'); - $this->assertEquals([], $user->getRoles()); - - $user = new User('fabien', 'superpass', ['ROLE_ADMIN']); - $this->assertEquals(['ROLE_ADMIN'], $user->getRoles()); - } - - public function testGetPassword() - { - $user = new User('fabien', 'superpass'); - $this->assertEquals('superpass', $user->getPassword()); - } - - /** - * @group legacy - */ - public function testGetUsername() - { - $user = new User('fabien', 'superpass'); - - $this->expectDeprecation('Since symfony/security-core 5.3: Method "Symfony\Component\Security\Core\User\User::getUsername()" is deprecated, use getUserIdentifier() instead.'); - $this->assertEquals('fabien', $user->getUsername()); - } - - public function testGetUserIdentifier() - { - $user = new User('fabien', 'superpass'); - $this->assertEquals('fabien', $user->getUserIdentifier()); - } - - public function testGetSalt() - { - $user = new User('fabien', 'superpass'); - $this->assertEquals('', $user->getSalt()); - } - - public function testIsAccountNonExpired() - { - $user = new User('fabien', 'superpass'); - $this->assertTrue($user->isAccountNonExpired()); - - $user = new User('fabien', 'superpass', [], true, false); - $this->assertFalse($user->isAccountNonExpired()); - } - - public function testIsCredentialsNonExpired() - { - $user = new User('fabien', 'superpass'); - $this->assertTrue($user->isCredentialsNonExpired()); - - $user = new User('fabien', 'superpass', [], true, true, false); - $this->assertFalse($user->isCredentialsNonExpired()); - } - - public function testIsAccountNonLocked() - { - $user = new User('fabien', 'superpass'); - $this->assertTrue($user->isAccountNonLocked()); - - $user = new User('fabien', 'superpass', [], true, true, true, false); - $this->assertFalse($user->isAccountNonLocked()); - } - - public function testIsEnabled() - { - $user = new User('fabien', 'superpass'); - $this->assertTrue($user->isEnabled()); - - $user = new User('fabien', 'superpass', [], false); - $this->assertFalse($user->isEnabled()); - } - - public function testEraseCredentials() - { - $user = new User('fabien', 'superpass'); - $user->eraseCredentials(); - $this->assertEquals('superpass', $user->getPassword()); - } - - public function testToString() - { - $user = new User('fabien', 'superpass'); - $this->assertEquals('fabien', (string) $user); - } - - /** - * @dataProvider isEqualToData - * - * @param bool $expectation - * @param EquatableInterface|UserInterface $a - * @param EquatableInterface|UserInterface $b - */ - public function testIsEqualTo($expectation, $a, $b) - { - $this->assertSame($expectation, $a->isEqualTo($b)); - $this->assertSame($expectation, $b->isEqualTo($a)); - } - - public static function isEqualToData() - { - return [ - [true, new User('username', 'password'), new User('username', 'password')], - [false, new User('username', 'password', ['ROLE']), new User('username', 'password')], - [false, new User('username', 'password', ['ROLE']), new User('username', 'password', ['NO ROLE'])], - [false, new User('diff', 'diff'), new User('username', 'password')], - [false, new User('diff', 'diff', [], false), new User('username', 'password')], - [false, new User('diff', 'diff', [], false, false), new User('username', 'password')], - [false, new User('diff', 'diff', [], false, false, false), new User('username', 'password')], - [false, new User('diff', 'diff', [], false, false, false, false), new User('username', 'password')], - ]; - } - - public function testIsEqualToWithDifferentUser() - { - $user = new User('username', 'password'); - $this->assertFalse($user->isEqualTo($this->createMock(UserInterface::class))); - } -} diff --git a/Tests/Validator/Constraints/UserPasswordTest.php b/Tests/Validator/Constraints/UserPasswordTest.php index 7e22d28b..ed4ca442 100644 --- a/Tests/Validator/Constraints/UserPasswordTest.php +++ b/Tests/Validator/Constraints/UserPasswordTest.php @@ -14,7 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Security\Core\Validator\Constraints\UserPassword; use Symfony\Component\Validator\Mapping\ClassMetadata; -use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Validator\Mapping\Loader\AttributeLoader; class UserPasswordTest extends TestCase { @@ -37,25 +37,18 @@ public static function provideServiceValidatedConstraints(): iterable { yield 'Doctrine style' => [new UserPassword(['service' => 'my_service'])]; - if (\PHP_VERSION_ID < 80000) { - return; - } - - yield 'named arguments' => [eval('return new \Symfony\Component\Security\Core\Validator\Constraints\UserPassword(service: "my_service");')]; + yield 'named arguments' => [new UserPassword(service: 'my_service')]; $metadata = new ClassMetadata(UserPasswordDummy::class); - self::assertTrue((new AnnotationLoader())->loadClassMetadata($metadata)); + self::assertTrue((new AttributeLoader())->loadClassMetadata($metadata)); yield 'attribute' => [$metadata->properties['b']->constraints[0]]; } - /** - * @requires PHP 8 - */ public function testAttributes() { $metadata = new ClassMetadata(UserPasswordDummy::class); - self::assertTrue((new AnnotationLoader())->loadClassMetadata($metadata)); + self::assertTrue((new AttributeLoader())->loadClassMetadata($metadata)); [$bConstraint] = $metadata->properties['b']->getConstraints(); self::assertSame('myMessage', $bConstraint->message); diff --git a/Tests/Validator/Constraints/UserPasswordValidatorTestCase.php b/Tests/Validator/Constraints/UserPasswordValidatorTestCase.php index 6fe8de6a..c78f6b5f 100644 --- a/Tests/Validator/Constraints/UserPasswordValidatorTestCase.php +++ b/Tests/Validator/Constraints/UserPasswordValidatorTestCase.php @@ -29,22 +29,11 @@ abstract class UserPasswordValidatorTestCase extends ConstraintValidatorTestCase private const PASSWORD = 's3Cr3t'; private const SALT = '^S4lt$'; - /** - * @var TokenStorageInterface - */ - protected $tokenStorage; - - /** - * @var PasswordHasherInterface - */ - protected $hasher; + protected TokenStorageInterface $tokenStorage; + protected PasswordHasherInterface $hasher; + protected PasswordHasherFactoryInterface $hasherFactory; - /** - * @var PasswordHasherFactoryInterface - */ - protected $hasherFactory; - - protected function createValidator() + protected function createValidator(): UserPasswordValidator { return new UserPasswordValidator($this->tokenStorage, $this->hasherFactory); } @@ -87,6 +76,7 @@ public function testPasswordIsNotValid(UserPassword $constraint) $this->validator->validate('secret', $constraint); $this->buildViolation('myMessage') + ->setCode(UserPassword::INVALID_PASSWORD_ERROR) ->assertRaised(); } @@ -94,9 +84,7 @@ public static function provideConstraints(): iterable { yield 'Doctrine style' => [new UserPassword(['message' => 'myMessage'])]; - if (\PHP_VERSION_ID >= 80000) { - yield 'named arguments' => [eval('return new \Symfony\Component\Security\Core\Validator\Constraints\UserPassword(message: "myMessage");')]; - } + yield 'named arguments' => [new UserPassword(message: 'myMessage')]; } /** @@ -111,6 +99,7 @@ public function testEmptyPasswordsAreNotValid($password) $this->validator->validate($password, $constraint); $this->buildViolation('myMessage') + ->setCode(UserPassword::INVALID_PASSWORD_ERROR) ->assertRaised(); } @@ -124,13 +113,14 @@ public static function emptyPasswordData() public function testUserIsNotValid() { - $this->expectException(ConstraintDefinitionException::class); $user = new \stdClass(); $this->tokenStorage = $this->createTokenStorage($user); $this->validator = $this->createValidator(); $this->validator->initialize($this->context); + $this->expectException(ConstraintDefinitionException::class); + $this->validator->validate('secret', new UserPassword()); } diff --git a/User/AttributesBasedUserProviderInterface.php b/User/AttributesBasedUserProviderInterface.php new file mode 100644 index 00000000..9d79422a --- /dev/null +++ b/User/AttributesBasedUserProviderInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\User; + +use Symfony\Component\Security\Core\Exception\UserNotFoundException; + +/** + * Overrides UserProviderInterface to add an "attributes" argument on loadUserByIdentifier. + * This is particularly useful with self-contained access tokens. + * + * @template-covariant TUser of UserInterface + * + * @template-extends UserProviderInterface + */ +interface AttributesBasedUserProviderInterface extends UserProviderInterface +{ + /** + * Loads the user for the given user identifier (e.g. username or email) and attributes. + * + * This method must throw UserNotFoundException if the user is not found. + * + * @return TUser + * + * @throws UserNotFoundException + */ + public function loadUserByIdentifier(string $identifier, array $attributes = []): UserInterface; +} diff --git a/User/ChainUserChecker.php b/User/ChainUserChecker.php new file mode 100644 index 00000000..67fd76b9 --- /dev/null +++ b/User/ChainUserChecker.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\User; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +final class ChainUserChecker implements UserCheckerInterface +{ + /** + * @param iterable $checkers + */ + public function __construct(private readonly iterable $checkers) + { + } + + public function checkPreAuth(UserInterface $user): void + { + foreach ($this->checkers as $checker) { + $checker->checkPreAuth($user); + } + } + + public function checkPostAuth(UserInterface $user /*, TokenInterface $token*/): void + { + $token = 1 < \func_num_args() ? func_get_arg(1) : null; + + foreach ($this->checkers as $checker) { + if ($token instanceof TokenInterface) { + $checker->checkPostAuth($user, $token); + } else { + $checker->checkPostAuth($user); + } + } + } +} diff --git a/User/ChainUserProvider.php b/User/ChainUserProvider.php index 148b239f..21e2e618 100644 --- a/User/ChainUserProvider.php +++ b/User/ChainUserProvider.php @@ -21,23 +21,23 @@ * handle the request. * * @author Johannes M. Schmitt + * + * @template-implements UserProviderInterface */ class ChainUserProvider implements UserProviderInterface, PasswordUpgraderInterface { - private $providers; - /** * @param iterable $providers */ - public function __construct(iterable $providers) - { - $this->providers = $providers; + public function __construct( + private iterable $providers, + ) { } /** * @return UserProviderInterface[] */ - public function getProviders() + public function getProviders(): array { if ($this->providers instanceof \Traversable) { return iterator_to_array($this->providers); @@ -46,42 +46,22 @@ public function getProviders() return $this->providers; } - /** - * {@inheritdoc} - */ - public function loadUserByUsername(string $username) - { - trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use loadUserByIdentifier() instead.', __METHOD__); - - return $this->loadUserByIdentifier($username); - } - public function loadUserByIdentifier(string $identifier): UserInterface { foreach ($this->providers as $provider) { try { - // @deprecated since Symfony 5.3, change to $provider->loadUserByIdentifier() in 6.0 - if (!method_exists($provider, 'loadUserByIdentifier')) { - trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', get_debug_type($provider)); - - return $provider->loadUserByUsername($identifier); - } - return $provider->loadUserByIdentifier($identifier); - } catch (UserNotFoundException $e) { + } catch (UserNotFoundException) { // try next one } } - $ex = new UserNotFoundException(sprintf('There is no user with identifier "%s".', $identifier)); + $ex = new UserNotFoundException(\sprintf('There is no user with identifier "%s".', $identifier)); $ex->setUserIdentifier($identifier); throw $ex; } - /** - * {@inheritdoc} - */ - public function refreshUser(UserInterface $user) + public function refreshUser(UserInterface $user): UserInterface { $supportedUserFound = false; @@ -92,29 +72,25 @@ public function refreshUser(UserInterface $user) } return $provider->refreshUser($user); - } catch (UnsupportedUserException $e) { + } catch (UnsupportedUserException) { // try next one - } catch (UserNotFoundException $e) { + } catch (UserNotFoundException) { $supportedUserFound = true; // try next one } } if ($supportedUserFound) { - // @deprecated since Symfony 5.3, change to $user->getUserIdentifier() in 6.0 - $username = method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(); - $e = new UserNotFoundException(sprintf('There is no user with name "%s".', $username)); + $username = $user->getUserIdentifier(); + $e = new UserNotFoundException(\sprintf('There is no user with name "%s".', $username)); $e->setUserIdentifier($username); throw $e; - } else { - throw new UnsupportedUserException(sprintf('There is no user provider for user "%s". Shouldn\'t the "supportsClass()" method of your user provider return true for this classname?', get_debug_type($user))); } + + throw new UnsupportedUserException(\sprintf('There is no user provider for user "%s". Shouldn\'t the "supportsClass()" method of your user provider return true for this classname?', get_debug_type($user))); } - /** - * {@inheritdoc} - */ - public function supportsClass(string $class) + public function supportsClass(string $class): bool { foreach ($this->providers as $provider) { if ($provider->supportsClass($class)) { @@ -125,26 +101,13 @@ public function supportsClass(string $class) return false; } - /** - * @param PasswordAuthenticatedUserInterface $user - * - * {@inheritdoc} - */ - public function upgradePassword($user, string $newHashedPassword): void + public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void { - if (!$user instanceof PasswordAuthenticatedUserInterface) { - trigger_deprecation('symfony/security-core', '5.3', 'The "%s::upgradePassword()" method expects an instance of "%s" as first argument, the "%s" class should implement it.', PasswordUpgraderInterface::class, PasswordAuthenticatedUserInterface::class, get_debug_type($user)); - - if (!$user instanceof UserInterface) { - throw new \TypeError(sprintf('The "%s::upgradePassword()" method expects an instance of "%s" as first argument, "%s" given.', static::class, PasswordAuthenticatedUserInterface::class, get_debug_type($user))); - } - } - foreach ($this->providers as $provider) { if ($provider instanceof PasswordUpgraderInterface) { try { $provider->upgradePassword($user, $newHashedPassword); - } catch (UnsupportedUserException $e) { + } catch (UnsupportedUserException) { // ignore: password upgrades are opportunistic } } diff --git a/User/EquatableInterface.php b/User/EquatableInterface.php index 70408101..3fa9e488 100644 --- a/User/EquatableInterface.php +++ b/User/EquatableInterface.php @@ -25,8 +25,6 @@ interface EquatableInterface * * However, you do not need to compare every attribute, but only those that * are relevant for assessing whether re-authentication is required. - * - * @return bool */ - public function isEqualTo(UserInterface $user); + public function isEqualTo(UserInterface $user): bool; } diff --git a/User/InMemoryUser.php b/User/InMemoryUser.php index 39da71e3..b14bc077 100644 --- a/User/InMemoryUser.php +++ b/User/InMemoryUser.php @@ -19,58 +19,90 @@ * @author Robin Chalas * @author Fabien Potencier */ -final class InMemoryUser extends User +final class InMemoryUser implements UserInterface, PasswordAuthenticatedUserInterface, EquatableInterface, \Stringable { - /** - * {@inheritdoc} - * - * @deprecated since Symfony 5.3 - */ - public function isAccountNonExpired(): bool - { - trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, you should stop using it.', __METHOD__); + private string $username; + + public function __construct( + ?string $username, + private ?string $password, + private array $roles = [], + private bool $enabled = true, + ) { + if ('' === $username || null === $username) { + throw new \InvalidArgumentException('The username cannot be empty.'); + } - return parent::isAccountNonExpired(); + $this->username = $username; } - /** - * {@inheritdoc} - * - * @deprecated since Symfony 5.3 - */ - public function isAccountNonLocked(): bool + public function __toString(): string + { + return $this->getUserIdentifier(); + } + + public function getRoles(): array { - trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, you should stop using it.', __METHOD__); + return $this->roles; + } - return parent::isAccountNonLocked(); + public function getPassword(): ?string + { + return $this->password; } /** - * {@inheritdoc} - * - * @deprecated since Symfony 5.3 + * Returns the identifier for this user (e.g. its username or email address). */ - public function isCredentialsNonExpired(): bool + public function getUserIdentifier(): string { - trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, you should stop using it.', __METHOD__); - - return parent::isCredentialsNonExpired(); + return $this->username; } /** - * @deprecated since Symfony 5.3 + * Checks whether the user is enabled. + * + * Internally, if this method returns false, the authentication system + * will throw a DisabledException and prevent login. + * + * @return bool true if the user is enabled, false otherwise + * + * @see DisabledException */ - public function getExtraFields(): array + public function isEnabled(): bool { - trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, you should stop using it.', __METHOD__); + return $this->enabled; + } - return parent::getExtraFields(); + public function eraseCredentials(): void + { } - public function setPassword(string $password) + public function isEqualTo(UserInterface $user): bool { - trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, you should stop using it.', __METHOD__); + if (!$user instanceof self) { + return false; + } + + if ($this->getPassword() !== $user->getPassword()) { + return false; + } + + $currentRoles = array_map('strval', $this->getRoles()); + $newRoles = array_map('strval', $user->getRoles()); + $rolesChanged = \count($currentRoles) !== \count($newRoles) || \count($currentRoles) !== \count(array_intersect($currentRoles, $newRoles)); + if ($rolesChanged) { + return false; + } + + if ($this->getUserIdentifier() !== $user->getUserIdentifier()) { + return false; + } + + if ($this->isEnabled() !== $user->isEnabled()) { + return false; + } - parent::setPassword($password); + return true; } } diff --git a/User/InMemoryUserChecker.php b/User/InMemoryUserChecker.php index b35e6303..61367c2c 100644 --- a/User/InMemoryUserChecker.php +++ b/User/InMemoryUserChecker.php @@ -11,10 +11,7 @@ namespace Symfony\Component\Security\Core\User; -use Symfony\Component\Security\Core\Exception\AccountExpiredException; -use Symfony\Component\Security\Core\Exception\CredentialsExpiredException; use Symfony\Component\Security\Core\Exception\DisabledException; -use Symfony\Component\Security\Core\Exception\LockedException; /** * Checks the state of the in-memory user account. @@ -23,11 +20,9 @@ */ class InMemoryUserChecker implements UserCheckerInterface { - public function checkPreAuth(UserInterface $user) + public function checkPreAuth(UserInterface $user): void { - // @deprecated since Symfony 5.3, in 6.0 change to: - // if (!$user instanceof InMemoryUser) { - if (!$user instanceof InMemoryUser && !$user instanceof User) { + if (!$user instanceof InMemoryUser) { return; } @@ -36,38 +31,9 @@ public function checkPreAuth(UserInterface $user) $ex->setUser($user); throw $ex; } - - // @deprecated since Symfony 5.3 - if (User::class === \get_class($user)) { - if (!$user->isAccountNonLocked()) { - $ex = new LockedException('User account is locked.'); - $ex->setUser($user); - throw $ex; - } - - if (!$user->isAccountNonExpired()) { - $ex = new AccountExpiredException('User account has expired.'); - $ex->setUser($user); - throw $ex; - } - } } - public function checkPostAuth(UserInterface $user) + public function checkPostAuth(UserInterface $user): void { - // @deprecated since Symfony 5.3, noop in 6.0 - if (User::class !== \get_class($user)) { - return; - } - - if (!$user->isCredentialsNonExpired()) { - $ex = new CredentialsExpiredException('User credentials have expired.'); - $ex->setUser($user); - throw $ex; - } } } - -if (!class_exists(UserChecker::class, false)) { - class_alias(InMemoryUserChecker::class, UserChecker::class); -} diff --git a/User/InMemoryUserProvider.php b/User/InMemoryUserProvider.php index 7e923951..db6267a5 100644 --- a/User/InMemoryUserProvider.php +++ b/User/InMemoryUserProvider.php @@ -21,13 +21,15 @@ * (a backend with a unique admin for instance) * * @author Fabien Potencier + * + * @template-implements UserProviderInterface */ class InMemoryUserProvider implements UserProviderInterface { /** * @var array */ - private $users; + private array $users = []; /** * The user array is a hash where the keys are usernames and the values are @@ -49,13 +51,14 @@ public function __construct(array $users = []) /** * Adds a new User to the provider. - * - * @throws \LogicException */ - public function createUser(UserInterface $user) + public function createUser(UserInterface $user): void { - // @deprecated since Symfony 5.3, change to $user->getUserIdentifier() in 6.0 - $userIdentifier = strtolower(method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername()); + if (!$user instanceof InMemoryUser) { + throw new UnsupportedUserException(\sprintf('Instances of "%s" are not supported.', get_debug_type($user))); + } + + $userIdentifier = strtolower($user->getUserIdentifier()); if (isset($this->users[$userIdentifier])) { throw new \LogicException('Another user with the same username already exists.'); } @@ -63,77 +66,39 @@ public function createUser(UserInterface $user) $this->users[$userIdentifier] = $user; } - /** - * {@inheritdoc} - */ - public function loadUserByUsername(string $username) - { - trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use loadUserByIdentifier() instead.', __METHOD__); - - return $this->loadUserByIdentifier($username); - } - public function loadUserByIdentifier(string $identifier): UserInterface { $user = $this->getUser($identifier); - // @deprecated since Symfony 5.3, change to $user->getUserIdentifier() in 6.0 - return new InMemoryUser(method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(), $user->getPassword(), $user->getRoles(), $user->isEnabled()); + return new InMemoryUser($user->getUserIdentifier(), $user->getPassword(), $user->getRoles(), $user->isEnabled()); } - /** - * {@inheritdoc} - */ - public function refreshUser(UserInterface $user) + public function refreshUser(UserInterface $user): UserInterface { - if (!$user instanceof InMemoryUser && !$user instanceof User) { - throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_debug_type($user))); + if (!$user instanceof InMemoryUser) { + throw new UnsupportedUserException(\sprintf('Instances of "%s" are not supported.', get_debug_type($user))); } - // @deprecated since Symfony 5.3, change to $user->getUserIdentifier() in 6.0 - $storedUser = $this->getUser(method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername()); - $userIdentifier = method_exists($storedUser, 'getUserIdentifier') ? $storedUser->getUserIdentifier() : $storedUser->getUsername(); - - // @deprecated since Symfony 5.3 - if (User::class === \get_class($user)) { - if (User::class !== \get_class($storedUser)) { - $accountNonExpired = true; - $credentialsNonExpired = $storedUser->getPassword() === $user->getPassword(); - $accountNonLocked = true; - } else { - $accountNonExpired = $storedUser->isAccountNonExpired(); - $credentialsNonExpired = $storedUser->isCredentialsNonExpired() && $storedUser->getPassword() === $user->getPassword(); - $accountNonLocked = $storedUser->isAccountNonLocked(); - } - - return new User($userIdentifier, $storedUser->getPassword(), $storedUser->getRoles(), $storedUser->isEnabled(), $accountNonExpired, $credentialsNonExpired, $accountNonLocked); - } + $storedUser = $this->getUser($user->getUserIdentifier()); + $userIdentifier = $storedUser->getUserIdentifier(); return new InMemoryUser($userIdentifier, $storedUser->getPassword(), $storedUser->getRoles(), $storedUser->isEnabled()); } - /** - * {@inheritdoc} - */ - public function supportsClass(string $class) + public function supportsClass(string $class): bool { - // @deprecated since Symfony 5.3 - if (User::class === $class) { - return true; - } - return InMemoryUser::class == $class; } /** - * Returns the user by given username. + * Returns the user by given user. * * @throws UserNotFoundException if user whose given username does not exist */ - private function getUser(string $username)/* : InMemoryUser */ + private function getUser(string $username): InMemoryUser { if (!isset($this->users[strtolower($username)])) { - $ex = new UserNotFoundException(sprintf('Username "%s" does not exist.', $username)); + $ex = new UserNotFoundException(\sprintf('Username "%s" does not exist.', $username)); $ex->setUserIdentifier($username); throw $ex; diff --git a/User/MissingUserProvider.php b/User/MissingUserProvider.php index 02df0516..9869259a 100644 --- a/User/MissingUserProvider.php +++ b/User/MissingUserProvider.php @@ -18,6 +18,8 @@ * when a firewall requires a user provider but none was defined. * * @internal + * + * @template-implements UserProviderInterface */ class MissingUserProvider implements UserProviderInterface { @@ -26,12 +28,9 @@ class MissingUserProvider implements UserProviderInterface */ public function __construct(string $firewall) { - throw new InvalidConfigurationException(sprintf('"%s" firewall requires a user provider but none was defined.', $firewall)); + throw new InvalidConfigurationException(\sprintf('"%s" firewall requires a user provider but none was defined.', $firewall)); } - /** - * {@inheritdoc} - */ public function loadUserByUsername(string $username): UserInterface { throw new \BadMethodCallException(); @@ -42,17 +41,11 @@ public function loadUserByIdentifier(string $identifier): UserInterface throw new \BadMethodCallException(); } - /** - * {@inheritdoc} - */ public function refreshUser(UserInterface $user): UserInterface { throw new \BadMethodCallException(); } - /** - * {@inheritdoc} - */ public function supportsClass(string $class): bool { throw new \BadMethodCallException(); diff --git a/User/OidcUser.php b/User/OidcUser.php new file mode 100644 index 00000000..bcce363f --- /dev/null +++ b/User/OidcUser.php @@ -0,0 +1,182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\User; + +/** + * UserInterface implementation used by the access-token security workflow with an OIDC server. + */ +class OidcUser implements UserInterface +{ + private array $additionalClaims = []; + + public function __construct( + private ?string $userIdentifier = null, + private array $roles = ['ROLE_USER'], + + // Standard Claims (https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) + private ?string $sub = null, + private ?string $name = null, + private ?string $givenName = null, + private ?string $familyName = null, + private ?string $middleName = null, + private ?string $nickname = null, + private ?string $preferredUsername = null, + private ?string $profile = null, + private ?string $picture = null, + private ?string $website = null, + private ?string $email = null, + private ?bool $emailVerified = null, + private ?string $gender = null, + private ?string $birthdate = null, + private ?string $zoneinfo = null, + private ?string $locale = null, + private ?string $phoneNumber = null, + private ?bool $phoneNumberVerified = null, + private ?array $address = null, + private ?\DateTimeInterface $updatedAt = null, + + // Additional Claims (https://openid.net/specs/openid-connect-core-1_0.html#AdditionalClaims) + ...$additionalClaims, + ) { + if (null === $sub || '' === $sub) { + throw new \InvalidArgumentException('The "sub" claim cannot be empty.'); + } + + $this->additionalClaims = $additionalClaims['additionalClaims'] ?? $additionalClaims; + } + + /** + * OIDC or OAuth specs don't have any "role" notion. + * + * If you want to implement "roles" from your OIDC server, + * send a "roles" constructor argument to this object + * (e.g.: using a custom UserProvider). + */ + public function getRoles(): array + { + return $this->roles; + } + + public function getUserIdentifier(): string + { + return (string) ($this->userIdentifier ?? $this->getSub()); + } + + public function eraseCredentials(): void + { + } + + public function getSub(): ?string + { + return $this->sub; + } + + public function getName(): ?string + { + return $this->name; + } + + public function getGivenName(): ?string + { + return $this->givenName; + } + + public function getFamilyName(): ?string + { + return $this->familyName; + } + + public function getMiddleName(): ?string + { + return $this->middleName; + } + + public function getNickname(): ?string + { + return $this->nickname; + } + + public function getPreferredUsername(): ?string + { + return $this->preferredUsername; + } + + public function getProfile(): ?string + { + return $this->profile; + } + + public function getPicture(): ?string + { + return $this->picture; + } + + public function getWebsite(): ?string + { + return $this->website; + } + + public function getEmail(): ?string + { + return $this->email; + } + + public function getEmailVerified(): ?bool + { + return $this->emailVerified; + } + + public function getGender(): ?string + { + return $this->gender; + } + + public function getBirthdate(): ?string + { + return $this->birthdate; + } + + public function getZoneinfo(): ?string + { + return $this->zoneinfo; + } + + public function getLocale(): ?string + { + return $this->locale; + } + + public function getPhoneNumber(): ?string + { + return $this->phoneNumber; + } + + public function getphoneNumberVerified(): ?bool + { + return $this->phoneNumberVerified; + } + + public function getAddress(): ?array + { + return $this->address; + } + + public function getUpdatedAt(): ?\DateTimeInterface + { + return $this->updatedAt; + } + + public function getAdditionalClaims(): array + { + return $this->additionalClaims; + } +} diff --git a/User/PasswordUpgraderInterface.php b/User/PasswordUpgraderInterface.php index 16195fad..fd21f14a 100644 --- a/User/PasswordUpgraderInterface.php +++ b/User/PasswordUpgraderInterface.php @@ -13,12 +13,15 @@ /** * @author Nicolas Grekas - * - * @method void upgradePassword(PasswordAuthenticatedUserInterface|UserInterface $user, string $newHashedPassword) Upgrades the hashed password of a user, typically for using a better hash algorithm. - * This method should persist the new password in the user storage and update the $user object accordingly. - * Because you don't want your users not being able to log in, this method should be opportunistic: - * it's fine if it does nothing or if it fails without throwing any exception. */ interface PasswordUpgraderInterface { + /** + * Upgrades the hashed password of a user, typically for using a better hash algorithm. + * + * This method should persist the new password in the user storage and update the $user object accordingly. + * Because you don't want your users not being able to log in, this method should be opportunistic: + * it's fine if it does nothing or if it fails without throwing any exception. + */ + public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void; } diff --git a/User/User.php b/User/User.php deleted file mode 100644 index 28f3304e..00000000 --- a/User/User.php +++ /dev/null @@ -1,218 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\User; - -/** - * User is the user implementation used by the in-memory user provider. - * - * This should not be used for anything else. - * - * @author Fabien Potencier - * - * @deprecated since Symfony 5.3, use {@link InMemoryUser} instead - */ -class User implements UserInterface, PasswordAuthenticatedUserInterface, EquatableInterface -{ - private $username; - private $password; - private $enabled; - private $accountNonExpired; - private $credentialsNonExpired; - private $accountNonLocked; - private $roles; - private $extraFields; - - public function __construct(?string $username, ?string $password, array $roles = [], bool $enabled = true, bool $userNonExpired = true, bool $credentialsNonExpired = true, bool $userNonLocked = true, array $extraFields = []) - { - if (InMemoryUser::class !== static::class) { - trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', self::class, InMemoryUser::class); - } - - if ('' === $username || null === $username) { - throw new \InvalidArgumentException('The username cannot be empty.'); - } - - $this->username = $username; - $this->password = $password; - $this->enabled = $enabled; - $this->accountNonExpired = $userNonExpired; - $this->credentialsNonExpired = $credentialsNonExpired; - $this->accountNonLocked = $userNonLocked; - $this->roles = $roles; - $this->extraFields = $extraFields; - } - - public function __toString(): string - { - return $this->getUserIdentifier(); - } - - /** - * {@inheritdoc} - */ - public function getRoles(): array - { - return $this->roles; - } - - /** - * {@inheritdoc} - */ - public function getPassword(): ?string - { - return $this->password; - } - - /** - * {@inheritdoc} - */ - public function getSalt(): ?string - { - return null; - } - - /** - * {@inheritdoc} - */ - public function getUsername(): string - { - trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use getUserIdentifier() instead.', __METHOD__); - - return $this->username; - } - - /** - * Returns the identifier for this user (e.g. its username or email address). - */ - public function getUserIdentifier(): string - { - return $this->username; - } - - /** - * Checks whether the user's account has expired. - * - * Internally, if this method returns false, the authentication system - * will throw an AccountExpiredException and prevent login. - * - * @see AccountExpiredException - */ - public function isAccountNonExpired(): bool - { - return $this->accountNonExpired; - } - - /** - * Checks whether the user is locked. - * - * Internally, if this method returns false, the authentication system - * will throw a LockedException and prevent login. - * - * @see LockedException - */ - public function isAccountNonLocked(): bool - { - return $this->accountNonLocked; - } - - /** - * Checks whether the user's credentials (password) has expired. - * - * Internally, if this method returns false, the authentication system - * will throw a CredentialsExpiredException and prevent login. - * - * @see CredentialsExpiredException - */ - public function isCredentialsNonExpired(): bool - { - return $this->credentialsNonExpired; - } - - /** - * Checks whether the user is enabled. - * - * Internally, if this method returns false, the authentication system - * will throw a DisabledException and prevent login. - * - * @see DisabledException - */ - public function isEnabled(): bool - { - return $this->enabled; - } - - /** - * {@inheritdoc} - */ - public function eraseCredentials() - { - } - - public function getExtraFields(): array - { - return $this->extraFields; - } - - /** - * {@inheritdoc} - */ - public function isEqualTo(UserInterface $user): bool - { - if (!$user instanceof self) { - return false; - } - - if ($this->getPassword() !== $user->getPassword()) { - return false; - } - - if ($this->getSalt() !== $user->getSalt()) { - return false; - } - - $currentRoles = array_map('strval', (array) $this->getRoles()); - $newRoles = array_map('strval', (array) $user->getRoles()); - $rolesChanged = \count($currentRoles) !== \count($newRoles) || \count($currentRoles) !== \count(array_intersect($currentRoles, $newRoles)); - if ($rolesChanged) { - return false; - } - - if ($this->getUserIdentifier() !== $user->getUserIdentifier()) { - return false; - } - - if (self::class === static::class) { - if ($this->isAccountNonExpired() !== $user->isAccountNonExpired()) { - return false; - } - - if ($this->isAccountNonLocked() !== $user->isAccountNonLocked()) { - return false; - } - - if ($this->isCredentialsNonExpired() !== $user->isCredentialsNonExpired()) { - return false; - } - } - - if ($this->isEnabled() !== $user->isEnabled()) { - return false; - } - - return true; - } - - public function setPassword(string $password) - { - $this->password = $password; - } -} diff --git a/User/UserChecker.php b/User/UserChecker.php deleted file mode 100644 index 8ffecf17..00000000 --- a/User/UserChecker.php +++ /dev/null @@ -1,29 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\User; - -trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', UserChecker::class, InMemoryUserChecker::class); - -class_exists(InMemoryUserChecker::class); - -if (false) { - /** - * UserChecker checks the user account flags. - * - * @author Fabien Potencier - * - * @deprecated since Symfony 5.3, use {@link InMemoryUserChecker} instead - */ - class UserChecker - { - } -} diff --git a/User/UserCheckerInterface.php b/User/UserCheckerInterface.php index a7c5f179..2dc748aa 100644 --- a/User/UserCheckerInterface.php +++ b/User/UserCheckerInterface.php @@ -28,12 +28,12 @@ interface UserCheckerInterface * * @throws AccountStatusException */ - public function checkPreAuth(UserInterface $user); + public function checkPreAuth(UserInterface $user): void; /** * Checks the user account after authentication. * * @throws AccountStatusException */ - public function checkPostAuth(UserInterface $user); + public function checkPostAuth(UserInterface $user /*, TokenInterface $token*/): void; } diff --git a/User/UserInterface.php b/User/UserInterface.php index 806de8ce..e6078399 100644 --- a/User/UserInterface.php +++ b/User/UserInterface.php @@ -26,8 +26,6 @@ * * @see UserProviderInterface * - * @method string getUserIdentifier() returns the identifier for this user (e.g. its username or email address) - * * @author Fabien Potencier */ interface UserInterface @@ -46,30 +44,7 @@ interface UserInterface * * @return string[] */ - public function getRoles(); - - /** - * Returns the password used to authenticate the user. - * - * This should be the hashed password. On authentication, a plain-text - * password will be hashed, and then compared to this value. - * - * This method is deprecated since Symfony 5.3, implement it from {@link PasswordAuthenticatedUserInterface} instead. - * - * @return string|null - */ - public function getPassword(); - - /** - * Returns the salt that was originally used to hash the password. - * - * This can return null if the password was not hashed using a salt. - * - * This method is deprecated since Symfony 5.3, implement it from {@link LegacyPasswordAuthenticatedUserInterface} instead. - * - * @return string|null - */ - public function getSalt(); + public function getRoles(): array; /** * Removes sensitive data from the user. @@ -77,12 +52,12 @@ public function getSalt(); * This is important if, at any given point, sensitive information like * the plain-text password is stored on this object. */ - public function eraseCredentials(); + public function eraseCredentials(): void; /** - * @return string + * Returns the identifier for this user (e.g. username or email address). * - * @deprecated since Symfony 5.3, use getUserIdentifier() instead + * @return non-empty-string */ - public function getUsername(); + public function getUserIdentifier(): string; } diff --git a/User/UserProviderInterface.php b/User/UserProviderInterface.php index 52d24f99..9a5e6762 100644 --- a/User/UserProviderInterface.php +++ b/User/UserProviderInterface.php @@ -27,12 +27,9 @@ * configuration, web service). This is totally independent of how the authentication * information is submitted or what the UserInterface object looks like. * - * @see UserInterface - * - * @method UserInterface loadUserByIdentifier(string $identifier) loads the user for the given user identifier (e.g. username or email). - * This method must throw UserNotFoundException if the user is not found. - * * @author Fabien Potencier + * + * @template-covariant TUser of UserInterface */ interface UserProviderInterface { @@ -44,26 +41,26 @@ interface UserProviderInterface * object can just be merged into some internal array of users / identity * map. * - * @return UserInterface + * @psalm-return TUser * * @throws UnsupportedUserException if the user is not supported * @throws UserNotFoundException if the user is not found */ - public function refreshUser(UserInterface $user); + public function refreshUser(UserInterface $user): UserInterface; /** * Whether this provider supports the given user class. - * - * @return bool */ - public function supportsClass(string $class); + public function supportsClass(string $class): bool; /** - * @return UserInterface + * Loads the user for the given user identifier (e.g. username or email). * - * @throws UserNotFoundException + * This method must throw UserNotFoundException if the user is not found. + * + * @return TUser * - * @deprecated since Symfony 5.3, use loadUserByIdentifier() instead + * @throws UserNotFoundException */ - public function loadUserByUsername(string $username); + public function loadUserByIdentifier(string $identifier): UserInterface; } diff --git a/Validator/Constraints/UserPassword.php b/Validator/Constraints/UserPassword.php index 0bd47065..e6741a48 100644 --- a/Validator/Constraints/UserPassword.php +++ b/Validator/Constraints/UserPassword.php @@ -13,17 +13,19 @@ use Symfony\Component\Validator\Constraint; -/** - * @Annotation - * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) - */ #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class UserPassword extends Constraint { - public $message = 'This value should be the user\'s current password.'; - public $service = 'security.validator.user_password'; + public const INVALID_PASSWORD_ERROR = '2d2a8bb4-ddc8-45e4-9b0f-8670d3a3e290'; + + protected const ERROR_NAMES = [ + self::INVALID_PASSWORD_ERROR => 'INVALID_PASSWORD_ERROR', + ]; + + public string $message = 'This value should be the user\'s current password.'; + public string $service = 'security.validator.user_password'; - public function __construct(?array $options = null, ?string $message = null, ?string $service = null, ?array $groups = null, $payload = null) + public function __construct(?array $options = null, ?string $message = null, ?string $service = null, ?array $groups = null, mixed $payload = null) { parent::__construct($options, $groups, $payload); @@ -31,10 +33,7 @@ public function __construct(?array $options = null, ?string $message = null, ?st $this->service = $service ?? $this->service; } - /** - * {@inheritdoc} - */ - public function validatedBy() + public function validatedBy(): string { return $this->service; } diff --git a/Validator/Constraints/UserPasswordValidator.php b/Validator/Constraints/UserPasswordValidator.php index 688426ae..04c7e1ff 100644 --- a/Validator/Constraints/UserPasswordValidator.php +++ b/Validator/Constraints/UserPasswordValidator.php @@ -13,11 +13,8 @@ use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; -use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; -use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface; use Symfony\Component\Security\Core\User\LegacyPasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; -use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\ConstraintDefinitionException; @@ -25,33 +22,22 @@ class UserPasswordValidator extends ConstraintValidator { - private $tokenStorage; - private $hasherFactory; - - /** - * @param PasswordHasherFactoryInterface $hasherFactory - */ - public function __construct(TokenStorageInterface $tokenStorage, $hasherFactory) - { - if ($hasherFactory instanceof EncoderFactoryInterface) { - trigger_deprecation('symfony/security-core', '5.3', 'Passing a "%s" instance to the "%s" constructor is deprecated, use "%s" instead.', EncoderFactoryInterface::class, __CLASS__, PasswordHasherFactoryInterface::class); - } - - $this->tokenStorage = $tokenStorage; - $this->hasherFactory = $hasherFactory; + public function __construct( + private TokenStorageInterface $tokenStorage, + private PasswordHasherFactoryInterface $hasherFactory, + ) { } - /** - * {@inheritdoc} - */ - public function validate($password, Constraint $constraint) + public function validate(mixed $password, Constraint $constraint): void { if (!$constraint instanceof UserPassword) { throw new UnexpectedTypeException($constraint, UserPassword::class); } if (null === $password || '' === $password) { - $this->context->addViolation($constraint->message); + $this->context->buildViolation($constraint->message) + ->setCode(UserPassword::INVALID_PASSWORD_ERROR) + ->addViolation(); return; } @@ -62,23 +48,16 @@ public function validate($password, Constraint $constraint) $user = $this->tokenStorage->getToken()->getUser(); - if (!$user instanceof UserInterface) { - throw new ConstraintDefinitionException('The User object must implement the UserInterface interface.'); - } - if (!$user instanceof PasswordAuthenticatedUserInterface) { - trigger_deprecation('symfony/security-core', '5.3', 'Using the "%s" validation constraint without implementing the "%s" interface is deprecated, the "%s" class should implement it.', UserPassword::class, PasswordAuthenticatedUserInterface::class, get_debug_type($user)); - } - - $salt = $user->getSalt(); - if ($salt && !$user instanceof LegacyPasswordAuthenticatedUserInterface) { - trigger_deprecation('symfony/security-core', '5.3', 'Returning a string from "getSalt()" without implementing the "%s" interface is deprecated, the "%s" class should implement it.', LegacyPasswordAuthenticatedUserInterface::class, get_debug_type($user)); + throw new ConstraintDefinitionException(\sprintf('The "%s" class must implement the "%s" interface.', get_debug_type($user), PasswordAuthenticatedUserInterface::class)); } - $hasher = $this->hasherFactory instanceof EncoderFactoryInterface ? $this->hasherFactory->getEncoder($user) : $this->hasherFactory->getPasswordHasher($user); + $hasher = $this->hasherFactory->getPasswordHasher($user); - if (null === $user->getPassword() || !($hasher instanceof PasswordEncoderInterface ? $hasher->isPasswordValid($user->getPassword(), $password, $user->getSalt()) : $hasher->verify($user->getPassword(), $password, $user->getSalt()))) { - $this->context->addViolation($constraint->message); + if (null === $user->getPassword() || !$hasher->verify($user->getPassword(), $password, $user instanceof LegacyPasswordAuthenticatedUserInterface ? $user->getSalt() : null)) { + $this->context->buildViolation($constraint->message) + ->setCode(UserPassword::INVALID_PASSWORD_ERROR) + ->addViolation(); } } } diff --git a/composer.json b/composer.json index 3a700ac9..0aaff1e3 100644 --- a/composer.json +++ b/composer.json @@ -16,40 +16,33 @@ } ], "require": { - "php": ">=7.2.5", - "symfony/event-dispatcher-contracts": "^1.1|^2|^3", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1.6|^2|^3", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/password-hasher": "^5.3|^6.0" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/event-dispatcher-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3", + "symfony/password-hasher": "^6.4|^7.0" }, "require-dev": { - "psr/container": "^1.0|^2.0", + "psr/container": "^1.1|^2.0", "psr/cache": "^1.0|^2.0|^3.0", - "symfony/cache": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^5.3|^6.0", - "symfony/ldap": "^4.4|^5.0|^6.0", - "symfony/translation": "^5.4.35|~6.3.12|^6.4.3", - "symfony/validator": "^5.2|^6.0", + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/ldap": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0", + "symfony/translation": "^6.4.3|^7.0.3", + "symfony/validator": "^6.4|^7.0", "psr/log": "^1|^2|^3" }, "conflict": { - "symfony/event-dispatcher": "<4.4", - "symfony/http-foundation": "<5.3", - "symfony/security-guard": "<4.4", - "symfony/ldap": "<4.4", - "symfony/translation": "<5.4.35|>=6.0,<6.3.12|>=6.4,<6.4.3", - "symfony/validator": "<5.2" - }, - "suggest": { - "psr/container-implementation": "To instantiate the Security class", - "symfony/event-dispatcher": "", - "symfony/http-foundation": "", - "symfony/validator": "For using the user password constraint", - "symfony/expression-language": "For using the expression voter", - "symfony/ldap": "For using LDAP integration" + "symfony/dependency-injection": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/ldap": "<6.4", + "symfony/translation": "<6.4.3|>=7.0,<7.0.3", + "symfony/validator": "<6.4" }, "autoload": { "psr-4": { "Symfony\\Component\\Security\\Core\\": "" }, diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 5b56c184..223091f3 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,7 +1,7 @@ - - + + ./ - - ./Resources - ./Tests - ./vendor - - - + + + ./Resources + ./Tests + ./vendor + +