Skip to content

Commit cddb20d

Browse files
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
2 parents 202ffe5 + c55a8f7 commit cddb20d

File tree

2 files changed

+34
-2
lines changed

2 files changed

+34
-2
lines changed

Authentication/AuthenticatorManager.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +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;
2223
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
24+
use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException;
25+
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
2326
use Symfony\Component\Security\Core\User\UserInterface;
2427
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
2528
use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface;
@@ -48,19 +51,21 @@ class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthent
4851
private $eraseCredentials;
4952
private $logger;
5053
private $firewallName;
54+
private $hideUserNotFoundExceptions;
5155
private $requiredBadges;
5256

5357
/**
5458
* @param AuthenticatorInterface[] $authenticators
5559
*/
56-
public function __construct(iterable $authenticators, TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher, string $firewallName, ?LoggerInterface $logger = null, bool $eraseCredentials = true, array $requiredBadges = [])
60+
public function __construct(iterable $authenticators, TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher, string $firewallName, ?LoggerInterface $logger = null, bool $eraseCredentials = true, bool $hideUserNotFoundExceptions = true, array $requiredBadges = [])
5761
{
5862
$this->authenticators = $authenticators;
5963
$this->tokenStorage = $tokenStorage;
6064
$this->eventDispatcher = $eventDispatcher;
6165
$this->firewallName = $firewallName;
6266
$this->logger = $logger;
6367
$this->eraseCredentials = $eraseCredentials;
68+
$this->hideUserNotFoundExceptions = $hideUserNotFoundExceptions;
6469
$this->requiredBadges = $requiredBadges;
6570
}
6671

@@ -251,6 +256,12 @@ private function handleAuthenticationFailure(AuthenticationException $authentica
251256
$this->logger->info('Authenticator failed.', ['exception' => $authenticationException, 'authenticator' => \get_class($authenticator)]);
252257
}
253258

259+
// Avoid leaking error details in case of invalid user (e.g. user not found or invalid account status)
260+
// to prevent user enumeration via response content comparison
261+
if ($this->hideUserNotFoundExceptions && ($authenticationException instanceof UsernameNotFoundException || ($authenticationException instanceof AccountStatusException && !$authenticationException instanceof CustomUserMessageAccountStatusException))) {
262+
$authenticationException = new BadCredentialsException('Bad credentials.', 0, $authenticationException);
263+
}
264+
254265
$response = $authenticator->onAuthenticationFailure($request, $authenticationException);
255266
if (null !== $response && null !== $this->logger) {
256267
$this->logger->debug('The "{authenticator}" authenticator set the failure response.', ['authenticator' => \get_class($authenticator)]);

Tests/Authentication/AuthenticatorManagerTest.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
2020
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
2121
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
22+
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
2223
use Symfony\Component\Security\Core\User\InMemoryUser;
2324
use Symfony\Component\Security\Http\Authentication\AuthenticatorManager;
2425
use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface;
@@ -268,6 +269,26 @@ public function testInteractiveAuthenticator()
268269
$this->assertSame($this->response, $response);
269270
}
270271

272+
public function testAuthenticateRequestHidesInvalidUserExceptions()
273+
{
274+
$invalidUserException = new UsernameNotFoundException();
275+
$authenticator = $this->createMock(InteractiveAuthenticatorInterface::class);
276+
$this->request->attributes->set('_security_authenticators', [$authenticator]);
277+
278+
$authenticator->expects($this->any())->method('authenticate')->willThrowException($invalidUserException);
279+
280+
$authenticator->expects($this->any())
281+
->method('onAuthenticationFailure')
282+
->with($this->equalTo($this->request), $this->callback(function ($e) use ($invalidUserException) {
283+
return $e instanceof BadCredentialsException && $invalidUserException === $e->getPrevious();
284+
}))
285+
->willReturn($this->response);
286+
287+
$manager = $this->createManager([$authenticator]);
288+
$response = $manager->authenticateRequest($this->request);
289+
$this->assertSame($this->response, $response);
290+
}
291+
271292
private function createAuthenticator($supports = true)
272293
{
273294
$authenticator = $this->createMock(InteractiveAuthenticatorInterface::class);
@@ -278,6 +299,6 @@ private function createAuthenticator($supports = true)
278299

279300
private function createManager($authenticators, $firewallName = 'main', $eraseCredentials = true, array $requiredBadges = [])
280301
{
281-
return new AuthenticatorManager($authenticators, $this->tokenStorage, $this->eventDispatcher, $firewallName, null, $eraseCredentials, $requiredBadges);
302+
return new AuthenticatorManager($authenticators, $this->tokenStorage, $this->eventDispatcher, $firewallName, null, $eraseCredentials, true, $requiredBadges);
282303
}
283304
}

0 commit comments

Comments
 (0)