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

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