[SecurityBundle] Added a way to extend the providers section of the config

This commit is contained in:
Christophe Coevoet 2011-10-23 14:37:52 +02:00
parent 6aee641a1e
commit 74732dc223
12 changed files with 231 additions and 76 deletions

View File

@ -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()
;
}
}

View File

@ -13,6 +13,7 @@ namespace Symfony\Bundle\DoctrineBundle;
use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Bundle\DoctrineBundle\DependencyInjection\Compiler\RegisterEventListenersAndSubscribersPass; use Symfony\Bundle\DoctrineBundle\DependencyInjection\Compiler\RegisterEventListenersAndSubscribersPass;
use Symfony\Bundle\DoctrineBundle\DependencyInjection\Security\UserProvider\EntityFactory;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\HttpKernel\Bundle\Bundle;
@ -29,6 +30,9 @@ class DoctrineBundle extends Bundle
parent::build($container); parent::build($container);
$container->addCompilerPass(new RegisterEventListenersAndSubscribersPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION); $container->addCompilerPass(new RegisterEventListenersAndSubscribersPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION);
if ($container->hasExtension('security')) {
$container->getExtension('security')->addUserProviderFactory(new EntityFactory());
}
} }
public function boot() public function boot()

View File

@ -31,15 +31,18 @@ use Symfony\Component\Config\Definition\ConfigurationInterface;
class MainConfiguration implements ConfigurationInterface class MainConfiguration implements ConfigurationInterface
{ {
private $factories; private $factories;
private $userProviderFactories;
/** /**
* Constructor. * Constructor.
* *
* @param array $factories * @param array $factories
* @param array $userProviderFactories
*/ */
public function __construct(array $factories) public function __construct(array $factories, array $userProviderFactories)
{ {
$this->factories = $factories; $this->factories = $factories;
$this->userProviderFactories = $userProviderFactories;
} }
/** /**
@ -287,7 +290,7 @@ class MainConfiguration implements ConfigurationInterface
private function addProvidersSection(ArrayNodeDefinition $rootNode) private function addProvidersSection(ArrayNodeDefinition $rootNode)
{ {
$rootNode $providerNodeBuilder = $rootNode
->fixXmlConfig('provider') ->fixXmlConfig('provider')
->children() ->children()
->arrayNode('providers') ->arrayNode('providers')
@ -296,44 +299,48 @@ class MainConfiguration implements ConfigurationInterface
->requiresAtLeastOneElement() ->requiresAtLeastOneElement()
->useAttributeAsKey('name') ->useAttributeAsKey('name')
->prototype('array') ->prototype('array')
->children() ;
->scalarNode('id')->end()
->arrayNode('entity') /** @var $providerNodeBuilder \Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition */
->children() $providerNodeBuilder
->scalarNode('class')->isRequired()->cannotBeEmpty()->end() ->children()
->scalarNode('property')->defaultNull()->end() ->scalarNode('id')->end()
->end() ->end()
->end() ->fixXmlConfig('provider')
->end() ->children()
->fixXmlConfig('provider') ->arrayNode('providers')
->children() ->beforeNormalization()
->arrayNode('providers') ->ifString()
->beforeNormalization() ->then(function($v) { return preg_split('/\s*,\s*/', $v); })
->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()
->end() ->end()
->prototype('scalar')->end()
->end() ->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) private function addEncodersSection(ArrayNodeDefinition $rootNode)

View File

@ -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()
;
}
}

View File

@ -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);
}

View File

