Lazily load the user during the check passport event

This commit is contained in:
Wouter de Jong 2020-08-15 14:02:24 +02:00 committed by Fabien Potencier
parent 53a8f7d490
commit 907ef311bf
35 changed files with 570 additions and 88 deletions

View File

@ -41,6 +41,7 @@ use Symfony\Component\Security\Core\User\ChainUserProvider;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Controller\UserValueResolver;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
use Symfony\Component\Security\Http\Event\CheckPassportEvent;
use Twig\Extension\AbstractExtension;
/**
@ -342,6 +343,12 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
throw new InvalidConfigurationException(sprintf('Invalid firewall "%s": user provider "%s" not found.', $id, $firewall['provider']));
}
$defaultProvider = $providerIds[$normalizedName];
if ($this->authenticatorManagerEnabled) {
$container->setDefinition('security.listener.'.$id.'.user_provider', new ChildDefinition('security.listener.user_provider.abstract'))
->addTag('kernel.event_listener', ['event' => CheckPassportEvent::class, 'priority' => 2048, 'method' => 'checkPassport'])
->replaceArgument(0, new Reference($defaultProvider));
}
} elseif (1 === \count($providerIds)) {
$defaultProvider = reset($providerIds);
}
@ -632,7 +639,7 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
return $userProvider;
}
if ('remember_me' === $factoryKey || 'anonymous' === $factoryKey) {
if ('remember_me' === $factoryKey || 'anonymous' === $factoryKey || 'custom_authenticators' === $factoryKey) {
return 'security.user_providers';
}

View File

@ -23,11 +23,13 @@ use Symfony\Component\Security\Http\Authenticator\JsonLoginAuthenticator;
use Symfony\Component\Security\Http\Authenticator\RememberMeAuthenticator;
use Symfony\Component\Security\Http\Authenticator\RemoteUserAuthenticator;
use Symfony\Component\Security\Http\Authenticator\X509Authenticator;
use Symfony\Component\Security\Http\Event\CheckPassportEvent;
use Symfony\Component\Security\Http\EventListener\CheckCredentialsListener;
use Symfony\Component\Security\Http\EventListener\PasswordMigratingListener;
use Symfony\Component\Security\Http\EventListener\RememberMeListener;
use Symfony\Component\Security\Http\EventListener\SessionStrategyListener;
use Symfony\Component\Security\Http\EventListener\UserCheckerListener;
use Symfony\Component\Security\Http\EventListener\UserProviderListener;
use Symfony\Component\Security\Http\Firewall\AuthenticatorManagerListener;
return static function (ContainerConfigurator $container) {
@ -73,6 +75,18 @@ return static function (ContainerConfigurator $container) {
])
->tag('kernel.event_subscriber')
->set('security.listener.user_provider', UserProviderListener::class)
->args([
service('security.user_providers'),
])
->tag('kernel.event_listener', ['event' => CheckPassportEvent::class, 'priority' => 1024, 'method' => 'checkPassport'])
->set('security.listener.user_provider.abstract', UserProviderListener::class)
->abstract()
->args([
abstract_arg('user provider'),
])
->set('security.listener.password_migrating', PasswordMigratingListener::class)
->args([
service('security.encoder_factory'),

View File

@ -0,0 +1,52 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\Tests\Functional;
class AuthenticatorTest extends AbstractWebTestCase
{
/**
* @dataProvider provideEmails
*/
public function testGlobalUserProvider($email)
{
$client = $this->createClient(['test_case' => 'Authenticator', 'root_config' => 'implicit_user_provider.yml']);
$client->request('GET', '/profile', [], [], [
'HTTP_X-USER-EMAIL' => $email,
]);
$this->assertJsonStringEqualsJsonString('{"email":"'.$email.'"}', $client->getResponse()->getContent());
}
/**
* @dataProvider provideEmails
*/
public function testFirewallUserProvider($email, $withinFirewall)
{
$client = $this->createClient(['test_case' => 'Authenticator', 'root_config' => 'firewall_user_provider.yml']);
$client->request('GET', '/profile', [], [], [
'HTTP_X-USER-EMAIL' => $email,
]);
if ($withinFirewall) {
$this->assertJsonStringEqualsJsonString('{"email":"'.$email.'"}', $client->getResponse()->getContent());
} else {
$this->assertJsonStringEqualsJsonString('{"error":"Username could not be found."}', $client->getResponse()->getContent());
}
}
public function provideEmails()
{
yield ['jane@example.org', true];
yield ['john@example.org', false];
}
}

View File

@ -0,0 +1,53 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
class ApiAuthenticator extends AbstractAuthenticator
{
public function supports(Request $request): ?bool
{
return $request->headers->has('X-USER-EMAIL');
}
public function authenticate(Request $request): PassportInterface
{
$email = $request->headers->get('X-USER-EMAIL');
if (false === strpos($email, '@')) {
throw new BadCredentialsException('Email is not a valid email address.');
}
return new SelfValidatingPassport(new UserBadge($email));
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
return new JsonResponse([
'error' => $exception->getMessageKey(),
], JsonResponse::HTTP_FORBIDDEN);
}
}

