The start of something beautiful
This commit is contained in:
Vendored
+39
@@ -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\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
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
<?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;
|
||||
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
Vendored
+45
@@ -0,0 +1,45 @@
|
||||
<?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
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
<?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
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if (!$container->hasDefinition('cache.system')) {
|
||||
$container->removeDefinition('cache.security_token_verifier');
|
||||
}
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
<?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
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
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),
|
||||
])
|
||||
->addTag('monolog.logger', ['channel' => 'event'])
|
||||
->addTag('kernel.reset', ['method' => 'reset']);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+74
@@ -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\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\Security\Csrf\TokenStorage\ClearableTokenStorageInterface;
|
||||
use Symfony\Component\Security\Http\EventListener\CsrfProtectionListener;
|
||||
use Symfony\Component\Security\Http\EventListener\CsrfTokenClearingLogoutListener;
|
||||
use Symfony\Component\Security\Http\EventListener\IsCsrfTokenValidAttributeListener;
|
||||
|
||||
/**
|
||||
* @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->hasDefinition('cache.system')) {
|
||||
$container->removeDefinition('cache.security_is_csrf_token_valid_attribute_expression_language');
|
||||
}
|
||||
|
||||
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');
|
||||
|
||||
$container->register('controller.is_csrf_token_valid_attribute_listener', IsCsrfTokenValidAttributeListener::class)
|
||||
->addArgument(new Reference('security.csrf.token_manager'))
|
||||
->addArgument(new Reference('security.is_csrf_token_valid_attribute_expression_language', ContainerInterface::NULL_ON_INVALID_REFERENCE))
|
||||
->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');
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
<?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
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+86
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+53
@@ -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']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+58
@@ -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'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+78
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,463 @@
|
||||
<?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()
|
||||
->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 => \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_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,
|
||||
];
|
||||
}
|
||||
}
|
||||
Vendored
+62
@@ -0,0 +1,62 @@
|
||||
<?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\Reference;
|
||||
use Symfony\Component\Security\Http\AccessToken\Cas\Cas2Handler;
|
||||
|
||||
class CasTokenHandlerFactory implements TokenHandlerFactoryInterface
|
||||
{
|
||||
public function create(ContainerBuilder $container, string $id, array|string $config): void
|
||||
{
|
||||
$container->setDefinition($id, new ChildDefinition('security.access_token_handler.cas'));
|
||||
|
||||
$container
|
||||
->register('security.access_token_handler.cas', Cas2Handler::class)
|
||||
->setArguments([
|
||||
new Reference('request_stack'),
|
||||
$config['validation_url'],
|
||||
$config['prefix'],
|
||||
$config['http_client'] ? new Reference($config['http_client']) : null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'cas';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeBuilder $node): void
|
||||
{
|
||||
$node
|
||||
->arrayNode($this->getKey())
|
||||
->fixXmlConfig($this->getKey())
|
||||
->children()
|
||||
->scalarNode('validation_url')
|
||||
->info('CAS server validation URL')
|
||||
->isRequired()
|
||||
->end()
|
||||
->scalarNode('prefix')
|
||||
->info('CAS prefix')
|
||||
->defaultValue('cas')
|
||||
->end()
|
||||
->scalarNode('http_client')
|
||||
->info('HTTP Client service')
|
||||
->defaultNull()
|
||||
->end()
|
||||
->end()
|
||||
->end();
|
||||
}
|
||||
}
|
||||
Vendored
+122
@@ -0,0 +1,122 @@
|
||||
<?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\Config\Definition\Exception\InvalidConfigurationException;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* 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-library', Algorithm::class, ['symfony/security-bundle'])) {
|
||||
throw new LogicException('You cannot use the "oidc" token handler since "web-token/jwt-library" is not installed. Try running "composer require web-token/jwt-library".');
|
||||
}
|
||||
|
||||
$tokenHandlerDefinition->replaceArgument(0, (new ChildDefinition('security.access_token_handler.oidc.signature'))
|
||||
->replaceArgument(0, $config['algorithms']));
|
||||
|
||||
$tokenHandlerDefinition->replaceArgument(1, (new ChildDefinition('security.access_token_handler.oidc.jwkset'))
|
||||
->replaceArgument(0, $config['keyset'])
|
||||
);
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'oidc';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeBuilder $node): void
|
||||
{
|
||||
$node
|
||||
->arrayNode($this->getKey())
|
||||
->fixXmlConfig($this->getKey())
|
||||
->validate()
|
||||
->ifTrue(static fn ($v) => !isset($v['algorithm']) && !isset($v['algorithms']))
|
||||
->thenInvalid('You must set either "algorithm" or "algorithms".')
|
||||
->end()
|
||||
->validate()
|
||||
->ifTrue(static fn ($v) => !isset($v['key']) && !isset($v['keyset']))
|
||||
->thenInvalid('You must set either "key" or "keyset".')
|
||||
->end()
|
||||
->beforeNormalization()
|
||||
->ifTrue(static fn ($v) => isset($v['algorithm']) && \is_string($v['algorithm']))
|
||||
->then(static function ($v) {
|
||||
if (isset($v['algorithms'])) {
|
||||
throw new InvalidConfigurationException('You cannot use both "algorithm" and "algorithms" at the same time.');
|
||||
}
|
||||
$v['algorithms'] = [$v['algorithm']];
|
||||
unset($v['algorithm']);
|
||||
|
||||
return $v;
|
||||
})
|
||||
->end()
|
||||
->beforeNormalization()
|
||||
->ifTrue(static fn ($v) => isset($v['key']) && \is_string($v['key']))
|
||||
->then(static function ($v) {
|
||||
if (isset($v['keyset'])) {
|
||||
throw new InvalidConfigurationException('You cannot use both "key" and "keyset" at the same time.');
|
||||
}
|
||||
$v['keyset'] = sprintf('{"keys":[%s]}', $v['key']);
|
||||
|
||||
return $v;
|
||||
})
|
||||
->end()
|
||||
->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()
|
||||
->scalarPrototype()->end()
|
||||
->end()
|
||||
->arrayNode('algorithm')
|
||||
->info('Algorithm used to sign the token.')
|
||||
->setDeprecated('symfony/security-bundle', '7.1', 'The "%node%" option is deprecated and will be removed in 8.0. Use the "algorithms" option instead.')
|
||||
->end()
|
||||
->arrayNode('algorithms')
|
||||
->info('Algorithms used to sign the token.')
|
||||
->isRequired()
|
||||
->scalarPrototype()->end()
|
||||
->end()
|
||||
->scalarNode('key')
|
||||
->info('JSON-encoded JWK used to sign the token (must contain a "kty" key).')
|
||||
->setDeprecated('symfony/security-bundle', '7.1', 'The "%node%" option is deprecated and will be removed in 8.0. Use the "keyset" option instead.')
|
||||
->end()
|
||||
->scalarNode('keyset')
|
||||
->info('JSON-encoded JWKSet used to sign the token (must contain a list of valid keys).')
|
||||
->isRequired()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
}
|
||||
+74
@@ -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()
|
||||
;
|
||||
}
|
||||
}
|
||||
+39
@@ -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();
|
||||
}
|
||||
}
|
||||
+36
@@ -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;
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
<?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 array $options = [
|
||||
'check_path' => '/login_check',
|
||||
'use_forward' => false,
|
||||
'login_path' => '/login',
|
||||
];
|
||||
|
||||
protected array $defaultSuccessHandlerOptions = [
|
||||
'always_use_default_target_path' => false,
|
||||
'default_target_path' => '/',
|
||||
'login_path' => '/login',
|
||||
'target_path_parameter' => '_target_path',
|
||||
'use_referer' => false,
|
||||
];
|
||||
|
||||
protected array $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;
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeDefinition $node): void
|
||||
{
|
||||
$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 (\is_bool($default)) {
|
||||
$builder->booleanNode($name)->defaultValue($default);
|
||||
} else {
|
||||
$builder->scalarNode($name)->defaultValue($default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function createAuthenticationSuccessHandler(ContainerBuilder $container, string $id, array $config): string
|
||||
{
|
||||
$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;
|
||||
}
|
||||
|
||||
protected function createAuthenticationFailureHandler(ContainerBuilder $container, string $id, array $config): string
|
||||
{
|
||||
$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;
|
||||
}
|
||||
|
||||
protected function getSuccessHandlerId(string $id): string
|
||||
{
|
||||
return 'security.authentication.success_handler.'.$id.'.'.str_replace('-', '_', $this->getKey());
|
||||
}
|
||||
|
||||
protected function getFailureHandlerId(string $id): string
|
||||
{
|
||||
return 'security.authentication.failure_handler.'.$id.'.'.str_replace('-', '_', $this->getKey());
|
||||
}
|
||||
}
|
||||
+166
@@ -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;
|
||||
}
|
||||
}
|
||||
+43
@@ -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 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;
|
||||
|
||||
public function addConfiguration(NodeDefinition $builder): void;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Vendored
+64
@@ -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;
|
||||
}
|
||||
}
|
||||
+31
@@ -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;
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* 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 createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string
|
||||
{
|
||||
$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;
|
||||
}
|
||||
}
|
||||
Vendored
+42
@@ -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('{user_identifier}')->end()
|
||||
->scalarNode('query_string')->end()
|
||||
->scalarNode('search_dn')->defaultValue('')->end()
|
||||
->scalarNode('search_password')->defaultValue('')->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
}
|
||||
+60
@@ -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()
|
||||
;
|
||||
}
|
||||
}
|
||||
Vendored
+87
@@ -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('{user_identifier}')->end()
|
||||
->scalarNode('query_string')->end()
|
||||
->scalarNode('search_dn')->defaultValue('')->end()
|
||||
->scalarNode('search_password')->defaultValue('')->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
}
|
||||
+60
@@ -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;
|
||||
}
|
||||
}
|
||||
Vendored
+39
@@ -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('{user_identifier}')->end()
|
||||
->scalarNode('query_string')->end()
|
||||
->scalarNode('search_dn')->defaultValue('')->end()
|
||||
->scalarNode('search_password')->defaultValue('')->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
}
|
||||
+65
@@ -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;
|
||||
}
|
||||
}
|
||||
+151
@@ -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;
|
||||
}
|
||||
}
|
||||
Vendored
+120
@@ -0,0 +1,120 @@
|
||||
<?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\Parameter;
|
||||
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(new Parameter('container.build_hash'))
|
||||
;
|
||||
}
|
||||
|
||||
$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');
|
||||
}
|
||||
}
|
||||
+247
@@ -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 array $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;
|
||||
}
|
||||
}
|
||||
+63
@@ -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()
|
||||
;
|
||||
}
|
||||
}
|
||||
+28
@@ -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;
|
||||
}
|
||||
+66
@@ -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()
|
||||
;
|
||||
}
|
||||
}
|
||||
Vendored
+66
@@ -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\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
|
||||
{
|
||||
public function create(ContainerBuilder $container, string $id, array $config): void
|
||||
{
|
||||
$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);
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'memory';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeDefinition $node): void
|
||||
{
|
||||
$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()
|
||||
;
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
<?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
|
||||
{
|
||||
public function create(ContainerBuilder $container, string $id, array $config): void
|
||||
{
|
||||
$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'])
|
||||
;
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'ldap';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeDefinition $node): void
|
||||
{
|
||||
$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}={user_identifier})')->end()
|
||||
->scalarNode('password_attribute')->defaultNull()->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<?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
|
||||
{
|
||||
public function create(ContainerBuilder $container, string $id, array $config): void;
|
||||
|
||||
public function getKey(): string;
|
||||
|
||||
public function addConfiguration(NodeDefinition $builder): void;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user