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

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