diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/EntryPointFactoryInterface.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/EntryPointFactoryInterface.php new file mode 100644 index 0000000000..804399ad51 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/EntryPointFactoryInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * @author Wouter de Jong + */ +interface EntryPointFactoryInterface +{ + /** + * Creates the entry point and returns the service ID. + */ + public function createEntryPoint(ContainerBuilder $container, string $id, array $config, ?string $defaultEntryPointId): string; +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php index 2a773b34ad..386ba8e462 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php @@ -22,7 +22,7 @@ use Symfony\Component\DependencyInjection\Reference; * @author Fabien Potencier * @author Johannes M. Schmitt */ -class FormLoginFactory extends AbstractFactory implements GuardFactoryInterface +class FormLoginFactory extends AbstractFactory implements GuardFactoryInterface, EntryPointFactoryInterface { public function __construct() { @@ -84,7 +84,7 @@ class FormLoginFactory extends AbstractFactory implements GuardFactoryInterface return $listenerId; } - protected function createEntryPoint(ContainerBuilder $container, string $id, array $config, ?string $defaultEntryPoint) + public function createEntryPoint(ContainerBuilder $container, string $id, array $config, ?string $defaultEntryPoint): string { $entryPointId = 'security.authentication.form_entry_point.'.$id; $container @@ -105,7 +105,8 @@ class FormLoginFactory extends AbstractFactory implements GuardFactoryInterface $container ->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.form_login')) ->replaceArgument(1, isset($config['csrf_token_generator']) ? new Reference($config['csrf_token_generator']) : null) - ->replaceArgument(3, $options); + ->replaceArgument(2, new Reference($userProviderId)) + ->replaceArgument(4, $options); return $authenticatorId; } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 94450d2461..54403cfa4a 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\SecurityBundle\DependencyInjection; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\EntryPointFactoryInterface; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\GuardFactoryInterface; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RememberMeFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface; @@ -516,6 +517,10 @@ class SecurityExtension extends Extension implements PrependExtensionInterface } else { $authenticationProviders[$id.'_'.$key] = $authenticators; } + + if ($factory instanceof EntryPointFactoryInterface) { + $defaultEntryPoint = $factory->createEntryPoint($container, $id, $firewall[$key], $defaultEntryPoint); + } } else { list($provider, $listenerId, $defaultEntryPoint) = $factory->create($container, $id, $firewall[$key], $userProvider, $defaultEntryPoint); diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/authenticators.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/authenticators.xml index f9268c380e..9da2d3b8a5 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/authenticators.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/authenticators.xml @@ -33,6 +33,7 @@ abstract="true"> + user provider options diff --git a/src/Symfony/Component/Security/Core/Authentication/Authenticator/AbstractAuthenticator.php b/src/Symfony/Component/Security/Core/Authentication/Authenticator/AbstractAuthenticator.php new file mode 100644 index 0000000000..8e9bee6f07 --- /dev/null +++ b/src/Symfony/Component/Security/Core/Authentication/Authenticator/AbstractAuthenticator.php @@ -0,0 +1,35 @@ + + * + * 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\Authenticator; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken; + +/** + * An optional base class that creates the necessary tokens for you. + * + * @author Ryan Weaver + */ +abstract class AbstractAuthenticator implements AuthenticatorInterface +{ + /** + * Shortcut to create a PostAuthenticationGuardToken for you, if you don't really + * care about which authenticated token you're using. + * + * @return PostAuthenticationGuardToken + */ + public function createAuthenticatedToken(UserInterface $user, string $providerKey): TokenInterface + { + return new PostAuthenticationGuardToken($user, $providerKey, $user->getRoles()); + } +} diff --git a/src/Symfony/Component/Security/Core/Authentication/Authenticator/AbstractFormLoginAuthenticator.php b/src/Symfony/Component/Security/Core/Authentication/Authenticator/AbstractFormLoginAuthenticator.php new file mode 100644 index 0000000000..1f4b3352e7 --- /dev/null +++ b/src/Symfony/Component/Security/Core/Authentication/Authenticator/AbstractFormLoginAuthenticator.php @@ -0,0 +1,62 @@ + + * + * 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\Authenticator; + +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\Security; +use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; + +/** + * A base class to make form login authentication easier! + * + * @author Ryan Weaver + */ +abstract class AbstractFormLoginAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface +{ + /** + * Return the URL to the login page. + */ + abstract protected function getLoginUrl(): string; + + /** + * Override to change what happens after a bad username/password is submitted. + */ + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response + { + if ($request->hasSession()) { + $request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception); + } + + $url = $this->getLoginUrl(); + + return new RedirectResponse($url); + } + + public function supportsRememberMe(): bool + { + return true; + } + + /** + * Override to control what happens when the user hits a secure page + * but isn't logged in yet. + */ + public function start(Request $request, AuthenticationException $authException = null): Response + { + $url = $this->getLoginUrl(); + + return new RedirectResponse($url); + } +} diff --git a/src/Symfony/Component/Security/Core/Authentication/Authenticator/AnonymousAuthenticator.php b/src/Symfony/Component/Security/Core/Authentication/Authenticator/AnonymousAuthenticator.php index e173792dba..78c80800aa 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Authenticator/AnonymousAuthenticator.php +++ b/src/Symfony/Component/Security/Core/Authentication/Authenticator/AnonymousAuthenticator.php @@ -1,5 +1,14 @@ + * + * 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\Authenticator; use Symfony\Component\HttpFoundation\Request; @@ -9,9 +18,6 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\User\User; use Symfony\Component\Security\Core\User\UserInterface; -use Symfony\Component\Security\Core\User\UserProviderInterface; -use Symfony\Component\Security\Guard\AuthenticatorInterface; -use Symfony\Component\Security\Guard\Token\GuardTokenInterface; /** * @author Wouter de Jong @@ -25,11 +31,6 @@ class AnonymousAuthenticator implements AuthenticatorInterface $this->secret = $secret; } - public function start(Request $request, AuthenticationException $authException = null) - { - return new Response(null, Response::HTTP_UNAUTHORIZED); - } - public function supports(Request $request): ?bool { return true; @@ -40,27 +41,29 @@ class AnonymousAuthenticator implements AuthenticatorInterface return []; } - public function getUser($credentials, UserProviderInterface $userProvider) + public function getUser($credentials): ?UserInterface { return new User('anon.', null); } - public function checkCredentials($credentials, UserInterface $user) + public function checkCredentials($credentials, UserInterface $user): bool { return true; } - public function createAuthenticatedToken(UserInterface $user, string $providerKey) + public function createAuthenticatedToken(UserInterface $user, string $providerKey): TokenInterface { return new AnonymousToken($this->secret, 'anon.', []); } - public function onAuthenticationFailure(Request $request, AuthenticationException $exception) + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response { + return null; } - public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey) + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?Response { + return null; } public function supportsRememberMe(): bool diff --git a/src/Symfony/Component/Security/Core/Authentication/Authenticator/AuthenticatorInterface.php b/src/Symfony/Component/Security/Core/Authentication/Authenticator/AuthenticatorInterface.php new file mode 100644 index 0000000000..c4a9965381 --- /dev/null +++ b/src/Symfony/Component/Security/Core/Authentication/Authenticator/AuthenticatorInterface.php @@ -0,0 +1,129 @@ + + * + * 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\Authenticator; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * The interface for all authenticators. + * + * @author Ryan Weaver + * @author Amaury Leroux de Lens + * @author Wouter de Jong + */ +interface AuthenticatorInterface +{ + /** + * Does the authenticator support the given Request? + * + * If this returns false, the authenticator will be skipped. + */ + public function supports(Request $request): ?bool; + + /** + * Get the authentication credentials from the request and return them + * as any type (e.g. an associate array). + * + * Whatever value you return here will be passed to getUser() and checkCredentials() + * + * For example, for a form login, you might: + * + * return [ + * 'username' => $request->request->get('_username'), + * 'password' => $request->request->get('_password'), + * ]; + * + * Or for an API token that's on a header, you might use: + * + * return ['api_key' => $request->headers->get('X-API-TOKEN')]; + * + * @return mixed Any non-null value + * + * @throws \UnexpectedValueException If null is returned + */ + public function getCredentials(Request $request); + + /** + * Return a UserInterface object based on the credentials. + * + * You may throw an AuthenticationException if you wish. If you return + * null, then a UsernameNotFoundException is thrown for you. + * + * @param mixed $credentials the value returned from getCredentials() + * + * @throws AuthenticationException + */ + public function getUser($credentials): ?UserInterface; + + /** + * Returns true if the credentials are valid. + * + * If false is returned, authentication will fail. You may also throw + * an AuthenticationException if you wish to cause authentication to fail. + * + * @param mixed $credentials the value returned from getCredentials() + * + * @throws AuthenticationException + */ + public function checkCredentials($credentials, UserInterface $user): bool; + + /** + * Create an authenticated token for the given user. + * + * If you don't care about which token class is used or don't really + * understand what a "token" is, you can skip this method by extending + * the AbstractAuthenticator class from your authenticator. + * + * @see AbstractAuthenticator + */ + public function createAuthenticatedToken(UserInterface $user, string $providerKey): TokenInterface; + + /** + * Called when authentication executed, but failed (e.g. wrong username password). + * + * This should return the Response sent back to the user, like a + * RedirectResponse to the login page or a 403 response. + * + * If you return null, the request will continue, but the user will + * not be authenticated. This is probably not what you want to do. + */ + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response; + + /** + * Called when authentication executed and was successful! + * + * This should return the Response sent back to the user, like a + * RedirectResponse to the last page they visited. + * + * If you return null, the current request will continue, and the user + * will be authenticated. This makes sense, for example, with an API. + */ + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?Response; + + /** + * Does this method support remember me cookies? + * + * Remember me cookie will be set if *all* of the following are met: + * A) This method returns true + * B) The remember_me key under your firewall is configured + * C) The "remember me" functionality is activated. This is usually + * done by having a _remember_me checkbox in your form, but + * can be configured by the "always_remember_me" and "remember_me_parameter" + * parameters under the "remember_me" firewall key + * D) The onAuthenticationSuccess method returns a Response object + */ + public function supportsRememberMe(): bool; +} diff --git a/src/Symfony/Component/Security/Core/Authentication/Authenticator/FormLoginAuthenticator.php b/src/Symfony/Component/Security/Core/Authentication/Authenticator/FormLoginAuthenticator.php index 72e2bc5ff1..06f400242c 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Authenticator/FormLoginAuthenticator.php +++ b/src/Symfony/Component/Security/Core/Authentication/Authenticator/FormLoginAuthenticator.php @@ -1,5 +1,14 @@ + * + * 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\Authenticator; use Symfony\Component\HttpFoundation\Request; @@ -7,7 +16,6 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; -use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; use Symfony\Component\Security\Core\Security; @@ -15,7 +23,6 @@ use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; -use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator; use Symfony\Component\Security\Http\HttpUtils; use Symfony\Component\Security\Http\ParameterBagUtils; use Symfony\Component\Security\Http\Util\TargetPathTrait; @@ -25,16 +32,17 @@ use Symfony\Component\Security\Http\Util\TargetPathTrait; */ class FormLoginAuthenticator extends AbstractFormLoginAuthenticator { - use TargetPathTrait, UsernamePasswordTrait, UserProviderTrait { + use TargetPathTrait, UsernamePasswordTrait { UsernamePasswordTrait::checkCredentials as checkPassword; } private $options; private $httpUtils; private $csrfTokenManager; + private $userProvider; private $encoderFactory; - public function __construct(HttpUtils $httpUtils, ?CsrfTokenManagerInterface $csrfTokenManager, EncoderFactoryInterface $encoderFactory, array $options) + public function __construct(HttpUtils $httpUtils, ?CsrfTokenManagerInterface $csrfTokenManager, UserProviderInterface $userProvider, EncoderFactoryInterface $encoderFactory, array $options) { $this->httpUtils = $httpUtils; $this->csrfTokenManager = $csrfTokenManager; @@ -52,6 +60,7 @@ class FormLoginAuthenticator extends AbstractFormLoginAuthenticator 'target_path_parameter' => '_target_path', 'use_referer' => false, ], $options); + $this->userProvider = $userProvider; } protected function getLoginUrl(): string @@ -91,11 +100,16 @@ class FormLoginAuthenticator extends AbstractFormLoginAuthenticator throw new BadCredentialsException('Invalid username.'); } - $request->getSession()->set(Security::LAST_USERNAME, $username); + $request->getSession()->set(Security::LAST_USERNAME, $credentials['username']); return $credentials; } + public function getUser($credentials): ?UserInterface + { + return $this->userProvider->loadUserByUsername($credentials['username']); + } + public function checkCredentials($credentials, UserInterface $user): bool { if (null !== $this->csrfTokenManager) { @@ -107,7 +121,7 @@ class FormLoginAuthenticator extends AbstractFormLoginAuthenticator return $this->checkPassword($credentials, $user); } - public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey) + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): Response { return $this->httpUtils->createRedirectResponse($request, $this->determineTargetUrl($request, $providerKey)); } diff --git a/src/Symfony/Component/Security/Core/Authentication/Authenticator/HttpBasicAuthenticator.php b/src/Symfony/Component/Security/Core/Authentication/Authenticator/HttpBasicAuthenticator.php index 9ba11d0ddb..78e6d91cc2 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Authenticator/HttpBasicAuthenticator.php +++ b/src/Symfony/Component/Security/Core/Authentication/Authenticator/HttpBasicAuthenticator.php @@ -19,16 +19,14 @@ use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; -use Symfony\Component\Security\Guard\AuthenticatorInterface; +use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; /** * @author Wouter de Jong */ -class HttpBasicAuthenticator implements AuthenticatorInterface +class HttpBasicAuthenticator implements AuthenticatorInterface, AuthenticationEntryPointInterface { - use UserProviderTrait, UsernamePasswordTrait { - UserProviderTrait::getUser as getUserTrait; - } + use UsernamePasswordTrait; private $realmName; private $userProvider; @@ -52,16 +50,11 @@ class HttpBasicAuthenticator implements AuthenticatorInterface return $response; } - public function supports(Request $request): bool + public function supports(Request $request): ?bool { return $request->headers->has('PHP_AUTH_USER'); } - public function getUser($credentials, UserProviderInterface $userProvider): ?UserInterface - { - return $this->getUserTrait($credentials, $this->userProvider); - } - public function getCredentials(Request $request) { return [ @@ -70,6 +63,11 @@ class HttpBasicAuthenticator implements AuthenticatorInterface ]; } + public function getUser($credentials): ?UserInterface + { + return $this->userProvider->loadUserByUsername($credentials['username']); + } + public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): ?Response { return null; diff --git a/src/Symfony/Component/Security/Core/Authentication/Authenticator/UserProviderTrait.php b/src/Symfony/Component/Security/Core/Authentication/Authenticator/UserProviderTrait.php deleted file mode 100644 index b0bad3844e..0000000000 --- a/src/Symfony/Component/Security/Core/Authentication/Authenticator/UserProviderTrait.php +++ /dev/null @@ -1,26 +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\Authenticator; - -use Symfony\Component\Security\Core\User\UserInterface; -use Symfony\Component\Security\Core\User\UserProviderInterface; - -/** - * @author Wouter de Jong - */ -trait UserProviderTrait -{ - public function getUser($credentials, UserProviderInterface $userProvider): ?UserInterface - { - return $userProvider->loadUserByUsername($credentials['username']); - } -} diff --git a/src/Symfony/Component/Security/Core/Authentication/Authenticator/UsernamePasswordTrait.php b/src/Symfony/Component/Security/Core/Authentication/Authenticator/UsernamePasswordTrait.php index e791d52405..05f340a68f 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Authenticator/UsernamePasswordTrait.php +++ b/src/Symfony/Component/Security/Core/Authentication/Authenticator/UsernamePasswordTrait.php @@ -11,11 +11,11 @@ namespace Symfony\Component\Security\Core\Authentication\Authenticator; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Core\User\UserInterface; -use Symfony\Component\Security\Guard\Token\GuardTokenInterface; /** * @author Wouter de Jong @@ -41,7 +41,7 @@ trait UsernamePasswordTrait return true; } - public function createAuthenticatedToken(UserInterface $user, $providerKey): GuardTokenInterface + public function createAuthenticatedToken(UserInterface $user, $providerKey): TokenInterface { return new UsernamePasswordToken($user, null, $providerKey, $user->getRoles()); } diff --git a/src/Symfony/Component/Security/Core/Authentication/GuardAuthenticationManager.php b/src/Symfony/Component/Security/Core/Authentication/GuardAuthenticationManager.php index 624b0a678c..68b542af97 100644 --- a/src/Symfony/Component/Security/Core/Authentication/GuardAuthenticationManager.php +++ b/src/Symfony/Component/Security/Core/Authentication/GuardAuthenticationManager.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Security\Core\Authentication; +use Symfony\Component\Security\Core\Authentication\Authenticator\AuthenticatorInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\AuthenticationEvents; use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent; @@ -19,7 +20,6 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\AuthenticationExpiredException; use Symfony\Component\Security\Core\Exception\ProviderNotFoundException; use Symfony\Component\Security\Core\User\UserCheckerInterface; -use Symfony\Component\Security\Guard\AuthenticatorInterface; use Symfony\Component\Security\Guard\Provider\GuardAuthenticationProviderTrait; use Symfony\Component\Security\Guard\Token\GuardTokenInterface; use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken; diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/UsernamePasswordToken.php b/src/Symfony/Component/Security/Core/Authentication/Token/UsernamePasswordToken.php index b751bde7f1..b9eaa68246 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Token/UsernamePasswordToken.php +++ b/src/Symfony/Component/Security/Core/Authentication/Token/UsernamePasswordToken.php @@ -12,14 +12,13 @@ namespace Symfony\Component\Security\Core\Authentication\Token; use Symfony\Component\Security\Core\User\UserInterface; -use Symfony\Component\Security\Guard\Token\GuardTokenInterface; /** * UsernamePasswordToken implements a username and password token. * * @author Fabien Potencier */ -class UsernamePasswordToken extends AbstractToken implements GuardTokenInterface +class UsernamePasswordToken extends AbstractToken { private $credentials; private $providerKey; diff --git a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php index 35c4bda103..d30a95fdd7 100644 --- a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php +++ b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php @@ -13,14 +13,10 @@ namespace Symfony\Component\Security\Guard\Firewall; use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Guard\AuthenticatorInterface; use Symfony\Component\Security\Guard\GuardAuthenticatorHandler; -use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken; use Symfony\Component\Security\Http\Firewall\AbstractListener; use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface; diff --git a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticatorListenerTrait.php b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticatorListenerTrait.php index 043c51c7a8..245f02c906 100644 --- a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticatorListenerTrait.php +++ b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticatorListenerTrait.php @@ -1,10 +1,20 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Security\Guard\Firewall; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\Security\Core\Authentication\Authenticator\AuthenticatorInterface as CoreAuthenticatorInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Guard\AuthenticatorInterface; @@ -37,7 +47,7 @@ trait GuardAuthenticatorListenerTrait } /** - * @param AuthenticatorInterface[] $guardAuthenticators + * @param (CoreAuthenticatorInterface|AuthenticatorInterface)[] $guardAuthenticators */ protected function executeGuardAuthenticators(array $guardAuthenticators, RequestEvent $event): void { @@ -56,8 +66,15 @@ trait GuardAuthenticatorListenerTrait } } - private function executeGuardAuthenticator(string $uniqueGuardKey, AuthenticatorInterface $guardAuthenticator, RequestEvent $event) + /** + * @param CoreAuthenticatorInterface|AuthenticatorInterface $guardAuthenticator + */ + private function executeGuardAuthenticator(string $uniqueGuardKey, $guardAuthenticator, RequestEvent $event) { + if (!$guardAuthenticator instanceof AuthenticatorInterface && !$guardAuthenticator instanceof CoreAuthenticatorInterface) { + throw new \UnexpectedValueException('Invalid guard authenticator passed to '.__METHOD__.'. Expected AuthenticatorInterface of either Security Core or Security Guard.'); + } + $request = $event->getRequest(); try { if (null !== $this->logger) { @@ -124,9 +141,15 @@ trait GuardAuthenticatorListenerTrait /** * Checks to see if remember me is supported in the authenticator and * on the firewall. If it is, the RememberMeServicesInterface is notified. + * + * @param CoreAuthenticatorInterface|AuthenticatorInterface $guardAuthenticator */ - private function triggerRememberMe(AuthenticatorInterface $guardAuthenticator, Request $request, TokenInterface $token, Response $response = null) + private function triggerRememberMe($guardAuthenticator, Request $request, TokenInterface $token, Response $response = null) { + if (!$guardAuthenticator instanceof AuthenticatorInterface && !$guardAuthenticator instanceof CoreAuthenticatorInterface) { + throw new \UnexpectedValueException('Invalid guard authenticator passed to '.__METHOD__.'. Expected AuthenticatorInterface of either Security Core or Security Guard.'); + } + if (null === $this->rememberMeServices) { if (null !== $this->logger) { $this->logger->debug('Remember me skipped: it is not configured for the firewall.', ['authenticator' => \get_class($guardAuthenticator)]); diff --git a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php index 11f207a9ab..d2c0d298d2 100644 --- a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php +++ b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Security\Guard; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Authenticator\AuthenticatorInterface as CoreAuthenticatorInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; @@ -65,9 +66,15 @@ class GuardAuthenticatorHandler /** * Returns the "on success" response for the given GuardAuthenticator. + * + * @param CoreAuthenticatorInterface|AuthenticatorInterface $guardAuthenticator */ - public function handleAuthenticationSuccess(TokenInterface $token, Request $request, AuthenticatorInterface $guardAuthenticator, string $providerKey): ?Response + public function handleAuthenticationSuccess(TokenInterface $token, Request $request, $guardAuthenticator, string $providerKey): ?Response { + if (!$guardAuthenticator instanceof AuthenticatorInterface && !$guardAuthenticator instanceof CoreAuthenticatorInterface) { + throw new \UnexpectedValueException('Invalid guard authenticator passed to '.__METHOD__.'. Expected AuthenticatorInterface of either Security Core or Security Guard.'); + } + $response = $guardAuthenticator->onAuthenticationSuccess($request, $token, $providerKey); // check that it's a Response or null @@ -81,9 +88,15 @@ class GuardAuthenticatorHandler /** * Convenience method for authenticating the user and returning the * Response *if any* for success. + * + * @param CoreAuthenticatorInterface|AuthenticatorInterface $authenticator */ - public function authenticateUserAndHandleSuccess(UserInterface $user, Request $request, AuthenticatorInterface $authenticator, string $providerKey): ?Response + public function authenticateUserAndHandleSuccess(UserInterface $user, Request $request, $authenticator, string $providerKey): ?Response { + if (!$authenticator instanceof AuthenticatorInterface && !$authenticator instanceof CoreAuthenticatorInterface) { + throw new \UnexpectedValueException('Invalid guard authenticator passed to '.__METHOD__.'. Expected AuthenticatorInterface of either Security Core or Security Guard.'); + } + // create an authenticated token for the User $token = $authenticator->createAuthenticatedToken($user, $providerKey); // authenticate this in the system @@ -96,9 +109,15 @@ class GuardAuthenticatorHandler /** * Handles an authentication failure and returns the Response for the * GuardAuthenticator. + * + * @param CoreAuthenticatorInterface|AuthenticatorInterface $guardAuthenticator */ - public function handleAuthenticationFailure(AuthenticationException $authenticationException, Request $request, AuthenticatorInterface $guardAuthenticator, string $providerKey): ?Response + public function handleAuthenticationFailure(AuthenticationException $authenticationException, Request $request, $guardAuthenticator, string $providerKey): ?Response { + if (!$guardAuthenticator instanceof AuthenticatorInterface && !$guardAuthenticator instanceof CoreAuthenticatorInterface) { + throw new \UnexpectedValueException('Invalid guard authenticator passed to '.__METHOD__.'. Expected AuthenticatorInterface of either Security Core or Security Guard.'); + } + $response = $guardAuthenticator->onAuthenticationFailure($request, $authenticationException); if ($response instanceof Response || null === $response) { // returning null is ok, it means they want the request to continue diff --git a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProviderTrait.php b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProviderTrait.php index 0112256b85..0d25f167db 100644 --- a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProviderTrait.php +++ b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProviderTrait.php @@ -11,14 +11,15 @@ namespace Symfony\Component\Security\Guard\Provider; +use Symfony\Component\Security\Core\Authentication\Authenticator\AuthenticatorInterface as CoreAuthenticatorInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Core\Exception\LogicException; use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Guard\AuthenticatorInterface; use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface; -use Symfony\Component\Security\Guard\Token\GuardTokenInterface; use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken; /** @@ -28,10 +29,22 @@ use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken; */ trait GuardAuthenticationProviderTrait { - private function authenticateViaGuard(AuthenticatorInterface $guardAuthenticator, PreAuthenticationGuardToken $token, string $providerKey): TokenInterface + /** + * @param CoreAuthenticatorInterface|AuthenticatorInterface $guardAuthenticator + */ + private function authenticateViaGuard($guardAuthenticator, PreAuthenticationGuardToken $token, string $providerKey): TokenInterface { // get the user from the GuardAuthenticator - $user = $guardAuthenticator->getUser($token->getCredentials(), $this->userProvider); + if ($guardAuthenticator instanceof AuthenticatorInterface) { + if (!isset($this->userProvider)) { + throw new LogicException(sprintf('%s only supports authenticators implementing "%s", update "%s" or use the legacy guard integration instead.', __CLASS__, CoreAuthenticatorInterface::class, \get_class($guardAuthenticator))); + } + $user = $guardAuthenticator->getUser($token->getCredentials(), $this->userProvider); + } elseif ($guardAuthenticator instanceof CoreAuthenticatorInterface) { + $user = $guardAuthenticator->getUser($token->getCredentials()); + } else { + throw new \UnexpectedValueException('Invalid guard authenticator passed to '.__METHOD__.'. Expected AuthenticatorInterface of either Security Core or Security Guard.'); + } if (null === $user) { throw new UsernameNotFoundException(sprintf('Null returned from "%s::getUser()".', get_debug_type($guardAuthenticator))); @@ -63,7 +76,10 @@ trait GuardAuthenticationProviderTrait return $authenticatedToken; } - private function findOriginatingAuthenticator(PreAuthenticationGuardToken $token): ?AuthenticatorInterface + /** + * @return CoreAuthenticatorInterface|\Symfony\Component\Security\Guard\AuthenticatorInterface|null + */ + private function findOriginatingAuthenticator(PreAuthenticationGuardToken $token) { // find the *one* GuardAuthenticator that this token originated from foreach ($this->guardAuthenticators as $key => $guardAuthenticator) { diff --git a/src/Symfony/Component/Security/Http/Firewall/GuardManagerListener.php b/src/Symfony/Component/Security/Http/Firewall/GuardManagerListener.php index b1261bf2b1..564f60d31b 100644 --- a/src/Symfony/Component/Security/Http/Firewall/GuardManagerListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/GuardManagerListener.php @@ -12,10 +12,9 @@ namespace Symfony\Component\Security\Http\Firewall; use Psr\Log\LoggerInterface; -use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; -use Symfony\Component\Security\Guard\AuthenticatorInterface; +use Symfony\Component\Security\Core\Authentication\Authenticator\AuthenticatorInterface; use Symfony\Component\Security\Guard\Firewall\GuardAuthenticatorListenerTrait; use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;