|
1 <?php |
|
2 |
|
3 /* |
|
4 * This file is part of the Symfony framework. |
|
5 * |
|
6 * (c) Fabien Potencier <fabien@symfony.com> |
|
7 * |
|
8 * This source file is subject to the MIT license that is bundled |
|
9 * with this source code in the file LICENSE. |
|
10 */ |
|
11 |
|
12 namespace Symfony\Bundle\SecurityBundle\DependencyInjection; |
|
13 |
|
14 use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AbstractFactory; |
|
15 |
|
16 use Symfony\Component\Config\Definition\Builder\TreeBuilder; |
|
17 use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; |
|
18 use Symfony\Component\Config\Definition\ConfigurationInterface; |
|
19 |
|
20 /** |
|
21 * This class contains the configuration information for the following tags: |
|
22 * |
|
23 * * security.config |
|
24 * * security.acl |
|
25 * |
|
26 * This information is solely responsible for how the different configuration |
|
27 * sections are normalized, and merged. |
|
28 * |
|
29 * @author Johannes M. Schmitt <schmittjoh@gmail.com> |
|
30 */ |
|
31 class MainConfiguration implements ConfigurationInterface |
|
32 { |
|
33 private $factories; |
|
34 |
|
35 /** |
|
36 * Constructor. |
|
37 * |
|
38 * @param array $factories |
|
39 */ |
|
40 public function __construct(array $factories) |
|
41 { |
|
42 $this->factories = $factories; |
|
43 } |
|
44 |
|
45 /** |
|
46 * Generates the configuration tree builder. |
|
47 * |
|
48 * @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder |
|
49 */ |
|
50 public function getConfigTreeBuilder() |
|
51 { |
|
52 $tb = new TreeBuilder(); |
|
53 $rootNode = $tb->root('security'); |
|
54 |
|
55 $rootNode |
|
56 ->children() |
|
57 ->scalarNode('access_denied_url')->defaultNull()->end() |
|
58 ->scalarNode('session_fixation_strategy')->cannotBeEmpty()->defaultValue('migrate')->end() |
|
59 ->booleanNode('hide_user_not_found')->defaultTrue()->end() |
|
60 ->booleanNode('always_authenticate_before_granting')->defaultFalse()->end() |
|
61 ->arrayNode('access_decision_manager') |
|
62 ->addDefaultsIfNotSet() |
|
63 ->children() |
|
64 ->scalarNode('strategy')->defaultValue('affirmative')->end() |
|
65 ->booleanNode('allow_if_all_abstain')->defaultFalse()->end() |
|
66 ->booleanNode('allow_if_equal_granted_denied')->defaultTrue()->end() |
|
67 ->end() |
|
68 ->end() |
|
69 ->end() |
|
70 // add a faux-entry for factories, so that no validation error is thrown |
|
71 ->fixXmlConfig('factory', 'factories') |
|
72 ->children() |
|
73 ->arrayNode('factories')->ignoreExtraKeys()->end() |
|
74 ->end() |
|
75 ; |
|
76 |
|
77 $this->addAclSection($rootNode); |
|
78 $this->addEncodersSection($rootNode); |
|
79 $this->addProvidersSection($rootNode); |
|
80 $this->addFirewallsSection($rootNode, $this->factories); |
|
81 $this->addAccessControlSection($rootNode); |
|
82 $this->addRoleHierarchySection($rootNode); |
|
83 |
|
84 return $tb; |
|
85 } |
|
86 |
|
87 private function addAclSection(ArrayNodeDefinition $rootNode) |
|
88 { |
|
89 $rootNode |
|
90 ->children() |
|
91 ->arrayNode('acl') |
|
92 ->children() |
|
93 ->scalarNode('connection')->end() |
|
94 ->arrayNode('cache') |
|
95 ->addDefaultsIfNotSet() |
|
96 ->children() |
|
97 ->scalarNode('id')->end() |
|
98 ->scalarNode('prefix')->defaultValue('sf2_acl_')->end() |
|
99 ->end() |
|
100 ->end() |
|
101 ->scalarNode('provider')->end() |
|
102 ->arrayNode('tables') |
|
103 ->addDefaultsIfNotSet() |
|
104 ->children() |
|
105 ->scalarNode('class')->defaultValue('acl_classes')->end() |
|
106 ->scalarNode('entry')->defaultValue('acl_entries')->end() |
|
107 ->scalarNode('object_identity')->defaultValue('acl_object_identities')->end() |
|
108 ->scalarNode('object_identity_ancestors')->defaultValue('acl_object_identity_ancestors')->end() |
|
109 ->scalarNode('security_identity')->defaultValue('acl_security_identities')->end() |
|
110 ->end() |
|
111 ->end() |
|
112 ->arrayNode('voter') |
|
113 ->addDefaultsIfNotSet() |
|
114 ->children() |
|
115 ->booleanNode('allow_if_object_identity_unavailable')->defaultTrue()->end() |
|
116 ->end() |
|
117 ->end() |
|
118 ->end() |
|
119 ->end() |
|
120 ->end() |
|
121 ; |
|
122 } |
|
123 |
|
124 private function addRoleHierarchySection(ArrayNodeDefinition $rootNode) |
|
125 { |
|
126 $rootNode |
|
127 ->fixXmlConfig('role', 'role_hierarchy') |
|
128 ->children() |
|
129 ->arrayNode('role_hierarchy') |
|
130 ->useAttributeAsKey('id') |
|
131 ->prototype('array') |
|
132 ->performNoDeepMerging() |
|
133 ->beforeNormalization()->ifString()->then(function($v) { return array('value' => $v); })->end() |
|
134 ->beforeNormalization() |
|
135 ->ifTrue(function($v) { return is_array($v) && isset($v['value']); }) |
|
136 ->then(function($v) { return preg_split('/\s*,\s*/', $v['value']); }) |
|
137 ->end() |
|
138 ->prototype('scalar')->end() |
|
139 ->end() |
|
140 ->end() |
|
141 ->end() |
|
142 ; |
|
143 } |
|
144 |
|
145 private function addAccessControlSection(ArrayNodeDefinition $rootNode) |
|
146 { |
|
147 $rootNode |
|
148 ->fixXmlConfig('rule', 'access_control') |
|
149 ->children() |
|
150 ->arrayNode('access_control') |
|
151 ->cannotBeOverwritten() |
|
152 ->prototype('array') |
|
153 ->children() |
|
154 ->scalarNode('requires_channel')->defaultNull()->end() |
|
155 ->scalarNode('path')->defaultNull()->end() |
|
156 ->scalarNode('host')->defaultNull()->end() |
|
157 ->scalarNode('ip')->defaultNull()->end() |
|
158 ->arrayNode('methods') |
|
159 ->beforeNormalization()->ifString()->then(function($v) { return preg_split('/\s*,\s*/', $v); })->end() |
|
160 ->prototype('scalar')->end() |
|
161 ->end() |
|
162 ->end() |
|
163 ->fixXmlConfig('role') |
|
164 ->children() |
|
165 ->arrayNode('roles') |
|
166 ->beforeNormalization()->ifString()->then(function($v) { return preg_split('/\s*,\s*/', $v); })->end() |
|
167 ->prototype('scalar')->end() |
|
168 ->end() |
|
169 ->end() |
|
170 ->end() |
|
171 ->end() |
|
172 ->end() |
|
173 ; |
|
174 } |
|
175 |
|
176 private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $factories) |
|
177 { |
|
178 $firewallNodeBuilder = $rootNode |
|
179 ->fixXmlConfig('firewall') |
|
180 ->children() |
|
181 ->arrayNode('firewalls') |
|
182 ->isRequired() |
|
183 ->requiresAtLeastOneElement() |
|
184 ->disallowNewKeysInSubsequentConfigs() |
|
185 ->useAttributeAsKey('name') |
|
186 ->prototype('array') |
|
187 ->children() |
|
188 ; |
|
189 |
|
190 $firewallNodeBuilder |
|
191 ->scalarNode('pattern')->end() |
|
192 ->booleanNode('security')->defaultTrue()->end() |
|
193 ->scalarNode('request_matcher')->end() |
|
194 ->scalarNode('access_denied_url')->end() |
|
195 ->scalarNode('access_denied_handler')->end() |
|
196 ->scalarNode('entry_point')->end() |
|
197 ->scalarNode('provider')->end() |
|
198 ->booleanNode('stateless')->defaultFalse()->end() |
|
199 ->scalarNode('context')->cannotBeEmpty()->end() |
|
200 ->arrayNode('logout') |
|
201 ->treatTrueLike(array()) |
|
202 ->canBeUnset() |
|
203 ->children() |
|
204 ->scalarNode('path')->defaultValue('/logout')->end() |
|
205 ->scalarNode('target')->defaultValue('/')->end() |
|
206 ->scalarNode('success_handler')->end() |
|
207 ->booleanNode('invalidate_session')->defaultTrue()->end() |
|
208 ->end() |
|
209 ->fixXmlConfig('delete_cookie') |
|
210 ->children() |
|
211 ->arrayNode('delete_cookies') |
|
212 ->beforeNormalization() |
|
213 ->ifTrue(function($v) { return is_array($v) && is_int(key($v)); }) |
|
214 ->then(function($v) { return array_map(function($v) { return array('name' => $v); }, $v); }) |
|
215 ->end() |
|
216 ->useAttributeAsKey('name') |
|
217 ->prototype('array') |
|
218 ->children() |
|
219 ->scalarNode('path')->defaultNull()->end() |
|
220 ->scalarNode('domain')->defaultNull()->end() |
|
221 ->end() |
|
222 ->end() |
|
223 ->end() |
|
224 ->end() |
|
225 ->fixXmlConfig('handler') |
|
226 ->children() |
|
227 ->arrayNode('handlers') |
|
228 ->prototype('scalar')->end() |
|
229 ->end() |
|
230 ->end() |
|
231 ->end() |
|
232 ->arrayNode('anonymous') |
|
233 ->canBeUnset() |
|
234 ->children() |
|
235 ->scalarNode('key')->defaultValue(uniqid())->end() |
|
236 ->end() |
|
237 ->end() |
|
238 ->arrayNode('switch_user') |
|
239 ->canBeUnset() |
|
240 ->children() |
|
241 ->scalarNode('provider')->end() |
|
242 ->scalarNode('parameter')->defaultValue('_switch_user')->end() |
|
243 ->scalarNode('role')->defaultValue('ROLE_ALLOWED_TO_SWITCH')->end() |
|
244 ->end() |
|
245 ->end() |
|
246 ; |
|
247 |
|
248 $abstractFactoryKeys = array(); |
|
249 foreach ($factories as $factoriesAtPosition) { |
|
250 foreach ($factoriesAtPosition as $factory) { |
|
251 $name = str_replace('-', '_', $factory->getKey()); |
|
252 $factoryNode = $firewallNodeBuilder->arrayNode($name) |
|
253 ->canBeUnset() |
|
254 ; |
|
255 |
|
256 if ($factory instanceof AbstractFactory) { |
|
257 $abstractFactoryKeys[] = $name; |
|
258 } |
|
259 |
|
260 $factory->addConfiguration($factoryNode); |
|
261 } |
|
262 } |
|
263 |
|
264 // check for unreachable check paths |
|
265 $firewallNodeBuilder |
|
266 ->end() |
|
267 ->validate() |
|
268 ->ifTrue(function($v) { |
|
269 return true === $v['security'] && isset($v['pattern']) && !isset($v['request_matcher']); |
|
270 }) |
|
271 ->then(function($firewall) use($abstractFactoryKeys) { |
|
272 foreach ($abstractFactoryKeys as $k) { |
|
273 if (!isset($firewall[$k]['check_path'])) { |
|
274 continue; |
|
275 } |
|
276 |
|
277 if (!preg_match('#'.$firewall['pattern'].'#', $firewall[$k]['check_path'])) { |
|
278 throw new \LogicException(sprintf('The check_path "%s" for login method "%s" is not matched by the firewall pattern "%s".', $firewall[$k]['check_path'], $k, $firewall['pattern'])); |
|
279 } |
|
280 } |
|
281 |
|
282 return $firewall; |
|
283 }) |
|
284 ->end() |
|
285 ; |
|
286 } |
|
287 |
|
288 private function addProvidersSection(ArrayNodeDefinition $rootNode) |
|
289 { |
|
290 $rootNode |
|
291 ->fixXmlConfig('provider') |
|
292 ->children() |
|
293 ->arrayNode('providers') |
|
294 ->disallowNewKeysInSubsequentConfigs() |
|
295 ->isRequired() |
|
296 ->requiresAtLeastOneElement() |
|
297 ->useAttributeAsKey('name') |
|
298 ->prototype('array') |
|
299 ->children() |
|
300 ->scalarNode('id')->end() |
|
301 ->arrayNode('entity') |
|
302 ->children() |
|
303 ->scalarNode('class')->isRequired()->cannotBeEmpty()->end() |
|
304 ->scalarNode('property')->defaultNull()->end() |
|
305 ->end() |
|
306 ->end() |
|
307 ->end() |
|
308 ->fixXmlConfig('provider') |
|
309 ->children() |
|
310 ->arrayNode('providers') |
|
311 ->beforeNormalization() |
|
312 ->ifString() |
|
313 ->then(function($v) { return preg_split('/\s*,\s*/', $v); }) |
|
314 ->end() |
|
315 ->prototype('scalar')->end() |
|
316 ->end() |
|
317 ->end() |
|
318 ->fixXmlConfig('user') |
|
319 ->children() |
|
320 ->arrayNode('users') |
|
321 ->useAttributeAsKey('name') |
|
322 ->prototype('array') |
|
323 ->children() |
|
324 ->scalarNode('password')->defaultValue(uniqid())->end() |
|
325 ->arrayNode('roles') |
|
326 ->beforeNormalization()->ifString()->then(function($v) { return preg_split('/\s*,\s*/', $v); })->end() |
|
327 ->prototype('scalar')->end() |
|
328 ->end() |
|
329 ->end() |
|
330 ->end() |
|
331 ->end() |
|
332 ->end() |
|
333 ->end() |
|
334 ->end() |
|
335 ->end() |
|
336 ; |
|
337 } |
|
338 |
|
339 private function addEncodersSection(ArrayNodeDefinition $rootNode) |
|
340 { |
|
341 $rootNode |
|
342 ->fixXmlConfig('encoder') |
|
343 ->children() |
|
344 ->arrayNode('encoders') |
|
345 ->requiresAtLeastOneElement() |
|
346 ->useAttributeAsKey('class') |
|
347 ->prototype('array') |
|
348 ->canBeUnset() |
|
349 ->performNoDeepMerging() |
|
350 ->beforeNormalization()->ifString()->then(function($v) { return array('algorithm' => $v); })->end() |
|
351 ->children() |
|
352 ->scalarNode('algorithm')->cannotBeEmpty()->end() |
|
353 ->booleanNode('ignore_case')->defaultFalse()->end() |
|
354 ->booleanNode('encode_as_base64')->defaultTrue()->end() |
|
355 ->scalarNode('iterations')->defaultValue(5000)->end() |
|
356 ->scalarNode('id')->end() |
|
357 ->end() |
|
358 ->end() |
|
359 ->end() |
|
360 ->end() |
|
361 ; |
|
362 } |
|
363 } |