- <?php
- /*
-  * This file is part of the Symfony package.
-  *
-  * (c) Fabien Potencier <fabien@symfony.com>
-  *
-  * For the full copyright and license information, please view the LICENSE
-  * file that was distributed with this source code.
-  */
- namespace Symfony\Bundle\SecurityBundle\DependencyInjection;
- use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AbstractFactory;
- use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface;
- use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
- use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
- use Symfony\Component\Config\Definition\Builder\TreeBuilder;
- use Symfony\Component\Config\Definition\ConfigurationInterface;
- use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
- use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
- use Symfony\Component\Security\Http\Event\LogoutEvent;
- use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy;
- /**
-  * SecurityExtension configuration structure.
-  *
-  * @author Johannes M. Schmitt <schmittjoh@gmail.com>
-  */
- class MainConfiguration implements ConfigurationInterface
- {
-     /** @internal */
-     public const STRATEGY_AFFIRMATIVE = 'affirmative';
-     /** @internal */
-     public const STRATEGY_CONSENSUS = 'consensus';
-     /** @internal */
-     public const STRATEGY_UNANIMOUS = 'unanimous';
-     /** @internal */
-     public const STRATEGY_PRIORITY = 'priority';
-     private $factories;
-     private $userProviderFactories;
-     /**
-      * @param array<array-key, SecurityFactoryInterface|AuthenticatorFactoryInterface> $factories
-      */
-     public function __construct(array $factories, array $userProviderFactories)
-     {
-         if (\is_array(current($factories))) {
-             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__);
-             $factories = array_merge(...array_values($factories));
-         }
-         $this->factories = $factories;
-         $this->userProviderFactories = $userProviderFactories;
-     }
-     /**
-      * Generates the configuration tree builder.
-      *
-      * @return TreeBuilder
-      */
-     public function getConfigTreeBuilder()
-     {
-         $tb = new TreeBuilder('security');
-         $rootNode = $tb->getRootNode();
-         $rootNode
-             ->beforeNormalization()
-                 ->ifTrue(function ($v) {
-                     if ($v['encoders'] ?? false) {
-                         trigger_deprecation('symfony/security-bundle', '5.3', 'The child node "encoders" at path "security" is deprecated, use "password_hashers" instead.');
-                         return true;
-                     }
-                     return $v['password_hashers'] ?? false;
-                 })
-                 ->then(function ($v) {
-                     $v['password_hashers'] = array_merge($v['password_hashers'] ?? [], $v['encoders'] ?? []);
-                     $v['encoders'] = $v['password_hashers'];
-                     return $v;
-                 })
-             ->end()
-             ->children()
-                 ->scalarNode('access_denied_url')->defaultNull()->example('/foo/error403')->end()
-                 ->enumNode('session_fixation_strategy')
-                     ->values([SessionAuthenticationStrategy::NONE, SessionAuthenticationStrategy::MIGRATE, SessionAuthenticationStrategy::INVALIDATE])
-                     ->defaultValue(SessionAuthenticationStrategy::MIGRATE)
-                 ->end()
-                 ->booleanNode('hide_user_not_found')->defaultTrue()->end()
-                 ->booleanNode('always_authenticate_before_granting')
-                     ->defaultFalse()
-                     ->setDeprecated('symfony/security-bundle', '5.4')
-                 ->end()
-                 ->booleanNode('erase_credentials')->defaultTrue()->end()
-                 ->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()
-                 ->arrayNode('access_decision_manager')
-                     ->addDefaultsIfNotSet()
-                     ->children()
-                         ->enumNode('strategy')
-                             ->values($this->getAccessDecisionStrategies())
-                         ->end()
-                         ->scalarNode('service')->end()
-                         ->scalarNode('strategy_service')->end()
-                         ->booleanNode('allow_if_all_abstain')->defaultFalse()->end()
-                         ->booleanNode('allow_if_equal_granted_denied')->defaultTrue()->end()
-                     ->end()
-                     ->validate()
-                         ->ifTrue(function ($v) { return isset($v['strategy'], $v['service']); })
-                         ->thenInvalid('"strategy" and "service" cannot be used together.')
-                     ->end()
-                     ->validate()
-                         ->ifTrue(function ($v) { return isset($v['strategy'], $v['strategy_service']); })
-                         ->thenInvalid('"strategy" and "strategy_service" cannot be used together.')
-                     ->end()
-                     ->validate()
-                         ->ifTrue(function ($v) { return isset($v['service'], $v['strategy_service']); })
-                         ->thenInvalid('"service" and "strategy_service" cannot be used together.')
-                     ->end()
-                 ->end()
-             ->end()
-         ;
-         $this->addEncodersSection($rootNode);
-         $this->addPasswordHashersSection($rootNode);
-         $this->addProvidersSection($rootNode);
-         $this->addFirewallsSection($rootNode, $this->factories);
-         $this->addAccessControlSection($rootNode);
-         $this->addRoleHierarchySection($rootNode);
-         return $tb;
-     }
-     private function addRoleHierarchySection(ArrayNodeDefinition $rootNode)
-     {
-         $rootNode
-             ->fixXmlConfig('role', 'role_hierarchy')
-             ->children()
-                 ->arrayNode('role_hierarchy')
-                     ->useAttributeAsKey('id')
-                     ->prototype('array')
-                         ->performNoDeepMerging()
-                         ->beforeNormalization()->ifString()->then(function ($v) { return ['value' => $v]; })->end()
-                         ->beforeNormalization()
-                             ->ifTrue(function ($v) { return \is_array($v) && isset($v['value']); })
-                             ->then(function ($v) { return preg_split('/\s*,\s*/', $v['value']); })
-                         ->end()
-                         ->prototype('scalar')->end()
-                     ->end()
-                 ->end()
-             ->end()
-         ;
-     }
-     private function addAccessControlSection(ArrayNodeDefinition $rootNode)
-     {
-         $rootNode
-             ->fixXmlConfig('rule', 'access_control')
-             ->children()
-                 ->arrayNode('access_control')
-                     ->cannotBeOverwritten()
-                     ->prototype('array')
-                         ->fixXmlConfig('ip')
-                         ->fixXmlConfig('method')
-                         ->children()
-                             ->scalarNode('requires_channel')->defaultNull()->end()
-                             ->scalarNode('path')
-                                 ->defaultNull()
-                                 ->info('use the urldecoded format')
-                                 ->example('^/path to resource/')
-                             ->end()
-                             ->scalarNode('host')->defaultNull()->end()
-                             ->integerNode('port')->defaultNull()->end()
-                             ->arrayNode('ips')
-                                 ->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()
-                                 ->prototype('scalar')->end()
-                             ->end()
-                             ->arrayNode('methods')
-                                 ->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/', $v); })->end()
-                                 ->prototype('scalar')->end()
-                             ->end()
-                             ->scalarNode('allow_if')->defaultNull()->end()
-                         ->end()
-                         ->fixXmlConfig('role')
-                         ->children()
-                             ->arrayNode('roles')
-                                 ->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/', $v); })->end()
-                                 ->prototype('scalar')->end()
-                             ->end()
-                         ->end()
-                     ->end()
-                 ->end()
-             ->end()
-         ;
-     }
-     /**
-      * @param array<array-key, SecurityFactoryInterface|AuthenticatorFactoryInterface> $factories
-      */
-     private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $factories)
-     {
-         $firewallNodeBuilder = $rootNode
-             ->fixXmlConfig('firewall')
-             ->children()
-                 ->arrayNode('firewalls')
-                     ->isRequired()
-                     ->requiresAtLeastOneElement()
-                     ->disallowNewKeysInSubsequentConfigs()
-                     ->useAttributeAsKey('name')
-                     ->prototype('array')
-                         ->fixXmlConfig('required_badge')
-                         ->children()
-         ;
-         $firewallNodeBuilder
-             ->scalarNode('pattern')->end()
-             ->scalarNode('host')->end()
-             ->arrayNode('methods')
-                 ->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/', $v); })->end()
-                 ->prototype('scalar')->end()
-             ->end()
-             ->booleanNode('security')->defaultTrue()->end()
-             ->scalarNode('user_checker')
-                 ->defaultValue('security.user_checker')
-                 ->treatNullLike('security.user_checker')
-                 ->info('The UserChecker to use when authenticating users in this firewall.')
-             ->end()
-             ->scalarNode('request_matcher')->end()
-             ->scalarNode('access_denied_url')->end()
-             ->scalarNode('access_denied_handler')->end()
-             ->scalarNode('entry_point')
-                 ->info(sprintf('An enabled authenticator name or a service id that implements "%s"', AuthenticationEntryPointInterface::class))
-             ->end()
-             ->scalarNode('provider')->end()
-             ->booleanNode('stateless')->defaultFalse()->end()
-             ->booleanNode('lazy')->defaultFalse()->end()
-             ->scalarNode('context')->cannotBeEmpty()->end()
-             ->arrayNode('logout')
-                 ->treatTrueLike([])
-                 ->canBeUnset()
-                 ->children()
-                     ->scalarNode('csrf_parameter')->defaultValue('_csrf_token')->end()
-                     ->scalarNode('csrf_token_generator')->cannotBeEmpty()->end()
-                     ->scalarNode('csrf_token_id')->defaultValue('logout')->end()
-                     ->scalarNode('path')->defaultValue('/logout')->end()
-                     ->scalarNode('target')->defaultValue('/')->end()
-                     ->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()
-                     ->booleanNode('invalidate_session')->defaultTrue()->end()
-                 ->end()
-                 ->fixXmlConfig('delete_cookie')
-                 ->children()
-                     ->arrayNode('delete_cookies')
-                         ->normalizeKeys(false)
-                         ->beforeNormalization()
-                             ->ifTrue(function ($v) { return \is_array($v) && \is_int(key($v)); })
-                             ->then(function ($v) { return array_map(function ($v) { return ['name' => $v]; }, $v); })
-                         ->end()
-                         ->useAttributeAsKey('name')
-                         ->prototype('array')
-                             ->children()
-                                 ->scalarNode('path')->defaultNull()->end()
-                                 ->scalarNode('domain')->defaultNull()->end()
-                                 ->scalarNode('secure')->defaultFalse()->end()
-                                 ->scalarNode('samesite')->defaultNull()->end()
-                             ->end()
-                         ->end()
-                     ->end()
-                 ->end()
-                 ->fixXmlConfig('handler')
-                 ->children()
-                     ->arrayNode('handlers')
-                         ->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()
-                     ->end()
-                 ->end()
-             ->end()
-             ->arrayNode('switch_user')
-                 ->canBeUnset()
-                 ->children()
-                     ->scalarNode('provider')->end()
-                     ->scalarNode('parameter')->defaultValue('_switch_user')->end()
-                     ->scalarNode('role')->defaultValue('ROLE_ALLOWED_TO_SWITCH')->end()
-                 ->end()
-             ->end()
-             ->arrayNode('required_badges')
-                 ->info('A list of badges that must be present on the authenticated passport.')
-                 ->validate()
-                     ->always()
-                     ->then(function ($requiredBadges) {
-                         return array_map(function ($requiredBadge) {
-                             if (class_exists($requiredBadge)) {
-                                 return $requiredBadge;
-                             }
-                             if (false === strpos($requiredBadge, '\\')) {
-                                 $fqcn = 'Symfony\Component\Security\Http\Authenticator\Passport\Badge\\'.$requiredBadge;
-                                 if (class_exists($fqcn)) {
-                                     return $fqcn;
-                                 }
-                             }
-                             throw new InvalidConfigurationException(sprintf('Undefined security Badge class "%s" set in "security.firewall.required_badges".', $requiredBadge));
-                         }, $requiredBadges);
-                     })
-                 ->end()
-                 ->prototype('scalar')->end()
-             ->end()
-         ;
-         $abstractFactoryKeys = [];
-         foreach ($factories as $factory) {
-             $name = str_replace('-', '_', $factory->getKey());
-             $factoryNode = $firewallNodeBuilder->arrayNode($name)
-                 ->canBeUnset()
-             ;
-             if ($factory instanceof AbstractFactory) {
-                 $abstractFactoryKeys[] = $name;
-             }
-             $factory->addConfiguration($factoryNode);
-         }
-         // check for unreachable check paths
-         $firewallNodeBuilder
-             ->end()
-             ->validate()
-                 ->ifTrue(function ($v) {
-                     return true === $v['security'] && isset($v['pattern']) && !isset($v['request_matcher']);
-                 })
-                 ->then(function ($firewall) use ($abstractFactoryKeys) {
-                     foreach ($abstractFactoryKeys as $k) {
-                         if (!isset($firewall[$k]['check_path'])) {
-                             continue;
-                         }
-                         if (str_contains($firewall[$k]['check_path'], '/') && !preg_match('#'.$firewall['pattern'].'#', $firewall[$k]['check_path'])) {
-                             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']));
-                         }
-                     }
-                     return $firewall;
-                 })
-             ->end()
-         ;
-     }
-     private function addProvidersSection(ArrayNodeDefinition $rootNode)
-     {
-         $providerNodeBuilder = $rootNode
-             ->fixXmlConfig('provider')
-             ->children()
-                 ->arrayNode('providers')
-                     ->example([
-                         'my_memory_provider' => [
-                             'memory' => [
-                                 'users' => [
-                                     'foo' => ['password' => 'foo', 'roles' => 'ROLE_USER'],
-                                     'bar' => ['password' => 'bar', 'roles' => '[ROLE_USER, ROLE_ADMIN]'],
-                                 ],
-                             ],
-                         ],
-                         'my_entity_provider' => ['entity' => ['class' => 'SecurityBundle:User', 'property' => 'username']],
-                     ])
-                     ->requiresAtLeastOneElement()
-                     ->useAttributeAsKey('name')
-                     ->prototype('array')
-         ;
-         $providerNodeBuilder
-             ->children()
-                 ->scalarNode('id')->end()
-                 ->arrayNode('chain')
-                     ->fixXmlConfig('provider')
-                     ->children()
-                         ->arrayNode('providers')
-                             ->beforeNormalization()
-                                 ->ifString()
-                                 ->then(function ($v) { return preg_split('/\s*,\s*/', $v); })
-                             ->end()
-                             ->prototype('scalar')->end()
-                         ->end()
-                     ->end()
-                 ->end()
-             ->end()
-         ;
-         foreach ($this->userProviderFactories as $factory) {
-             $name = str_replace('-', '_', $factory->getKey());
-             $factoryNode = $providerNodeBuilder->children()->arrayNode($name)->canBeUnset();
-             $factory->addConfiguration($factoryNode);
-         }
-         $providerNodeBuilder
-             ->validate()
-                 ->ifTrue(function ($v) { return \count($v) > 1; })
-                 ->thenInvalid('You cannot set multiple provider types for the same provider')
-             ->end()
-             ->validate()
-                 ->ifTrue(function ($v) { return 0 === \count($v); })
-                 ->thenInvalid('You must set a provider definition for the provider.')
-             ->end()
-         ;
-     }
-     private function addEncodersSection(ArrayNodeDefinition $rootNode)
-     {
-         $rootNode
-             ->fixXmlConfig('encoder')
-             ->children()
-                 ->arrayNode('encoders')
-                     ->example([
-                         'App\Entity\User1' => 'auto',
-                         'App\Entity\User2' => [
-                             'algorithm' => 'auto',
-                             'time_cost' => 8,
-                             'cost' => 13,
-                         ],
-                     ])
-                     ->requiresAtLeastOneElement()
-                     ->useAttributeAsKey('class')
-                     ->prototype('array')
-                         ->canBeUnset()
-                         ->performNoDeepMerging()
-                         ->beforeNormalization()->ifString()->then(function ($v) { return ['algorithm' => $v]; })->end()
-                         ->children()
-                             ->scalarNode('algorithm')
-                                 ->cannotBeEmpty()
-                                 ->validate()
-                                     ->ifTrue(function ($v) { return !\is_string($v); })
-                                     ->thenInvalid('You must provide a string value.')
-                                 ->end()
-                             ->end()
-                             ->arrayNode('migrate_from')
-                                 ->prototype('scalar')->end()
-                                 ->beforeNormalization()->castToArray()->end()
-                             ->end()
-                             ->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()
-                             ->scalarNode('key_length')->defaultValue(40)->end()
-                             ->booleanNode('ignore_case')->defaultFalse()->end()
-                             ->booleanNode('encode_as_base64')->defaultTrue()->end()
-                             ->scalarNode('iterations')->defaultValue(5000)->end()
-                             ->integerNode('cost')
-                                 ->min(4)
-                                 ->max(31)
-                                 ->defaultNull()
-                             ->end()
-                             ->scalarNode('memory_cost')->defaultNull()->end()
-                             ->scalarNode('time_cost')->defaultNull()->end()
-                             ->scalarNode('id')->end()
-                         ->end()
-                     ->end()
-                 ->end()
-             ->end()
-         ;
-     }
-     private function addPasswordHashersSection(ArrayNodeDefinition $rootNode)
-     {
-         $rootNode
-             ->fixXmlConfig('password_hasher')
-             ->children()
-                 ->arrayNode('password_hashers')
-                     ->example([
-                         'App\Entity\User1' => 'auto',
-                         'App\Entity\User2' => [
-                             'algorithm' => 'auto',
-                             'time_cost' => 8,
-                             'cost' => 13,
-                         ],
-                     ])
-                     ->requiresAtLeastOneElement()
-                     ->useAttributeAsKey('class')
-                     ->prototype('array')
-                         ->canBeUnset()
-                         ->performNoDeepMerging()
-                         ->beforeNormalization()->ifString()->then(function ($v) { return ['algorithm' => $v]; })->end()
-                         ->children()
-                             ->scalarNode('algorithm')
-                                 ->cannotBeEmpty()
-                                 ->validate()
-                                     ->ifTrue(function ($v) { return !\is_string($v); })
-                                     ->thenInvalid('You must provide a string value.')
-                                 ->end()
-                             ->end()
-                             ->arrayNode('migrate_from')
-                                 ->prototype('scalar')->end()
-                                 ->beforeNormalization()->castToArray()->end()
-                             ->end()
-                             ->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()
-                             ->scalarNode('key_length')->defaultValue(40)->end()
-                             ->booleanNode('ignore_case')->defaultFalse()->end()
-                             ->booleanNode('encode_as_base64')->defaultTrue()->end()
-                             ->scalarNode('iterations')->defaultValue(5000)->end()
-                             ->integerNode('cost')
-                                 ->min(4)
-                                 ->max(31)
-                                 ->defaultNull()
-                             ->end()
-                             ->scalarNode('memory_cost')->defaultNull()->end()
-                             ->scalarNode('time_cost')->defaultNull()->end()
-                             ->scalarNode('id')->end()
-                         ->end()
-                     ->end()
-                 ->end()
-         ->end();
-     }
-     private function getAccessDecisionStrategies(): array
-     {
-         return [
-             self::STRATEGY_AFFIRMATIVE,
-             self::STRATEGY_CONSENSUS,
-             self::STRATEGY_UNANIMOUS,
-             self::STRATEGY_PRIORITY,
-         ];
-     }
- }
-