Skip to content

Commit

Permalink
Integrated GuardAuthenticationManager in the SecurityBundle
Browse files Browse the repository at this point in the history
  • Loading branch information
wouterj committed Apr 20, 2020
1 parent a6890db commit 9b7fddd
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 23 deletions.
Expand Up @@ -73,6 +73,7 @@ public function getConfigTreeBuilder()
->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()
Expand Down
@@ -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'];
}
}
@@ -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);
}
Expand Up @@ -21,7 +21,7 @@
*
* @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)
{
Expand All @@ -46,6 +46,17 @@ public function create(ContainerBuilder $container, string $id, array $config, s
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';
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -135,6 +138,8 @@ public function load(array $configs, ContainerBuilder $container)
$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);
Expand Down Expand Up @@ -258,8 +263,13 @@ private function createFirewalls(array $config, ContainerBuilder $container)
$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))
;

Expand Down Expand Up @@ -467,31 +477,27 @@ private function createAuthenticationListeners(ContainerBuilder $container, stri
$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 = $this->getUserProvider($container, $id, $firewall, $key, $defaultProvider, $providerIds, $contextListenerId);

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));
}
$userProvider = $providerIds[$normalizedName];
} elseif ('remember_me' === $key || 'anonymous' === $key) {
// RememberMeFactory will use the firewall secret when created, AnonymousAuthenticationListener does not load users.
$userProvider = null;

if ('remember_me' === $key && $contextListenerId) {
$container->getDefinition($contextListenerId)->addTag('security.remember_me_aware', ['id' => $id, 'provider' => 'none']);
$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);
list($provider, $listenerId, $defaultEntryPoint) = $factory->create($container, $id, $firewall[$key], $userProvider, $defaultEntryPoint);

$listeners[] = new Reference($listenerId);
$authenticationProviders[] = $provider;
$listeners[] = new Reference($listenerId);
$authenticationProviders[] = $provider;
}
$hasListeners = true;
}
}
Expand All @@ -504,6 +510,42 @@ private function createAuthenticationListeners(ContainerBuilder $container, stri
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 = [];
Expand Down
@@ -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>
11 changes: 10 additions & 1 deletion src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml
Expand Up @@ -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" />
Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Bundle/SecurityBundle/SecurityBundle.php
Expand Up @@ -17,6 +17,7 @@
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;
Expand Down Expand Up @@ -63,6 +64,7 @@ public function build(ContainerBuilder $container)
$extension->addSecurityListenerFactory(new RemoteUserFactory());
$extension->addSecurityListenerFactory(new GuardAuthenticationFactory());
$extension->addSecurityListenerFactory(new AnonymousFactory());
$extension->addSecurityListenerFactory(new CustomAuthenticatorFactory());

$extension->addUserProviderFactory(new InMemoryFactory());
$extension->addUserProviderFactory(new LdapFactory());
Expand Down

0 comments on commit 9b7fddd

Please sign in to comment.