Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
[Security/DependencyInjection] adds support for merging security conf…
…igurations

The merging is done in three steps:

    1. Normalization:
    =================
    All passed config arrays will be transformed into the same structure
    regardless of what format they come from.

    2. Merging:
    ===========
    This is the step when the actual merging is performed. Starting at the root
    the configs will be passed along the tree until a node has no children, or
    the merging of sub-paths of the current node has been specifically disabled.

       Left-Side       Right-Side      Merge Result
       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       -nothing-       array           Right-Side will be taken.
       scalar          scalar          Right-Side will be taken.
       array           false           Right-Side will be taken if ->canBeUnset()
                                       was called on the array node.
       false           array           Right-Side will be taken.
       array           array           Each value in the array will be passed to
                                       the specific child node, or the prototype
                                       node (whatever is present).

    3. Finalization:
    ================
    The normalized, and merged config will be passed through the config tree to
    perform final validation on the submitted values, and set default values
    where this has been requested.

You can influence this process in various ways, here is a list with some examples.
All of these methods must be called on the node on which they should be applied.

  * isRequired(): Node must be present in at least one config file.
  * requiresAtLeastOneElement(): PrototypeNode must have at least one element.
  * treatNullLike($value): Replaces null with $value during normalization.
  * treatTrueLike($value): Same as above just for true
  * treatFalseLike($value): Same as above just for false
  * defaultValue($value): Sets a default value for this node (only for scalars)
  * addDefaultsIfNotSet(): Whether to add default values of an array which has not
                           been defined in any configuration file.
  * disallowNewKeysInSubsequentConfigs(): All keys for this array must be defined
                                          in one configuration file, subsequent
                                          configurations may only overwrite these.
  * fixXmlConfig($key, $plural = null): Transforms XML config into same structure
                                        as YAML, and PHP configurations.
  * useAttributeAsKey($name): Defines which XML attribute to use as array key.
  * cannotBeOverwritten(): Declares a certain sub-path as non-overwritable. All
                           configuration for this path must be defined in the same
                           configuration file.
  * cannotBeEmpty(): If value is set, it must be non-empty.
  * canBeUnset(): If array values should be unset if false is specified.

Architecture:
=============
The configuration consists basically out of two different sets of classes.

  1. Builder classes: These classes provide the fluent interface and
                      are used to construct the config tree.

  2. Node classes: These classes contain the actual logic for normalization,
                   merging, and finalizing configurations.

After you have added all the metadata to your builders, the call to
->buildTree() will convert this metadata to actual node classes. Most of the
time, you will not have to interact with the config nodes directly, but will
delegate this to the Processor class which will call the respective methods
on the config node classes.
  • Loading branch information
schmittjoh authored and jmikola committed Feb 6, 2011
commit f3f1d94e24bf07cd5bb04de392b2af0c924d85fb
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
<?php

namespace Symfony\Bundle\SecurityBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\Configuration\Builder\TreeBuilder;

