From 9e7959d58586f9878197e9bc6c46042a4a52338d Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Tue, 2 Jun 2020 17:04:50 +0200 Subject: [PATCH] Automatically provide entry points from the custom_authenticator --- .../Factory/CustomAuthenticatorFactory.php | 56 ++++++++++++++----- .../SecurityExtensionTest.php | 16 +++++- 2 files changed, 58 insertions(+), 14 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php index d9245b0616022..20be003c6218a 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php @@ -13,7 +13,9 @@ use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; /** * @author Wouter de Jong @@ -21,7 +23,7 @@ * @internal * @experimental in Symfony 5.1 */ -class CustomAuthenticatorFactory implements AuthenticatorFactoryInterface, SecurityFactoryInterface +class CustomAuthenticatorFactory implements AuthenticatorFactoryInterface, SecurityFactoryInterface, EntryPointFactoryInterface { public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint) { @@ -44,27 +46,55 @@ public function getKey(): string public function addConfiguration(NodeDefinition $builder) { $builder - ->info('An array of service ids for all of your "authenticators"') - ->requiresAtLeastOneElement() - ->prototype('scalar')->end(); + ->fixXmlConfig('service') + ->beforeNormalization() + ->ifTrue(function ($v) { return is_string($v) || (is_array($v) && !isset($v['services']) && !isset($v['entry_point'])); }) + ->then(function ($v) { + return ['services' => (array) $v]; + }) + ->end() + ->children() + ->arrayNode('services') + ->info('An array of service ids for all of your "authenticators"') + ->requiresAtLeastOneElement() + ->prototype('scalar')->end() + ->end() + ->scalarNode('entry_point')->defaultNull()->end() + ->end(); // get the parent array node builder ("firewalls") from inside the children builder $factoryRootNode = $builder->end()->end(); $factoryRootNode ->fixXmlConfig('custom_authenticator') - ->validate() - ->ifTrue(function ($v) { return isset($v['custom_authenticators']) && empty($v['custom_authenticators']); }) - ->then(function ($v) { - unset($v['custom_authenticators']); - - return $v; - }) - ->end() ; } public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): array { - return $config; + return $config['services']; + } + + public function registerEntryPoint(ContainerBuilder $container, string $id, array $config): ?string + { + if (isset($config['entry_point'])) { + return $config['entry_point']; + } + + $entryPoints = []; + foreach ($config['services'] as $authenticatorId) { + if (class_exists($authenticatorId) && is_subclass_of($authenticatorId, AuthenticationEntryPointInterface::class)) { + $entryPoints[] = $authenticatorId; + } + } + + if (!$entryPoints) { + return null; + } + + if (1 === \count($entryPoints)) { + return current($entryPoints); + } + + throw new InvalidConfigurationException(sprintf('Because you have multiple custom authenticators, you need to set the "custom_authenticators.entry_point" key to one of your authenticators (%s).', implode(', ', $config['services']))); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php index b7063f42a05b5..077e8533081e6 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php @@ -31,6 +31,7 @@ use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Guard\AuthenticatorInterface as GuardAuthenticatorInterface; use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; +use Symfony\Component\Security\Http\Authenticator\FormLoginAuthenticator; use Symfony\Component\Security\Http\Authenticator\HttpBasicAuthenticator; use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; @@ -527,9 +528,13 @@ public function testAlwaysAuthenticateBeforeGrantingCannotBeTrueWithAuthenticati /** * @dataProvider provideConfigureCustomAuthenticatorData */ - public function testConfigureCustomAuthenticator(array $firewall, array $expectedAuthenticators) + public function testConfigureCustomAuthenticator(array $firewall, array $expectedAuthenticators, string $expectedEntryPoint) { $container = $this->getRawContainer(); + $container->register(TestAuthenticator::class); + $container->register(HttpBasicAuthenticator::class); + $container->register(FormLoginAuthenticator::class); + $container->loadFromExtension('security', [ 'enable_authenticator_manager' => true, 'providers' => [ @@ -544,6 +549,7 @@ public function testConfigureCustomAuthenticator(array $firewall, array $expecte $container->compile(); $this->assertEquals($expectedAuthenticators, array_map('strval', $container->getDefinition('security.authenticator.manager.main')->getArgument(0))); + $this->assertEquals($expectedEntryPoint, (string) $container->getDefinition('security.firewall.map.config.main')->getArgument(7)); } public function provideConfigureCustomAuthenticatorData() @@ -551,11 +557,19 @@ public function provideConfigureCustomAuthenticatorData() yield [ ['custom_authenticator' => TestAuthenticator::class], [TestAuthenticator::class], + '', ]; yield [ ['custom_authenticators' => [TestAuthenticator::class, HttpBasicAuthenticator::class]], [TestAuthenticator::class, HttpBasicAuthenticator::class], + HttpBasicAuthenticator::class, + ]; + + yield [ + ['custom_authenticators' => ['services' => [FormLoginAuthenticator::class, HttpBasicAuthenticator::class], 'entry_point' => HttpBasicAuthenticator::class]], + [FormLoginAuthenticator::class, HttpBasicAuthenticator::class], + HttpBasicAuthenticator::class, ]; }