Integrated GuardAuthenticationManager in the SecurityBundle

This commit is contained in:
Wouter J 2019-09-08 15:55:27 +02:00 committed by Wouter de Jong
parent a6890dbcf0
commit 9b7fddd10c
8 changed files with 188 additions and 24 deletions

View File

@ -73,6 +73,7 @@ class MainConfiguration implements ConfigurationInterface
->booleanNode('hide_user_not_found')->defaultTrue()->end()
->booleanNode('always_authenticate_before_granting')->defaultFalse()->end()
->booleanNode('erase_credentials')->defaultTrue()->end()
->booleanNode('guard_authentication_manager')->defaultFalse()->end()
->arrayNode('access_decision_manager')
->addDefaultsIfNotSet()
->children()

View File

@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class CustomAuthenticatorFactory implements AuthenticatorFactoryInterface, SecurityFactoryInterface
{
public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint)
{
throw new \LogicException('Custom authenticators are not supported when "security.enable_authenticator_manager" is not set to true.');
}
public function getPosition(): string
{
return 'pre_auth';
}
public function getKey(): string
{
return 'custom_authenticator';
}
/**
* @param ArrayNodeDefinition $builder
*/
public function addConfiguration(NodeDefinition $builder)
{
$builder
->fixXmlConfig('service')
->children()
->arrayNode('services')
->info('An array of service ids for all of your "authenticators"')
->requiresAtLeastOneElement()
->prototype('scalar')->end()
->end()
->end()
;
}
public function createAuthenticator(ContainerBuilder $container, string $id, array $config, string $userProviderId): array
{
return $config['services'];
}
}

View File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* @author Wouter de Jong <wouter@wouterj.nl>
*/
interface GuardFactoryInterface
{
/**
* Creates the Guard service(s) for the provided configuration.
*
* @return string|string[] The Guard service ID(s) to be used by the firewall
*/
public function createGuard(ContainerBuilder $container, string $id, array $config, string $userProviderId);
}

View File

