Subida del módulo y tema de PrestaShop

This commit is contained in:
Kaloyan
2026-04-09 18:31:51 +02:00
parent 12c253296f
commit 16b3ff9424
39262 changed files with 7418797 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* Registers the expression language providers.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class AddExpressionLanguageProvidersPass implements CompilerPassInterface
{
/**
* @return void
*/
public function process(ContainerBuilder $container)
{
if ($container->has('security.expression_language')) {
$definition = $container->findDefinition('security.expression_language');
foreach ($container->findTaggedServiceIds('security.expression_language_provider', true) as $id => $attributes) {
$definition->addMethodCall('registerProvider', [new Reference($id)]);
}
}
if (!$container->hasDefinition('cache.system')) {
$container->removeDefinition('cache.security_expression_language');
$container->removeDefinition('cache.security_is_granted_attribute_expression_language');
}
}
}

View File

@@ -0,0 +1,73 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Security\Core\Authorization\Voter\TraceableVoter;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
/**
* Adds all configured security voters to the access decision manager.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class AddSecurityVotersPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;
/**
* @return void
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('security.access.decision_manager')) {
return;
}
$voters = $this->findAndSortTaggedServices('security.voter', $container);
if (!$voters) {
throw new LogicException('No security voters found. You need to tag at least one with "security.voter".');
}
$debug = $container->getParameter('kernel.debug');
$voterServices = [];
foreach ($voters as $voter) {
$voterServiceId = (string) $voter;
$definition = $container->getDefinition($voterServiceId);
$class = $container->getParameterBag()->resolveValue($definition->getClass());
if (!is_a($class, VoterInterface::class, true)) {
throw new LogicException(\sprintf('"%s" must implement the "%s" when used as a voter.', $class, VoterInterface::class));
}
if ($debug) {
$voterServices[] = new Reference($debugVoterServiceId = '.debug.security.voter.'.$voterServiceId);
$container
->register($debugVoterServiceId, TraceableVoter::class)
->addArgument($voter)
->addArgument(new Reference('event_dispatcher'));
} else {
$voterServices[] = $voter;
}
}
$container->getDefinition('security.access.decision_manager')
->replaceArgument(0, new IteratorArgument($voterServices));
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Uses the session domain to restrict allowed redirection targets.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class AddSessionDomainConstraintPass implements CompilerPassInterface
{
/**
* @return void
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasParameter('session.storage.options') || !$container->has('security.http_utils')) {
return;
}
$sessionOptions = $container->getParameter('session.storage.options');
$domainRegexp = empty($sessionOptions['cookie_domain']) ? '%%s' : \sprintf('(?:%%%%s|(?:.+\.)?%s)', preg_quote(trim($sessionOptions['cookie_domain'], '.')));
if ('auto' === ($sessionOptions['cookie_secure'] ?? null)) {
$secureDomainRegexp = \sprintf('{^https://%s$}i', $domainRegexp);
$domainRegexp = 'https?://'.$domainRegexp;
} else {
$secureDomainRegexp = null;
$domainRegexp = (empty($sessionOptions['cookie_secure']) ? 'https?://' : 'https://').$domainRegexp;
}
$container->findDefinition('security.http_utils')
->addArgument(\sprintf('{^%s$}i', $domainRegexp))
->addArgument($secureDomainRegexp);
}
}

View File

@@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Cleans up the remember me verifier cache if cache is missing.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class CleanRememberMeVerifierPass implements CompilerPassInterface
{
/**
* @return void
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('cache.system')) {
$container->removeDefinition('cache.security_token_verifier');
}
}
}

View File

@@ -0,0 +1,73 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher;
/**
* @author Mathieu Lechat <mathieu.lechat@les-tilleuls.coop>
*/
class MakeFirewallsEventDispatcherTraceablePass implements CompilerPassInterface
{
/**
* @return void
*/
public function process(ContainerBuilder $container)
{
if (!$container->has('event_dispatcher') || !$container->hasParameter('security.firewalls')) {
return;
}
if (!$container->getParameter('kernel.debug') || !$container->has('debug.stopwatch')) {
return;
}
$dispatchersId = [];
foreach ($container->getParameter('security.firewalls') as $firewallName) {
$dispatcherId = 'security.event_dispatcher.'.$firewallName;
if (!$container->has($dispatcherId)) {
continue;
}
$dispatchersId[$dispatcherId] = 'debug.'.$dispatcherId;
$container->register($dispatchersId[$dispatcherId], TraceableEventDispatcher::class)
->setDecoratedService($dispatcherId)
->setArguments([
new Reference($dispatchersId[$dispatcherId].'.inner'),
new Reference('debug.stopwatch'),
new Reference('logger', ContainerInterface::NULL_ON_INVALID_REFERENCE),
new Reference('request_stack', ContainerInterface::NULL_ON_INVALID_REFERENCE),
]);
}
foreach (['kernel.event_subscriber', 'kernel.event_listener'] as $tagName) {
foreach ($container->findTaggedServiceIds($tagName) as $taggedServiceId => $tags) {
$taggedServiceDefinition = $container->findDefinition($taggedServiceId);
$taggedServiceDefinition->clearTag($tagName);
foreach ($tags as $tag) {
if ($dispatcherId = $tag['dispatcher'] ?? null) {
$tag['dispatcher'] = $dispatchersId[$dispatcherId] ?? $dispatcherId;
}
$taggedServiceDefinition->addTag($tagName, $tag);
}
}
}
}
}

View File

@@ -0,0 +1,63 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface;
use Symfony\Component\Security\Http\EventListener\CsrfProtectionListener;
use Symfony\Component\Security\Http\EventListener\CsrfTokenClearingLogoutListener;
/**
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
* @author Wouter de Jong <wouter@wouterj.nl>
*
* @internal
*/
class RegisterCsrfFeaturesPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
$this->registerCsrfProtectionListener($container);
$this->registerLogoutHandler($container);
}
private function registerCsrfProtectionListener(ContainerBuilder $container): void
{
if (!$container->has('security.authenticator.manager') || !$container->has('security.csrf.token_manager')) {
return;
}
$container->register('security.listener.csrf_protection', CsrfProtectionListener::class)
->addArgument(new Reference('security.csrf.token_manager'))
->addTag('kernel.event_subscriber');
}
protected function registerLogoutHandler(ContainerBuilder $container): void
{
if (!$container->has('security.logout_listener') || !$container->has('security.csrf.token_storage')) {
return;
}
$csrfTokenStorage = $container->findDefinition('security.csrf.token_storage');
$csrfTokenStorageClass = $container->getParameterBag()->resolveValue($csrfTokenStorage->getClass());
if (!is_subclass_of($csrfTokenStorageClass, ClearableTokenStorageInterface::class)) {
return;
}
$container->register('security.logout.listener.csrf_token_clearing', CsrfTokenClearingLogoutListener::class)
->addArgument(new Reference('security.csrf.token_storage'))
->addTag('kernel.event_subscriber');
}
}

View File

@@ -0,0 +1,86 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
/**
* @author Wouter de Jong <wouter@wouterj.nl>
*/
class RegisterEntryPointPass implements CompilerPassInterface
{
/**
* @return void
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasParameter('security.firewalls')) {
return;
}
$firewalls = $container->getParameter('security.firewalls');
foreach ($firewalls as $firewallName) {
if (!$container->hasDefinition('security.authenticator.manager.'.$firewallName) || !$container->hasParameter('security.'.$firewallName.'._indexed_authenticators')) {
continue;
}
$entryPoints = [];
$indexedAuthenticators = $container->getParameter('security.'.$firewallName.'._indexed_authenticators');
// this is a compile-only parameter, removing it cleans up space and avoids unintended usage
$container->getParameterBag()->remove('security.'.$firewallName.'._indexed_authenticators');
foreach ($indexedAuthenticators as $key => $authenticatorId) {
if (!$container->has($authenticatorId)) {
continue;
}
// because this pass runs before ResolveChildDefinitionPass, child definitions didn't inherit the parent class yet
$definition = $container->findDefinition($authenticatorId);
while (!($authenticatorClass = $definition->getClass()) && $definition instanceof ChildDefinition) {
$definition = $container->findDefinition($definition->getParent());
}
if (is_a($authenticatorClass, AuthenticationEntryPointInterface::class, true)) {
$entryPoints[$key] = $authenticatorId;
}
}
if (!$entryPoints) {
continue;
}
$config = $container->getDefinition('security.firewall.map.config.'.$firewallName);
$configuredEntryPoint = $config->getArgument(7);
if (null !== $configuredEntryPoint) {
// allow entry points to be configured by authenticator key (e.g. "http_basic")
$entryPoint = $entryPoints[$configuredEntryPoint] ?? $configuredEntryPoint;
} elseif (1 === \count($entryPoints)) {
$entryPoint = array_shift($entryPoints);
} else {
$entryPointNames = [];
foreach ($entryPoints as $key => $serviceId) {
$entryPointNames[] = is_numeric($key) ? $serviceId : $key;
}
throw new InvalidConfigurationException(\sprintf('Because you have multiple authenticators in firewall "%s", you need to set the "entry_point" key to one of your authenticators ("%s") or a service ID implementing "%s". The "entry_point" determines what should happen (e.g. redirect to "/login") when an anonymous user tries to access a protected page.', $firewallName, implode('", "', $entryPointNames), AuthenticationEntryPointInterface::class));
}
$config->replaceArgument(7, $entryPoint);
$container->getDefinition('security.exception_listener.'.$firewallName)->replaceArgument(4, new Reference($entryPoint));
}
}
}

View File

@@ -0,0 +1,86 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Security\Core\AuthenticationEvents;
use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
use Symfony\Component\Security\Http\Event\AuthenticationTokenCreatedEvent;
use Symfony\Component\Security\Http\Event\CheckPassportEvent;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\Event\LoginFailureEvent;
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
use Symfony\Component\Security\Http\Event\LogoutEvent;
use Symfony\Component\Security\Http\Event\TokenDeauthenticatedEvent;
use Symfony\Component\Security\Http\SecurityEvents;
/**
* Makes sure all event listeners on the global dispatcher are also listening
* to events on the firewall-specific dispatchers.
*
* This compiler pass must be run after RegisterListenersPass of the
* EventDispatcher component.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*
* @internal
*/
class RegisterGlobalSecurityEventListenersPass implements CompilerPassInterface
{
private const EVENT_BUBBLING_EVENTS = [
CheckPassportEvent::class,
LoginFailureEvent::class,
LoginSuccessEvent::class,
LogoutEvent::class,
AuthenticationTokenCreatedEvent::class,
AuthenticationSuccessEvent::class,
InteractiveLoginEvent::class,
TokenDeauthenticatedEvent::class,
// When events are registered by their name
AuthenticationEvents::AUTHENTICATION_SUCCESS,
SecurityEvents::INTERACTIVE_LOGIN,
];
public function process(ContainerBuilder $container): void
{
if (!$container->has('event_dispatcher') || !$container->hasParameter('security.firewalls')) {
return;
}
$firewallDispatchers = [];
foreach ($container->getParameter('security.firewalls') as $firewallName) {
if (!$container->has('security.event_dispatcher.'.$firewallName)) {
continue;
}
$firewallDispatchers[] = $container->findDefinition('security.event_dispatcher.'.$firewallName);
}
$globalDispatcher = $container->findDefinition('event_dispatcher');
foreach ($globalDispatcher->getMethodCalls() as $methodCall) {
if ('addListener' !== $methodCall[0]) {
continue;
}
$methodCallArguments = $methodCall[1];
if (!\in_array($methodCallArguments[0], self::EVENT_BUBBLING_EVENTS, true)) {
continue;
}
foreach ($firewallDispatchers as $firewallDispatcher) {
$firewallDispatcher->addMethodCall('addListener', $methodCallArguments);
}
}
}
}

View File

@@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator;
/**
* @author Wouter de Jong <wouter@wouterj.nl>
*
* @internal
*/
class RegisterLdapLocatorPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
$definition = $container->setDefinition('security.ldap_locator', new Definition(ServiceLocator::class));
$locators = [];
foreach ($container->findTaggedServiceIds('ldap') as $serviceId => $tags) {
$locators[$serviceId] = new ServiceClosureArgument(new Reference($serviceId));
}
$definition->addArgument($locators);
}
}

View File

@@ -0,0 +1,53 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
use Monolog\Processor\ProcessorInterface;
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
/**
* Injects the session tracker enabler in "security.context_listener" + binds "security.untracked_token_storage" to ProcessorInterface instances.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class RegisterTokenUsageTrackingPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!$container->has('security.untracked_token_storage')) {
return;
}
$processorAutoconfiguration = $container->registerForAutoconfiguration(ProcessorInterface::class);
$processorAutoconfiguration->setBindings($processorAutoconfiguration->getBindings() + [
TokenStorageInterface::class => new BoundArgument(new Reference('security.untracked_token_storage'), false),
]);
if (!$container->has('session.factory')) {
$container->setAlias('security.token_storage', 'security.untracked_token_storage')->setPublic(true);
$container->getDefinition('security.untracked_token_storage')->addTag('kernel.reset', ['method' => 'reset']);
} elseif ($container->hasDefinition('security.context_listener')) {
$tokenStorageClass = $container->getParameterBag()->resolveValue($container->findDefinition('security.token_storage')->getClass());
if (method_exists($tokenStorageClass, 'enableUsageTracking')) {
$container->getDefinition('security.context_listener')
->setArgument(6, [new Reference('security.token_storage'), 'enableUsageTracking']);
}
}
}
}

View File

@@ -0,0 +1,58 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
use Symfony\Bundle\SecurityBundle\RememberMe\DecoratedRememberMeHandler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Replaces the DecoratedRememberMeHandler services with the real definition.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*
* @internal
*/
final class ReplaceDecoratedRememberMeHandlerPass implements CompilerPassInterface
{
private const HANDLER_TAG = 'security.remember_me_handler';
public function process(ContainerBuilder $container): void
{
$handledFirewalls = [];
foreach ($container->findTaggedServiceIds(self::HANDLER_TAG) as $definitionId => $rememberMeHandlerTags) {
$definition = $container->findDefinition($definitionId);
if (DecoratedRememberMeHandler::class !== $definition->getClass()) {
continue;
}
// get the actual custom remember me handler definition (passed to the decorator)
$realRememberMeHandler = $container->findDefinition((string) $definition->getArgument(0));
if (null === $realRememberMeHandler) {
throw new \LogicException(\sprintf('Invalid service definition for custom remember me handler; no service found with ID "%s".', (string) $definition->getArgument(0)));
}
foreach ($rememberMeHandlerTags as $rememberMeHandlerTag) {
// some custom handlers may be used on multiple firewalls in the same application
if (\in_array($rememberMeHandlerTag['firewall'], $handledFirewalls, true)) {
continue;
}
$rememberMeHandler = clone $realRememberMeHandler;
$rememberMeHandler->addTag(self::HANDLER_TAG, $rememberMeHandlerTag);
$container->setDefinition('security.authenticator.remember_me_handler.'.$rememberMeHandlerTag['firewall'], $rememberMeHandler);
$handledFirewalls[] = $rememberMeHandlerTag['firewall'];
}
}
}
}

View File

@@ -0,0 +1,78 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface;
/**
* Sorts firewall listeners based on the execution order provided by FirewallListenerInterface::getPriority().
*
* @author Christian Scheb <me@christianscheb.de>
*/
class SortFirewallListenersPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!$container->hasParameter('security.firewalls')) {
return;
}
foreach ($container->getParameter('security.firewalls') as $firewallName) {
$firewallContextDefinition = $container->getDefinition('security.firewall.map.context.'.$firewallName);
$this->sortFirewallContextListeners($firewallContextDefinition, $container);
}
}
private function sortFirewallContextListeners(Definition $definition, ContainerBuilder $container): void
{
/** @var IteratorArgument $listenerIteratorArgument */
$listenerIteratorArgument = $definition->getArgument(0);
$prioritiesByServiceId = $this->getListenerPriorities($listenerIteratorArgument, $container);
$listeners = $listenerIteratorArgument->getValues();
usort($listeners, fn (Reference $a, Reference $b) => $prioritiesByServiceId[(string) $b] <=> $prioritiesByServiceId[(string) $a]);
$listenerIteratorArgument->setValues(array_values($listeners));
}
private function getListenerPriorities(IteratorArgument $listeners, ContainerBuilder $container): array
{
$priorities = [];
foreach ($listeners->getValues() as $reference) {
$id = (string) $reference;
$def = $container->getDefinition($id);
// We must assume that the class value has been correctly filled, even if the service is created by a factory
$class = $def->getClass();
if (!$r = $container->getReflectionClass($class)) {
throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
}
$priority = 0;
if ($r->isSubclassOf(FirewallListenerInterface::class)) {
$priority = $r->getMethod('getPriority')->invoke(null);
}
$priorities[$id] = $priority;
}
return $priorities;
}
}

View File

