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

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