feature #36918 [Security] Removed "services" prototype node from "custom_authenticator" (wouterj)

This PR was merged into the 5.1 branch.

Discussion
----------

[Security] Removed "services" prototype node from "custom_authenticator"

| Q             | A
| ------------- | ---
| Branch?       | 5.1
| Bug fix?      | no
| New feature?  | no
| Deprecations? | no
| Tickets       | -
| License       | MIT
| Doc PR        | -

This was discussed multiple times in the original authenticators PR. It's a bit hacky to implement, but I think it's worth the win on DX side.

I have no idea if and how this should be documented in the CHANGELOG.

---

**Before**
```yaml
security:
    # ...
    firewalls:
        custom_authenticator:
            services:
                - App\Security\MyCustomAuthenticator
```

**After**
```yaml
security:
    # ...
    firewalls:
        custom_authenticator: App\Security\MyCustomAuthenticator
```

---

**Before**
```yaml
security:
    # ...
    firewalls:
        custom_authenticator:
            services:
                - App\Security\MyCustomAuthenticator
                - App\Security\OtherCustomAuthenticator
```

**After**
```yaml
security:
    # ...
    firewalls:
        custom_authenticators:
            - App\Security\MyCustomAuthenticator
            - App\Security\OtherCustomAuthenticator
```

Commits
-------

387ed4a0a3 Removed "services" prototype node from "custom_authenticator"
This commit is contained in:
Fabien Potencier 2020-05-23 14:25:49 +02:00
commit c40378986c
2 changed files with 81 additions and 11 deletions

View File

@ -35,7 +35,7 @@ class CustomAuthenticatorFactory implements AuthenticatorFactoryInterface, Secur
public function getKey(): string public function getKey(): string
{ {
return 'custom_authenticator'; return 'custom_authenticators';
} }
/** /**
@ -44,19 +44,27 @@ class CustomAuthenticatorFactory implements AuthenticatorFactoryInterface, Secur
public function addConfiguration(NodeDefinition $builder) public function addConfiguration(NodeDefinition $builder)
{ {
$builder $builder
->fixXmlConfig('service') ->info('An array of service ids for all of your "authenticators"')
->children() ->requiresAtLeastOneElement()
->arrayNode('services') ->prototype('scalar')->end();
->info('An array of service ids for all of your "authenticators"')
->requiresAtLeastOneElement() // get the parent array node builder ("firewalls") from inside the children builder
->prototype('scalar')->end() $factoryRootNode = $builder->end()->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() ->end()
; ;
} }
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): array public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): array
{ {
return $config['services']; return $config;
} }
} }

View File

@ -24,11 +24,15 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\ExpressionLanguage\Expression; use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\AuthenticatorInterface; use Symfony\Component\Security\Guard\AuthenticatorInterface as GuardAuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\HttpBasicAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
class SecurityExtensionTest extends TestCase class SecurityExtensionTest extends TestCase
{ {
@ -520,6 +524,41 @@ class SecurityExtensionTest extends TestCase
$container->compile(); $container->compile();
} }
/**
* @dataProvider provideConfigureCustomAuthenticatorData
*/
public function testConfigureCustomAuthenticator(array $firewall, array $expectedAuthenticators)
{
$container = $this->getRawContainer();
$container->loadFromExtension('security', [
'enable_authenticator_manager' => true,
'providers' => [
'first' => ['id' => 'users'],
],
'firewalls' => [
'main' => $firewall,
],
]);
$container->compile();
$this->assertEquals($expectedAuthenticators, array_map('strval', $container->getDefinition('security.authenticator.manager.main')->getArgument(0)));
}
public function provideConfigureCustomAuthenticatorData()
{
yield [
['custom_authenticator' => TestAuthenticator::class],
[TestAuthenticator::class],
];
yield [
['custom_authenticators' => [TestAuthenticator::class, HttpBasicAuthenticator::class]],
[TestAuthenticator::class, HttpBasicAuthenticator::class],
];
}
protected function getRawContainer() protected function getRawContainer()
{ {
$container = new ContainerBuilder(); $container = new ContainerBuilder();
@ -547,7 +586,30 @@ class SecurityExtensionTest extends TestCase
} }
} }
class NullAuthenticator implements AuthenticatorInterface class TestAuthenticator implements AuthenticatorInterface
{
public function supports(Request $request): ?bool
{
}
public function authenticate(Request $request): PassportInterface
{
}
public function createAuthenticatedToken(PassportInterface $passport, string $firewallName): TokenInterface
{
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
}
}
class NullAuthenticator implements GuardAuthenticatorInterface
{ {
public function start(Request $request, AuthenticationException $authException = null) public function start(Request $request, AuthenticationException $authException = null)
{ {