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

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