Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a27e37a

Browse filesBrowse files
committed
feature #48594 [SecurityBundle] Improve support for authenticators that don't need a user provider (wouterj)
This PR was merged into the 6.3 branch. Discussion ---------- [SecurityBundle] Improve support for authenticators that don't need a user provider | Q | A | ------------- | --- | Branch? | 6.3 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | Ref #48285, #48272 | License | MIT | Doc PR | - This builds on top of the self-contained token feature added in 6.2 (#48285). While that PR allows access token handlers to load the user from the access token without user provider, it was still required to configure a user provider in the code. With this PR, the bundle allows a user provider to not be configured when: 1. The firewall is `stateless`, otherwise we still need the user provider to refresh the user 2. The authenticator factory implements `StatelessAuthenticatorFactoryInterface` (i.e. declares compatibility with no user provider) This can help with simplifying the code in #48272 (comment) , as we no longer have to define a special user badge and provider. cc `@Jeroeny` Commits ------- 5464c57 [SecurityBundle] Improve support for authenticators that don't need a user provider
2 parents 6bf414c + 5464c57 commit a27e37a
Copy full SHA for a27e37a

File tree

Expand file treeCollapse file tree

7 files changed

+93
-14
lines changed
Filter options
Expand file treeCollapse file tree

7 files changed

+93
-14
lines changed

‎src/Symfony/Bundle/SecurityBundle/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* Deprecate enabling bundle and not configuring it
88
* Add `_stateless` attribute to the request when firewall is stateless
9+
* Add `StatelessAuthenticatorFactoryInterface` for authenticators targeting `stateless` firewalls only and that don't require a user provider
910

1011
6.2
1112
---

‎src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php
+3-3Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
*
2424
* @internal
2525
*/
26-
final class AccessTokenFactory extends AbstractFactory
26+
final class AccessTokenFactory extends AbstractFactory implements StatelessAuthenticatorFactoryInterface
2727
{
2828
private const PRIORITY = -40;
2929

@@ -67,7 +67,7 @@ public function getKey(): string
6767
return 'access_token';
6868
}
6969

70-
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string
70+
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, ?string $userProviderId): string
7171
{
7272
$successHandler = isset($config['success_handler']) ? new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config)) : null;
7373
$failureHandler = isset($config['failure_handler']) ? new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config)) : null;
@@ -78,7 +78,7 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal
7878
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.access_token'))
7979
->replaceArgument(0, new Reference($config['token_handler']))
8080
->replaceArgument(1, new Reference($extractorId))
81-
->replaceArgument(2, new Reference($userProviderId))
81+
->replaceArgument(2, $userProviderId ? new Reference($userProviderId) : null)
8282
->replaceArgument(3, $successHandler)
8383
->replaceArgument(4, $failureHandler)
8484
->replaceArgument(5, $config['realm'])
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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\DependencyInjection\Security\Factory;
13+
14+
use Symfony\Component\DependencyInjection\ContainerBuilder;
15+
16+
/**
17+
* Stateless authenticators are authenticators that can work without a user provider.
18+
*
19+
* This situation can only occur in stateless firewalls, as statefull firewalls
20+
* need the user provider to refresh the user in each subsequent request. A
21+
* stateless authenticator can be used on both stateless and statefull authenticators.
22+
*
23+
* @author Wouter de Jong <wouter@wouterj.nl>
24+
*/
25+
interface StatelessAuthenticatorFactoryInterface extends AuthenticatorFactoryInterface
26+
{
27+
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, ?string $userProviderId): string|array;
28+
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
+21-7Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Bridge\Twig\Extension\LogoutUrlExtension;
1515
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface;
1616
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FirewallListenerFactoryInterface;
17+
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\StatelessAuthenticatorFactoryInterface;
1718
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface;
1819
use Symfony\Bundle\SecurityBundle\SecurityBundle;
1920
use Symfony\Component\Config\Definition\ConfigurationInterface;
@@ -615,6 +616,10 @@ private function createAuthenticationListeners(ContainerBuilder $container, stri
615616
throw new InvalidConfigurationException(sprintf('Authenticator factory "%s" ("%s") must implement "%s".', get_debug_type($factory), $key, AuthenticatorFactoryInterface::class));
616617
}
617618

