Skip to content

Commit e1caab1

Browse files
introduce TokenHandlerFactory
1 parent 4c9394f commit e1caab1

File tree

10 files changed

+326
-25
lines changed

10 files changed

+326
-25
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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\AccessToken;
13+
14+
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
15+
use Symfony\Component\DependencyInjection\ChildDefinition;
16+
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
18+
/**
19+
* Configure a token handler from a service id.
20+
*
21+
* @see \Symfony\Bundle\SecurityBundle\Tests\DependencyInjection\Security\Factory\AccessTokenFactoryTest
22+
*
23+
* @author Vincent Chalamon <vincentchalamon@gmail.com>
24+
*/
25+
class IdTokenHandlerFactory implements TokenHandlerFactoryInterface
26+
{
27+
public function create(ContainerBuilder $container, string $id, array|string $config): void
28+
{
29+
$container->setDefinition($id, new ChildDefinition($config));
30+
}
31+
32+
public function getKey(): string
33+
{
34+
return 'id';
35+
}
36+
37+
public function addConfiguration(NodeBuilder $node): void
38+
{
39+
$node->scalarNode($this->getKey())->end();
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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\AccessToken;
13+
14+
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
15+
use Symfony\Component\DependencyInjection\ChildDefinition;
16+
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
use Symfony\Component\DependencyInjection\Reference;
18+
use Symfony\Component\HttpClient\HttpClient;
19+
20+
/**
21+
* Configure a token handler for an OIDC server.
22+
*
23+
* @see \Symfony\Bundle\SecurityBundle\Tests\DependencyInjection\Security\Factory\AccessTokenFactoryTest
24+
*
25+
* @author Vincent Chalamon <vincentchalamon@gmail.com>
26+
*/
27+
class OidcUserInfoTokenHandlerFactory implements TokenHandlerFactoryInterface
28+
{
29+
public function create(ContainerBuilder $container, string $id, array|string $config): void
30+
{
31+
$tokenHandlerDefinition = $container->setDefinition($id, new ChildDefinition('security.access_token_handler.oidc_user_info'));
32+
$tokenHandlerDefinition->setArgument(2, $config['claim']);
33+
34+
// Create the client service
35+
if (!isset($config['client']['id'])) {
36+
$clientDefinitionId = 'http_client.security.access_token_handler.oidc_user_info';
37+
$container->register($clientDefinitionId, HttpClient::class)
38+
->setFactory([HttpClient::class, 'create'])
39+
->setArguments([$config['client']])
40+
->addTag('http_client.client')
41+
;
42+
$config['client'] = ['id' => $clientDefinitionId];
43+
}
44+
45+
$tokenHandlerDefinition->setArgument(0, new Reference($config['client']['id']));
46+
}
47+
48+
public function getKey(): string
49+
{
50+
return 'oidc_user_info';
51+
}
52+
53+
public function addConfiguration(NodeBuilder $node): void
54+
{
55+
$node
56+
->arrayNode($this->getKey())
57+
->fixXmlConfig($this->getKey())
58+
->children()
59+
->scalarNode('claim')->defaultValue('sub')->end()
60+
->arrayNode('client')
61+
->isRequired()
62+
->beforeNormalization()
63+
->ifString()
64+
->then(static function ($v): array { return ['id' => $v]; })
65+
->end()
66+
->prototype('scalar')->end()
67+
->end()
68+
->end()
69+
->end()
70+
;
71+
}
72+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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\AccessToken;
13+
14+
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
15+
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
17+
/**
18+
* TokenHandlerFactoryInterface is the interface for all token handler factories.
19+
*
20+
* @author Vincent Chalamon <vincentchalamon@gmail.com>
21+
*/
22+
interface TokenHandlerFactoryInterface
23+
{
24+
/**
25+
* Create a generic token handler service.
26+
*/
27+
public function create(ContainerBuilder $container, string $id, array|string $config): void;
28+
29+
/**
30+
* Get a generic token handler configuration key.
31+
*/
32+
public function getKey(): string;
33+
34+
/**
35+
* Add a generic token handler configuration.
36+
*/
37+
public function addConfiguration(NodeBuilder $node): void;
38+
}

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

+57-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111

1212
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
1313

14+
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\TokenHandlerFactoryInterface;
1415
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
16+
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
1517
use Symfony\Component\DependencyInjection\ChildDefinition;
1618
use Symfony\Component\DependencyInjection\ContainerBuilder;
1719
use Symfony\Component\DependencyInjection\Reference;
@@ -20,14 +22,18 @@
2022
* AccessTokenFactory creates services for Access Token authentication.
2123
*
2224
* @author Florent Morselli <florent.morselli@spomky-labs.com>
25+
* @author Vincent Chalamon <vincentchalamon@gmail.com>
2326
*
2427
* @internal
2528
*/
2629
final class AccessTokenFactory extends AbstractFactory
2730
{
2831
private const PRIORITY = -40;
2932

30-
public function __construct()
33+
/**
34+
* @param array<array-key, TokenHandlerFactoryInterface> $tokenHandlerFactories
35+
*/
36+
public function __construct(private readonly array $tokenHandlerFactories)
3137
{
3238
$this->options = [];
3339
$this->defaultFailureHandlerOptions = [];
@@ -39,7 +45,6 @@ public function addConfiguration(NodeDefinition $node): void
3945
$builder = $node->children();
4046

4147
$builder
42-
->scalarNode('token_handler')->isRequired()->end()
4348
->scalarNode('user_provider')->defaultNull()->end()
4449
->scalarNode('realm')->defaultNull()->end()
4550
->scalarNode('success_handler')->defaultNull()->end()
@@ -57,6 +62,38 @@ public function addConfiguration(NodeDefinition $node): void
5762
->scalarPrototype()->end()
5863
->end()
5964
;
65+
66+
$tokenHandlerNodeBuilder = $builder
67+
->arrayNode('token_handler')
68+
->example([
69+
'id' => 'App\Security\CustomTokenHandler',
70+
])
71+
72+
->beforeNormalization()
73+
->ifString()
74+
->then(static function (string $v): array { return ['id' => $v]; })
75+
->end()
76+
77+
->beforeNormalization()
78+
->ifTrue(static function ($v) { return \is_array($v) && 1 < \count($v); })
79+
->then(static function () { throw new InvalidConfigurationException('You cannot configure multiple token handlers.'); })
80+
->end()
81+
82+
// "isRequired" must be set otherwise the following custom validation is not called
83+
->isRequired()
84+
->beforeNormalization()
85+
->ifTrue(static function ($v) { return \is_array($v) && 1 > \count($v); })
86+
->then(static function () { throw new InvalidConfigurationException('You must set a token handler.'); })
87+
->end()
88+
89+
->children()
90+
;
91+
92+
foreach ($this->tokenHandlerFactories as $factory) {
93+
$factory->addConfiguration($tokenHandlerNodeBuilder);
94+
}
95+
96+
$tokenHandlerNodeBuilder->end();
6097
}
6198

6299
public function getPriority(): int
@@ -76,10 +113,11 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal
76113
$failureHandler = isset($config['failure_handler']) ? new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config)) : null;
77114
$authenticatorId = sprintf('security.authenticator.access_token.%s', $firewallName);
78115
$extractorId = $this->createExtractor($container, $firewallName, $config['token_extractors']);
116+
$tokenHandlerId = $this->createTokenHandler($container, $firewallName, $config['token_handler']);
79117

80118
$container
81119
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.access_token'))
82-
->replaceArgument(0, new Reference($config['token_handler']))
120+
->replaceArgument(0, new Reference($tokenHandlerId))
83121
->replaceArgument(1, new Reference($extractorId))
84122
->replaceArgument(2, $userProvider)
85123
->replaceArgument(3, $successHandler)
@@ -115,4 +153,20 @@ private function createExtractor(ContainerBuilder $container, string $firewallNa
115153

116154
return $extractorId;
117155
}
156+
157+
private function createTokenHandler(ContainerBuilder $container, string $firewallName, array $config): string
158+
{
159+
$key = array_keys($config)[0];
160+
$id = sprintf('security.access_token_handler.%s', $firewallName);
161+
162+
foreach ($this->tokenHandlerFactories as $factory) {
163+
if ($key !== $factory->getKey()) {
164+
continue;
165+
}
166+
167+
$factory->create($container, $id, $config[$key]);
168+
}
169+
170+
return $id;
171+
}
118172
}

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

+2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
abstract_arg('access token extractors'),
4343
])
4444

