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

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