View File

@ -0,0 +1,24 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class ProfileController extends AbstractController
{
public function __invoke()
{
$this->denyAccessUnlessGranted('ROLE_USER');
return $this->json(['email' => $this->getUser()->getUsername()]);
}
}

View File

@ -51,10 +51,6 @@ class CsrfFormLoginTest extends AbstractWebTestCase
$client = $this->createClient($options);
$form = $client->request('GET', '/login')->selectButton('login')->form();
if ($options['enable_authenticator_manager'] ?? false) {
$form['user_login[username]'] = 'johannes';
$form['user_login[password]'] = 'test';
}
$form['user_login[_token]'] = '';
$client->submit($form);

View File

@ -0,0 +1,15 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
return [
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
];

View File

@ -0,0 +1,33 @@
framework:
secret: test
router: { resource: "%kernel.project_dir%/%kernel.test_case%/routing.yml", utf8: true }
test: ~
default_locale: en
profiler: false
session:
storage_id: session.storage.mock_file
services:
logger: { class: Psr\Log\NullLogger }
Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ProfileController:
public: true
calls:
- ['setContainer', ['@Psr\Container\ContainerInterface']]
tags: [container.service_subscriber]
Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ApiAuthenticator: ~
security:
enable_authenticator_manager: true
encoders:
Symfony\Component\Security\Core\User\User: plaintext
providers:
in_memory:
memory:
users:
'jane@example.org': { password: test, roles: [ROLE_USER] }
in_memory2:
memory:
users:
'john@example.org': { password: test, roles: [ROLE_USER] }

View File

@ -0,0 +1,10 @@
imports:
- { resource: ./config.yml }
security:
firewalls:
api:
pattern: /
provider: in_memory
custom_authenticator: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ApiAuthenticator

View File

@ -0,0 +1,9 @@
imports:
- { resource: ./config.yml }
security:
firewalls:
api:
pattern: /
custom_authenticator: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ApiAuthenticator

View File

@ -0,0 +1,4 @@
profile:
path: /profile
defaults:
_controller: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ProfileController

View File

@ -27,6 +27,7 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
@ -64,14 +65,13 @@ class CheckLdapCredentialsListenerTest extends TestCase
$this->markTestSkipped('This test requires symfony/security-http:^5.1');
}
$user = new User('Wouter', null, ['ROLE_USER']);
// no LdapBadge
yield [new TestAuthenticator(), new Passport($user, new PasswordCredentials('s3cret'))];
yield [new TestAuthenticator(), new Passport(new UserBadge('test'), new PasswordCredentials('s3cret'))];
// ldap already resolved
$badge = new LdapBadge('app.ldap');
$badge->markResolved();
yield [new TestAuthenticator(), new Passport($user, new PasswordCredentials('s3cret'), [$badge])];
yield [new TestAuthenticator(), new Passport(new UserBadge('test'), new PasswordCredentials('s3cret'), [$badge])];
}
public function testPasswordCredentialsAlreadyResolvedThrowsException()
@ -81,8 +81,7 @@ class CheckLdapCredentialsListenerTest extends TestCase
$badge = new PasswordCredentials('s3cret');
$badge->markResolved();
$user = new User('Wouter', null, ['ROLE_USER']);
$passport = new Passport($user, $badge, [new LdapBadge('app.ldap')]);
$passport = new Passport(new UserBadge('test'), $badge, [new LdapBadge('app.ldap')]);
$listener = $this->createListener();
$listener->onCheckPassport(new CheckPassportEvent(new TestAuthenticator(), $passport));
@ -116,7 +115,7 @@ class CheckLdapCredentialsListenerTest extends TestCase
}
// no password credentials
yield [new SelfValidatingPassport(new User('Wouter', null, ['ROLE_USER']), [new LdapBadge('app.ldap')])];
yield [new SelfValidatingPassport(new UserBadge('test'), [new LdapBadge('app.ldap')])];
// no user passport
$passport = $this->createMock(PassportInterface::class);
@ -181,7 +180,7 @@ class CheckLdapCredentialsListenerTest extends TestCase
{
return new CheckPassportEvent(
new TestAuthenticator(),
new Passport(new User('Wouter', null, ['ROLE_USER']), new PasswordCredentials($password), [$ldapBadge ?? new LdapBadge('app.ldap')])
new Passport(new UserBadge('Wouter', function () { return new User('Wouter', null, ['ROLE_USER']); }), new PasswordCredentials($password), [$ldapBadge ?? new LdapBadge('app.ldap')])
);
}

View File

