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