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 5464c57

Browse filesBrowse files
wouterjchalasr
authored andcommitted
[SecurityBundle] Improve support for authenticators that don't need a user provider
Ref #48285
1 parent cb6f2c5 commit 5464c57
Copy full SHA for 5464c57

File tree

7 files changed

+93
-14
lines changed
Filter options

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.