@ -24,6 +24,7 @@ use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface;
use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
@ -62,14 +63,11 @@ class GuardBridgeAuthenticator implements InteractiveAuthenticatorInterface
}
// get the user from the GuardAuthenticator
$user = $this->guard->getUser($credentials, $this->userProvider);
if (null === $user) {
throw new UsernameNotFoundException(sprintf('Null returned from "%s::getUser()".', get_debug_type($this->guard)));
}
if (!$user instanceof UserInterface) {
throw new \UnexpectedValueException(sprintf('The "%s::getUser()" method must return a UserInterface. You returned "%s".', get_debug_type($this->guard), get_debug_type($user)));
if (class_exists(UserBadge::class)) {
$user = new UserBadge('guard_authenticator_'.md5(serialize($credentials)), function () use ($credentials) { return $this->getUser($credentials); });
} else {
// BC with symfony/security-http:5.1
$user = $this->getUser($credentials);
}
$passport = new Passport($user, new CustomCredentials([$this->guard, 'checkCredentials'], $credentials));
@ -84,6 +82,21 @@ class GuardBridgeAuthenticator implements InteractiveAuthenticatorInterface
return $passport;
}
private function getUser($credentials): UserInterface
{
$user = $this->guard->getUser($credentials, $this->userProvider);
if (null === $user) {
throw new UsernameNotFoundException(sprintf('Null returned from "%s::getUser()".', get_debug_type($this->guard)));
}
if (!$user instanceof UserInterface) {
throw new \UnexpectedValueException(sprintf('The "%s::getUser()" method must return a UserInterface. You returned "%s".', get_debug_type($this->guard), get_debug_type($user)));
}
return $user;
}
public function createAuthenticatedToken(PassportInterface $passport, string $firewallName): TokenInterface
{
if (!$passport instanceof UserPassportInterface) {

View File

@ -22,6 +22,7 @@ use Symfony\Component\Security\Guard\Authenticator\GuardBridgeAuthenticator;
use Symfony\Component\Security\Guard\AuthenticatorInterface;
use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
@ -83,6 +84,7 @@ class GuardBridgeAuthenticatorTest extends TestCase
->willReturn($user);
$passport = $this->authenticator->authenticate($request);
$this->assertEquals($user, $passport->getUser());
$this->assertTrue($passport->hasBadge(CustomCredentials::class));
$this->guardAuthenticator->expects($this->once())
@ -110,7 +112,8 @@ class GuardBridgeAuthenticatorTest extends TestCase
->with($credentials, $this->userProvider)
->willReturn(null);
$this->authenticator->authenticate($request);
$passport = $this->authenticator->authenticate($request);
$passport->getUser();
}
/**
@ -126,12 +129,6 @@ class GuardBridgeAuthenticatorTest extends TestCase
->with($request)
->willReturn($credentials);
$user = new User('test', null, ['ROLE_USER']);
$this->guardAuthenticator->expects($this->once())
->method('getUser')
->with($credentials, $this->userProvider)
->willReturn($user);
$this->guardAuthenticator->expects($this->once())
->method('supportsRememberMe')
->willReturn($rememberMeSupported);
@ -156,7 +153,7 @@ class GuardBridgeAuthenticatorTest extends TestCase
->with($user, 'main')
->willReturn($token);
$this->assertSame($token, $this->authenticator->createAuthenticatedToken(new SelfValidatingPassport($user), 'main'));
$this->assertSame($token, $this->authenticator->createAuthenticatedToken(new SelfValidatingPassport(new UserBadge('test', function () use ($user) { return $user; })), 'main'));
}
public function testHandleSuccess()

View File

@ -24,6 +24,7 @@ use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\AnonymousPassport;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\Event\AuthenticationTokenCreatedEvent;
@ -69,7 +70,7 @@ class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthent
public function authenticateUser(UserInterface $user, AuthenticatorInterface $authenticator, Request $request, array $badges = []): ?Response
{
// create an authenticated token for the User
$token = $authenticator->createAuthenticatedToken($passport = new SelfValidatingPassport($user, $badges), $this->firewallName);
$token = $authenticator->createAuthenticatedToken($passport = new SelfValidatingPassport(new UserBadge($user->getUsername(), function () use ($user) { return $user; }), $badges), $this->firewallName);
// announce the authenticated token
$token = $this->eventDispatcher->dispatch(new AuthenticationTokenCreatedEvent($token))->getAuthenticatedToken();

View File

@ -21,6 +21,7 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PreAuthenticatedUserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
@ -86,10 +87,9 @@ abstract class AbstractPreAuthenticatedAuthenticator implements InteractiveAuthe
public function authenticate(Request $request): PassportInterface
{
$username = $request->attributes->get('_pre_authenticated_username');
$user = $this->userProvider->loadUserByUsername($username);
return new SelfValidatingPassport($user, [new PreAuthenticatedUserBadge()]);
return new SelfValidatingPassport(new UserBadge($request->attributes->get('_pre_authenticated_username'), function ($username) {
return $this->userProvider->loadUserByUsername($username);
}), [new PreAuthenticatedUserBadge()]);
}
public function createAuthenticatedToken(PassportInterface $passport, string $firewallName): TokenInterface

View File

@ -28,6 +28,7 @@ use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerI
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
@ -80,12 +81,15 @@ class FormLoginAuthenticator extends AbstractLoginFormAuthenticator
public function authenticate(Request $request): PassportInterface
{
$credentials = $this->getCredentials($request);
$user = $this->userProvider->loadUserByUsername($credentials['username']);
if (!$user instanceof UserInterface) {
throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
}
$passport = new Passport($user, new PasswordCredentials($credentials['password']), [new RememberMeBadge()]);
$passport = new Passport(new UserBadge($credentials['username'], function ($username) {
$user = $this->userProvider->loadUserByUsername($username);
if (!$user instanceof UserInterface) {
throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
}
return $user;
}), new PasswordCredentials($credentials['password']), [new RememberMeBadge()]);
if ($this->options['enable_csrf']) {
$passport->addBadge(new CsrfTokenBadge($this->options['csrf_token_id'], $credentials['csrf_token']));
}

View File

@ -22,6 +22,7 @@ use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
@ -66,12 +67,14 @@ class HttpBasicAuthenticator implements AuthenticatorInterface, AuthenticationEn
$username = $request->headers->get('PHP_AUTH_USER');
$password = $request->headers->get('PHP_AUTH_PW', '');
$user = $this->userProvider->loadUserByUsername($username);
if (!$user instanceof UserInterface) {
throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
}
$passport = new Passport(new UserBadge($username, function ($username) {
$user = $this->userProvider->loadUserByUsername($username);
if (!$user instanceof UserInterface) {
throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
}
$passport = new Passport($user, new PasswordCredentials($password));
return $user;
}), new PasswordCredentials($password));
if ($this->userProvider instanceof PasswordUpgraderInterface) {
$passport->addBadge(new PasswordUpgradeBadge($password, $this->userProvider));
}

View File

@ -30,6 +30,7 @@ use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
@ -87,12 +88,14 @@ class JsonLoginAuthenticator implements InteractiveAuthenticatorInterface
throw $e;
}
$user = $this->userProvider->loadUserByUsername($credentials['username']);
if (!$user instanceof UserInterface) {
throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
}
$passport = new Passport(new UserBadge($credentials['username'], function ($username) {
$user = $this->userProvider->loadUserByUsername($username);
if (!$user instanceof UserInterface) {
throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
}
$passport = new Passport($user, new PasswordCredentials($credentials['password']));
return $user;
}), new PasswordCredentials($credentials['password']));
if ($this->userProvider instanceof PasswordUpgraderInterface) {
$passport->addBadge(new PasswordUpgradeBadge($credentials['password'], $this->userProvider));
}

View File

@ -0,0 +1,83 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Http\Authenticator\Passport\Badge;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\EventListener\UserProviderListener;
/**
* Represents the user in the authentication process.
*
* It uses an identifier (e.g. email, or username) and
* "user loader" to load the related User object.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*
* @experimental in 5.2
*/
class UserBadge implements BadgeInterface
{
private $userIdentifier;
private $userLoader;
private $user;
/**
* Initializes the user badge.
*
* You must provide a $userIdentifier. This is a unique string representing the
* user for this authentication (e.g. the email if authentication is done using
* email + password; or a string combining email+company if authentication is done
* based on email *and* company name). This string can be used for e.g. login throttling.
*
* Optionally, you may pass a user loader. This callable receives the $userIdentifier
* as argument and must return a UserInterface object (otherwise a UsernameNotFoundException
* is thrown). If this is not set, the default user provider will be used with
* $userIdentifier as username.
*/
public function __construct(string $userIdentifier, ?callable $userLoader = null)
{
$this->userIdentifier = $userIdentifier;
$this->userLoader = $userLoader;
}
public function getUser(): UserInterface
{
if (null === $this->user) {
if (null === $this->userLoader) {
throw new \LogicException(sprintf('No user loader is configured, did you forget to register the "%s" listener?', UserProviderListener::class));
}
$this->user = ($this->userLoader)($this->userIdentifier);
if (!$this->user instanceof UserInterface) {
throw new UsernameNotFoundException();
}
}
return $this->user;
}
public function getUserLoader(): ?callable
{
return $this->userLoader;
}
public function setUserLoader(callable $userLoader): void
{
$this->userLoader = $userLoader;
}
public function isResolved(): bool
{
return true;
}
}

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Security\Http\Authenticator\Passport;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CredentialsInterface;
/**
@ -31,13 +32,22 @@ class Passport implements UserPassportInterface
private $attributes = [];
/**
* @param UserBadge $userBadge
* @param CredentialsInterface $credentials the credentials to check for this authentication, use
* SelfValidatingPassport if no credentials should be checked
* @param BadgeInterface[] $badges
*/
public function __construct(UserInterface $user, CredentialsInterface $credentials, array $badges = [])
public function __construct($userBadge, CredentialsInterface $credentials, array $badges = [])
{
$this->user = $user;
if ($userBadge instanceof UserInterface) {
trigger_deprecation('symfony/security-http', '5.2', 'The 1st argument of "%s" must be an instance of "%s", support for "%s" will be removed in symfony/security-http 5.3.', __CLASS__, UserBadge::class, UserInterface::class);
$this->user = $userBadge;
} elseif ($userBadge instanceof UserBadge) {
$this->addBadge($userBadge);
} else {
throw new \TypeError(sprintf('Argument 1 of "%s" must be an instance of "%s", "%s" given.', __METHOD__, UserBadge::class, get_debug_type($userBadge)));
}
$this->addBadge($credentials);
foreach ($badges as $badge) {
@ -47,6 +57,14 @@ class Passport implements UserPassportInterface
public function getUser(): UserInterface
{
if (null === $this->user) {
if (!$this->hasBadge(UserBadge::class)) {
throw new \LogicException('Cannot get the Security user, no username or UserBadge configured for this passport.');
}
$this->user = $this->getBadge(UserBadge::class)->getUser();
}
return $this->user;
}

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Security\Http\Authenticator\Passport;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
/**
* An implementation used when there are no credentials to be checked (e.g.
@ -25,11 +26,20 @@ use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface;
class SelfValidatingPassport extends Passport
{
/**
* @param UserBadge $userBadge
* @param BadgeInterface[] $badges
*/
public function __construct(UserInterface $user, array $badges = [])
public function __construct($userBadge, array $badges = [])
{
$this->user = $user;
if ($userBadge instanceof UserInterface) {
trigger_deprecation('symfony/security-http', '5.2', 'The 1st argument of "%s" must be an instance of "%s", support for "%s" will be removed in symfony/security-http 5.3.', __CLASS__, UserBadge::class, UserInterface::class);
$this->user = $userBadge;
} elseif ($userBadge instanceof UserBadge) {
$this->addBadge($userBadge);
} else {
throw new \TypeError(sprintf('Argument 1 of "%s" must be an instance of "%s", "%s" given.', __METHOD__, UserBadge::class, get_debug_type($userBadge)));
}
foreach ($badges as $badge) {
$this->addBadge($badge);

View File

@ -17,6 +17,7 @@ use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
@ -74,7 +75,7 @@ class RememberMeAuthenticator implements InteractiveAuthenticatorInterface
throw new \LogicException('No remember me token is set.');
}
return new SelfValidatingPassport($token->getUser());
return new SelfValidatingPassport(new UserBadge($token->getUsername(), [$token, 'getUser']));
}
public function createAuthenticatedToken(PassportInterface $passport, string $firewallName): TokenInterface

View File

@ -57,6 +57,6 @@ class CsrfProtectionListener implements EventSubscriberInterface
public static function getSubscribedEvents(): array
{
return [CheckPassportEvent::class => ['checkPassport', 128]];
return [CheckPassportEvent::class => ['checkPassport', 512]];
}
}

View File

@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Http\EventListener;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Event\CheckPassportEvent;
/**
* @author Wouter de Jong <wouter@wouterj.nl>
*
* @final
* @experimental in 5.2
*/
class UserProviderListener
{
private $userProvider;
public function __construct(UserProviderInterface $userProvider)
{
$this->userProvider = $userProvider;
}
public function checkPassport(CheckPassportEvent $event): void
{
$passport = $event->getPassport();
if (!$passport->hasBadge(UserBadge::class)) {
return;
}
/** @var UserBadge $badge */
$badge = $passport->getBadge(UserBadge::class);
if (null !== $badge->getUserLoader()) {
return;
}
$badge->setUserLoader([$this->userProvider, 'loadUserByUsername']);
}
}

View File

@ -21,6 +21,7 @@ use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Http\Authentication\AuthenticatorManager;
use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
@ -93,7 +94,7 @@ class AuthenticatorManagerTest extends TestCase
$authenticators[($matchingAuthenticatorIndex + 1) % 2]->expects($this->never())->method('authenticate');
$matchingAuthenticator->expects($this->any())->method('authenticate')->willReturn(new SelfValidatingPassport($this->user));
$matchingAuthenticator->expects($this->any())->method('authenticate')->willReturn(new SelfValidatingPassport(new UserBadge('wouter', function () { return $this->user; })));
$listenerCalled = false;
$this->eventDispatcher->addListener(CheckPassportEvent::class, function (CheckPassportEvent $event) use (&$listenerCalled, $matchingAuthenticator) {
@ -121,7 +122,7 @@ class AuthenticatorManagerTest extends TestCase
$authenticator = $this->createAuthenticator();
$this->request->attributes->set('_security_authenticators', [$authenticator]);
$authenticator->expects($this->any())->method('authenticate')->willReturn(new Passport($this->user, new PasswordCredentials('pass')));
$authenticator->expects($this->any())->method('authenticate')->willReturn(new Passport(new UserBadge('wouter', function () { return $this->user; }), new PasswordCredentials('pass')));
$authenticator->expects($this->once())
->method('onAuthenticationFailure')
@ -139,7 +140,7 @@ class AuthenticatorManagerTest extends TestCase
$authenticator = $this->createAuthenticator();
$this->request->attributes->set('_security_authenticators', [$authenticator]);
$authenticator->expects($this->any())->method('authenticate')->willReturn(new SelfValidatingPassport($this->user));
$authenticator->expects($this->any())->method('authenticate')->willReturn(new SelfValidatingPassport(new UserBadge('wouter', function () { return $this->user; })));
$authenticator->expects($this->any())->method('createAuthenticatedToken')->willReturn($this->token);
@ -160,7 +161,7 @@ class AuthenticatorManagerTest extends TestCase
$authenticator = $this->createAuthenticator();
$this->request->attributes->set('_security_authenticators', [$authenticator]);
$authenticator->expects($this->any())->method('authenticate')->willReturn(new SelfValidatingPassport($this->user));
$authenticator->expects($this->any())->method('authenticate')->willReturn(new SelfValidatingPassport(new UserBadge('wouter', function () { return $this->user; })));
$authenticator->expects($this->any())->method('createAuthenticatedToken')->willReturn($this->token);
@ -216,7 +217,7 @@ class AuthenticatorManagerTest extends TestCase
$authenticator->expects($this->any())->method('isInteractive')->willReturn(true);
$this->request->attributes->set('_security_authenticators', [$authenticator]);
$authenticator->expects($this->any())->method('authenticate')->willReturn(new SelfValidatingPassport($this->user));
$authenticator->expects($this->any())->method('authenticate')->willReturn(new SelfValidatingPassport(new UserBadge('wouter', function () { return $this->user; })));
$authenticator->expects($this->any())->method('createAuthenticatedToken')->willReturn($this->token);
$this->tokenStorage->expects($this->once())->method('setToken')->with($this->token);

View File

@ -16,7 +16,6 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authenticator\JsonLoginAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
@ -72,8 +71,6 @@ class JsonLoginAuthenticatorTest extends TestCase
{
$this->setUpAuthenticator();
$this->userProvider->expects($this->once())->method('loadUserByUsername')->with('dunglas')->willReturn(new User('dunglas', 'pa$$'));
$request = new Request([], [], [], [], [], ['HTTP_CONTENT_TYPE' => 'application/json'], '{"username": "dunglas", "password": "foo"}');
$passport = $this->authenticator->authenticate($request);
$this->assertEquals('foo', $passport->getBadge(PasswordCredentials::class)->getPassword());
@ -86,8 +83,6 @@ class JsonLoginAuthenticatorTest extends TestCase
'password_path' => 'authentication.password',
]);
$this->userProvider->expects($this->once())->method('loadUserByUsername')->with('dunglas')->willReturn(new User('dunglas', 'pa$$'));
$request = new Request([], [], [], [], [], ['HTTP_CONTENT_TYPE' => 'application/json'], '{"authentication": {"username": "dunglas", "password": "foo"}}');
$passport = $this->authenticator->authenticate($request);
$this->assertEquals('foo', $passport->getBadge(PasswordCredentials::class)->getPassword());

View File

@ -32,11 +32,11 @@ class X509AuthenticatorTest extends TestCase
/**
* @dataProvider provideServerVars
*/
public function testAuthentication($user, $credentials)
public function testAuthentication($username, $credentials)
{
$serverVars = [];
if ('' !== $user) {
$serverVars['SSL_CLIENT_S_DN_Email'] = $user;
if ('' !== $username) {
$serverVars['SSL_CLIENT_S_DN_Email'] = $username;
}
if ('' !== $credentials) {
$serverVars['SSL_CLIENT_S_DN'] = $credentials;
@ -45,12 +45,13 @@ class X509AuthenticatorTest extends TestCase
$request = $this->createRequest($serverVars);
$this->assertTrue($this->authenticator->supports($request));
$this->userProvider->expects($this->once())
$this->userProvider->expects($this->any())
->method('loadUserByUsername')
->with($user)
->willReturn(new User($user, null));
->with($username)
->willReturn(new User($username, null));
$this->authenticator->authenticate($request);
$passport = $this->authenticator->authenticate($request);
$this->assertEquals($username, $passport->getUser()->getUsername());
}
public static function provideServerVars()
@ -73,7 +74,8 @@ class X509AuthenticatorTest extends TestCase
->with($emailAddress)
->willReturn(new User($emailAddress, null));
$this->authenticator->authenticate($request);
$passport = $this->authenticator->authenticate($request);
$this->assertEquals($emailAddress, $passport->getUser()->getUsername());
}
public static function provideServerVarsNoUser()
@ -108,7 +110,8 @@ class X509AuthenticatorTest extends TestCase
->with('TheUser')
->willReturn(new User('TheUser', null));
$authenticator->authenticate($request);
$passport = $this->authenticator->authenticate($request);
$this->assertEquals('TheUser', $passport->getUser()->getUsername());
}
public function testAuthenticationCustomCredentialsKey()
@ -125,7 +128,8 @@ class X509AuthenticatorTest extends TestCase
->with('cert@example.com')
->willReturn(new User('cert@example.com', null));
$authenticator->authenticate($request);
$passport = $authenticator->authenticate($request);
$this->assertEquals('cert@example.com', $passport->getUser()->getUsername());
}
private function createRequest(array $server)

View File

@ -17,6 +17,7 @@ use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
@ -53,7 +54,7 @@ class CheckCredentialsListenerTest extends TestCase
}
$credentials = new PasswordCredentials($password);
$this->listener->checkPassport($this->createEvent(new Passport($this->user, $credentials)));
$this->listener->checkPassport($this->createEvent(new Passport(new UserBadge('wouter', function () { return $this->user; }), $credentials)));
if (true === $result) {
$this->assertTrue($credentials->isResolved());
@ -73,7 +74,7 @@ class CheckCredentialsListenerTest extends TestCase
$this->encoderFactory->expects($this->never())->method('getEncoder');
$event = $this->createEvent(new Passport($this->user, new PasswordCredentials('')));
$event = $this->createEvent(new Passport(new UserBadge('wouter', function () { return $this->user; }), new PasswordCredentials('')));
$this->listener->checkPassport($event);
}
@ -91,7 +92,7 @@ class CheckCredentialsListenerTest extends TestCase
$credentials = new CustomCredentials(function () use ($result) {
return $result;
}, ['password' => 'foo']);
$this->listener->checkPassport($this->createEvent(new Passport($this->user, $credentials)));
$this->listener->checkPassport($this->createEvent(new Passport(new UserBadge('wouter', function () { return $this->user; }), $credentials)));
if (true === $result) {
$this->assertTrue($credentials->isResolved());
@ -108,7 +109,7 @@ class CheckCredentialsListenerTest extends TestCase
{
$this->encoderFactory->expects($this->never())->method('getEncoder');
$event = $this->createEvent(new SelfValidatingPassport($this->user));
$event = $this->createEvent(new SelfValidatingPassport(new UserBadge('wouter', function () { return $this->user; })));
$this->listener->checkPassport($event);
}

View File

@ -18,6 +18,7 @@ use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\Event\CheckPassportEvent;
use Symfony\Component\Security\Http\EventListener\CsrfProtectionListener;
@ -75,7 +76,7 @@ class CsrfProtectionListenerTest extends TestCase
private function createPassport(?CsrfTokenBadge $badge)
{
$passport = new SelfValidatingPassport(new User('wouter', 'pass'));
$passport = new SelfValidatingPassport(new UserBadge('wouter', function ($username) { return new User($username, 'pass'); }));
if ($badge) {
$passport->addBadge($badge);
}

View File

@ -20,6 +20,7 @@ use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
@ -51,10 +52,10 @@ class PasswordMigratingListenerTest extends TestCase
public function provideUnsupportedEvents()
{
// no password upgrade badge
yield [$this->createEvent(new SelfValidatingPassport($this->createMock(UserInterface::class)))];
yield [$this->createEvent(new SelfValidatingPassport(new UserBadge('test', function () { return $this->createMock(UserInterface::class); })))];
// blank password
yield [$this->createEvent(new SelfValidatingPassport($this->createMock(UserInterface::class), [new PasswordUpgradeBadge('', $this->createPasswordUpgrader())]))];
yield [$this->createEvent(new SelfValidatingPassport(new UserBadge('test', function () { return $this->createMock(UserInterface::class); }), [new PasswordUpgradeBadge('', $this->createPasswordUpgrader())]))];
// no user
yield [$this->createEvent($this->createMock(PassportInterface::class))];
@ -76,7 +77,7 @@ class PasswordMigratingListenerTest extends TestCase
->with($this->user, 'new-encoded-password')
;
$event = $this->createEvent(new SelfValidatingPassport($this->user, [new PasswordUpgradeBadge('pa$$word', $passwordUpgrader)]));
$event = $this->createEvent(new SelfValidatingPassport(new UserBadge('test', function () { return $this->user; }), [new PasswordUpgradeBadge('pa$$word', $passwordUpgrader)]));
$this->listener->onLoginSuccess($event);
}

View File

@ -19,6 +19,7 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\Event\LoginFailureEvent;
@ -47,7 +48,7 @@ class RememberMeListenerTest extends TestCase
{
$this->rememberMeServices->expects($this->never())->method('loginSuccess');
$event = $this->createLoginSuccessfulEvent('main_firewall', $this->response, new SelfValidatingPassport(new User('wouter', null)));
$event = $this->createLoginSuccessfulEvent('main_firewall', $this->response, new SelfValidatingPassport(new UserBadge('wouter', function ($username) { return new User($username, null); })));
$this->listener->onSuccessfulLogin($event);
}
@ -78,7 +79,7 @@ class RememberMeListenerTest extends TestCase
private function createLoginSuccessfulEvent($firewallName, $response, PassportInterface $passport = null)
{
if (null === $passport) {
$passport = new SelfValidatingPassport(new User('test', null), [new RememberMeBadge()]);
$passport = new SelfValidatingPassport(new UserBadge('test', function ($username) { return new User($username, null); }), [new RememberMeBadge()]);
}
return new LoginSuccessEvent($this->createMock(AuthenticatorInterface::class), $passport, $this->token, $this->request, $response, $firewallName);

View File

@ -16,6 +16,7 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
use Symfony\Component\Security\Http\EventListener\SessionStrategyListener;
@ -62,7 +63,7 @@ class SessionStrategyListenerTest extends TestCase
private function createEvent($firewallName)
{
return new LoginSuccessEvent($this->createMock(AuthenticatorInterface::class), new SelfValidatingPassport(new User('test', null)), $this->token, $this->request, null, $firewallName);
return new LoginSuccessEvent($this->createMock(AuthenticatorInterface::class), new SelfValidatingPassport(new UserBadge('test', function ($username) { return new User($username, null); })), $this->token, $this->request, null, $firewallName);
}
private function configurePreviousSession()

View File

@ -18,6 +18,7 @@ use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PreAuthenticatedUserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\Event\CheckPassportEvent;
@ -55,7 +56,7 @@ class UserCheckerListenerTest extends TestCase
{
$this->userChecker->expects($this->never())->method('checkPreAuth');
$this->listener->preCheckCredentials($this->createCheckPassportEvent(new SelfValidatingPassport($this->user, [new PreAuthenticatedUserBadge()])));
$this->listener->preCheckCredentials($this->createCheckPassportEvent(new SelfValidatingPassport(new UserBadge('test', function () { return $this->user; }), [new PreAuthenticatedUserBadge()])));
}
public function testPostAuthValidCredentials()
@ -75,7 +76,7 @@ class UserCheckerListenerTest extends TestCase
private function createCheckPassportEvent($passport = null)
{
if (null === $passport) {
$passport = new SelfValidatingPassport($this->user);
$passport = new SelfValidatingPassport(new UserBadge('test', function () { return $this->user; }));
}
return new CheckPassportEvent($this->createMock(AuthenticatorInterface::class), $passport);
@ -84,7 +85,7 @@ class UserCheckerListenerTest extends TestCase
private function createLoginSuccessEvent($passport = null)
{
if (null === $passport) {
$passport = new SelfValidatingPassport($this->user);
$passport = new SelfValidatingPassport(new UserBadge('test', function () { return $this->user; }));
}
return new LoginSuccessEvent($this->createMock(AuthenticatorInterface::class), $passport, $this->createMock(TokenInterface::class), new Request(), null, 'main');

View File

@ -0,0 +1,79 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Http\Tests\EventListener;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\AnonymousPassport;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\Event\CheckPassportEvent;
use Symfony\Component\Security\Http\EventListener\UserProviderListener;
class UserProviderListenerTest extends TestCase
{
private $userProvider;
private $listener;
protected function setUp(): void
{
$this->userProvider = $this->createMock(UserProviderInterface::class);
$this->listener = new UserProviderListener($this->userProvider);
}
public function testSetUserProvider()
{
$passport = new SelfValidatingPassport(new UserBadge('wouter'));
$this->listener->checkPassport(new CheckPassportEvent($this->createMock(AuthenticatorInterface::class), $passport));
$badge = $passport->getBadge(UserBadge::class);
$this->assertEquals([$this->userProvider, 'loadUserByUsername'], $badge->getUserLoader());
$user = new User('wouter', null);
$this->userProvider->expects($this->once())->method('loadUserByUsername')->with('wouter')->willReturn($user);
$this->assertSame($user, $passport->getUser());
}
/**
* @dataProvider provideCompletePassports
*/
public function testNotOverrideUserLoader($passport)
{
$badgeBefore = $passport->hasBadge(UserBadge::class) ? $passport->getBadge(UserBadge::class) : null;
$this->listener->checkPassport(new CheckPassportEvent($this->createMock(AuthenticatorInterface::class), $passport));
$this->assertEquals($passport->hasBadge(UserBadge::class) ? $passport->getBadge(UserBadge::class) : null, $badgeBefore);
}
public function provideCompletePassports()
{
yield [new AnonymousPassport()];
yield [new SelfValidatingPassport(new UserBadge('wouter', function () {}))];
}
/**
* @group legacy
*/
public function testLegacyUserPassport()
{
$passport = new SelfValidatingPassport($user = $this->createMock(UserInterface::class));
$this->listener->checkPassport(new CheckPassportEvent($this->createMock(AuthenticatorInterface::class), $passport));
$this->assertFalse($passport->hasBadge(UserBadge::class));
$this->assertSame($user, $passport->getUser());
}
}