/**
* This class contains the configuration information for the following tags:
*
* * security.config
* * security.acl
*
* This information is solely responsible for how the different configuration
* sections are normalized, and merged.
*
* @author Johannes M. Schmitt <[email protected]>
*/
class Configuration
{
public function getAclConfigTree()
{
$tb = new TreeBuilder();

return $tb
->root('security:acl', 'array')
->scalarNode('connection')->end()
->scalarNode('cache')->end()
->end()
->buildTree();
}

public function getFactoryConfigTree()
{
$tb = new TreeBuilder();

return $tb
->root('security:config', 'array')
->fixXmlConfig('factory', 'factories')
->arrayNode('factories')
->prototype('scalar')->end()
->end()
->end()
->buildTree();
}

public function getMainConfigTree(array $factories)
{
$tb = new TreeBuilder();
$rootNode = $tb->root('security:config', 'array');

$rootNode
->scalarNode('access_denied_url')->end()
->scalarNode('session_fixation_strategy')->cannotBeEmpty()->defaultValue('migrate')->end()
;

$this->addEncodersSection($rootNode);
$this->addProvidersSection($rootNode);
$this->addFirewallsSection($rootNode, $factories);
$this->addAccessControlSection($rootNode);
$this->addRoleHierarchySection($rootNode);

return $tb->buildTree();
}

protected function addRoleHierarchySection($rootNode)
{
$rootNode
->fixXmlConfig('role', 'role_hierarchy')
->arrayNode('role_hierarchy')
->containsNameValuePairsWithKeyAttribute('id')
->prototype('array')
->beforeNormalization()->ifString()->then(function($v) { return array('value' => $v); })->end()
->beforeNormalization()
->ifTrue(function($v) { return is_array($v) && isset($v['value']); })
->then(function($v) { return preg_split('/\s*,\s*/', $v['value']); })
->end()
->prototype('scalar')->end()
->end()
->end()
;
}

protected function addAccessControlSection($rootNode)
{
$rootNode
->fixXmlConfig('rule', 'access_control')
->arrayNode('access_control')
->cannotBeOverwritten()
->prototype('array')
->scalarNode('requires_channel')->defaultNull()->end()
->scalarNode('path')->defaultNull()->end()
->scalarNode('host')->defaultNull()->end()
->scalarNode('ip')->defaultNull()->end()
->arrayNode('methods')
->beforeNormalization()->ifString()->then(function($v) { return preg_split('/\s*,\s*/', $v); })->end()
->prototype('scalar')->end()
->end()
->fixXmlConfig('role')
->arrayNode('roles')
->beforeNormalization()->ifString()->then(function($v) { return preg_split('/\s*,\s*/', $v); })->end()
->prototype('scalar')->end()
->end()
->fixXmlConfig('attribute')
->arrayNode('attributes')
->containsNameValuePairsWithKeyAttribute('key')
->prototype('scalar')
->beforeNormalization()
->ifTrue(function($v) { return is_array($v) && isset($v['pattern']); })
->then(function($v) { return $v['pattern']; })
->end()
->end()
->end()
->end()
->end()
;
}

protected function addFirewallsSection($rootNode, array $factories)
{
$firewallNodeBuilder =
$rootNode
->fixXmlConfig('firewall')
->arrayNode('firewalls')
->disallowNewKeysInSubsequentConfigs()
->useAttributeAsKey('name')
->prototype('array')
->scalarNode('pattern')->end()
->booleanNode('security')->defaultTrue()->end()
->scalarNode('request_matcher')->end()
->scalarNode('access_denied_url')->end()
->scalarNode('access_denied_handler')->end()
->scalarNode('entry_point')->end()
->scalarNode('provider')->end()
->booleanNode('stateless')->defaultFalse()->end()
->scalarNode('context')->cannotBeEmpty()->end()
->arrayNode('logout')
->treatTrueLike(array())
->canBeUnset()
->scalarNode('path')->defaultValue('/logout')->end()
->scalarNode('target')->defaultValue('/')->end()
->booleanNode('invalidate_session')->defaultTrue()->end()
->fixXmlConfig('delete_cookie')
->arrayNode('delete_cookies')
->beforeNormalization()
->ifTrue(function($v) { return is_array($v) && is_int(key($v)); })
->then(function($v) { return array_map(function($v) { return array('name' => $v); }, $v); })
->end()
->useAttributeAsKey('name')
->prototype('array')
->scalarNode('path')->defaultNull()->end()
->scalarNode('domain')->defaultNull()->end()
->end()
->end()
->fixXmlConfig('handler')
->arrayNode('handlers')
->prototype('scalar')->end()
->end()
->end()
->booleanNode('anonymous')->end()
->arrayNode('switch_user')
->scalarNode('provider')->end()
->scalarNode('parameter')->defaultValue('_switch_user')->end()
->scalarNode('role')->defaultValue('ROLE_ALLOWED_TO_SWITCH')->end()
->end()
;

foreach ($factories as $factoriesAtPosition) {
foreach ($factoriesAtPosition as $factory) {
$factoryNode =
$firewallNodeBuilder->arrayNode(str_replace('-', '_', $factory->getKey()))
->canBeUnset()
;

$factory->addConfiguration($factoryNode);
}
}
}

protected function addProvidersSection($rootNode)
{
$rootNode
->fixXmlConfig('provider')
->arrayNode('providers')
->disallowNewKeysInSubsequentConfigs()
->requiresAtLeastOneElement()
->useAttributeAsKey('name')
->prototype('array')
->scalarNode('id')->end()
->fixXmlConfig('provider')
->arrayNode('providers')
->prototype('scalar')->end()
->end()
->fixXmlConfig('user')
->arrayNode('users')
->useAttributeAsKey('name')
->prototype('array')
->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()
->arrayNode('entity')
->scalarNode('class')->isRequired()->cannotBeEmpty()->end()
->scalarNode('property')->defaultNull()->end()
->end()
->arrayNode('document')
->scalarNode('class')->isRequired()->cannotBeEmpty()->end()
->scalarNode('property')->defaultNull()->end()
->end()
->end()
->end()
;
}

protected function addEncodersSection($rootNode)
{
$rootNode
->fixXmlConfig('encoder')
->arrayNode('encoders')
->useAttributeAsKey('class')
->prototype('array')
->beforeNormalization()->ifString()->then(function($v) { return array('algorithm' => $v); })->end()
->scalarNode('algorithm')->isRequired()->cannotBeEmpty()->end()
->booleanNode('ignore_case')->end()
->booleanNode('encode_as_base64')->end()
->scalarNode('iterations')->end()
->scalarNode('id')->end()
->end()
->end()
;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;

use Symfony\Component\DependencyInjection\Configuration\Builder\NodeBuilder;

use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
Expand Down Expand Up @@ -38,10 +40,6 @@ abstract class AbstractFactory implements SecurityFactoryInterface

public function create(ContainerBuilder $container, $id, $config, $userProviderId, $defaultEntryPointId)
{
if (!is_array($config)) {
$config = array();
}

// authentication provider
$authProviderId = $this->createAuthProvider($container, $id, $config, $userProviderId);
$container
Expand All @@ -66,6 +64,24 @@ public function create(ContainerBuilder $container, $id, $config, $userProviderI
return array($authProviderId, $listenerId, $entryPointId);
}

public function addConfiguration(NodeBuilder $node)
{
$node
->scalarNode('provider')->end()
->booleanNode('remember_me')->defaultTrue()->end()
->scalarNode('success_handler')->end()
->scalarNode('failure_handler')->end()
;

foreach ($this->options as $name => $default) {
if (is_bool($default)) {
$node->booleanNode($name)->defaultValue($default);
} else {
$node->scalarNode($name)->defaultValue($default);
}
}
}

public final function addOption($name, $default = null)
{
$this->options[$name] = $default;
Expand Down Expand Up @@ -127,18 +143,15 @@ protected function createEntryPoint($container, $id, $config, $defaultEntryPoint
*/
protected function isRememberMeAware($config)
{
return !isset($config['remember_me']) || (Boolean) $config['remember_me'];
return $config['remember_me'];
}

protected function createListener($container, $id, $config, $userProvider)
{
// merge set options with default options
$options = $this->getOptionsFromConfig($config);

$listenerId = $this->getListenerId();
$listener = new DefinitionDecorator($listenerId);
$listener->setArgument(3, $id);
$listener->setArgument(4, $options);
$listener->setArgument(4, array_intersect_key($config, $this->options));

// success handler
if (isset($config['success_handler'])) {
Expand All @@ -155,17 +168,4 @@ protected function createListener($container, $id, $config, $userProvider)

return $listenerId;
}

protected final function getOptionsFromConfig($config)
{
$options = $this->options;

foreach (array_keys($options) as $key) {
if (array_key_exists($key, $config)) {
$options[$key] = $config[$key];
}
}

return $options;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,11 @@ protected function createAuthProvider(ContainerBuilder $container, $id, $config,

protected function createEntryPoint($container, $id, $config, $defaultEntryPoint)
{
// merge set options with default options
$options = $this->getOptionsFromConfig($config);

$entryPointId = 'security.authentication.form_entry_point.'.$id;
$container
->setDefinition($entryPointId, new DefinitionDecorator('security.authentication.form_entry_point'))
->addArgument($options['login_path'])
->addArgument($options['use_forward'])
->addArgument($config['login_path'])
->addArgument($config['use_forward'])
;

return $entryPointId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;

use Symfony\Component\DependencyInjection\Configuration\Builder\NodeBuilder;

use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
Expand Down Expand Up @@ -53,4 +55,11 @@ public function getKey()
{
return 'http-basic';
}

public function addConfiguration(NodeBuilder $builder)
{
$builder
->scalarNode('provider')->end()
;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;

use Symfony\Component\DependencyInjection\Configuration\Builder\NodeBuilder;

use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
Expand Down Expand Up @@ -53,4 +55,11 @@ public function getKey()
{
return 'http-digest';
}

public function addConfiguration(NodeBuilder $builder)
{
$builder
->scalarNode('provider')->end()
;
}
}
Loading