vendor/symfony/symfony/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php line 386

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