Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 0b8fef2

Browse filesBrowse files
schmittjohfabpot
authored andcommitted
[Security/DependencyInjection] adds support for merging security configurations
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.
1 parent c5fb96b commit 0b8fef2
Copy full SHA for 0b8fef2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Dismiss banner
Expand file treeCollapse file tree

41 files changed

+1713
-429
lines changed
+234Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\SecurityBundle\DependencyInjection;
4+
5+
use Symfony\Component\DependencyInjection\Configuration\Builder\TreeBuilder;
6+
7+
/**
8+
* This class contains the configuration information for the following tags:
9+
*
10+
* * security.config
11+
* * security.acl
12+
*
13+
* This information is solely responsible for how the different configuration
14+
* sections are normalized, and merged.
15+
*
16+
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
17+
*/
18+
class Configuration
19+
{
20+
public function getAclConfigTree()
21+
{
22+
$tb = new TreeBuilder();
23+
24+
return $tb
25+
->root('security:acl', 'array')
26+
->scalarNode('connection')->end()
27+
->scalarNode('cache')->end()
28+
->end()
29+
->buildTree();
30+
}
31+
32+
public function getFactoryConfigTree()
33+
{
34+
$tb = new TreeBuilder();
35+
36+
return $tb
37+
->root('security:config', 'array')
38+
->fixXmlConfig('factory', 'factories')
39+
->arrayNode('factories')
40+
->prototype('scalar')->end()
41+
->end()
42+
->end()
43+
->buildTree();
44+
}
45+
46+
public function getMainConfigTree(array $factories)
47+
{
48+
$tb = new TreeBuilder();
49+
$rootNode = $tb->root('security:config', 'array');
50+
51+
$rootNode
52+
->scalarNode('access_denied_url')->end()
53+
->scalarNode('session_fixation_strategy')->cannotBeEmpty()->defaultValue('migrate')->end()
54+
;
55+
56+
$this->addEncodersSection($rootNode);
57+
$this->addProvidersSection($rootNode);
58+
$this->addFirewallsSection($rootNode, $factories);
59+
$this->addAccessControlSection($rootNode);
60+
$this->addRoleHierarchySection($rootNode);
61+
62+
return $tb->buildTree();
63+
}
64+
65+
protected function addRoleHierarchySection($rootNode)
66+
{
67+
$rootNode
68+
->fixXmlConfig('role', 'role_hierarchy')
69+
->arrayNode('role_hierarchy')
70+
->containsNameValuePairsWithKeyAttribute('id')
71+
->prototype('array')
72+
->beforeNormalization()->ifString()->then(function($v) { return array('value' => $v); })->end()
73+
->beforeNormalization()
74+
->ifTrue(function($v) { return is_array($v) && isset($v['value']); })
75+
->then(function($v) { return preg_split('/\s*,\s*/', $v['value']); })
76+
->end()
77+
->prototype('scalar')->end()
78+
->end()
79+
->end()
80+
;
81+
}
82+
83+
protected function addAccessControlSection($rootNode)
84+
{
85+
$rootNode
86+
->fixXmlConfig('rule', 'access_control')
87+
->arrayNode('access_control')
88+
->cannotBeOverwritten()
89+
->prototype('array')
90+
->scalarNode('requires_channel')->defaultNull()->end()
91+
->scalarNode('path')->defaultNull()->end()
92+
->scalarNode('host')->defaultNull()->end()
93+
->scalarNode('ip')->defaultNull()->end()
94+
->arrayNode('methods')
95+
->beforeNormalization()->ifString()->then(function($v) { return preg_split('/\s*,\s*/', $v); })->end()
96+
->prototype('scalar')->end()
97+
->end()
98+
->fixXmlConfig('role')
99+
->arrayNode('roles')
100+
->beforeNormalization()->ifString()->then(function($v) { return preg_split('/\s*,\s*/', $v); })->end()
101+
->prototype('scalar')->end()
102+
->end()
103+
->fixXmlConfig('attribute')
104+
->arrayNode('attributes')
105+
->containsNameValuePairsWithKeyAttribute('key')
106+
->prototype('scalar')
107+
->beforeNormalization()
108+
->ifTrue(function($v) { return is_array($v) && isset($v['pattern']); })
109+
->then(function($v) { return $v['pattern']; })
110+
->end()
111+
->end()
112+
->end()
113+
->end()
114+
->end()
115+
;
116+
}
117+
118+
protected function addFirewallsSection($rootNode, array $factories)
119+
{
120+
$firewallNodeBuilder =
121+
$rootNode
122+
->fixXmlConfig('firewall')
123+
->arrayNode('firewalls')
124+
->disallowNewKeysInSubsequentConfigs()
125+
->useAttributeAsKey('name')
126+
->prototype('array')
127+
->scalarNode('pattern')->end()
128+
->booleanNode('security')->defaultTrue()->end()
129+
->scalarNode('request_matcher')->end()
130+
->scalarNode('access_denied_url')->end()
131+
->scalarNode('access_denied_handler')->end()
132+
->scalarNode('entry_point')->end()
133+
->scalarNode('provider')->end()
134+
->booleanNode('stateless')->defaultFalse()->end()
135+
->scalarNode('context')->cannotBeEmpty()->end()
136+
->arrayNode('logout')
137+
->treatTrueLike(array())
138+
->canBeUnset()
139+
->scalarNode('path')->defaultValue('/logout')->end()
140+
->scalarNode('target')->defaultValue('/')->end()
141+
->booleanNode('invalidate_session')->defaultTrue()->end()
142+
->fixXmlConfig('delete_cookie')
143+
->arrayNode('delete_cookies')
144+
->beforeNormalization()
145+
->ifTrue(function($v) { return is_array($v) && is_int(key($v)); })
146+
->then(function($v) { return array_map(function($v) { return array('name' => $v); }, $v); })
147+
->end()
148+
->useAttributeAsKey('name')
149+
->prototype('array')
150+
->scalarNode('path')->defaultNull()->end()
151+
->scalarNode('domain')->defaultNull()->end()
152+
->end()
153+
->end()
154+
->fixXmlConfig('handler')
155+
->arrayNode('handlers')
156+
->prototype('scalar')->end()
157+
->end()
158+
->end()
159+
->booleanNode('anonymous')->end()
160+
->arrayNode('switch_user')
161+
->scalarNode('provider')->end()
162+
->scalarNode('parameter')->defaultValue('_switch_user')->end()
163+
->scalarNode('role')->defaultValue('ROLE_ALLOWED_TO_SWITCH')->end()
164+
->end()
165+
;
166+
167+
foreach ($factories as $factoriesAtPosition) {
168+
foreach ($factoriesAtPosition as $factory) {
169+
$factoryNode =
170+
$firewallNodeBuilder->arrayNode(str_replace('-', '_', $factory->getKey()))
171+
->canBeUnset()
172+
;
173+
174+
$factory->addConfiguration($factoryNode);
175+
}
176+
}
177+
}
178+
179+
protected function addProvidersSection($rootNode)
180+
{
181+
$rootNode
182+
->fixXmlConfig('provider')
183+
->arrayNode('providers')
184+
->disallowNewKeysInSubsequentConfigs()
185+
->requiresAtLeastOneElement()
186+
->useAttributeAsKey('name')
187+
->prototype('array')
188+
->scalarNode('id')->end()
189+
->fixXmlConfig('provider')
190+
->arrayNode('providers')
191+
->prototype('scalar')->end()
192+
->end()
193+
->fixXmlConfig('user')
194+
->arrayNode('users')
195+
->useAttributeAsKey('name')
196+
->prototype('array')
197+
->scalarNode('password')->defaultValue(uniqid())->end()
198+
->arrayNode('roles')
199+
->beforeNormalization()->ifString()->then(function($v) { return preg_split('/\s*,\s*/', $v); })->end()
200+
->prototype('scalar')->end()
201+
->end()
202+
->end()
203+
->end()
204+
->arrayNode('entity')
205+
->scalarNode('class')->isRequired()->cannotBeEmpty()->end()
206+
->scalarNode('property')->defaultNull()->end()
207+
->end()
208+
->arrayNode('document')
209+
->scalarNode('class')->isRequired()->cannotBeEmpty()->end()
210+
->scalarNode('property')->defaultNull()->end()
211+
->end()
212+
->end()
213+
->end()
214+
;
215+
}
216+
217+
protected function addEncodersSection($rootNode)
218+
{
219+
$rootNode
220+
->fixXmlConfig('encoder')
221+
->arrayNode('encoders')
222+
->useAttributeAsKey('class')
223+
->prototype('array')
224+
->beforeNormalization()->ifString()->then(function($v) { return array('algorithm' => $v); })->end()
225+
->scalarNode('algorithm')->isRequired()->cannotBeEmpty()->end()
226+
->booleanNode('ignore_case')->end()
227+
->booleanNode('encode_as_base64')->end()
228+
->scalarNode('iterations')->end()
229+
->scalarNode('id')->end()
230+
->end()
231+
->end()
232+
;
233+
}
234+
}

