Skip to content

Commit e61adff

Browse files
committed
Lazily load the user during the check passport event
1 parent efdc35c commit e61adff

35 files changed

+570
-88
lines changed

src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
use Symfony\Component\Security\Core\User\UserProviderInterface;
4242
use Symfony\Component\Security\Http\Controller\UserValueResolver;
4343
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
44+
use Symfony\Component\Security\Http\Event\CheckPassportEvent;
4445
use Twig\Extension\AbstractExtension;
4546

4647
/**
@@ -342,6 +343,12 @@ private function createFirewall(ContainerBuilder $container, string $id, array $
342343
throw new InvalidConfigurationException(sprintf('Invalid firewall "%s": user provider "%s" not found.', $id, $firewall['provider']));
343344
}
344345
$defaultProvider = $providerIds[$normalizedName];
346+
347+
if ($this->authenticatorManagerEnabled) {
348+
$container->setDefinition('security.listener.'.$id.'.user_provider', new ChildDefinition('security.listener.user_provider.abstract'))
349+
->addTag('kernel.event_listener', ['event' => CheckPassportEvent::class, 'priority' => 2048, 'method' => 'checkPassport'])
350+
->replaceArgument(0, new Reference($defaultProvider));
351+
}
345352
} elseif (1 === \count($providerIds)) {
346353
$defaultProvider = reset($providerIds);
347354
}
@@ -632,7 +639,7 @@ private function getUserProvider(ContainerBuilder $container, string $id, array
632639
return $userProvider;
633640
}
634641

635-
if ('remember_me' === $factoryKey || 'anonymous' === $factoryKey) {
642+
if ('remember_me' === $factoryKey || 'anonymous' === $factoryKey || 'custom_authenticators' === $factoryKey) {
636643
return 'security.user_providers';
637644
}
638645

src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@
2323
use Symfony\Component\Security\Http\Authenticator\RememberMeAuthenticator;
2424
use Symfony\Component\Security\Http\Authenticator\RemoteUserAuthenticator;
2525
use Symfony\Component\Security\Http\Authenticator\X509Authenticator;
26+
use Symfony\Component\Security\Http\Event\CheckPassportEvent;
2627
use Symfony\Component\Security\Http\EventListener\CheckCredentialsListener;
2728
use Symfony\Component\Security\Http\EventListener\PasswordMigratingListener;
2829
use Symfony\Component\Security\Http\EventListener\RememberMeListener;
2930
use Symfony\Component\Security\Http\EventListener\SessionStrategyListener;
3031
use Symfony\Component\Security\Http\EventListener\UserCheckerListener;
32+
use Symfony\Component\Security\Http\EventListener\UserProviderListener;
3133
use Symfony\Component\Security\Http\Firewall\AuthenticatorManagerListener;
3234

3335
return static function (ContainerConfigurator $container) {
@@ -73,6 +75,18 @@
7375
])
7476
->tag('kernel.event_subscriber')
7577

78+
->set('security.listener.user_provider', UserProviderListener::class)
79+
->args([
80+
service('security.user_providers'),
81+
])
82+
->tag('kernel.event_listener', ['event' => CheckPassportEvent::class, 'priority' => 1024, 'method' => 'checkPassport'])
83+
84+
->set('security.listener.user_provider.abstract', UserProviderListener::class)
85+
->abstract()
86+
->args([
87+
abstract_arg('user provider'),
88+
])
89+
7690
->set('security.listener.password_migrating', PasswordMigratingListener::class)
7791
->args([
7892
service('security.encoder_factory'),
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\SecurityBundle\Tests\Functional;
13+
14+
class AuthenticatorTest extends AbstractWebTestCase
15+
{
16+
/**
17+
* @dataProvider provideEmails
18+
*/
19+
public function testGlobalUserProvider($email)
20+
{
21+
$client = $this->createClient(['test_case' => 'Authenticator', 'root_config' => 'implicit_user_provider.yml']);
22+
23+
$client->request('GET', '/profile', [], [], [
24+
'HTTP_X-USER-EMAIL' => $email,
25+
]);
26+
$this->assertJsonStringEqualsJsonString('{"email":"'.$email.'"}', $client->getResponse()->getContent());
27+
}
28+
29+
/**
30+
* @dataProvider provideEmails
31+
*/
32+
public function testFirewallUserProvider($email, $withinFirewall)
33+
{
34+
$client = $this->createClient(['test_case' => 'Authenticator', 'root_config' => 'firewall_user_provider.yml']);
35+
36+
$client->request('GET', '/profile', [], [], [
37+
'HTTP_X-USER-EMAIL' => $email,
38+
]);
39+
40+
if ($withinFirewall) {
41+
$this->assertJsonStringEqualsJsonString('{"email":"'.$email.'"}', $client->getResponse()->getContent());
42+
} else {
43+
$this->assertJsonStringEqualsJsonString('{"error":"Username could not be found."}', $client->getResponse()->getContent());
44+
}
45+
}
46+
47+
public function provideEmails()
48+
{
49+
yield ['jane@example.org', true];
50+
yield ['john@example.org', false];
51+
}
52+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle;
13+
14+
use Symfony\Component\HttpFoundation\JsonResponse;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\HttpFoundation\Response;
17+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
18+
use Symfony\Component\Security\Core\Exception\AuthenticationException;
19+
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
20+
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
21+
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
22+
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
23+
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
24+
25+
class ApiAuthenticator extends AbstractAuthenticator
26+
{
27+
public function supports(Request $request): ?bool
28+
{
29+
return $request->headers->has('X-USER-EMAIL');
30+
}
31+
32+
public function authenticate(Request $request): PassportInterface
33+
{
34+
$email = $request->headers->get('X-USER-EMAIL');
35+
if (false === strpos($email, '@')) {
36+
throw new BadCredentialsException('Email is not a valid email address.');
37+
}
38+
39+
return new SelfValidatingPassport(new UserBadge($email));
40+
}
41+
42+
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
43+
{
44+
return null;
45+
}
46+
47+
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
48+
{
49+
return new JsonResponse([
50+
'error' => $exception->getMessageKey(),
51+
], JsonResponse::HTTP_FORBIDDEN);
52+
}
53+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle;
13+
14+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
15+
16+
class ProfileController extends AbstractController
17+
{
18+
public function __invoke()
19+
{
20+
$this->denyAccessUnlessGranted('ROLE_USER');
21+
22+
return $this->json(['email' => $this->getUser()->getUsername()]);
23+
}
24+
}

src/Symfony/Bundle/SecurityBundle/Tests/Functional/CsrfFormLoginTest.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,6 @@ public function testFormLoginWithInvalidCsrfToken($options)
5151
$client = $this->createClient($options);
5252

5353
$form = $client->request('GET', '/login')->selectButton('login')->form();
54-
if ($options['enable_authenticator_manager'] ?? false) {
55-
$form['user_login[username]'] = 'johannes';
56-
$form['user_login[password]'] = 'test';
57-
}
5854
$form['user_login[_token]'] = '';
5955
$client->submit($form);
6056

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
return [
13+
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
14+
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
15+
];
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
framework:
2+
secret: test
3+
router: { resource: "%kernel.project_dir%/%kernel.test_case%/routing.yml", utf8: true }
4+
test: ~
5+
default_locale: en
6+
profiler: false
7+
session:
8+
storage_id: session.storage.mock_file
9+
10+
services:
11+
logger: { class: Psr\Log\NullLogger }
12+
Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ProfileController:
13+
public: true
14+
calls:
15+
- ['setContainer', ['@Psr\Container\ContainerInterface']]
16+
tags: [container.service_subscriber]
17+
Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ApiAuthenticator: ~
18+
19+
security:
20+
enable_authenticator_manager: true
21+
22+
encoders:
23+
Symfony\Component\Security\Core\User\User: plaintext
24+
25+
providers:
26+
in_memory:
27+
memory:
28+
users:
29+
'jane@example.org': { password: test, roles: [ROLE_USER] }
30+
in_memory2:
31+
memory:
32+
users:
33+
'john@example.org': { password: test, roles: [ROLE_USER] }
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
imports:
2+
- { resource: ./config.yml }
3+
4+
security:
5+
firewalls:
6+
api:
7+
pattern: /
8+
provider: in_memory
9+
custom_authenticator: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ApiAuthenticator
10+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
imports:
2+
- { resource: ./config.yml }
3+
4+
security:
5+
firewalls:
6+
api:
7+
pattern: /
8+
custom_authenticator: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ApiAuthenticator
9+

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy