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

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.                 ;
  457.             }
  458.             // user checker listener
  459.             $container
  460.                 ->setDefinition('security.listener.user_checker.'.$id, new ChildDefinition('security.listener.user_checker'))
  461.                 ->replaceArgument(0, new Reference('security.user_checker.'.$id))
  462.                 ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
  463.             $listeners[] = new Reference('security.firewall.authenticator.'.$id);
  464.             // Add authenticators to the debug:firewall command
  465.             if ($container->hasDefinition('security.command.debug_firewall')) {
  466.                 $debugCommand $container->getDefinition('security.command.debug_firewall');
  467.                 $debugCommand->replaceArgument(3array_merge($debugCommand->getArgument(3), [$id => $authenticators]));
  468.             }
  469.         }
  470.         $config->replaceArgument(7$configuredEntryPoint ?: $defaultEntryPoint);
  471.         $listeners array_merge($listeners$authListeners);
  472.         // Switch user listener
  473.         if (isset($firewall['switch_user'])) {
  474.             $listenerKeys[] = 'switch_user';
  475.             $listeners[] = new Reference($this->createSwitchUserListener($container$id$firewall['switch_user'], $defaultProvider$firewall['stateless']));
  476.         }
  477.         // Access listener
  478.         $listeners[] = new Reference('security.access_listener');
  479.         // Exception listener
  480.         $exceptionListener = new Reference($this->createExceptionListener($container$firewall$id$configuredEntryPoint ?: $defaultEntryPoint$firewall['stateless']));
  481.         $config->replaceArgument(8$firewall['access_denied_handler'] ?? null);
  482.         $config->replaceArgument(9$firewall['access_denied_url'] ?? null);
  483.         $container->setAlias('security.user_checker.'.$id, new Alias($firewall['user_checker'], false));
  484.         foreach ($this->getSortedFactories() as $factory) {
  485.             $key str_replace('-''_'$factory->getKey());
  486.             if ('custom_authenticators' !== $key && \array_key_exists($key$firewall)) {
  487.                 $listenerKeys[] = $key;
  488.             }
  489.         }
  490.         if ($firewall['custom_authenticators'] ?? false) {
  491.             foreach ($firewall['custom_authenticators'] as $customAuthenticatorId) {
  492.                 $listenerKeys[] = $customAuthenticatorId;
  493.             }
  494.         }
  495.         $config->replaceArgument(10$listenerKeys);
  496.         $config->replaceArgument(11$firewall['switch_user'] ?? null);
  497.         return [$matcher$listeners$exceptionListenernull !== $logoutListenerId ? new Reference($logoutListenerId) : null];
  498.     }
  499.     private function createContextListener(ContainerBuilder $containerstring $contextKey, ?string $firewallEventDispatcherId)
  500.     {
  501.         if (isset($this->contextListeners[$contextKey])) {
  502.             return $this->contextListeners[$contextKey];
  503.         }
  504.         $listenerId 'security.context_listener.'.\count($this->contextListeners);
  505.         $listener $container->setDefinition($listenerId, new ChildDefinition('security.context_listener'));
  506.         $listener->replaceArgument(2$contextKey);
  507.         if (null !== $firewallEventDispatcherId) {
  508.             $listener->replaceArgument(4, new Reference($firewallEventDispatcherId));
  509.             $listener->addTag('kernel.event_listener', ['event' => KernelEvents::RESPONSE'method' => 'onKernelResponse']);
  510.         }
  511.         return $this->contextListeners[$contextKey] = $listenerId;
  512.     }
  513.     private function createAuthenticationListeners(ContainerBuilder $containerstring $id, array $firewall, array &$authenticationProviders, ?string $defaultProvider, array $providerIds, ?string $defaultEntryPointstring $contextListenerId null)
  514.     {
  515.         $listeners = [];
  516.         $hasListeners false;
  517.         $entryPoints = [];
  518.         foreach ($this->getSortedFactories() as $factory) {
  519.             $key str_replace('-''_'$factory->getKey());
  520.             if (isset($firewall[$key])) {
  521.                 $userProvider $this->getUserProvider($container$id$firewall$key$defaultProvider$providerIds$contextListenerId);
  522.                 if ($this->authenticatorManagerEnabled) {
  523.                     if (!$factory instanceof AuthenticatorFactoryInterface) {
  524.                         throw new InvalidConfigurationException(sprintf('Cannot configure AuthenticatorManager as "%s" authentication does not support it, set "security.enable_authenticator_manager" to `false`.'$key));
  525.                     }
  526.                     $authenticators $factory->createAuthenticator($container$id$firewall[$key], $userProvider);
  527.                     if (\is_array($authenticators)) {
  528.                         foreach ($authenticators as $authenticator) {
  529.                             $authenticationProviders[] = $authenticator;
  530.                             $entryPoints[] = $authenticator;
  531.                         }
  532.                     } else {
  533.                         $authenticationProviders[] = $authenticators;
  534.                         $entryPoints[$key] = $authenticators;
  535.                     }
  536.                 } else {
  537.                     [$provider$listenerId$defaultEntryPoint] = $factory->create($container$id$firewall[$key], $userProvider$defaultEntryPoint);
  538.                     $listeners[] = new Reference($listenerId);
  539.                     $authenticationProviders[] = $provider;
  540.                 }
  541.                 if ($factory instanceof FirewallListenerFactoryInterface) {
  542.                     $firewallListenerIds $factory->createListeners($container$id$firewall[$key]);
  543.                     foreach ($firewallListenerIds as $firewallListenerId) {
  544.                         $listeners[] = new Reference($firewallListenerId);
  545.                     }
  546.                 }
  547.                 $hasListeners true;
  548.             }
  549.         }
  550.         // the actual entry point is configured by the RegisterEntryPointPass
  551.         $container->setParameter('security.'.$id.'._indexed_authenticators'$entryPoints);
  552.         if (false === $hasListeners && !$this->authenticatorManagerEnabled) {
  553.             throw new InvalidConfigurationException(sprintf('No authentication listener registered for firewall "%s".'$id));
  554.         }
  555.         return [$listeners$defaultEntryPoint];
  556.     }
  557.     private function getUserProvider(ContainerBuilder $containerstring $id, array $firewallstring $factoryKey, ?string $defaultProvider, array $providerIds, ?string $contextListenerId): string
  558.     {
  559.         if (isset($firewall[$factoryKey]['provider'])) {
  560.             if (!isset($providerIds[$normalizedName str_replace('-''_'$firewall[$factoryKey]['provider'])])) {
  561.                 throw new InvalidConfigurationException(sprintf('Invalid firewall "%s": user provider "%s" not found.'$id$firewall[$factoryKey]['provider']));
  562.             }
  563.             return $providerIds[$normalizedName];
  564.         }
  565.         if ('remember_me' === $factoryKey && $contextListenerId) {
  566.             $container->getDefinition($contextListenerId)->addTag('security.remember_me_aware', ['id' => $id'provider' => 'none']);
  567.         }
  568.         if ($defaultProvider) {
  569.             return $defaultProvider;
  570.         }
  571.         if (!$providerIds) {
  572.             $userProvider sprintf('security.user.provider.missing.%s'$factoryKey);
  573.             $container->setDefinition(
  574.                 $userProvider,
  575.                 (new ChildDefinition('security.user.provider.missing'))->replaceArgument(0$id)
  576.             );
  577.             return $userProvider;
  578.         }
  579.         if ('remember_me' === $factoryKey || 'anonymous' === $factoryKey || 'custom_authenticators' === $factoryKey) {
  580.             if ('custom_authenticators' === $factoryKey) {
  581.                 trigger_deprecation('symfony/security-bundle''5.4''Not configuring explicitly the provider for the "%s" listener on "%s" firewall is deprecated because it\'s ambiguous as there is more than one registered provider.'$factoryKey$id);
  582.             }
  583.             return 'security.user_providers';
  584.         }
  585.         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));
  586.     }
  587.     private function createEncoders(array $encodersContainerBuilder $container)
  588.     {
  589.         $encoderMap = [];
  590.         foreach ($encoders as $class => $encoder) {
  591.             if (class_exists($class) && !is_a($classPasswordAuthenticatedUserInterface::class, true)) {
  592.                 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);
  593.             }
  594.             $encoderMap[$class] = $this->createEncoder($encoder);
  595.         }
  596.         $container
  597.             ->getDefinition('security.encoder_factory.generic')
  598.             ->setArguments([$encoderMap])
  599.         ;
  600.     }
  601.     private function createEncoder(array $config)
  602.     {
  603.         // a custom encoder service
  604.         if (isset($config['id'])) {
  605.             return new Reference($config['id']);
  606.         }
  607.         if ($config['migrate_from'] ?? false) {
  608.             return $config;
  609.         }
  610.         // plaintext encoder
  611.         if ('plaintext' === $config['algorithm']) {
  612.             $arguments = [$config['ignore_case']];
  613.             return [
  614.                 'class' => 'Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder',
  615.                 'arguments' => $arguments,
  616.             ];
  617.         }
  618.         // pbkdf2 encoder
  619.         if ('pbkdf2' === $config['algorithm']) {
  620.             return [
  621.                 'class' => 'Symfony\Component\Security\Core\Encoder\Pbkdf2PasswordEncoder',
  622.                 'arguments' => [
  623.                     $config['hash_algorithm'],
  624.                     $config['encode_as_base64'],
  625.                     $config['iterations'],
  626.                     $config['key_length'],
  627.                 ],
  628.             ];
  629.         }
  630.         // bcrypt encoder
  631.         if ('bcrypt' === $config['algorithm']) {
  632.             $config['algorithm'] = 'native';
  633.             $config['native_algorithm'] = \PASSWORD_BCRYPT;
  634.             return $this->createEncoder($config);
  635.         }
  636.         // Argon2i encoder
  637.         if ('argon2i' === $config['algorithm']) {
  638.             if (SodiumPasswordHasher::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
  639.                 $config['algorithm'] = 'sodium';
  640.             } elseif (\defined('PASSWORD_ARGON2I')) {
  641.                 $config['algorithm'] = 'native';
  642.                 $config['native_algorithm'] = \PASSWORD_ARGON2I;
  643.             } else {
  644.                 throw new InvalidConfigurationException(sprintf('Algorithm "argon2i" is not available. Use "%s" instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? 'argon2id", "auto' 'auto'));
  645.             }
  646.             return $this->createEncoder($config);
  647.         }
  648.         if ('argon2id' === $config['algorithm']) {
  649.             if (($hasSodium SodiumPasswordHasher::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
  650.                 $config['algorithm'] = 'sodium';
  651.             } elseif (\defined('PASSWORD_ARGON2ID')) {
  652.                 $config['algorithm'] = 'native';
  653.                 $config['native_algorithm'] = \PASSWORD_ARGON2ID;
  654.             } else {
  655.                 throw new InvalidConfigurationException(sprintf('Algorithm "argon2id" is not available. Either use "%s", upgrade to PHP 7.3+ or use libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium 'argon2i", "auto' 'auto'));
  656.             }
  657.             return $this->createEncoder($config);
  658.         }
  659.         if ('native' === $config['algorithm']) {
  660.             return [
  661.                 'class' => NativePasswordEncoder::class,
  662.                 'arguments' => [
  663.                         $config['time_cost'],
  664.                         (($config['memory_cost'] ?? 0) << 10) ?: null,
  665.                         $config['cost'],
  666.                     ] + (isset($config['native_algorithm']) ? [=> $config['native_algorithm']] : []),
  667.             ];
  668.         }
  669.         if ('sodium' === $config['algorithm']) {
  670.             if (!SodiumPasswordHasher::isSupported()) {
  671.                 throw new InvalidConfigurationException('Libsodium is not available. Install the sodium extension or use "auto" instead.');
  672.             }
  673.             return [
  674.                 'class' => SodiumPasswordEncoder::class,
  675.                 'arguments' => [
  676.                     $config['time_cost'],
  677.                     (($config['memory_cost'] ?? 0) << 10) ?: null,
  678.                 ],
  679.             ];
  680.         }
  681.         // run-time configured encoder
  682.         return $config;
  683.     }
  684.     private function createHashers(array $hashersContainerBuilder $container)
  685.     {
  686.         $hasherMap = [];
  687.         foreach ($hashers as $class => $hasher) {
  688.             // @deprecated since Symfony 5.3, remove the check in 6.0
  689.             if (class_exists($class) && !is_a($classPasswordAuthenticatedUserInterface::class, true)) {
  690.                 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);
  691.             }
  692.             $hasherMap[$class] = $this->createHasher($hasher);
  693.         }
  694.         $container
  695.             ->getDefinition('security.password_hasher_factory')
  696.             ->setArguments([$hasherMap])
  697.         ;
  698.     }
  699.     private function createHasher(array $config)
  700.     {
  701.         // a custom hasher service
  702.         if (isset($config['id'])) {
  703.             return new Reference($config['id']);
  704.         }
  705.         if ($config['migrate_from'] ?? false) {
  706.             return $config;
  707.         }
  708.         // plaintext hasher
  709.         if ('plaintext' === $config['algorithm']) {
  710.             $arguments = [$config['ignore_case']];
  711.             return [
  712.                 'class' => PlaintextPasswordHasher::class,
  713.                 'arguments' => $arguments,
  714.             ];
  715.         }
  716.         // pbkdf2 hasher
  717.         if ('pbkdf2' === $config['algorithm']) {
  718.             return [
  719.                 'class' => Pbkdf2PasswordHasher::class,
  720.                 'arguments' => [
  721.                     $config['hash_algorithm'],
  722.                     $config['encode_as_base64'],
  723.                     $config['iterations'],
  724.                     $config['key_length'],
  725.                 ],
  726.             ];
  727.         }
  728.         // bcrypt hasher
  729.         if ('bcrypt' === $config['algorithm']) {
  730.             $config['algorithm'] = 'native';
  731.             $config['native_algorithm'] = \PASSWORD_BCRYPT;
  732.             return $this->createHasher($config);
  733.         }
  734.         // Argon2i hasher
  735.         if ('argon2i' === $config['algorithm']) {
  736.             if (SodiumPasswordHasher::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
  737.                 $config['algorithm'] = 'sodium';
  738.             } elseif (\defined('PASSWORD_ARGON2I')) {
  739.                 $config['algorithm'] = 'native';
  740.                 $config['native_algorithm'] = \PASSWORD_ARGON2I;
  741.             } else {
  742.                 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'));
  743.             }
  744.             return $this->createHasher($config);
  745.         }
  746.         if ('argon2id' === $config['algorithm']) {
  747.             if (($hasSodium SodiumPasswordHasher::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
  748.                 $config['algorithm'] = 'sodium';
  749.             } elseif (\defined('PASSWORD_ARGON2ID')) {
  750.                 $config['algorithm'] = 'native';
  751.                 $config['native_algorithm'] = \PASSWORD_ARGON2ID;
  752.             } else {
  753.                 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'));
  754.             }
  755.             return $this->createHasher($config);
  756.         }
  757.         if ('native' === $config['algorithm']) {
  758.             return [
  759.                 'class' => NativePasswordHasher::class,
  760.                 'arguments' => [
  761.                     $config['time_cost'],
  762.                     (($config['memory_cost'] ?? 0) << 10) ?: null,
  763.                     $config['cost'],
  764.                 ] + (isset($config['native_algorithm']) ? [=> $config['native_algorithm']] : []),
  765.             ];
  766.         }
  767.         if ('sodium' === $config['algorithm']) {
  768.             if (!SodiumPasswordHasher::isSupported()) {
  769.                 throw new InvalidConfigurationException('Libsodium is not available. Install the sodium extension or use "auto" instead.');
  770.             }
  771.             return [
  772.                 'class' => SodiumPasswordHasher::class,
  773.                 'arguments' => [
  774.                     $config['time_cost'],
  775.                     (($config['memory_cost'] ?? 0) << 10) ?: null,
  776.                 ],
  777.             ];
  778.         }
  779.         // run-time configured hasher
  780.         return $config;
  781.     }
  782.     // Parses user providers and returns an array of their ids
  783.     private function createUserProviders(array $configContainerBuilder $container): array
  784.     {
  785.         $providerIds = [];
  786.         foreach ($config['providers'] as $name => $provider) {
  787.             $id $this->createUserDaoProvider($name$provider$container);
  788.             $providerIds[str_replace('-''_'$name)] = $id;
  789.         }
  790.         return $providerIds;
  791.     }
  792.     // Parses a <provider> tag and returns the id for the related user provider service
  793.     private function createUserDaoProvider(string $name, array $providerContainerBuilder $container): string
  794.     {
  795.         $name $this->getUserProviderId($name);
  796.         // Doctrine Entity and In-memory DAO provider are managed by factories
  797.         foreach ($this->userProviderFactories as $factory) {
  798.             $key str_replace('-''_'$factory->getKey());
  799.             if (!empty($provider[$key])) {
  800.                 $factory->create($container$name$provider[$key]);
  801.                 return $name;
  802.             }
  803.         }
  804.         // Existing DAO service provider
  805.         if (isset($provider['id'])) {
  806.             $container->setAlias($name, new Alias($provider['id'], false));
  807.             return $provider['id'];
  808.         }
  809.         // Chain provider
  810.         if (isset($provider['chain'])) {
  811.             $providers = [];
  812.             foreach ($provider['chain']['providers'] as $providerName) {
  813.                 $providers[] = new Reference($this->getUserProviderId($providerName));
  814.             }
  815.             $container
  816.                 ->setDefinition($name, new ChildDefinition('security.user.provider.chain'))
  817.                 ->addArgument(new IteratorArgument($providers));
  818.             return $name;
  819.         }
  820.         throw new InvalidConfigurationException(sprintf('Unable to create definition for "%s" user provider.'$name));
  821.     }
  822.     private function getUserProviderId(string $name): string
  823.     {
  824.         return 'security.user.provider.concrete.'.strtolower($name);
  825.     }
  826.     private function createExceptionListener(ContainerBuilder $container, array $configstring $id, ?string $defaultEntryPointbool $stateless): string
  827.     {
  828.         $exceptionListenerId 'security.exception_listener.'.$id;
  829.         $listener $container->setDefinition($exceptionListenerId, new ChildDefinition('security.exception_listener'));
  830.         $listener->replaceArgument(3$id);
  831.         $listener->replaceArgument(4null === $defaultEntryPoint null : new Reference($defaultEntryPoint));
  832.         $listener->replaceArgument(8$stateless);
  833.         // access denied handler setup
  834.         if (isset($config['access_denied_handler'])) {
  835.             $listener->replaceArgument(6, new Reference($config['access_denied_handler']));
  836.         } elseif (isset($config['access_denied_url'])) {
  837.             $listener->replaceArgument(5$config['access_denied_url']);
  838.         }
  839.         return $exceptionListenerId;
  840.     }
  841.     private function createSwitchUserListener(ContainerBuilder $containerstring $id, array $config, ?string $defaultProviderbool $stateless): string
  842.     {
  843.         $userProvider = isset($config['provider']) ? $this->getUserProviderId($config['provider']) : $defaultProvider;
  844.         if (!$userProvider) {
  845.             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));
  846.         }
  847.         $switchUserListenerId 'security.authentication.switchuser_listener.'.$id;
  848.         $listener $container->setDefinition($switchUserListenerId, new ChildDefinition('security.authentication.switchuser_listener'));
  849.         $listener->replaceArgument(1, new Reference($userProvider));
  850.         $listener->replaceArgument(2, new Reference('security.user_checker.'.$id));
  851.         $listener->replaceArgument(3$id);
  852.         $listener->replaceArgument(6$config['parameter']);
  853.         $listener->replaceArgument(7$config['role']);
  854.         $listener->replaceArgument(9$stateless);
  855.         return $switchUserListenerId;
  856.     }
  857.     private function createExpression(ContainerBuilder $containerstring $expression): Reference
  858.     {
  859.         if (isset($this->expressions[$id '.security.expression.'.ContainerBuilder::hash($expression)])) {
  860.             return $this->expressions[$id];
  861.         }
  862.         if (!$container::willBeAvailable('symfony/expression-language'ExpressionLanguage::class, ['symfony/security-bundle'], true)) {
  863.             throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".');
  864.         }
  865.         $container
  866.             ->register($id'Symfony\Component\ExpressionLanguage\Expression')
  867.             ->setPublic(false)
  868.             ->addArgument($expression)
  869.         ;
  870.         return $this->expressions[$id] = new Reference($id);
  871.     }
  872.     private function createRequestMatcher(ContainerBuilder $containerstring $path nullstring $host nullint $port null, array $methods = [], array $ips null, array $attributes = []): Reference
  873.     {
  874.         if ($methods) {
  875.             $methods array_map('strtoupper'$methods);
  876.         }
  877.         if (null !== $ips) {
  878.             foreach ($ips as $ip) {
  879.                 $container->resolveEnvPlaceholders($ipnull$usedEnvs);
  880.                 if (!$usedEnvs && !$this->isValidIps($ip)) {
  881.                     throw new \LogicException(sprintf('The given value "%s" in the "security.access_control" config option is not a valid IP address.'$ip));
  882.                 }
  883.                 $usedEnvs null;
  884.             }
  885.         }
  886.         $id '.security.request_matcher.'.ContainerBuilder::hash([$path$host$port$methods$ips$attributes]);
  887.         if (isset($this->requestMatchers[$id])) {
  888.             return $this->requestMatchers[$id];
  889.         }
  890.         // only add arguments that are necessary
  891.         $arguments = [$path$host$methods$ips$attributesnull$port];
  892.         while (\count($arguments) > && !end($arguments)) {
  893.             array_pop($arguments);
  894.         }
  895.         $container
  896.             ->register($id'Symfony\Component\HttpFoundation\RequestMatcher')
  897.             ->setPublic(false)
  898.             ->setArguments($arguments)
  899.         ;
  900.         return $this->requestMatchers[$id] = new Reference($id);
  901.     }
  902.     /**
  903.      * @deprecated since Symfony 5.4, use "addAuthenticatorFactory()" instead
  904.      */
  905.     public function addSecurityListenerFactory(SecurityFactoryInterface $factory)
  906.     {
  907.         trigger_deprecation('symfony/security-bundle''5.4''Method "%s()" is deprecated, use "addAuthenticatorFactory()" instead.'__METHOD__);
  908.         $this->factories[] = [[
  909.             'pre_auth' => -10,
  910.             'form' => -30,
  911.             'http' => -40,
  912.             'remember_me' => -50,
  913.             'anonymous' => -60,
  914.         ][$factory->getPosition()], $factory];
  915.         $this->sortedFactories = [];
  916.     }
  917.     public function addAuthenticatorFactory(AuthenticatorFactoryInterface $factory)
  918.     {
  919.         $this->factories[] = [method_exists($factory'getPriority') ? $factory->getPriority() : 0$factory];
  920.         $this->sortedFactories = [];
  921.     }
  922.     public function addUserProviderFactory(UserProviderFactoryInterface $factory)
  923.     {
  924.         $this->userProviderFactories[] = $factory;
  925.     }
  926.     /**
  927.      * {@inheritdoc}
  928.      */
  929.     public function getXsdValidationBasePath()
  930.     {
  931.         return __DIR__.'/../Resources/config/schema';
  932.     }
  933.     public function getNamespace()
  934.     {
  935.         return 'http://symfony.com/schema/dic/security';
  936.     }
  937.     public function getConfiguration(array $configContainerBuilder $container)
  938.     {
  939.         // first assemble the factories
  940.         return new MainConfiguration($this->getSortedFactories(), $this->userProviderFactories);
  941.     }
  942.     private function isValidIps($ips): bool
  943.     {
  944.         $ipsList array_reduce((array) $ips, static function (array $ipsstring $ip) {
  945.             return array_merge($ipspreg_split('/\s*,\s*/'$ip));
  946.         }, []);
  947.         if (!$ipsList) {
  948.             return false;
  949.         }
  950.         foreach ($ipsList as $cidr) {
  951.             if (!$this->isValidIp($cidr)) {
  952.                 return false;
  953.             }
  954.         }
  955.         return true;
  956.     }
  957.     private function isValidIp(string $cidr): bool
  958.     {
  959.         $cidrParts explode('/'$cidr);
  960.         if (=== \count($cidrParts)) {
  961.             return false !== filter_var($cidrParts[0], \FILTER_VALIDATE_IP);
  962.         }
  963.         $ip $cidrParts[0];
  964.         $netmask $cidrParts[1];
  965.         if (!ctype_digit($netmask)) {
  966.             return false;
  967.         }
  968.         if (filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) {
  969.             return $netmask <= 32;
  970.         }
  971.         if (filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
  972.             return $netmask <= 128;
  973.         }
  974.         return false;
  975.     }
  976.     /**
  977.      * @return array<int, SecurityFactoryInterface|AuthenticatorFactoryInterface>
  978.      */
  979.     private function getSortedFactories(): array
  980.     {
  981.         if (!$this->sortedFactories) {
  982.             $factories = [];
  983.             foreach ($this->factories as $i => $factory) {
  984.                 $factories[] = array_merge($factory, [$i]);
  985.             }
  986.             usort($factories, function ($a$b) {
  987.                 return $b[0] <=> $a[0] ?: $a[2] <=> $b[2];
  988.             });
  989.             $this->sortedFactories array_column($factories1);
  990.         }
  991.         return $this->sortedFactories;
  992.     }
  993. }