[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\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()

View File

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

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;
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.

View File

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

View File

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

View File

@ -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'),
),
),

View File

@ -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" />

View File

@ -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:

View File

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