45+
// OIDC
4546
->set('security.access_token_handler.oidc_user_info', OidcUserInfoTokenHandler::class)
47+
->abstract()
4648
;
4749
};

src/Symfony/Bundle/SecurityBundle/SecurityBundle.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterTokenUsageTrackingPass;
2323
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\ReplaceDecoratedRememberMeHandlerPass;
2424
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\SortFirewallListenersPass;
25+
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\IdTokenHandlerFactory;
26+
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\OidcUserInfoTokenHandlerFactory;
2527
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AccessTokenFactory;
2628
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\CustomAuthenticatorFactory;
2729
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginFactory;
@@ -70,7 +72,10 @@ public function build(ContainerBuilder $container)
7072
$extension->addAuthenticatorFactory(new CustomAuthenticatorFactory());
7173
$extension->addAuthenticatorFactory(new LoginThrottlingFactory());
7274
$extension->addAuthenticatorFactory(new LoginLinkFactory());
73-
$extension->addAuthenticatorFactory(new AccessTokenFactory());
75+
$extension->addAuthenticatorFactory(new AccessTokenFactory([
76+
new IdTokenHandlerFactory(),
77+
new OidcUserInfoTokenHandlerFactory(),
78+
]));
7479

7580
$extension->addUserProviderFactory(new InMemoryFactory());
7681
$extension->addUserProviderFactory(new LdapFactory());

0 commit comments

Comments
 (0)