‎src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php
+22-22Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

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

14+
use Symfony\Component\DependencyInjection\Configuration\Builder\NodeBuilder;
15+
1416
use Symfony\Component\DependencyInjection\DefinitionDecorator;
1517
use Symfony\Component\DependencyInjection\ContainerBuilder;
1618
use Symfony\Component\DependencyInjection\Reference;
@@ -38,10 +40,6 @@ abstract class AbstractFactory implements SecurityFactoryInterface
3840

3941
public function create(ContainerBuilder $container, $id, $config, $userProviderId, $defaultEntryPointId)
4042
{
41-
if (!is_array($config)) {
42-
$config = array();
43-
}
44-
4543
// authentication provider
4644
$authProviderId = $this->createAuthProvider($container, $id, $config, $userProviderId);
4745
$container
@@ -66,6 +64,24 @@ public function create(ContainerBuilder $container, $id, $config, $userProviderI
6664
return array($authProviderId, $listenerId, $entryPointId);
6765
}
6866

67+
public function addConfiguration(NodeBuilder $node)
68+
{
69+
$node
70+
->scalarNode('provider')->end()
71+
->booleanNode('remember_me')->defaultTrue()->end()
72+
->scalarNode('success_handler')->end()
73+
->scalarNode('failure_handler')->end()
74+
;
75+
76+
foreach ($this->options as $name => $default) {
77+
if (is_bool($default)) {
78+
$node->booleanNode($name)->defaultValue($default);
79+
} else {
80+
$node->scalarNode($name)->defaultValue($default);
81+
}
82+
}
83+
}
84+
6985
public final function addOption($name, $default = null)
7086
{
7187
$this->options[$name] = $default;
@@ -127,18 +143,15 @@ protected function createEntryPoint($container, $id, $config, $defaultEntryPoint
127143
*/
128144
protected function isRememberMeAware($config)
129145
{
130-
return !isset($config['remember_me']) || (Boolean) $config['remember_me'];
146+
return $config['remember_me'];
131147
}
132148

133149
protected function createListener($container, $id, $config, $userProvider)
134150
{
135-
// merge set options with default options
136-
$options = $this->getOptionsFromConfig($config);
137-
138151
$listenerId = $this->getListenerId();
139152
$listener = new DefinitionDecorator($listenerId);
140153
$listener->setArgument(3, $id);
141-
$listener->setArgument(4, $options);
154+
$listener->setArgument(4, array_intersect_key($config, $this->options));
142155

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

156169
return $listenerId;
157170
}
158-
159-
protected final function getOptionsFromConfig($config)
160-
{
161-
$options = $this->options;
162-
163-
foreach (array_keys($options) as $key) {
164-
if (array_key_exists($key, $config)) {
165-
$options[$key] = $config[$key];
166-
}
167-
}
168-
169-
return $options;
170-
}
171171
}

‎src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php
+2-5Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,11 @@ protected function createAuthProvider(ContainerBuilder $container, $id, $config,
5959

6060
protected function createEntryPoint($container, $id, $config, $defaultEntryPoint)
6161
{
62-
// merge set options with default options
63-
$options = $this->getOptionsFromConfig($config);
64-
6562
$entryPointId = 'security.authentication.form_entry_point.'.$id;
6663
$container
6764
->setDefinition($entryPointId, new DefinitionDecorator('security.authentication.form_entry_point'))
68-
->addArgument($options['login_path'])
69-
->addArgument($options['use_forward'])
65+
->addArgument($config['login_path'])
66+
->addArgument($config['use_forward'])
7067
;
7168

7269
return $entryPointId;

‎src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

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

14+
use Symfony\Component\DependencyInjection\Configuration\Builder\NodeBuilder;
15+
1416
use Symfony\Component\DependencyInjection\DefinitionDecorator;
1517
use Symfony\Component\DependencyInjection\ContainerBuilder;
1618
use Symfony\Component\DependencyInjection\Reference;
@@ -53,4 +55,11 @@ public function getKey()
5355
{
5456
return 'http-basic';
5557
}
58+
59+
public function addConfiguration(NodeBuilder $builder)
60+
{
61+
$builder
62+
->scalarNode('provider')->end()
63+
;
64+
}
5665
}

‎src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpDigestFactory.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpDigestFactory.php
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

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

14+
use Symfony\Component\DependencyInjection\Configuration\Builder\NodeBuilder;
15+
1416
use Symfony\Component\DependencyInjection\DefinitionDecorator;
1517
use Symfony\Component\DependencyInjection\ContainerBuilder;
1618
use Symfony\Component\DependencyInjection\Reference;
@@ -53,4 +55,11 @@ public function getKey()
5355
{
5456
return 'http-digest';
5557
}
58+
59+
public function addConfiguration(NodeBuilder $builder)
60+
{
61+
$builder
62+
->scalarNode('provider')->end()
63+
;
64+
}
5665
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.