MainConfiguration.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Bundle\SecurityBundle\DependencyInjection;
  11. use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AbstractFactory;
  12. use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
  13. use Symfony\Component\Config\Definition\Builder\TreeBuilder;
  14. use Symfony\Component\Config\Definition\ConfigurationInterface;
  15. use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
  16. use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy;
  17. /**
  18. * This class contains the configuration information.
  19. *
  20. * This information is for the following tags:
  21. *
  22. * * security.config
  23. * * security.acl
  24. *
  25. * This information is solely responsible for how the different configuration
  26. * sections are normalized, and merged.
  27. *
  28. * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  29. */
  30. class MainConfiguration implements ConfigurationInterface
  31. {
  32. private $factories;
  33. private $userProviderFactories;
  34. public function __construct(array $factories, array $userProviderFactories)
  35. {
  36. $this->factories = $factories;
  37. $this->userProviderFactories = $userProviderFactories;
  38. }
  39. /**
  40. * Generates the configuration tree builder.
  41. *
  42. * @return TreeBuilder The tree builder
  43. */
  44. public function getConfigTreeBuilder()
  45. {
  46. $tb = new TreeBuilder();
  47. $rootNode = $tb->root('security');
  48. $rootNode
  49. ->children()
  50. ->scalarNode('access_denied_url')->defaultNull()->example('/foo/error403')->end()
  51. ->enumNode('session_fixation_strategy')
  52. ->values(array(SessionAuthenticationStrategy::NONE, SessionAuthenticationStrategy::MIGRATE, SessionAuthenticationStrategy::INVALIDATE))
  53. ->defaultValue(SessionAuthenticationStrategy::MIGRATE)
  54. ->end()
  55. ->booleanNode('hide_user_not_found')->defaultTrue()->end()
  56. ->booleanNode('always_authenticate_before_granting')->defaultFalse()->end()
  57. ->booleanNode('erase_credentials')->defaultTrue()->end()
  58. ->arrayNode('access_decision_manager')
  59. ->addDefaultsIfNotSet()
  60. ->children()
  61. ->enumNode('strategy')
  62. ->values(array(AccessDecisionManager::STRATEGY_AFFIRMATIVE, AccessDecisionManager::STRATEGY_CONSENSUS, AccessDecisionManager::STRATEGY_UNANIMOUS))
  63. ->defaultValue(AccessDecisionManager::STRATEGY_AFFIRMATIVE)
  64. ->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. ;
  71. $this->addAclSection($rootNode);
  72. $this->addEncodersSection($rootNode);
  73. $this->addProvidersSection($rootNode);
  74. $this->addFirewallsSection($rootNode, $this->factories);
  75. $this->addAccessControlSection($rootNode);
  76. $this->addRoleHierarchySection($rootNode);
  77. return $tb;
  78. }
  79. private function addAclSection(ArrayNodeDefinition $rootNode)
  80. {
  81. $rootNode
  82. ->children()
  83. ->arrayNode('acl')
  84. ->children()
  85. ->scalarNode('connection')
  86. ->defaultNull()
  87. ->info('any name configured in doctrine.dbal section')
  88. ->end()
  89. ->arrayNode('cache')
  90. ->addDefaultsIfNotSet()
  91. ->children()
  92. ->scalarNode('id')->end()
  93. ->scalarNode('prefix')->defaultValue('sf2_acl_')->end()
  94. ->end()
  95. ->end()
  96. ->scalarNode('provider')->end()
  97. ->arrayNode('tables')
  98. ->addDefaultsIfNotSet()
  99. ->children()
  100. ->scalarNode('class')->defaultValue('acl_classes')->end()
  101. ->scalarNode('entry')->defaultValue('acl_entries')->end()
  102. ->scalarNode('object_identity')->defaultValue('acl_object_identities')->end()
  103. ->scalarNode('object_identity_ancestors')->defaultValue('acl_object_identity_ancestors')->end()
  104. ->scalarNode('security_identity')->defaultValue('acl_security_identities')->end()
  105. ->end()
  106. ->end()
  107. ->arrayNode('voter')
  108. ->addDefaultsIfNotSet()
  109. ->children()
  110. ->booleanNode('allow_if_object_identity_unavailable')->defaultTrue()->end()
  111. ->end()
  112. ->end()
  113. ->end()
  114. ->end()
  115. ->end()
  116. ;
  117. }
  118. private function addRoleHierarchySection(ArrayNodeDefinition $rootNode)
  119. {
  120. $rootNode
  121. ->fixXmlConfig('role', 'role_hierarchy')
  122. ->children()
  123. ->arrayNode('role_hierarchy')
  124. ->useAttributeAsKey('id')
  125. ->prototype('array')
  126. ->performNoDeepMerging()
  127. ->beforeNormalization()->ifString()->then(function ($v) { return array('value' => $v); })->end()
  128. ->beforeNormalization()
  129. ->ifTrue(function ($v) { return \is_array($v) && isset($v['value']); })
  130. ->then(function ($v) { return preg_split('/\s*,\s*/', $v['value']); })
  131. ->end()
  132. ->prototype('scalar')->end()
  133. ->end()
  134. ->end()
  135. ->end()
  136. ;
  137. }
  138. private function addAccessControlSection(ArrayNodeDefinition $rootNode)
  139. {
  140. $rootNode
  141. ->fixXmlConfig('rule', 'access_control')
  142. ->children()
  143. ->arrayNode('access_control')
  144. ->cannotBeOverwritten()
  145. ->prototype('array')
  146. ->fixXmlConfig('ip')
  147. ->fixXmlConfig('method')
  148. ->children()
  149. ->scalarNode('requires_channel')->defaultNull()->end()
  150. ->scalarNode('path')
  151. ->defaultNull()
  152. ->info('use the urldecoded format')
  153. ->example('^/path to resource/')
  154. ->end()
  155. ->scalarNode('host')->defaultNull()->end()
  156. ->arrayNode('ips')
  157. ->beforeNormalization()->ifString()->then(function ($v) { return array($v); })->end()
  158. ->prototype('scalar')->end()
  159. ->end()
  160. ->arrayNode('methods')
  161. ->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/', $v); })->end()
  162. ->prototype('scalar')->end()
  163. ->end()
  164. ->scalarNode('allow_if')->defaultNull()->end()
  165. ->end()
  166. ->fixXmlConfig('role')
  167. ->children()
  168. ->arrayNode('roles')
  169. ->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/', $v); })->end()
  170. ->prototype('scalar')->end()
  171. ->end()
  172. ->end()
  173. ->end()
  174. ->end()
  175. ->end()
  176. ;
  177. }
  178. private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $factories)
  179. {
  180. $firewallNodeBuilder = $rootNode
  181. ->fixXmlConfig('firewall')
  182. ->children()
  183. ->arrayNode('firewalls')
  184. ->isRequired()
  185. ->requiresAtLeastOneElement()
  186. ->disallowNewKeysInSubsequentConfigs()
  187. ->useAttributeAsKey('name')
  188. ->prototype('array')
  189. ->children()
  190. ;
  191. $firewallNodeBuilder
  192. ->scalarNode('pattern')->end()
  193. ->scalarNode('host')->end()
  194. ->arrayNode('methods')
  195. ->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/', $v); })->end()
  196. ->prototype('scalar')->end()
  197. ->end()
  198. ->booleanNode('security')->defaultTrue()->end()
  199. ->scalarNode('user_checker')
  200. ->defaultValue('security.user_checker')
  201. ->treatNullLike('security.user_checker')
  202. ->info('The UserChecker to use when authenticating users in this firewall.')
  203. ->end()
  204. ->scalarNode('request_matcher')->end()
  205. ->scalarNode('access_denied_url')->end()
  206. ->scalarNode('access_denied_handler')->end()
  207. ->scalarNode('entry_point')->end()
  208. ->scalarNode('provider')->end()
  209. ->booleanNode('stateless')->defaultFalse()->end()
  210. ->scalarNode('context')->cannotBeEmpty()->end()
  211. ->arrayNode('logout')
  212. ->treatTrueLike(array())
  213. ->canBeUnset()
  214. ->beforeNormalization()
  215. ->ifTrue(function ($v) { return isset($v['csrf_provider']) && isset($v['csrf_token_generator']); })
  216. ->thenInvalid("You should define a value for only one of 'csrf_provider' and 'csrf_token_generator' on a security firewall. Use 'csrf_token_generator' as this replaces 'csrf_provider'.")
  217. ->end()
  218. ->beforeNormalization()
  219. ->ifTrue(function ($v) { return isset($v['intention']) && isset($v['csrf_token_id']); })
  220. ->thenInvalid("You should define a value for only one of 'intention' and 'csrf_token_id' on a security firewall. Use 'csrf_token_id' as this replaces 'intention'.")
  221. ->end()
  222. ->beforeNormalization()
  223. ->ifTrue(function ($v) { return isset($v['csrf_provider']); })
  224. ->then(function ($v) {
  225. @trigger_error("Setting the 'csrf_provider' configuration key on a security firewall is deprecated since Symfony 2.8 and will be removed in 3.0. Use the 'csrf_token_generator' configuration key instead.", E_USER_DEPRECATED);
  226. $v['csrf_token_generator'] = $v['csrf_provider'];
  227. unset($v['csrf_provider']);
  228. return $v;
  229. })
  230. ->end()
  231. ->beforeNormalization()
  232. ->ifTrue(function ($v) { return isset($v['intention']); })
  233. ->then(function ($v) {
  234. @trigger_error("Setting the 'intention' configuration key on a security firewall is deprecated since Symfony 2.8 and will be removed in 3.0. Use the 'csrf_token_id' key instead.", E_USER_DEPRECATED);
  235. $v['csrf_token_id'] = $v['intention'];
  236. unset($v['intention']);
  237. return $v;
  238. })
  239. ->end()
  240. ->children()
  241. ->scalarNode('csrf_parameter')->defaultValue('_csrf_token')->end()
  242. ->scalarNode('csrf_token_generator')->cannotBeEmpty()->end()
  243. ->scalarNode('csrf_token_id')->defaultValue('logout')->end()
  244. ->scalarNode('path')->defaultValue('/logout')->end()
  245. ->scalarNode('target')->defaultValue('/')->end()
  246. ->scalarNode('success_handler')->end()
  247. ->booleanNode('invalidate_session')->defaultTrue()->end()
  248. ->end()
  249. ->fixXmlConfig('delete_cookie')
  250. ->children()
  251. ->arrayNode('delete_cookies')
  252. ->beforeNormalization()
  253. ->ifTrue(function ($v) { return \is_array($v) && \is_int(key($v)); })
  254. ->then(function ($v) { return array_map(function ($v) { return array('name' => $v); }, $v); })
  255. ->end()
  256. ->useAttributeAsKey('name')
  257. ->prototype('array')
  258. ->children()
  259. ->scalarNode('path')->defaultNull()->end()
  260. ->scalarNode('domain')->defaultNull()->end()
  261. ->end()
  262. ->end()
  263. ->end()
  264. ->end()
  265. ->fixXmlConfig('handler')
  266. ->children()
  267. ->arrayNode('handlers')
  268. ->prototype('scalar')->end()
  269. ->end()
  270. ->end()
  271. ->end()
  272. ->arrayNode('anonymous')
  273. ->canBeUnset()
  274. ->beforeNormalization()
  275. ->ifTrue(function ($v) { return isset($v['key']); })
  276. ->then(function ($v) {
  277. if (isset($v['secret'])) {
  278. throw new \LogicException('Cannot set both key and secret options for security.firewall.anonymous, use only secret instead.');
  279. }
  280. @trigger_error('security.firewall.anonymous.key is deprecated since Symfony 2.8 and will be removed in 3.0. Use security.firewall.anonymous.secret instead.', E_USER_DEPRECATED);
  281. $v['secret'] = $v['key'];
  282. unset($v['key']);
  283. })
  284. ->end()
  285. ->children()
  286. ->scalarNode('secret')->defaultValue(uniqid('', true))->end()
  287. ->end()
  288. ->end()
  289. ->arrayNode('switch_user')
  290. ->canBeUnset()
  291. ->children()
  292. ->scalarNode('provider')->end()
  293. ->scalarNode('parameter')->defaultValue('_switch_user')->end()
  294. ->scalarNode('role')->defaultValue('ROLE_ALLOWED_TO_SWITCH')->end()
  295. ->end()
  296. ->end()
  297. ;
  298. $abstractFactoryKeys = array();
  299. foreach ($factories as $factoriesAtPosition) {
  300. foreach ($factoriesAtPosition as $factory) {
  301. $name = str_replace('-', '_', $factory->getKey());
  302. $factoryNode = $firewallNodeBuilder->arrayNode($name)
  303. ->canBeUnset()
  304. ;
  305. if ($factory instanceof AbstractFactory) {
  306. $abstractFactoryKeys[] = $name;
  307. }
  308. $factory->addConfiguration($factoryNode);
  309. }
  310. }
  311. // check for unreachable check paths
  312. $firewallNodeBuilder
  313. ->end()
  314. ->validate()
  315. ->ifTrue(function ($v) {
  316. return true === $v['security'] && isset($v['pattern']) && !isset($v['request_matcher']);
  317. })
  318. ->then(function ($firewall) use ($abstractFactoryKeys) {
  319. foreach ($abstractFactoryKeys as $k) {
  320. if (!isset($firewall[$k]['check_path'])) {
  321. continue;
  322. }
  323. if (false !== strpos($firewall[$k]['check_path'], '/') && !preg_match('#'.$firewall['pattern'].'#', $firewall[$k]['check_path'])) {
  324. 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']));
  325. }
  326. }
  327. return $firewall;
  328. })
  329. ->end()
  330. ;
  331. }
  332. private function addProvidersSection(ArrayNodeDefinition $rootNode)
  333. {
  334. $providerNodeBuilder = $rootNode
  335. ->fixXmlConfig('provider')
  336. ->children()
  337. ->arrayNode('providers')
  338. ->example(array(
  339. 'my_memory_provider' => array(
  340. 'memory' => array(
  341. 'users' => array(
  342. 'foo' => array('password' => 'foo', 'roles' => 'ROLE_USER'),
  343. 'bar' => array('password' => 'bar', 'roles' => '[ROLE_USER, ROLE_ADMIN]'),
  344. ),
  345. ),
  346. ),
  347. 'my_entity_provider' => array('entity' => array('class' => 'SecurityBundle:User', 'property' => 'username')),
  348. ))
  349. ->isRequired()
  350. ->requiresAtLeastOneElement()
  351. ->useAttributeAsKey('name')
  352. ->prototype('array')
  353. ;
  354. $providerNodeBuilder
  355. ->children()
  356. ->scalarNode('id')->end()
  357. ->arrayNode('chain')
  358. ->fixXmlConfig('provider')
  359. ->children()
  360. ->arrayNode('providers')
  361. ->beforeNormalization()
  362. ->ifString()
  363. ->then(function ($v) { return preg_split('/\s*,\s*/', $v); })
  364. ->end()
  365. ->prototype('scalar')->end()
  366. ->end()
  367. ->end()
  368. ->end()
  369. ->end()
  370. ;
  371. foreach ($this->userProviderFactories as $factory) {
  372. $name = str_replace('-', '_', $factory->getKey());
  373. $factoryNode = $providerNodeBuilder->children()->arrayNode($name)->canBeUnset();
  374. $factory->addConfiguration($factoryNode);
  375. }
  376. $providerNodeBuilder
  377. ->validate()
  378. ->ifTrue(function ($v) { return \count($v) > 1; })
  379. ->thenInvalid('You cannot set multiple provider types for the same provider')
  380. ->end()
  381. ->validate()
  382. ->ifTrue(function ($v) { return 0 === \count($v); })
  383. ->thenInvalid('You must set a provider definition for the provider.')
  384. ->end()
  385. ;
  386. }
  387. private function addEncodersSection(ArrayNodeDefinition $rootNode)
  388. {
  389. $rootNode
  390. ->fixXmlConfig('encoder')
  391. ->children()
  392. ->arrayNode('encoders')
  393. ->example(array(
  394. 'AppBundle\Entity\User1' => 'bcrypt',
  395. 'AppBundle\Entity\User2' => array(
  396. 'algorithm' => 'bcrypt',
  397. 'cost' => 13,
  398. ),
  399. ))
  400. ->requiresAtLeastOneElement()
  401. ->useAttributeAsKey('class')
  402. ->prototype('array')
  403. ->canBeUnset()
  404. ->performNoDeepMerging()
  405. ->beforeNormalization()->ifString()->then(function ($v) { return array('algorithm' => $v); })->end()
  406. ->children()
  407. ->scalarNode('algorithm')->cannotBeEmpty()->end()
  408. ->scalarNode('hash_algorithm')->info('Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms.')->defaultValue('sha512')->end()
  409. ->scalarNode('key_length')->defaultValue(40)->end()
  410. ->booleanNode('ignore_case')->defaultFalse()->end()
  411. ->booleanNode('encode_as_base64')->defaultTrue()->end()
  412. ->scalarNode('iterations')->defaultValue(5000)->end()
  413. ->integerNode('cost')
  414. ->min(4)
  415. ->max(31)
  416. ->defaultValue(13)
  417. ->end()
  418. ->scalarNode('id')->end()
  419. ->end()
  420. ->end()
  421. ->end()
  422. ->end()
  423. ;
  424. }
  425. }