Skip to content

Commit 0d24b25

Browse files
committed
Sort providers via config
1 parent b02ffe4 commit 0d24b25

4 files changed

Lines changed: 92 additions & 52 deletions

File tree

docs/authentication.md

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,25 +51,14 @@ babdev_websocket:
5151
firewalls: ['main'] # This can be an array to specify multiple firewalls or a string when specifying a single firewall
5252
```
5353

54-
### Provider Priority
55-
56-
When providers are registered to the authenticator service, they are then used in a "first in, first out" order, meaning the order they are triggered will be the same order they are configured in. Assuming your application has multiple authenticators and you want a custom authenticator to be attempted before the session authenticator, you would use the below configuration to do so:
57-
58-
```yaml
59-
babdev_websocket:
60-
authentication:
61-
providers:
62-
custom: ~
63-
session: ~
64-
```
65-
6654
### Registering New Authenticators
6755

6856
In addition to creating a class implementing `BabDev\WebSocketBundle\Authentication\Provider\AuthenticationProvider`, you must also register the authenticator with a `BabDev\WebSocketBundle\DependencyInjection\Factory\Authentication\AuthenticationProviderFactory` to the bundle's container extension. Similar to factories used by Symfony's `SecurityBundle`, this factory is used to allow you to configure the authenticator for your application and build the authentication provider service.
6957

70-
A factory is required to have two methods:
58+
A factory is required to have these methods:
7159

7260
- `getKey()` - A unique name to identify the provider in the application configuration, this name is used as the key in the `providers` list
61+
- `getPriority()` - Defines the priority at which the authentication provider is called
7362
- `addConfiguration()` - Defines the configuration nodes (if any are required) for the authenticator
7463
- `createAuthenticationProvider()` - Registers the authentication provider service to the dependency injection container and returns the provider's service ID
7564

src/DependencyInjection/BabDevWebSocketExtension.php

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,43 @@
1313
use Symfony\Component\DependencyInjection\ChildDefinition;
1414
use Symfony\Component\DependencyInjection\ContainerBuilder;
1515
use Symfony\Component\DependencyInjection\Exception\LogicException;
16+
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
1617
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
1718
use Symfony\Component\DependencyInjection\Reference;
1819
use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension;
1920

20-
final class BabDevWebSocketExtension extends ConfigurableExtension
21+
final class BabDevWebSocketExtension extends ConfigurableExtension implements PrependExtensionInterface
2122
{
2223
/**
23-
* @var list<AuthenticationProviderFactory>
24+
* @var list<array{int, AuthenticationProviderFactory}>
2425
*/
2526
private array $authenticationProviderFactories = [];
2627

28+
/**
29+
* @var AuthenticationProviderFactory[]
30+
*/
31+
private array $sortedAuthenticationProviderFactories = [];
32+
33+
#[\Override]
34+
public function prepend(ContainerBuilder $container): void
35+
{
36+
foreach ($this->getSortedAuthenticationProviderFactories() as $factory) {
37+
if ($factory instanceof PrependExtensionInterface) {
38+
$factory->prepend($container);
39+
}
40+
}
41+
}
42+
2743
public function addAuthenticationProviderFactory(AuthenticationProviderFactory $factory): void
2844
{
29-
$this->authenticationProviderFactories[] = $factory;
45+
$this->authenticationProviderFactories[] = [$factory->getPriority(), $factory];
46+
$this->sortedAuthenticationProviderFactories = [];
3047
}
3148

3249
#[\Override]
3350
public function getConfiguration(array $config, ContainerBuilder $container): Configuration
3451
{
35-
return new Configuration($this->authenticationProviderFactories);
52+
return new Configuration($this->getSortedAuthenticationProviderFactories());
3653
}
3754

3855
#[\Override]
@@ -65,7 +82,7 @@ private function registerAuthenticationConfiguration(array $mergedConfig, Contai
6582
$authenticators = [];
6683

6784
if (isset($mergedConfig['authentication']['providers'])) {
68-
foreach ($this->authenticationProviderFactories as $factory) {
85+
foreach ($this->getSortedAuthenticationProviderFactories() as $factory) {
6986
$key = str_replace('-', '_', $factory->getKey());
7087

7188
if (!isset($mergedConfig['authentication']['providers'][$key])) {
@@ -186,4 +203,23 @@ private function configureWebSocketSession(array $sessionConfig, ContainerBuilde
186203
$container->removeDefinition('babdev_websocket_server.server.session.factory');
187204
$container->removeDefinition('babdev_websocket_server.server.session.storage.factory.read_only_native');
188205
}
206+
207+
/**
208+
* @return AuthenticationProviderFactory[]
209+
*/
210+
private function getSortedAuthenticationProviderFactories(): array
211+
{
212+
if (!$this->sortedAuthenticationProviderFactories) {
213+
$authenticationProviderFactories = [];
214+
foreach ($this->authenticationProviderFactories as $i => $factory) {
215+
$authenticationProviderFactories[] = array_merge($factory, [$i]);
216+
}
217+
218+
usort($authenticationProviderFactories, static fn ($a, $b) => $b[0] <=> $a[0] ?: $a[2] <=> $b[2]);
219+
220+
$this->sortedAuthenticationProviderFactories = array_column($authenticationProviderFactories, 1);
221+
}
222+
223+
return $this->sortedAuthenticationProviderFactories;
224+
}
189225
}

src/DependencyInjection/Factory/Authentication/AuthenticationProviderFactory.php

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,23 @@
88
interface AuthenticationProviderFactory
99
{
1010
/**
11-
* Creates the authentication provider service for the provided configuration.
11+
* Defines the configuration key used to reference the provider in the configuration.
1212
*
13-
* @return string The authentication provider service ID to be used
13+
* @return non-empty-string
1414
*/
15-
public function createAuthenticationProvider(ContainerBuilder $container, array $config): string;
15+
public function getKey(): string;
1616

1717
/**
18-
* Defines the configuration key used to reference the provider in the configuration.
18+
* Defines the priority at which the authentication provider is called.
1919
*/
20-
public function getKey(): string;
20+
public function getPriority(): int;
2121

2222
public function addConfiguration(NodeDefinition $builder): void;
23+
24+
/**
25+
* Creates the authentication provider service for the provided configuration.
26+
*
27+
* @return non-empty-string The authentication provider service ID to be used
28+
*/
29+
public function createAuthenticationProvider(ContainerBuilder $container, array $config): string;
2330
}

src/DependencyInjection/Factory/Authentication/SessionAuthenticationProviderFactory.php

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,64 @@
55
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
66
use Symfony\Component\DependencyInjection\ChildDefinition;
77
use Symfony\Component\DependencyInjection\ContainerBuilder;
8-
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
98
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
109
use Symfony\Component\DependencyInjection\Parameter;
1110

1211
final class SessionAuthenticationProviderFactory implements AuthenticationProviderFactory
1312
{
13+
/**
14+
* Defines the configuration key used to reference the provider in the configuration.
15+
*
16+
* @return non-empty-string
17+
*/
18+
public function getKey(): string
19+
{
20+
return 'session';
21+
}
22+
23+
/**
24+
* Defines the priority at which the authentication provider is called.
25+
*/
26+
public function getPriority(): int
27+
{
28+
return 0;
29+
}
30+
31+
public function addConfiguration(NodeDefinition $builder): void
32+
{
33+
$builder->children()
34+
->variableNode('firewalls')
35+
->defaultNull()
36+
->info('The firewalls from which the session token can be used; can be an array, a string, or null to allow all firewalls.')
37+
->validate()
38+
->ifTrue(static fn ($firewalls): bool => !\is_array($firewalls) && !\is_string($firewalls) && null !== $firewalls)
39+
->thenInvalid('The firewalls node must be an array, a string, or null')
40+
->end()
41+
->end()
42+
;
43+
}
44+
1445
/**
1546
* Creates the authentication provider service for the provided configuration.
1647
*
17-
* @return string The authentication provider service ID to be used
48+
* @param array{firewalls: list<non-empty-string>|non-empty-string|null} $config
49+
*
50+
* @return non-empty-string The authentication provider service ID to be used
1851
*
19-
* @throws InvalidArgumentException if the firewalls node is an invalid type
20-
* @throws RuntimeException if the firewalls node is not configured and the "security.firewalls" container parameter is missing
52+
* @throws RuntimeException if the firewalls node is not configured and the "security.firewalls" container parameter is missing
2153
*/
2254
public function createAuthenticationProvider(ContainerBuilder $container, array $config): string
2355
{
2456
if (\is_array($config['firewalls'])) {
2557
$firewalls = $config['firewalls'];
2658
} elseif (\is_string($config['firewalls'])) {
2759
$firewalls = [$config['firewalls']];
28-
} elseif (null === $config['firewalls']) {
60+
} else {
2961
if (!$container->hasParameter('security.firewalls')) {
3062
throw new RuntimeException('The "firewalls" config for the session authentication provider is not set and the "security.firewalls" container parameter has not been set. Ensure the SecurityBundle is configured or set a list of firewalls to use.');
3163
}
3264

3365
$firewalls = new Parameter('security.firewalls');
34-
} else {
35-
throw new InvalidArgumentException(\sprintf('The "firewalls" config must be an array, a string, or null; "%s" given.', get_debug_type($config['firewalls'])));
3666
}
3767

3868
$providerId = 'babdev_websocket_server.authentication.provider.session.default';
@@ -42,26 +72,4 @@ public function createAuthenticationProvider(ContainerBuilder $container, array
4272

4373
return $providerId;
4474
}
45-
46-
/**
47-
* Defines the configuration key used to reference the provider in the configuration.
48-
*/
49-
public function getKey(): string
50-
{
51-
return 'session';
52-
}
53-
54-
public function addConfiguration(NodeDefinition $builder): void
55-
{
56-
$builder->children()
57-
->variableNode('firewalls')
58-
->defaultNull()
59-
->info('The firewalls from which the session token can be used; can be an array, a string, or null to allow all firewalls.')
60-
->validate()
61-
->ifTrue(static fn ($firewalls): bool => !\is_array($firewalls) && !\is_string($firewalls) && null !== $firewalls)
62-
->thenInvalid('The firewalls node must be an array, a string, or null')
63-
->end()
64-
->end()
65-
;
66-
}
6775
}

0 commit comments

Comments
 (0)