From b20ebe6b908cadca5c37040f305a101ce8cdf981 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 26 Nov 2019 18:28:34 +0100 Subject: [PATCH] [Security/Http] call auth listeners/guards eagerly when they "support" the request --- UPGRADE-4.4.md | 1 + UPGRADE-5.0.md | 2 +- .../SecurityBundle/Debug/WrappedListener.php | 2 +- .../DependencyInjection/SecurityExtension.php | 4 +- .../Resources/config/security.xml | 2 - .../Security/LazyFirewallContext.php | 58 ++++++---- .../GuardedBundle/AppCustomAuthenticator.php | 59 ++++++++++ .../Tests/Functional/GuardedTest.php | 24 ++++ .../Tests/Functional/app/Guarded/bundles.php | 15 +++ .../Tests/Functional/app/Guarded/config.yml | 22 ++++ .../Tests/Functional/app/Guarded/routing.yml | 5 + .../Bundle/SecurityBundle/composer.json | 2 +- src/Symfony/Component/Security/CHANGELOG.md | 1 + .../Firewall/GuardAuthenticationListener.php | 52 ++++++--- .../Component/Security/Guard/composer.json | 2 +- .../Component/Security/Http/Firewall.php | 2 +- .../AbstractAuthenticationListener.php | 16 ++- .../Http/Firewall/AbstractListener.php | 42 +++++++ .../AbstractPreAuthenticatedListener.php | 25 ++-- .../Security/Http/Firewall/AccessListener.php | 22 +++- .../AnonymousAuthenticationListener.php | 13 ++- .../Firewall/BasicAuthenticationListener.php | 12 +- .../Http/Firewall/ChannelListener.php | 28 +++-- .../Http/Firewall/ContextListener.php | 12 +- .../Security/Http/Firewall/LogoutListener.php | 16 ++- .../Http/Firewall/RememberMeListener.php | 13 ++- .../SimplePreAuthenticationListener.php | 32 +++-- .../Http/Firewall/SwitchUserListener.php | 29 +++-- ...namePasswordJsonAuthenticationListener.php | 21 ++-- .../Tests/Firewall/AccessListenerTest.php | 54 ++------- .../AnonymousAuthenticationListenerTest.php | 8 +- .../Tests/Firewall/RememberMeListenerTest.php | 109 +++++------------- 32 files changed, 462 insertions(+), 243 deletions(-) create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/GuardedBundle/AppCustomAuthenticator.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/GuardedTest.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/bundles.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/config.yml create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/routing.yml create mode 100644 src/Symfony/Component/Security/Http/Firewall/AbstractListener.php diff --git a/UPGRADE-4.4.md b/UPGRADE-4.4.md index 4bb7edc5d92da..92847de89b679 100644 --- a/UPGRADE-4.4.md +++ b/UPGRADE-4.4.md @@ -219,6 +219,7 @@ Security * The `LdapUserProvider` class has been deprecated, use `Symfony\Component\Ldap\Security\LdapUserProvider` instead. * Implementations of `PasswordEncoderInterface` and `UserPasswordEncoderInterface` should add a new `needsRehash()` method * Deprecated returning a non-boolean value when implementing `Guard\AuthenticatorInterface::checkCredentials()`. Please explicitly return `false` to indicate invalid credentials. + * The `ListenerInterface` is deprecated, extend `AbstractListener` instead. * Deprecated passing more than one attribute to `AccessDecisionManager::decide()` and `AuthorizationChecker::isGranted()` (and indirectly the `is_granted()` Twig and ExpressionLanguage function) **Before** diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index 8a0b3ebe2731b..9fa32d9131882 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -434,7 +434,7 @@ Security * `SimpleAuthenticatorInterface`, `SimpleFormAuthenticatorInterface`, `SimplePreAuthenticatorInterface`, `SimpleAuthenticationProvider`, `SimpleAuthenticationHandler`, `SimpleFormAuthenticationListener` and `SimplePreAuthenticationListener` have been removed. Use Guard instead. - * The `ListenerInterface` has been removed, turn your listeners into callables instead. + * The `ListenerInterface` has been removed, extend `AbstractListener` instead. * The `Firewall::handleRequest()` method has been removed, use `Firewall::callListeners()` instead. * `\Serializable` interface has been removed from `AbstractToken` and `AuthenticationException`, thus `serialize()` and `unserialize()` aren't available. diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php index 36b01fda12fbd..0bc7fdda9e573 100644 --- a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php +++ b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php @@ -50,7 +50,7 @@ public function __invoke(RequestEvent $event) if (\is_callable($this->listener)) { ($this->listener)($event); } else { - @trigger_error(sprintf('Calling the "%s::handle()" method from the firewall is deprecated since Symfony 4.3, implement "__invoke()" instead.', \get_class($this->listener)), E_USER_DEPRECATED); + @trigger_error(sprintf('Calling the "%s::handle()" method from the firewall is deprecated since Symfony 4.3, extend "%s" instead.', \get_class($this->listener), AbstractListener::class), E_USER_DEPRECATED); $this->listener->handle($event); } $this->time = microtime(true) - $startTime; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 627d7b92d5ef3..97cbc824be90d 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -409,9 +409,7 @@ private function createFirewall(ContainerBuilder $container, string $id, array $ } // Access listener - if ($firewall['stateless'] || empty($firewall['anonymous']['lazy'])) { - $listeners[] = new Reference('security.access_listener'); - } + $listeners[] = new Reference('security.access_listener'); // Exception listener $exceptionListener = new Reference($this->createExceptionListener($container, $firewall, $id, $configuredEntryPoint ?: $defaultEntryPoint, $firewall['stateless'])); diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml index 410646d9ba5d6..2ea2c3fa7d732 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml @@ -156,9 +156,7 @@ - - diff --git a/src/Symfony/Bundle/SecurityBundle/Security/LazyFirewallContext.php b/src/Symfony/Bundle/SecurityBundle/Security/LazyFirewallContext.php index ef9b1e217cd5a..a45cc9c6d667c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/LazyFirewallContext.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/LazyFirewallContext.php @@ -13,11 +13,8 @@ use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; -use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter; -use Symfony\Component\Security\Core\Exception\LazyResponseException; -use Symfony\Component\Security\Http\AccessMapInterface; use Symfony\Component\Security\Http\Event\LazyResponseEvent; -use Symfony\Component\Security\Http\Firewall\AccessListener; +use Symfony\Component\Security\Http\Firewall\AbstractListener; use Symfony\Component\Security\Http\Firewall\ExceptionListener; use Symfony\Component\Security\Http\Firewall\LogoutListener; @@ -28,17 +25,13 @@ */ class LazyFirewallContext extends FirewallContext { - private $accessListener; private $tokenStorage; - private $map; - public function __construct(iterable $listeners, ?ExceptionListener $exceptionListener, ?LogoutListener $logoutListener, ?FirewallConfig $config, AccessListener $accessListener, TokenStorage $tokenStorage, AccessMapInterface $map) + public function __construct(iterable $listeners, ?ExceptionListener $exceptionListener, ?LogoutListener $logoutListener, ?FirewallConfig $config, TokenStorage $tokenStorage) { parent::__construct($listeners, $exceptionListener, $logoutListener, $config); - $this->accessListener = $accessListener; $this->tokenStorage = $tokenStorage; - $this->map = $map; } public function getListeners(): iterable @@ -48,26 +41,41 @@ public function getListeners(): iterable public function __invoke(RequestEvent $event) { - $this->tokenStorage->setInitializer(function () use ($event) { - $event = new LazyResponseEvent($event); - foreach (parent::getListeners() as $listener) { - if (\is_callable($listener)) { - $listener($event); - } else { - @trigger_error(sprintf('Calling the "%s::handle()" method from the firewall is deprecated since Symfony 4.3, implement "__invoke()" instead.', \get_class($listener)), E_USER_DEPRECATED); - $listener->handle($event); - } + $listeners = []; + $request = $event->getRequest(); + $lazy = $request->isMethodCacheable(); + + foreach (parent::getListeners() as $listener) { + if (!\is_callable($listener)) { + @trigger_error(sprintf('Calling the "%s::handle()" method from the firewall is deprecated since Symfony 4.3, extend "%s" instead.', \get_class($listener), AbstractListener::class), E_USER_DEPRECATED); + $listeners[] = [$listener, 'handle']; + $lazy = false; + } elseif (!$lazy || !$listener instanceof AbstractListener) { + $listeners[] = $listener; + $lazy = $lazy && $listener instanceof AbstractListener; + } elseif (false !== $supports = $listener->supports($request)) { + $listeners[] = [$listener, 'authenticate']; + $lazy = null === $supports; } - }); + } - try { - [$attributes] = $this->map->getPatterns($event->getRequest()); + if (!$lazy) { + foreach ($listeners as $listener) { + $listener($event); - if ($attributes && [AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY] !== $attributes) { - ($this->accessListener)($event); + if ($event->hasResponse()) { + return; + } } - } catch (LazyResponseException $e) { - $event->setResponse($e->getResponse()); + + return; } + + $this->tokenStorage->setInitializer(function () use ($event, $listeners) { + $event = new LazyResponseEvent($event); + foreach ($listeners as $listener) { + $listener($event); + } + }); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/GuardedBundle/AppCustomAuthenticator.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/GuardedBundle/AppCustomAuthenticator.php new file mode 100644 index 0000000000000..fef2732759fa1 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/GuardedBundle/AppCustomAuthenticator.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\GuardedBundle; + +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; +use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Security\Guard\AbstractGuardAuthenticator; + +class AppCustomAuthenticator extends AbstractGuardAuthenticator +{ + public function supports(Request $request) + { + return true; + } + + public function getCredentials(Request $request) + { + throw new AuthenticationException('This should be hit'); + } + + public function getUser($credentials, UserProviderInterface $userProvider) + { + } + + public function checkCredentials($credentials, UserInterface $user) + { + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception) + { + return new Response('', 418); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) + { + } + + public function start(Request $request, AuthenticationException $authException = null) + { + return new Response($authException->getMessage(), Response::HTTP_UNAUTHORIZED); + } + + public function supportsRememberMe() + { + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/GuardedTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/GuardedTest.php new file mode 100644 index 0000000000000..bb0969c36a2fd --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/GuardedTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\Functional; + +class GuardedTest extends AbstractWebTestCase +{ + public function testGuarded() + { + $client = $this->createClient(['test_case' => 'Guarded', 'root_config' => 'config.yml']); + + $client->request('GET', '/'); + + $this->assertSame(418, $client->getResponse()->getStatusCode()); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/bundles.php new file mode 100644 index 0000000000000..d1e9eb7e0d36a --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/bundles.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), + new Symfony\Bundle\SecurityBundle\SecurityBundle(), +]; diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/config.yml new file mode 100644 index 0000000000000..2d1f779a530ec --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/config.yml @@ -0,0 +1,22 @@ +framework: + secret: test + router: { resource: "%kernel.project_dir%/%kernel.test_case%/routing.yml" } + test: ~ + default_locale: en + profiler: false + session: + storage_id: session.storage.mock_file + +services: + logger: { class: Psr\Log\NullLogger } + Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\GuardedBundle\AppCustomAuthenticator: ~ + +security: + firewalls: + secure: + pattern: ^/ + anonymous: lazy + stateless: false + guard: + authenticators: + - Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\GuardedBundle\AppCustomAuthenticator diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/routing.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/routing.yml new file mode 100644 index 0000000000000..4d11154375219 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/routing.yml @@ -0,0 +1,5 @@ +main: + path: / + defaults: + _controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::urlRedirectAction + path: /app diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 4ec8665479300..4093fe2b94b84 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -24,7 +24,7 @@ "symfony/security-core": "^4.4", "symfony/security-csrf": "^4.2|^5.0", "symfony/security-guard": "^4.2|^5.0", - "symfony/security-http": "^4.4" + "symfony/security-http": "^4.4.1" }, "require-dev": { "doctrine/doctrine-bundle": "^1.5|^2.0", diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index cc30a5608fffa..0877880d880c1 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -14,6 +14,7 @@ CHANGELOG * Deprecated returning a non-boolean value when implementing `Guard\AuthenticatorInterface::checkCredentials()`. * Deprecated passing more than one attribute to `AccessDecisionManager::decide()` and `AuthorizationChecker::isGranted()` * Added new `argon2id` encoder, undeprecated the `bcrypt` and `argon2i` ones (using `auto` is still recommended by default.) + * Added `AbstractListener` which replaces the deprecated `ListenerInterface` 4.3.0 ----- diff --git a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php index bcfa30dc585bd..83f0c5d2bc163 100644 --- a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php +++ b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php @@ -21,6 +21,7 @@ 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\Firewall\LegacyListenerTrait; use Symfony\Component\Security\Http\Firewall\ListenerInterface; use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface; @@ -33,7 +34,7 @@ * * @final since Symfony 4.3 */ -class GuardAuthenticationListener implements ListenerInterface +class GuardAuthenticationListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -62,9 +63,9 @@ public function __construct(GuardAuthenticatorHandler $guardHandler, Authenticat } /** - * Iterates over each authenticator to see if each wants to authenticate the request. + * {@inheritdoc} */ - public function __invoke(RequestEvent $event) + public function supports(Request $request): ?bool { if (null !== $this->logger) { $context = ['firewall_key' => $this->providerKey]; @@ -76,7 +77,39 @@ public function __invoke(RequestEvent $event) $this->logger->debug('Checking for guard authentication credentials.', $context); } + $guardAuthenticators = []; + foreach ($this->guardAuthenticators as $key => $guardAuthenticator) { + if (null !== $this->logger) { + $this->logger->debug('Checking support on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]); + } + + if ($guardAuthenticator->supports($request)) { + $guardAuthenticators[$key] = $guardAuthenticator; + } elseif (null !== $this->logger) { + $this->logger->debug('Guard authenticator does not support the request.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]); + } + } + + if (!$guardAuthenticators) { + return false; + } + + $request->attributes->set('_guard_authenticators', $guardAuthenticators); + + return true; + } + + /** + * Iterates over each authenticator to see if each wants to authenticate the request. + */ + public function authenticate(RequestEvent $event) + { + $request = $event->getRequest(); + $guardAuthenticators = $request->attributes->get('_guard_authenticators'); + $request->attributes->remove('_guard_authenticators'); + + foreach ($guardAuthenticators as $key => $guardAuthenticator) { // get a key that's unique to *this* guard authenticator // this MUST be the same as GuardAuthenticationProvider $uniqueGuardKey = $this->providerKey.'_'.$key; @@ -97,19 +130,6 @@ private function executeGuardAuthenticator(string $uniqueGuardKey, Authenticator { $request = $event->getRequest(); try { - if (null !== $this->logger) { - $this->logger->debug('Checking support on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]); - } - - // abort the execution of the authenticator if it doesn't support the request - if (!$guardAuthenticator->supports($request)) { - if (null !== $this->logger) { - $this->logger->debug('Guard authenticator does not support the request.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]); - } - - return; - } - if (null !== $this->logger) { $this->logger->debug('Calling getCredentials() on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]); } diff --git a/src/Symfony/Component/Security/Guard/composer.json b/src/Symfony/Component/Security/Guard/composer.json index af3ce94a9b2d0..09b30d11ef3c9 100644 --- a/src/Symfony/Component/Security/Guard/composer.json +++ b/src/Symfony/Component/Security/Guard/composer.json @@ -18,7 +18,7 @@ "require": { "php": "^7.1.3", "symfony/security-core": "^3.4.22|^4.2.3|^5.0", - "symfony/security-http": "^4.3" + "symfony/security-http": "^4.4.1" }, "require-dev": { "psr/log": "~1.0" diff --git a/src/Symfony/Component/Security/Http/Firewall.php b/src/Symfony/Component/Security/Http/Firewall.php index 08d4873c28af8..ee769496a6918 100644 --- a/src/Symfony/Component/Security/Http/Firewall.php +++ b/src/Symfony/Component/Security/Http/Firewall.php @@ -138,7 +138,7 @@ protected function handleRequest(GetResponseEvent $event, $listeners) if (\is_callable($listener)) { $listener($event); } else { - @trigger_error(sprintf('Calling the "%s::handle()" method from the firewall is deprecated since Symfony 4.3, implement "__invoke()" instead.', \get_class($listener)), E_USER_DEPRECATED); + @trigger_error(sprintf('Calling the "%s::handle()" method from the firewall is deprecated since Symfony 4.3, extend "%s" instead.', \get_class($listener), AbstractListener::class), E_USER_DEPRECATED); $listener->handle($event); } diff --git a/src/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.php index fed7785a6153a..736c247e7d924 100644 --- a/src/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.php @@ -49,7 +49,7 @@ * @author Fabien Potencier * @author Johannes M. Schmitt */ -abstract class AbstractAuthenticationListener implements ListenerInterface +abstract class AbstractAuthenticationListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -105,20 +105,24 @@ public function setRememberMeServices(RememberMeServicesInterface $rememberMeSer $this->rememberMeServices = $rememberMeServices; } + /** + * {@inheritdoc} + */ + public function supports(Request $request): ?bool + { + return $this->requiresAuthentication($request); + } + /** * Handles form based authentication. * * @throws \RuntimeException * @throws SessionUnavailableException */ - public function __invoke(RequestEvent $event) + public function authenticate(RequestEvent $event) { $request = $event->getRequest(); - if (!$this->requiresAuthentication($request)) { - return; - } - if (!$request->hasSession()) { throw new \RuntimeException('This authentication method requires a session.'); } diff --git a/src/Symfony/Component/Security/Http/Firewall/AbstractListener.php b/src/Symfony/Component/Security/Http/Firewall/AbstractListener.php new file mode 100644 index 0000000000000..ecbfa30233eb5 --- /dev/null +++ b/src/Symfony/Component/Security/Http/Firewall/AbstractListener.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Firewall; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\RequestEvent; + +/** + * A base class for listeners that can tell whether they should authenticate incoming requests. + * + * @author Nicolas Grekas + */ +abstract class AbstractListener +{ + final public function __invoke(RequestEvent $event) + { + if (false !== $this->supports($event->getRequest())) { + $this->authenticate($event); + } + } + + /** + * Tells whether the authenticate() method should be called or not depending on the incoming request. + * + * Returning null means authenticate() can be called lazily when accessing the token storage. + */ + abstract public function supports(Request $request): ?bool; + + /** + * Does whatever is required to authenticate the request, typically calling $event->setResponse() internally. + */ + abstract public function authenticate(RequestEvent $event); +} diff --git a/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php b/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php index 500ae43e498bd..e14dd1a95a946 100644 --- a/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php @@ -35,7 +35,7 @@ * * @internal since Symfony 4.3 */ -abstract class AbstractPreAuthenticatedListener implements ListenerInterface +abstract class AbstractPreAuthenticatedListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -56,20 +56,31 @@ public function __construct(TokenStorageInterface $tokenStorage, AuthenticationM } /** - * Handles pre-authentication. + * {@inheritdoc} */ - public function __invoke(RequestEvent $event) + public function supports(Request $request): ?bool { - $request = $event->getRequest(); - try { - list($user, $credentials) = $this->getPreAuthenticatedData($request); + $request->attributes->set('_pre_authenticated_data', $this->getPreAuthenticatedData($request)); } catch (BadCredentialsException $e) { $this->clearToken($e); - return; + return false; } + return true; + } + + /** + * Handles pre-authentication. + */ + public function authenticate(RequestEvent $event) + { + $request = $event->getRequest(); + + [$user, $credentials] = $request->attributes->get('_pre_authenticated_data'); + $request->attributes->remove('_pre_authenticated_data'); + if (null !== $this->logger) { $this->logger->debug('Checking current security token.', ['token' => (string) $this->tokenStorage->getToken()]); } diff --git a/src/Symfony/Component/Security/Http/Firewall/AccessListener.php b/src/Symfony/Component/Security/Http/Firewall/AccessListener.php index 6164adde5db02..00673f60aba2d 100644 --- a/src/Symfony/Component/Security/Http/Firewall/AccessListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/AccessListener.php @@ -11,10 +11,12 @@ namespace Symfony\Component\Security\Http\Firewall; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; +use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter; use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; use Symfony\Component\Security\Http\AccessMapInterface; @@ -27,7 +29,7 @@ * * @final since Symfony 4.3 */ -class AccessListener implements ListenerInterface +class AccessListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -44,13 +46,24 @@ public function __construct(TokenStorageInterface $tokenStorage, AccessDecisionM $this->authManager = $authManager; } + /** + * {@inheritdoc} + */ + public function supports(Request $request): ?bool + { + [$attributes] = $this->map->getPatterns($request); + $request->attributes->set('_access_control_attributes', $attributes); + + return $attributes && [AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY] !== $attributes ? true : null; + } + /** * Handles access authorization. * * @throws AccessDeniedException * @throws AuthenticationCredentialsNotFoundException */ - public function __invoke(RequestEvent $event) + public function authenticate(RequestEvent $event) { if (!$event instanceof LazyResponseEvent && null === $token = $this->tokenStorage->getToken()) { throw new AuthenticationCredentialsNotFoundException('A Token was not found in the TokenStorage.'); @@ -58,9 +71,10 @@ public function __invoke(RequestEvent $event) $request = $event->getRequest(); - list($attributes) = $this->map->getPatterns($request); + $attributes = $request->attributes->get('_access_control_attributes'); + $request->attributes->remove('_access_control_attributes'); - if (!$attributes) { + if (!$attributes || ([AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY] === $attributes && $event instanceof LazyResponseEvent)) { return; } diff --git a/src/Symfony/Component/Security/Http/Firewall/AnonymousAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/AnonymousAuthenticationListener.php index b7a7381bfc885..0f1da391e6dff 100644 --- a/src/Symfony/Component/Security/Http/Firewall/AnonymousAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/AnonymousAuthenticationListener.php @@ -12,6 +12,7 @@ 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\Core\Authentication\Token\AnonymousToken; @@ -26,7 +27,7 @@ * * @final since Symfony 4.3 */ -class AnonymousAuthenticationListener implements ListenerInterface +class AnonymousAuthenticationListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -43,10 +44,18 @@ public function __construct(TokenStorageInterface $tokenStorage, string $secret, $this->logger = $logger; } + /** + * {@inheritdoc} + */ + public function supports(Request $request): ?bool + { + return null; // always run authenticate() lazily with lazy firewalls + } + /** * Handles anonymous authentication. */ - public function __invoke(RequestEvent $event) + public function authenticate(RequestEvent $event) { if (null !== $this->tokenStorage->getToken()) { return; diff --git a/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php index 9d6d81715c294..dd18e87c5b307 100644 --- a/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php @@ -29,7 +29,7 @@ * * @final since Symfony 4.3 */ -class BasicAuthenticationListener implements ListenerInterface +class BasicAuthenticationListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -55,10 +55,18 @@ public function __construct(TokenStorageInterface $tokenStorage, AuthenticationM $this->ignoreFailure = false; } + /** + * {@inheritdoc} + */ + public function supports(Request $request): ?bool + { + return null !== $request->headers->get('PHP_AUTH_USER'); + } + /** * Handles basic authentication. */ - public function __invoke(RequestEvent $event) + public function authenticate(RequestEvent $event) { $request = $event->getRequest(); diff --git a/src/Symfony/Component/Security/Http/Firewall/ChannelListener.php b/src/Symfony/Component/Security/Http/Firewall/ChannelListener.php index 671f279fdf9a2..1033aa47ed3b4 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ChannelListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ChannelListener.php @@ -12,6 +12,7 @@ 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\Http\AccessMapInterface; use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; @@ -24,7 +25,7 @@ * * @final since Symfony 4.3 */ -class ChannelListener implements ListenerInterface +class ChannelListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -42,10 +43,8 @@ public function __construct(AccessMapInterface $map, AuthenticationEntryPointInt /** * Handles channel management. */ - public function __invoke(RequestEvent $event) + public function supports(Request $request): ?bool { - $request = $event->getRequest(); - list(, $channel) = $this->map->getPatterns($request); if ('https' === $channel && !$request->isSecure()) { @@ -59,11 +58,7 @@ public function __invoke(RequestEvent $event) } } - $response = $this->authenticationEntryPoint->start($request); - - $event->setResponse($response); - - return; + return true; } if ('http' === $channel && $request->isSecure()) { @@ -71,9 +66,18 @@ public function __invoke(RequestEvent $event) $this->logger->info('Redirecting to HTTP.'); } - $response = $this->authenticationEntryPoint->start($request); - - $event->setResponse($response); + return true; } + + return false; + } + + public function authenticate(RequestEvent $event) + { + $request = $event->getRequest(); + + $response = $this->authenticationEntryPoint->start($request); + + $event->setResponse($response); } } diff --git a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php index 4015262f01b87..2100968897d90 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php @@ -41,7 +41,7 @@ * * @final since Symfony 4.3 */ -class ContextListener implements ListenerInterface +class ContextListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -84,10 +84,18 @@ public function setLogoutOnUserChange($logoutOnUserChange) @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1.', __METHOD__), E_USER_DEPRECATED); } + /** + * {@inheritdoc} + */ + public function supports(Request $request): ?bool + { + return null; // always run authenticate() lazily with lazy firewalls + } + /** * Reads the Security Token from the session. */ - public function __invoke(RequestEvent $event) + public function authenticate(RequestEvent $event) { if (!$this->registered && null !== $this->dispatcher && $event->isMasterRequest()) { $this->dispatcher->addListener(KernelEvents::RESPONSE, [$this, 'onKernelResponse']); diff --git a/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php b/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php index a53aeccf4a25a..e78f21826f362 100644 --- a/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php @@ -30,7 +30,7 @@ * * @final since Symfony 4.3 */ -class LogoutListener implements ListenerInterface +class LogoutListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -63,6 +63,14 @@ public function addHandler(LogoutHandlerInterface $handler) $this->handlers[] = $handler; } + /** + * {@inheritdoc} + */ + public function supports(Request $request): ?bool + { + return $this->requiresLogout($request); + } + /** * Performs the logout if requested. * @@ -72,14 +80,10 @@ public function addHandler(LogoutHandlerInterface $handler) * @throws LogoutException if the CSRF token is invalid * @throws \RuntimeException if the LogoutSuccessHandlerInterface instance does not return a response */ - public function __invoke(RequestEvent $event) + public function authenticate(RequestEvent $event) { $request = $event->getRequest(); - if (!$this->requiresLogout($request)) { - return; - } - if (null !== $this->csrfTokenManager) { $csrfToken = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']); diff --git a/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php b/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php index ebc03db862952..0cfac54b3412d 100644 --- a/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php @@ -13,6 +13,7 @@ use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; @@ -31,7 +32,7 @@ * * @final since Symfony 4.3 */ -class RememberMeListener implements ListenerInterface +class RememberMeListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -54,10 +55,18 @@ public function __construct(TokenStorageInterface $tokenStorage, RememberMeServi $this->sessionStrategy = null === $sessionStrategy ? new SessionAuthenticationStrategy(SessionAuthenticationStrategy::MIGRATE) : $sessionStrategy; } + /** + * {@inheritdoc} + */ + public function supports(Request $request): ?bool + { + return null; // always run authenticate() lazily with lazy firewalls + } + /** * Handles remember-me cookie based authentication. */ - public function __invoke(RequestEvent $event) + public function authenticate(RequestEvent $event) { if (null !== $this->tokenStorage->getToken()) { return; diff --git a/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php index 2c444e823b6fe..0641d9e45a128 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php @@ -41,7 +41,7 @@ * * @deprecated since Symfony 4.2, use Guard instead. */ -class SimplePreAuthenticationListener implements ListenerInterface +class SimplePreAuthenticationListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -79,10 +79,28 @@ public function setSessionAuthenticationStrategy(SessionAuthenticationStrategyIn $this->sessionStrategy = $sessionStrategy; } + public function supports(Request $request): ?bool + { + if ((null !== $token = $this->tokenStorage->getToken()) && !$this->trustResolver->isAnonymous($token)) { + return false; + } + + $token = $this->simpleAuthenticator->createToken($request, $this->providerKey); + + // allow null to be returned to skip authentication + if (null === $token) { + return false; + } + + $request->attributes->set('_simple_pre_authenticator_token', $token); + + return true; + } + /** * Handles basic authentication. */ - public function __invoke(RequestEvent $event) + public function authenticate(RequestEvent $event) { $request = $event->getRequest(); @@ -91,16 +109,14 @@ public function __invoke(RequestEvent $event) } if ((null !== $token = $this->tokenStorage->getToken()) && !$this->trustResolver->isAnonymous($token)) { + $request->attributes->remove('_simple_pre_authenticator_token'); + return; } try { - $token = $this->simpleAuthenticator->createToken($request, $this->providerKey); - - // allow null to be returned to skip authentication - if (null === $token) { - return; - } + $token = $request->attributes->get('_simple_pre_authenticator_token'); + $request->attributes->remove('_simple_pre_authenticator_token'); $token = $this->authenticationManager->authenticate($token); diff --git a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php index 4d546285f52a2..d762e5e429a47 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php @@ -39,7 +39,7 @@ * * @final since Symfony 4.3 */ -class SwitchUserListener implements ListenerInterface +class SwitchUserListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -75,14 +75,10 @@ public function __construct(TokenStorageInterface $tokenStorage, UserProviderInt } /** - * Handles the switch to another user. - * - * @throws \LogicException if switching to a user failed + * {@inheritdoc} */ - public function __invoke(RequestEvent $event) + public function supports(Request $request): ?bool { - $request = $event->getRequest(); - // usernames can be falsy $username = $request->get($this->usernameParameter); @@ -92,9 +88,26 @@ public function __invoke(RequestEvent $event) // if it's still "empty", nothing to do. if (null === $username || '' === $username) { - return; + return false; } + $request->attributes->set('_switch_user_username', $username); + + return true; + } + + /** + * Handles the switch to another user. + * + * @throws \LogicException if switching to a user failed + */ + public function authenticate(RequestEvent $event) + { + $request = $event->getRequest(); + + $username = $request->attributes->get('_switch_user_username'); + $request->attributes->remove('_switch_user_username'); + if (null === $this->tokenStorage->getToken()) { throw new AuthenticationCredentialsNotFoundException('Could not find original Token object.'); } diff --git a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php index 851e160bebbef..50eb405c6120d 100644 --- a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php @@ -44,7 +44,7 @@ * * @final since Symfony 4.3 */ -class UsernamePasswordJsonAuthenticationListener implements ListenerInterface +class UsernamePasswordJsonAuthenticationListener extends AbstractListener implements ListenerInterface { use LegacyListenerTrait; @@ -74,22 +74,27 @@ public function __construct(TokenStorageInterface $tokenStorage, AuthenticationM $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); } - /** - * {@inheritdoc} - */ - public function __invoke(RequestEvent $event) + public function supports(Request $request): ?bool { - $request = $event->getRequest(); if (false === strpos($request->getRequestFormat(), 'json') && false === strpos($request->getContentType(), 'json') ) { - return; + return false; } if (isset($this->options['check_path']) && !$this->httpUtils->checkRequestPath($request, $this->options['check_path'])) { - return; + return false; } + return true; + } + + /** + * {@inheritdoc} + */ + public function authenticate(RequestEvent $event) + { + $request = $event->getRequest(); $data = json_decode($request->getContent()); try { diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php index 1dff48dfda84f..168e25643705b 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; @@ -26,7 +27,7 @@ class AccessListenerTest extends TestCase public function testHandleWhenTheAccessDecisionManagerDecidesToRefuseAccess() { $this->expectException('Symfony\Component\Security\Core\Exception\AccessDeniedException'); - $request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->disableOriginalConstructor()->disableOriginalClone()->getMock(); + $request = new Request(); $accessMap = $this->getMockBuilder('Symfony\Component\Security\Http\AccessMapInterface')->getMock(); $accessMap @@ -65,19 +66,12 @@ public function testHandleWhenTheAccessDecisionManagerDecidesToRefuseAccess() $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock() ); - $event = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); - $event - ->expects($this->any()) - ->method('getRequest') - ->willReturn($request) - ; - - $listener($event); + $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST)); } public function testHandleWhenTheTokenIsNotAuthenticated() { - $request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->disableOriginalConstructor()->disableOriginalClone()->getMock(); + $request = new Request(); $accessMap = $this->getMockBuilder('Symfony\Component\Security\Http\AccessMapInterface')->getMock(); $accessMap @@ -136,19 +130,12 @@ public function testHandleWhenTheTokenIsNotAuthenticated() $authManager ); - $event = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); - $event - ->expects($this->any()) - ->method('getRequest') - ->willReturn($request) - ; - - $listener($event); + $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST)); } public function testHandleWhenThereIsNoAccessMapEntryMatchingTheRequest() { - $request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->disableOriginalConstructor()->disableOriginalClone()->getMock(); + $request = new Request(); $accessMap = $this->getMockBuilder('Symfony\Component\Security\Http\AccessMapInterface')->getMock(); $accessMap @@ -178,19 +165,12 @@ public function testHandleWhenThereIsNoAccessMapEntryMatchingTheRequest() $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock() ); - $event = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); - $event - ->expects($this->any()) - ->method('getRequest') - ->willReturn($request) - ; - - $listener($event); + $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST)); } public function testHandleWhenAccessMapReturnsEmptyAttributes() { - $request = $this->getMockBuilder(Request::class)->disableOriginalConstructor()->disableOriginalClone()->getMock(); + $request = new Request(); $accessMap = $this->getMockBuilder(AccessMapInterface::class)->getMock(); $accessMap @@ -213,12 +193,7 @@ public function testHandleWhenAccessMapReturnsEmptyAttributes() $this->getMockBuilder(AuthenticationManagerInterface::class)->getMock() ); - $event = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); - $event - ->expects($this->any()) - ->method('getRequest') - ->willReturn($request) - ; + $event = new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST); $listener(new LazyResponseEvent($event)); } @@ -233,7 +208,7 @@ public function testHandleWhenTheSecurityTokenStorageHasNoToken() ->willReturn(null) ; - $request = $this->getMockBuilder(Request::class)->disableOriginalConstructor()->disableOriginalClone()->getMock(); + $request = new Request(); $accessMap = $this->getMockBuilder(AccessMapInterface::class)->getMock(); $accessMap @@ -250,13 +225,6 @@ public function testHandleWhenTheSecurityTokenStorageHasNoToken() $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock() ); - $event = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); - $event - ->expects($this->any()) - ->method('getRequest') - ->willReturn($request) - ; - - $listener($event); + $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST)); } } diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/AnonymousAuthenticationListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/AnonymousAuthenticationListenerTest.php index 47f09199c43e5..e6f9f42217efb 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/AnonymousAuthenticationListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/AnonymousAuthenticationListenerTest.php @@ -12,7 +12,9 @@ namespace Symfony\Component\Security\Http\Tests\Firewall; use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use Symfony\Component\Security\Http\Firewall\AnonymousAuthenticationListener; @@ -38,7 +40,7 @@ public function testHandleWithTokenStorageHavingAToken() ; $listener = new AnonymousAuthenticationListener($tokenStorage, 'TheSecret', null, $authenticationManager); - $listener($this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock()); + $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), new Request(), HttpKernelInterface::MASTER_REQUEST)); } public function testHandleWithTokenStorageHavingNoToken() @@ -69,7 +71,7 @@ public function testHandleWithTokenStorageHavingNoToken() ; $listener = new AnonymousAuthenticationListener($tokenStorage, 'TheSecret', null, $authenticationManager); - $listener($this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock()); + $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), new Request(), HttpKernelInterface::MASTER_REQUEST)); } public function testHandledEventIsLogged() @@ -84,6 +86,6 @@ public function testHandledEventIsLogged() $authenticationManager = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock(); $listener = new AnonymousAuthenticationListener($tokenStorage, 'TheSecret', $logger, $authenticationManager); - $listener($this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock()); + $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), new Request(), HttpKernelInterface::MASTER_REQUEST)); } } diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php index ceb557b139d0a..d321ed68921bd 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php @@ -15,6 +15,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Http\Firewall\RememberMeListener; use Symfony\Component\Security\Http\SecurityEvents; @@ -27,7 +28,7 @@ public function testOnCoreSecurityDoesNotTryToPopulateNonEmptyTokenStorage() list($listener, $tokenStorage) = $this->getListener(); $tokenStorage - ->expects($this->once()) + ->expects($this->any()) ->method('getToken') ->willReturn($this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock()) ; @@ -45,7 +46,7 @@ public function testOnCoreSecurityDoesNothingWhenNoCookieIsSet() list($listener, $tokenStorage, $service) = $this->getListener(); $tokenStorage - ->expects($this->once()) + ->expects($this->any()) ->method('getToken') ->willReturn(null) ; @@ -57,11 +58,6 @@ public function testOnCoreSecurityDoesNothingWhenNoCookieIsSet() ; $event = $this->getGetResponseEvent(); - $event - ->expects($this->once()) - ->method('getRequest') - ->willReturn(new Request()) - ; $this->assertNull($listener($event)); } @@ -73,7 +69,7 @@ public function testOnCoreSecurityIgnoresAuthenticationExceptionThrownByAuthenti $exception = new AuthenticationException('Authentication failed.'); $tokenStorage - ->expects($this->once()) + ->expects($this->any()) ->method('getToken') ->willReturn(null) ; @@ -96,12 +92,7 @@ public function testOnCoreSecurityIgnoresAuthenticationExceptionThrownByAuthenti ->willThrowException($exception) ; - $event = $this->getGetResponseEvent(); - $event - ->expects($this->once()) - ->method('getRequest') - ->willReturn($request) - ; + $event = $this->getGetResponseEvent($request); $listener($event); } @@ -113,7 +104,7 @@ public function testOnCoreSecurityIgnoresAuthenticationOptionallyRethrowsExcepti list($listener, $tokenStorage, $service, $manager) = $this->getListener(false, false); $tokenStorage - ->expects($this->once()) + ->expects($this->any()) ->method('getToken') ->willReturn(null) ; @@ -137,11 +128,6 @@ public function testOnCoreSecurityIgnoresAuthenticationOptionallyRethrowsExcepti ; $event = $this->getGetResponseEvent(); - $event - ->expects($this->once()) - ->method('getRequest') - ->willReturn(new Request()) - ; $listener($event); } @@ -151,7 +137,7 @@ public function testOnCoreSecurityAuthenticationExceptionDuringAutoLoginTriggers list($listener, $tokenStorage, $service, $manager) = $this->getListener(); $tokenStorage - ->expects($this->once()) + ->expects($this->any()) ->method('getToken') ->willReturn(null) ; @@ -174,11 +160,6 @@ public function testOnCoreSecurityAuthenticationExceptionDuringAutoLoginTriggers ; $event = $this->getGetResponseEvent(); - $event - ->expects($this->once()) - ->method('getRequest') - ->willReturn(new Request()) - ; $listener($event); } @@ -188,7 +169,7 @@ public function testOnCoreSecurity() list($listener, $tokenStorage, $service, $manager) = $this->getListener(); $tokenStorage - ->expects($this->once()) + ->expects($this->any()) ->method('getToken') ->willReturn(null) ; @@ -213,11 +194,6 @@ public function testOnCoreSecurity() ; $event = $this->getGetResponseEvent(); - $event - ->expects($this->once()) - ->method('getRequest') - ->willReturn(new Request()) - ; $listener($event); } @@ -227,7 +203,7 @@ public function testSessionStrategy() list($listener, $tokenStorage, $service, $manager, , , $sessionStrategy) = $this->getListener(false, true, true); $tokenStorage - ->expects($this->once()) + ->expects($this->any()) ->method('getToken') ->willReturn(null) ; @@ -258,25 +234,10 @@ public function testSessionStrategy() ->willReturn(true) ; - $request = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Request')->getMock(); - $request - ->expects($this->once()) - ->method('hasSession') - ->willReturn(true) - ; - - $request - ->expects($this->once()) - ->method('getSession') - ->willReturn($session) - ; + $request = new Request(); + $request->setSession($session); - $event = $this->getGetResponseEvent(); - $event - ->expects($this->once()) - ->method('getRequest') - ->willReturn($request) - ; + $event = $this->getGetResponseEvent($request); $sessionStrategy ->expects($this->once()) @@ -292,7 +253,7 @@ public function testSessionIsMigratedByDefault() list($listener, $tokenStorage, $service, $manager) = $this->getListener(false, true, false); $tokenStorage - ->expects($this->once()) + ->expects($this->any()) ->method('getToken') ->willReturn(null) ; @@ -327,25 +288,10 @@ public function testSessionIsMigratedByDefault() ->method('migrate') ; - $request = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Request')->getMock(); - $request - ->expects($this->any()) - ->method('hasSession') - ->willReturn(true) - ; + $request = new Request(); + $request->setSession($session); - $request - ->expects($this->any()) - ->method('getSession') - ->willReturn($session) - ; - - $event = $this->getGetResponseEvent(); - $event - ->expects($this->once()) - ->method('getRequest') - ->willReturn($request) - ; + $event = $this->getGetResponseEvent($request); $listener($event); } @@ -355,7 +301,7 @@ public function testOnCoreSecurityInteractiveLoginEventIsDispatchedIfDispatcherI list($listener, $tokenStorage, $service, $manager, , $dispatcher) = $this->getListener(true); $tokenStorage - ->expects($this->once()) + ->expects($this->any()) ->method('getToken') ->willReturn(null) ; @@ -380,12 +326,6 @@ public function testOnCoreSecurityInteractiveLoginEventIsDispatchedIfDispatcherI ; $event = $this->getGetResponseEvent(); - $request = new Request(); - $event - ->expects($this->once()) - ->method('getRequest') - ->willReturn($request) - ; $dispatcher ->expects($this->once()) @@ -399,9 +339,20 @@ public function testOnCoreSecurityInteractiveLoginEventIsDispatchedIfDispatcherI $listener($event); } - protected function getGetResponseEvent() + protected function getGetResponseEvent(Request $request = null): RequestEvent { - return $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); + $request = $request ?? new Request(); + + $event = $this->getMockBuilder(RequestEvent::class) + ->setConstructorArgs([$this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST]) + ->getMock(); + $event + ->expects($this->any()) + ->method('getRequest') + ->willReturn($request) + ; + + return $event; } protected function getResponseEvent(): ResponseEvent pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy