Skip to content

Commit c55a8f7

Browse files
Merge branch '4.4' into 5.2
* 4.4: [CI][Psalm] Install stable/released PHPUnit [Security] Add missing Finnish translations [Security][Guard] Prevent user enumeration via response content
2 parents 5c56326 + 2b2e821 commit c55a8f7

File tree

2 files changed

+34
-1
lines changed

2 files changed

+34
-1
lines changed

Authentication/AuthenticatorManager.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@
1818
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
1919
use Symfony\Component\Security\Core\AuthenticationEvents;
2020
use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
21+
use Symfony\Component\Security\Core\Exception\AccountStatusException;
2122
use Symfony\Component\Security\Core\Exception\AuthenticationException;
23+
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
24+
use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException;
25+
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
2226
use Symfony\Component\Security\Core\User\UserInterface;
2327
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
2428
use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface;
@@ -49,18 +53,20 @@ class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthent
4953
private $eraseCredentials;
5054
private $logger;
5155
private $firewallName;
56+
private $hideUserNotFoundExceptions;
5257

5358
/**
5459
* @param AuthenticatorInterface[] $authenticators
5560
*/
56-
public function __construct(iterable $authenticators, TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher, string $firewallName, ?LoggerInterface $logger = null, bool $eraseCredentials = true)
61+
public function __construct(iterable $authenticators, TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher, string $firewallName, ?LoggerInterface $logger = null, bool $eraseCredentials = true, bool $hideUserNotFoundExceptions = true)
5762
{
5863
$this->authenticators = $authenticators;
5964
$this->tokenStorage = $tokenStorage;
6065
$this->eventDispatcher = $eventDispatcher;
6166
$this->firewallName = $firewallName;
6267
$this->logger = $logger;
6368
$this->eraseCredentials = $eraseCredentials;
69+
$this->hideUserNotFoundExceptions = $hideUserNotFoundExceptions;
6470
}
6571

6672
/**
@@ -232,6 +238,12 @@ private function handleAuthenticationFailure(AuthenticationException $authentica
232238
$this->logger->info('Authenticator failed.', ['exception' => $authenticationException, 'authenticator' => \get_class($authenticator)]);
233239
}
234240

241+
// Avoid leaking error details in case of invalid user (e.g. user not found or invalid account status)
242+
// to prevent user enumeration via response content comparison
243+
if ($this->hideUserNotFoundExceptions && ($authenticationException instanceof UsernameNotFoundException || ($authenticationException instanceof AccountStatusException && !$authenticationException instanceof CustomUserMessageAccountStatusException))) {
244+
$authenticationException = new BadCredentialsException('Bad credentials.', 0, $authenticationException);
245+
}
246+
235247
$response = $authenticator->onAuthenticationFailure($request, $authenticationException);
236248
if (null !== $response && null !== $this->logger) {
237249
$this->logger->debug('The "{authenticator}" authenticator set the failure response.', ['authenticator' => \get_class($authenticator)]);

Tests/Authentication/AuthenticatorManagerTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
1919
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
2020
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
21+
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
2122
use Symfony\Component\Security\Core\User\User;
2223
use Symfony\Component\Security\Http\Authentication\AuthenticatorManager;
2324
use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface;
@@ -232,6 +233,26 @@ public function testInteractiveAuthenticator()
232233
$this->assertSame($this->response, $response);
233234
}
234235

236+
public function testAuthenticateRequestHidesInvalidUserExceptions()
237+
{
238+
$invalidUserException = new UsernameNotFoundException();
239+
$authenticator = $this->createMock(InteractiveAuthenticatorInterface::class);
240+
$this->request->attributes->set('_security_authenticators', [$authenticator]);
241+
242+
$authenticator->expects($this->any())->method('authenticate')->willThrowException($invalidUserException);
243+
244+
$authenticator->expects($this->any())
245+
->method('onAuthenticationFailure')
246+
->with($this->equalTo($this->request), $this->callback(function ($e) use ($invalidUserException) {
247+
return $e instanceof BadCredentialsException && $invalidUserException === $e->getPrevious();
248+
}))
249+
->willReturn($this->response);
250+
251+
$manager = $this->createManager([$authenticator]);
252+
$response = $manager->authenticateRequest($this->request);
253+
$this->assertSame($this->response, $response);
254+
}
255+
235256
private function createAuthenticator($supports = true)
236257
{
237258
$authenticator = $this->createMock(InteractiveAuthenticatorInterface::class);

0 commit comments

Comments
 (0)