[SecurityBundle] Added a way to extend the providers section of the config
This commit is contained in:
parent
6aee641a1e
commit
74732dc223
@ -0,0 +1,57 @@
|
||||
<?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\DoctrineBundle\DependencyInjection\Security\UserProvider;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface;
|
||||
use Symfony\Component\DependencyInjection\DefinitionDecorator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* HttpBasicFactory creates services for HTTP basic authentication.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Christophe Coevoet <stof@notk.org>
|
||||
*/
|
||||
class EntityFactory implements UserProviderFactoryInterface
|
||||
{
|
||||
public function create(ContainerBuilder $container, $id, $config)
|
||||
{
|
||||
$container
|
||||
->setDefinition($id, new DefinitionDecorator('security.user.provider.entity'))
|
||||
->addArgument($config['class'])
|
||||
->addArgument($config['property'])
|
||||
;
|
||||
}
|
||||
|
||||
public function getKey()
|
||||
{
|
||||
return 'entity';
|
||||
}
|
||||
|
||||
public function getFixableKey()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeDefinition $node)
|
||||
{
|
||||
$node
|
||||
->children()
|
||||
->scalarNode('class')->isRequired()->cannotBeEmpty()->end()
|
||||
->scalarNode('property')->defaultNull()->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ namespace Symfony\Bundle\DoctrineBundle;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
|
||||
use Symfony\Bundle\DoctrineBundle\DependencyInjection\Compiler\RegisterEventListenersAndSubscribersPass;
|
||||
use Symfony\Bundle\DoctrineBundle\DependencyInjection\Security\UserProvider\EntityFactory;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
@ -29,6 +30,9 @@ class DoctrineBundle extends Bundle
|
||||
parent::build($container);
|
||||
|
||||
$container->addCompilerPass(new RegisterEventListenersAndSubscribersPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION);
|
||||
if ($container->hasExtension('security')) {
|
||||
$container->getExtension('security')->addUserProviderFactory(new EntityFactory());
|
||||
}
|
||||
}
|
||||
|
||||
public function boot()
|
||||
|
@ -31,15 +31,18 @@ use Symfony\Component\Config\Definition\ConfigurationInterface;
|
||||
class MainConfiguration implements ConfigurationInterface
|
||||
{
|
||||
private $factories;
|
||||
private $userProviderFactories;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $factories
|
||||
* @param array $userProviderFactories
|
||||
*/
|
||||
public function __construct(array $factories)
|
||||
public function __construct(array $factories, array $userProviderFactories)
|
||||
{
|
||||
$this->factories = $factories;
|
||||
$this->userProviderFactories = $userProviderFactories;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -287,7 +290,7 @@ class MainConfiguration implements ConfigurationInterface
|
||||
|
||||
private function addProvidersSection(ArrayNodeDefinition $rootNode)
|
||||
{
|
||||
$rootNode
|
||||
$providerNodeBuilder = $rootNode
|
||||
->fixXmlConfig('provider')
|
||||
->children()
|
||||
->arrayNode('providers')
|
||||
@ -296,44 +299,48 @@ class MainConfiguration implements ConfigurationInterface
|
||||
->requiresAtLeastOneElement()
|
||||
->useAttributeAsKey('name')
|
||||
->prototype('array')
|
||||
->children()
|
||||
->scalarNode('id')->end()
|
||||
->arrayNode('entity')
|
||||
->children()
|
||||
->scalarNode('class')->isRequired()->cannotBeEmpty()->end()
|
||||
->scalarNode('property')->defaultNull()->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->fixXmlConfig('provider')
|
||||
->children()
|
||||
->arrayNode('providers')
|
||||
->beforeNormalization()
|
||||
->ifString()
|
||||
->then(function($v) { return preg_split('/\s*,\s*/', $v); })
|
||||
->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
->fixXmlConfig('user')
|
||||
->children()
|
||||
->arrayNode('users')
|
||||
->useAttributeAsKey('name')
|
||||
->prototype('array')
|
||||
->children()
|
||||
->scalarNode('password')->defaultValue(uniqid())->end()
|
||||
->arrayNode('roles')
|
||||
->beforeNormalization()->ifString()->then(function($v) { return preg_split('/\s*,\s*/', $v); })->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
|
||||
/** @var $providerNodeBuilder \Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition */
|
||||
$providerNodeBuilder
|
||||
->children()
|
||||
->scalarNode('id')->end()
|
||||
->end()
|
||||
->fixXmlConfig('provider')
|
||||
->children()
|
||||
->arrayNode('providers')
|
||||
->beforeNormalization()
|
||||
->ifString()
|
||||
->then(function($v) { return preg_split('/\s*,\s*/', $v); })
|
||||
->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
|
||||
/** @var $factory \Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface */
|
||||
foreach ($this->userProviderFactories as $factory) {
|
||||
$name = str_replace('-', '_', $factory->getKey());
|
||||
if (null !== $factory->getFixableKey()) {
|
||||
$providerNodeBuilder->fixXmlConfig($factory->getFixableKey(), $name);
|
||||
}
|
||||
$factoryNode = $providerNodeBuilder->children()->arrayNode($name)->canBeUnset();
|
||||
|
||||
$factory->addConfiguration($factoryNode);
|
||||
}
|
||||
/*
|
||||
* TODO find a way to do the validation. Issue appears because of prototyped nodes
|
||||
$providerNodeBuilder
|
||||
->validate()
|
||||
->ifTrue(function($v){return count($v) > 1;})
|
||||
->thenInvalid('You cannot set multiple provider types for the same provider')
|
||||
->end()
|
||||
->validate()
|
||||
->ifTrue(function($v){return count($v) === 0;})
|
||||
->thenInvalid('You must set a provider definition for the provider.')
|
||||
->end()
|
||||
;
|
||||
*/
|
||||
}
|
||||
|
||||
private function addEncodersSection(ArrayNodeDefinition $rootNode)
|
||||
|
@ -0,0 +1,70 @@
|
||||
<?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\UserProvider;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
|
||||
use Symfony\Component\DependencyInjection\DefinitionDecorator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* HttpBasicFactory creates services for HTTP basic authentication.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Christophe Coevoet <stof@notk.org>
|
||||
*/
|
||||
class InMemoryFactory implements UserProviderFactoryInterface
|
||||
{
|
||||
public function create(ContainerBuilder $container, $id, $config)
|
||||
{
|
||||
$definition = $container->setDefinition($id, new DefinitionDecorator('security.user.provider.in_memory'));
|
||||
|
||||
|
||||
foreach ($config as $username => $user) {
|
||||
$userId = $id.'_'.$username;
|
||||
|
||||
$container
|
||||
->setDefinition($userId, new DefinitionDecorator('security.user.provider.in_memory.user'))
|
||||
->setArguments(array($username, (string) $user['password'], $user['roles']))
|
||||
;
|
||||
|
||||
$definition->addMethodCall('createUser', array(new Reference($userId)));
|
||||
}
|
||||
}
|
||||
|
||||
public function getKey()
|
||||
{
|
||||
return 'users';
|
||||
}
|
||||
|
||||
public function getFixableKey()
|
||||
{
|
||||
return 'user';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeDefinition $node)
|
||||
{
|
||||
$node
|
||||
->useAttributeAsKey('name')
|
||||
->prototype('array')
|
||||
->children()
|
||||
->scalarNode('password')->defaultValue(uniqid())->end()
|
||||
->arrayNode('roles')
|
||||
->beforeNormalization()->ifString()->then(function($v) { return preg_split('/\s*,\s*/', $v); })->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
<?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\UserProvider;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* SecurityFactoryInterface is the interface for all security authentication listener.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Christophe Coevoet <stof@notk.org>
|
||||
*/
|
||||
interface UserProviderFactoryInterface
|
||||
{
|
||||
function create(ContainerBuilder $container, $id, $config);
|
||||
|
||||
function getKey();
|
||||
|
||||
function getFixableKey();
|
||||
|
||||
function addConfiguration(NodeDefinition $builder);
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface;
|
||||
use Symfony\Component\DependencyInjection\DefinitionDecorator;
|
||||
use Symfony\Component\DependencyInjection\Alias;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||
@ -36,6 +37,7 @@ class SecurityExtension extends Extension
|
||||
private $contextListeners = array();
|
||||
private $listenerPositions = array('pre_auth', 'form', 'http', 'remember_me');
|
||||
private $factories;
|
||||
private $userProviderFactories = array();
|
||||
|
||||
public function load(array $configs, ContainerBuilder $container)
|
||||
{
|
||||
@ -49,7 +51,7 @@ class SecurityExtension extends Extension
|
||||
$factories = $this->createListenerFactories($container, $config);
|
||||
|
||||
// normalize and merge the actual configuration
|
||||
$mainConfig = new MainConfiguration($factories);
|
||||
$mainConfig = new MainConfiguration($factories, $this->userProviderFactories);
|
||||
$config = $this->processConfiguration($mainConfig, $configs);
|
||||
|
||||
// load services
|
||||
@ -452,6 +454,16 @@ class SecurityExtension extends Extension
|
||||
{
|
||||
$name = $this->getUserProviderId(strtolower($name));
|
||||
|
||||
foreach ($this->userProviderFactories as $factory) {
|
||||
$key = str_replace('-', '_', $factory->getKey());
|
||||
|
||||
if (!empty($provider[$key])) {
|
||||
$factory->create($container, $name, $provider[$key]);
|
||||
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
|
||||
// Existing DAO service provider
|
||||
if (isset($provider['id'])) {
|
||||
$container->setAlias($name, new Alias($provider['id'], false));
|
||||
@ -474,30 +486,6 @@ class SecurityExtension extends Extension
|
||||
return $name;
|
||||
}
|
||||
|
||||
// Doctrine Entity DAO provider
|
||||
if (isset($provider['entity'])) {
|
||||
$container
|
||||
->setDefinition($name, new DefinitionDecorator('security.user.provider.entity'))
|
||||
->addArgument($provider['entity']['class'])
|
||||
->addArgument($provider['entity']['property'])
|
||||
;
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
// In-memory DAO provider
|
||||
$definition = $container->setDefinition($name, new DefinitionDecorator('security.user.provider.in_memory'));
|
||||
foreach ($provider['users'] as $username => $user) {
|
||||
$userId = $name.'_'.$username;
|
||||
|
||||
$container
|
||||
->setDefinition($userId, new DefinitionDecorator('security.user.provider.in_memory.user'))
|
||||
->setArguments(array($username, (string)$user['password'], $user['roles']))
|
||||
;
|
||||
|
||||
$definition->addMethodCall('createUser', array(new Reference($userId)));
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
@ -600,6 +588,10 @@ class SecurityExtension extends Extension
|
||||
return $this->factories = $factories;
|
||||
}
|
||||
|
||||
public function addUserProviderFactory(UserProviderFactoryInterface $factory)
|
||||
{
|
||||
$this->userProviderFactories[] = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base path for the XSD files.
|
||||
|
@ -14,6 +14,7 @@ namespace Symfony\Bundle\SecurityBundle;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\InMemoryFactory;
|
||||
|
||||
/**
|
||||
* Bundle.
|
||||
@ -26,6 +27,7 @@ class SecurityBundle extends Bundle
|
||||
{
|
||||
parent::build($container);
|
||||
|
||||
$container->getExtension('security')->addUserProviderFactory(new InMemoryFactory());
|
||||
$container->addCompilerPass(new AddSecurityVotersPass());
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
|
||||
));
|
||||
|
||||
$processor = new Processor();
|
||||
$configuration = new MainConfiguration(array());
|
||||
$configuration = new MainConfiguration(array(), array());
|
||||
$config = $processor->processConfiguration($configuration, array($config));
|
||||
|
||||
$this->assertFalse(array_key_exists('factory', $config), 'The factory key is silently removed without an exception');
|
||||
|
@ -33,14 +33,11 @@ $container->loadFromExtension('security', array(
|
||||
'bar' => array('password' => '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33', 'roles' => array('ROLE_USER', 'ROLE_ADMIN')),
|
||||
),
|
||||
),
|
||||
'doctrine' => array(
|
||||
'entity' => array('class' => 'SecurityBundle:User', 'property' => 'username')
|
||||
),
|
||||
'service' => array(
|
||||
'id' => 'user.manager',
|
||||
),
|
||||
'chain' => array(
|
||||
'providers' => array('service', 'doctrine', 'basic'),
|
||||
'providers' => array('service', 'basic'),
|
||||
),
|
||||
),
|
||||
|
||||
|
@ -29,13 +29,9 @@
|
||||
<user name="bar" password="0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33" roles="ROLE_USER, ROLE_ADMIN" />
|
||||
</provider>
|
||||
|
||||
<provider name="doctrine">
|
||||
<entity class="SecurityBundle:User" property="username" />
|
||||
</provider>
|
||||
|
||||
<provider name="service" id="user.manager" />
|
||||
|
||||
<provider name="chain" providers="service, doctrine, basic" />
|
||||
<provider name="chain" providers="service, basic" />
|
||||
|
||||
<firewall name="simple" pattern="/login" security="false" />
|
||||
|
||||
|
@ -22,12 +22,10 @@ security:
|
||||
users:
|
||||
foo: { password: 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33, roles: ROLE_SUPER_ADMIN }
|
||||
bar: { password: 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33, roles: [ROLE_USER, ROLE_ADMIN] }
|
||||
doctrine:
|
||||
entity: { class: SecurityBundle:User, property: username }
|
||||
service:
|
||||
id: user.manager
|
||||
chain:
|
||||
providers: [service, doctrine, basic]
|
||||
providers: [service, basic]
|
||||
|
||||
|
||||
firewalls:
|
||||
|
@ -16,6 +16,7 @@ use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\Parameter;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\InMemoryFactory;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
abstract class SecurityExtensionTest extends \PHPUnit_Framework_TestCase
|
||||
@ -46,7 +47,6 @@ abstract class SecurityExtensionTest extends \PHPUnit_Framework_TestCase
|
||||
'security.user.provider.concrete.basic',
|
||||
'security.user.provider.concrete.basic_foo',
|
||||
'security.user.provider.concrete.basic_bar',
|
||||
'security.user.provider.concrete.doctrine',
|
||||
'security.user.provider.concrete.service',
|
||||
'security.user.provider.concrete.chain',
|
||||
);
|
||||
@ -57,7 +57,6 @@ abstract class SecurityExtensionTest extends \PHPUnit_Framework_TestCase
|
||||
// chain provider
|
||||
$this->assertEquals(array(array(
|
||||
new Reference('security.user.provider.concrete.service'),
|
||||
new Reference('security.user.provider.concrete.doctrine'),
|
||||
new Reference('security.user.provider.concrete.basic'),
|
||||
)), $container->getDefinition('security.user.provider.concrete.chain')->getArguments());
|
||||
}
|
||||
@ -170,6 +169,7 @@ abstract class SecurityExtensionTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$security = new SecurityExtension();
|
||||
$security->addUserProviderFactory(new InMemoryFactory());
|
||||
$container->registerExtension($security);
|
||||
$this->loadFromFile($container, $file);
|
||||
|
||||
|
Reference in New Issue
Block a user