@@ -0,0 +1,479 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AbstractFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy;
/**
* SecurityExtension configuration structure.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class MainConfiguration implements ConfigurationInterface
{
/** @internal */
public const STRATEGY_AFFIRMATIVE = 'affirmative';
/** @internal */
public const STRATEGY_CONSENSUS = 'consensus';
/** @internal */
public const STRATEGY_UNANIMOUS = 'unanimous';
/** @internal */
public const STRATEGY_PRIORITY = 'priority';
private array $factories;
private array $userProviderFactories;
/**
* @param array<AuthenticatorFactoryInterface> $factories
*/
public function __construct(array $factories, array $userProviderFactories)
{
$this->factories = $factories;
$this->userProviderFactories = $userProviderFactories;
}
/**
* Generates the configuration tree builder.
*/
public function getConfigTreeBuilder(): TreeBuilder
{
$tb = new TreeBuilder('security');
$rootNode = $tb->getRootNode();
$rootNode
->children()
->scalarNode('access_denied_url')->defaultNull()->example('/foo/error403')->end()
->enumNode('session_fixation_strategy')
->values([SessionAuthenticationStrategy::NONE, SessionAuthenticationStrategy::MIGRATE, SessionAuthenticationStrategy::INVALIDATE])
->defaultValue(SessionAuthenticationStrategy::MIGRATE)
->end()
->booleanNode('hide_user_not_found')->defaultTrue()->end()
->booleanNode('erase_credentials')->defaultTrue()->end()
->booleanNode('enable_authenticator_manager')->setDeprecated('symfony/security-bundle', '6.2', 'The "%node%" option at "%path%" is deprecated.')->defaultTrue()->end()
->arrayNode('access_decision_manager')
->addDefaultsIfNotSet()
->children()
->enumNode('strategy')
->values($this->getAccessDecisionStrategies())
->end()
->scalarNode('service')->end()
->scalarNode('strategy_service')->end()
->booleanNode('allow_if_all_abstain')->defaultFalse()->end()
->booleanNode('allow_if_equal_granted_denied')->defaultTrue()->end()
->end()
->validate()
->ifTrue(fn ($v) => isset($v['strategy'], $v['service']))
->thenInvalid('"strategy" and "service" cannot be used together.')
->end()
->validate()
->ifTrue(fn ($v) => isset($v['strategy'], $v['strategy_service']))
->thenInvalid('"strategy" and "strategy_service" cannot be used together.')
->end()
->validate()
->ifTrue(fn ($v) => isset($v['service'], $v['strategy_service']))
->thenInvalid('"service" and "strategy_service" cannot be used together.')
->end()
->end()
->end()
;
$this->addPasswordHashersSection($rootNode);
$this->addProvidersSection($rootNode);
$this->addFirewallsSection($rootNode, $this->factories);
$this->addAccessControlSection($rootNode);
$this->addRoleHierarchySection($rootNode);
return $tb;
}
private function addRoleHierarchySection(ArrayNodeDefinition $rootNode): void
{
$rootNode
->fixXmlConfig('role', 'role_hierarchy')
->children()
->arrayNode('role_hierarchy')
->useAttributeAsKey('id')
->prototype('array')
->performNoDeepMerging()
->beforeNormalization()->ifString()->then(fn ($v) => ['value' => $v])->end()
->beforeNormalization()
->ifTrue(fn ($v) => \is_array($v) && isset($v['value']))
->then(fn ($v) => preg_split('/\s*,\s*/', $v['value']))
->end()
->prototype('scalar')->end()
->end()
->end()
->end()
;
}
private function addAccessControlSection(ArrayNodeDefinition $rootNode): void
{
$rootNode
->fixXmlConfig('rule', 'access_control')
->children()
->arrayNode('access_control')
->cannotBeOverwritten()
->prototype('array')
->fixXmlConfig('ip')
->fixXmlConfig('method')
->fixXmlConfig('attribute')
->children()
->scalarNode('request_matcher')->defaultNull()->end()
->scalarNode('requires_channel')->defaultNull()->end()
->scalarNode('path')
->defaultNull()
->info('use the urldecoded format')
->example('^/path to resource/')
->end()
->scalarNode('host')->defaultNull()->end()
->integerNode('port')->defaultNull()->end()
->arrayNode('ips')
->beforeNormalization()->ifString()->then(fn ($v) => [$v])->end()
->prototype('scalar')->end()
->end()
->arrayNode('attributes')
->useAttributeAsKey('key')
->prototype('scalar')->end()
->end()
->scalarNode('route')->defaultNull()->end()
->arrayNode('methods')
->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end()
->prototype('scalar')->end()
->end()
->scalarNode('allow_if')->defaultNull()->end()
->end()
->fixXmlConfig('role')
->children()
->arrayNode('roles')
->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end()
->prototype('scalar')->end()
->end()
->end()
->end()
->end()
->end()
;
}
/**
* @param array<AuthenticatorFactoryInterface> $factories
*/
private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $factories): void
{
$firewallNodeBuilder = $rootNode
->fixXmlConfig('firewall')
->children()
->arrayNode('firewalls')
->isRequired()
->requiresAtLeastOneElement()
->disallowNewKeysInSubsequentConfigs()
->useAttributeAsKey('name')
->prototype('array')
->fixXmlConfig('required_badge')
->children()
;
$firewallNodeBuilder
->scalarNode('pattern')
->beforeNormalization()
->ifArray()
->then(fn ($v) => \sprintf('(?:%s)', implode('|', $v)))
->end()
->end()
->scalarNode('host')->end()
->arrayNode('methods')
->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end()
->prototype('scalar')->end()
->end()
->booleanNode('security')->defaultTrue()->end()
->scalarNode('user_checker')
->defaultValue('security.user_checker')
->treatNullLike('security.user_checker')
->info('The UserChecker to use when authenticating users in this firewall.')
->end()
->scalarNode('request_matcher')->end()
->scalarNode('access_denied_url')->end()
->scalarNode('access_denied_handler')->end()
->scalarNode('entry_point')
->info(\sprintf('An enabled authenticator name or a service id that implements "%s"', AuthenticationEntryPointInterface::class))
->end()
->scalarNode('provider')->end()
->booleanNode('stateless')->defaultFalse()->end()
->booleanNode('lazy')->defaultFalse()->end()
->scalarNode('context')->cannotBeEmpty()->end()
->arrayNode('logout')
->treatTrueLike([])
->canBeUnset()
->beforeNormalization()
->ifTrue(fn ($v): bool => isset($v['csrf_token_generator']) && !isset($v['csrf_token_manager']))
->then(function (array $v): array {
$v['csrf_token_manager'] = $v['csrf_token_generator'];
return $v;
})
->end()
->beforeNormalization()
->ifTrue(fn ($v): bool => \is_array($v) && (isset($v['csrf_token_manager']) xor isset($v['enable_csrf'])))
->then(function (array $v): array {
if (isset($v['csrf_token_manager'])) {
$v['enable_csrf'] = true;
} elseif ($v['enable_csrf']) {
$v['csrf_token_manager'] = 'security.csrf.token_manager';
}
return $v;
})
->end()
->children()
->booleanNode('enable_csrf')->defaultNull()->end()
->scalarNode('csrf_token_id')->defaultValue('logout')->end()
->scalarNode('csrf_parameter')->defaultValue('_csrf_token')->end()
->scalarNode('csrf_token_generator')
->setDeprecated(
'symfony/security-bundle',
'6.3',
'The "%node%" option is deprecated. Use "csrf_token_manager" instead.'
)
->end()
->scalarNode('csrf_token_manager')->end()
->scalarNode('path')->defaultValue('/logout')->end()
->scalarNode('target')->defaultValue('/')->end()
->booleanNode('invalidate_session')->defaultTrue()->end()
->arrayNode('clear_site_data')
->performNoDeepMerging()
->beforeNormalization()->ifString()->then(fn ($v) => $v ? array_map('trim', explode(',', $v)) : [])->end()
->enumPrototype()
->values([
'*', 'cache', 'cookies', 'storage', 'executionContexts',
])
->end()
->end()
->end()
->fixXmlConfig('delete_cookie')
->children()
->arrayNode('delete_cookies')
->normalizeKeys(false)
->beforeNormalization()
->ifTrue(fn ($v) => \is_array($v) && \is_int(key($v)))
->then(fn ($v) => array_map(fn ($v) => ['name' => $v], $v))
->end()
->useAttributeAsKey('name')
->prototype('array')
->children()
->scalarNode('path')->defaultNull()->end()
->scalarNode('domain')->defaultNull()->end()
->scalarNode('secure')->defaultFalse()->end()
->scalarNode('samesite')->defaultNull()->end()
->scalarNode('partitioned')->defaultFalse()->end()
->end()
->end()
->end()
->end()
->end()
->arrayNode('switch_user')
->canBeUnset()
->children()
->scalarNode('provider')->end()
->scalarNode('parameter')->defaultValue('_switch_user')->end()
->scalarNode('role')->defaultValue('ROLE_ALLOWED_TO_SWITCH')->end()
->scalarNode('target_route')->defaultValue(null)->end()
->end()
->end()
->arrayNode('required_badges')
->info('A list of badges that must be present on the authenticated passport.')
->validate()
->always()
->then(function ($requiredBadges) {
return array_map(function ($requiredBadge) {
if (class_exists($requiredBadge)) {
return $requiredBadge;
}
if (!str_contains($requiredBadge, '\\')) {
$fqcn = 'Symfony\Component\Security\Http\Authenticator\Passport\Badge\\'.$requiredBadge;
if (class_exists($fqcn)) {
return $fqcn;
}
}
throw new InvalidConfigurationException(\sprintf('Undefined security Badge class "%s" set in "security.firewall.required_badges".', $requiredBadge));
}, $requiredBadges);
})
->end()
->prototype('scalar')->end()
->end()
;
$abstractFactoryKeys = [];
foreach ($factories as $factory) {
$name = str_replace('-', '_', $factory->getKey());
$factoryNode = $firewallNodeBuilder->arrayNode($name)
->canBeUnset()
;
if ($factory instanceof AbstractFactory) {
$abstractFactoryKeys[] = $name;
}
$factory->addConfiguration($factoryNode);
}
// check for unreachable check paths
$firewallNodeBuilder
->end()
->validate()
->ifTrue(fn ($v) => true === $v['security'] && isset($v['pattern']) && !isset($v['request_matcher']))
->then(function ($firewall) use ($abstractFactoryKeys) {
foreach ($abstractFactoryKeys as $k) {
if (!isset($firewall[$k]['check_path'])) {
continue;
}
if (str_contains($firewall[$k]['check_path'], '/') && !preg_match('#'.$firewall['pattern'].'#', $firewall[$k]['check_path'])) {
throw new \LogicException(\sprintf('The check_path "%s" for login method "%s" is not matched by the firewall pattern "%s".', $firewall[$k]['check_path'], $k, $firewall['pattern']));
}
}
return $firewall;
})
->end()
;
}
private function addProvidersSection(ArrayNodeDefinition $rootNode): void
{
$providerNodeBuilder = $rootNode
->fixXmlConfig('provider')
->children()
->arrayNode('providers')
->example([
'my_memory_provider' => [
'memory' => [
'users' => [
'foo' => ['password' => 'foo', 'roles' => 'ROLE_USER'],
'bar' => ['password' => 'bar', 'roles' => '[ROLE_USER, ROLE_ADMIN]'],
],
],
],
'my_entity_provider' => ['entity' => ['class' => 'SecurityBundle:User', 'property' => 'username']],
])
->requiresAtLeastOneElement()
->useAttributeAsKey('name')
->prototype('array')
;
$providerNodeBuilder
->children()
->scalarNode('id')->end()
->arrayNode('chain')
->fixXmlConfig('provider')
->children()
->arrayNode('providers')
->beforeNormalization()
->ifString()
->then(fn ($v) => preg_split('/\s*,\s*/', $v))
->end()
->prototype('scalar')->end()
->end()
->end()
->end()
->end()
;
foreach ($this->userProviderFactories as $factory) {
$name = str_replace('-', '_', $factory->getKey());
$factoryNode = $providerNodeBuilder->children()->arrayNode($name)->canBeUnset();
$factory->addConfiguration($factoryNode);
}
$providerNodeBuilder
->validate()
->ifTrue(fn ($v) => \count($v) > 1)
->thenInvalid('You cannot set multiple provider types for the same provider')
->end()
->validate()
->ifTrue(fn ($v) => 0 === \count($v))
->thenInvalid('You must set a provider definition for the provider.')
->end()
;
}
private function addPasswordHashersSection(ArrayNodeDefinition $rootNode): void
{
$rootNode
->fixXmlConfig('password_hasher')
->children()
->arrayNode('password_hashers')
->example([
'App\Entity\User1' => 'auto',
'App\Entity\User2' => [
'algorithm' => 'auto',
'time_cost' => 8,
'cost' => 13,
],
])
->requiresAtLeastOneElement()
->useAttributeAsKey('class')
->prototype('array')
->canBeUnset()
->performNoDeepMerging()
->beforeNormalization()->ifString()->then(fn ($v) => ['algorithm' => $v])->end()
->children()
->scalarNode('algorithm')
->cannotBeEmpty()
->validate()
->ifTrue(fn ($v) => !\is_string($v))
->thenInvalid('You must provide a string value.')
->end()
->end()
->arrayNode('migrate_from')
->prototype('scalar')->end()
->beforeNormalization()->castToArray()->end()
->end()
->scalarNode('hash_algorithm')->info('Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms.')->defaultValue('sha512')->end()
->scalarNode('key_length')->defaultValue(40)->end()
->booleanNode('ignore_case')->defaultFalse()->end()
->booleanNode('encode_as_base64')->defaultTrue()->end()
->scalarNode('iterations')->defaultValue(5000)->end()
->integerNode('cost')
->min(4)
->max(31)
->defaultNull()
->end()
->scalarNode('memory_cost')->defaultNull()->end()
->scalarNode('time_cost')->defaultNull()->end()
->scalarNode('id')->end()
->end()
->end()
->end()
->end();
}
private function getAccessDecisionStrategies(): array
{
return [
self::STRATEGY_AFFIRMATIVE,
self::STRATEGY_CONSENSUS,
self::STRATEGY_UNANIMOUS,
self::STRATEGY_PRIORITY,
];
}
}

