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

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