From 74732dc2237818645bdebd11786f78fbc6bb1f45 Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Sun, 23 Oct 2011 14:37:52 +0200 Subject: [PATCH] [SecurityBundle] Added a way to extend the providers section of the config --- .../Security/UserProvider/EntityFactory.php | 57 +++++++++++++ .../Bundle/DoctrineBundle/DoctrineBundle.php | 4 + .../DependencyInjection/MainConfiguration.php | 79 ++++++++++--------- .../Security/UserProvider/InMemoryFactory.php | 70 ++++++++++++++++ .../UserProviderFactoryInterface.php | 32 ++++++++ .../DependencyInjection/SecurityExtension.php | 42 ++++------ .../Bundle/SecurityBundle/SecurityBundle.php | 2 + .../DependencyInjection/ConfigurationTest.php | 2 +- .../Fixtures/php/container1.php | 5 +- .../Fixtures/xml/container1.xml | 6 +- .../Fixtures/yml/container1.yml | 4 +- .../SecurityExtensionTest.php | 4 +- 12 files changed, 231 insertions(+), 76 deletions(-) create mode 100644 src/Symfony/Bundle/DoctrineBundle/DependencyInjection/Security/UserProvider/EntityFactory.php create mode 100644 src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php create mode 100644 src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/UserProviderFactoryInterface.php diff --git a/src/Symfony/Bundle/DoctrineBundle/DependencyInjection/Security/UserProvider/EntityFactory.php b/src/Symfony/Bundle/DoctrineBundle/DependencyInjection/Security/UserProvider/EntityFactory.php new file mode 100644 index 0000000000..5aa9386e01 --- /dev/null +++ b/src/Symfony/Bundle/DoctrineBundle/DependencyInjection/Security/UserProvider/EntityFactory.php @@ -0,0 +1,57 @@ + + * + * 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 + * @author Christophe Coevoet + */ +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() + ; + } +} diff --git a/src/Symfony/Bundle/DoctrineBundle/DoctrineBundle.php b/src/Symfony/Bundle/DoctrineBundle/DoctrineBundle.php index 68d94f5df4..d2602c7962 100644 --- a/src/Symfony/Bundle/DoctrineBundle/DoctrineBundle.php +++ b/src/Symfony/Bundle/DoctrineBundle/DoctrineBundle.php @@ -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() diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php index f5cdcf42ae..a5f1dd9902 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php @@ -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) diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php new file mode 100644 index 0000000000..fbcd3a9c2e --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php @@ -0,0 +1,70 @@ + + * + * 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 + * @author Christophe Coevoet + */ +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() + ; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/UserProviderFactoryInterface.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/UserProviderFactoryInterface.php new file mode 100644 index 0000000000..947b2c6100 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/UserProviderFactoryInterface.php @@ -0,0 +1,32 @@ + + * + * 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 + * @author Christophe Coevoet + */ +interface UserProviderFactoryInterface +{ + function create(ContainerBuilder $container, $id, $config); + + function getKey(); + + function getFixableKey(); + + function addConfiguration(NodeDefinition $builder); +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 95786d80b5..1c69b2b00f 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -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. diff --git a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php index 0e3ae9dbeb..38c38760d5 100644 --- a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php @@ -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()); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/ConfigurationTest.php index c0e6070d8a..5dfcf7655a 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -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'); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php index 20792c7754..078db51133 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php @@ -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'), ), ), diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml index c9fb6618a1..9a5b3a574f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml @@ -29,13 +29,9 @@ - - - - - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml index 8cb2e8c158..cc67e43187 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml @@ -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: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php index afb7d77a94..b3c2a2a4db 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php @@ -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);