View File

@@ -0,0 +1,89 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken;
use Jose\Component\Core\Algorithm;
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Configures a token handler for decoding and validating an OIDC token.
*/
class OidcTokenHandlerFactory implements TokenHandlerFactoryInterface
{
public function create(ContainerBuilder $container, string $id, array|string $config): void
{
$tokenHandlerDefinition = $container->setDefinition($id, (new ChildDefinition('security.access_token_handler.oidc'))
->replaceArgument(2, $config['audience'])
->replaceArgument(3, $config['issuers'])
->replaceArgument(4, $config['claim'])
);
if (!ContainerBuilder::willBeAvailable('web-token/jwt-core', Algorithm::class, ['symfony/security-bundle'])) {
throw new LogicException('You cannot use the "oidc" token handler since "web-token/jwt-core" is not installed. Try running "composer require web-token/jwt-core".');
}
// @see Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SignatureAlgorithmFactory
// for supported algorithms
if (\in_array($config['algorithm'], ['ES256', 'ES384', 'ES512'], true)) {
$tokenHandlerDefinition->replaceArgument(0, new Reference('security.access_token_handler.oidc.signature.'.$config['algorithm']));
} else {
$tokenHandlerDefinition->replaceArgument(0, (new ChildDefinition('security.access_token_handler.oidc.signature'))
->replaceArgument(0, $config['algorithm'])
);
}
$tokenHandlerDefinition->replaceArgument(1, (new ChildDefinition('security.access_token_handler.oidc.jwk'))
->replaceArgument(0, $config['key'])
);
}
public function getKey(): string
{
return 'oidc';
}
public function addConfiguration(NodeBuilder $node): void
{
$node
->arrayNode($this->getKey())
->fixXmlConfig($this->getKey())
->children()
->scalarNode('claim')
->info('Claim which contains the user identifier (e.g.: sub, email..).')
->defaultValue('sub')
->end()
->scalarNode('audience')
->info('Audience set in the token, for validation purpose.')
->isRequired()
->end()
->arrayNode('issuers')
->info('Issuers allowed to generate the token, for validation purpose.')
->isRequired()
->prototype('scalar')->end()
->end()
->scalarNode('algorithm')
->info('Algorithm used to sign the token.')
->isRequired()
->end()
->scalarNode('key')
->info('JSON-encoded JWK used to sign the token (must contain a "kty" key).')
->isRequired()
->end()
->end()
->end()
;
}
}

View File

@@ -0,0 +1,74 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken;
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Contracts\HttpClient\HttpClientInterface;
/**
* Configures a token handler for an OIDC server.
*/
class OidcUserInfoTokenHandlerFactory implements TokenHandlerFactoryInterface
{
public function create(ContainerBuilder $container, string $id, array|string $config): void
{
$clientDefinition = (new ChildDefinition('security.access_token_handler.oidc_user_info.http_client'))
->replaceArgument(0, ['base_uri' => $config['base_uri']]);
if (isset($config['client'])) {
$clientDefinition->setFactory([new Reference($config['client']), 'withOptions']);
} elseif (!ContainerBuilder::willBeAvailable('symfony/http-client', HttpClientInterface::class, ['symfony/security-bundle'])) {
throw new LogicException('You cannot use the "oidc_user_info" token handler since the HttpClient component is not installed. Try running "composer require symfony/http-client".');
}
$container->setDefinition($id, new ChildDefinition('security.access_token_handler.oidc_user_info'))
->replaceArgument(0, $clientDefinition)
->replaceArgument(2, $config['claim']);
}
public function getKey(): string
{
return 'oidc_user_info';
}
public function addConfiguration(NodeBuilder $node): void
{
$node
->arrayNode($this->getKey())
->fixXmlConfig($this->getKey())
->beforeNormalization()
->ifString()
->then(fn ($v) => ['claim' => 'sub', 'base_uri' => $v])
->end()
->children()
->scalarNode('base_uri')
->info('Base URI of the userinfo endpoint on the OIDC server.')
->isRequired()
->cannotBeEmpty()
->end()
->scalarNode('claim')
->info('Claim which contains the user identifier (e.g. sub, email, etc.).')
->defaultValue('sub')
->cannotBeEmpty()
->end()
->scalarNode('client')
->info('HttpClient service id to use to call the OIDC server.')
->end()
->end()
->end()
;
}
}

View File

