Skip to content

Commit 471c470

Browse files
committed
Introduce Passport & Badges to extend authenticators
1 parent afc2ed8 commit 471c470

File tree

53 files changed

+1174
-769
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1174
-769
lines changed

Authentication/AuthenticatorManager.php

Lines changed: 35 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
use Symfony\Component\Security\Core\AuthenticationEvents;
2020
use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
2121
use Symfony\Component\Security\Core\Exception\AuthenticationException;
22-
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
23-
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
2422
use Symfony\Component\Security\Core\User\UserInterface;
2523
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
2624
use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface;
25+
use Symfony\Component\Security\Http\Authenticator\Passport\AnonymousPassport;
26+
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface;
27+
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
28+
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
2729
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
2830
use Symfony\Component\Security\Http\Event\LoginFailureEvent;
2931
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
@@ -60,13 +62,16 @@ public function __construct(iterable $authenticators, TokenStorageInterface $tok
6062
$this->eraseCredentials = $eraseCredentials;
6163
}
6264

63-
public function authenticateUser(UserInterface $user, AuthenticatorInterface $authenticator, Request $request): ?Response
65+
/**
66+
* @param BadgeInterface[] $badges Optionally, pass some Passport badges to use for the manual login
67+
*/
68+
public function authenticateUser(UserInterface $user, AuthenticatorInterface $authenticator, Request $request, array $badges = []): ?Response
6469
{
6570
// create an authenticated token for the User
66-
$token = $authenticator->createAuthenticatedToken($user, $this->providerKey);
71+
$token = $authenticator->createAuthenticatedToken($passport = new SelfValidatingPassport($user, $badges), $this->providerKey);
6772

6873
// authenticate this in the system
69-
return $this->handleAuthenticationSuccess($token, $request, $authenticator);
74+
return $this->handleAuthenticationSuccess($token, $passport, $request, $authenticator);
7075
}
7176

7277
public function supports(Request $request): ?bool
@@ -133,7 +138,7 @@ private function executeAuthenticators(array $authenticators, Request $request):
133138
continue;
134139
}
135140