619+
if (null === $userProvider && !$factory instanceof StatelessAuthenticatorFactoryInterface) {
620+
$userProvider = $this->createMissingUserProvider($container, $id, $key);
621+
}
622+
618623
$authenticators = $factory->createAuthenticator($container, $id, $firewall[$key], $userProvider);
619624
if (\is_array($authenticators)) {
620625
foreach ($authenticators as $authenticator) {
@@ -641,7 +646,7 @@ private function createAuthenticationListeners(ContainerBuilder $container, stri
641646
return [$listeners, $defaultEntryPoint];
642647
}
643648

644-
private function getUserProvider(ContainerBuilder $container, string $id, array $firewall, string $factoryKey, ?string $defaultProvider, array $providerIds, ?string $contextListenerId): string
649+
private function getUserProvider(ContainerBuilder $container, string $id, array $firewall, string $factoryKey, ?string $defaultProvider, array $providerIds, ?string $contextListenerId): ?string
645650
{
646651
if (isset($firewall[$factoryKey]['provider'])) {
647652
if (!isset($providerIds[$normalizedName = str_replace('-', '_', $firewall[$factoryKey]['provider'])])) {
@@ -660,13 +665,11 @@ private function getUserProvider(ContainerBuilder $container, string $id, array
660665
}
661666

662667
if (!$providerIds) {
663-
$userProvider = sprintf('security.user.provider.missing.%s', $factoryKey);
664-
$container->setDefinition(
665-
$userProvider,
666-
(new ChildDefinition('security.user.provider.missing'))->replaceArgument(0, $id)
667-
);
668+
if ($firewall['stateless'] ?? false) {
669+
return null;
670+
}
668671

669-
return $userProvider;
672+
return $this->createMissingUserProvider($container, $id, $factoryKey);
670673
}
671674

672675
if ('remember_me' === $factoryKey || 'anonymous' === $factoryKey || 'custom_authenticators' === $factoryKey) {
@@ -680,6 +683,17 @@ private function getUserProvider(ContainerBuilder $container, string $id, array
680683
throw new InvalidConfigurationException(sprintf('Not configuring explicitly the provider for the "%s" authenticator on "%s" firewall is ambiguous as there is more than one registered provider.', $factoryKey, $id));
681684
}
682685

686+
private function createMissingUserProvider(ContainerBuilder $container, string $id, string $factoryKey): string
687+
{
688+
$userProvider = sprintf('security.user.provider.missing.%s', $factoryKey);
689+
$container->setDefinition(
690+
$userProvider,
691+
(new ChildDefinition('security.user.provider.missing'))->replaceArgument(0, $id)
692+
);
693+
694+
return $userProvider;
695+
}
696+
683697
private function createHashers(array $hashers, ContainerBuilder $container)
684698
{
685699
$hasherMap = [];

‎src/Symfony/Bundle/SecurityBundle/Tests/Functional/AccessTokenTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/Functional/AccessTokenTest.php
+12Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,4 +315,16 @@ public function customQueryAccessTokenFailure(): iterable
315315
{
316316
yield ['/foo?protection_token=INVALID_ACCESS_TOKEN'];
317317
}
318+
319+
public function testSelfContainedTokens()
320+
{
321+
$client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_self_contained_token.yml']);
322+
$client->catchExceptions(false);
323+
$client->request('GET', '/foo', [], [], ['HTTP_AUTHORIZATION' => 'Bearer SELF_CONTAINED_ACCESS_TOKEN']);
324+
$response = $client->getResponse();
325+
326+
$this->assertInstanceOf(Response::class, $response);
327+
$this->assertSame(200, $response->getStatusCode());
328+
$this->assertSame(['message' => 'Welcome @dunglas!'], json_decode($response->getContent(), true));
329+
}
318330
}

‎src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Handler/AccessTokenHandler.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Handler/AccessTokenHandler.php
+2-4Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,17 @@
1212
namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Handler;
1313

1414
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
15+
use Symfony\Component\Security\Core\User\InMemoryUser;
1516
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
1617
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
1718

1819
class AccessTokenHandler implements AccessTokenHandlerInterface
1920
{
20-
public function __construct()
21-
{
22-
}
23-
2421
public function getUserBadgeFrom(string $accessToken): UserBadge
2522
{
2623
return match ($accessToken) {
2724
'VALID_ACCESS_TOKEN' => new UserBadge('dunglas'),
25+
'SELF_CONTAINED_ACCESS_TOKEN' => new UserBadge('dunglas', fn () => new InMemoryUser('dunglas', null, ['ROLE_USER'])),
2826
default => throw new BadCredentialsException('Invalid credentials.'),
2927
};
3028
}
+26Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
imports:
2+
- { resource: ./../config/framework.yml }
3+
4+
framework:
5+
http_method_override: false
6+
serializer: ~
7+
8+
security:
9+
password_hashers:
10+
Symfony\Component\Security\Core\User\InMemoryUser: plaintext
11+
12+
firewalls:
13+
main:
14+
pattern: ^/
15+
stateless: true
16+
access_token:
17+
token_handler: access_token.access_token_handler
18+
token_extractors: 'header'
19+
realm: 'My API'
20+
21+
access_control:
22+
- { path: ^/foo, roles: ROLE_USER }
23+
24+
services:
25+
access_token.access_token_handler:
26+
class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Handler\AccessTokenHandler

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.