@@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken;
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Configures a token handler from a service id.
*
* @see \Symfony\Bundle\SecurityBundle\Tests\DependencyInjection\Security\Factory\AccessTokenFactoryTest
*/
class ServiceTokenHandlerFactory implements TokenHandlerFactoryInterface
{
public function create(ContainerBuilder $container, string $id, array|string $config): void
{
$container->setDefinition($id, new ChildDefinition($config));
}
public function getKey(): string
{
return 'id';
}
public function addConfiguration(NodeBuilder $node): void
{
$node->scalarNode($this->getKey())->end();
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken;
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Allows creating configurable token handlers.
*/
interface TokenHandlerFactoryInterface
{
/**
* Creates a generic token handler service.
*/
public function create(ContainerBuilder $container, string $id, array|string $config): void;
/**
* Gets a generic token handler configuration key.
*/
public function getKey(): string;
/**
* Adds a generic token handler configuration.
*/
public function addConfiguration(NodeBuilder $node): void;
}

View File

@@ -0,0 +1,137 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* @author Fabien Potencier <fabien@symfony.com>
* @author Lukas Kahwe Smith <smith@pooteeweet.org>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
abstract class AbstractFactory implements AuthenticatorFactoryInterface
{
protected $options = [
'check_path' => '/login_check',
'use_forward' => false,
'require_previous_session' => false,
'login_path' => '/login',
];
protected $defaultSuccessHandlerOptions = [
'always_use_default_target_path' => false,
'default_target_path' => '/',
'login_path' => '/login',
'target_path_parameter' => '_target_path',
'use_referer' => false,
];
protected $defaultFailureHandlerOptions = [
'failure_path' => null,
'failure_forward' => false,
'login_path' => '/login',
'failure_path_parameter' => '_failure_path',
];
final public function addOption(string $name, mixed $default = null): void
{
$this->options[$name] = $default;
}
/**
* @return void
*/
public function addConfiguration(NodeDefinition $node)
{
$builder = $node->children();
$builder
->scalarNode('provider')->end()
->booleanNode('remember_me')->defaultTrue()->end()
->scalarNode('success_handler')->end()
->scalarNode('failure_handler')->end()
;
foreach (array_merge($this->options, $this->defaultSuccessHandlerOptions, $this->defaultFailureHandlerOptions) as $name => $default) {
if ('require_previous_session' === $name) {
$builder
->booleanNode($name)
->setDeprecated('symfony/security-bundle', '6.4', 'Option "%node%" at "%path%" is deprecated, it will be removed in version 7.0. Setting it has no effect anymore.')
->defaultValue($default);
} elseif (\is_bool($default)) {
$builder->booleanNode($name)->defaultValue($default);
} else {
$builder->scalarNode($name)->defaultValue($default);
}
}
}
/**
* @return string
*/
protected function createAuthenticationSuccessHandler(ContainerBuilder $container, string $id, array $config)
{
$successHandlerId = $this->getSuccessHandlerId($id);
$options = array_intersect_key($config, $this->defaultSuccessHandlerOptions);
if (isset($config['success_handler'])) {
$successHandler = $container->setDefinition($successHandlerId, new ChildDefinition('security.authentication.custom_success_handler'));
$successHandler->replaceArgument(0, new ChildDefinition($config['success_handler']));
$successHandler->replaceArgument(1, $options);
$successHandler->replaceArgument(2, $id);
} else {
$successHandler = $container->setDefinition($successHandlerId, new ChildDefinition('security.authentication.success_handler'));
$successHandler->addMethodCall('setOptions', [$options]);
$successHandler->addMethodCall('setFirewallName', [$id]);
}
return $successHandlerId;
}
/**
* @return string
*/
protected function createAuthenticationFailureHandler(ContainerBuilder $container, string $id, array $config)
{
$id = $this->getFailureHandlerId($id);
$options = array_intersect_key($config, $this->defaultFailureHandlerOptions);
if (isset($config['failure_handler'])) {
$failureHandler = $container->setDefinition($id, new ChildDefinition('security.authentication.custom_failure_handler'));
$failureHandler->replaceArgument(0, new ChildDefinition($config['failure_handler']));
$failureHandler->replaceArgument(1, $options);
} else {
$failureHandler = $container->setDefinition($id, new ChildDefinition('security.authentication.failure_handler'));
$failureHandler->addMethodCall('setOptions', [$options]);
}
return $id;
}
/**
* @return string
*/
protected function getSuccessHandlerId(string $id)
{
return 'security.authentication.success_handler.'.$id.'.'.str_replace('-', '_', $this->getKey());
}
/**
* @return string
*/
protected function getFailureHandlerId(string $id)
{
return 'security.authentication.failure_handler.'.$id.'.'.str_replace('-', '_', $this->getKey());
}
}

View File

@@ -0,0 +1,166 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\TokenHandlerFactoryInterface;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* AccessTokenFactory creates services for Access Token authentication.
*
* @author Florent Morselli <florent.morselli@spomky-labs.com>
*
* @internal
*/
final class AccessTokenFactory extends AbstractFactory implements StatelessAuthenticatorFactoryInterface
{
private const PRIORITY = -40;
/**
* @param array<TokenHandlerFactoryInterface> $tokenHandlerFactories
*/
public function __construct(private readonly array $tokenHandlerFactories)
{
$this->options = [];
$this->defaultFailureHandlerOptions = [];
$this->defaultSuccessHandlerOptions = [];
}
public function addConfiguration(NodeDefinition $node): void
{
parent::addConfiguration($node);
$builder = $node->children();
$builder
->scalarNode('realm')->defaultNull()->end()
->arrayNode('token_extractors')
->fixXmlConfig('token_extractors')
->beforeNormalization()
->ifString()
->then(fn ($v) => [$v])
->end()
->cannotBeEmpty()
->defaultValue([
'security.access_token_extractor.header',
])
->scalarPrototype()->end()
->end()
;
$tokenHandlerNodeBuilder = $builder
->arrayNode('token_handler')
->example([
'id' => 'App\Security\CustomTokenHandler',
])
->beforeNormalization()
->ifString()
->then(fn ($v) => ['id' => $v])
->end()
->beforeNormalization()
->ifTrue(fn ($v) => \is_array($v) && 1 < \count($v))
->then(fn () => throw new InvalidConfigurationException('You cannot configure multiple token handlers.'))
->end()
// "isRequired" must be set otherwise the following custom validation is not called
->isRequired()
->beforeNormalization()
->ifTrue(fn ($v) => \is_array($v) && !$v)
->then(fn () => throw new InvalidConfigurationException('You must set a token handler.'))
->end()
->children()
;
foreach ($this->tokenHandlerFactories as $factory) {
$factory->addConfiguration($tokenHandlerNodeBuilder);
}
$tokenHandlerNodeBuilder->end();
}
public function getPriority(): int
{
return self::PRIORITY;
}
public function getKey(): string
{
return 'access_token';
}
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, ?string $userProviderId): string
{
$successHandler = isset($config['success_handler']) ? new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config)) : null;
$failureHandler = isset($config['failure_handler']) ? new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config)) : null;
$authenticatorId = \sprintf('security.authenticator.access_token.%s', $firewallName);
$extractorId = $this->createExtractor($container, $firewallName, $config['token_extractors']);
$tokenHandlerId = $this->createTokenHandler($container, $firewallName, $config['token_handler'], $userProviderId);
$container
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.access_token'))
->replaceArgument(0, new Reference($tokenHandlerId))
->replaceArgument(1, new Reference($extractorId))
->replaceArgument(2, $userProviderId ? new Reference($userProviderId) : null)
->replaceArgument(3, $successHandler)
->replaceArgument(4, $failureHandler)
->replaceArgument(5, $config['realm'])
;
return $authenticatorId;
}
/**
* @param array<string> $extractors
*/
private function createExtractor(ContainerBuilder $container, string $firewallName, array $extractors): string
{
$aliases = [
'query_string' => 'security.access_token_extractor.query_string',
'request_body' => 'security.access_token_extractor.request_body',
'header' => 'security.access_token_extractor.header',
];
$extractors = array_map(fn ($extractor) => $aliases[$extractor] ?? $extractor, $extractors);
if (1 === \count($extractors)) {
return current($extractors);
}
$extractorId = \sprintf('security.authenticator.access_token.chain_extractor.%s', $firewallName);
$container
->setDefinition($extractorId, new ChildDefinition('security.authenticator.access_token.chain_extractor'))
->replaceArgument(0, array_map(fn (string $extractorId): Reference => new Reference($extractorId), $extractors))
;
return $extractorId;
}
private function createTokenHandler(ContainerBuilder $container, string $firewallName, array $config, ?string $userProviderId): string
{
$key = array_keys($config)[0];
$id = \sprintf('security.access_token_handler.%s', $firewallName);
foreach ($this->tokenHandlerFactories as $factory) {
if ($key !== $factory->getKey()) {
continue;
}
$factory->create($container, $id, $config[$key], $userProviderId);
}
return $id;
}
}

View File

@@ -0,0 +1,46 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* @author Wouter de Jong <wouter@wouterj.nl>
*/
interface AuthenticatorFactoryInterface
{
/**
* Defines the priority at which the authenticator is called.
*/
public function getPriority(): int;
/**
* Defines the configuration key used to reference the provider
* in the firewall configuration.
*/
public function getKey(): string;
/**
* @return void
*/
public function addConfiguration(NodeDefinition $builder);
/**
* Creates the authenticator service(s) for the provided configuration.
*
* @param array<string, mixed> $config
*
* @return string|string[] The authenticator service ID(s) to be used by the firewall
*/
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string|array;
}

View File

@@ -0,0 +1,64 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* @author Wouter de Jong <wouter@wouterj.nl>
*
* @internal
*/
class CustomAuthenticatorFactory implements AuthenticatorFactoryInterface
{
public function getPriority(): int
{
return 0;
}
public function getKey(): string
{
return 'custom_authenticators';
}
/**
* @param ArrayNodeDefinition $builder
*/
public function addConfiguration(NodeDefinition $builder): void
{
$builder
->info('An array of service ids for all of your "authenticators"')
->requiresAtLeastOneElement()
->prototype('scalar')->end();
// get the parent array node builder ("firewalls") from inside the children builder
$factoryRootNode = $builder->end()->end();
$factoryRootNode
->fixXmlConfig('custom_authenticator')
->validate()
->ifTrue(fn ($v) => isset($v['custom_authenticators']) && empty($v['custom_authenticators']))
->then(function ($v) {
unset($v['custom_authenticators']);
return $v;
})
->end()
;
}
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): array
{
return $config;
}
}

View File

@@ -0,0 +1,31 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Can be implemented by a security factory to add a listener to the firewall.
*
* @author Christian Scheb <me@christianscheb.de>
*/
interface FirewallListenerFactoryInterface
{
/**
* Creates the firewall listener services for the provided configuration.
*
* @param array<string, mixed> $config
*
* @return string[] The listener service IDs to be used by the firewall
*/
public function createListeners(ContainerBuilder $container, string $firewallName, array $config): array;
}

View File

@@ -0,0 +1,85 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* FormLoginFactory creates services for form login authentication.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*
* @internal
*/
class FormLoginFactory extends AbstractFactory
{
public const PRIORITY = -30;
public function __construct()
{
$this->addOption('username_parameter', '_username');
$this->addOption('password_parameter', '_password');
$this->addOption('csrf_parameter', '_csrf_token');
$this->addOption('csrf_token_id', 'authenticate');
$this->addOption('enable_csrf', false);
$this->addOption('post_only', true);
$this->addOption('form_only', false);
}
public function getPriority(): int
{
return self::PRIORITY;
}
public function getKey(): string
{
return 'form-login';
}
public function addConfiguration(NodeDefinition $node): void
{
parent::addConfiguration($node);
$node
->children()
->scalarNode('csrf_token_generator')->cannotBeEmpty()->end()
->end()
;
}
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string
{
if (isset($config['csrf_token_generator'])) {
throw new InvalidConfigurationException('The "csrf_token_generator" on "form_login" does not exist, use "enable_csrf" instead.');
}
$authenticatorId = 'security.authenticator.form_login.'.$firewallName;
$options = array_intersect_key($config, $this->options);
$authenticator = $container
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.form_login'))
->replaceArgument(1, new Reference($userProviderId))
->replaceArgument(2, new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config)))
->replaceArgument(3, new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config)))
->replaceArgument(4, $options);
if ($options['use_forward'] ?? false) {
$authenticator->addMethodCall('setHttpKernel', [new Reference('http_kernel')]);
}
return $authenticatorId;
}
}

View File

@@ -0,0 +1,42 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
/**
* FormLoginLdapFactory creates services for form login ldap authentication.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
* @author Charles Sarrazin <charles@sarraz.in>
*
* @internal
*/
class FormLoginLdapFactory extends FormLoginFactory
{
use LdapFactoryTrait;
public function addConfiguration(NodeDefinition $node): void
{
parent::addConfiguration($node);
$node
->children()
->scalarNode('service')->defaultValue('ldap')->end()
->scalarNode('dn_string')->defaultValue('{username}')->end()
->scalarNode('query_string')->end()
->scalarNode('search_dn')->defaultValue('')->end()
->scalarNode('search_password')->defaultValue('')->end()
->end()
;
}
}

View File

