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

  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.         // Context serializer listener
  352.         if (false === $firewall['stateless']) {
  353.             $contextKey $firewall['context'] ?? $id;
  354.             $listeners[] = new Reference($this->createContextListener($container$contextKey$firewallEventDispatcherId));
  355.             $sessionStrategyId 'security.authentication.session_strategy';
  356.             $container
  357.                 ->setDefinition('security.listener.session.'.$id, new ChildDefinition('security.listener.session'))
  358.                 ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
  359.         } else {
  360.             $sessionStrategyId 'security.authentication.session_strategy_noop';
  361.         }
  362.         $container->setAlias(new Alias('security.authentication.session_strategy.'.$idfalse), $sessionStrategyId);
  363.         $config->replaceArgument(6$contextKey);
  364.         // Logout listener
  365.         $logoutListenerId null;
  366.         if (isset($firewall['logout'])) {
  367.             $logoutListenerId 'security.logout_listener.'.$id;
  368.             $logoutListener $container->setDefinition($logoutListenerId, new ChildDefinition('security.logout_listener'));
  369.             $logoutListener->replaceArgument(2, new Reference($firewallEventDispatcherId));
  370.             $logoutListener->replaceArgument(3, [
  371.                 'csrf_parameter' => $firewall['logout']['csrf_parameter'],
  372.                 'csrf_token_id' => $firewall['logout']['csrf_token_id'],
  373.                 'logout_path' => $firewall['logout']['path'],
  374.             ]);
  375.             $logoutSuccessListenerId 'security.logout.listener.default.'.$id;
  376.             $container->setDefinition($logoutSuccessListenerId, new ChildDefinition('security.logout.listener.default'))
  377.                 ->replaceArgument(1$firewall['logout']['target'])
  378.                 ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
  379.             // add CSRF provider
  380.             if ($firewall['logout']['enable_csrf']) {
  381.                 $logoutListener->addArgument(new Reference($firewall['logout']['csrf_token_generator']));
  382.             }
  383.             // add session logout listener
  384.             if (true === $firewall['logout']['invalidate_session'] && false === $firewall['stateless']) {
  385.                 $container->setDefinition('security.logout.listener.session.'.$id, new ChildDefinition('security.logout.listener.session'))
  386.                     ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
  387.             }
  388.             // add cookie logout listener
  389.             if (\count($firewall['logout']['delete_cookies']) > 0) {
  390.                 $container->setDefinition('security.logout.listener.cookie_clearing.'.$id, new ChildDefinition('security.logout.listener.cookie_clearing'))
  391.                     ->addArgument($firewall['logout']['delete_cookies'])
  392.                     ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
  393.             }
  394.             // register with LogoutUrlGenerator
  395.             $container
  396.                 ->getDefinition('security.logout_url_generator')
  397.                 ->addMethodCall('registerListener', [
  398.                     $id,
  399.                     $firewall['logout']['path'],
  400.                     $firewall['logout']['csrf_token_id'],
  401.                     $firewall['logout']['csrf_parameter'],
  402.                     isset($firewall['logout']['csrf_token_generator']) ? new Reference($firewall['logout']['csrf_token_generator']) : null,
  403.                     false === $firewall['stateless'] && isset($firewall['context']) ? $firewall['context'] : null,
  404.                 ])
  405.             ;
  406.             $config->replaceArgument(12$firewall['logout']);
  407.         }
  408.         // Determine default entry point
  409.         $configuredEntryPoint $firewall['entry_point'] ?? null;
  410.         // Authentication listeners
  411.         $firewallAuthenticationProviders = [];
  412.         [$authListeners$defaultEntryPoint] = $this->createAuthenticationListeners($container$id$firewall$firewallAuthenticationProviders$defaultProvider$providerIds$configuredEntryPoint);
  413.         // $configuredEntryPoint is resolved into a service ID and stored in $defaultEntryPoint
  414.         $configuredEntryPoint $defaultEntryPoint;
  415.         // authenticator manager
  416.         $authenticators array_map(function ($id) {
  417.             return new Reference($id);
  418.         }, $firewallAuthenticationProviders);
  419.         $container
  420.             ->setDefinition($managerId 'security.authenticator.manager.'.$id, new ChildDefinition('security.authenticator.manager'))
  421.             ->replaceArgument(0$authenticators)
  422.             ->replaceArgument(2, new Reference($firewallEventDispatcherId))
  423.             ->replaceArgument(3$id)
  424.             ->replaceArgument(7$firewall['required_badges'] ?? [])
  425.             ->addTag('monolog.logger', ['channel' => 'security'])
  426.         ;
  427.         $managerLocator $container->getDefinition('security.authenticator.managers_locator');
  428.         $managerLocator->replaceArgument(0array_merge($managerLocator->getArgument(0), [$id => new ServiceClosureArgument(new Reference($managerId))]));
  429.         // authenticator manager listener
  430.         $container
  431.             ->setDefinition('security.firewall.authenticator.'.$id, new ChildDefinition('security.firewall.authenticator'))
  432.             ->replaceArgument(0, new Reference($managerId))
  433.         ;
  434.         if ($container->hasDefinition('debug.security.firewall')) {
  435.             $container
  436.                 ->register('debug.security.firewall.authenticator.'.$idTraceableAuthenticatorManagerListener::class)
  437.                 ->setDecoratedService('security.firewall.authenticator.'.$id)
  438.                 ->setArguments([new Reference('debug.security.firewall.authenticator.'.$id.'.inner')])
  439.             ;
  440.         }
  441.         // user checker listener
  442.         $container
  443.             ->setDefinition('security.listener.user_checker.'.$id, new ChildDefinition('security.listener.user_checker'))
  444.             ->replaceArgument(0, new Reference('security.user_checker.'.$id))
  445.             ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
  446.         $listeners[] = new Reference('security.firewall.authenticator.'.$id);
  447.         // Add authenticators to the debug:firewall command
  448.         if ($container->hasDefinition('security.command.debug_firewall')) {
  449.             $debugCommand $container->getDefinition('security.command.debug_firewall');
  450.             $debugCommand->replaceArgument(3array_merge($debugCommand->getArgument(3), [$id => $authenticators]));
  451.         }
  452.         $config->replaceArgument(7$configuredEntryPoint ?: $defaultEntryPoint);
  453.         $listeners array_merge($listeners$authListeners);
  454.         // Switch user listener
  455.         if (isset($firewall['switch_user'])) {
  456.             $listenerKeys[] = 'switch_user';
  457.             $listeners[] = new Reference($this->createSwitchUserListener($container$id$firewall['switch_user'], $defaultProvider$firewall['stateless']));
  458.         }
  459.         // Access listener
  460.         $listeners[] = new Reference('security.access_listener');
  461.         // Exception listener
  462.         $exceptionListener = new Reference($this->createExceptionListener($container$firewall$id$configuredEntryPoint ?: $defaultEntryPoint$firewall['stateless']));
  463.         $config->replaceArgument(8$firewall['access_denied_handler'] ?? null);
  464.         $config->replaceArgument(9$firewall['access_denied_url'] ?? null);
  465.         $container->setAlias('security.user_checker.'.$id, new Alias($firewall['user_checker'], false));
  466.         foreach ($this->getSortedFactories() as $factory) {
  467.             $key str_replace('-''_'$factory->getKey());
  468.             if ('custom_authenticators' !== $key && \array_key_exists($key$firewall)) {
  469.                 $listenerKeys[] = $key;
  470.             }
  471.         }
  472.         if ($firewall['custom_authenticators'] ?? false) {
  473.             foreach ($firewall['custom_authenticators'] as $customAuthenticatorId) {
  474.                 $listenerKeys[] = $customAuthenticatorId;
  475.             }
  476.         }
  477.         $config->replaceArgument(10$listenerKeys);
  478.         $config->replaceArgument(11$firewall['switch_user'] ?? null);
  479.         return [$matcher$listeners$exceptionListenernull !== $logoutListenerId ? new Reference($logoutListenerId) : null$firewallAuthenticationProviders];
  480.     }
  481.     private function createContextListener(ContainerBuilder $containerstring $contextKey, ?string $firewallEventDispatcherId)
  482.     {
  483.         if (isset($this->contextListeners[$contextKey])) {
  484.             return $this->contextListeners[$contextKey];
  485.         }
  486.         $listenerId 'security.context_listener.'.\count($this->contextListeners);
  487.         $listener $container->setDefinition($listenerId, new ChildDefinition('security.context_listener'));
  488.         $listener->replaceArgument(2$contextKey);
  489.         if (null !== $firewallEventDispatcherId) {
  490.             $listener->replaceArgument(4, new Reference($firewallEventDispatcherId));
  491.             $listener->addTag('kernel.event_listener', ['event' => KernelEvents::RESPONSE'method' => 'onKernelResponse']);
  492.         }
  493.         return $this->contextListeners[$contextKey] = $listenerId;
  494.     }
  495.     private function createAuthenticationListeners(ContainerBuilder $containerstring $id, array $firewall, array &$authenticationProviders, ?string $defaultProvider, array $providerIds, ?string $defaultEntryPoint)
  496.     {
  497.         $listeners = [];
  498.         $entryPoints = [];
  499.         foreach ($this->getSortedFactories() as $factory) {
  500.             $key str_replace('-''_'$factory->getKey());
  501.             if (isset($firewall[$key])) {
  502.                 $userProvider $this->getUserProvider($container$id$firewall$key$defaultProvider$providerIds);
  503.                 if (!$factory instanceof AuthenticatorFactoryInterface) {
  504.                     throw new InvalidConfigurationException(sprintf('Authenticator factory "%s" ("%s") must implement "%s".'get_debug_type($factory), $keyAuthenticatorFactoryInterface::class));
  505.                 }
  506.                 $authenticators $factory->createAuthenticator($container$id$firewall[$key], $userProvider);
  507.                 if (\is_array($authenticators)) {
  508.                     foreach ($authenticators as $authenticator) {
  509.                         $authenticationProviders[] = $authenticator;
  510.                         $entryPoints[] = $authenticator;
  511.                     }
  512.                 } else {
  513.                     $authenticationProviders[] = $authenticators;
  514.                     $entryPoints[$key] = $authenticators;
  515.                 }
  516.                 if ($factory instanceof FirewallListenerFactoryInterface) {
  517.                     $firewallListenerIds $factory->createListeners($container$id$firewall[$key]);
  518.                     foreach ($firewallListenerIds as $firewallListenerId) {
  519.                         $listeners[] = new Reference($firewallListenerId);
  520.                     }
  521.                 }
  522.             }
  523.         }
  524.         // the actual entry point is configured by the RegisterEntryPointPass
  525.         $container->setParameter('security.'.$id.'._indexed_authenticators'$entryPoints);
  526.         return [$listeners$defaultEntryPoint];
  527.     }
  528.     private function getUserProvider(ContainerBuilder $containerstring $id, array $firewallstring $factoryKey, ?string $defaultProvider, array $providerIds): string
  529.     {
  530.         if (isset($firewall[$factoryKey]['provider'])) {
  531.             if (!isset($providerIds[$normalizedName str_replace('-''_'$firewall[$factoryKey]['provider'])])) {
  532.                 throw new InvalidConfigurationException(sprintf('Invalid firewall "%s": user provider "%s" not found.'$id$firewall[$factoryKey]['provider']));
  533.             }
  534.             return $providerIds[$normalizedName];
  535.         }
  536.         if ($defaultProvider) {
  537.             return $defaultProvider;
  538.         }
  539.         if (!$providerIds) {
  540.             $userProvider sprintf('security.user.provider.missing.%s'$factoryKey);
  541.             $container->setDefinition(
  542.                 $userProvider,
  543.                 (new ChildDefinition('security.user.provider.missing'))->replaceArgument(0$id)
  544.             );
  545.             return $userProvider;
  546.         }
  547.         if ('remember_me' === $factoryKey || 'anonymous' === $factoryKey || 'custom_authenticators' === $factoryKey) {
  548.             if ('custom_authenticators' === $factoryKey) {
  549.                 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);
  550.             }
  551.             return 'security.user_providers';
  552.         }
  553.         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));
  554.     }
  555.     private function createHashers(array $hashersContainerBuilder $container)
  556.     {
  557.         $hasherMap = [];
  558.         foreach ($hashers as $class => $hasher) {
  559.             $hasherMap[$class] = $this->createHasher($hasher);
  560.         }
  561.         $container
  562.             ->getDefinition('security.password_hasher_factory')
  563.             ->setArguments([$hasherMap])
  564.         ;
  565.     }
  566.     private function createHasher(array $config)
  567.     {
  568.         // a custom hasher service
  569.         if (isset($config['id'])) {
  570.             return new Reference($config['id']);
  571.         }
  572.         if ($config['migrate_from'] ?? false) {
  573.             return $config;
  574.         }
  575.         // plaintext hasher
  576.         if ('plaintext' === $config['algorithm']) {
  577.             $arguments = [$config['ignore_case']];
  578.             return [
  579.                 'class' => PlaintextPasswordHasher::class,
  580.                 'arguments' => $arguments,
  581.             ];
  582.         }
  583.         // pbkdf2 hasher
  584.         if ('pbkdf2' === $config['algorithm']) {
  585.             return [
  586.                 'class' => Pbkdf2PasswordHasher::class,
  587.                 'arguments' => [
  588.                     $config['hash_algorithm'],
  589.                     $config['encode_as_base64'],
  590.                     $config['iterations'],
  591.                     $config['key_length'],
  592.                 ],
  593.             ];
  594.         }
  595.         // bcrypt hasher
  596.         if ('bcrypt' === $config['algorithm']) {
  597.             $config['algorithm'] = 'native';
  598.             $config['native_algorithm'] = \PASSWORD_BCRYPT;
  599.             return $this->createHasher($config);
  600.         }
  601.         // Argon2i hasher
  602.         if ('argon2i' === $config['algorithm']) {
  603.             if (SodiumPasswordHasher::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
  604.                 $config['algorithm'] = 'sodium';
  605.             } elseif (\defined('PASSWORD_ARGON2I')) {
  606.                 $config['algorithm'] = 'native';
  607.                 $config['native_algorithm'] = \PASSWORD_ARGON2I;
  608.             } else {
  609.                 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'));
  610.             }
  611.             return $this->createHasher($config);
  612.         }
  613.         if ('argon2id' === $config['algorithm']) {
  614.             if (($hasSodium SodiumPasswordHasher::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
  615.                 $config['algorithm'] = 'sodium';
  616.             } elseif (\defined('PASSWORD_ARGON2ID')) {
  617.                 $config['algorithm'] = 'native';
  618.                 $config['native_algorithm'] = \PASSWORD_ARGON2ID;
  619.             } else {
  620.                 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'));
  621.             }
  622.             return $this->createHasher($config);
  623.         }
  624.         if ('native' === $config['algorithm']) {
  625.             return [
  626.                 'class' => NativePasswordHasher::class,
  627.                 'arguments' => [
  628.                     $config['time_cost'],
  629.                     (($config['memory_cost'] ?? 0) << 10) ?: null,
  630.                     $config['cost'],
  631.                 ] + (isset($config['native_algorithm']) ? [=> $config['native_algorithm']] : []),
  632.             ];
  633.         }
  634.         if ('sodium' === $config['algorithm']) {
  635.             if (!SodiumPasswordHasher::isSupported()) {
  636.                 throw new InvalidConfigurationException('Libsodium is not available. Install the sodium extension or use "auto" instead.');
  637.             }
  638.             return [
  639.                 'class' => SodiumPasswordHasher::class,
  640.                 'arguments' => [
  641.                     $config['time_cost'],
  642.                     (($config['memory_cost'] ?? 0) << 10) ?: null,
  643.                 ],
  644.             ];
  645.         }
  646.         // run-time configured hasher
  647.         return $config;
  648.     }
  649.     // Parses user providers and returns an array of their ids
  650.     private function createUserProviders(array $configContainerBuilder $container): array
  651.     {
  652.         $providerIds = [];
  653.         foreach ($config['providers'] as $name => $provider) {
  654.             $id $this->createUserDaoProvider($name$provider$container);
  655.             $providerIds[str_replace('-''_'$name)] = $id;
  656.         }
  657.         return $providerIds;
  658.     }
  659.     // Parses a <provider> tag and returns the id for the related user provider service
  660.     private function createUserDaoProvider(string $name, array $providerContainerBuilder $container): string
  661.     {
  662.         $name $this->getUserProviderId($name);
  663.         // Doctrine Entity and In-memory DAO provider are managed by factories
  664.         foreach ($this->userProviderFactories as $factory) {
  665.             $key str_replace('-''_'$factory->getKey());
  666.             if (!empty($provider[$key])) {
  667.                 $factory->create($container$name$provider[$key]);
  668.                 return $name;
  669.             }
  670.         }
  671.         // Existing DAO service provider
  672.         if (isset($provider['id'])) {
  673.             $container->setAlias($name, new Alias($provider['id'], false));
  674.             return $provider['id'];
  675.         }
  676.         // Chain provider
  677.         if (isset($provider['chain'])) {
  678.             $providers = [];
  679.             foreach ($provider['chain']['providers'] as $providerName) {
  680.                 $providers[] = new Reference($this->getUserProviderId($providerName));
  681.             }
  682.             $container
  683.                 ->setDefinition($name, new ChildDefinition('security.user.provider.chain'))
  684.                 ->addArgument(new IteratorArgument($providers));
  685.             return $name;
  686.         }
  687.         throw new InvalidConfigurationException(sprintf('Unable to create definition for "%s" user provider.'$name));
  688.     }
  689.     private function getUserProviderId(string $name): string
  690.     {
  691.         return 'security.user.provider.concrete.'.strtolower($name);
  692.     }
  693.     private function createExceptionListener(ContainerBuilder $container, array $configstring $id, ?string $defaultEntryPointbool $stateless): string
  694.     {
  695.         $exceptionListenerId 'security.exception_listener.'.$id;
  696.         $listener $container->setDefinition($exceptionListenerId, new ChildDefinition('security.exception_listener'));
  697.         $listener->replaceArgument(3$id);
  698.         $listener->replaceArgument(4null === $defaultEntryPoint null : new Reference($defaultEntryPoint));
  699.         $listener->replaceArgument(8$stateless);
  700.         // access denied handler setup
  701.         if (isset($config['access_denied_handler'])) {
  702.             $listener->replaceArgument(6, new Reference($config['access_denied_handler']));
  703.         } elseif (isset($config['access_denied_url'])) {
  704.             $listener->replaceArgument(5$config['access_denied_url']);
  705.         }
  706.         return $exceptionListenerId;
  707.     }
  708.     private function createSwitchUserListener(ContainerBuilder $containerstring $id, array $config, ?string $defaultProviderbool $stateless): string
  709.     {
  710.         $userProvider = isset($config['provider']) ? $this->getUserProviderId($config['provider']) : $defaultProvider;
  711.         if (!$userProvider) {
  712.             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));
  713.         }
  714.         if ($stateless && null !== $config['target_route']) {
  715.             throw new InvalidConfigurationException(sprintf('Cannot set a "target_route" for the "switch_user" listener on the "%s" firewall as it is stateless.'$id));
  716.         }
  717.         $switchUserListenerId 'security.authentication.switchuser_listener.'.$id;
  718.         $listener $container->setDefinition($switchUserListenerId, new ChildDefinition('security.authentication.switchuser_listener'));
  719.         $listener->replaceArgument(1, new Reference($userProvider));
  720.         $listener->replaceArgument(2, new Reference('security.user_checker.'.$id));
  721.         $listener->replaceArgument(3$id);
  722.         $listener->replaceArgument(6$config['parameter']);
  723.         $listener->replaceArgument(7$config['role']);
  724.         $listener->replaceArgument(9$stateless);
  725.         $listener->replaceArgument(11$config['target_route']);
  726.         return $switchUserListenerId;
  727.     }
  728.     private function createExpression(ContainerBuilder $containerstring $expression): Reference
  729.     {
  730.         if (isset($this->expressions[$id '.security.expression.'.ContainerBuilder::hash($expression)])) {
  731.             return $this->expressions[$id];
  732.         }
  733.         if (!$container::willBeAvailable('symfony/expression-language'ExpressionLanguage::class, ['symfony/security-bundle'])) {
  734.             throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".');
  735.         }
  736.         $container
  737.             ->register($idExpression::class)
  738.             ->setPublic(false)
  739.             ->addArgument($expression)
  740.         ;
  741.         return $this->expressions[$id] = new Reference($id);
  742.     }
  743.     private function createRequestMatcher(ContainerBuilder $containerstring $path nullstring $host nullint $port null, array $methods = [], array $ips null, array $attributes = []): Reference
  744.     {
  745.         if ($methods) {
  746.             $methods array_map('strtoupper'$methods);
  747.         }
  748.         if ($ips) {
  749.             foreach ($ips as $ip) {
  750.                 $container->resolveEnvPlaceholders($ipnull$usedEnvs);
  751.                 if (!$usedEnvs && !$this->isValidIps($ip)) {
  752.                     throw new \LogicException(sprintf('The given value "%s" in the "security.access_control" config option is not a valid IP address.'$ip));
  753.                 }
  754.                 $usedEnvs null;
  755.             }
  756.         }
  757.         $id '.security.request_matcher.'.ContainerBuilder::hash([ChainRequestMatcher::class, $path$host$port$methods$ips$attributes]);
  758.         if (isset($this->requestMatchers[$id])) {
  759.             return $this->requestMatchers[$id];
  760.         }
  761.         $arguments = [];
  762.         if ($methods) {
  763.             if (!$container->hasDefinition($lid '.security.request_matcher.'.ContainerBuilder::hash([MethodRequestMatcher::class, $methods]))) {
  764.                 $container->register($lidMethodRequestMatcher::class)->setArguments([$methods]);
  765.             }
  766.             $arguments[] = new Reference($lid);
  767.         }
  768.         if ($path) {
  769.             if (!$container->hasDefinition($lid '.security.request_matcher.'.ContainerBuilder::hash([PathRequestMatcher::class, $path]))) {
  770.                 $container->register($lidPathRequestMatcher::class)->setArguments([$path]);
  771.             }
  772.             $arguments[] = new Reference($lid);
  773.         }
  774.         if ($host) {
  775.             if (!$container->hasDefinition($lid '.security.request_matcher.'.ContainerBuilder::hash([HostRequestMatcher::class, $host]))) {
  776.                 $container->register($lidHostRequestMatcher::class)->setArguments([$host]);
  777.             }
  778.             $arguments[] = new Reference($lid);
  779.         }
  780.         if ($ips) {
  781.             if (!$container->hasDefinition($lid '.security.request_matcher.'.ContainerBuilder::hash([IpsRequestMatcher::class, $ips]))) {
  782.                 $container->register($lidIpsRequestMatcher::class)->setArguments([$ips]);
  783.             }
  784.             $arguments[] = new Reference($lid);
  785.         }
  786.         if ($attributes) {
  787.             if (!$container->hasDefinition($lid '.security.request_matcher.'.ContainerBuilder::hash([AttributesRequestMatcher::class, $attributes]))) {
  788.                 $container->register($lidAttributesRequestMatcher::class)->setArguments([$attributes]);
  789.             }
  790.             $arguments[] = new Reference($lid);
  791.         }
  792.         if ($port) {
  793.             if (!$container->hasDefinition($lid '.security.request_matcher.'.ContainerBuilder::hash([PortRequestMatcher::class, $port]))) {
  794.                 $container->register($lidPortRequestMatcher::class)->setArguments([$port]);
  795.             }
  796.             $arguments[] = new Reference($lid);
  797.         }
  798.         $container
  799.             ->register($idChainRequestMatcher::class)
  800.             ->setArguments([$arguments])
  801.         ;
  802.         return $this->requestMatchers[$id] = new Reference($id);
  803.     }
  804.     public function addAuthenticatorFactory(AuthenticatorFactoryInterface $factory)
  805.     {
  806.         $this->factories[] = [$factory->getPriority(), $factory];
  807.         $this->sortedFactories = [];
  808.     }
  809.     public function addUserProviderFactory(UserProviderFactoryInterface $factory)
  810.     {
  811.         $this->userProviderFactories[] = $factory;
  812.     }
  813.     public function getXsdValidationBasePath(): string|false
  814.     {
  815.         return __DIR__.'/../Resources/config/schema';
  816.     }
  817.     public function getNamespace(): string
  818.     {
  819.         return 'http://symfony.com/schema/dic/security';
  820.     }
  821.     public function getConfiguration(array $configContainerBuilder $container): ?ConfigurationInterface
  822.     {
  823.         // first assemble the factories
  824.         return new MainConfiguration($this->getSortedFactories(), $this->userProviderFactories);
  825.     }
  826.     private function isValidIps(string|array $ips): bool
  827.     {
  828.         $ipsList array_reduce((array) $ips, static function (array $ipsstring $ip) {
  829.             return array_merge($ipspreg_split('/\s*,\s*/'$ip));
  830.         }, []);
  831.         if (!$ipsList) {
  832.             return false;
  833.         }
  834.         foreach ($ipsList as $cidr) {
  835.             if (!$this->isValidIp($cidr)) {
  836.                 return false;
  837.             }
  838.         }
  839.         return true;
  840.     }
  841.     private function isValidIp(string $cidr): bool
  842.     {
  843.         $cidrParts explode('/'$cidr);
  844.         if (=== \count($cidrParts)) {
  845.             return false !== filter_var($cidrParts[0], \FILTER_VALIDATE_IP);
  846.         }
  847.         $ip $cidrParts[0];
  848.         $netmask $cidrParts[1];
  849.         if (!ctype_digit($netmask)) {
  850.             return false;
  851.         }
  852.         if (filter_var($ip\FILTER_VALIDATE_IP\FILTER_FLAG_IPV4)) {
  853.             return $netmask <= 32;
  854.         }
  855.         if (filter_var($ip\FILTER_VALIDATE_IP\FILTER_FLAG_IPV6)) {
  856.             return $netmask <= 128;
  857.         }
  858.         return false;
  859.     }
  860.     /**
  861.      * @return array<int, AuthenticatorFactoryInterface>
  862.      */
  863.     private function getSortedFactories(): array
  864.     {
  865.         if (!$this->sortedFactories) {
  866.             $factories = [];
  867.             foreach ($this->factories as $i => $factory) {
  868.                 $factories[] = array_merge($factory, [$i]);
  869.             }
  870.             usort($factories, function ($a$b) {
  871.                 return $b[0] <=> $a[0] ?: $a[2] <=> $b[2];
  872.             });
  873.             $this->sortedFactories array_column($factories1);
  874.         }
  875.         return $this->sortedFactories;
  876.     }
  877. }