vendor/symfony/security-bundle/DependencyInjection/SecurityExtension.php line 721

  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\Bridge\Twig\Extension\LogoutUrlExtension;
  12. use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface;
  13. use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FirewallListenerFactoryInterface;
  14. use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\StatelessAuthenticatorFactoryInterface;
  15. use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface;
  16. use Symfony\Bundle\SecurityBundle\SecurityBundle;
  17. use Symfony\Component\Config\Definition\ConfigurationInterface;
  18. use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
  19. use Symfony\Component\Config\FileLocator;
  20. use Symfony\Component\Console\Application;
  21. use Symfony\Component\DependencyInjection\Alias;
  22. use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
  23. use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
  24. use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
  25. use Symfony\Component\DependencyInjection\ChildDefinition;
  26. use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
  27. use Symfony\Component\DependencyInjection\ContainerBuilder;
  28. use Symfony\Component\DependencyInjection\Definition;
  29. use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
  30. use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
  31. use Symfony\Component\DependencyInjection\Reference;
  32. use Symfony\Component\EventDispatcher\EventDispatcher;
  33. use Symfony\Component\ExpressionLanguage\Expression;
  34. use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
  35. use Symfony\Component\Form\Extension\PasswordHasher\PasswordHasherExtension;
  36. use Symfony\Component\HttpFoundation\ChainRequestMatcher;
  37. use Symfony\Component\HttpFoundation\RequestMatcher\AttributesRequestMatcher;
  38. use Symfony\Component\HttpFoundation\RequestMatcher\HostRequestMatcher;
  39. use Symfony\Component\HttpFoundation\RequestMatcher\IpsRequestMatcher;
  40. use Symfony\Component\HttpFoundation\RequestMatcher\MethodRequestMatcher;
  41. use Symfony\Component\HttpFoundation\RequestMatcher\PathRequestMatcher;
  42. use Symfony\Component\HttpFoundation\RequestMatcher\PortRequestMatcher;
  43. use Symfony\Component\HttpKernel\DependencyInjection\Extension;
  44. use Symfony\Component\HttpKernel\KernelEvents;
  45. use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher;
  46. use Symfony\Component\PasswordHasher\Hasher\Pbkdf2PasswordHasher;
  47. use Symfony\Component\PasswordHasher\Hasher\PlaintextPasswordHasher;
  48. use Symfony\Component\PasswordHasher\Hasher\SodiumPasswordHasher;
  49. use Symfony\Component\Routing\Loader\ContainerLoader;
  50. use Symfony\Component\Security\Core\Authorization\Strategy\AffirmativeStrategy;
  51. use Symfony\Component\Security\Core\Authorization\Strategy\ConsensusStrategy;
  52. use Symfony\Component\Security\Core\Authorization\Strategy\PriorityStrategy;
  53. use Symfony\Component\Security\Core\Authorization\Strategy\UnanimousStrategy;
  54. use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
  55. use Symfony\Component\Security\Core\User\ChainUserChecker;
  56. use Symfony\Component\Security\Core\User\ChainUserProvider;
  57. use Symfony\Component\Security\Core\User\UserCheckerInterface;
  58. use Symfony\Component\Security\Core\User\UserProviderInterface;
  59. use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticatorManagerListener;
  60. use Symfony\Component\Security\Http\Event\CheckPassportEvent;
  61. /**
  62.  * SecurityExtension.
  63.  *
  64.  * @author Fabien Potencier <fabien@symfony.com>
  65.  * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  66.  */
  67. class SecurityExtension extends Extension implements PrependExtensionInterface
  68. {
  69.     private array $requestMatchers = [];
  70.     private array $expressions = [];
  71.     private array $contextListeners = [];
  72.     /** @var list<array{int, AuthenticatorFactoryInterface}> */
  73.     private array $factories = [];
  74.     /** @var AuthenticatorFactoryInterface[] */
  75.     private array $sortedFactories = [];
  76.     private array $userProviderFactories = [];
  77.     /**
  78.      * @return void
  79.      */
  80.     public function prepend(ContainerBuilder $container)
  81.     {
  82.         foreach ($this->getSortedFactories() as $factory) {
  83.             if ($factory instanceof PrependExtensionInterface) {
  84.                 $factory->prepend($container);
  85.             }
  86.         }
  87.     }
  88.     /**
  89.      * @return void
  90.      */
  91.     public function load(array $configsContainerBuilder $container)
  92.     {
  93.         if (!array_filter($configs)) {
  94.             trigger_deprecation('symfony/security-bundle''6.3''Enabling bundle "%s" and not configuring it is deprecated.'SecurityBundle::class);
  95.             // uncomment the following line in 7.0
  96.             // throw new InvalidConfigurationException(sprintf('Enabling bundle "%s" and not configuring it is not allowed.', SecurityBundle::class));
  97.             return;
  98.         }
  99.         $mainConfig $this->getConfiguration($configs$container);
  100.         $config $this->processConfiguration($mainConfig$configs);
  101.         // load services
  102.         $loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__).'/Resources/config'));
  103.         $loader->load('security.php');
  104.         $loader->load('password_hasher.php');
  105.         $loader->load('security_listeners.php');
  106.         if (!$config['enable_authenticator_manager']) {
  107.             throw new InvalidConfigurationException('"security.enable_authenticator_manager" must be set to "true".');
  108.         }
  109.         $loader->load('security_authenticator.php');
  110.         $loader->load('security_authenticator_access_token.php');
  111.         if ($container::willBeAvailable('symfony/twig-bridge'LogoutUrlExtension::class, ['symfony/security-bundle'])) {
  112.             $loader->load('templating_twig.php');
  113.         }
  114.         $loader->load('collectors.php');
  115.         if ($container->hasParameter('kernel.debug') && $container->getParameter('kernel.debug')) {
  116.             $loader->load('security_debug.php');
  117.         }
  118.         if (!$container::willBeAvailable('symfony/expression-language'ExpressionLanguage::class, ['symfony/security-bundle'])) {
  119.             $container->removeDefinition('security.expression_language');
  120.             $container->removeDefinition('security.access.expression_voter');
  121.             $container->removeDefinition('security.is_granted_attribute_expression_language');
  122.         }
  123.         if (!class_exists(PasswordHasherExtension::class)) {
  124.             $container->removeDefinition('form.listener.password_hasher');
  125.             $container->removeDefinition('form.type_extension.form.password_hasher');
  126.             $container->removeDefinition('form.type_extension.password.password_hasher');
  127.         }
  128.         // set some global scalars
  129.         $container->setParameter('security.access.denied_url'$config['access_denied_url']);
  130.         $container->setParameter('security.authentication.manager.erase_credentials'$config['erase_credentials']);
  131.         $container->setParameter('security.authentication.session_strategy.strategy'$config['session_fixation_strategy']);
  132.         if (isset($config['access_decision_manager']['service'])) {
  133.             $container->setAlias('security.access.decision_manager'$config['access_decision_manager']['service']);
  134.         } elseif (isset($config['access_decision_manager']['strategy_service'])) {
  135.             $container
  136.                 ->getDefinition('security.access.decision_manager')
  137.                 ->addArgument(new Reference($config['access_decision_manager']['strategy_service']));
  138.         } else {
  139.             $container
  140.                 ->getDefinition('security.access.decision_manager')
  141.                 ->addArgument($this->createStrategyDefinition(
  142.                     $config['access_decision_manager']['strategy'] ?? MainConfiguration::STRATEGY_AFFIRMATIVE,
  143.                     $config['access_decision_manager']['allow_if_all_abstain'],
  144.                     $config['access_decision_manager']['allow_if_equal_granted_denied']
  145.                 ));
  146.         }
  147.         $container->setParameter('security.authentication.hide_user_not_found'$config['hide_user_not_found']);
  148.         if (class_exists(Application::class)) {
  149.             $loader->load('debug_console.php');
  150.         }
  151.         $this->createFirewalls($config$container);
  152.         if ($container::willBeAvailable('symfony/routing'ContainerLoader::class, ['symfony/security-bundle'])) {
  153.             $this->createLogoutUrisParameter($config['firewalls'] ?? [], $container);
  154.         } else {
  155.             $container->removeDefinition('security.route_loader.logout');
  156.         }
  157.         $this->createAuthorization($config$container);
  158.         $this->createRoleHierarchy($config$container);
  159.         if ($config['password_hashers']) {
  160.             $this->createHashers($config['password_hashers'], $container);
  161.         }
  162.         if (class_exists(Application::class)) {
  163.             $loader->load('console.php');
  164.             $container->getDefinition('security.command.user_password_hash')->replaceArgument(1array_keys($config['password_hashers']));
  165.         }
  166.         $container->registerForAutoconfiguration(VoterInterface::class)
  167.             ->addTag('security.voter');
  168.         // required for compatibility with Symfony 5.4
  169.         $container->getDefinition('security.access_listener')->setArgument(3false);
  170.         $container->getDefinition('security.authorization_checker')->setArgument(2false);
  171.         $container->getDefinition('security.authorization_checker')->setArgument(3false);
  172.     }
  173.     private function createStrategyDefinition(string $strategybool $allowIfAllAbstainDecisionsbool $allowIfEqualGrantedDeniedDecisions): Definition
  174.     {
  175.         return match ($strategy) {
  176.             MainConfiguration::STRATEGY_AFFIRMATIVE => new Definition(AffirmativeStrategy::class, [$allowIfAllAbstainDecisions]),
  177.             MainConfiguration::STRATEGY_CONSENSUS => new Definition(ConsensusStrategy::class, [$allowIfAllAbstainDecisions$allowIfEqualGrantedDeniedDecisions]),
  178.             MainConfiguration::STRATEGY_UNANIMOUS => new Definition(UnanimousStrategy::class, [$allowIfAllAbstainDecisions]),
  179.             MainConfiguration::STRATEGY_PRIORITY => new Definition(PriorityStrategy::class, [$allowIfAllAbstainDecisions]),
  180.             default => throw new InvalidConfigurationException(sprintf('The strategy "%s" is not supported.'$strategy)),
  181.         };
  182.     }
  183.     private function createRoleHierarchy(array $configContainerBuilder $container): void
  184.     {
  185.         if (!isset($config['role_hierarchy']) || === \count($config['role_hierarchy'])) {
  186.             $container->removeDefinition('security.access.role_hierarchy_voter');
  187.             return;
  188.         }
  189.         $container->setParameter('security.role_hierarchy.roles'$config['role_hierarchy']);
  190.         $container->removeDefinition('security.access.simple_role_voter');
  191.     }
  192.     private function createAuthorization(array $configContainerBuilder $container): void
  193.     {
  194.         foreach ($config['access_control'] as $access) {
  195.             if (isset($access['request_matcher'])) {
  196.                 if ($access['path'] || $access['host'] || $access['port'] || $access['ips'] || $access['methods'] || $access['attributes'] || $access['route']) {
  197.                     throw new InvalidConfigurationException('The "request_matcher" option should not be specified alongside other options. Consider integrating your constraints inside your RequestMatcher directly.');
  198.                 }
  199.                 $matcher = new Reference($access['request_matcher']);
  200.             } else {
  201.                 $attributes $access['attributes'];
  202.                 if ($access['route']) {
  203.                     if (\array_key_exists('_route'$attributes)) {
  204.                         throw new InvalidConfigurationException('The "route" option should not be specified alongside "attributes._route" option. Use just one of the options.');
  205.                     }
  206.                     $attributes['_route'] = $access['route'];
  207.                 }
  208.                 $matcher $this->createRequestMatcher(
  209.                     $container,
  210.                     $access['path'],
  211.                     $access['host'],
  212.                     $access['port'],
  213.                     $access['methods'],
  214.                     $access['ips'],
  215.                     $attributes
  216.                 );
  217.             }
  218.             $roles $access['roles'];
  219.             if ($access['allow_if']) {
  220.                 $roles[] = $this->createExpression($container$access['allow_if']);
  221.             }
  222.             $emptyAccess === \count(array_filter($access));
  223.             if ($emptyAccess) {
  224.                 throw new InvalidConfigurationException('One or more access control items are empty. Did you accidentally add lines only containing a "-" under "security.access_control"?');
  225.             }
  226.             $container->getDefinition('security.access_map')
  227.                       ->addMethodCall('add', [$matcher$roles$access['requires_channel']]);
  228.         }
  229.         // allow cache warm-up for expressions
  230.         if (\count($this->expressions)) {
  231.             $container->getDefinition('security.cache_warmer.expression')
  232.                 ->replaceArgument(0, new IteratorArgument(array_values($this->expressions)));
  233.         } else {
  234.             $container->removeDefinition('security.cache_warmer.expression');
  235.         }
  236.     }
  237.     private function createFirewalls(array $configContainerBuilder $container): void
  238.     {
  239.         if (!isset($config['firewalls'])) {
  240.             return;
  241.         }
  242.         $firewalls $config['firewalls'];
  243.         $providerIds $this->createUserProviders($config$container);
  244.         $container->setParameter('security.firewalls'array_keys($firewalls));
  245.         // make the ContextListener aware of the configured user providers
  246.         $contextListenerDefinition $container->getDefinition('security.context_listener');
  247.         $arguments $contextListenerDefinition->getArguments();
  248.         $userProviders = [];
  249.         foreach ($providerIds as $userProviderId) {
  250.             $userProviders[] = new Reference($userProviderId);
  251.         }
  252.         $arguments[1] = $userProviderIteratorsArgument = new IteratorArgument($userProviders);
  253.         $contextListenerDefinition->setArguments($arguments);
  254.         $nbUserProviders \count($userProviders);
  255.         if ($nbUserProviders 1) {
  256.             $container->setDefinition('security.user_providers', new Definition(ChainUserProvider::class, [$userProviderIteratorsArgument]));
  257.         } elseif (=== $nbUserProviders) {
  258.             $container->removeDefinition('security.listener.user_provider');
  259.         } else {
  260.             $container->setAlias('security.user_providers', new Alias(current($providerIds)));
  261.         }
  262.         if (=== \count($providerIds)) {
  263.             $container->setAlias(UserProviderInterface::class, current($providerIds));
  264.         }
  265.         $customUserChecker false;
  266.         // load firewall map
  267.         $mapDef $container->getDefinition('security.firewall.map');
  268.         $map $authenticationProviders $contextRefs $authenticators = [];
  269.         foreach ($firewalls as $name => $firewall) {
  270.             if (isset($firewall['user_checker']) && 'security.user_checker' !== $firewall['user_checker']) {
  271.                 $customUserChecker true;
  272.             }
  273.             $configId 'security.firewall.map.config.'.$name;
  274.             [$matcher$listeners$exceptionListener$logoutListener$firewallAuthenticators] = $this->createFirewall($container$name$firewall$authenticationProviders$providerIds$configId);
  275.             if (!$firewallAuthenticators) {
  276.                 $authenticators[$name] = null;
  277.             } else {
  278.                 $firewallAuthenticatorRefs = [];
  279.                 foreach ($firewallAuthenticators as $authenticatorId) {
  280.                     $firewallAuthenticatorRefs[$authenticatorId] = new Reference($authenticatorId);
  281.                 }
  282.                 $authenticators[$name] = ServiceLocatorTagPass::register($container$firewallAuthenticatorRefs);
  283.             }
  284.             $contextId 'security.firewall.map.context.'.$name;
  285.             $isLazy = !$firewall['stateless'] && (!empty($firewall['anonymous']['lazy']) || $firewall['lazy']);
  286.             $context = new ChildDefinition($isLazy 'security.firewall.lazy_context' 'security.firewall.context');
  287.             $context $container->setDefinition($contextId$context);
  288.             $context
  289.                 ->replaceArgument(0, new IteratorArgument($listeners))
  290.                 ->replaceArgument(1$exceptionListener)
  291.                 ->replaceArgument(2$logoutListener)
  292.                 ->replaceArgument(3, new Reference($configId))
  293.             ;
  294.             $contextRefs[$contextId] = new Reference($contextId);
  295.             $map[$contextId] = $matcher;
  296.         }
  297.         $container
  298.             ->getDefinition('security.helper')
  299.             ->replaceArgument(1$authenticators)
  300.         ;
  301.         $container->setAlias('security.firewall.context_locator', (string) ServiceLocatorTagPass::register($container$contextRefs));
  302.         $mapDef->replaceArgument(0, new Reference('security.firewall.context_locator'));
  303.         $mapDef->replaceArgument(1, new IteratorArgument($map));
  304.         // register an autowire alias for the UserCheckerInterface if no custom user checker service is configured
  305.         if (!$customUserChecker) {
  306.             $container->setAlias(UserCheckerInterface::class, new Alias('security.user_checker'false));
  307.         }
  308.     }
  309.     private function createFirewall(ContainerBuilder $containerstring $id, array $firewall, array &$authenticationProviders, array $providerIdsstring $configId): array
  310.     {
  311.         $config $container->setDefinition($configId, new ChildDefinition('security.firewall.config'));
  312.         $config->replaceArgument(0$id);
  313.         $config->replaceArgument(1$firewall['user_checker']);
  314.         // Matcher
  315.         $matcher null;
  316.         if (isset($firewall['request_matcher'])) {
  317.             $matcher = new Reference($firewall['request_matcher']);
  318.         } elseif (isset($firewall['pattern']) || isset($firewall['host'])) {
  319.             $pattern $firewall['pattern'] ?? null;
  320.             $host $firewall['host'] ?? null;
  321.             $methods $firewall['methods'] ?? [];
  322.             $matcher $this->createRequestMatcher($container$pattern$hostnull$methods);
  323.         }
  324.         $config->replaceArgument(2$matcher ? (string) $matcher null);
  325.         $config->replaceArgument(3$firewall['security']);
  326.         // Security disabled?
  327.         if (false === $firewall['security']) {
  328.             return [$matcher, [], nullnull, []];
  329.         }
  330.         $config->replaceArgument(4$firewall['stateless']);
  331.         $firewallEventDispatcherId 'security.event_dispatcher.'.$id;
  332.         // Provider id (must be configured explicitly per firewall/authenticator if more than one provider is set)
  333.         $defaultProvider null;
  334.         if (isset($firewall['provider'])) {
  335.             if (!isset($providerIds[$normalizedName str_replace('-''_'$firewall['provider'])])) {
  336.                 throw new InvalidConfigurationException(sprintf('Invalid firewall "%s": user provider "%s" not found.'$id$firewall['provider']));
  337.             }
  338.             $defaultProvider $providerIds[$normalizedName];
  339.             $container->setDefinition('security.listener.'.$id.'.user_provider', new ChildDefinition('security.listener.user_provider.abstract'))
  340.                 ->addTag('kernel.event_listener', ['dispatcher' => $firewallEventDispatcherId'event' => CheckPassportEvent::class, 'priority' => 2048'method' => 'checkPassport'])
  341.                 ->replaceArgument(0, new Reference($defaultProvider));
  342.         } elseif (=== \count($providerIds)) {
  343.             $defaultProvider reset($providerIds);
  344.         }
  345.         $config->replaceArgument(5$defaultProvider);
  346.         // Register Firewall-specific event dispatcher
  347.         $container->register($firewallEventDispatcherIdEventDispatcher::class)
  348.             ->addTag('event_dispatcher.dispatcher', ['name' => $firewallEventDispatcherId]);
  349.         $eventDispatcherLocator $container->getDefinition('security.firewall.event_dispatcher_locator');
  350.         $eventDispatcherLocator
  351.             ->replaceArgument(0array_merge($eventDispatcherLocator->getArgument(0), [
  352.                 $id => new ServiceClosureArgument(new Reference($firewallEventDispatcherId)),
  353.             ]))
  354.         ;
  355.         // Register Firewall-specific chained user checker
  356.         $container->register('security.user_checker.chain.'.$idChainUserChecker::class)
  357.             ->addArgument(new TaggedIteratorArgument('security.user_checker.'.$id));
  358.         // Register listeners
  359.         $listeners = [];
  360.         $listenerKeys = [];
  361.         // Channel listener
  362.         $listeners[] = new Reference('security.channel_listener');
  363.         $contextKey null;
  364.         // Context serializer listener
  365.         if (false === $firewall['stateless']) {
  366.             $contextKey $firewall['context'] ?? $id;
  367.             $listeners[] = new Reference($this->createContextListener($container$contextKey$firewallEventDispatcherId));
  368.             $sessionStrategyId 'security.authentication.session_strategy';
  369.             $container
  370.                 ->setDefinition('security.listener.session.'.$id, new ChildDefinition('security.listener.session'))
  371.                 ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
  372.         } else {
  373.             $sessionStrategyId 'security.authentication.session_strategy_noop';
  374.         }
  375.         $container->setAlias(new Alias('security.authentication.session_strategy.'.$idfalse), $sessionStrategyId);
  376.         $config->replaceArgument(6$contextKey);
  377.         // Logout listener
  378.         $logoutListenerId null;
  379.         if (isset($firewall['logout'])) {
  380.             $logoutListenerId 'security.logout_listener.'.$id;
  381.             $logoutListener $container->setDefinition($logoutListenerId, new ChildDefinition('security.logout_listener'));
  382.             $logoutListener->replaceArgument(2, new Reference($firewallEventDispatcherId));
  383.             $logoutListener->replaceArgument(3, [
  384.                 'csrf_parameter' => $firewall['logout']['csrf_parameter'],
  385.                 'csrf_token_id' => $firewall['logout']['csrf_token_id'],
  386.                 'logout_path' => $firewall['logout']['path'],
  387.             ]);
  388.             $container->setDefinition('security.logout.listener.default.'.$id, new ChildDefinition('security.logout.listener.default'))
  389.                 ->replaceArgument(1$firewall['logout']['target'])
  390.                 ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
  391.             // add CSRF provider
  392.             if ($firewall['logout']['enable_csrf']) {
  393.                 $logoutListener->addArgument(new Reference($firewall['logout']['csrf_token_manager']));
  394.             }
  395.             // add session logout listener
  396.             if (true === $firewall['logout']['invalidate_session'] && false === $firewall['stateless']) {
  397.                 $container->setDefinition('security.logout.listener.session.'.$id, new ChildDefinition('security.logout.listener.session'))
  398.                     ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
  399.             }
  400.             // add cookie logout listener
  401.             if (\count($firewall['logout']['delete_cookies']) > 0) {
  402.                 $container->setDefinition('security.logout.listener.cookie_clearing.'.$id, new ChildDefinition('security.logout.listener.cookie_clearing'))
  403.                     ->addArgument($firewall['logout']['delete_cookies'])
  404.                     ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
  405.             }
  406.             // add clear site data listener
  407.             if ($firewall['logout']['clear_site_data'] ?? false) {
  408.                 $container->setDefinition('security.logout.listener.clear_site_data.'.$id, new ChildDefinition('security.logout.listener.clear_site_data'))
  409.                     ->addArgument($firewall['logout']['clear_site_data'])
  410.                     ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
  411.             }
  412.             // register with LogoutUrlGenerator
  413.             $container
  414.                 ->getDefinition('security.logout_url_generator')
  415.                 ->addMethodCall('registerListener', [
  416.                     $id,
  417.                     $firewall['logout']['path'],
  418.                     $firewall['logout']['csrf_token_id'],
  419.                     $firewall['logout']['csrf_parameter'],
  420.                     isset($firewall['logout']['csrf_token_manager']) ? new Reference($firewall['logout']['csrf_token_manager']) : null,
  421.                     false === $firewall['stateless'] && isset($firewall['context']) ? $firewall['context'] : null,
  422.                 ])
  423.             ;
  424.             $config->replaceArgument(12$firewall['logout']);
  425.         }
  426.         // Determine default entry point
  427.         $configuredEntryPoint $firewall['entry_point'] ?? null;
  428.         // Authentication listeners
  429.         $firewallAuthenticationProviders = [];
  430.         [$authListeners$defaultEntryPoint] = $this->createAuthenticationListeners($container$id$firewall$firewallAuthenticationProviders$defaultProvider$providerIds$configuredEntryPoint);
  431.         // $configuredEntryPoint is resolved into a service ID and stored in $defaultEntryPoint
  432.         $configuredEntryPoint $defaultEntryPoint;
  433.         // authenticator manager
  434.         $authenticators array_map(fn ($id) => new Reference($id), $firewallAuthenticationProviders);
  435.         $container
  436.             ->setDefinition($managerId 'security.authenticator.manager.'.$id, new ChildDefinition('security.authenticator.manager'))
  437.             ->replaceArgument(0$authenticators)
  438.             ->replaceArgument(2, new Reference($firewallEventDispatcherId))
  439.             ->replaceArgument(3$id)
  440.             ->replaceArgument(7$firewall['required_badges'] ?? [])
  441.             ->addTag('monolog.logger', ['channel' => 'security'])
  442.         ;
  443.         $managerLocator $container->getDefinition('security.authenticator.managers_locator');
  444.         $managerLocator->replaceArgument(0array_merge($managerLocator->getArgument(0), [$id => new ServiceClosureArgument(new Reference($managerId))]));
  445.         // authenticator manager listener
  446.         $container
  447.             ->setDefinition('security.firewall.authenticator.'.$id, new ChildDefinition('security.firewall.authenticator'))
  448.             ->replaceArgument(0, new Reference($managerId))
  449.         ;
  450.         if ($container->hasDefinition('debug.security.firewall')) {
  451.             $container
  452.                 ->register('debug.security.firewall.authenticator.'.$idTraceableAuthenticatorManagerListener::class)
  453.                 ->setDecoratedService('security.firewall.authenticator.'.$id)
  454.                 ->setArguments([new Reference('debug.security.firewall.authenticator.'.$id.'.inner')])
  455.                 ->addTag('kernel.reset', ['method' => 'reset'])
  456.             ;
  457.         }
  458.         // user checker listener
  459.         $container
  460.             ->setDefinition('security.listener.user_checker.'.$id, new ChildDefinition('security.listener.user_checker'))
  461.             ->replaceArgument(0, new Reference('security.user_checker.'.$id))
  462.             ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
  463.         $listeners[] = new Reference('security.firewall.authenticator.'.$id);
  464.         // Add authenticators to the debug:firewall command
  465.         if ($container->hasDefinition('security.command.debug_firewall')) {
  466.             $debugCommand $container->getDefinition('security.command.debug_firewall');
  467.             $debugCommand->replaceArgument(3array_merge($debugCommand->getArgument(3), [$id => $authenticators]));
  468.         }
  469.         $config->replaceArgument(7$configuredEntryPoint ?: $defaultEntryPoint);
  470.         $listeners array_merge($listeners$authListeners);
  471.         // Switch user listener
  472.         if (isset($firewall['switch_user'])) {
  473.             $listenerKeys[] = 'switch_user';
  474.             $listeners[] = new Reference($this->createSwitchUserListener($container$id$firewall['switch_user'], $defaultProvider$firewall['stateless']));
  475.         }
  476.         // Access listener
  477.         $listeners[] = new Reference('security.access_listener');
  478.         // Exception listener
  479.         $exceptionListener = new Reference($this->createExceptionListener($container$firewall$id$configuredEntryPoint ?: $defaultEntryPoint$firewall['stateless']));
  480.         $config->replaceArgument(8$firewall['access_denied_handler'] ?? null);
  481.         $config->replaceArgument(9$firewall['access_denied_url'] ?? null);
  482.         $container->setAlias('security.user_checker.'.$id, new Alias($firewall['user_checker'], false));
  483.         foreach ($this->getSortedFactories() as $factory) {
  484.             $key str_replace('-''_'$factory->getKey());
  485.             if ('custom_authenticators' !== $key && \array_key_exists($key$firewall)) {
  486.                 $listenerKeys[] = $key;
  487.             }
  488.         }
  489.         if ($firewall['custom_authenticators'] ?? false) {
  490.             foreach ($firewall['custom_authenticators'] as $customAuthenticatorId) {
  491.                 $listenerKeys[] = $customAuthenticatorId;
  492.             }
  493.         }
  494.         $config->replaceArgument(10$listenerKeys);
  495.         $config->replaceArgument(11$firewall['switch_user'] ?? null);
  496.         return [$matcher$listeners$exceptionListenernull !== $logoutListenerId ? new Reference($logoutListenerId) : null$firewallAuthenticationProviders];
  497.     }
  498.     private function createContextListener(ContainerBuilder $containerstring $contextKey, ?string $firewallEventDispatcherId): string
  499.     {
  500.         if (isset($this->contextListeners[$contextKey])) {
  501.             return $this->contextListeners[$contextKey];
  502.         }
  503.         $listenerId 'security.context_listener.'.\count($this->contextListeners);
  504.         $listener $container->setDefinition($listenerId, new ChildDefinition('security.context_listener'));
  505.         $listener->replaceArgument(2$contextKey);
  506.         if (null !== $firewallEventDispatcherId) {
  507.             $listener->replaceArgument(4, new Reference($firewallEventDispatcherId));
  508.             $listener->addTag('kernel.event_listener', ['event' => KernelEvents::RESPONSE'method' => 'onKernelResponse']);
  509.         }
  510.         return $this->contextListeners[$contextKey] = $listenerId;
  511.     }
  512.     private function createAuthenticationListeners(ContainerBuilder $containerstring $id, array $firewall, array &$authenticationProviders, ?string $defaultProvider, array $providerIds, ?string $defaultEntryPoint): array
  513.     {
  514.         $listeners = [];
  515.         $entryPoints = [];
  516.         foreach ($this->getSortedFactories() as $factory) {
  517.             $key str_replace('-''_'$factory->getKey());
  518.             if (isset($firewall[$key])) {
  519.                 $userProvider $this->getUserProvider($container$id$firewall$key$defaultProvider$providerIds);
  520.                 if (!$factory instanceof AuthenticatorFactoryInterface) {
  521.                     throw new InvalidConfigurationException(sprintf('Authenticator factory "%s" ("%s") must implement "%s".'get_debug_type($factory), $keyAuthenticatorFactoryInterface::class));
  522.                 }
  523.                 if (null === $userProvider && !$factory instanceof StatelessAuthenticatorFactoryInterface) {
  524.                     $userProvider $this->createMissingUserProvider($container$id$key);
  525.                 }
  526.                 $authenticators $factory->createAuthenticator($container$id$firewall[$key], $userProvider);
  527.                 if (\is_array($authenticators)) {
  528.                     foreach ($authenticators as $authenticator) {
  529.                         $authenticationProviders[] = $authenticator;
  530.                         $entryPoints[] = $authenticator;
  531.                     }
  532.                 } else {
  533.                     $authenticationProviders[] = $authenticators;
  534.                     $entryPoints[$key] = $authenticators;
  535.                 }
  536.                 if ($factory instanceof FirewallListenerFactoryInterface) {
  537.                     $firewallListenerIds $factory->createListeners($container$id$firewall[$key]);
  538.                     foreach ($firewallListenerIds as $firewallListenerId) {
  539.                         $listeners[] = new Reference($firewallListenerId);
  540.                     }
  541.                 }
  542.             }
  543.         }
  544.         // the actual entry point is configured by the RegisterEntryPointPass
  545.         $container->setParameter('security.'.$id.'._indexed_authenticators'$entryPoints);
  546.         return [$listeners$defaultEntryPoint];
  547.     }
  548.     private function getUserProvider(ContainerBuilder $containerstring $id, array $firewallstring $factoryKey, ?string $defaultProvider, array $providerIds): ?string
  549.     {
  550.         if (isset($firewall[$factoryKey]['provider'])) {
  551.             if (!isset($providerIds[$normalizedName str_replace('-''_'$firewall[$factoryKey]['provider'])])) {
  552.                 throw new InvalidConfigurationException(sprintf('Invalid firewall "%s": user provider "%s" not found.'$id$firewall[$factoryKey]['provider']));
  553.             }
  554.             return $providerIds[$normalizedName];
  555.         }
  556.         if ($defaultProvider) {
  557.             return $defaultProvider;
  558.         }
  559.         if (!$providerIds) {
  560.             if ($firewall['stateless'] ?? false) {
  561.                 return null;
  562.             }
  563.             return $this->createMissingUserProvider($container$id$factoryKey);
  564.         }
  565.         if ('remember_me' === $factoryKey || 'anonymous' === $factoryKey || 'custom_authenticators' === $factoryKey) {
  566.             if ('custom_authenticators' === $factoryKey) {
  567.                 trigger_deprecation('symfony/security-bundle''5.4''Not configuring explicitly the provider for the "%s" firewall is deprecated because it\'s ambiguous as there is more than one registered provider. Set the "provider" key to one of the configured providers, even if your custom authenticators don\'t use it.'$id);
  568.             }
  569.             return 'security.user_providers';
  570.         }
  571.         throw new InvalidConfigurationException(sprintf('Not configuring explicitly the provider for the "%s" authenticator on "%s" firewall is ambiguous as there is more than one registered provider.'$factoryKey$id));
  572.     }
  573.     private function createMissingUserProvider(ContainerBuilder $containerstring $idstring $factoryKey): string
  574.     {
  575.         $userProvider sprintf('security.user.provider.missing.%s'$factoryKey);
  576.         $container->setDefinition(
  577.             $userProvider,
  578.             (new ChildDefinition('security.user.provider.missing'))->replaceArgument(0$id)
  579.         );
  580.         return $userProvider;
  581.     }
  582.     private function createHashers(array $hashersContainerBuilder $container): void
  583.     {
  584.         $hasherMap = [];
  585.         foreach ($hashers as $class => $hasher) {
  586.             $hasherMap[$class] = $this->createHasher($hasher);
  587.         }
  588.         $container
  589.             ->getDefinition('security.password_hasher_factory')
  590.             ->setArguments([$hasherMap])
  591.         ;
  592.     }
  593.     /**
  594.      * @param array<string, mixed> $config
  595.      *
  596.      * @return Reference|array<string, mixed>
  597.      */
  598.     private function createHasher(array $config): Reference|array
  599.     {
  600.         // a custom hasher service
  601.         if (isset($config['id'])) {
  602.             return $config['migrate_from'] ?? false ? [
  603.                 'instance' => new Reference($config['id']),
  604.                 'migrate_from' => $config['migrate_from'],
  605.             ] : new Reference($config['id']);
  606.         }
  607.         if ($config['migrate_from'] ?? false) {
  608.             return $config;
  609.         }
  610.         // plaintext hasher
  611.         if ('plaintext' === $config['algorithm']) {
  612.             $arguments = [$config['ignore_case']];
  613.             return [
  614.                 'class' => PlaintextPasswordHasher::class,
  615.                 'arguments' => $arguments,
  616.             ];
  617.         }
  618.         // pbkdf2 hasher
  619.         if ('pbkdf2' === $config['algorithm']) {
  620.             return [
  621.                 'class' => Pbkdf2PasswordHasher::class,
  622.                 'arguments' => [
  623.                     $config['hash_algorithm'],
  624.                     $config['encode_as_base64'],
  625.                     $config['iterations'],
  626.                     $config['key_length'],
  627.                 ],
  628.             ];
  629.         }
  630.         // bcrypt hasher
  631.         if ('bcrypt' === $config['algorithm']) {
  632.             $config['algorithm'] = 'native';
  633.             $config['native_algorithm'] = \PASSWORD_BCRYPT;
  634.             return $this->createHasher($config);
  635.         }
  636.         // Argon2i hasher
  637.         if ('argon2i' === $config['algorithm']) {
  638.             if (SodiumPasswordHasher::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
  639.                 $config['algorithm'] = 'sodium';
  640.             } elseif (\defined('PASSWORD_ARGON2I')) {
  641.                 $config['algorithm'] = 'native';
  642.                 $config['native_algorithm'] = \PASSWORD_ARGON2I;
  643.             } else {
  644.                 throw new InvalidConfigurationException(sprintf('Algorithm "argon2i" is not available. Either use "%s" or upgrade to PHP 7.2+ instead.'\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? 'argon2id", "auto' 'auto'));
  645.             }
  646.             return $this->createHasher($config);
  647.         }
  648.         if ('argon2id' === $config['algorithm']) {
  649.             if (($hasSodium SodiumPasswordHasher::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
  650.                 $config['algorithm'] = 'sodium';
  651.             } elseif (\defined('PASSWORD_ARGON2ID')) {
  652.                 $config['algorithm'] = 'native';
  653.                 $config['native_algorithm'] = \PASSWORD_ARGON2ID;
  654.             } else {
  655.                 throw new InvalidConfigurationException(sprintf('Algorithm "argon2id" is not available. Either use "%s", upgrade to PHP 7.3+ or use libsodium 1.0.15+ instead.'\defined('PASSWORD_ARGON2I') || $hasSodium 'argon2i", "auto' 'auto'));
  656.             }
  657.             return $this->createHasher($config);
  658.         }
  659.         if ('native' === $config['algorithm']) {
  660.             return [
  661.                 'class' => NativePasswordHasher::class,
  662.                 'arguments' => [
  663.                     $config['time_cost'],
  664.                     (($config['memory_cost'] ?? 0) << 10) ?: null,
  665.                     $config['cost'],
  666.                 ] + (isset($config['native_algorithm']) ? [=> $config['native_algorithm']] : []),
  667.             ];
  668.         }
  669.         if ('sodium' === $config['algorithm']) {
  670.             if (!SodiumPasswordHasher::isSupported()) {
  671.                 throw new InvalidConfigurationException('Libsodium is not available. Install the sodium extension or use "auto" instead.');
  672.             }
  673.             return [
  674.                 'class' => SodiumPasswordHasher::class,
  675.                 'arguments' => [
  676.                     $config['time_cost'],
  677.                     (($config['memory_cost'] ?? 0) << 10) ?: null,
  678.                 ],
  679.             ];
  680.         }
  681.         // run-time configured hasher
  682.         return $config;
  683.     }
  684.     // Parses user providers and returns an array of their ids
  685.     private function createUserProviders(array $configContainerBuilder $container): array
  686.     {
  687.         $providerIds = [];
  688.         foreach ($config['providers'] as $name => $provider) {
  689.             $id $this->createUserDaoProvider($name$provider$container);
  690.             $providerIds[str_replace('-''_'$name)] = $id;
  691.         }
  692.         return $providerIds;
  693.     }
  694.     // Parses a <provider> tag and returns the id for the related user provider service
  695.     private function createUserDaoProvider(string $name, array $providerContainerBuilder $container): string
  696.     {
  697.         $name $this->getUserProviderId($name);
  698.         // Doctrine Entity and In-memory DAO provider are managed by factories
  699.         foreach ($this->userProviderFactories as $factory) {
  700.             $key str_replace('-''_'$factory->getKey());
  701.             if (!empty($provider[$key])) {
  702.                 $factory->create($container$name$provider[$key]);
  703.                 return $name;
  704.             }
  705.         }
  706.         // Existing DAO service provider
  707.         if (isset($provider['id'])) {
  708.             $container->setAlias($name, new Alias($provider['id'], false));
  709.             return $provider['id'];
  710.         }
  711.         // Chain provider
  712.         if (isset($provider['chain'])) {
  713.             $providers = [];
  714.             foreach ($provider['chain']['providers'] as $providerName) {
  715.                 $providers[] = new Reference($this->getUserProviderId($providerName));
  716.             }
  717.             $container
  718.                 ->setDefinition($name, new ChildDefinition('security.user.provider.chain'))
  719.                 ->addArgument(new IteratorArgument($providers));
  720.             return $name;
  721.         }
  722.         throw new InvalidConfigurationException(sprintf('Unable to create definition for "%s" user provider.'$name));
  723.     }
  724.     private function getUserProviderId(string $name): string
  725.     {
  726.         return 'security.user.provider.concrete.'.strtolower($name);
  727.     }
  728.     private function createExceptionListener(ContainerBuilder $container, array $configstring $id, ?string $defaultEntryPointbool $stateless): string
  729.     {
  730.         $exceptionListenerId 'security.exception_listener.'.$id;
  731.         $listener $container->setDefinition($exceptionListenerId, new ChildDefinition('security.exception_listener'));
  732.         $listener->replaceArgument(3$id);
  733.         $listener->replaceArgument(4null === $defaultEntryPoint null : new Reference($defaultEntryPoint));
  734.         $listener->replaceArgument(8$stateless);
  735.         // access denied handler setup
  736.         if (isset($config['access_denied_handler'])) {
  737.             $listener->replaceArgument(6, new Reference($config['access_denied_handler']));
  738.         } elseif (isset($config['access_denied_url'])) {
  739.             $listener->replaceArgument(5$config['access_denied_url']);
  740.         }
  741.         return $exceptionListenerId;
  742.     }
  743.     private function createSwitchUserListener(ContainerBuilder $containerstring $id, array $config, ?string $defaultProviderbool $stateless): string
  744.     {
  745.         $userProvider = isset($config['provider']) ? $this->getUserProviderId($config['provider']) : $defaultProvider;
  746.         if (!$userProvider) {
  747.             throw new InvalidConfigurationException(sprintf('Not configuring explicitly the provider for the "switch_user" listener on "%s" firewall is ambiguous as there is more than one registered provider.'$id));
  748.         }
  749.         if ($stateless && null !== $config['target_route']) {
  750.             throw new InvalidConfigurationException(sprintf('Cannot set a "target_route" for the "switch_user" listener on the "%s" firewall as it is stateless.'$id));
  751.         }
  752.         $switchUserListenerId 'security.authentication.switchuser_listener.'.$id;
  753.         $listener $container->setDefinition($switchUserListenerId, new ChildDefinition('security.authentication.switchuser_listener'));
  754.         $listener->replaceArgument(1, new Reference($userProvider));
  755.         $listener->replaceArgument(2, new Reference('security.user_checker.'.$id));
  756.         $listener->replaceArgument(3$id);
  757.         $listener->replaceArgument(6$config['parameter']);
  758.         $listener->replaceArgument(7$config['role']);
  759.         $listener->replaceArgument(9$stateless);
  760.         $listener->replaceArgument(11$config['target_route']);
  761.         return $switchUserListenerId;
  762.     }
  763.     private function createExpression(ContainerBuilder $containerstring $expression): Reference
  764.     {
  765.         if (isset($this->expressions[$id '.security.expression.'.ContainerBuilder::hash($expression)])) {
  766.             return $this->expressions[$id];
  767.         }
  768.         if (!$container::willBeAvailable('symfony/expression-language'ExpressionLanguage::class, ['symfony/security-bundle'])) {
  769.             throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".');
  770.         }
  771.         $container
  772.             ->register($idExpression::class)
  773.             ->addArgument($expression)
  774.         ;
  775.         return $this->expressions[$id] = new Reference($id);
  776.     }
  777.     private function createRequestMatcher(ContainerBuilder $container, ?string $path null, ?string $host null, ?int $port null, array $methods = [], ?array $ips null, array $attributes = []): Reference
  778.     {
  779.         if ($methods) {
  780.             $methods array_map('strtoupper'$methods);
  781.         }
  782.         if ($ips) {
  783.             foreach ($ips as $ip) {
  784.                 $container->resolveEnvPlaceholders($ipnull$usedEnvs);
  785.                 if (!$usedEnvs && !$this->isValidIps($ip)) {
  786.                     throw new \LogicException(sprintf('The given value "%s" in the "security.access_control" config option is not a valid IP address.'$ip));
  787.                 }
  788.                 $usedEnvs null;
  789.             }
  790.         }
  791.         $id '.security.request_matcher.'.ContainerBuilder::hash([ChainRequestMatcher::class, $path$host$port$methods$ips$attributes]);
  792.         if (isset($this->requestMatchers[$id])) {
  793.             return $this->requestMatchers[$id];
  794.         }
  795.         $arguments = [];
  796.         if ($methods) {
  797.             if (!$container->hasDefinition($lid '.security.request_matcher.'.ContainerBuilder::hash([MethodRequestMatcher::class, $methods]))) {
  798.                 $container->register($lidMethodRequestMatcher::class)->setArguments([$methods]);
  799.             }
  800.             $arguments[] = new Reference($lid);
  801.         }
  802.         if ($path) {
  803.             if (!$container->hasDefinition($lid '.security.request_matcher.'.ContainerBuilder::hash([PathRequestMatcher::class, $path]))) {
  804.                 $container->register($lidPathRequestMatcher::class)->setArguments([$path]);
  805.             }
  806.             $arguments[] = new Reference($lid);
  807.         }
  808.         if ($host) {
  809.             if (!$container->hasDefinition($lid '.security.request_matcher.'.ContainerBuilder::hash([HostRequestMatcher::class, $host]))) {
  810.                 $container->register($lidHostRequestMatcher::class)->setArguments([$host]);
  811.             }
  812.             $arguments[] = new Reference($lid);
  813.         }
  814.         if ($ips) {
  815.             if (!$container->hasDefinition($lid '.security.request_matcher.'.ContainerBuilder::hash([IpsRequestMatcher::class, $ips]))) {
  816.                 $container->register($lidIpsRequestMatcher::class)->setArguments([$ips]);
  817.             }
  818.             $arguments[] = new Reference($lid);
  819.         }
  820.         if ($attributes) {
  821.             if (!$container->hasDefinition($lid '.security.request_matcher.'.ContainerBuilder::hash([AttributesRequestMatcher::class, $attributes]))) {
  822.                 $container->register($lidAttributesRequestMatcher::class)->setArguments([$attributes]);
  823.             }
  824.             $arguments[] = new Reference($lid);
  825.         }
  826.         if ($port) {
  827.             if (!$container->hasDefinition($lid '.security.request_matcher.'.ContainerBuilder::hash([PortRequestMatcher::class, $port]))) {
  828.                 $container->register($lidPortRequestMatcher::class)->setArguments([$port]);
  829.             }
  830.             $arguments[] = new Reference($lid);
  831.         }
  832.         $container
  833.             ->register($idChainRequestMatcher::class)
  834.             ->setArguments([$arguments])
  835.         ;
  836.         return $this->requestMatchers[$id] = new Reference($id);
  837.     }
  838.     public function addAuthenticatorFactory(AuthenticatorFactoryInterface $factory): void
  839.     {
  840.         $this->factories[] = [$factory->getPriority(), $factory];
  841.         $this->sortedFactories = [];
  842.     }
  843.     public function addUserProviderFactory(UserProviderFactoryInterface $factory): void
  844.     {
  845.         $this->userProviderFactories[] = $factory;
  846.     }
  847.     public function getXsdValidationBasePath(): string|false
  848.     {
  849.         return __DIR__.'/../Resources/config/schema';
  850.     }
  851.     public function getNamespace(): string
  852.     {
  853.         return 'http://symfony.com/schema/dic/security';
  854.     }
  855.     public function getConfiguration(array $configContainerBuilder $container): ?ConfigurationInterface
  856.     {
  857.         // first assemble the factories
  858.         return new MainConfiguration($this->getSortedFactories(), $this->userProviderFactories);
  859.     }
  860.     private function isValidIps(string|array $ips): bool
  861.     {
  862.         $ipsList array_reduce((array) $ips, fn ($ips$ip) => array_merge($ipspreg_split('/\s*,\s*/'$ip)), []);
  863.         if (!$ipsList) {
  864.             return false;
  865.         }
  866.         foreach ($ipsList as $cidr) {
  867.             if (!$this->isValidIp($cidr)) {
  868.                 return false;
  869.             }
  870.         }
  871.         return true;
  872.     }
  873.     private function isValidIp(string $cidr): bool
  874.     {
  875.         $cidrParts explode('/'$cidr);
  876.         if (=== \count($cidrParts)) {
  877.             return false !== filter_var($cidrParts[0], \FILTER_VALIDATE_IP);
  878.         }
  879.         $ip $cidrParts[0];
  880.         $netmask $cidrParts[1];
  881.         if (!ctype_digit($netmask)) {
  882.             return false;
  883.         }
  884.         if (filter_var($ip\FILTER_VALIDATE_IP\FILTER_FLAG_IPV4)) {
  885.             return $netmask <= 32;
  886.         }
  887.         if (filter_var($ip\FILTER_VALIDATE_IP\FILTER_FLAG_IPV6)) {
  888.             return $netmask <= 128;
  889.         }
  890.         return false;
  891.     }
  892.     /**
  893.      * @return array<int, AuthenticatorFactoryInterface>
  894.      */
  895.     private function getSortedFactories(): array
  896.     {
  897.         if (!$this->sortedFactories) {
  898.             $factories = [];
  899.             foreach ($this->factories as $i => $factory) {
  900.                 $factories[] = array_merge($factory, [$i]);
  901.             }
  902.             usort($factories, fn ($a$b) => $b[0] <=> $a[0] ?: $a[2] <=> $b[2]);
  903.             $this->sortedFactories array_column($factories1);
  904.         }
  905.         return $this->sortedFactories;
  906.     }
  907.     private function createLogoutUrisParameter(array $firewallsConfigContainerBuilder $container): void
  908.     {
  909.         $logoutUris = [];
  910.         foreach ($firewallsConfig as $name => $config) {
  911.             if (!$logoutPath $config['logout']['path'] ?? null) {
  912.                 continue;
  913.             }
  914.             if ('/' === $logoutPath[0]) {
  915.                 $logoutUris[$name] = $logoutPath;
  916.             }
  917.         }
  918.         $container->setParameter('security.logout_uris'$logoutUris);
  919.     }
  920. }