@@ -0,0 +1,60 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* HttpBasicFactory creates services for HTTP basic authentication.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @internal
*/
class HttpBasicFactory implements AuthenticatorFactoryInterface
{
public const PRIORITY = -50;
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string
{
$authenticatorId = 'security.authenticator.http_basic.'.$firewallName;
$container
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.http_basic'))
->replaceArgument(0, $config['realm'])
->replaceArgument(1, new Reference($userProviderId));
return $authenticatorId;
}
public function getPriority(): int
{
return self::PRIORITY;
}
public function getKey(): string
{
return 'http-basic';
}
public function addConfiguration(NodeDefinition $node): void
{
$node
->children()
->scalarNode('provider')->end()
->scalarNode('realm')->defaultValue('Secured Area')->end()
->end()
;
}
}

View File

@@ -0,0 +1,87 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Security\Core\Exception\LogicException;
/**
* HttpBasicFactory creates services for HTTP basic authentication.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Grégoire Pineau <lyrixx@lyrixx.info>
* @author Charles Sarrazin <charles@sarraz.in>
*
* @internal
*/
class HttpBasicLdapFactory extends HttpBasicFactory
{
use LdapFactoryTrait;
public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint): array
{
$provider = 'security.authentication.provider.ldap_bind.'.$id;
$definition = $container
->setDefinition($provider, new ChildDefinition('security.authentication.provider.ldap_bind'))
->replaceArgument(0, new Reference($userProvider))
->replaceArgument(1, new Reference('security.user_checker.'.$id))
->replaceArgument(2, $id)
->replaceArgument(3, new Reference($config['service']))
->replaceArgument(4, $config['dn_string'])
->replaceArgument(6, $config['search_dn'])
->replaceArgument(7, $config['search_password'])
;
// entry point
$entryPointId = $defaultEntryPoint;
if (null === $entryPointId) {
$entryPointId = 'security.authentication.basic_entry_point.'.$id;
$container
->setDefinition($entryPointId, new ChildDefinition('security.authentication.basic_entry_point'))
->addArgument($config['realm']);
}
if (!empty($config['query_string'])) {
if ('' === $config['search_dn'] || '' === $config['search_password']) {
throw new LogicException('Using the "query_string" config without using a "search_dn" and a "search_password" is not supported.');
}
$definition->addMethodCall('setQueryString', [$config['query_string']]);
}
// listener
$listenerId = 'security.authentication.listener.basic.'.$id;
$listener = $container->setDefinition($listenerId, new ChildDefinition('security.authentication.listener.basic'));
$listener->replaceArgument(2, $id);
$listener->replaceArgument(3, new Reference($entryPointId));
return [$provider, $listenerId, $entryPointId];
}
public function addConfiguration(NodeDefinition $node): void
{
parent::addConfiguration($node);
$node
->children()
->scalarNode('service')->defaultValue('ldap')->end()
->scalarNode('dn_string')->defaultValue('{username}')->end()
->scalarNode('query_string')->end()
->scalarNode('search_dn')->defaultValue('')->end()
->scalarNode('search_password')->defaultValue('')->end()
->end()
;
}
}

View File

@@ -0,0 +1,60 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* JsonLoginFactory creates services for JSON login authentication.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*
* @internal
*/
class JsonLoginFactory extends AbstractFactory
{
public const PRIORITY = -40;
public function __construct()
{
$this->addOption('username_path', 'username');
$this->addOption('password_path', 'password');
$this->defaultFailureHandlerOptions = [];
$this->defaultSuccessHandlerOptions = [];
}
public function getPriority(): int
{
return self::PRIORITY;
}
public function getKey(): string
{
return 'json-login';
}
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string
{
$authenticatorId = 'security.authenticator.json_login.'.$firewallName;
$options = array_intersect_key($config, $this->options);
$container
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.json_login'))
->replaceArgument(1, new Reference($userProviderId))
->replaceArgument(2, isset($config['success_handler']) ? new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config)) : null)
->replaceArgument(3, isset($config['failure_handler']) ? new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config)) : null)
->replaceArgument(4, $options);
return $authenticatorId;
}
}

View File

@@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
/**
* JsonLoginLdapFactory creates services for json login ldap authentication.
*
* @internal
*/
class JsonLoginLdapFactory extends JsonLoginFactory
{
use LdapFactoryTrait;
public function addConfiguration(NodeDefinition $node): void
{
parent::addConfiguration($node);
$node
->children()
->scalarNode('service')->defaultValue('ldap')->end()
->scalarNode('dn_string')->defaultValue('{username}')->end()
->scalarNode('query_string')->end()
->scalarNode('search_dn')->defaultValue('')->end()
->scalarNode('search_password')->defaultValue('')->end()
->end()
;
}
}

View File

@@ -0,0 +1,65 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Ldap\Security\CheckLdapCredentialsListener;
use Symfony\Component\Ldap\Security\LdapAuthenticator;
/**
* A trait decorating the authenticator with LDAP functionality.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*
* @internal
*/
trait LdapFactoryTrait
{
public function getKey(): string
{
return parent::getKey().'-ldap';
}
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string
{
$key = str_replace('-', '_', $this->getKey());
$authenticatorId = parent::createAuthenticator($container, $firewallName, $config, $userProviderId);
$container->setDefinition('security.listener.'.$key.'.'.$firewallName, new Definition(CheckLdapCredentialsListener::class))
->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.'.$firewallName])
->addArgument(new Reference('security.ldap_locator'))
;
$ldapAuthenticatorId = 'security.authenticator.'.$key.'.'.$firewallName;
$definition = $container->setDefinition($ldapAuthenticatorId, new Definition(LdapAuthenticator::class))
->setArguments([
new Reference($authenticatorId),
$config['service'],
$config['dn_string'],
$config['search_dn'],
$config['search_password'],
]);
if (!empty($config['query_string'])) {
if ('' === $config['search_dn'] || '' === $config['search_password']) {
throw new InvalidConfigurationException('Using the "query_string" config without using a "search_dn" and a "search_password" is not supported.');
}
$definition->addArgument($config['query_string']);
}
return $ldapAuthenticatorId;
}
}

View File

@@ -0,0 +1,151 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
/**
* @internal
*/
class LoginLinkFactory extends AbstractFactory
{
public const PRIORITY = -20;
public function addConfiguration(NodeDefinition $node): void
{
/** @var NodeBuilder $builder */
$builder = $node->fixXmlConfig('signature_property', 'signature_properties')->children();
$builder
->scalarNode('check_route')
->isRequired()
->info('Route that will validate the login link - e.g. "app_login_link_verify".')
->end()
->scalarNode('check_post_only')
->defaultFalse()
->info('If true, only HTTP POST requests to "check_route" will be handled by the authenticator.')
->end()
->arrayNode('signature_properties')
->isRequired()
->prototype('scalar')->end()
->requiresAtLeastOneElement()
->info('An array of properties on your User that are used to sign the link. If any of these change, all existing links will become invalid.')
->example(['email', 'password'])
->end()
->integerNode('lifetime')
->defaultValue(600)
->info('The lifetime of the login link in seconds.')
->end()
->integerNode('max_uses')
->defaultNull()
->info('Max number of times a login link can be used - null means unlimited within lifetime.')
->end()
->scalarNode('used_link_cache')
->info('Cache service id used to expired links of max_uses is set.')
->end()
->scalarNode('success_handler')
->info(\sprintf('A service id that implements %s.', AuthenticationSuccessHandlerInterface::class))
->end()
->scalarNode('failure_handler')
->info(\sprintf('A service id that implements %s.', AuthenticationFailureHandlerInterface::class))
->end()
->scalarNode('provider')
->info('The user provider to load users from.')
->end()
;
foreach (array_merge($this->defaultSuccessHandlerOptions, $this->defaultFailureHandlerOptions) as $name => $default) {
if (\is_bool($default)) {
$builder->booleanNode($name)->defaultValue($default);
} else {
$builder->scalarNode($name)->defaultValue($default);
}
}
}
public function getKey(): string
{
return 'login-link';
}
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string
{
if (!$container->hasDefinition('security.authenticator.login_link')) {
$loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__).'/../../Resources/config'));
$loader->load('security_authenticator_login_link.php');
}
if (null !== $config['max_uses'] && !isset($config['used_link_cache'])) {
$config['used_link_cache'] = 'security.authenticator.cache.expired_links';
$defaultCacheDefinition = $container->getDefinition($config['used_link_cache']);
if (!$defaultCacheDefinition->hasTag('cache.pool')) {
$defaultCacheDefinition->addTag('cache.pool');
}
}
$expiredStorageId = null;
if (isset($config['used_link_cache'])) {
$expiredStorageId = 'security.authenticator.expired_login_link_storage.'.$firewallName;
$container
->setDefinition($expiredStorageId, new ChildDefinition('security.authenticator.expired_login_link_storage'))
->replaceArgument(0, new Reference($config['used_link_cache']))
->replaceArgument(1, $config['lifetime']);
}
$signatureHasherId = 'security.authenticator.login_link_signature_hasher.'.$firewallName;
$container
->setDefinition($signatureHasherId, new ChildDefinition('security.authenticator.abstract_login_link_signature_hasher'))
->replaceArgument(1, $config['signature_properties'])
->replaceArgument(3, $expiredStorageId ? new Reference($expiredStorageId) : null)
->replaceArgument(4, $config['max_uses'] ?? null)
;
$linkerId = 'security.authenticator.login_link_handler.'.$firewallName;
$linkerOptions = [
'route_name' => $config['check_route'],
'lifetime' => $config['lifetime'],
];
$container
->setDefinition($linkerId, new ChildDefinition('security.authenticator.abstract_login_link_handler'))
->replaceArgument(1, new Reference($userProviderId))
->replaceArgument(2, new Reference($signatureHasherId))
->replaceArgument(3, $linkerOptions)
->addTag('security.authenticator.login_linker', ['firewall' => $firewallName])
;
$authenticatorId = 'security.authenticator.login_link.'.$firewallName;
$container
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.login_link'))
->replaceArgument(0, new Reference($linkerId))
->replaceArgument(2, new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config)))
->replaceArgument(3, new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config)))
->replaceArgument(4, [
'check_route' => $config['check_route'],
'check_post_only' => $config['check_post_only'],
]);
return $authenticatorId;
}
public function getPriority(): int
{
return self::PRIORITY;
}
}

