Skip to content

Commit f4b88e2

Browse files
committed
[Security] Add concept of required passport badges
A badge on a passport is a critical security element, it determines which security checks are run during authentication. Using the `required_badges` setting, applications can make sure the expected security checks are run.
1 parent 512e30b commit f4b88e2

File tree

2 files changed

+46
-3
lines changed

2 files changed

+46
-3
lines changed

Authentication/AuthenticatorManager.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,18 +50,20 @@ class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthent
5050
private $eraseCredentials;
5151
private $logger;
5252
private $firewallName;
53+
private $requiredBadges;
5354

5455
/**
5556
* @param AuthenticatorInterface[] $authenticators
5657
*/
57-
public function __construct(iterable $authenticators, TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher, string $firewallName, ?LoggerInterface $logger = null, bool $eraseCredentials = true)
58+
public function __construct(iterable $authenticators, TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher, string $firewallName, ?LoggerInterface $logger = null, bool $eraseCredentials = true, array $requiredBadges = [])
5859
{
5960
$this->authenticators = $authenticators;
6061
$this->tokenStorage = $tokenStorage;
6162
$this->eventDispatcher = $eventDispatcher;
6263
$this->firewallName = $firewallName;
6364
$this->logger = $logger;
6465
$this->eraseCredentials = $eraseCredentials;
66+
$this->requiredBadges = $requiredBadges;
6567
}
6668

6769
/**
@@ -170,10 +172,18 @@ private function executeAuthenticator(AuthenticatorInterface $authenticator, Req
170172
$this->eventDispatcher->dispatch($event);
171173

172174
// check if all badges are resolved
175+
$resolvedBadges = [];
173176
foreach ($passport->getBadges() as $badge) {
174177
if (!$badge->isResolved()) {
175178
throw new BadCredentialsException(sprintf('Authentication failed: Security badge "%s" is not resolved, did you forget to register the correct listeners?', get_debug_type($badge)));
176179
}
180+
181+
$resolvedBadges[] = \get_class($badge);
182+
}
183+
184+
$missingRequiredBadges = array_diff($this->requiredBadges, $resolvedBadges);
185+
if ($missingRequiredBadges) {
186+
throw new BadCredentialsException(sprintf('Authentication failed; Some badges marked as required by the firewall config are not available on the passport: "%s".', implode('", "', $missingRequiredBadges)));
177187
}
178188

179189
// create the authenticated token

Tests/Authentication/AuthenticatorManagerTest.php

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
use Symfony\Component\HttpFoundation\Response;
1818
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
1919
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
20+
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
2021
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
2122
use Symfony\Component\Security\Core\User\InMemoryUser;
2223
use Symfony\Component\Security\Http\Authentication\AuthenticatorManager;
2324
use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface;
25+
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
2426
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
2527
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
2628
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
@@ -133,6 +135,37 @@ public function testNoCredentialsValidated()
133135
$manager->authenticateRequest($this->request);
134136
}
135137

138+
public function testRequiredBadgeMissing()
139+
{
140+
$authenticator = $this->createAuthenticator();
141+
$this->request->attributes->set('_security_authenticators', [$authenticator]);
142+
143+
$authenticator->expects($this->any())->method('authenticate')->willReturn(new SelfValidatingPassport(new UserBadge('wouter')));
144+
145+
$authenticator->expects($this->once())->method('onAuthenticationFailure')->with($this->anything(), $this->callback(function ($exception) {
146+
return 'Authentication failed; Some badges marked as required by the firewall config are not available on the passport: "'.CsrfTokenBadge::class.'".' === $exception->getMessage();
147+
}));
148+
149+
$manager = $this->createManager([$authenticator], 'main', true, [CsrfTokenBadge::class]);
150+
$manager->authenticateRequest($this->request);
151+
}
152+
153+
public function testAllRequiredBadgesPresent()
154+
{
155+
$authenticator = $this->createAuthenticator();
156+
$this->request->attributes->set('_security_authenticators', [$authenticator]);
157+
158+
$csrfBadge = new CsrfTokenBadge('csrfid', 'csrftoken');
159+
$csrfBadge->markResolved();
160+
$authenticator->expects($this->any())->method('authenticate')->willReturn(new SelfValidatingPassport(new UserBadge('wouter'), [$csrfBadge]));
161+
$authenticator->expects($this->any())->method('createAuthenticatedToken')->willReturn(new UsernamePasswordToken($this->user, null, 'main'));
162+
163+
$authenticator->expects($this->once())->method('onAuthenticationSuccess');
164+
165+
$manager = $this->createManager([$authenticator], 'main', true, [CsrfTokenBadge::class]);
166+
$manager->authenticateRequest($this->request);
167+
}
168+
136169
/**
137170
* @dataProvider provideEraseCredentialsData
138171
*/
@@ -243,8 +276,8 @@ private function createAuthenticator($supports = true)
243276
return $authenticator;
244277
}
245278

246-
private function createManager($authenticators, $firewallName = 'main', $eraseCredentials = true)
279+
private function createManager($authenticators, $firewallName = 'main', $eraseCredentials = true, array $requiredBadges = [])
247280
{
248-
return new AuthenticatorManager($authenticators, $this->tokenStorage, $this->eventDispatcher, $firewallName, null, $eraseCredentials);
281+
return new AuthenticatorManager($authenticators, $this->tokenStorage, $this->eventDispatcher, $firewallName, null, $eraseCredentials, $requiredBadges);
249282
}
250283
}

0 commit comments

Comments
 (0)