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

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