Skip to content

Commit

Permalink
Integrated GuardAuthenticationManager in the FrameworkBundle
Browse files Browse the repository at this point in the history
  • Loading branch information
wouterj committed Sep 15, 2019
1 parent 8485aa8 commit 93ce631
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 21 deletions.
Expand Up @@ -74,6 +74,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,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 for the provided configuration.
*
* @return string The Guard service ID to be used by the firewall
*/
public function createGuard(ContainerBuilder $container, string $id, array $config, string $userProviderId): string;
}
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, $id, $config, $userProvider, $defaultEntryPoint)
{
Expand All @@ -46,6 +46,17 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider,
return [$provider, $listenerId, $entryPointId];
}

public function createGuard(ContainerBuilder $container, string $id, array $config, string $userProviderId): string
{
$authenticatorId = 'security.authenticator.basic.'.$id;
$container
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.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 @@ -53,6 +54,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 @@ -140,6 +143,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 @@ -262,8 +267,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 @@ -462,27 +472,20 @@ 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);

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) {
// RememberMeFactory will use the firewall secret when created
$userProvider = null;
} 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);
$authenticationProviders[$id.'_'.$key] = $factory->createGuard($container, $id, $firewall[$key], $userProvider);
} else {
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 Down Expand Up @@ -519,6 +522,40 @@ 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
{
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) {
// RememberMeFactory will use the firewall secret when created
return null;
}

if ($defaultProvider) {
return $defaultProvider;
}

if (empty($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.basic"
class="Symfony\Component\Security\Core\Authentication\Authenticator\HttpBasicAuthenticator"
abstract="true">
<argument/> <!-- realm name -->
<argument/> <!-- user provider -->
<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 @@ -40,13 +40,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

0 comments on commit 93ce631

Please sign in to comment.