Added tests

This commit is contained in:
Wouter de Jong 2020-03-07 18:06:29 +01:00
parent 59f49b20ca
commit 6b9d78d5e0
21 changed files with 1193 additions and 107 deletions

View File

@ -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;
}

View File

@ -84,7 +84,6 @@
abstract="true">
<argument type="abstract">realm name</argument>
<argument type="abstract">user provider</argument>
<argument type="service" id="security.encoder_factory" />
<argument type="service" id="logger" on-invalid="null" />
</service>
@ -92,7 +91,6 @@
class="Symfony\Component\Security\Http\Authenticator\FormLoginAuthenticator"
abstract="true">
<argument type="service" id="security.http_utils" />
<argument /> <!-- csrf token generator -->
<argument type="abstract">user provider</argument>
<argument type="abstract">options</argument>
</service>

View File

@ -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);

View File

@ -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']);

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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)]);
}

View File

@ -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());
}

View File

@ -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

View File

@ -0,0 +1,225 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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);
}
}

View File

@ -0,0 +1,61 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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());
}
}

View File

@ -0,0 +1,141 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 '';
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace Symfony\Component\Security\Core\Tests\Authentication\Authenticator;
namespace Symfony\Component\Security\Http\Tests\Authenticator;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
@ -14,12 +14,10 @@ use Symfony\Component\Security\Http\Authenticator\HttpBasicAuthenticator;
class HttpBasicAuthenticatorTest extends TestCase
{
/** @var UserProviderInterface|MockObject */
private $userProvider;
/** @var EncoderFactoryInterface|MockObject */
private $encoderFactory;
/** @var PasswordEncoderInterface|MockObject */
private $encoder;
private $authenticator;
protected function setUp(): void
{
@ -30,17 +28,18 @@ class HttpBasicAuthenticatorTest extends TestCase
->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()

View File

@ -0,0 +1,92 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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']));
}
}

View File

@ -0,0 +1,89 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}
}

View File

@ -0,0 +1,101 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}
}

View File

@ -0,0 +1,101 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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);
}
}

View File

@ -0,0 +1,75 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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');
}
}

View File

@ -0,0 +1,78 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}
}

View File

@ -0,0 +1,167 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}
}