136-
$response = $this->executeAuthenticator($key, $authenticator, $request);
141+
$response = $this->executeAuthenticator($authenticator, $request);
137142
if (null !== $response) {
138143
if (null !== $this->logger) {
139144
$this->logger->debug('The "{authenticator}" authenticator set the response. Any later authenticator will not be called', ['authenticator' => \get_class($authenticator)]);
@@ -146,29 +151,35 @@ private function executeAuthenticators(array $authenticators, Request $request):
146151
return null;
147152
}
148153

149-
private function executeAuthenticator(string $uniqueAuthenticatorKey, AuthenticatorInterface $authenticator, Request $request): ?Response
154+
private function executeAuthenticator(AuthenticatorInterface $authenticator, Request $request): ?Response
150155
{
151156
try {
152-
if (null !== $this->logger) {
153-
$this->logger->debug('Calling getCredentials() on authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($authenticator)]);
154-
}
157+
// get the passport from the Authenticator
158+
$passport = $authenticator->authenticate($request);
159+
160+
// check the passport (e.g. password checking)
161+
$event = new VerifyAuthenticatorCredentialsEvent($authenticator, $passport);
162+
$this->eventDispatcher->dispatch($event);
155163

156-
// allow the authenticator to fetch authentication info from the request
157-
$credentials = $authenticator->getCredentials($request);
164+
// check if all badges are resolved
165+
$passport->checkIfCompletelyResolved();
158166

159-
if (null === $credentials) {
160-
throw new \UnexpectedValueException(sprintf('The return value of "%1$s::getCredentials()" must not be null. Return false from "%1$s::supports()" instead.', \get_class($authenticator)));
167+
// create the authenticated token
168+
$authenticatedToken = $authenticator->createAuthenticatedToken($passport, $this->providerKey);
169+
if (true === $this->eraseCredentials) {
170+
$authenticatedToken->eraseCredentials();
161171
}
162172

163-
// authenticate the credentials (e.g. check password)
164-
$token = $this->authenticateViaAuthenticator($authenticator, $credentials);
173+
if (null !== $this->eventDispatcher) {
174+
$this->eventDispatcher->dispatch(new AuthenticationSuccessEvent($authenticatedToken), AuthenticationEvents::AUTHENTICATION_SUCCESS);
175+
}
165176

166177
if (null !== $this->logger) {
167-
$this->logger->info('Authenticator successful!', ['token' => $token, 'authenticator' => \get_class($authenticator)]);
178+
$this->logger->info('Authenticator successful!', ['token' => $authenticatedToken, 'authenticator' => \get_class($authenticator)]);
168179
}
169180

170181
// success! (sets the token on the token storage, etc)
171-
$response = $this->handleAuthenticationSuccess($token, $request, $authenticator);
182+
$response = $this->handleAuthenticationSuccess($authenticatedToken, $passport, $request, $authenticator);
172183
if ($response instanceof Response) {
173184
return $response;
174185
}
@@ -189,35 +200,7 @@ private function executeAuthenticator(string $uniqueAuthenticatorKey, Authentica
189200
}
190201
}
191202

192-
private function authenticateViaAuthenticator(AuthenticatorInterface $authenticator, $credentials): TokenInterface
193-
{
194-
// get the user from the Authenticator
195-
$user = $authenticator->getUser($credentials);
196-
if (null === $user) {
197-
throw new UsernameNotFoundException(sprintf('Null returned from "%s::getUser()".', \get_class($authenticator)));
198-
}
199-
200-
$event = new VerifyAuthenticatorCredentialsEvent($authenticator, $credentials, $user);
201-
$this->eventDispatcher->dispatch($event);
202-
if (true !== $event->areCredentialsValid()) {
203-
throw new BadCredentialsException(sprintf('Authentication failed because "%s" did not approve the credentials.', \get_class($authenticator)));
204-
}
205-
206-
// turn the UserInterface into a TokenInterface
207-
$authenticatedToken = $authenticator->createAuthenticatedToken($user, $this->providerKey);
208-
209-
if (true === $this->eraseCredentials) {
210-
$authenticatedToken->eraseCredentials();
211-
}
212-
213-
if (null !== $this->eventDispatcher) {
214-
$this->eventDispatcher->dispatch(new AuthenticationSuccessEvent($authenticatedToken), AuthenticationEvents::AUTHENTICATION_SUCCESS);
215-
}
216-
217-
return $authenticatedToken;
218-
}
219-
220-
private function handleAuthenticationSuccess(TokenInterface $authenticatedToken, Request $request, AuthenticatorInterface $authenticator): ?Response
203+
private function handleAuthenticationSuccess(TokenInterface $authenticatedToken, PassportInterface $passport, Request $request, AuthenticatorInterface $authenticator): ?Response
221204
{
222205
$this->tokenStorage->setToken($authenticatedToken);
223206

@@ -227,7 +210,11 @@ private function handleAuthenticationSuccess(TokenInterface $authenticatedToken,
227210
$this->eventDispatcher->dispatch($loginEvent, SecurityEvents::INTERACTIVE_LOGIN);
228211
}
229212

230-
$this->eventDispatcher->dispatch($loginSuccessEvent = new LoginSuccessEvent($authenticator, $authenticatedToken, $request, $response, $this->providerKey));
213+
if ($passport instanceof AnonymousPassport) {
214+
return $response;
215+
}
216+
217+
$this->eventDispatcher->dispatch($loginSuccessEvent = new LoginSuccessEvent($authenticator, $passport, $authenticatedToken, $request, $response, $this->firewallName));
231218

232219
return $loginSuccessEvent->getResponse();
233220
}

Authenticator/AbstractAuthenticator.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
namespace Symfony\Component\Security\Http\Authenticator;
1313

1414
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
15-
use Symfony\Component\Security\Core\User\UserInterface;
15+
use Symfony\Component\Security\Core\Exception\LogicException;
16+
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
17+
use Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface;
1618
use Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken;
1719

1820
/**
@@ -30,8 +32,12 @@ abstract class AbstractAuthenticator implements AuthenticatorInterface
3032
*
3133
* @return PostAuthenticationToken
3234
*/
33-
public function createAuthenticatedToken(UserInterface $user, string $providerKey): TokenInterface
35+
public function createAuthenticatedToken(PassportInterface $passport, string $providerKey): TokenInterface
3436
{
35-
return new PostAuthenticationToken($user, $providerKey, $user->getRoles());
37+
if (!$passport instanceof UserPassportInterface) {
38+
throw new LogicException(sprintf('Passport does not contain a user, overwrite "createAuthenticatedToken()" in "%s" to create a custom authenticated token.', \get_class($this)));
39+
}
40+
41+
return new PostAuthenticationToken($passport->getUser(), $providerKey, $passport->getUser()->getRoles());
3642
}
3743
}

Authenticator/AbstractLoginFormAuthenticator.php

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
*
2626
* @experimental in 5.1
2727
*/
28-
abstract class AbstractLoginFormAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface, RememberMeAuthenticatorInterface, InteractiveAuthenticatorInterface
28+
abstract class AbstractLoginFormAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface, InteractiveAuthenticatorInterface
2929
{
3030
/**
3131
* Return the URL to the login page.
@@ -57,11 +57,6 @@ public function start(Request $request, AuthenticationException $authException =
5757
return new RedirectResponse($url);
5858
}
5959

60-
public function supportsRememberMe(): bool
61-
{
62-
return true;
63-
}
64-
6560
public function isInteractive(): bool
6661
{
6762
return true;

Authenticator/AbstractPreAuthenticatedAuthenticator.php

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
2020
use Symfony\Component\Security\Core\Exception\AuthenticationException;
2121
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
22-
use Symfony\Component\Security\Core\User\UserInterface;
2322
use Symfony\Component\Security\Core\User\UserProviderInterface;
23+
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PreAuthenticatedUserBadge;
24+
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
25+
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
2426

2527
/**
2628
* The base authenticator for authenticators to use pre-authenticated
@@ -32,7 +34,7 @@
3234
* @internal
3335
* @experimental in Symfony 5.1
3436
*/
35-
abstract class AbstractPreAuthenticatedAuthenticator implements InteractiveAuthenticatorInterface, CustomAuthenticatedInterface
37+
abstract class AbstractPreAuthenticatedAuthenticator implements InteractiveAuthenticatorInterface
3638
{
3739
private $userProvider;
3840
private $tokenStorage;
@@ -63,15 +65,15 @@ public function supports(Request $request): ?bool
6365
$this->clearToken($e);
6466

6567
if (null !== $this->logger) {
66-
$this->logger->debug('Skipping pre-authenticated authenticator as a BadCredentialsException is thrown.', ['exception' => $e, 'authenticator' => \get_class($this)]);
68+
$this->logger->debug('Skipping pre-authenticated authenticator as a BadCredentialsException is thrown.', ['exception' => $e, 'authenticator' => static::class]);
6769
}
6870

6971
return false;
7072
}
7173

7274
if (null === $username) {
7375
if (null !== $this->logger) {
74-
$this->logger->debug('Skipping pre-authenticated authenticator no username could be extracted.', ['authenticator' => \get_class($this)]);
76+
$this->logger->debug('Skipping pre-authenticated authenticator no username could be extracted.', ['authenticator' => static::class]);
7577
}
7678

7779
return false;
@@ -82,27 +84,17 @@ public function supports(Request $request): ?bool
8284
return true;
8385
}
8486

85-
public function getCredentials(Request $request)
87+
public function authenticate(Request $request): PassportInterface
8688
{
87-
return [
88-
'username' => $request->attributes->get('_pre_authenticated_username'),
89-
];
90-
}
91-
92-
public function getUser($credentials): ?UserInterface
93-
{
94-
return $this->userProvider->loadUserByUsername($credentials['username']);
95-
}
89+
$username = $request->attributes->get('_pre_authenticated_username');
90+
$user = $this->userProvider->loadUserByUsername($username);
9691

97-
public function checkCredentials($credentials, UserInterface $user): bool
98-
{
99-
// the user is already authenticated before it entered Symfony
100-
return true;
92+
return new SelfValidatingPassport($user, [new PreAuthenticatedUserBadge()]);
10193
}
10294

103-
public function createAuthenticatedToken(UserInterface $user, string $providerKey): TokenInterface
95+
public function createAuthenticatedToken(PassportInterface $passport, string $providerKey): TokenInterface
10496
{
105-
return new PreAuthenticatedToken($user, null, $providerKey);
97+
return new PreAuthenticatedToken($passport->getUser(), null, $providerKey, $passport->getUser()->getRoles());
10698
}
10799

108100
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?Response

Authenticator/AnonymousAuthenticator.php

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
1818
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
1919
use Symfony\Component\Security\Core\Exception\AuthenticationException;
20-
use Symfony\Component\Security\Core\User\User;
21-
use Symfony\Component\Security\Core\User\UserInterface;
20+
use Symfony\Component\Security\Http\Authenticator\Passport\AnonymousPassport;
21+
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
2222

2323
/**
2424
* @author Wouter de Jong <wouter@wouterj.nl>
@@ -27,7 +27,7 @@
2727
* @final
2828
* @experimental in 5.1
2929
*/
30-
class AnonymousAuthenticator implements AuthenticatorInterface, CustomAuthenticatedInterface
30+
class AnonymousAuthenticator implements AuthenticatorInterface
3131
{
3232
private $secret;
3333
private $tokenStorage;
@@ -45,23 +45,12 @@ public function supports(Request $request): ?bool
4545
return null === $this->tokenStorage->getToken() ? null : false;
4646
}
4747

48-
public function getCredentials(Request $request)
48+
public function authenticate(Request $request): PassportInterface
4949
{
50-
return [];
50+
return new AnonymousPassport();
5151
}
5252

53-
public function checkCredentials($credentials, UserInterface $user): bool
54-
{
55-
// anonymous users do not have credentials
56-
return true;
57-
}
58-
59-
public function getUser($credentials): ?UserInterface
60-
{
61-
return new User('anon.', null);
62-
}
63-
64-
public function createAuthenticatedToken(UserInterface $user, string $providerKey): TokenInterface
53+
public function createAuthenticatedToken(PassportInterface $passport, string $providerKey): TokenInterface
6554
{
6655
return new AnonymousToken($this->secret, 'anon.', []);
6756
}

0 commit comments

Comments
 (0)