View File

@@ -0,0 +1,119 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface;
use Symfony\Component\Lock\LockInterface;
use Symfony\Component\RateLimiter\RateLimiterFactory;
use Symfony\Component\RateLimiter\Storage\CacheStorage;
use Symfony\Component\Security\Http\RateLimiter\DefaultLoginRateLimiter;
/**
* @author Wouter de Jong <wouter@wouterj.nl>
*
* @internal
*/
class LoginThrottlingFactory implements AuthenticatorFactoryInterface
{
public function getPriority(): int
{
// this factory doesn't register any authenticators, this priority doesn't matter
return 0;
}
public function getKey(): string
{
return 'login_throttling';
}
/**
* @param ArrayNodeDefinition $builder
*/
public function addConfiguration(NodeDefinition $builder): void
{
$builder
->children()
->scalarNode('limiter')->info(\sprintf('A service id implementing "%s".', RequestRateLimiterInterface::class))->end()
->integerNode('max_attempts')->defaultValue(5)->end()
->scalarNode('interval')->defaultValue('1 minute')->end()
->scalarNode('lock_factory')->info('The service ID of the lock factory used by the login rate limiter (or null to disable locking)')->defaultNull()->end()
->end();
}
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): array
{
if (!class_exists(RateLimiterFactory::class)) {
throw new \LogicException('Login throttling requires the Rate Limiter component. Try running "composer require symfony/rate-limiter".');
}
if (!isset($config['limiter'])) {
$limiterOptions = [
'policy' => 'fixed_window',
'limit' => $config['max_attempts'],
'interval' => $config['interval'],
'lock_factory' => $config['lock_factory'],
];
$this->registerRateLimiter($container, $localId = '_login_local_'.$firewallName, $limiterOptions);
$limiterOptions['limit'] = 5 * $config['max_attempts'];
$this->registerRateLimiter($container, $globalId = '_login_global_'.$firewallName, $limiterOptions);
$container->register($config['limiter'] = 'security.login_throttling.'.$firewallName.'.limiter', DefaultLoginRateLimiter::class)
->addArgument(new Reference('limiter.'.$globalId))
->addArgument(new Reference('limiter.'.$localId))
->addArgument('%kernel.secret%')
;
}
$container
->setDefinition('security.listener.login_throttling.'.$firewallName, new ChildDefinition('security.listener.login_throttling'))
->replaceArgument(1, new Reference($config['limiter']))
->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.'.$firewallName]);
return [];
}
private function registerRateLimiter(ContainerBuilder $container, string $name, array $limiterConfig): void
{
// default configuration (when used by other DI extensions)
$limiterConfig += ['lock_factory' => 'lock.factory', 'cache_pool' => 'cache.rate_limiter'];
$limiter = $container->setDefinition($limiterId = 'limiter.'.$name, new ChildDefinition('limiter'));
if (null !== $limiterConfig['lock_factory']) {
if (!interface_exists(LockInterface::class)) {
throw new LogicException(\sprintf('Rate limiter "%s" requires the Lock component to be installed. Try running "composer require symfony/lock".', $name));
}
$limiter->replaceArgument(2, new Reference($limiterConfig['lock_factory']));
}
unset($limiterConfig['lock_factory']);
if (null === $storageId = $limiterConfig['storage_service'] ?? null) {
$container->register($storageId = 'limiter.storage.'.$name, CacheStorage::class)->addArgument(new Reference($limiterConfig['cache_pool']));
}
$limiter->replaceArgument(1, new Reference($storageId));
unset($limiterConfig['storage_service'], $limiterConfig['cache_pool']);
$limiterConfig['id'] = $name;
$limiter->replaceArgument(0, $limiterConfig);
$container->registerAliasForArgument($limiterId, RateLimiterFactory::class, $name.'.limiter');
}
}

View File

@@ -0,0 +1,247 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
use Symfony\Bridge\Doctrine\Security\RememberMe\DoctrineTokenProvider;
use Symfony\Bundle\SecurityBundle\RememberMe\DecoratedRememberMeHandler;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\Security\Core\Authentication\RememberMe\CacheTokenVerifier;
/**
* @internal
*/
class RememberMeFactory implements AuthenticatorFactoryInterface, PrependExtensionInterface
{
public const PRIORITY = -50;
protected $options = [
'name' => 'REMEMBERME',
'lifetime' => 31536000,
'path' => '/',
'domain' => null,
'secure' => false,
'httponly' => true,
'samesite' => null,
'always_remember_me' => false,
'remember_me_parameter' => '_remember_me',
];
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string
{
if (!$container->hasDefinition('security.authenticator.remember_me')) {
$loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__).'/../../Resources/config'));
$loader->load('security_authenticator_remember_me.php');
}
if ('auto' === $config['secure']) {
$config['secure'] = null;
}
// create remember me handler (which manage the remember-me cookies)
$rememberMeHandlerId = 'security.authenticator.remember_me_handler.'.$firewallName;
if (isset($config['service']) && isset($config['token_provider'])) {
throw new InvalidConfigurationException(\sprintf('You cannot use both "service" and "token_provider" in "security.firewalls.%s.remember_me".', $firewallName));
}
if (isset($config['service'])) {
$container->register($rememberMeHandlerId, DecoratedRememberMeHandler::class)
->addArgument(new Reference($config['service']))
->addTag('security.remember_me_handler', ['firewall' => $firewallName]);
} elseif (isset($config['token_provider'])) {
$tokenProviderId = $this->createTokenProvider($container, $firewallName, $config['token_provider']);
$tokenVerifier = $this->createTokenVerifier($container, $firewallName, $config['token_verifier'] ?? null);
$container->setDefinition($rememberMeHandlerId, new ChildDefinition('security.authenticator.persistent_remember_me_handler'))
->replaceArgument(0, new Reference($tokenProviderId))
->replaceArgument(1, new Reference($userProviderId))
->replaceArgument(3, $config)
->replaceArgument(5, $tokenVerifier)
->addTag('security.remember_me_handler', ['firewall' => $firewallName]);
} else {
$signatureHasherId = 'security.authenticator.remember_me_signature_hasher.'.$firewallName;
$container->setDefinition($signatureHasherId, new ChildDefinition('security.authenticator.remember_me_signature_hasher'))
->replaceArgument(1, $config['signature_properties'])
->replaceArgument(2, $config['secret'])
;
$container->setDefinition($rememberMeHandlerId, new ChildDefinition('security.authenticator.signature_remember_me_handler'))
->replaceArgument(0, new Reference($signatureHasherId))
->replaceArgument(1, new Reference($userProviderId))
->replaceArgument(3, $config)
->addTag('security.remember_me_handler', ['firewall' => $firewallName]);
}
// create check remember me conditions listener (which checks if a remember-me cookie is supported and requested)
$rememberMeConditionsListenerId = 'security.listener.check_remember_me_conditions.'.$firewallName;
$container->setDefinition($rememberMeConditionsListenerId, new ChildDefinition('security.listener.check_remember_me_conditions'))
->replaceArgument(0, array_intersect_key($config, ['always_remember_me' => true, 'remember_me_parameter' => true]))
->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.'.$firewallName])
;
// create remember me listener (which executes the remember me services for other authenticators and logout)
$rememberMeListenerId = 'security.listener.remember_me.'.$firewallName;
$container->setDefinition($rememberMeListenerId, new ChildDefinition('security.listener.remember_me'))
->replaceArgument(0, new Reference($rememberMeHandlerId))
->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.'.$firewallName])
;
// create remember me authenticator (which re-authenticates the user based on the remember-me cookie)
$authenticatorId = 'security.authenticator.remember_me.'.$firewallName;
$container
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.remember_me'))
->replaceArgument(0, new Reference($rememberMeHandlerId))
->replaceArgument(3, $config['name'] ?? $this->options['name'])
;
return $authenticatorId;
}
public function getPriority(): int
{
return self::PRIORITY;
}
public function getKey(): string
{
return 'remember-me';
}
public function addConfiguration(NodeDefinition $node): void
{
$builder = $node
->fixXmlConfig('user_provider')
->children()
;
$builder
->scalarNode('secret')
->cannotBeEmpty()
->defaultValue('%kernel.secret%')
->end()
->scalarNode('service')->end()
->arrayNode('user_providers')
->beforeNormalization()
->ifString()->then(fn ($v) => [$v])
->end()
->prototype('scalar')->end()
->end()
->booleanNode('catch_exceptions')->defaultTrue()->end()
->arrayNode('signature_properties')
->prototype('scalar')->end()
->requiresAtLeastOneElement()
->info('An array of properties on your User that are used to sign the remember-me cookie. If any of these change, all existing cookies will become invalid.')
->example(['email', 'password'])
->defaultValue(['password'])
->end()
->arrayNode('token_provider')
->beforeNormalization()
->ifString()->then(fn ($v) => ['service' => $v])
->end()
->children()
->scalarNode('service')->info('The service ID of a custom rememberme token provider.')->end()
->arrayNode('doctrine')
->canBeEnabled()
->children()
->scalarNode('connection')->defaultNull()->end()
->end()
->end()
->end()
->end()
->scalarNode('token_verifier')
->info('The service ID of a custom rememberme token verifier.')
->end();
foreach ($this->options as $name => $value) {
if ('secure' === $name) {
$builder->enumNode($name)->values([true, false, 'auto'])->defaultValue('auto' === $value ? null : $value);
} elseif ('samesite' === $name) {
$builder->enumNode($name)->values([null, Cookie::SAMESITE_LAX, Cookie::SAMESITE_STRICT, Cookie::SAMESITE_NONE])->defaultValue($value);
} elseif (\is_bool($value)) {
$builder->booleanNode($name)->defaultValue($value);
} elseif (\is_int($value)) {
$builder->integerNode($name)->defaultValue($value);
} else {
$builder->scalarNode($name)->defaultValue($value);
}
}
}
private function createTokenProvider(ContainerBuilder $container, string $firewallName, array $config): string
{
$tokenProviderId = $config['service'] ?? false;
if ($config['doctrine']['enabled'] ?? false) {
if (!class_exists(DoctrineTokenProvider::class)) {
throw new InvalidConfigurationException('Cannot use the "doctrine" token provider for "remember_me" because the Doctrine Bridge is not installed. Try running "composer require symfony/doctrine-bridge".');
}
if (null === $config['doctrine']['connection']) {
$connectionId = 'database_connection';
} else {
$connectionId = 'doctrine.dbal.'.$config['doctrine']['connection'].'_connection';
}
$tokenProviderId = 'security.remember_me.doctrine_token_provider.'.$firewallName;
$container->register($tokenProviderId, DoctrineTokenProvider::class)
->addArgument(new Reference($connectionId));
}
if (!$tokenProviderId) {
throw new InvalidConfigurationException(\sprintf('No token provider was set for firewall "%s". Either configure a service ID or set "remember_me.token_provider.doctrine" to true.', $firewallName));
}
return $tokenProviderId;
}
private function createTokenVerifier(ContainerBuilder $container, string $firewallName, ?string $serviceId): Reference
{
if ($serviceId) {
return new Reference($serviceId);
}
$tokenVerifierId = 'security.remember_me.token_verifier.'.$firewallName;
$container->register($tokenVerifierId, CacheTokenVerifier::class)
->addArgument(new Reference('cache.security_token_verifier', ContainerInterface::NULL_ON_INVALID_REFERENCE))
->addArgument(60)
->addArgument('rememberme-'.$firewallName.'-stale-');
return new Reference($tokenVerifierId, ContainerInterface::NULL_ON_INVALID_REFERENCE);
}
public function prepend(ContainerBuilder $container): void
{
$rememberMeSecureDefault = false;
$rememberMeSameSiteDefault = null;
if (!isset($container->getExtensions()['framework'])) {
return;
}
foreach ($container->getExtensionConfig('framework') as $config) {
if (isset($config['session']) && \is_array($config['session'])) {
$rememberMeSecureDefault = $config['session']['cookie_secure'] ?? $rememberMeSecureDefault;
$rememberMeSameSiteDefault = \array_key_exists('cookie_samesite', $config['session']) ? $config['session']['cookie_samesite'] : $rememberMeSameSiteDefault;
}
}
$this->options['secure'] = $rememberMeSecureDefault;
$this->options['samesite'] = $rememberMeSameSiteDefault;
}
}

