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

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\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RememberMeFactory;
  12. use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
  13. use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface;
  14. use Symfony\Bundle\SecurityBundle\SecurityUserValueResolver;
  15. use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
  16. use Symfony\Component\Config\FileLocator;
  17. use Symfony\Component\Console\Application;
  18. use Symfony\Component\DependencyInjection\Alias;
  19. use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
  20. use Symfony\Component\DependencyInjection\ChildDefinition;
  21. use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
  22. use Symfony\Component\DependencyInjection\ContainerBuilder;
  23. use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
  24. use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
  25. use Symfony\Component\DependencyInjection\Parameter;
  26. use Symfony\Component\DependencyInjection\Reference;
  27. use Symfony\Component\HttpKernel\DependencyInjection\Extension;
  28. use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
  29. use Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder;
  30. use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder;
  31. use Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder;
  32. use Symfony\Component\Security\Core\User\UserProviderInterface;
  33. use Symfony\Component\Security\Http\Controller\UserValueResolver;
  34. use Symfony\Component\Templating\Helper\Helper;
  35. use Twig\Extension\AbstractExtension;
  36. /**
  37.  * SecurityExtension.
  38.  *
  39.  * @author Fabien Potencier <fabien@symfony.com>
  40.  * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  41.  */
  42. class SecurityExtension extends Extension implements PrependExtensionInterface
  43. {
  44.     private $requestMatchers = [];
  45.     private $expressions = [];
  46.     private $contextListeners = [];
  47.     private $listenerPositions = ['pre_auth''form''http''remember_me'];
  48.     private $factories = [];
  49.     private $userProviderFactories = [];
  50.     private $statelessFirewallKeys = [];
  51.     public function __construct()
  52.     {
  53.         foreach ($this->listenerPositions as $position) {
  54.             $this->factories[$position] = [];
  55.         }
  56.     }
  57.     public function prepend(ContainerBuilder $container)
  58.     {
  59.         $rememberMeSecureDefault false;
  60.         $rememberMeSameSiteDefault null;
  61.         if (!isset($container->getExtensions()['framework'])) {
  62.             return;
  63.         }
  64.         foreach ($container->getExtensionConfig('framework') as $config) {
  65.             if (isset($config['session']) && \is_array($config['session'])) {
  66.                 $rememberMeSecureDefault $config['session']['cookie_secure'] ?? $rememberMeSecureDefault;
  67.                 $rememberMeSameSiteDefault = \array_key_exists('cookie_samesite'$config['session']) ? $config['session']['cookie_samesite'] : $rememberMeSameSiteDefault;
  68.             }
  69.         }
  70.         foreach ($this->listenerPositions as $position) {
  71.             foreach ($this->factories[$position] as $factory) {
  72.                 if ($factory instanceof RememberMeFactory) {
  73.                     \Closure::bind(function () use ($rememberMeSecureDefault$rememberMeSameSiteDefault) {
  74.                         $this->options['secure'] = $rememberMeSecureDefault;
  75.                         $this->options['samesite'] = $rememberMeSameSiteDefault;
  76.                     }, $factory$factory)();
  77.                 }
  78.             }
  79.         }
  80.     }
  81.     public function load(array $configsContainerBuilder $container)
  82.     {
  83.         if (!array_filter($configs)) {
  84.             return;
  85.         }
  86.         $mainConfig $this->getConfiguration($configs$container);
  87.         $config $this->processConfiguration($mainConfig$configs);
  88.         // load services
  89.         $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
  90.         $loader->load('security.xml');
  91.         $loader->load('security_listeners.xml');
  92.         $loader->load('security_rememberme.xml');
  93.         if (class_exists(Helper::class)) {
  94.             $loader->load('templating_php.xml');
  95.         }
  96.         if (class_exists(AbstractExtension::class)) {
  97.             $loader->load('templating_twig.xml');
  98.         }
  99.         $loader->load('collectors.xml');
  100.         $loader->load('guard.xml');
  101.         if ($container->hasParameter('kernel.debug') && $container->getParameter('kernel.debug')) {
  102.             $loader->load('security_debug.xml');
  103.         }
  104.         if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
  105.             $container->removeDefinition('security.expression_language');
  106.             $container->removeDefinition('security.access.expression_voter');
  107.         }
  108.         // set some global scalars
  109.         $container->setParameter('security.access.denied_url'$config['access_denied_url']);
  110.         $container->setParameter('security.authentication.manager.erase_credentials'$config['erase_credentials']);
  111.         $container->setParameter('security.authentication.session_strategy.strategy'$config['session_fixation_strategy']);
  112.         if (isset($config['access_decision_manager']['service'])) {
  113.             $container->setAlias('security.access.decision_manager'$config['access_decision_manager']['service'])->setPrivate(true);
  114.         } else {
  115.             $container
  116.                 ->getDefinition('security.access.decision_manager')
  117.                 ->addArgument($config['access_decision_manager']['strategy'])
  118.                 ->addArgument($config['access_decision_manager']['allow_if_all_abstain'])
  119.                 ->addArgument($config['access_decision_manager']['allow_if_equal_granted_denied']);
  120.         }
  121.         $container->setParameter('security.access.always_authenticate_before_granting'$config['always_authenticate_before_granting']);
  122.         $container->setParameter('security.authentication.hide_user_not_found'$config['hide_user_not_found']);
  123.         $this->createFirewalls($config$container);
  124.         $this->createAuthorization($config$container);
  125.         $this->createRoleHierarchy($config$container);
  126.         $container->getDefinition('security.authentication.guard_handler')
  127.             ->replaceArgument(2$this->statelessFirewallKeys);
  128.         if ($config['encoders']) {
  129.             $this->createEncoders($config['encoders'], $container);
  130.         }
  131.         if (class_exists(Application::class)) {
  132.             $loader->load('console.xml');
  133.             $container->getDefinition('security.command.user_password_encoder')->replaceArgument(1array_keys($config['encoders']));
  134.         }
  135.         if (!class_exists(UserValueResolver::class)) {
  136.             $container->getDefinition('security.user_value_resolver')->setClass(SecurityUserValueResolver::class);
  137.         }
  138.         $container->registerForAutoconfiguration(VoterInterface::class)
  139.             ->addTag('security.voter');
  140.     }
  141.     private function createRoleHierarchy(array $configContainerBuilder $container)
  142.     {
  143.         if (!isset($config['role_hierarchy']) || === \count($config['role_hierarchy'])) {
  144.             $container->removeDefinition('security.access.role_hierarchy_voter');
  145.             return;
  146.         }
  147.         $container->setParameter('security.role_hierarchy.roles'$config['role_hierarchy']);
  148.         $container->removeDefinition('security.access.simple_role_voter');
  149.     }
  150.     private function createAuthorization($configContainerBuilder $container)
  151.     {
  152.         foreach ($config['access_control'] as $access) {
  153.             $matcher $this->createRequestMatcher(
  154.                 $container,
  155.                 $access['path'],
  156.                 $access['host'],
  157.                 $access['port'],
  158.                 $access['methods'],
  159.                 $access['ips']
  160.             );
  161.             $attributes $access['roles'];
  162.             if ($access['allow_if']) {
  163.                 $attributes[] = $this->createExpression($container$access['allow_if']);
  164.             }
  165.             $container->getDefinition('security.access_map')
  166.                       ->addMethodCall('add', [$matcher$attributes$access['requires_channel']]);
  167.         }
  168.         // allow cache warm-up for expressions
  169.         if (\count($this->expressions)) {
  170.             $container->getDefinition('security.cache_warmer.expression')
  171.                 ->replaceArgument(0, new IteratorArgument(array_values($this->expressions)));
  172.         } else {
  173.             $container->removeDefinition('security.cache_warmer.expression');
  174.         }
  175.     }
  176.     private function createFirewalls($configContainerBuilder $container)
  177.     {
  178.         if (!isset($config['firewalls'])) {
  179.             return;
  180.         }
  181.         $firewalls $config['firewalls'];
  182.         $providerIds $this->createUserProviders($config$container);
  183.         // make the ContextListener aware of the configured user providers
  184.         $contextListenerDefinition $container->getDefinition('security.context_listener');
  185.         $arguments $contextListenerDefinition->getArguments();
  186.         $userProviders = [];
  187.         foreach ($providerIds as $userProviderId) {
  188.             $userProviders[] = new Reference($userProviderId);
  189.         }
  190.         $arguments[1] = new IteratorArgument($userProviders);
  191.         $contextListenerDefinition->setArguments($arguments);
  192.         if (=== \count($providerIds)) {
  193.             $container->setAlias(UserProviderInterface::class, current($providerIds));
  194.         }
  195.         $customUserChecker false;
  196.         // load firewall map
  197.         $mapDef $container->getDefinition('security.firewall.map');
  198.         $map $authenticationProviders $contextRefs = [];
  199.         foreach ($firewalls as $name => $firewall) {
  200.             if (isset($firewall['user_checker']) && 'security.user_checker' !== $firewall['user_checker']) {
  201.                 $customUserChecker true;
  202.             }
  203.             $configId 'security.firewall.map.config.'.$name;
  204.             list($matcher$listeners$exceptionListener$logoutListener) = $this->createFirewall($container$name$firewall$authenticationProviders$providerIds$configId);
  205.             $contextId 'security.firewall.map.context.'.$name;
  206.             $context $container->setDefinition($contextId, new ChildDefinition('security.firewall.context'));
  207.             $context
  208.                 ->replaceArgument(0, new IteratorArgument($listeners))
  209.                 ->replaceArgument(1$exceptionListener)
  210.                 ->replaceArgument(2$logoutListener)
  211.                 ->replaceArgument(3, new Reference($configId))
  212.             ;
  213.             $contextRefs[$contextId] = new Reference($contextId);
  214.             $map[$contextId] = $matcher;
  215.         }
  216.         $mapDef->replaceArgument(0ServiceLocatorTagPass::register($container$contextRefs));
  217.         $mapDef->replaceArgument(1, new IteratorArgument($map));
  218.         // add authentication providers to authentication manager
  219.         $authenticationProviders array_map(function ($id) {
  220.             return new Reference($id);
  221.         }, array_values(array_unique($authenticationProviders)));
  222.         $container
  223.             ->getDefinition('security.authentication.manager')
  224.             ->replaceArgument(0, new IteratorArgument($authenticationProviders))
  225.         ;
  226.         // register an autowire alias for the UserCheckerInterface if no custom user checker service is configured
  227.         if (!$customUserChecker) {
  228.             $container->setAlias('Symfony\Component\Security\Core\User\UserCheckerInterface', new Alias('security.user_checker'false));
  229.         }
  230.     }
  231.     private function createFirewall(ContainerBuilder $container$id$firewall, &$authenticationProviders$providerIds$configId)
  232.     {
  233.         $config $container->setDefinition($configId, new ChildDefinition('security.firewall.config'));
  234.         $config->replaceArgument(0$id);
  235.         $config->replaceArgument(1$firewall['user_checker']);
  236.         // Matcher
  237.         $matcher null;
  238.         if (isset($firewall['request_matcher'])) {
  239.             $matcher = new Reference($firewall['request_matcher']);
  240.         } elseif (isset($firewall['pattern']) || isset($firewall['host'])) {
  241.             $pattern = isset($firewall['pattern']) ? $firewall['pattern'] : null;
  242.             $host = isset($firewall['host']) ? $firewall['host'] : null;
  243.             $methods = isset($firewall['methods']) ? $firewall['methods'] : [];
  244.             $matcher $this->createRequestMatcher($container$pattern$hostnull$methods);
  245.         }
  246.         $config->replaceArgument(2$matcher ? (string) $matcher null);
  247.         $config->replaceArgument(3$firewall['security']);
  248.         // Security disabled?
  249.         if (false === $firewall['security']) {
  250.             return [$matcher, [], nullnull];
  251.         }
  252.         $config->replaceArgument(4$firewall['stateless']);
  253.         // Provider id (must be configured explicitly per firewall/authenticator if more than one provider is set)
  254.         $defaultProvider null;
  255.         if (isset($firewall['provider'])) {
  256.             if (!isset($providerIds[$normalizedName str_replace('-''_'$firewall['provider'])])) {
  257.                 throw new InvalidConfigurationException(sprintf('Invalid firewall "%s": user provider "%s" not found.'$id$firewall['provider']));
  258.             }
  259.             $defaultProvider $providerIds[$normalizedName];
  260.         } elseif (=== \count($providerIds)) {
  261.             $defaultProvider reset($providerIds);
  262.         }
  263.         $config->replaceArgument(5$defaultProvider);
  264.         // Register listeners
  265.         $listeners = [];
  266.         $listenerKeys = [];
  267.         // Channel listener
  268.         $listeners[] = new Reference('security.channel_listener');
  269.         $contextKey null;
  270.         $contextListenerId null;
  271.         // Context serializer listener
  272.         if (false === $firewall['stateless']) {
  273.             $contextKey $firewall['context'] ?? $id;
  274.             $listeners[] = new Reference($contextListenerId $this->createContextListener($container$contextKey));
  275.             $sessionStrategyId 'security.authentication.session_strategy';
  276.         } else {
  277.             $this->statelessFirewallKeys[] = $id;
  278.             $sessionStrategyId 'security.authentication.session_strategy_noop';
  279.         }
  280.         $container->setAlias(new Alias('security.authentication.session_strategy.'.$idfalse), $sessionStrategyId);
  281.         $config->replaceArgument(6$contextKey);
  282.         // Logout listener
  283.         $logoutListenerId null;
  284.         if (isset($firewall['logout'])) {
  285.             $logoutListenerId 'security.logout_listener.'.$id;
  286.             $logoutListener $container->setDefinition($logoutListenerId, new ChildDefinition('security.logout_listener'));
  287.             $logoutListener->replaceArgument(3, [
  288.                 'csrf_parameter' => $firewall['logout']['csrf_parameter'],
  289.                 'csrf_token_id' => $firewall['logout']['csrf_token_id'],
  290.                 'logout_path' => $firewall['logout']['path'],
  291.             ]);
  292.             // add logout success handler
  293.             if (isset($firewall['logout']['success_handler'])) {
  294.                 $logoutSuccessHandlerId $firewall['logout']['success_handler'];
  295.             } else {
  296.                 $logoutSuccessHandlerId 'security.logout.success_handler.'.$id;
  297.                 $logoutSuccessHandler $container->setDefinition($logoutSuccessHandlerId, new ChildDefinition('security.logout.success_handler'));
  298.                 $logoutSuccessHandler->replaceArgument(1$firewall['logout']['target']);
  299.             }
  300.             $logoutListener->replaceArgument(2, new Reference($logoutSuccessHandlerId));
  301.             // add CSRF provider
  302.             if (isset($firewall['logout']['csrf_token_generator'])) {
  303.                 $logoutListener->addArgument(new Reference($firewall['logout']['csrf_token_generator']));
  304.             }
  305.             // add session logout handler
  306.             if (true === $firewall['logout']['invalidate_session'] && false === $firewall['stateless']) {
  307.                 $logoutListener->addMethodCall('addHandler', [new Reference('security.logout.handler.session')]);
  308.             }
  309.             // add cookie logout handler
  310.             if (\count($firewall['logout']['delete_cookies']) > 0) {
  311.                 $cookieHandlerId 'security.logout.handler.cookie_clearing.'.$id;
  312.                 $cookieHandler $container->setDefinition($cookieHandlerId, new ChildDefinition('security.logout.handler.cookie_clearing'));
  313.                 $cookieHandler->addArgument($firewall['logout']['delete_cookies']);
  314.                 $logoutListener->addMethodCall('addHandler', [new Reference($cookieHandlerId)]);
  315.             }
  316.             // add custom handlers
  317.             foreach ($firewall['logout']['handlers'] as $handlerId) {
  318.                 $logoutListener->addMethodCall('addHandler', [new Reference($handlerId)]);
  319.             }
  320.             // register with LogoutUrlGenerator
  321.             $container
  322.                 ->getDefinition('security.logout_url_generator')
  323.                 ->addMethodCall('registerListener', [
  324.                     $id,
  325.                     $firewall['logout']['path'],
  326.                     $firewall['logout']['csrf_token_id'],
  327.                     $firewall['logout']['csrf_parameter'],
  328.                     isset($firewall['logout']['csrf_token_generator']) ? new Reference($firewall['logout']['csrf_token_generator']) : null,
  329.                     false === $firewall['stateless'] && isset($firewall['context']) ? $firewall['context'] : null,
  330.                 ])
  331.             ;
  332.         }
  333.         // Determine default entry point
  334.         $configuredEntryPoint = isset($firewall['entry_point']) ? $firewall['entry_point'] : null;
  335.         // Authentication listeners
  336.         list($authListeners$defaultEntryPoint) = $this->createAuthenticationListeners($container$id$firewall$authenticationProviders$defaultProvider$providerIds$configuredEntryPoint$contextListenerId);
  337.         $config->replaceArgument(7$configuredEntryPoint ?: $defaultEntryPoint);
  338.         $listeners array_merge($listeners$authListeners);
  339.         // Switch user listener
  340.         if (isset($firewall['switch_user'])) {
  341.             $listenerKeys[] = 'switch_user';
  342.             $listeners[] = new Reference($this->createSwitchUserListener($container$id$firewall['switch_user'], $defaultProvider$firewall['stateless'], $providerIds));
  343.         }
  344.         // Access listener
  345.         $listeners[] = new Reference('security.access_listener');
  346.         // Exception listener
  347.         $exceptionListener = new Reference($this->createExceptionListener($container$firewall$id$configuredEntryPoint ?: $defaultEntryPoint$firewall['stateless']));
  348.         $config->replaceArgument(8, isset($firewall['access_denied_handler']) ? $firewall['access_denied_handler'] : null);
  349.         $config->replaceArgument(9, isset($firewall['access_denied_url']) ? $firewall['access_denied_url'] : null);
  350.         $container->setAlias('security.user_checker.'.$id, new Alias($firewall['user_checker'], false));
  351.         foreach ($this->factories as $position) {
  352.             foreach ($position as $factory) {
  353.                 $key str_replace('-''_'$factory->getKey());
  354.                 if (\array_key_exists($key$firewall)) {
  355.                     $listenerKeys[] = $key;
  356.                 }
  357.             }
  358.         }
  359.         if (isset($firewall['anonymous'])) {
  360.             $listenerKeys[] = 'anonymous';
  361.         }
  362.         $config->replaceArgument(10$listenerKeys);
  363.         $config->replaceArgument(11, isset($firewall['switch_user']) ? $firewall['switch_user'] : null);
  364.         return [$matcher$listeners$exceptionListenernull !== $logoutListenerId ? new Reference($logoutListenerId) : null];
  365.     }
  366.     private function createContextListener($container$contextKey)
  367.     {
  368.         if (isset($this->contextListeners[$contextKey])) {
  369.             return $this->contextListeners[$contextKey];
  370.         }
  371.         $listenerId 'security.context_listener.'.\count($this->contextListeners);
  372.         $listener $container->setDefinition($listenerId, new ChildDefinition('security.context_listener'));
  373.         $listener->replaceArgument(2$contextKey);
  374.         return $this->contextListeners[$contextKey] = $listenerId;
  375.     }
  376.     private function createAuthenticationListeners($container$id$firewall, &$authenticationProviders$defaultProvider null, array $providerIds$defaultEntryPoint$contextListenerId null)
  377.     {
  378.         $listeners = [];
  379.         $hasListeners false;
  380.         foreach ($this->listenerPositions as $position) {
  381.             foreach ($this->factories[$position] as $factory) {
  382.                 $key str_replace('-''_'$factory->getKey());
  383.                 if (isset($firewall[$key])) {
  384.                     if (isset($firewall[$key]['provider'])) {
  385.                         if (!isset($providerIds[$normalizedName str_replace('-''_'$firewall[$key]['provider'])])) {
  386.                             throw new InvalidConfigurationException(sprintf('Invalid firewall "%s": user provider "%s" not found.'$id$firewall[$key]['provider']));
  387.                         }
  388.                         $userProvider $providerIds[$normalizedName];
  389.                     } elseif ('remember_me' === $key) {
  390.                         // RememberMeFactory will use the firewall secret when created
  391.                         $userProvider null;
  392.                         if ($contextListenerId) {
  393.                             $container->getDefinition($contextListenerId)->addTag('security.remember_me_aware', ['id' => $id'provider' => 'none']);
  394.                         }
  395.                     } elseif ($defaultProvider) {
  396.                         $userProvider $defaultProvider;
  397.                     } elseif (empty($providerIds)) {
  398.                         $userProvider sprintf('security.user.provider.missing.%s'$key);
  399.                         $container->setDefinition($userProvider, (new ChildDefinition('security.user.provider.missing'))->replaceArgument(0$id));
  400.                     } else {
  401.                         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.'$key$id));
  402.                     }
  403.                     list($provider$listenerId$defaultEntryPoint) = $factory->create($container$id$firewall[$key], $userProvider$defaultEntryPoint);
  404.                     $listeners[] = new Reference($listenerId);
  405.                     $authenticationProviders[] = $provider;
  406.                     $hasListeners true;
  407.                 }
  408.             }
  409.         }
  410.         // Anonymous
  411.         if (isset($firewall['anonymous'])) {
  412.             if (null === $firewall['anonymous']['secret']) {
  413.                 $firewall['anonymous']['secret'] = new Parameter('container.build_hash');
  414.             }
  415.             $listenerId 'security.authentication.listener.anonymous.'.$id;
  416.             $container
  417.                 ->setDefinition($listenerId, new ChildDefinition('security.authentication.listener.anonymous'))
  418.                 ->replaceArgument(1$firewall['anonymous']['secret'])
  419.             ;
  420.             $listeners[] = new Reference($listenerId);
  421.             $providerId 'security.authentication.provider.anonymous.'.$id;
  422.             $container
  423.                 ->setDefinition($providerId, new ChildDefinition('security.authentication.provider.anonymous'))
  424.                 ->replaceArgument(0$firewall['anonymous']['secret'])
  425.             ;
  426.             $authenticationProviders[] = $providerId;
  427.             $hasListeners true;
  428.         }
  429.         if (false === $hasListeners) {
  430.             throw new InvalidConfigurationException(sprintf('No authentication listener registered for firewall "%s".'$id));
  431.         }
  432.         return [$listeners$defaultEntryPoint];
  433.     }
  434.     private function createEncoders($encodersContainerBuilder $container)
  435.     {
  436.         $encoderMap = [];
  437.         foreach ($encoders as $class => $encoder) {
  438.             $encoderMap[$class] = $this->createEncoder($encoder);
  439.         }
  440.         $container
  441.             ->getDefinition('security.encoder_factory.generic')
  442.             ->setArguments([$encoderMap])
  443.         ;
  444.     }
  445.     private function createEncoder($config)
  446.     {
  447.         // a custom encoder service
  448.         if (isset($config['id'])) {
  449.             return new Reference($config['id']);
  450.         }
  451.         // plaintext encoder
  452.         if ('plaintext' === $config['algorithm']) {
  453.             $arguments = [$config['ignore_case']];
  454.             return [
  455.                 'class' => 'Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder',
  456.                 'arguments' => $arguments,
  457.             ];
  458.         }
  459.         // pbkdf2 encoder
  460.         if ('pbkdf2' === $config['algorithm']) {
  461.             return [
  462.                 'class' => 'Symfony\Component\Security\Core\Encoder\Pbkdf2PasswordEncoder',
  463.                 'arguments' => [
  464.                     $config['hash_algorithm'],
  465.                     $config['encode_as_base64'],
  466.                     $config['iterations'],
  467.                     $config['key_length'],
  468.                 ],
  469.             ];
  470.         }
  471.         // bcrypt encoder
  472.         if ('bcrypt' === $config['algorithm']) {
  473.             @trigger_error('Configuring an encoder with "bcrypt" as algorithm is deprecated since Symfony 4.3, use "auto" instead.'E_USER_DEPRECATED);
  474.             return [
  475.                 'class' => 'Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder',
  476.                 'arguments' => [$config['cost'] ?? 13],
  477.             ];
  478.         }
  479.         // Argon2i encoder
  480.         if ('argon2i' === $config['algorithm']) {
  481.             @trigger_error('Configuring an encoder with "argon2i" as algorithm is deprecated since Symfony 4.3, use "auto" instead.'E_USER_DEPRECATED);
  482.             if (!Argon2iPasswordEncoder::isSupported()) {
  483.                 if (\extension_loaded('sodium') && !\defined('SODIUM_CRYPTO_PWHASH_SALTBYTES')) {
  484.                     throw new InvalidConfigurationException('The installed libsodium version does not have support for Argon2i. Use "auto" instead.');
  485.                 }
  486.                 throw new InvalidConfigurationException('Argon2i algorithm is not supported. Install the libsodium extension or use "auto" instead.');
  487.             }
  488.             return [
  489.                 'class' => 'Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder',
  490.                 'arguments' => [
  491.                     $config['memory_cost'],
  492.                     $config['time_cost'],
  493.                     $config['threads'],
  494.                 ],
  495.             ];
  496.         }
  497.         if ('native' === $config['algorithm']) {
  498.             return [
  499.                 'class' => NativePasswordEncoder::class,
  500.                 'arguments' => [
  501.                     $config['time_cost'],
  502.                     (($config['memory_cost'] ?? 0) << 10) ?: null,
  503.                     $config['cost'],
  504.                 ],
  505.             ];
  506.         }
  507.         if ('sodium' === $config['algorithm']) {
  508.             if (!SodiumPasswordEncoder::isSupported()) {
  509.                 throw new InvalidConfigurationException('Libsodium is not available. Install the sodium extension or use "auto" instead.');
  510.             }
  511.             return [
  512.                 'class' => SodiumPasswordEncoder::class,
  513.                 'arguments' => [
  514.                     $config['time_cost'],
  515.                     (($config['memory_cost'] ?? 0) << 10) ?: null,
  516.                 ],
  517.             ];
  518.         }
  519.         // run-time configured encoder
  520.         return $config;
  521.     }
  522.     // Parses user providers and returns an array of their ids
  523.     private function createUserProviders($configContainerBuilder $container)
  524.     {
  525.         $providerIds = [];
  526.         foreach ($config['providers'] as $name => $provider) {
  527.             $id $this->createUserDaoProvider($name$provider$container);
  528.             $providerIds[str_replace('-''_'$name)] = $id;
  529.         }
  530.         return $providerIds;
  531.     }
  532.     // Parses a <provider> tag and returns the id for the related user provider service
  533.     private function createUserDaoProvider($name$providerContainerBuilder $container)
  534.     {
  535.         $name $this->getUserProviderId($name);
  536.         // Doctrine Entity and In-memory DAO provider are managed by factories
  537.         foreach ($this->userProviderFactories as $factory) {
  538.             $key str_replace('-''_'$factory->getKey());
  539.             if (!empty($provider[$key])) {
  540.                 $factory->create($container$name$provider[$key]);
  541.                 return $name;
  542.             }
  543.         }
  544.         // Existing DAO service provider
  545.         if (isset($provider['id'])) {
  546.             $container->setAlias($name, new Alias($provider['id'], false));
  547.             return $provider['id'];
  548.         }
  549.         // Chain provider
  550.         if (isset($provider['chain'])) {
  551.             $providers = [];
  552.             foreach ($provider['chain']['providers'] as $providerName) {
  553.                 $providers[] = new Reference($this->getUserProviderId($providerName));
  554.             }
  555.             $container
  556.                 ->setDefinition($name, new ChildDefinition('security.user.provider.chain'))
  557.                 ->addArgument(new IteratorArgument($providers));
  558.             return $name;
  559.         }
  560.         throw new InvalidConfigurationException(sprintf('Unable to create definition for "%s" user provider'$name));
  561.     }
  562.     private function getUserProviderId($name)
  563.     {
  564.         return 'security.user.provider.concrete.'.strtolower($name);
  565.     }
  566.     private function createExceptionListener($container$config$id$defaultEntryPoint$stateless)
  567.     {
  568.         $exceptionListenerId 'security.exception_listener.'.$id;
  569.         $listener $container->setDefinition($exceptionListenerId, new ChildDefinition('security.exception_listener'));
  570.         $listener->replaceArgument(3$id);
  571.         $listener->replaceArgument(4null === $defaultEntryPoint null : new Reference($defaultEntryPoint));
  572.         $listener->replaceArgument(8$stateless);
  573.         // access denied handler setup
  574.         if (isset($config['access_denied_handler'])) {
  575.             $listener->replaceArgument(6, new Reference($config['access_denied_handler']));
  576.         } elseif (isset($config['access_denied_url'])) {
  577.             $listener->replaceArgument(5$config['access_denied_url']);
  578.         }
  579.         return $exceptionListenerId;
  580.     }
  581.     private function createSwitchUserListener($container$id$config$defaultProvider$stateless$providerIds)
  582.     {
  583.         $userProvider = isset($config['provider']) ? $this->getUserProviderId($config['provider']) : $defaultProvider;
  584.         if (!$userProvider) {
  585.             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));
  586.         }
  587.         $switchUserListenerId 'security.authentication.switchuser_listener.'.$id;
  588.         $listener $container->setDefinition($switchUserListenerId, new ChildDefinition('security.authentication.switchuser_listener'));
  589.         $listener->replaceArgument(1, new Reference($userProvider));
  590.         $listener->replaceArgument(2, new Reference('security.user_checker.'.$id));
  591.         $listener->replaceArgument(3$id);
  592.         $listener->replaceArgument(6$config['parameter']);
  593.         $listener->replaceArgument(7$config['role']);
  594.         $listener->replaceArgument(9$stateless ?: $config['stateless']);
  595.         return $switchUserListenerId;
  596.     }
  597.     private function createExpression($container$expression)
  598.     {
  599.         if (isset($this->expressions[$id '.security.expression.'.ContainerBuilder::hash($expression)])) {
  600.             return $this->expressions[$id];
  601.         }
  602.         if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
  603.             throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
  604.         }
  605.         $container
  606.             ->register($id'Symfony\Component\ExpressionLanguage\Expression')
  607.             ->setPublic(false)
  608.             ->addArgument($expression)
  609.         ;
  610.         return $this->expressions[$id] = new Reference($id);
  611.     }
  612.     private function createRequestMatcher(ContainerBuilder $container$path null$host nullint $port null$methods = [], array $ips null, array $attributes = [])
  613.     {
  614.         if ($methods) {
  615.             $methods array_map('strtoupper', (array) $methods);
  616.         }
  617.         if (null !== $ips) {
  618.             foreach ($ips as $ip) {
  619.                 $container->resolveEnvPlaceholders($ipnull$usedEnvs);
  620.                 if (!$usedEnvs && !$this->isValidIp($ip)) {
  621.                     throw new \LogicException(sprintf('The given value "%s" in the "security.access_control" config option is not a valid IP address.'$ip));
  622.                 }
  623.                 $usedEnvs null;
  624.             }
  625.         }
  626.         $id '.security.request_matcher.'.ContainerBuilder::hash([$path$host$port$methods$ips$attributes]);
  627.         if (isset($this->requestMatchers[$id])) {
  628.             return $this->requestMatchers[$id];
  629.         }
  630.         // only add arguments that are necessary
  631.         $arguments = [$path$host$methods$ips$attributesnull$port];
  632.         while (\count($arguments) > && !end($arguments)) {
  633.             array_pop($arguments);
  634.         }
  635.         $container
  636.             ->register($id'Symfony\Component\HttpFoundation\RequestMatcher')
  637.             ->setPublic(false)
  638.             ->setArguments($arguments)
  639.         ;
  640.         return $this->requestMatchers[$id] = new Reference($id);
  641.     }
  642.     public function addSecurityListenerFactory(SecurityFactoryInterface $factory)
  643.     {
  644.         $this->factories[$factory->getPosition()][] = $factory;
  645.     }
  646.     public function addUserProviderFactory(UserProviderFactoryInterface $factory)
  647.     {
  648.         $this->userProviderFactories[] = $factory;
  649.     }
  650.     /**
  651.      * {@inheritdoc}
  652.      */
  653.     public function getXsdValidationBasePath()
  654.     {
  655.         return __DIR__.'/../Resources/config/schema';
  656.     }
  657.     public function getNamespace()
  658.     {
  659.         return 'http://symfony.com/schema/dic/security';
  660.     }
  661.     public function getConfiguration(array $configContainerBuilder $container)
  662.     {
  663.         // first assemble the factories
  664.         return new MainConfiguration($this->factories$this->userProviderFactories);
  665.     }
  666.     private function isValidIp(string $cidr): bool
  667.     {
  668.         $cidrParts explode('/'$cidr);
  669.         if (=== \count($cidrParts)) {
  670.             return false !== filter_var($cidrParts[0], FILTER_VALIDATE_IP);
  671.         }
  672.         $ip $cidrParts[0];
  673.         $netmask $cidrParts[1];
  674.         if (!ctype_digit($netmask)) {
  675.             return false;
  676.         }
  677.         if (filter_var($ipFILTER_VALIDATE_IPFILTER_FLAG_IPV4)) {
  678.             return $netmask <= 32;
  679.         }
  680.         if (filter_var($ipFILTER_VALIDATE_IPFILTER_FLAG_IPV6)) {
  681.             return $netmask <= 128;
  682.         }
  683.         return false;
  684.     }
  685. }