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

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