@ -11,6 +11,7 @@
namespace Symfony\Bundle\SecurityBundle\DependencyInjection; namespace Symfony\Bundle\SecurityBundle\DependencyInjection;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface;
use Symfony\Component\DependencyInjection\DefinitionDecorator; use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\HttpKernel\DependencyInjection\Extension;
@ -36,6 +37,7 @@ class SecurityExtension extends Extension
private $contextListeners = array(); private $contextListeners = array();
private $listenerPositions = array('pre_auth', 'form', 'http', 'remember_me'); private $listenerPositions = array('pre_auth', 'form', 'http', 'remember_me');
private $factories; private $factories;
private $userProviderFactories = array();
public function load(array $configs, ContainerBuilder $container) public function load(array $configs, ContainerBuilder $container)
{ {
@ -49,7 +51,7 @@ class SecurityExtension extends Extension
$factories = $this->createListenerFactories($container, $config); $factories = $this->createListenerFactories($container, $config);
// normalize and merge the actual configuration // normalize and merge the actual configuration
$mainConfig = new MainConfiguration($factories); $mainConfig = new MainConfiguration($factories, $this->userProviderFactories);
$config = $this->processConfiguration($mainConfig, $configs); $config = $this->processConfiguration($mainConfig, $configs);
// load services // load services
@ -452,6 +454,16 @@ class SecurityExtension extends Extension
{ {
$name = $this->getUserProviderId(strtolower($name)); $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 // Existing DAO service provider
if (isset($provider['id'])) { if (isset($provider['id'])) {
$container->setAlias($name, new Alias($provider['id'], false)); $container->setAlias($name, new Alias($provider['id'], false));
@ -474,30 +486,6 @@ class SecurityExtension extends Extension
return $name; 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; return $name;
} }
@ -600,6 +588,10 @@ class SecurityExtension extends Extension
return $this->factories = $factories; return $this->factories = $factories;
} }
public function addUserProviderFactory(UserProviderFactoryInterface $factory)
{
$this->userProviderFactories[] = $factory;
}
/** /**
* Returns the base path for the XSD files. * Returns the base path for the XSD files.

View File

@ -14,6 +14,7 @@ namespace Symfony\Bundle\SecurityBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\InMemoryFactory;
/** /**
* Bundle. * Bundle.
@ -26,6 +27,7 @@ class SecurityBundle extends Bundle
{ {
parent::build($container); parent::build($container);
$container->getExtension('security')->addUserProviderFactory(new InMemoryFactory());
$container->addCompilerPass(new AddSecurityVotersPass()); $container->addCompilerPass(new AddSecurityVotersPass());
} }
} }

View File

@ -43,7 +43,7 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
)); ));
$processor = new Processor(); $processor = new Processor();
$configuration = new MainConfiguration(array()); $configuration = new MainConfiguration(array(), array());
$config = $processor->processConfiguration($configuration, array($config)); $config = $processor->processConfiguration($configuration, array($config));
$this->assertFalse(array_key_exists('factory', $config), 'The factory key is silently removed without an exception'); $this->assertFalse(array_key_exists('factory', $config), 'The factory key is silently removed without an exception');

View File

@ -33,14 +33,11 @@ $container->loadFromExtension('security', array(
'bar' => array('password' => '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33', 'roles' => array('ROLE_USER', 'ROLE_ADMIN')), 'bar' => array('password' => '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33', 'roles' => array('ROLE_USER', 'ROLE_ADMIN')),
), ),
), ),
'doctrine' => array(
'entity' => array('class' => 'SecurityBundle:User', 'property' => 'username')
),
'service' => array( 'service' => array(
'id' => 'user.manager', 'id' => 'user.manager',
), ),
'chain' => array( 'chain' => array(
'providers' => array('service', 'doctrine', 'basic'), 'providers' => array('service', 'basic'),
), ),
), ),

View File

@ -29,13 +29,9 @@
<user name="bar" password="0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33" roles="ROLE_USER, ROLE_ADMIN" /> <user name="bar" password="0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33" roles="ROLE_USER, ROLE_ADMIN" />
</provider> </provider>
<provider name="doctrine">
<entity class="SecurityBundle:User" property="username" />
</provider>
<provider name="service" id="user.manager" /> <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" /> <firewall name="simple" pattern="/login" security="false" />

View File

@ -22,12 +22,10 @@ security:
users: users:
foo: { password: 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33, roles: ROLE_SUPER_ADMIN } foo: { password: 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33, roles: ROLE_SUPER_ADMIN }
bar: { password: 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33, roles: [ROLE_USER, ROLE_ADMIN] } bar: { password: 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33, roles: [ROLE_USER, ROLE_ADMIN] }
doctrine:
entity: { class: SecurityBundle:User, property: username }
service: service:
id: user.manager id: user.manager
chain: chain:
providers: [service, doctrine, basic] providers: [service, basic]
firewalls: firewalls:

View File

@ -16,6 +16,7 @@ use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Parameter; use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension; use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\InMemoryFactory;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
abstract class SecurityExtensionTest extends \PHPUnit_Framework_TestCase 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',
'security.user.provider.concrete.basic_foo', 'security.user.provider.concrete.basic_foo',
'security.user.provider.concrete.basic_bar', 'security.user.provider.concrete.basic_bar',
'security.user.provider.concrete.doctrine',
'security.user.provider.concrete.service', 'security.user.provider.concrete.service',
'security.user.provider.concrete.chain', 'security.user.provider.concrete.chain',
); );
@ -57,7 +57,6 @@ abstract class SecurityExtensionTest extends \PHPUnit_Framework_TestCase
// chain provider // chain provider
$this->assertEquals(array(array( $this->assertEquals(array(array(
new Reference('security.user.provider.concrete.service'), new Reference('security.user.provider.concrete.service'),
new Reference('security.user.provider.concrete.doctrine'),
new Reference('security.user.provider.concrete.basic'), new Reference('security.user.provider.concrete.basic'),
)), $container->getDefinition('security.user.provider.concrete.chain')->getArguments()); )), $container->getDefinition('security.user.provider.concrete.chain')->getArguments());
} }
@ -170,6 +169,7 @@ abstract class SecurityExtensionTest extends \PHPUnit_Framework_TestCase
{ {
$container = new ContainerBuilder(); $container = new ContainerBuilder();
$security = new SecurityExtension(); $security = new SecurityExtension();
$security->addUserProviderFactory(new InMemoryFactory());
$container->registerExtension($security); $container->registerExtension($security);
$this->loadFromFile($container, $file); $this->loadFromFile($container, $file);