diff --git a/UPGRADE-4.4.md b/UPGRADE-4.4.md index 0010a69f0745..f3377f43da2e 100644 --- a/UPGRADE-4.4.md +++ b/UPGRADE-4.4.md @@ -222,6 +222,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 667fdad3c674..3e67bb5e4d80 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -437,7 +437,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 36b01fda12fb..0bc7fdda9e57 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 627d7b92d5ef..97cbc824be90 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 410646d9ba5d..2ea2c3fa7d73 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 ef9b1e217cd5..a45cc9c6d667 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 000000000000..fef2732759fa --- /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 000000000000..bb0969c36a2f --- /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 000000000000..d1e9eb7e0d36 --- /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 000000000000..2d1f779a530e --- /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 000000000000..4d1115437521 --- /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 4ec866547930..4093fe2b94b8 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 cc30a5608fff..0877880d880c 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 bcfa30dc585b..83f0c5d2bc16 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 af3ce94a9b2d..09b30d11ef3c 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 08d4873c28af..ee769496a691 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 fed7785a6153..736c247e7d92 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 000000000000..ecbfa30233eb --- /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 500ae43e498b..e14dd1a95a94 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 6164adde5db0..00673f60aba2 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 b7a7381bfc88..0f1da391e6df 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 9d6d81715c29..dd18e87c5b30 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 671f279fdf9a..1033aa47ed3b 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 4015262f01b8..2100968897d9 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 a53aeccf4a25..e78f21826f36 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 ebc03db86295..0cfac54b3412 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 2c444e823b6f..0641d9e45a12 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 2385957d93d1..2263623d75d1 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 851e160bebbe..50eb405c6120 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 1dff48dfda84..168e25643705 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 47f09199c43e..e6f9f42217ef 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 ceb557b139d0..d321ed68921b 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