View File

@@ -0,0 +1,63 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* RemoteUserFactory creates services for REMOTE_USER based authentication.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Maxime Douailin <maxime.douailin@gmail.com>
*
* @internal
*/
class RemoteUserFactory implements AuthenticatorFactoryInterface
{
public const PRIORITY = -10;
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string
{
$authenticatorId = 'security.authenticator.remote_user.'.$firewallName;
$container
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.remote_user'))
->replaceArgument(0, new Reference($userProviderId))
->replaceArgument(2, $firewallName)
->replaceArgument(3, $config['user'])
;
return $authenticatorId;
}
public function getPriority(): int
{
return self::PRIORITY;
}
public function getKey(): string
{
return 'remote-user';
}
public function addConfiguration(NodeDefinition $node): void
{
$node
->children()
->scalarNode('provider')->end()
->scalarNode('user')->defaultValue('REMOTE_USER')->end()
->end()
;
}
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
use Jose\Component\Core\Algorithm as AlgorithmInterface;
use Jose\Component\Signature\Algorithm;
use Symfony\Component\Security\Core\Exception\InvalidArgumentException;
use Symfony\Component\Security\Http\AccessToken\Oidc\OidcTokenHandler;
/**
* Creates a signature algorithm for {@see OidcTokenHandler}.
*
* @internal
*/
final class SignatureAlgorithmFactory
{
public static function create(string $algorithm): AlgorithmInterface
{
switch ($algorithm) {
case 'ES256':
case 'ES384':
case 'ES512':
if (!class_exists(Algorithm::class.'\\'.$algorithm)) {
throw new \LogicException(\sprintf('You cannot use the "%s" signature algorithm since "web-token/jwt-signature-algorithm-ecdsa" is not installed. Try running "composer require web-token/jwt-signature-algorithm-ecdsa".', $algorithm));
}
$algorithm = Algorithm::class.'\\'.$algorithm;
return new $algorithm();
}
throw new InvalidArgumentException(\sprintf('Unsupported signature algorithm "%s". Only ES* algorithms are supported. If you want to use another algorithm, create your TokenHandler as a service.', $algorithm));
}
}

View File

@@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Stateless authenticators are authenticators that can work without a user provider.
*
* This situation can only occur in stateless firewalls, as statefull firewalls
* need the user provider to refresh the user in each subsequent request. A
* stateless authenticator can be used on both stateless and statefull authenticators.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
interface StatelessAuthenticatorFactoryInterface extends AuthenticatorFactoryInterface
{
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, ?string $userProviderId): string|array;
}

View File

@@ -0,0 +1,66 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* X509Factory creates services for X509 certificate authentication.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @internal
*/
class X509Factory implements AuthenticatorFactoryInterface
{
public const PRIORITY = -10;
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string
{
$authenticatorId = 'security.authenticator.x509.'.$firewallName;
$container
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.x509'))
->replaceArgument(0, new Reference($userProviderId))
->replaceArgument(2, $firewallName)
->replaceArgument(3, $config['user'])
->replaceArgument(4, $config['credentials'])
->replaceArgument(6, $config['user_identifier'])
;
return $authenticatorId;
}
public function getPriority(): int
{
return self::PRIORITY;
}
public function getKey(): string
{
return 'x509';
}
public function addConfiguration(NodeDefinition $node): void
{
$node
->children()
->scalarNode('provider')->end()
->scalarNode('user')->defaultValue('SSL_CLIENT_S_DN_Email')->end()
->scalarNode('credentials')->defaultValue('SSL_CLIENT_S_DN')->end()
->scalarNode('user_identifier')->defaultValue('emailAddress')->end()
->end()
;
}
}

View File

@@ -0,0 +1,75 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Parameter;
/**
* InMemoryFactory creates services for the memory provider.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Christophe Coevoet <stof@notk.org>
*/
class InMemoryFactory implements UserProviderFactoryInterface
{
/**
* @return void
*/
public function create(ContainerBuilder $container, string $id, array $config)
{
$definition = $container->setDefinition($id, new ChildDefinition('security.user.provider.in_memory'));
$defaultPassword = new Parameter('container.build_id');
$users = [];
foreach ($config['users'] as $username => $user) {
$users[$username] = ['password' => null !== $user['password'] ? (string) $user['password'] : $defaultPassword, 'roles' => $user['roles']];
}
$definition->addArgument($users);
}
/**
* @return string
*/
public function getKey()
{
return 'memory';
}
/**
* @return void
*/
public function addConfiguration(NodeDefinition $node)
{
$node
->fixXmlConfig('user')
->children()
->arrayNode('users')
->useAttributeAsKey('identifier')
->normalizeKeys(false)
->prototype('array')
->children()
->scalarNode('password')->defaultNull()->end()
->arrayNode('roles')
->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end()
->prototype('scalar')->end()
->end()
->end()
->end()
->end()
->end()
;
}
}

View File

@@ -0,0 +1,81 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* LdapFactory creates services for Ldap user provider.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
* @author Charles Sarrazin <charles@sarraz.in>
*/
class LdapFactory implements UserProviderFactoryInterface
{
/**
* @return void
*/
public function create(ContainerBuilder $container, string $id, array $config)
{
$container
->setDefinition($id, new ChildDefinition('security.user.provider.ldap'))
->replaceArgument(0, new Reference($config['service']))
->replaceArgument(1, $config['base_dn'])
->replaceArgument(2, $config['search_dn'])
->replaceArgument(3, $config['search_password'])
->replaceArgument(4, $config['default_roles'])
->replaceArgument(5, $config['uid_key'])
->replaceArgument(6, $config['filter'])
->replaceArgument(7, $config['password_attribute'])
->replaceArgument(8, $config['extra_fields'])
;
}
/**
* @return string
*/
public function getKey()
{
return 'ldap';
}
/**
* @return void
*/
public function addConfiguration(NodeDefinition $node)
{
$node
->fixXmlConfig('extra_field')
->fixXmlConfig('default_role')
->children()
->scalarNode('service')->isRequired()->cannotBeEmpty()->defaultValue('ldap')->end()
->scalarNode('base_dn')->isRequired()->cannotBeEmpty()->end()
->scalarNode('search_dn')->defaultNull()->end()
->scalarNode('search_password')->defaultNull()->end()
->arrayNode('extra_fields')
->prototype('scalar')->end()
->end()
->arrayNode('default_roles')
->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end()
->requiresAtLeastOneElement()
->prototype('scalar')->end()
->end()
->scalarNode('uid_key')->defaultValue('sAMAccountName')->end()
->scalarNode('filter')->defaultValue('({uid_key}={username})')->end()
->scalarNode('password_attribute')->defaultNull()->end()
->end()
;
}
}

View File

@@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* UserProviderFactoryInterface is the interface for all user provider factories.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Christophe Coevoet <stof@notk.org>
*/
interface UserProviderFactoryInterface
{
/**
* @return void
*/
public function create(ContainerBuilder $container, string $id, array $config);
/**
* @return string
*/
public function getKey();
/**
* @return void
*/
public function addConfiguration(NodeDefinition $builder);
}

File diff suppressed because it is too large Load Diff