vendor/symfony/security-bundle/DependencyInjection/MainConfiguration.php line 72

Open in your IDE?
  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\Config\Definition\Exception\InvalidConfigurationException;
  16. use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
  17. use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
  18. use Symfony\Component\Security\Http\Event\LogoutEvent;
  19. use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy;
  20. /**
  21.  * SecurityExtension configuration structure.
  22.  *
  23.  * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  24.  */
  25. class MainConfiguration implements ConfigurationInterface
  26. {
  27.     private $factories;
  28.     private $userProviderFactories;
  29.     public function __construct(array $factories, array $userProviderFactories)
  30.     {
  31.         $this->factories $factories;
  32.         $this->userProviderFactories $userProviderFactories;
  33.     }
  34.     /**
  35.      * Generates the configuration tree builder.
  36.      *
  37.      * @return TreeBuilder The tree builder
  38.      */
  39.     public function getConfigTreeBuilder()
  40.     {
  41.         $tb = new TreeBuilder('security');
  42.         $rootNode $tb->getRootNode();
  43.         $rootNode
  44.             ->beforeNormalization()
  45.                 ->ifTrue(function ($v) {
  46.                     if (!isset($v['access_decision_manager'])) {
  47.                         return true;
  48.                     }
  49.                     if (!isset($v['access_decision_manager']['strategy']) && !isset($v['access_decision_manager']['service'])) {
  50.                         return true;
  51.                     }
  52.                     return false;
  53.                 })
  54.                 ->then(function ($v) {
  55.                     $v['access_decision_manager']['strategy'] = AccessDecisionManager::STRATEGY_AFFIRMATIVE;
  56.                     return $v;
  57.                 })
  58.             ->end()
  59.             ->beforeNormalization()
  60.                 ->ifTrue(function ($v) {
  61.                     if ($v['encoders'] ?? false) {
  62.                         trigger_deprecation('symfony/security-bundle''5.3''The child node "encoders" at path "security" is deprecated, use "password_hashers" instead.');
  63.                         return true;
  64.                     }
  65.                     return $v['password_hashers'] ?? false;
  66.                 })
  67.                 ->then(function ($v) {
  68.                     $v['password_hashers'] = array_merge($v['password_hashers'] ?? [], $v['encoders'] ?? []);
  69.                     $v['encoders'] = $v['password_hashers'];
  70.                     return $v;
  71.                 })
  72.             ->end()
  73.             ->children()
  74.                 ->scalarNode('access_denied_url')->defaultNull()->example('/foo/error403')->end()
  75.                 ->enumNode('session_fixation_strategy')
  76.                     ->values([SessionAuthenticationStrategy::NONESessionAuthenticationStrategy::MIGRATESessionAuthenticationStrategy::INVALIDATE])
  77.                     ->defaultValue(SessionAuthenticationStrategy::MIGRATE)
  78.                 ->end()
  79.                 ->booleanNode('hide_user_not_found')->defaultTrue()->end()
  80.                 ->booleanNode('always_authenticate_before_granting')->defaultFalse()->end()
  81.                 ->booleanNode('erase_credentials')->defaultTrue()->end()
  82.                 ->booleanNode('enable_authenticator_manager')->defaultFalse()->info('Enables the new Symfony Security system based on Authenticators, all used authenticators must support this before enabling this.')->end()
  83.                 ->arrayNode('access_decision_manager')
  84.                     ->addDefaultsIfNotSet()
  85.                     ->children()
  86.                         ->enumNode('strategy')
  87.                             ->values($this->getAccessDecisionStrategies())
  88.                         ->end()
  89.                         ->scalarNode('service')->end()
  90.                         ->booleanNode('allow_if_all_abstain')->defaultFalse()->end()
  91.                         ->booleanNode('allow_if_equal_granted_denied')->defaultTrue()->end()
  92.                     ->end()
  93.                     ->validate()
  94.                         ->ifTrue(function ($v) { return isset($v['strategy']) && isset($v['service']); })
  95.                         ->thenInvalid('"strategy" and "service" cannot be used together.')
  96.                     ->end()
  97.                 ->end()
  98.             ->end()
  99.         ;
  100.         $this->addEncodersSection($rootNode);
  101.         $this->addPasswordHashersSection($rootNode);
  102.         $this->addProvidersSection($rootNode);
  103.         $this->addFirewallsSection($rootNode$this->factories);
  104.         $this->addAccessControlSection($rootNode);
  105.         $this->addRoleHierarchySection($rootNode);
  106.         return $tb;
  107.     }
  108.     private function addRoleHierarchySection(ArrayNodeDefinition $rootNode)
  109.     {
  110.         $rootNode
  111.             ->fixXmlConfig('role''role_hierarchy')
  112.             ->children()
  113.                 ->arrayNode('role_hierarchy')
  114.                     ->useAttributeAsKey('id')
  115.                     ->prototype('array')
  116.                         ->performNoDeepMerging()
  117.                         ->beforeNormalization()->ifString()->then(function ($v) { return ['value' => $v]; })->end()
  118.                         ->beforeNormalization()
  119.                             ->ifTrue(function ($v) { return \is_array($v) && isset($v['value']); })
  120.                             ->then(function ($v) { return preg_split('/\s*,\s*/'$v['value']); })
  121.                         ->end()
  122.                         ->prototype('scalar')->end()
  123.                     ->end()
  124.                 ->end()
  125.             ->end()
  126.         ;
  127.     }
  128.     private function addAccessControlSection(ArrayNodeDefinition $rootNode)
  129.     {
  130.         $rootNode
  131.             ->fixXmlConfig('rule''access_control')
  132.             ->children()
  133.                 ->arrayNode('access_control')
  134.                     ->cannotBeOverwritten()
  135.                     ->prototype('array')
  136.                         ->fixXmlConfig('ip')
  137.                         ->fixXmlConfig('method')
  138.                         ->children()
  139.                             ->scalarNode('requires_channel')->defaultNull()->end()
  140.                             ->scalarNode('path')
  141.                                 ->defaultNull()
  142.                                 ->info('use the urldecoded format')
  143.                                 ->example('^/path to resource/')
  144.                             ->end()
  145.                             ->scalarNode('host')->defaultNull()->end()
  146.                             ->integerNode('port')->defaultNull()->end()
  147.                             ->arrayNode('ips')
  148.                                 ->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()
  149.                                 ->prototype('scalar')->end()
  150.                             ->end()
  151.                             ->arrayNode('methods')
  152.                                 ->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/'$v); })->end()
  153.                                 ->prototype('scalar')->end()
  154.                             ->end()
  155.                             ->scalarNode('allow_if')->defaultNull()->end()
  156.                         ->end()
  157.                         ->fixXmlConfig('role')
  158.                         ->children()
  159.                             ->arrayNode('roles')
  160.                                 ->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/'$v); })->end()
  161.                                 ->prototype('scalar')->end()
  162.                             ->end()
  163.                         ->end()
  164.                     ->end()
  165.                 ->end()
  166.             ->end()
  167.         ;
  168.     }
  169.     private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $factories)
  170.     {
  171.         $firewallNodeBuilder $rootNode
  172.             ->fixXmlConfig('firewall')
  173.             ->children()
  174.                 ->arrayNode('firewalls')
  175.                     ->isRequired()
  176.                     ->requiresAtLeastOneElement()
  177.                     ->disallowNewKeysInSubsequentConfigs()
  178.                     ->useAttributeAsKey('name')
  179.                     ->prototype('array')
  180.                         ->fixXmlConfig('required_badge')
  181.                         ->children()
  182.         ;
  183.         $firewallNodeBuilder
  184.             ->scalarNode('pattern')->end()
  185.             ->scalarNode('host')->end()
  186.             ->arrayNode('methods')
  187.                 ->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/'$v); })->end()
  188.                 ->prototype('scalar')->end()
  189.             ->end()
  190.             ->booleanNode('security')->defaultTrue()->end()
  191.             ->scalarNode('user_checker')
  192.                 ->defaultValue('security.user_checker')
  193.                 ->treatNullLike('security.user_checker')
  194.                 ->info('The UserChecker to use when authenticating users in this firewall.')
  195.             ->end()
  196.             ->scalarNode('request_matcher')->end()
  197.             ->scalarNode('access_denied_url')->end()
  198.             ->scalarNode('access_denied_handler')->end()
  199.             ->scalarNode('entry_point')
  200.                 ->info(sprintf('An enabled authenticator name or a service id that implements "%s"'AuthenticationEntryPointInterface::class))
  201.             ->end()
  202.             ->scalarNode('provider')->end()
  203.             ->booleanNode('stateless')->defaultFalse()->end()
  204.             ->booleanNode('lazy')->defaultFalse()->end()
  205.             ->scalarNode('context')->cannotBeEmpty()->end()
  206.             ->arrayNode('logout')
  207.                 ->treatTrueLike([])
  208.                 ->canBeUnset()
  209.                 ->children()
  210.                     ->scalarNode('csrf_parameter')->defaultValue('_csrf_token')->end()
  211.                     ->scalarNode('csrf_token_generator')->cannotBeEmpty()->end()
  212.                     ->scalarNode('csrf_token_id')->defaultValue('logout')->end()
  213.                     ->scalarNode('path')->defaultValue('/logout')->end()
  214.                     ->scalarNode('target')->defaultValue('/')->end()
  215.                     ->scalarNode('success_handler')->setDeprecated('symfony/security-bundle''5.1'sprintf('The "%%node%%" at path "%%path%%" is deprecated, register a listener on the "%s" event instead.'LogoutEvent::class))->end()
  216.                     ->booleanNode('invalidate_session')->defaultTrue()->end()
  217.                 ->end()
  218.                 ->fixXmlConfig('delete_cookie')
  219.                 ->children()
  220.                     ->arrayNode('delete_cookies')
  221.                         ->normalizeKeys(false)
  222.                         ->beforeNormalization()
  223.                             ->ifTrue(function ($v) { return \is_array($v) && \is_int(key($v)); })
  224.                             ->then(function ($v) { return array_map(function ($v) { return ['name' => $v]; }, $v); })
  225.                         ->end()
  226.                         ->useAttributeAsKey('name')
  227.                         ->prototype('array')
  228.                             ->children()
  229.                                 ->scalarNode('path')->defaultNull()->end()
  230.                                 ->scalarNode('domain')->defaultNull()->end()
  231.                                 ->scalarNode('secure')->defaultFalse()->end()
  232.                                 ->scalarNode('samesite')->defaultNull()->end()
  233.                             ->end()
  234.                         ->end()
  235.                     ->end()
  236.                 ->end()
  237.                 ->fixXmlConfig('handler')
  238.                 ->children()
  239.                     ->arrayNode('handlers')
  240.                         ->prototype('scalar')->setDeprecated('symfony/security-bundle''5.1'sprintf('The "%%node%%" at path "%%path%%" is deprecated, register a listener on the "%s" event instead.'LogoutEvent::class))->end()
  241.                     ->end()
  242.                 ->end()
  243.             ->end()
  244.             ->arrayNode('switch_user')
  245.                 ->canBeUnset()
  246.                 ->children()
  247.                     ->scalarNode('provider')->end()
  248.                     ->scalarNode('parameter')->defaultValue('_switch_user')->end()
  249.                     ->scalarNode('role')->defaultValue('ROLE_ALLOWED_TO_SWITCH')->end()
  250.                 ->end()
  251.             ->end()
  252.             ->arrayNode('required_badges')
  253.                 ->info('A list of badges that must be present on the authenticated passport.')
  254.                 ->validate()
  255.                     ->always()
  256.                     ->then(function ($requiredBadges) {
  257.                         return array_map(function ($requiredBadge) {
  258.                             if (class_exists($requiredBadge)) {
  259.                                 return $requiredBadge;
  260.                             }
  261.                             if (false === strpos($requiredBadge'\\')) {
  262.                                 $fqcn 'Symfony\Component\Security\Http\Authenticator\Passport\Badge\\'.$requiredBadge;
  263.                                 if (class_exists($fqcn)) {
  264.                                     return $fqcn;
  265.                                 }
  266.                             }
  267.                             throw new InvalidConfigurationException(sprintf('Undefined security Badge class "%s" set in "security.firewall.required_badges".'$requiredBadge));
  268.                         }, $requiredBadges);
  269.                     })
  270.                 ->end()
  271.                 ->prototype('scalar')->end()
  272.             ->end()
  273.         ;
  274.         $abstractFactoryKeys = [];
  275.         foreach ($factories as $factoriesAtPosition) {
  276.             foreach ($factoriesAtPosition as $factory) {
  277.                 $name str_replace('-''_'$factory->getKey());
  278.                 $factoryNode $firewallNodeBuilder->arrayNode($name)
  279.                     ->canBeUnset()
  280.                 ;
  281.                 if ($factory instanceof AbstractFactory) {
  282.                     $abstractFactoryKeys[] = $name;
  283.                 }
  284.                 $factory->addConfiguration($factoryNode);
  285.             }
  286.         }
  287.         // check for unreachable check paths
  288.         $firewallNodeBuilder
  289.             ->end()
  290.             ->validate()
  291.                 ->ifTrue(function ($v) {
  292.                     return true === $v['security'] && isset($v['pattern']) && !isset($v['request_matcher']);
  293.                 })
  294.                 ->then(function ($firewall) use ($abstractFactoryKeys) {
  295.                     foreach ($abstractFactoryKeys as $k) {
  296.                         if (!isset($firewall[$k]['check_path'])) {
  297.                             continue;
  298.                         }
  299.                         if (str_contains($firewall[$k]['check_path'], '/') && !preg_match('#'.$firewall['pattern'].'#'$firewall[$k]['check_path'])) {
  300.                             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']));
  301.                         }
  302.                     }
  303.                     return $firewall;
  304.                 })
  305.             ->end()
  306.         ;
  307.     }
  308.     private function addProvidersSection(ArrayNodeDefinition $rootNode)
  309.     {
  310.         $providerNodeBuilder $rootNode
  311.             ->fixXmlConfig('provider')
  312.             ->children()
  313.                 ->arrayNode('providers')
  314.                     ->example([
  315.                         'my_memory_provider' => [
  316.                             'memory' => [
  317.                                 'users' => [
  318.                                     'foo' => ['password' => 'foo''roles' => 'ROLE_USER'],
  319.                                     'bar' => ['password' => 'bar''roles' => '[ROLE_USER, ROLE_ADMIN]'],
  320.                                 ],
  321.                             ],
  322.                         ],
  323.                         'my_entity_provider' => ['entity' => ['class' => 'SecurityBundle:User''property' => 'username']],
  324.                     ])
  325.                     ->requiresAtLeastOneElement()
  326.                     ->useAttributeAsKey('name')
  327.                     ->prototype('array')
  328.         ;
  329.         $providerNodeBuilder
  330.             ->children()
  331.                 ->scalarNode('id')->end()
  332.                 ->arrayNode('chain')
  333.                     ->fixXmlConfig('provider')
  334.                     ->children()
  335.                         ->arrayNode('providers')
  336.                             ->beforeNormalization()
  337.                                 ->ifString()
  338.                                 ->then(function ($v) { return preg_split('/\s*,\s*/'$v); })
  339.                             ->end()
  340.                             ->prototype('scalar')->end()
  341.                         ->end()
  342.                     ->end()
  343.                 ->end()
  344.             ->end()
  345.         ;
  346.         foreach ($this->userProviderFactories as $factory) {
  347.             $name str_replace('-''_'$factory->getKey());
  348.             $factoryNode $providerNodeBuilder->children()->arrayNode($name)->canBeUnset();
  349.             $factory->addConfiguration($factoryNode);
  350.         }
  351.         $providerNodeBuilder
  352.             ->validate()
  353.                 ->ifTrue(function ($v) { return \count($v) > 1; })
  354.                 ->thenInvalid('You cannot set multiple provider types for the same provider')
  355.             ->end()
  356.             ->validate()
  357.                 ->ifTrue(function ($v) { return === \count($v); })
  358.                 ->thenInvalid('You must set a provider definition for the provider.')
  359.             ->end()
  360.         ;
  361.     }
  362.     private function addEncodersSection(ArrayNodeDefinition $rootNode)
  363.     {
  364.         $rootNode
  365.             ->fixXmlConfig('encoder')
  366.             ->children()
  367.                 ->arrayNode('encoders')
  368.                     ->example([
  369.                         'App\Entity\User1' => 'auto',
  370.                         'App\Entity\User2' => [
  371.                             'algorithm' => 'auto',
  372.                             'time_cost' => 8,
  373.                             'cost' => 13,
  374.                         ],
  375.                     ])
  376.                     ->requiresAtLeastOneElement()
  377.                     ->useAttributeAsKey('class')
  378.                     ->prototype('array')
  379.                         ->canBeUnset()
  380.                         ->performNoDeepMerging()
  381.                         ->beforeNormalization()->ifString()->then(function ($v) { return ['algorithm' => $v]; })->end()
  382.                         ->children()
  383.                             ->scalarNode('algorithm')
  384.                                 ->cannotBeEmpty()
  385.                                 ->validate()
  386.                                     ->ifTrue(function ($v) { return !\is_string($v); })
  387.                                     ->thenInvalid('You must provide a string value.')
  388.                                 ->end()
  389.                             ->end()
  390.                             ->arrayNode('migrate_from')
  391.                                 ->prototype('scalar')->end()
  392.                                 ->beforeNormalization()->castToArray()->end()
  393.                             ->end()
  394.                             ->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()
  395.                             ->scalarNode('key_length')->defaultValue(40)->end()
  396.                             ->booleanNode('ignore_case')->defaultFalse()->end()
  397.                             ->booleanNode('encode_as_base64')->defaultTrue()->end()
  398.                             ->scalarNode('iterations')->defaultValue(5000)->end()
  399.                             ->integerNode('cost')
  400.                                 ->min(4)
  401.                                 ->max(31)
  402.                                 ->defaultNull()
  403.                             ->end()
  404.                             ->scalarNode('memory_cost')->defaultNull()->end()
  405.                             ->scalarNode('time_cost')->defaultNull()->end()
  406.                             ->scalarNode('id')->end()
  407.                         ->end()
  408.                     ->end()
  409.                 ->end()
  410.             ->end()
  411.         ;
  412.     }
  413.     private function addPasswordHashersSection(ArrayNodeDefinition $rootNode)
  414.     {
  415.         $rootNode
  416.             ->fixXmlConfig('password_hasher')
  417.             ->children()
  418.                 ->arrayNode('password_hashers')
  419.                     ->example([
  420.                         'App\Entity\User1' => 'auto',
  421.                         'App\Entity\User2' => [
  422.                             'algorithm' => 'auto',
  423.                             'time_cost' => 8,
  424.                             'cost' => 13,
  425.                         ],
  426.                     ])
  427.                     ->requiresAtLeastOneElement()
  428.                     ->useAttributeAsKey('class')
  429.                     ->prototype('array')
  430.                         ->canBeUnset()
  431.                         ->performNoDeepMerging()
  432.                         ->beforeNormalization()->ifString()->then(function ($v) { return ['algorithm' => $v]; })->end()
  433.                         ->children()
  434.                             ->scalarNode('algorithm')
  435.                                 ->cannotBeEmpty()
  436.                                 ->validate()
  437.                                     ->ifTrue(function ($v) { return !\is_string($v); })
  438.                                     ->thenInvalid('You must provide a string value.')
  439.                                 ->end()
  440.                             ->end()
  441.                             ->arrayNode('migrate_from')
  442.                                 ->prototype('scalar')->end()
  443.                                 ->beforeNormalization()->castToArray()->end()
  444.                             ->end()
  445.                             ->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()
  446.                             ->scalarNode('key_length')->defaultValue(40)->end()
  447.                             ->booleanNode('ignore_case')->defaultFalse()->end()
  448.                             ->booleanNode('encode_as_base64')->defaultTrue()->end()
  449.                             ->scalarNode('iterations')->defaultValue(5000)->end()
  450.                             ->integerNode('cost')
  451.                                 ->min(4)
  452.                                 ->max(31)
  453.                                 ->defaultNull()
  454.                             ->end()
  455.                             ->scalarNode('memory_cost')->defaultNull()->end()
  456.                             ->scalarNode('time_cost')->defaultNull()->end()
  457.                             ->scalarNode('id')->end()
  458.                         ->end()
  459.                     ->end()
  460.                 ->end()
  461.         ->end();
  462.     }
  463.     private function getAccessDecisionStrategies()
  464.     {
  465.         $strategies = [
  466.             AccessDecisionManager::STRATEGY_AFFIRMATIVE,
  467.             AccessDecisionManager::STRATEGY_CONSENSUS,
  468.             AccessDecisionManager::STRATEGY_UNANIMOUS,
  469.         ];
  470.         if (\defined(AccessDecisionManager::class.'::STRATEGY_PRIORITY')) {
  471.             $strategies[] = AccessDecisionManager::STRATEGY_PRIORITY;
  472.         }
  473.         return $strategies;
  474.     }
  475. }