@ -21,7 +21,7 @@ use Symfony\Component\DependencyInjection\Reference;
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class HttpBasicFactory implements SecurityFactoryInterface
class HttpBasicFactory implements SecurityFactoryInterface, GuardFactoryInterface
{
public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint)
{
@ -46,6 +46,17 @@ class HttpBasicFactory implements SecurityFactoryInterface
return [$provider, $listenerId, $entryPointId];
}
public function createGuard(ContainerBuilder $container, string $id, array $config, string $userProviderId): string
{
$authenticatorId = 'security.authenticator.http_basic.'.$id;
$container
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.http_basic'))
->replaceArgument(0, $config['realm'])
->replaceArgument(1, new Reference($userProviderId));
return $authenticatorId;
}
public function getPosition()
{
return 'http';

View File

@ -11,6 +11,7 @@
namespace Symfony\Bundle\SecurityBundle\DependencyInjection;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\GuardFactoryInterface;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RememberMeFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface;
@ -52,6 +53,8 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
private $userProviderFactories = [];
private $statelessFirewallKeys = [];
private $guardAuthenticationManagerEnabled = false;
public function __construct()
{
foreach ($this->listenerPositions as $position) {
@ -135,6 +138,8 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
$container->setParameter('security.access.always_authenticate_before_granting', $config['always_authenticate_before_granting']);
$container->setParameter('security.authentication.hide_user_not_found', $config['hide_user_not_found']);
$this->guardAuthenticationManagerEnabled = $config['guard_authentication_manager'];
$this->createFirewalls($config, $container);
$this->createAuthorization($config, $container);
$this->createRoleHierarchy($config, $container);
@ -258,8 +263,13 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
$authenticationProviders = array_map(function ($id) {
return new Reference($id);
}, array_values(array_unique($authenticationProviders)));
$authenticationManagerId = 'security.authentication.manager.provider';
if ($this->guardAuthenticationManagerEnabled) {
$authenticationManagerId = 'security.authentication.manager.guard';
$container->setAlias('security.authentication.manager', new Alias($authenticationManagerId));
}
$container
->getDefinition('security.authentication.manager')
->getDefinition($authenticationManagerId)
->replaceArgument(0, new IteratorArgument($authenticationProviders))
;
@ -467,31 +477,27 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
$key = str_replace('-', '_', $factory->getKey());
if (isset($firewall[$key])) {
if (isset($firewall[$key]['provider'])) {
if (!isset($providerIds[$normalizedName = str_replace('-', '_', $firewall[$key]['provider'])])) {
throw new InvalidConfigurationException(sprintf('Invalid firewall "%s": user provider "%s" not found.', $id, $firewall[$key]['provider']));
}
$userProvider = $providerIds[$normalizedName];
} elseif ('remember_me' === $key || 'anonymous' === $key) {
// RememberMeFactory will use the firewall secret when created, AnonymousAuthenticationListener does not load users.
$userProvider = null;
$userProvider = $this->getUserProvider($container, $id, $firewall, $key, $defaultProvider, $providerIds, $contextListenerId);
if ('remember_me' === $key && $contextListenerId) {
$container->getDefinition($contextListenerId)->addTag('security.remember_me_aware', ['id' => $id, 'provider' => 'none']);
if ($this->guardAuthenticationManagerEnabled) {
if (!$factory instanceof GuardFactoryInterface) {
throw new InvalidConfigurationException(sprintf('Cannot configure GuardAuthenticationManager as %s authentication does not support it, set security.guard_authentication_manager to `false`.', $key));
}
$authenticators = $factory->createGuard($container, $id, $firewall[$key], $userProvider);
if (\is_array($authenticators)) {
foreach ($authenticators as $i => $authenticator) {
$authenticationProviders[$id.'_'.$key.$i] = $authenticator;
}
} else {
$authenticationProviders[$id.'_'.$key] = $authenticators;
}
} elseif ($defaultProvider) {
$userProvider = $defaultProvider;
} elseif (empty($providerIds)) {
$userProvider = sprintf('security.user.provider.missing.%s', $key);
$container->setDefinition($userProvider, (new ChildDefinition('security.user.provider.missing'))->replaceArgument(0, $id));
} else {
throw new InvalidConfigurationException(sprintf('Not configuring explicitly the provider for the "%s" listener on "%s" firewall is ambiguous as there is more than one registered provider.', $key, $id));
list($provider, $listenerId, $defaultEntryPoint) = $factory->create($container, $id, $firewall[$key], $userProvider, $defaultEntryPoint);
$listeners[] = new Reference($listenerId);
$authenticationProviders[] = $provider;
}
list($provider, $listenerId, $defaultEntryPoint) = $factory->create($container, $id, $firewall[$key], $userProvider, $defaultEntryPoint);
$listeners[] = new Reference($listenerId);
$authenticationProviders[] = $provider;
$hasListeners = true;
}
}
@ -504,6 +510,42 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
return [$listeners, $defaultEntryPoint];
}
private function getUserProvider(ContainerBuilder $container, string $id, array $firewall, string $factoryKey, ?string $defaultProvider, array $providerIds, ?string $contextListenerId): ?string
{
if (isset($firewall[$factoryKey]['provider'])) {
if (!isset($providerIds[$normalizedName = str_replace('-', '_', $firewall[$factoryKey]['provider'])])) {
throw new InvalidConfigurationException(sprintf('Invalid firewall "%s": user provider "%s" not found.', $id, $firewall[$factoryKey]['provider']));
}
return $providerIds[$normalizedName];
}
if ('remember_me' === $factoryKey || 'anonymous' === $factoryKey) {
if ('remember_me' === $factoryKey && $contextListenerId) {
$container->getDefinition($contextListenerId)->addTag('security.remember_me_aware', ['id' => $id, 'provider' => 'none']);
}
// RememberMeFactory will use the firewall secret when created
return null;
}
if ($defaultProvider) {
return $defaultProvider;
}
if (!$providerIds) {
$userProvider = sprintf('security.user.provider.missing.%s', $factoryKey);
$container->setDefinition(
$userProvider,
(new ChildDefinition('security.user.provider.missing'))->replaceArgument(0, $id)
);
return $userProvider;
}
throw new InvalidConfigurationException(sprintf('Not configuring explicitly the provider for the "%s" listener on "%s" firewall is ambiguous as there is more than one registered provider.', $factoryKey, $id));
}
private function createEncoders(array $encoders, ContainerBuilder $container)
{
$encoderMap = [];

View File

@ -0,0 +1,16 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="security.authenticator.http_basic"
class="Symfony\Component\Security\Core\Authentication\Authenticator\HttpBasicAuthenticator"
abstract="true">
<argument type="abstract">realm name</argument>
<argument type="abstract">user provider</argument>
<argument type="service" id="security.encoder_factory" />
<argument type="service" id="logger" on-invalid="null" />
</service>
</services>
</container>

View File

@ -45,13 +45,22 @@
</service>
<!-- Authentication related services -->
<service id="security.authentication.manager" class="Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager">
<service id="security.authentication.manager.provider" class="Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager">
<argument /> <!-- providers -->
<argument>%security.authentication.manager.erase_credentials%</argument>
<call method="setEventDispatcher">
<argument type="service" id="event_dispatcher" />
</call>
</service>
<service id="security.authentication.manager.guard" class="Symfony\Component\Security\Core\Authentication\GuardAuthenticationManager">
<argument /> <!-- guard authenticators -->
<argument /> <!-- User Checker -->
<argument>%security.authentication.manager.erase_credentials%</argument>
<call method="setEventDispatcher">
<argument type="service" id="event_dispatcher" />
</call>
</service>
<service id="security.authentication.manager" alias="security.authentication.manager.provider"/>
<service id="Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface" alias="security.authentication.manager" />
<service id="security.authentication.trust_resolver" class="Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver" />

View File

@ -17,6 +17,7 @@ use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSessionDomainC
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterCsrfTokenClearingLogoutHandlerPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterTokenUsageTrackingPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AnonymousFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\CustomAuthenticatorFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginLdapFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\GuardAuthenticationFactory;
@ -63,6 +64,7 @@ class SecurityBundle extends Bundle
$extension->addSecurityListenerFactory(new RemoteUserFactory());
$extension->addSecurityListenerFactory(new GuardAuthenticationFactory());
$extension->addSecurityListenerFactory(new AnonymousFactory());
$extension->addSecurityListenerFactory(new CustomAuthenticatorFactory());
$extension->addUserProviderFactory(new InMemoryFactory());
$extension->addUserProviderFactory(new LdapFactory());