diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php
index 555cac383e..0fe2d995b3 100644
--- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php
+++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php
@@ -104,9 +104,8 @@ class FormLoginFactory extends AbstractFactory implements AuthenticatorFactoryIn
$options = array_merge($defaultOptions, array_intersect_key($config, $defaultOptions));
$container
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.form_login'))
- ->replaceArgument(1, isset($config['csrf_token_generator']) ? new Reference($config['csrf_token_generator']) : null)
- ->replaceArgument(2, new Reference($userProviderId))
- ->replaceArgument(3, $options);
+ ->replaceArgument(1, new Reference($userProviderId))
+ ->replaceArgument(2, $options);
return $authenticatorId;
}
diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.xml
index 757aef78e7..a5b6e87782 100644
--- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.xml
+++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.xml
@@ -84,7 +84,6 @@
abstract="true">
realm name
user provider
-
@@ -92,7 +91,6 @@
class="Symfony\Component\Security\Http\Authenticator\FormLoginAuthenticator"
abstract="true">
-
user provider
options
diff --git a/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php b/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php
index f7dacacbc4..c309485293 100644
--- a/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php
+++ b/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php
@@ -23,7 +23,6 @@ use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
-use Symfony\Component\Security\Http\Authenticator\Token\PreAuthenticationToken;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\Event\LoginFailureEvent;
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
@@ -40,8 +39,6 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
*/
class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthenticatorInterface
{
- use AuthenticatorManagerTrait;
-
private $authenticators;
private $tokenStorage;
private $eventDispatcher;
@@ -131,7 +128,9 @@ class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthent
// lazily (after initialization). This is important for e.g. the AnonymousAuthenticator
// as its support is relying on the (initialized) token in the TokenStorage.
if (false === $authenticator->supports($request)) {
- $this->logger->debug('Skipping the "{authenticator}" authenticator as it did not support the request.', ['authenticator' => \get_class($authenticator)]);
+ if (null !== $this->logger) {
+ $this->logger->debug('Skipping the "{authenticator}" authenticator as it did not support the request.', ['authenticator' => \get_class($authenticator)]);
+ }
continue;
}
@@ -215,21 +214,14 @@ class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthent
throw new UsernameNotFoundException(sprintf('Null returned from "%s::getUser()".', \get_class($authenticator)));
}
- if (!$user instanceof UserInterface) {
- throw new \UnexpectedValueException(sprintf('The %s::getUser() method must return a UserInterface. You returned %s.', \get_class($authenticator), \is_object($user) ? \get_class($user) : \gettype($user)));
- }
-
$event = new VerifyAuthenticatorCredentialsEvent($authenticator, $credentials, $user);
$this->eventDispatcher->dispatch($event);
if (true !== $event->areCredentialsValid()) {
throw new BadCredentialsException(sprintf('Authentication failed because "%s" did not approve the credentials.', \get_class($authenticator)));
}
-// turn the UserInterface into a TokenInterface
+ // turn the UserInterface into a TokenInterface
$authenticatedToken = $authenticator->createAuthenticatedToken($user, $this->providerKey);
- if (!$authenticatedToken instanceof TokenInterface) {
- throw new \UnexpectedValueException(sprintf('The %s::createAuthenticatedToken() method must return a TokenInterface. You returned %s.', \get_class($authenticator), \is_object($authenticatedToken) ? \get_class($authenticatedToken) : \gettype($authenticatedToken)));
- }
if (true === $this->eraseCredentials) {
$authenticatedToken->eraseCredentials();
@@ -259,21 +251,10 @@ class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthent
return $loginSuccessEvent->getResponse();
}
- private function handleAuthenticationFailure(AuthenticationException $exception, TokenInterface $token)
- {
- if (null !== $this->eventDispatcher) {
- $this->eventDispatcher->dispatch(new AuthenticationFailureEvent($token, $exception), AuthenticationEvents::AUTHENTICATION_FAILURE);
- }
-
- $exception->setToken($token);
-
- throw $exception;
- }
-
/**
* Handles an authentication failure and returns the Response for the authenticator.
*/
- private function handleAuthenticatorFailure(AuthenticationException $authenticationException, Request $request, AuthenticatorInterface $authenticator): ?Response
+ private function handleAuthenticationFailure(AuthenticationException $authenticationException, Request $request, AuthenticatorInterface $authenticator): ?Response
{
$response = $authenticator->onAuthenticationFailure($request, $authenticationException);
diff --git a/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php
index 2ec3792a7c..cd8c569c57 100644
--- a/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php
+++ b/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php
@@ -20,7 +20,6 @@ use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
-use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\ParameterBagUtils;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
@@ -38,13 +37,11 @@ class FormLoginAuthenticator extends AbstractLoginFormAuthenticator implements P
private $options;
private $httpUtils;
- private $csrfTokenManager;
private $userProvider;
- public function __construct(HttpUtils $httpUtils, ?CsrfTokenManagerInterface $csrfTokenManager, UserProviderInterface $userProvider, array $options)
+ public function __construct(HttpUtils $httpUtils, UserProviderInterface $userProvider, array $options)
{
$this->httpUtils = $httpUtils;
- $this->csrfTokenManager = $csrfTokenManager;
$this->options = array_merge([
'username_parameter' => '_username',
'password_parameter' => '_password',
@@ -75,10 +72,7 @@ class FormLoginAuthenticator extends AbstractLoginFormAuthenticator implements P
public function getCredentials(Request $request): array
{
$credentials = [];
-
- if (null !== $this->csrfTokenManager) {
- $credentials['csrf_token'] = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']);
- }
+ $credentials['csrf_token'] = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']);
if ($this->options['post_only']) {
$credentials['username'] = ParameterBagUtils::getParameterBagValue($request->request, $this->options['username_parameter']);
diff --git a/src/Symfony/Component/Security/Http/Authenticator/HttpBasicAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/HttpBasicAuthenticator.php
index f896d924a8..77480eea45 100644
--- a/src/Symfony/Component/Security/Http/Authenticator/HttpBasicAuthenticator.php
+++ b/src/Symfony/Component/Security/Http/Authenticator/HttpBasicAuthenticator.php
@@ -16,7 +16,6 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
-use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
@@ -33,14 +32,12 @@ class HttpBasicAuthenticator implements AuthenticatorInterface, AuthenticationEn
{
private $realmName;
private $userProvider;
- private $encoderFactory;
private $logger;
- public function __construct(string $realmName, UserProviderInterface $userProvider, EncoderFactoryInterface $encoderFactory, ?LoggerInterface $logger = null)
+ public function __construct(string $realmName, UserProviderInterface $userProvider, ?LoggerInterface $logger = null)
{
$this->realmName = $realmName;
$this->userProvider = $userProvider;
- $this->encoderFactory = $encoderFactory;
$this->logger = $logger;
}
diff --git a/src/Symfony/Component/Security/Http/Authenticator/RememberMeAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/RememberMeAuthenticator.php
index 893bd099de..1ffdd1b997 100644
--- a/src/Symfony/Component/Security/Http/Authenticator/RememberMeAuthenticator.php
+++ b/src/Symfony/Component/Security/Http/Authenticator/RememberMeAuthenticator.php
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Security\Http\Authenticator\Token;
+namespace Symfony\Component\Security\Http\Authenticator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -18,9 +18,7 @@ use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInt
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\Http\Authenticator\AuthenticatorInterface;
use Symfony\Component\Security\Http\RememberMe\AbstractRememberMeServices;
-use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy;
/**
* The RememberMe *Authenticator* performs remember me authentication.
@@ -35,21 +33,22 @@ use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy;
*
* @final
*/
-class RememberMeAuthenticator implements AuthenticatorInterface
+class RememberMeAuthenticator implements AuthenticatorInterface, CustomAuthenticatedInterface
{
private $rememberMeServices;
private $secret;
private $tokenStorage;
- private $options;
- private $sessionStrategy;
+ private $options = [
+ 'secure' => false,
+ 'httponly' => true,
+ ];
- public function __construct(AbstractRememberMeServices $rememberMeServices, string $secret, TokenStorageInterface $tokenStorage, array $options, ?SessionAuthenticationStrategy $sessionStrategy = null)
+ public function __construct(AbstractRememberMeServices $rememberMeServices, string $secret, TokenStorageInterface $tokenStorage, array $options)
{
$this->rememberMeServices = $rememberMeServices;
$this->secret = $secret;
$this->tokenStorage = $tokenStorage;
- $this->options = $options;
- $this->sessionStrategy = $sessionStrategy;
+ $this->options = array_merge($this->options, $options);
}
public function supports(Request $request): ?bool
@@ -87,6 +86,12 @@ class RememberMeAuthenticator implements AuthenticatorInterface
return $this->rememberMeServices->performLogin($credentials['cookie_parts'], $credentials['request']);
}
+ public function checkCredentials($credentials, UserInterface $user): bool
+ {
+ // remember me always is valid (if a user could be found)
+ return true;
+ }
+
public function createAuthenticatedToken(UserInterface $user, string $providerKey): TokenInterface
{
return new RememberMeToken($user, $providerKey, $this->secret);
@@ -101,10 +106,6 @@ class RememberMeAuthenticator implements AuthenticatorInterface
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?Response
{
- if ($request->hasSession() && $request->getSession()->isStarted()) {
- $this->sessionStrategy->onAuthentication($request, $token);
- }
-
return null;
}
}
diff --git a/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php b/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php
index c97b722ff1..28800e6260 100644
--- a/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php
+++ b/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php
@@ -36,7 +36,7 @@ class PasswordMigratingListener implements EventSubscriberInterface
return;
}
- if (null !== $password = $authenticator->getPassword($event->getCredentials())) {
+ if (null === $password = $authenticator->getPassword($event->getCredentials())) {
return;
}
@@ -46,11 +46,11 @@ class PasswordMigratingListener implements EventSubscriberInterface
}
$passwordEncoder = $this->encoderFactory->getEncoder($user);
- if (!method_exists($passwordEncoder, 'needsRehash') || !$passwordEncoder->needsRehash($user)) {
+ if (!$passwordEncoder->needsRehash($user->getPassword())) {
return;
}
- $authenticator->upgradePassword($user, $passwordEncoder->encodePassword($user, $password));
+ $authenticator->upgradePassword($user, $passwordEncoder->encodePassword($password, $user->getSalt()));
}
public static function getSubscribedEvents(): array
diff --git a/src/Symfony/Component/Security/Http/EventListener/RememberMeListener.php b/src/Symfony/Component/Security/Http/EventListener/RememberMeListener.php
index 522f5090d6..72ce7c13f9 100644
--- a/src/Symfony/Component/Security/Http/EventListener/RememberMeListener.php
+++ b/src/Symfony/Component/Security/Http/EventListener/RememberMeListener.php
@@ -39,7 +39,15 @@ class RememberMeListener implements EventSubscriberInterface
public function onSuccessfulLogin(LoginSuccessEvent $event): void
{
- if (!$this->isRememberMeEnabled($event->getAuthenticator(), $event->getProviderKey())) {
+ if (!$this->isRememberMeEnabled($event->getProviderKey(), $event->getAuthenticator())) {
+ return;
+ }
+
+ if (null === $event->getResponse()) {
+ if (null !== $this->logger) {
+ $this->logger->debug('Remember me skipped: the authenticator did not set a success response.', ['authenticator' => \get_class($event->getAuthenticator())]);
+ }
+
return;
}
@@ -48,21 +56,21 @@ class RememberMeListener implements EventSubscriberInterface
public function onFailedLogin(LoginFailureEvent $event): void
{
- if (!$this->isRememberMeEnabled($event->getAuthenticator(), $event->getProviderKey())) {
+ if (!$this->isRememberMeEnabled($event->getProviderKey())) {
return;
}
$this->rememberMeServices->loginFail($event->getRequest(), $event->getException());
}
- private function isRememberMeEnabled(AuthenticatorInterface $authenticator, string $providerKey): bool
+ private function isRememberMeEnabled(string $providerKey, ?AuthenticatorInterface $authenticator = null): bool
{
if ($providerKey !== $this->providerKey) {
// This listener is created for a different firewall.
return false;
}
- if (!$authenticator instanceof RememberMeAuthenticatorInterface || !$authenticator->supportsRememberMe()) {
+ if (null !== $authenticator && (!$authenticator instanceof RememberMeAuthenticatorInterface || !$authenticator->supportsRememberMe())) {
if (null !== $this->logger) {
$this->logger->debug('Remember me skipped: your authenticator does not support it.', ['authenticator' => \get_class($authenticator)]);
}
diff --git a/src/Symfony/Component/Security/Http/EventListener/UserCheckerListener.php b/src/Symfony/Component/Security/Http/EventListener/UserCheckerListener.php
index c0c6c6895d..8ebbaca709 100644
--- a/src/Symfony/Component/Security/Http/EventListener/UserCheckerListener.php
+++ b/src/Symfony/Component/Security/Http/EventListener/UserCheckerListener.php
@@ -23,11 +23,19 @@ class UserCheckerListener implements EventSubscriberInterface
public function preCredentialsVerification(VerifyAuthenticatorCredentialsEvent $event): void
{
+ if (null === $event->getUser()) {
+ return;
+ }
+
$this->userChecker->checkPreAuth($event->getUser());
}
public function postCredentialsVerification(VerifyAuthenticatorCredentialsEvent $event): void
{
+ if (null === $event->getUser() || !$event->areCredentialsValid()) {
+ return;
+ }
+
$this->userChecker->checkPostAuth($event->getUser());
}
diff --git a/src/Symfony/Component/Security/Http/EventListener/VerifyAuthenticatorCredentialsListener.php b/src/Symfony/Component/Security/Http/EventListener/VerifyAuthenticatorCredentialsListener.php
index c8ab235f79..77bbb39ec9 100644
--- a/src/Symfony/Component/Security/Http/EventListener/VerifyAuthenticatorCredentialsListener.php
+++ b/src/Symfony/Component/Security/Http/EventListener/VerifyAuthenticatorCredentialsListener.php
@@ -31,6 +31,10 @@ class VerifyAuthenticatorCredentialsListener implements EventSubscriberInterface
public function onAuthenticating(VerifyAuthenticatorCredentialsEvent $event): void
{
+ if ($event->areCredentialsValid()) {
+ return;
+ }
+
$authenticator = $event->getAuthenticator();
if ($authenticator instanceof PasswordAuthenticatedInterface) {
// Use the password encoder to validate the credentials
diff --git a/src/Symfony/Component/Security/Http/Tests/Authentication/AuthenticatorManagerTest.php b/src/Symfony/Component/Security/Http/Tests/Authentication/AuthenticatorManagerTest.php
new file mode 100644
index 0000000000..46dc09e2f8
--- /dev/null
+++ b/src/Symfony/Component/Security/Http/Tests/Authentication/AuthenticatorManagerTest.php
@@ -0,0 +1,225 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Http\Tests\Authentication;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
+use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
+use Symfony\Component\Security\Core\Exception\BadCredentialsException;
+use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
+use Symfony\Component\Security\Core\User\UserInterface;
+use Symfony\Component\Security\Http\Authentication\AuthenticatorManager;
+use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
+use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
+use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
+use Symfony\Component\Security\Http\Event\VerifyAuthenticatorCredentialsEvent;
+use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
+
+class AuthenticatorManagerTest extends TestCase
+{
+ private $tokenStorage;
+ private $eventDispatcher;
+ private $request;
+ private $user;
+ private $token;
+ private $response;
+
+ protected function setUp(): void
+ {
+ $this->tokenStorage = $this->createMock(TokenStorageInterface::class);
+ $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class);
+ $this->request = new Request();
+ $this->user = $this->createMock(UserInterface::class);
+ $this->token = $this->createMock(TokenInterface::class);
+ $this->response = $this->createMock(Response::class);
+ }
+
+ /**
+ * @dataProvider provideSupportsData
+ */
+ public function testSupports($authenticators, $result)
+ {
+ $manager = $this->createManager($authenticators);
+
+ $this->assertEquals($result, $manager->supports($this->request));
+ }
+
+ public function provideSupportsData()
+ {
+ yield [[$this->createAuthenticator(null), $this->createAuthenticator(null)], null];
+ yield [[$this->createAuthenticator(null), $this->createAuthenticator(false)], null];
+
+ yield [[$this->createAuthenticator(null), $this->createAuthenticator(true)], true];
+ yield [[$this->createAuthenticator(true), $this->createAuthenticator(false)], true];
+
+ yield [[$this->createAuthenticator(false), $this->createAuthenticator(false)], false];
+ yield [[], false];
+ }
+
+ public function testSupportCheckedUponRequestAuthentication()
+ {
+ // the attribute stores the supported authenticators, returning false now
+ // means support changed between calling supports() and authenticateRequest()
+ // (which is the case with lazy firewalls and e.g. the AnonymousAuthenticator)
+ $authenticator = $this->createAuthenticator(false);
+ $this->request->attributes->set('_guard_authenticators', [$authenticator]);
+
+ $authenticator->expects($this->never())->method('getCredentials');
+
+ $manager = $this->createManager([$authenticator]);
+ $manager->authenticateRequest($this->request);
+ }
+
+ /**
+ * @dataProvider provideMatchingAuthenticatorIndex
+ */
+ public function testAuthenticateRequest($matchingAuthenticatorIndex)
+ {
+ $authenticators = [$this->createAuthenticator(0 === $matchingAuthenticatorIndex), $this->createAuthenticator(1 === $matchingAuthenticatorIndex)];
+ $this->request->attributes->set('_guard_authenticators', $authenticators);
+ $matchingAuthenticator = $authenticators[$matchingAuthenticatorIndex];
+
+ $authenticators[($matchingAuthenticatorIndex + 1) % 2]->expects($this->never())->method('getCredentials');
+
+ $matchingAuthenticator->expects($this->any())->method('getCredentials')->willReturn(['password' => 'pa$$']);
+ $matchingAuthenticator->expects($this->any())->method('getUser')->willReturn($this->user);
+ $this->eventDispatcher->expects($this->exactly(4))
+ ->method('dispatch')
+ ->with($this->callback(function ($event) use ($matchingAuthenticator) {
+ if ($event instanceof VerifyAuthenticatorCredentialsEvent) {
+ return $event->getAuthenticator() === $matchingAuthenticator
+ && $event->getCredentials() === ['password' => 'pa$$']
+ && $event->getUser() === $this->user;
+ }
+
+ return $event instanceof InteractiveLoginEvent || $event instanceof LoginSuccessEvent || $event instanceof AuthenticationSuccessEvent;
+ }))
+ ->will($this->returnCallback(function ($event) {
+ if ($event instanceof VerifyAuthenticatorCredentialsEvent) {
+ $event->setCredentialsValid(true);
+ }
+
+ return $event;
+ }));
+ $matchingAuthenticator->expects($this->any())->method('createAuthenticatedToken')->willReturn($this->token);
+
+ $this->tokenStorage->expects($this->once())->method('setToken')->with($this->token);
+
+ $matchingAuthenticator->expects($this->any())
+ ->method('onAuthenticationSuccess')
+ ->with($this->anything(), $this->token, 'main')
+ ->willReturn($this->response);
+
+ $manager = $this->createManager($authenticators);
+ $this->assertSame($this->response, $manager->authenticateRequest($this->request));
+ }
+
+ public function provideMatchingAuthenticatorIndex()
+ {
+ yield [0];
+ yield [1];
+ }
+
+ public function testUserNotFound()
+ {
+ $authenticator = $this->createAuthenticator();
+ $this->request->attributes->set('_guard_authenticators', [$authenticator]);
+
+ $authenticator->expects($this->any())->method('getCredentials')->willReturn(['username' => 'john']);
+ $authenticator->expects($this->any())->method('getUser')->with(['username' => 'john'])->willReturn(null);
+
+ $authenticator->expects($this->once())
+ ->method('onAuthenticationFailure')
+ ->with($this->request, $this->isInstanceOf(UsernameNotFoundException::class));
+
+ $manager = $this->createManager([$authenticator]);
+ $manager->authenticateRequest($this->request);
+ }
+
+ public function testNoCredentialsValidated()
+ {
+ $authenticator = $this->createAuthenticator();
+ $this->request->attributes->set('_guard_authenticators', [$authenticator]);
+
+ $authenticator->expects($this->any())->method('getCredentials')->willReturn(['username' => 'john']);
+ $authenticator->expects($this->any())->method('getUser')->willReturn($this->user);
+
+ $authenticator->expects($this->once())
+ ->method('onAuthenticationFailure')
+ ->with($this->request, $this->isInstanceOf(BadCredentialsException::class));
+
+ $manager = $this->createManager([$authenticator]);
+ $manager->authenticateRequest($this->request);
+ }
+
+ /**
+ * @dataProvider provideEraseCredentialsData
+ */
+ public function testEraseCredentials($eraseCredentials)
+ {
+ $authenticator = $this->createAuthenticator();
+ $this->request->attributes->set('_guard_authenticators', [$authenticator]);
+
+ $authenticator->expects($this->any())->method('getCredentials')->willReturn(['username' => 'john']);
+ $authenticator->expects($this->any())->method('getUser')->willReturn($this->user);
+ $this->eventDispatcher->expects($this->any())
+ ->method('dispatch')
+ ->will($this->returnCallback(function ($event) {
+ if ($event instanceof VerifyAuthenticatorCredentialsEvent) {
+ $event->setCredentialsValid(true);
+ }
+
+ return $event;
+ }));
+
+ $authenticator->expects($this->any())->method('createAuthenticatedToken')->willReturn($this->token);
+
+ $this->token->expects($eraseCredentials ? $this->once() : $this->never())->method('eraseCredentials');
+
+ $manager = $this->createManager([$authenticator], 'main', $eraseCredentials);
+ $manager->authenticateRequest($this->request);
+ }
+
+ public function provideEraseCredentialsData()
+ {
+ yield [true];
+ yield [false];
+ }
+
+ public function testAuthenticateUser()
+ {
+ $authenticator = $this->createAuthenticator();
+ $authenticator->expects($this->any())->method('createAuthenticatedToken')->willReturn($this->token);
+ $authenticator->expects($this->any())->method('onAuthenticationSuccess')->willReturn($this->response);
+
+ $this->tokenStorage->expects($this->once())->method('setToken')->with($this->token);
+
+ $manager = $this->createManager([$authenticator]);
+ $this->assertSame($this->response, $manager->authenticateUser($this->user, $authenticator, $this->request));
+ }
+
+ private function createAuthenticator($supports = true)
+ {
+ $authenticator = $this->createMock(AuthenticatorInterface::class);
+ $authenticator->expects($this->any())->method('supports')->willReturn($supports);
+
+ return $authenticator;
+ }
+
+ private function createManager($authenticators, $providerKey = 'main', $eraseCredentials = true)
+ {
+ return new AuthenticatorManager($authenticators, $this->tokenStorage, $this->eventDispatcher, $providerKey, null, $eraseCredentials);
+ }
+}
diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/AnonymousAuthenticatorTest.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/AnonymousAuthenticatorTest.php
new file mode 100644
index 0000000000..f5d1cfdf98
--- /dev/null
+++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/AnonymousAuthenticatorTest.php
@@ -0,0 +1,61 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Http\Tests\Authenticator;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
+use Symfony\Component\Security\Core\User\UserInterface;
+use Symfony\Component\Security\Http\Authenticator\AnonymousAuthenticator;
+
+class AnonymousAuthenticatorTest extends TestCase
+{
+ private $tokenStorage;
+ private $authenticator;
+ private $request;
+
+ protected function setUp(): void
+ {
+ $this->tokenStorage = $this->createMock(TokenStorageInterface::class);
+ $this->authenticator = new AnonymousAuthenticator('s3cr3t', $this->tokenStorage);
+ $this->request = new Request();
+ }
+
+ /**
+ * @dataProvider provideSupportsData
+ */
+ public function testSupports($tokenAlreadyAvailable, $result)
+ {
+ $this->tokenStorage->expects($this->any())->method('getToken')->willReturn($tokenAlreadyAvailable ? $this->createMock(TokenStorageInterface::class) : null);
+
+ $this->assertEquals($result, $this->authenticator->supports($this->request));
+ }
+
+ public function provideSupportsData()
+ {
+ yield [true, null];
+ yield [false, false];
+ }
+
+ public function testAlwaysValidCredentials()
+ {
+ $this->assertTrue($this->authenticator->checkCredentials([], $this->createMock(UserInterface::class)));
+ }
+
+ public function testAuthenticatedToken()
+ {
+ $token = $this->authenticator->createAuthenticatedToken($this->authenticator->getUser([]), 'main');
+
+ $this->assertTrue($token->isAuthenticated());
+ $this->assertEquals('anon.', $token->getUser());
+ }
+}
diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/FormLoginAuthenticatorTest.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/FormLoginAuthenticatorTest.php
new file mode 100644
index 0000000000..058508f25e
--- /dev/null
+++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/FormLoginAuthenticatorTest.php
@@ -0,0 +1,141 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Http\Tests\Authenticator;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
+use Symfony\Component\Security\Core\Exception\BadCredentialsException;
+use Symfony\Component\Security\Core\Security;
+use Symfony\Component\Security\Core\User\UserProviderInterface;
+use Symfony\Component\Security\Http\Authenticator\FormLoginAuthenticator;
+use Symfony\Component\Security\Http\HttpUtils;
+
+class FormLoginAuthenticatorTest extends TestCase
+{
+ private $userProvider;
+ private $authenticator;
+
+ protected function setUp(): void
+ {
+ $this->userProvider = $this->createMock(UserProviderInterface::class);
+ }
+
+ /**
+ * @dataProvider provideUsernamesForLength
+ */
+ public function testHandleWhenUsernameLength($username, $ok)
+ {
+ if ($ok) {
+ $this->expectNotToPerformAssertions();
+ } else {
+ $this->expectException(BadCredentialsException::class);
+ $this->expectExceptionMessage('Invalid username.');
+ }
+
+ $request = Request::create('/login_check', 'POST', ['_username' => $username]);
+ $request->setSession($this->createSession());
+
+ $this->setUpAuthenticator();
+ $this->authenticator->getCredentials($request);
+ }
+
+ public function provideUsernamesForLength()
+ {
+ yield [str_repeat('x', Security::MAX_USERNAME_LENGTH + 1), false];
+ yield [str_repeat('x', Security::MAX_USERNAME_LENGTH - 1), true];
+ }
+
+ /**
+ * @dataProvider postOnlyDataProvider
+ */
+ public function testHandleNonStringUsernameWithArray($postOnly)
+ {
+ $this->expectException(BadRequestHttpException::class);
+ $this->expectExceptionMessage('The key "_username" must be a string, "array" given.');
+
+ $request = Request::create('/login_check', 'POST', ['_username' => []]);
+ $request->setSession($this->createSession());
+
+ $this->setUpAuthenticator(['post_only' => $postOnly]);
+ $this->authenticator->getCredentials($request);
+ }
+
+ /**
+ * @dataProvider postOnlyDataProvider
+ */
+ public function testHandleNonStringUsernameWithInt($postOnly)
+ {
+ $this->expectException(BadRequestHttpException::class);
+ $this->expectExceptionMessage('The key "_username" must be a string, "integer" given.');
+
+ $request = Request::create('/login_check', 'POST', ['_username' => 42]);
+ $request->setSession($this->createSession());
+
+ $this->setUpAuthenticator(['post_only' => $postOnly]);
+ $this->authenticator->getCredentials($request);
+ }
+
+ /**
+ * @dataProvider postOnlyDataProvider
+ */
+ public function testHandleNonStringUsernameWithObject($postOnly)
+ {
+ $this->expectException(BadRequestHttpException::class);
+ $this->expectExceptionMessage('The key "_username" must be a string, "object" given.');
+
+ $request = Request::create('/login_check', 'POST', ['_username' => new \stdClass()]);
+ $request->setSession($this->createSession());
+
+ $this->setUpAuthenticator(['post_only' => $postOnly]);
+ $this->authenticator->getCredentials($request);
+ }
+
+ /**
+ * @dataProvider postOnlyDataProvider
+ */
+ public function testHandleNonStringUsernameWith__toString($postOnly)
+ {
+ $usernameObject = $this->getMockBuilder(DummyUserClass::class)->getMock();
+ $usernameObject->expects($this->once())->method('__toString')->willReturn('someUsername');
+
+ $request = Request::create('/login_check', 'POST', ['_username' => $usernameObject]);
+ $request->setSession($this->createSession());
+
+ $this->setUpAuthenticator(['post_only' => $postOnly]);
+ $this->authenticator->getCredentials($request);
+ }
+
+ public function postOnlyDataProvider()
+ {
+ yield [true];
+ yield [false];
+ }
+
+ private function setUpAuthenticator(array $options = [])
+ {
+ $this->authenticator = new FormLoginAuthenticator(new HttpUtils(), $this->userProvider, $options);
+ }
+
+ private function createSession()
+ {
+ return $this->createMock('Symfony\Component\HttpFoundation\Session\SessionInterface');
+ }
+}
+
+class DummyUserClass
+{
+ public function __toString(): string
+ {
+ return '';
+ }
+}
diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Authenticator/HttpBasicAuthenticatorTest.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/HttpBasicAuthenticatorTest.php
similarity index 52%
rename from src/Symfony/Component/Security/Core/Tests/Authentication/Authenticator/HttpBasicAuthenticatorTest.php
rename to src/Symfony/Component/Security/Http/Tests/Authenticator/HttpBasicAuthenticatorTest.php
index b713840441..e2ac0ac991 100644
--- a/src/Symfony/Component/Security/Core/Tests/Authentication/Authenticator/HttpBasicAuthenticatorTest.php
+++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/HttpBasicAuthenticatorTest.php
@@ -1,6 +1,6 @@
expects($this->any())
->method('getEncoder')
->willReturn($this->encoder);
+
+ $this->authenticator = new HttpBasicAuthenticator('test', $this->userProvider);
}
- public function testValidUsernameAndPasswordServerParameters()
+ public function testExtractCredentialsAndUserFromRequest()
{
$request = new Request([], [], [], [], [], [
'PHP_AUTH_USER' => 'TheUsername',
'PHP_AUTH_PW' => 'ThePassword',
]);
- $authenticator = new HttpBasicAuthenticator('test', $this->userProvider, $this->encoderFactory);
- $credentials = $authenticator->getCredentials($request);
+ $credentials = $this->authenticator->getCredentials($request);
$this->assertEquals([
'username' => 'TheUsername',
'password' => 'ThePassword',
@@ -55,53 +54,20 @@ class HttpBasicAuthenticatorTest extends TestCase
->with('TheUsername')
->willReturn($mockedUser);
- $user = $authenticator->getUser($credentials, $this->userProvider);
+ $user = $this->authenticator->getUser($credentials);
$this->assertSame($mockedUser, $user);
- $this->encoder
- ->expects($this->any())
- ->method('isPasswordValid')
- ->with('ThePassword', 'ThePassword', null)
- ->willReturn(true);
-
- $checkCredentials = $authenticator->checkCredentials($credentials, $user);
- $this->assertTrue($checkCredentials);
+ $this->assertEquals('ThePassword', $this->authenticator->getPassword($credentials));
}
- /** @dataProvider provideInvalidPasswords */
- public function testInvalidPassword($presentedPassword, $exceptionMessage)
- {
- $authenticator = new HttpBasicAuthenticator('test', $this->userProvider, $this->encoderFactory);
-
- $this->encoder
- ->expects($this->any())
- ->method('isPasswordValid')
- ->willReturn(false);
-
- $this->expectException(BadCredentialsException::class);
- $this->expectExceptionMessage($exceptionMessage);
-
- $authenticator->checkCredentials([
- 'username' => 'TheUsername',
- 'password' => $presentedPassword,
- ], $this->getMockBuilder(UserInterface::class)->getMock());
- }
-
- public function provideInvalidPasswords()
- {
- return [
- ['InvalidPassword', 'The presented password is invalid.'],
- ['', 'The presented password cannot be empty.'],
- ];
- }
-
- /** @dataProvider provideMissingHttpBasicServerParameters */
+ /**
+ * @dataProvider provideMissingHttpBasicServerParameters
+ */
public function testHttpBasicServerParametersMissing(array $serverParameters)
{
$request = new Request([], [], [], [], [], $serverParameters);
- $authenticator = new HttpBasicAuthenticator('test', $this->userProvider, $this->encoderFactory);
- $this->assertFalse($authenticator->supports($request));
+ $this->assertFalse($this->authenticator->supports($request));
}
public function provideMissingHttpBasicServerParameters()
diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/RememberMeAuthenticatorTest.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/RememberMeAuthenticatorTest.php
new file mode 100644
index 0000000000..9bd11ab62d
--- /dev/null
+++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/RememberMeAuthenticatorTest.php
@@ -0,0 +1,92 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Http\Tests\Authenticator;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Cookie;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
+use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+use Symfony\Component\Security\Core\User\UserInterface;
+use Symfony\Component\Security\Http\Authenticator\RememberMeAuthenticator;
+use Symfony\Component\Security\Http\RememberMe\AbstractRememberMeServices;
+
+class RememberMeAuthenticatorTest extends TestCase
+{
+ private $rememberMeServices;
+ private $tokenStorage;
+ private $authenticator;
+ private $request;
+
+ protected function setUp(): void
+ {
+ $this->rememberMeServices = $this->createMock(AbstractRememberMeServices::class);
+ $this->tokenStorage = $this->createMock(TokenStorage::class);
+ $this->authenticator = new RememberMeAuthenticator($this->rememberMeServices, 's3cr3t', $this->tokenStorage, [
+ 'name' => '_remember_me_cookie',
+ ]);
+ $this->request = new Request();
+ $this->request->cookies->set('_remember_me_cookie', $val = $this->generateCookieValue());
+ $this->request->attributes->set(AbstractRememberMeServices::COOKIE_ATTR_NAME, new Cookie('_remember_me_cookie', $val));
+ }
+
+ public function testSupportsTokenStorageWithToken()
+ {
+ $this->tokenStorage->expects($this->any())->method('getToken')->willReturn(TokenInterface::class);
+
+ $this->assertFalse($this->authenticator->supports($this->request));
+ }
+
+ public function testSupportsRequestWithoutAttribute()
+ {
+ $this->request->attributes->remove(AbstractRememberMeServices::COOKIE_ATTR_NAME);
+
+ $this->assertNull($this->authenticator->supports($this->request));
+ }
+
+ public function testSupportsRequestWithoutCookie()
+ {
+ $this->request->cookies->remove('_remember_me_cookie');
+
+ $this->assertFalse($this->authenticator->supports($this->request));
+ }
+
+ public function testSupports()
+ {
+ $this->assertNull($this->authenticator->supports($this->request));
+ }
+
+ public function testAuthenticate()
+ {
+ $credentials = $this->authenticator->getCredentials($this->request);
+ $this->assertEquals(['part1', 'part2'], $credentials['cookie_parts']);
+ $this->assertSame($this->request, $credentials['request']);
+
+ $user = $this->createMock(UserInterface::class);
+ $this->rememberMeServices->expects($this->any())
+ ->method('performLogin')
+ ->with($credentials['cookie_parts'], $credentials['request'])
+ ->willReturn($user);
+
+ $this->assertSame($user, $this->authenticator->getUser($credentials));
+ }
+
+ public function testCredentialsAlwaysValid()
+ {
+ $this->assertTrue($this->authenticator->checkCredentials([], $this->createMock(UserInterface::class)));
+ }
+
+ private function generateCookieValue()
+ {
+ return base64_encode(implode(AbstractRememberMeServices::COOKIE_DELIMITER, ['part1', 'part2']));
+ }
+}
diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/CsrfProtectionListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/CsrfProtectionListenerTest.php
new file mode 100644
index 0000000000..0c2a15d952
--- /dev/null
+++ b/src/Symfony/Component/Security/Http/Tests/EventListener/CsrfProtectionListenerTest.php
@@ -0,0 +1,89 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Http\Tests\EventListener;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
+use Symfony\Component\Security\Csrf\CsrfToken;
+use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
+use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
+use Symfony\Component\Security\Http\Authenticator\CsrfProtectedAuthenticatorInterface;
+use Symfony\Component\Security\Http\Event\VerifyAuthenticatorCredentialsEvent;
+use Symfony\Component\Security\Http\EventListener\CsrfProtectionListener;
+
+class CsrfProtectionListenerTest extends TestCase
+{
+ private $csrfTokenManager;
+ private $listener;
+
+ protected function setUp(): void
+ {
+ $this->csrfTokenManager = $this->createMock(CsrfTokenManagerInterface::class);
+ $this->listener = new CsrfProtectionListener($this->csrfTokenManager);
+ }
+
+ public function testNonCsrfProtectedAuthenticator()
+ {
+ $this->csrfTokenManager->expects($this->never())->method('isTokenValid');
+
+ $event = $this->createEvent($this->createAuthenticator(false));
+ $this->listener->verifyCredentials($event);
+ }
+
+ public function testValidCsrfToken()
+ {
+ $this->csrfTokenManager->expects($this->any())
+ ->method('isTokenValid')
+ ->with(new CsrfToken('authenticator_token_id', 'abc123'))
+ ->willReturn(true);
+
+ $event = $this->createEvent($this->createAuthenticator(true), ['_csrf' => 'abc123']);
+ $this->listener->verifyCredentials($event);
+
+ $this->expectNotToPerformAssertions();
+ }
+
+ public function testInvalidCsrfToken()
+ {
+ $this->expectException(InvalidCsrfTokenException::class);
+ $this->expectExceptionMessage('Invalid CSRF token.');
+
+ $this->csrfTokenManager->expects($this->any())
+ ->method('isTokenValid')
+ ->with(new CsrfToken('authenticator_token_id', 'abc123'))
+ ->willReturn(false);
+
+ $event = $this->createEvent($this->createAuthenticator(true), ['_csrf' => 'abc123']);
+ $this->listener->verifyCredentials($event);
+ }
+
+ private function createEvent($authenticator, $credentials = null)
+ {
+ return new VerifyAuthenticatorCredentialsEvent($authenticator, $credentials, null);
+ }
+
+ private function createAuthenticator($supportsCsrf)
+ {
+ if (!$supportsCsrf) {
+ return $this->createMock(AuthenticatorInterface::class);
+ }
+
+ $authenticator = $this->createMock([AuthenticatorInterface::class, CsrfProtectedAuthenticatorInterface::class]);
+ $authenticator->expects($this->any())->method('getCsrfTokenId')->willReturn('authenticator_token_id');
+ $authenticator->expects($this->any())
+ ->method('getCsrfToken')
+ ->with(['_csrf' => 'abc123'])
+ ->willReturn('abc123');
+
+ return $authenticator;
+ }
+}
diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/PasswordMigratingListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/PasswordMigratingListenerTest.php
new file mode 100644
index 0000000000..37d9ee23cc
--- /dev/null
+++ b/src/Symfony/Component/Security/Http/Tests/EventListener/PasswordMigratingListenerTest.php
@@ -0,0 +1,101 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Http\Tests\EventListener;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
+use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
+use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
+use Symfony\Component\Security\Core\User\UserInterface;
+use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
+use Symfony\Component\Security\Http\Authenticator\PasswordAuthenticatedInterface;
+use Symfony\Component\Security\Http\Event\VerifyAuthenticatorCredentialsEvent;
+use Symfony\Component\Security\Http\EventListener\PasswordMigratingListener;
+
+class PasswordMigratingListenerTest extends TestCase
+{
+ private $encoderFactory;
+ private $listener;
+ private $user;
+
+ protected function setUp(): void
+ {
+ $this->encoderFactory = $this->createMock(EncoderFactoryInterface::class);
+ $this->listener = new PasswordMigratingListener($this->encoderFactory);
+ $this->user = $this->createMock(UserInterface::class);
+ }
+
+ /**
+ * @dataProvider provideUnsupportedEvents
+ */
+ public function testUnsupportedEvents($event)
+ {
+ $this->encoderFactory->expects($this->never())->method('getEncoder');
+
+ $this->listener->onCredentialsVerification($event);
+ }
+
+ public function provideUnsupportedEvents()
+ {
+ // unsupported authenticators
+ yield [$this->createEvent($this->createMock(AuthenticatorInterface::class), $this->user)];
+ yield [$this->createEvent($this->createMock([AuthenticatorInterface::class, PasswordAuthenticatedInterface::class]), $this->user)];
+
+ // null password
+ yield [$this->createEvent($this->createAuthenticator(null), $this->user)];
+
+ // no user
+ yield [$this->createEvent($this->createAuthenticator('pa$$word'), null)];
+
+ // invalid password
+ yield [$this->createEvent($this->createAuthenticator('pa$$word'), $this->user, false)];
+ }
+
+ public function testUpgrade()
+ {
+ $encoder = $this->createMock(PasswordEncoderInterface::class);
+ $encoder->expects($this->any())->method('needsRehash')->willReturn(true);
+ $encoder->expects($this->any())->method('encodePassword')->with('pa$$word', null)->willReturn('new-encoded-password');
+
+ $this->encoderFactory->expects($this->any())->method('getEncoder')->with($this->user)->willReturn($encoder);
+
+ $this->user->expects($this->any())->method('getPassword')->willReturn('old-encoded-password');
+
+ $authenticator = $this->createAuthenticator('pa$$word');
+ $authenticator->expects($this->once())
+ ->method('upgradePassword')
+ ->with($this->user, 'new-encoded-password')
+ ;
+
+ $event = $this->createEvent($authenticator, $this->user);
+ $this->listener->onCredentialsVerification($event);
+ }
+
+ /**
+ * @return AuthenticatorInterface
+ */
+ private function createAuthenticator($password)
+ {
+ $authenticator = $this->createMock([AuthenticatorInterface::class, PasswordAuthenticatedInterface::class, PasswordUpgraderInterface::class]);
+ $authenticator->expects($this->any())->method('getPassword')->willReturn($password);
+
+ return $authenticator;
+ }
+
+ private function createEvent($authenticator, $user, $credentialsValid = true)
+ {
+ $event = new VerifyAuthenticatorCredentialsEvent($authenticator, [], $user);
+ $event->setCredentialsValid($credentialsValid);
+
+ return $event;
+ }
+}
diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/RememberMeListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/RememberMeListenerTest.php
new file mode 100644
index 0000000000..910c67a0bd
--- /dev/null
+++ b/src/Symfony/Component/Security/Http/Tests/EventListener/RememberMeListenerTest.php
@@ -0,0 +1,101 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Http\Tests\EventListener;
+
+use PHPUnit\Framework\TestCase;
+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\Http\Authenticator\AuthenticatorInterface;
+use Symfony\Component\Security\Http\Authenticator\RememberMeAuthenticatorInterface;
+use Symfony\Component\Security\Http\Event\LoginFailureEvent;
+use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
+use Symfony\Component\Security\Http\EventListener\RememberMeListener;
+use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
+
+class RememberMeListenerTest extends TestCase
+{
+ private $rememberMeServices;
+ private $listener;
+ private $request;
+ private $response;
+ private $token;
+
+ protected function setUp(): void
+ {
+ $this->rememberMeServices = $this->createMock(RememberMeServicesInterface::class);
+ $this->listener = new RememberMeListener($this->rememberMeServices);
+ $this->request = $this->getMockBuilder(Request::class)->disableOriginalConstructor()->getMock();
+ $this->response = $this->createMock(Response::class);
+ $this->token = $this->createMock(TokenInterface::class);
+ }
+
+ /**
+ * @dataProvider provideUnsupportingAuthenticators
+ */
+ public function testSuccessfulLoginWithoutSupportingAuthenticator($authenticator)
+ {
+ $this->rememberMeServices->expects($this->never())->method('loginSuccess');
+
+ $event = $this->createLoginSuccessfulEvent('main_firewall', $this->response, $authenticator);
+ $this->listener->onSuccessfulLogin($event);
+ }
+
+ public function provideUnsupportingAuthenticators()
+ {
+ yield [$this->createMock(AuthenticatorInterface::class)];
+
+ $authenticator = $this->createMock([AuthenticatorInterface::class, RememberMeAuthenticatorInterface::class]);
+ $authenticator->expects($this->any())->method('supportsRememberMe')->willReturn(false);
+ yield [$authenticator];
+ }
+
+ public function testSuccessfulLoginWithoutSuccessResponse()
+ {
+ $this->rememberMeServices->expects($this->never())->method('loginSuccess');
+
+ $event = $this->createLoginSuccessfulEvent('main_firewall', null);
+ $this->listener->onSuccessfulLogin($event);
+ }
+
+ public function testSuccessfulLogin()
+ {
+ $this->rememberMeServices->expects($this->once())->method('loginSuccess')->with($this->request, $this->response, $this->token);
+
+ $event = $this->createLoginSuccessfulEvent('main_firewall', $this->response);
+ $this->listener->onSuccessfulLogin($event);
+ }
+
+ public function testCredentialsInvalid()
+ {
+ $this->rememberMeServices->expects($this->once())->method('loginFail')->with($this->request, $this->isInstanceOf(AuthenticationException::class));
+
+ $event = $this->createLoginFailureEvent('main_firewall');
+ $this->listener->onFailedLogin($event);
+ }
+
+ private function createLoginSuccessfulEvent($providerKey, $response, $authenticator = null)
+ {
+ if (null === $authenticator) {
+ $authenticator = $this->createMock([AuthenticatorInterface::class, RememberMeAuthenticatorInterface::class]);
+ $authenticator->expects($this->any())->method('supportsRememberMe')->willReturn(true);
+ }
+
+ return new LoginSuccessEvent($authenticator, $this->token, $this->request, $response, $providerKey);
+ }
+
+ private function createLoginFailureEvent($providerKey)
+ {
+ return new LoginFailureEvent(new AuthenticationException(), $this->createMock(AuthenticatorInterface::class), $this->request, null, $providerKey);
+ }
+}
diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/SessionListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/SessionListenerTest.php
new file mode 100644
index 0000000000..176921d1a1
--- /dev/null
+++ b/src/Symfony/Component/Security/Http/Tests/EventListener/SessionListenerTest.php
@@ -0,0 +1,75 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Http\Tests\EventListener;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
+use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
+use Symfony\Component\Security\Http\EventListener\SessionStrategyListener;
+use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
+
+class SessionListenerTest extends TestCase
+{
+ private $sessionAuthenticationStrategy;
+ private $listener;
+ private $request;
+ private $token;
+
+ protected function setUp(): void
+ {
+ $this->sessionAuthenticationStrategy = $this->createMock(SessionAuthenticationStrategyInterface::class);
+ $this->listener = new SessionStrategyListener($this->sessionAuthenticationStrategy);
+ $this->request = new Request();
+ $this->token = $this->createMock(TokenInterface::class);
+ }
+
+ public function testRequestWithSession()
+ {
+ $this->configurePreviousSession();
+
+ $this->sessionAuthenticationStrategy->expects($this->once())->method('onAuthentication')->with($this->request, $this->token);
+
+ $this->listener->onSuccessfulLogin($this->createEvent('main_firewall'));
+ }
+
+ public function testRequestWithoutPreviousSession()
+ {
+ $this->sessionAuthenticationStrategy->expects($this->never())->method('onAuthentication')->with($this->request, $this->token);
+
+ $this->listener->onSuccessfulLogin($this->createEvent('main_firewall'));
+ }
+
+ public function testStatelessFirewalls()
+ {
+ $this->sessionAuthenticationStrategy->expects($this->never())->method('onAuthentication');
+
+ $listener = new SessionStrategyListener($this->sessionAuthenticationStrategy, ['api_firewall']);
+ $listener->onSuccessfulLogin($this->createEvent('api_firewall'));
+ }
+
+ private function createEvent($providerKey)
+ {
+ return new LoginSuccessEvent($this->createMock(AuthenticatorInterface::class), $this->token, $this->request, null, $providerKey);
+ }
+
+ private function configurePreviousSession()
+ {
+ $session = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\SessionInterface')->getMock();
+ $session->expects($this->any())
+ ->method('getName')
+ ->willReturn('test_session_name');
+ $this->request->setSession($session);
+ $this->request->cookies->set('test_session_name', 'session_cookie_val');
+ }
+}
diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/UserCheckerListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/UserCheckerListenerTest.php
new file mode 100644
index 0000000000..785a312963
--- /dev/null
+++ b/src/Symfony/Component/Security/Http/Tests/EventListener/UserCheckerListenerTest.php
@@ -0,0 +1,78 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Http\Tests\EventListener;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Security\Core\User\UserCheckerInterface;
+use Symfony\Component\Security\Core\User\UserInterface;
+use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
+use Symfony\Component\Security\Http\Event\VerifyAuthenticatorCredentialsEvent;
+use Symfony\Component\Security\Http\EventListener\UserCheckerListener;
+
+class UserCheckerListenerTest extends TestCase
+{
+ private $userChecker;
+ private $listener;
+ private $user;
+
+ protected function setUp(): void
+ {
+ $this->userChecker = $this->createMock(UserCheckerInterface::class);
+ $this->listener = new UserCheckerListener($this->userChecker);
+ $this->user = $this->createMock(UserInterface::class);
+ }
+
+ public function testPreAuth()
+ {
+ $this->userChecker->expects($this->once())->method('checkPreAuth')->with($this->user);
+
+ $this->listener->preCredentialsVerification($this->createEvent());
+ }
+
+ public function testPreAuthNoUser()
+ {
+ $this->userChecker->expects($this->never())->method('checkPreAuth');
+
+ $this->listener->preCredentialsVerification($this->createEvent(true, null));
+ }
+
+ public function testPostAuthValidCredentials()
+ {
+ $this->userChecker->expects($this->once())->method('checkPostAuth')->with($this->user);
+
+ $this->listener->postCredentialsVerification($this->createEvent(true));
+ }
+
+ public function testPostAuthInvalidCredentials()
+ {
+ $this->userChecker->expects($this->never())->method('checkPostAuth')->with($this->user);
+
+ $this->listener->postCredentialsVerification($this->createEvent());
+ }
+
+ public function testPostAuthNoUser()
+ {
+ $this->userChecker->expects($this->never())->method('checkPostAuth');
+
+ $this->listener->postCredentialsVerification($this->createEvent(true, null));
+ }
+
+ private function createEvent($credentialsValid = false, $customUser = false)
+ {
+ $event = new VerifyAuthenticatorCredentialsEvent($this->createMock(AuthenticatorInterface::class), [], false === $customUser ? $this->user : $customUser);
+ if ($credentialsValid) {
+ $event->setCredentialsValid(true);
+ }
+
+ return $event;
+ }
+}
diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/VerifyAuthenticatorCredentialsListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/VerifyAuthenticatorCredentialsListenerTest.php
new file mode 100644
index 0000000000..e2c2cc6605
--- /dev/null
+++ b/src/Symfony/Component/Security/Http/Tests/EventListener/VerifyAuthenticatorCredentialsListenerTest.php
@@ -0,0 +1,167 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Http\Tests\EventListener;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
+use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
+use Symfony\Component\Security\Core\Exception\BadCredentialsException;
+use Symfony\Component\Security\Core\Exception\LogicException;
+use Symfony\Component\Security\Core\User\UserInterface;
+use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
+use Symfony\Component\Security\Http\Authenticator\CustomAuthenticatedInterface;
+use Symfony\Component\Security\Http\Authenticator\PasswordAuthenticatedInterface;
+use Symfony\Component\Security\Http\Authenticator\TokenAuthenticatedInterface;
+use Symfony\Component\Security\Http\Event\VerifyAuthenticatorCredentialsEvent;
+use Symfony\Component\Security\Http\EventListener\VerifyAuthenticatorCredentialsListener;
+
+class VerifyAuthenticatorCredentialsListenerTest extends TestCase
+{
+ private $encoderFactory;
+ private $listener;
+ private $user;
+
+ protected function setUp(): void
+ {
+ $this->encoderFactory = $this->createMock(EncoderFactoryInterface::class);
+ $this->listener = new VerifyAuthenticatorCredentialsListener($this->encoderFactory);
+ $this->user = $this->createMock(UserInterface::class);
+ }
+
+ /**
+ * @dataProvider providePasswords
+ */
+ public function testPasswordAuthenticated($password, $passwordValid, $result)
+ {
+ $this->user->expects($this->any())->method('getPassword')->willReturn('encoded-password');
+
+ $encoder = $this->createMock(PasswordEncoderInterface::class);
+ $encoder->expects($this->any())->method('isPasswordValid')->with('encoded-password', $password)->willReturn($passwordValid);
+
+ $this->encoderFactory->expects($this->any())->method('getEncoder')->with($this->identicalTo($this->user))->willReturn($encoder);
+
+ $event = new VerifyAuthenticatorCredentialsEvent($this->createAuthenticator('password', $password), ['password' => $password], $this->user);
+ $this->listener->onAuthenticating($event);
+ $this->assertEquals($result, $event->areCredentialsValid());
+ }
+
+ public function providePasswords()
+ {
+ yield ['ThePa$$word', true, true];
+ yield ['Invalid', false, false];
+ }
+
+ public function testEmptyPassword()
+ {
+ $this->expectException(BadCredentialsException::class);
+ $this->expectExceptionMessage('The presented password cannot be empty.');
+
+ $this->encoderFactory->expects($this->never())->method('getEncoder');
+
+ $event = new VerifyAuthenticatorCredentialsEvent($this->createAuthenticator('password', ''), ['password' => ''], $this->user);
+ $this->listener->onAuthenticating($event);
+ }
+
+ public function testTokenAuthenticated()
+ {
+ $this->encoderFactory->expects($this->never())->method('getEncoder');
+
+ $event = new VerifyAuthenticatorCredentialsEvent($this->createAuthenticator('token', 'some_token'), ['token' => 'abc'], $this->user);
+ $this->listener->onAuthenticating($event);
+
+ $this->assertTrue($event->areCredentialsValid());
+ }
+
+ public function testTokenAuthenticatedReturningNull()
+ {
+ $this->encoderFactory->expects($this->never())->method('getEncoder');
+
+ $event = new VerifyAuthenticatorCredentialsEvent($this->createAuthenticator('token', null), ['token' => 'abc'], $this->user);
+ $this->listener->onAuthenticating($event);
+
+ $this->assertFalse($event->areCredentialsValid());
+ }
+
+ /**
+ * @dataProvider provideCustomAuthenticatedResults
+ */
+ public function testCustomAuthenticated($result)
+ {
+ $this->encoderFactory->expects($this->never())->method('getEncoder');
+
+ $event = new VerifyAuthenticatorCredentialsEvent($this->createAuthenticator('custom', $result), [], $this->user);
+ $this->listener->onAuthenticating($event);
+
+ $this->assertEquals($result, $event->areCredentialsValid());
+ }
+
+ public function provideCustomAuthenticatedResults()
+ {
+ yield [true];
+ yield [false];
+ }
+
+ public function testAlreadyAuthenticated()
+ {
+ $event = new VerifyAuthenticatorCredentialsEvent($this->createAuthenticator(), [], $this->user);
+ $event->setCredentialsValid(true);
+ $this->listener->onAuthenticating($event);
+
+ $this->assertTrue($event->areCredentialsValid());
+ }
+
+ public function testNoAuthenticatedInterfaceImplemented()
+ {
+ $authenticator = $this->createAuthenticator();
+ $this->expectException(LogicException::class);
+ $this->expectExceptionMessage(sprintf('Authenticator %s does not have valid credentials. Authenticators must implement one of the authenticated interfaces (%s, %s or %s).', \get_class($authenticator), PasswordAuthenticatedInterface::class, TokenAuthenticatedInterface::class, CustomAuthenticatedInterface::class));
+
+ $this->encoderFactory->expects($this->never())->method('getEncoder');
+
+ $event = new VerifyAuthenticatorCredentialsEvent($authenticator, [], $this->user);
+ $this->listener->onAuthenticating($event);
+ }
+
+ /**
+ * @return AuthenticatorInterface
+ */
+ private function createAuthenticator(?string $type = null, $result = null)
+ {
+ $interfaces = [AuthenticatorInterface::class];
+ switch ($type) {
+ case 'password':
+ $interfaces[] = PasswordAuthenticatedInterface::class;
+ break;
+ case 'token':
+ $interfaces[] = TokenAuthenticatedInterface::class;
+ break;
+ case 'custom':
+ $interfaces[] = CustomAuthenticatedInterface::class;
+ break;
+ }
+
+ $authenticator = $this->createMock(1 === \count($interfaces) ? $interfaces[0] : $interfaces);
+ switch ($type) {
+ case 'password':
+ $authenticator->expects($this->any())->method('getPassword')->willReturn($result);
+ break;
+ case 'token':
+ $authenticator->expects($this->any())->method('getToken')->willReturn($result);
+ break;
+ case 'custom':
+ $authenticator->expects($this->any())->method('checkCredentials')->willReturn($result);
+ break;
+ }
+
+ return $authenticator;
+ }
+}