Merge branch '5.2' into 5.x
* 5.2: [CI][Psalm] Install stable/released PHPUnit [Security] Add missing Finnish translations [Security][Guard] Prevent user enumeration via response content
This commit is contained in:
commit
8fb0ed752e
2
.github/workflows/psalm.yml
vendored
2
.github/workflows/psalm.yml
vendored
@ -39,7 +39,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "::group::modify composer.json"
|
echo "::group::modify composer.json"
|
||||||
composer remove --no-update --no-interaction symfony/phpunit-bridge
|
composer remove --no-update --no-interaction symfony/phpunit-bridge
|
||||||
composer require --no-update psalm/phar phpunit/phpunit php-http/discovery psr/event-dispatcher
|
composer require --no-update psalm/phar phpunit/phpunit:@stable php-http/discovery psr/event-dispatcher
|
||||||
echo "::endgroup::"
|
echo "::endgroup::"
|
||||||
echo "::group::composer update"
|
echo "::group::composer update"
|
||||||
composer update --no-progress --ansi
|
composer update --no-progress --ansi
|
||||||
|
@ -502,7 +502,7 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
|
|||||||
->replaceArgument(0, $authenticators)
|
->replaceArgument(0, $authenticators)
|
||||||
->replaceArgument(2, new Reference($firewallEventDispatcherId))
|
->replaceArgument(2, new Reference($firewallEventDispatcherId))
|
||||||
->replaceArgument(3, $id)
|
->replaceArgument(3, $id)
|
||||||
->replaceArgument(6, $firewall['required_badges'] ?? [])
|
->replaceArgument(7, $firewall['required_badges'] ?? [])
|
||||||
->addTag('monolog.logger', ['channel' => 'security'])
|
->addTag('monolog.logger', ['channel' => 'security'])
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ -45,6 +45,7 @@ return static function (ContainerConfigurator $container) {
|
|||||||
abstract_arg('Provider-shared Key'),
|
abstract_arg('Provider-shared Key'),
|
||||||
abstract_arg('Authenticators'),
|
abstract_arg('Authenticators'),
|
||||||
service('logger')->nullOnInvalid(),
|
service('logger')->nullOnInvalid(),
|
||||||
|
param('security.authentication.hide_user_not_found'),
|
||||||
])
|
])
|
||||||
->tag('monolog.logger', ['channel' => 'security'])
|
->tag('monolog.logger', ['channel' => 'security'])
|
||||||
;
|
;
|
||||||
|
@ -44,6 +44,7 @@ return static function (ContainerConfigurator $container) {
|
|||||||
abstract_arg('provider key'),
|
abstract_arg('provider key'),
|
||||||
service('logger')->nullOnInvalid(),
|
service('logger')->nullOnInvalid(),
|
||||||
param('security.authentication.manager.erase_credentials'),
|
param('security.authentication.manager.erase_credentials'),
|
||||||
|
param('security.authentication.hide_user_not_found'),
|
||||||
abstract_arg('required badges'),
|
abstract_arg('required badges'),
|
||||||
])
|
])
|
||||||
->tag('monolog.logger', ['channel' => 'security'])
|
->tag('monolog.logger', ['channel' => 'security'])
|
||||||
|
@ -43,7 +43,7 @@ abstract class CompleteConfigurationTest extends TestCase
|
|||||||
$this->assertEquals(AuthenticatorManager::class, $authenticatorManager->getClass());
|
$this->assertEquals(AuthenticatorManager::class, $authenticatorManager->getClass());
|
||||||
|
|
||||||
// required badges
|
// required badges
|
||||||
$this->assertEquals([CsrfTokenBadge::class, RememberMeBadge::class], $authenticatorManager->getArgument(6));
|
$this->assertEquals([CsrfTokenBadge::class, RememberMeBadge::class], $authenticatorManager->getArgument(7));
|
||||||
|
|
||||||
// login link
|
// login link
|
||||||
$expiredStorage = $container->getDefinition($expiredStorageId = 'security.authenticator.expired_login_link_storage.main');
|
$expiredStorage = $container->getDefinition($expiredStorageId = 'security.authenticator.expired_login_link_storage.main');
|
||||||
|
@ -40,7 +40,7 @@ class AuthenticatorTest extends AbstractWebTestCase
|
|||||||
if ($withinFirewall) {
|
if ($withinFirewall) {
|
||||||
$this->assertJsonStringEqualsJsonString('{"email":"'.$email.'"}', $client->getResponse()->getContent());
|
$this->assertJsonStringEqualsJsonString('{"email":"'.$email.'"}', $client->getResponse()->getContent());
|
||||||
} else {
|
} else {
|
||||||
$this->assertJsonStringEqualsJsonString('{"error":"Username could not be found."}', $client->getResponse()->getContent());
|
$this->assertJsonStringEqualsJsonString('{"error":"Invalid credentials."}', $client->getResponse()->getContent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,7 +142,7 @@ class FormLoginTest extends AbstractWebTestCase
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case 2: // Third attempt with unexisting username
|
case 2: // Third attempt with unexisting username
|
||||||
$this->assertStringContainsString('Username could not be found.', $text, 'Invalid response on 3rd attempt');
|
$this->assertStringContainsString('Invalid credentials.', $text, 'Invalid response on 3rd attempt');
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 3: // Fourth attempt : still login throttling !
|
case 3: // Fourth attempt : still login throttling !
|
||||||
|
@ -14,6 +14,7 @@ namespace Symfony\Component\Security\Core\Authentication\Provider;
|
|||||||
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
|
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||||
|
use Symfony\Component\Security\Core\Exception\AccountStatusException;
|
||||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||||
use Symfony\Component\Security\Core\Exception\AuthenticationServiceException;
|
use Symfony\Component\Security\Core\Exception\AuthenticationServiceException;
|
||||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||||
@ -79,7 +80,7 @@ abstract class UserAuthenticationProvider implements AuthenticationProviderInter
|
|||||||
$this->userChecker->checkPreAuth($user);
|
$this->userChecker->checkPreAuth($user);
|
||||||
$this->checkAuthentication($user, $token);
|
$this->checkAuthentication($user, $token);
|
||||||
$this->userChecker->checkPostAuth($user);
|
$this->userChecker->checkPostAuth($user);
|
||||||
} catch (BadCredentialsException $e) {
|
} catch (AccountStatusException $e) {
|
||||||
if ($this->hideUserNotFoundExceptions) {
|
if ($this->hideUserNotFoundExceptions) {
|
||||||
throw new BadCredentialsException('Bad credentials.', 0, $e);
|
throw new BadCredentialsException('Bad credentials.', 0, $e);
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,14 @@
|
|||||||
<source>Invalid or expired login link.</source>
|
<source>Invalid or expired login link.</source>
|
||||||
<target>Virheellinen tai vanhentunut kirjautumislinkki.</target>
|
<target>Virheellinen tai vanhentunut kirjautumislinkki.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="19">
|
||||||
|
<source>Too many failed login attempts, please try again in %minutes% minute.</source>
|
||||||
|
<target>Liian monta epäonnistunutta kirjautumisyritystä, yritä uudelleen %minutes% minuutin kuluttua.</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="20">
|
||||||
|
<source>Too many failed login attempts, please try again in %minutes% minutes.</source>
|
||||||
|
<target>Liian monta epäonnistunutta kirjautumisyritystä, yritä uudelleen %minutes% minuutin kuluttua.</target>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
@ -83,7 +83,7 @@ class UserAuthenticationProviderTest extends TestCase
|
|||||||
|
|
||||||
public function testAuthenticateWhenPreChecksFails()
|
public function testAuthenticateWhenPreChecksFails()
|
||||||
{
|
{
|
||||||
$this->expectException(CredentialsExpiredException::class);
|
$this->expectException(BadCredentialsException::class);
|
||||||
$userChecker = $this->createMock(UserCheckerInterface::class);
|
$userChecker = $this->createMock(UserCheckerInterface::class);
|
||||||
$userChecker->expects($this->once())
|
$userChecker->expects($this->once())
|
||||||
->method('checkPreAuth')
|
->method('checkPreAuth')
|
||||||
@ -101,7 +101,7 @@ class UserAuthenticationProviderTest extends TestCase
|
|||||||
|
|
||||||
public function testAuthenticateWhenPostChecksFails()
|
public function testAuthenticateWhenPostChecksFails()
|
||||||
{
|
{
|
||||||
$this->expectException(AccountExpiredException::class);
|
$this->expectException(BadCredentialsException::class);
|
||||||
$userChecker = $this->createMock(UserCheckerInterface::class);
|
$userChecker = $this->createMock(UserCheckerInterface::class);
|
||||||
$userChecker->expects($this->once())
|
$userChecker->expects($this->once())
|
||||||
->method('checkPostAuth')
|
->method('checkPostAuth')
|
||||||
@ -128,7 +128,7 @@ class UserAuthenticationProviderTest extends TestCase
|
|||||||
;
|
;
|
||||||
$provider->expects($this->once())
|
$provider->expects($this->once())
|
||||||
->method('checkAuthentication')
|
->method('checkAuthentication')
|
||||||
->willThrowException(new BadCredentialsException())
|
->willThrowException(new CredentialsExpiredException())
|
||||||
;
|
;
|
||||||
|
|
||||||
$provider->authenticate($this->getSupportedToken());
|
$provider->authenticate($this->getSupportedToken());
|
||||||
|
@ -17,7 +17,11 @@ use Symfony\Component\HttpFoundation\Response;
|
|||||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||||
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
|
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
|
use Symfony\Component\Security\Core\Exception\AccountStatusException;
|
||||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||||
|
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||||
|
use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException;
|
||||||
|
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
|
||||||
use Symfony\Component\Security\Guard\AuthenticatorInterface;
|
use Symfony\Component\Security\Guard\AuthenticatorInterface;
|
||||||
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
|
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
|
||||||
use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken;
|
use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken;
|
||||||
@ -40,12 +44,13 @@ class GuardAuthenticationListener extends AbstractListener
|
|||||||
private $guardAuthenticators;
|
private $guardAuthenticators;
|
||||||
private $logger;
|
private $logger;
|
||||||
private $rememberMeServices;
|
private $rememberMeServices;
|
||||||
|
private $hideUserNotFoundExceptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $providerKey The provider (i.e. firewall) key
|
* @param string $providerKey The provider (i.e. firewall) key
|
||||||
* @param iterable|AuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationProvider
|
* @param iterable|AuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationProvider
|
||||||
*/
|
*/
|
||||||
public function __construct(GuardAuthenticatorHandler $guardHandler, AuthenticationManagerInterface $authenticationManager, string $providerKey, iterable $guardAuthenticators, LoggerInterface $logger = null)
|
public function __construct(GuardAuthenticatorHandler $guardHandler, AuthenticationManagerInterface $authenticationManager, string $providerKey, iterable $guardAuthenticators, LoggerInterface $logger = null, bool $hideUserNotFoundExceptions = true)
|
||||||
{
|
{
|
||||||
if (empty($providerKey)) {
|
if (empty($providerKey)) {
|
||||||
throw new \InvalidArgumentException('$providerKey must not be empty.');
|
throw new \InvalidArgumentException('$providerKey must not be empty.');
|
||||||
@ -56,6 +61,7 @@ class GuardAuthenticationListener extends AbstractListener
|
|||||||
$this->providerKey = $providerKey;
|
$this->providerKey = $providerKey;
|
||||||
$this->guardAuthenticators = $guardAuthenticators;
|
$this->guardAuthenticators = $guardAuthenticators;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
|
$this->hideUserNotFoundExceptions = $hideUserNotFoundExceptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -160,6 +166,12 @@ class GuardAuthenticationListener extends AbstractListener
|
|||||||
$this->logger->info('Guard authentication failed.', ['exception' => $e, 'authenticator' => \get_class($guardAuthenticator)]);
|
$this->logger->info('Guard authentication failed.', ['exception' => $e, 'authenticator' => \get_class($guardAuthenticator)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Avoid leaking error details in case of invalid user (e.g. user not found or invalid account status)
|
||||||
|
// to prevent user enumeration via response content
|
||||||
|
if ($this->hideUserNotFoundExceptions && ($e instanceof UsernameNotFoundException || ($e instanceof AccountStatusException && !$e instanceof CustomUserMessageAccountStatusException))) {
|
||||||
|
$e = new BadCredentialsException('Bad credentials.', 0, $e);
|
||||||
|
}
|
||||||
|
|
||||||
$response = $this->guardHandler->handleAuthenticationFailure($e, $request, $guardAuthenticator, $this->providerKey);
|
$response = $this->guardHandler->handleAuthenticationFailure($e, $request, $guardAuthenticator, $this->providerKey);
|
||||||
|
|
||||||
if ($response instanceof Response) {
|
if ($response instanceof Response) {
|
||||||
|
@ -19,6 +19,9 @@ use Symfony\Component\HttpKernel\Event\RequestEvent;
|
|||||||
use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager;
|
use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||||
|
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||||
|
use Symfony\Component\Security\Core\Exception\LockedException;
|
||||||
|
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
|
||||||
use Symfony\Component\Security\Guard\AuthenticatorInterface;
|
use Symfony\Component\Security\Guard\AuthenticatorInterface;
|
||||||
use Symfony\Component\Security\Guard\Firewall\GuardAuthenticationListener;
|
use Symfony\Component\Security\Guard\Firewall\GuardAuthenticationListener;
|
||||||
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
|
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
|
||||||
@ -211,6 +214,54 @@ class GuardAuthenticationListenerTest extends TestCase
|
|||||||
$listener($this->event);
|
$listener($this->event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider exceptionsToHide
|
||||||
|
*/
|
||||||
|
public function testHandleHidesInvalidUserExceptions(AuthenticationException $exceptionToHide)
|
||||||
|
{
|
||||||
|
$authenticator = $this->createMock(AuthenticatorInterface::class);
|
||||||
|
$providerKey = 'my_firewall2';
|
||||||
|
|
||||||
|
$authenticator
|
||||||
|
->expects($this->once())
|
||||||
|
->method('supports')
|
||||||
|
->willReturn(true);
|
||||||
|
$authenticator
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getCredentials')
|
||||||
|
->willReturn(['username' => 'robin', 'password' => 'hood']);
|
||||||
|
|
||||||
|
$this->authenticationManager
|
||||||
|
->expects($this->once())
|
||||||
|
->method('authenticate')
|
||||||
|
->willThrowException($exceptionToHide);
|
||||||
|
|
||||||
|
$this->guardAuthenticatorHandler
|
||||||
|
->expects($this->once())
|
||||||
|
->method('handleAuthenticationFailure')
|
||||||
|
->with($this->callback(function ($e) use ($exceptionToHide) {
|
||||||
|
return $e instanceof BadCredentialsException && $exceptionToHide === $e->getPrevious();
|
||||||
|
}), $this->request, $authenticator, $providerKey);
|
||||||
|
|
||||||
|
$listener = new GuardAuthenticationListener(
|
||||||
|
$this->guardAuthenticatorHandler,
|
||||||
|
$this->authenticationManager,
|
||||||
|
$providerKey,
|
||||||
|
[$authenticator],
|
||||||
|
$this->logger
|
||||||
|
);
|
||||||
|
|
||||||
|
$listener($this->event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exceptionsToHide()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[new UsernameNotFoundException()],
|
||||||
|
[new LockedException()],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public function testSupportsReturnFalseSkipAuth()
|
public function testSupportsReturnFalseSkipAuth()
|
||||||
{
|
{
|
||||||
$authenticator = $this->createMock(AuthenticatorInterface::class);
|
$authenticator = $this->createMock(AuthenticatorInterface::class);
|
||||||
|
@ -18,8 +18,11 @@ use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInt
|
|||||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
use Symfony\Component\Security\Core\AuthenticationEvents;
|
use Symfony\Component\Security\Core\AuthenticationEvents;
|
||||||
use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
|
use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
|
||||||
|
use Symfony\Component\Security\Core\Exception\AccountStatusException;
|
||||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||||
|
use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException;
|
||||||
|
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
|
||||||
use Symfony\Component\Security\Core\User\UserInterface;
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
|
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
|
||||||
use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface;
|
use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface;
|
||||||
@ -48,12 +51,13 @@ class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthent
|
|||||||
private $eraseCredentials;
|
private $eraseCredentials;
|
||||||
private $logger;
|
private $logger;
|
||||||
private $firewallName;
|
private $firewallName;
|
||||||
|
private $hideUserNotFoundExceptions;
|
||||||
private $requiredBadges;
|
private $requiredBadges;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param AuthenticatorInterface[] $authenticators
|
* @param AuthenticatorInterface[] $authenticators
|
||||||
*/
|
*/
|
||||||
public function __construct(iterable $authenticators, TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher, string $firewallName, ?LoggerInterface $logger = null, bool $eraseCredentials = true, array $requiredBadges = [])
|
public function __construct(iterable $authenticators, TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher, string $firewallName, ?LoggerInterface $logger = null, bool $eraseCredentials = true, bool $hideUserNotFoundExceptions = true, array $requiredBadges = [])
|
||||||
{
|
{
|
||||||
$this->authenticators = $authenticators;
|
$this->authenticators = $authenticators;
|
||||||
$this->tokenStorage = $tokenStorage;
|
$this->tokenStorage = $tokenStorage;
|
||||||
@ -61,6 +65,7 @@ class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthent
|
|||||||
$this->firewallName = $firewallName;
|
$this->firewallName = $firewallName;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
$this->eraseCredentials = $eraseCredentials;
|
$this->eraseCredentials = $eraseCredentials;
|
||||||
|
$this->hideUserNotFoundExceptions = $hideUserNotFoundExceptions;
|
||||||
$this->requiredBadges = $requiredBadges;
|
$this->requiredBadges = $requiredBadges;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,6 +256,12 @@ class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthent
|
|||||||
$this->logger->info('Authenticator failed.', ['exception' => $authenticationException, 'authenticator' => \get_class($authenticator)]);
|
$this->logger->info('Authenticator failed.', ['exception' => $authenticationException, 'authenticator' => \get_class($authenticator)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Avoid leaking error details in case of invalid user (e.g. user not found or invalid account status)
|
||||||
|
// to prevent user enumeration via response content comparison
|
||||||
|
if ($this->hideUserNotFoundExceptions && ($authenticationException instanceof UsernameNotFoundException || ($authenticationException instanceof AccountStatusException && !$authenticationException instanceof CustomUserMessageAccountStatusException))) {
|
||||||
|
$authenticationException = new BadCredentialsException('Bad credentials.', 0, $authenticationException);
|
||||||
|
}
|
||||||
|
|
||||||
$response = $authenticator->onAuthenticationFailure($request, $authenticationException);
|
$response = $authenticator->onAuthenticationFailure($request, $authenticationException);
|
||||||
if (null !== $response && null !== $this->logger) {
|
if (null !== $response && null !== $this->logger) {
|
||||||
$this->logger->debug('The "{authenticator}" authenticator set the failure response.', ['authenticator' => \get_class($authenticator)]);
|
$this->logger->debug('The "{authenticator}" authenticator set the failure response.', ['authenticator' => \get_class($authenticator)]);
|
||||||
|
@ -19,6 +19,7 @@ use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInt
|
|||||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||||
|
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
|
||||||
use Symfony\Component\Security\Core\User\InMemoryUser;
|
use Symfony\Component\Security\Core\User\InMemoryUser;
|
||||||
use Symfony\Component\Security\Http\Authentication\AuthenticatorManager;
|
use Symfony\Component\Security\Http\Authentication\AuthenticatorManager;
|
||||||
use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface;
|
use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface;
|
||||||
@ -268,6 +269,26 @@ class AuthenticatorManagerTest extends TestCase
|
|||||||
$this->assertSame($this->response, $response);
|
$this->assertSame($this->response, $response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testAuthenticateRequestHidesInvalidUserExceptions()
|
||||||
|
{
|
||||||
|
$invalidUserException = new UsernameNotFoundException();
|
||||||
|
$authenticator = $this->createMock(InteractiveAuthenticatorInterface::class);
|
||||||
|
$this->request->attributes->set('_security_authenticators', [$authenticator]);
|
||||||
|
|
||||||
|
$authenticator->expects($this->any())->method('authenticate')->willThrowException($invalidUserException);
|
||||||
|
|
||||||
|
$authenticator->expects($this->any())
|
||||||
|
->method('onAuthenticationFailure')
|
||||||
|
->with($this->equalTo($this->request), $this->callback(function ($e) use ($invalidUserException) {
|
||||||
|
return $e instanceof BadCredentialsException && $invalidUserException === $e->getPrevious();
|
||||||
|
}))
|
||||||
|
->willReturn($this->response);
|
||||||
|
|
||||||
|
$manager = $this->createManager([$authenticator]);
|
||||||
|
$response = $manager->authenticateRequest($this->request);
|
||||||
|
$this->assertSame($this->response, $response);
|
||||||
|
}
|
||||||
|
|
||||||
private function createAuthenticator($supports = true)
|
private function createAuthenticator($supports = true)
|
||||||
{
|
{
|
||||||
$authenticator = $this->createMock(InteractiveAuthenticatorInterface::class);
|
$authenticator = $this->createMock(InteractiveAuthenticatorInterface::class);
|
||||||
@ -278,6 +299,6 @@ class AuthenticatorManagerTest extends TestCase
|
|||||||
|
|
||||||
private function createManager($authenticators, $firewallName = 'main', $eraseCredentials = true, array $requiredBadges = [])
|
private function createManager($authenticators, $firewallName = 'main', $eraseCredentials = true, array $requiredBadges = [])
|
||||||
{
|
{
|
||||||
return new AuthenticatorManager($authenticators, $this->tokenStorage, $this->eventDispatcher, $firewallName, null, $eraseCredentials, $requiredBadges);
|
return new AuthenticatorManager($authenticators, $this->tokenStorage, $this->eventDispatcher, $firewallName, null, $eraseCredentials, true, $requiredBadges);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user