Subida del módulo y tema de PrestaShop

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

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\CacheWarmer;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
use Symfony\Component\Security\Core\Authorization\ExpressionLanguage;
class ExpressionCacheWarmer implements CacheWarmerInterface
{
private iterable $expressions;
private ExpressionLanguage $expressionLanguage;
/**
* @param iterable<mixed, Expression|string> $expressions
*/
public function __construct(iterable $expressions, ExpressionLanguage $expressionLanguage)
{
$this->expressions = $expressions;
$this->expressionLanguage = $expressionLanguage;
}
public function isOptional(): bool
{
return true;
}
/**
* @param string|null $buildDir
*/
public function warmUp(string $cacheDir /* , string $buildDir = null */): array
{
foreach ($this->expressions as $expression) {
$this->expressionLanguage->parse($expression, ['token', 'user', 'object', 'subject', 'role_names', 'request', 'trust_resolver']);
}
return [];
}
}

View File

@@ -0,0 +1,275 @@
<?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\Command;
use Psr\Container\ContainerInterface;
use Symfony\Bundle\SecurityBundle\Security\FirewallContext;
use Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
/**
* @author Timo Bakx <timobakx@gmail.com>
*/
#[AsCommand(name: 'debug:firewall', description: 'Display information about your security firewall(s)')]
final class DebugFirewallCommand extends Command
{
private array $firewallNames;
private ContainerInterface $contexts;
private ContainerInterface $eventDispatchers;
private array $authenticators;
/**
* @param string[] $firewallNames
* @param AuthenticatorInterface[][] $authenticators
*/
public function __construct(array $firewallNames, ContainerInterface $contexts, ContainerInterface $eventDispatchers, array $authenticators)
{
$this->firewallNames = $firewallNames;
$this->contexts = $contexts;
$this->eventDispatchers = $eventDispatchers;
$this->authenticators = $authenticators;
parent::__construct();
}
protected function configure(): void
{
$exampleName = $this->getExampleName();
$this
->setHelp(<<<EOF
The <info>%command.name%</info> command displays the firewalls that are configured
in your application:
<info>php %command.full_name%</info>
You can pass a firewall name to display more detailed information about
a specific firewall:
<info>php %command.full_name% $exampleName</info>
To include all events and event listeners for a specific firewall, use the
<info>events</info> option:
<info>php %command.full_name% --events $exampleName</info>
EOF
)
->setDefinition([
new InputArgument('name', InputArgument::OPTIONAL, \sprintf('A firewall name (for example "%s")', $exampleName)),
new InputOption('events', null, InputOption::VALUE_NONE, 'Include a list of event listeners (only available in combination with the "name" argument)'),
]);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$name = $input->getArgument('name');
if (null === $name) {
$this->displayFirewallList($io);
return 0;
}
$serviceId = \sprintf('security.firewall.map.context.%s', $name);
if (!$this->contexts->has($serviceId)) {
$io->error(\sprintf('Firewall %s was not found. Available firewalls are: %s', $name, implode(', ', $this->firewallNames)));
return 1;
}
/** @var FirewallContext $context */
$context = $this->contexts->get($serviceId);
$io->title(\sprintf('Firewall "%s"', $name));
$this->displayFirewallSummary($name, $context, $io);
$this->displaySwitchUser($context, $io);
if ($input->getOption('events')) {
$this->displayEventListeners($name, $context, $io);
}
$this->displayAuthenticators($name, $io);
return 0;
}
protected function displayFirewallList(SymfonyStyle $io): void
{
$io->title('Firewalls');
$io->text('The following firewalls are defined:');
$io->listing($this->firewallNames);
$io->comment(\sprintf('To view details of a specific firewall, re-run this command with a firewall name. (e.g. <comment>debug:firewall %s</comment>)', $this->getExampleName()));
}
protected function displayFirewallSummary(string $name, FirewallContext $context, SymfonyStyle $io): void
{
if (null === $context->getConfig()) {
return;
}
$rows = [
['Name', $name],
['Context', $context->getConfig()->getContext()],
['Lazy', $context instanceof LazyFirewallContext ? 'Yes' : 'No'],
['Stateless', $context->getConfig()->isStateless() ? 'Yes' : 'No'],
['User Checker', $context->getConfig()->getUserChecker()],
['Provider', $context->getConfig()->getProvider()],
['Entry Point', $context->getConfig()->getEntryPoint()],
['Access Denied URL', $context->getConfig()->getAccessDeniedUrl()],
['Access Denied Handler', $context->getConfig()->getAccessDeniedHandler()],
];
$io->table(
['Option', 'Value'],
$rows
);
}
private function displaySwitchUser(FirewallContext $context, SymfonyStyle $io): void
{
if ((null === $config = $context->getConfig()) || (null === $switchUser = $config->getSwitchUser())) {
return;
}
$io->section('User switching');
$io->table(['Option', 'Value'], [
['Parameter', $switchUser['parameter'] ?? ''],
['Provider', $switchUser['provider'] ?? $config->getProvider()],
['User Role', $switchUser['role'] ?? ''],
]);
}
protected function displayEventListeners(string $name, FirewallContext $context, SymfonyStyle $io): void
{
$io->title(\sprintf('Event listeners for firewall "%s"', $name));
$dispatcherId = \sprintf('security.event_dispatcher.%s', $name);
if (!$this->eventDispatchers->has($dispatcherId)) {
$io->text('No event dispatcher has been registered for this firewall.');
return;
}
/** @var EventDispatcherInterface $dispatcher */
$dispatcher = $this->eventDispatchers->get($dispatcherId);
foreach ($dispatcher->getListeners() as $event => $listeners) {
$io->section(\sprintf('"%s" event', $event));
$rows = [];
foreach ($listeners as $order => $listener) {
$rows[] = [
\sprintf('#%d', $order + 1),
$this->formatCallable($listener),
$dispatcher->getListenerPriority($event, $listener),
];
}
$io->table(
['Order', 'Callable', 'Priority'],
$rows
);
}
}
private function displayAuthenticators(string $name, SymfonyStyle $io): void
{
$io->title(\sprintf('Authenticators for firewall "%s"', $name));
$authenticators = $this->authenticators[$name] ?? [];
if (0 === \count($authenticators)) {
$io->text('No authenticators have been registered for this firewall.');
return;
}
$io->table(
['Classname'],
array_map(
fn ($authenticator) => [$authenticator::class],
$authenticators
)
);
}
private function formatCallable(mixed $callable): string
{
if (\is_array($callable)) {
if (\is_object($callable[0])) {
return \sprintf('%s::%s()', $callable[0]::class, $callable[1]);
}
return \sprintf('%s::%s()', $callable[0], $callable[1]);
}
if (\is_string($callable)) {
return \sprintf('%s()', $callable);
}
if ($callable instanceof \Closure) {
$r = new \ReflectionFunction($callable);
if (str_contains($r->name, '{closure')) {
return 'Closure()';
}
if ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) {
return \sprintf('%s::%s()', $class->name, $r->name);
}
return $r->name.'()';
}
if (method_exists($callable, '__invoke')) {
return \sprintf('%s::__invoke()', $callable::class);
}
throw new \InvalidArgumentException('Callable is not describable.');
}
private function getExampleName(): string
{
$name = 'main';
if (!\in_array($name, $this->firewallNames, true)) {
$name = reset($this->firewallNames);
}
return $name;
}
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('name')) {
$suggestions->suggestValues($this->firewallNames);
}
}
}

View File

@@ -0,0 +1,352 @@
<?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\DataCollector;
use Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener;
use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager;
use Symfony\Component\Security\Core\Authorization\Voter\TraceableVoter;
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
use Symfony\Component\Security\Http\Firewall\SwitchUserListener;
use Symfony\Component\Security\Http\FirewallMapInterface;
use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator;
use Symfony\Component\VarDumper\Caster\ClassStub;
use Symfony\Component\VarDumper\Cloner\Data;
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class SecurityDataCollector extends DataCollector implements LateDataCollectorInterface
{
private ?TokenStorageInterface $tokenStorage;
private ?RoleHierarchyInterface $roleHierarchy;
private ?LogoutUrlGenerator $logoutUrlGenerator;
private ?AccessDecisionManagerInterface $accessDecisionManager;
private ?FirewallMapInterface $firewallMap;
private ?TraceableFirewallListener $firewall;
private bool $hasVarDumper;
public function __construct(?TokenStorageInterface $tokenStorage = null, ?RoleHierarchyInterface $roleHierarchy = null, ?LogoutUrlGenerator $logoutUrlGenerator = null, ?AccessDecisionManagerInterface $accessDecisionManager = null, ?FirewallMapInterface $firewallMap = null, ?TraceableFirewallListener $firewall = null)
{
$this->tokenStorage = $tokenStorage;
$this->roleHierarchy = $roleHierarchy;
$this->logoutUrlGenerator = $logoutUrlGenerator;
$this->accessDecisionManager = $accessDecisionManager;
$this->firewallMap = $firewallMap;
$this->firewall = $firewall;
$this->hasVarDumper = class_exists(ClassStub::class);
}
public function collect(Request $request, Response $response, ?\Throwable $exception = null): void
{
if (null === $this->tokenStorage) {
$this->data = [
'enabled' => false,
'authenticated' => false,
'impersonated' => false,
'impersonator_user' => null,
'impersonation_exit_path' => null,
'token' => null,
'token_class' => null,
'logout_url' => null,
'user' => '',
'roles' => [],
'inherited_roles' => [],
'supports_role_hierarchy' => null !== $this->roleHierarchy,
];
} elseif (null === $token = $this->tokenStorage->getToken()) {
$this->data = [
'enabled' => true,
'authenticated' => false,
'impersonated' => false,
'impersonator_user' => null,
'impersonation_exit_path' => null,
'token' => null,
'token_class' => null,
'logout_url' => null,
'user' => '',
'roles' => [],
'inherited_roles' => [],
'supports_role_hierarchy' => null !== $this->roleHierarchy,
];
} else {
$inheritedRoles = [];
$assignedRoles = $token->getRoleNames();
$impersonatorUser = null;
if ($token instanceof SwitchUserToken) {
$originalToken = $token->getOriginalToken();
$impersonatorUser = $originalToken->getUserIdentifier();
}
if (null !== $this->roleHierarchy) {
foreach ($this->roleHierarchy->getReachableRoleNames($assignedRoles) as $role) {
if (!\in_array($role, $assignedRoles, true)) {
$inheritedRoles[] = $role;
}
}
}
$logoutUrl = null;
try {
$logoutUrl = $this->logoutUrlGenerator?->getLogoutPath();
} catch (\Exception) {
// fail silently when the logout URL cannot be generated
}
$this->data = [
'enabled' => true,
'authenticated' => (bool) $token->getUser(),
'impersonated' => null !== $impersonatorUser,
'impersonator_user' => $impersonatorUser,
'impersonation_exit_path' => null,
'token' => $token,
'token_class' => $this->hasVarDumper ? new ClassStub($token::class) : $token::class,
'logout_url' => $logoutUrl,
'user' => $token->getUserIdentifier(),
'roles' => $assignedRoles,
'inherited_roles' => array_unique($inheritedRoles),
'supports_role_hierarchy' => null !== $this->roleHierarchy,
];
}
// collect voters and access decision manager information
if ($this->accessDecisionManager instanceof TraceableAccessDecisionManager) {
$this->data['voter_strategy'] = $this->accessDecisionManager->getStrategy();
$this->data['voters'] = [];
foreach ($this->accessDecisionManager->getVoters() as $voter) {
if ($voter instanceof TraceableVoter) {
$voter = $voter->getDecoratedVoter();
}
$this->data['voters'][] = $this->hasVarDumper ? new ClassStub($voter::class) : $voter::class;
}
// collect voter details
$decisionLog = $this->accessDecisionManager->getDecisionLog();
foreach ($decisionLog as $key => $log) {
$decisionLog[$key]['voter_details'] = [];
foreach ($log['voterDetails'] as $voterDetail) {
$voterClass = $voterDetail['voter']::class;
$classData = $this->hasVarDumper ? new ClassStub($voterClass) : $voterClass;
$decisionLog[$key]['voter_details'][] = [
'class' => $classData,
'attributes' => $voterDetail['attributes'], // Only displayed for unanimous strategy
'vote' => $voterDetail['vote'],
];
}
unset($decisionLog[$key]['voterDetails']);
}
$this->data['access_decision_log'] = $decisionLog;
} else {
$this->data['access_decision_log'] = [];
$this->data['voter_strategy'] = 'unknown';
$this->data['voters'] = [];
}
// collect firewall context information
$this->data['firewall'] = null;
if ($this->firewallMap instanceof FirewallMap) {
$firewallConfig = $this->firewallMap->getFirewallConfig($request);
if (null !== $firewallConfig) {
$this->data['firewall'] = [
'name' => $firewallConfig->getName(),
'request_matcher' => $firewallConfig->getRequestMatcher(),
'security_enabled' => $firewallConfig->isSecurityEnabled(),
'stateless' => $firewallConfig->isStateless(),
'provider' => $firewallConfig->getProvider(),
'context' => $firewallConfig->getContext(),
'entry_point' => $firewallConfig->getEntryPoint(),
'access_denied_handler' => $firewallConfig->getAccessDeniedHandler(),
'access_denied_url' => $firewallConfig->getAccessDeniedUrl(),
'user_checker' => $firewallConfig->getUserChecker(),
'authenticators' => $firewallConfig->getAuthenticators(),
];
// generate exit impersonation path from current request
if ($this->data['impersonated'] && null !== $switchUserConfig = $firewallConfig->getSwitchUser()) {
$exitPath = $request->getRequestUri();
$exitPath .= null === $request->getQueryString() ? '?' : '&';
$exitPath .= \sprintf('%s=%s', urlencode($switchUserConfig['parameter']), SwitchUserListener::EXIT_VALUE);
$this->data['impersonation_exit_path'] = $exitPath;
}
}
}
// collect firewall listeners information
$this->data['listeners'] = [];
if ($this->firewall) {
$this->data['listeners'] = $this->firewall->getWrappedListeners();
}
$this->data['authenticators'] = $this->firewall ? $this->firewall->getAuthenticatorsInfo() : [];
}
public function reset(): void
{
$this->data = [];
}
public function lateCollect(): void
{
$this->data = $this->cloneVar($this->data);
}
/**
* Checks if security is enabled.
*/
public function isEnabled(): bool
{
return $this->data['enabled'];
}
/**
* Gets the user.
*/
public function getUser(): string
{
return $this->data['user'];
}
/**
* Gets the roles of the user.
*/
public function getRoles(): array|Data
{
return $this->data['roles'];
}
/**
* Gets the inherited roles of the user.
*/
public function getInheritedRoles(): array|Data
{
return $this->data['inherited_roles'];
}
/**
* Checks if the data contains information about inherited roles. Still the inherited
* roles can be an empty array.
*/
public function supportsRoleHierarchy(): bool
{
return $this->data['supports_role_hierarchy'];
}
/**
* Checks if the user is authenticated or not.
*/
public function isAuthenticated(): bool
{
return $this->data['authenticated'];
}
public function isImpersonated(): bool
{
return $this->data['impersonated'];
}
public function getImpersonatorUser(): ?string
{
return $this->data['impersonator_user'];
}
public function getImpersonationExitPath(): ?string
{
return $this->data['impersonation_exit_path'];
}
/**
* Get the class name of the security token.
*/
public function getTokenClass(): string|Data|null
{
return $this->data['token_class'];
}
/**
* Get the full security token class as Data object.
*/
public function getToken(): ?Data
{
return $this->data['token'];
}
/**
* Get the logout URL.
*/
public function getLogoutUrl(): ?string
{
return $this->data['logout_url'];
}
/**
* Returns the FQCN of the security voters enabled in the application.
*
* @return string[]|Data
*/
public function getVoters(): array|Data
{
return $this->data['voters'];
}
/**
* Returns the strategy configured for the security voters.
*/
public function getVoterStrategy(): string
{
return $this->data['voter_strategy'];
}
/**
* Returns the log of the security decisions made by the access decision manager.
*/
public function getAccessDecisionLog(): array|Data
{
return $this->data['access_decision_log'];
}
/**
* Returns the configuration of the current firewall context.
*/
public function getFirewall(): array|Data|null
{
return $this->data['firewall'];
}
public function getListeners(): array|Data
{
return $this->data['listeners'];
}
public function getAuthenticators(): array|Data
{
return $this->data['authenticators'];
}
public function getName(): string
{
return 'security';
}
}

View File

@@ -0,0 +1,107 @@
<?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\Debug;
use Symfony\Bundle\SecurityBundle\EventListener\FirewallListener;
use Symfony\Bundle\SecurityBundle\Security\FirewallContext;
use Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticatorManagerListener;
use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface;
use Symfony\Contracts\Service\ResetInterface;
/**
* Firewall collecting called security listeners and authenticators.
*
* @author Robin Chalas <robin.chalas@gmail.com>
*/
final class TraceableFirewallListener extends FirewallListener implements ResetInterface
{
private array $wrappedListeners = [];
private array $authenticatorsInfo = [];
/**
* @return array
*/
public function getWrappedListeners()
{
return $this->wrappedListeners;
}
public function getAuthenticatorsInfo(): array
{
return $this->authenticatorsInfo;
}
public function reset(): void
{
$this->wrappedListeners = [];
$this->authenticatorsInfo = [];
}
protected function callListeners(RequestEvent $event, iterable $listeners): void
{
$wrappedListeners = [];
$wrappedLazyListeners = [];
$authenticatorManagerListener = null;
foreach ($listeners as $listener) {
if ($listener instanceof LazyFirewallContext) {
\Closure::bind(function () use (&$wrappedLazyListeners, &$wrappedListeners, &$authenticatorManagerListener) {
$listeners = [];
foreach ($this->listeners as $listener) {
if (!$authenticatorManagerListener && $listener instanceof TraceableAuthenticatorManagerListener) {
$authenticatorManagerListener = $listener;
}
if ($listener instanceof FirewallListenerInterface) {
$listener = new WrappedLazyListener($listener);
$listeners[] = $listener;
$wrappedLazyListeners[] = $listener;
} else {
$listeners[] = function (RequestEvent $event) use ($listener, &$wrappedListeners) {
$wrappedListener = new WrappedListener($listener);
$wrappedListener($event);
$wrappedListeners[] = $wrappedListener->getInfo();
};
}
}
$this->listeners = $listeners;
}, $listener, FirewallContext::class)();
$listener($event);
} else {
$wrappedListener = $listener instanceof FirewallListenerInterface ? new WrappedLazyListener($listener) : new WrappedListener($listener);
$wrappedListener($event);
$wrappedListeners[] = $wrappedListener->getInfo();
if (!$authenticatorManagerListener && $listener instanceof TraceableAuthenticatorManagerListener) {
$authenticatorManagerListener = $listener;
}
}
if ($event->hasResponse()) {
break;
}
}
if ($wrappedLazyListeners) {
foreach ($wrappedLazyListeners as $lazyListener) {
$this->wrappedListeners[] = $lazyListener->getInfo();
}
}
$this->wrappedListeners = array_merge($this->wrappedListeners, $wrappedListeners);
if ($authenticatorManagerListener) {
$this->authenticatorsInfo = $authenticatorManagerListener->getAuthenticatorsInfo();
}
}
}

View File

@@ -0,0 +1,51 @@
<?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\Debug;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticatorManagerListener;
use Symfony\Component\VarDumper\Caster\ClassStub;
/**
* @author Robin Chalas <robin.chalas@gmail.com>
*
* @internal
*/
trait TraceableListenerTrait
{
private ?Response $response = null;
private mixed $listener;
private ?float $time = null;
private object $stub;
/**
* Proxies all method calls to the original listener.
*/
public function __call(string $method, array $arguments): mixed
{
return $this->listener->{$method}(...$arguments);
}
public function getWrappedListener(): mixed
{
return $this->listener;
}
public function getInfo(): array
{
return [
'response' => $this->response,
'time' => $this->time,
'stub' => $this->stub ??= ClassStub::wrapCallable($this->listener instanceof TraceableAuthenticatorManagerListener ? $this->listener->getAuthenticatorManagerListener() : $this->listener),
];
}
}

View File

@@ -0,0 +1,57 @@
<?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\Debug;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Exception\LazyResponseException;
use Symfony\Component\Security\Http\Firewall\AbstractListener;
use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface;
/**
* Wraps a lazy security listener.
*
* @author Robin Chalas <robin.chalas@gmail.com>
*
* @internal
*/
final class WrappedLazyListener extends AbstractListener
{
use TraceableListenerTrait;
public function __construct(FirewallListenerInterface $listener)
{
$this->listener = $listener;
}
public function supports(Request $request): ?bool
{
return $this->listener->supports($request);
}
public function authenticate(RequestEvent $event): void
{
$startTime = microtime(true);
try {
$this->listener->authenticate($event);
} catch (LazyResponseException $e) {
$this->response = $e->getResponse();
throw $e;
} finally {
$this->time = microtime(true) - $startTime;
}
$this->response = $event->getResponse();
}
}

View File

@@ -0,0 +1,42 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\Debug;
use Symfony\Component\HttpKernel\Event\RequestEvent;
/**
* Wraps a security listener for calls record.
*
* @author Robin Chalas <robin.chalas@gmail.com>
*
* @internal
*/
final class WrappedListener
{
use TraceableListenerTrait;
/**
* @param callable(RequestEvent):void $listener
*/
public function __construct(callable $listener)
{
$this->listener = $listener;
}
public function __invoke(RequestEvent $event): void
{
$startTime = microtime(true);
($this->listener)($event);
$this->time = microtime(true) - $startTime;
$this->response = $event->getResponse();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,75 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\EventListener;
use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Http\Firewall;
use Symfony\Component\Security\Http\FirewallMapInterface;
use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
/**
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
class FirewallListener extends Firewall
{
private FirewallMapInterface $map;
private LogoutUrlGenerator $logoutUrlGenerator;
public function __construct(FirewallMapInterface $map, EventDispatcherInterface $dispatcher, LogoutUrlGenerator $logoutUrlGenerator)
{
$this->map = $map;
$this->logoutUrlGenerator = $logoutUrlGenerator;
parent::__construct($map, $dispatcher);
}
/**
* @return void
*/
public function configureLogoutUrlGenerator(RequestEvent $event)
{
if (!$event->isMainRequest()) {
return;
}
if ($this->map instanceof FirewallMap && $config = $this->map->getFirewallConfig($event->getRequest())) {
$this->logoutUrlGenerator->setCurrentFirewall($config->getName(), $config->getContext());
}
}
/**
* @return void
*/
public function onKernelFinishRequest(FinishRequestEvent $event)
{
if ($event->isMainRequest()) {
$this->logoutUrlGenerator->setCurrentFirewall(null);
}
parent::onKernelFinishRequest($event);
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => [
['configureLogoutUrlGenerator', 8],
['onKernelRequest', 8],
],
KernelEvents::FINISH_REQUEST => 'onKernelFinishRequest',
];
}
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager;
use Symfony\Component\Security\Core\Event\VoteEvent;
/**
* Listen to vote events from traceable voters.
*
* @author Laurent VOULLEMIER <laurent.voullemier@gmail.com>
*
* @internal
*/
class VoteListener implements EventSubscriberInterface
{
private TraceableAccessDecisionManager $traceableAccessDecisionManager;
public function __construct(TraceableAccessDecisionManager $traceableAccessDecisionManager)
{
$this->traceableAccessDecisionManager = $traceableAccessDecisionManager;
}
public function onVoterVote(VoteEvent $event): void
{
$this->traceableAccessDecisionManager->addVoterVote($event->getVoter(), $event->getAttributes(), $event->getVote());
}
public static function getSubscribedEvents(): array
{
return ['debug.security.authorization.vote' => 'onVoterVote'];
}
}

19
vendor/symfony/security-bundle/LICENSE vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2004-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,50 @@
<?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\LoginLink;
use Psr\Container\ContainerInterface;
use Symfony\Bundle\SecurityBundle\Security\FirewallAwareTrait;
use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\LoginLink\LoginLinkDetails;
use Symfony\Component\Security\Http\LoginLink\LoginLinkHandlerInterface;
/**
* Decorates the login link handler for the current firewall.
*
* @author Ryan Weaver <ryan@symfonycasts.com>
*/
class FirewallAwareLoginLinkHandler implements LoginLinkHandlerInterface
{
use FirewallAwareTrait;
private const FIREWALL_OPTION = 'login_link';
public function __construct(FirewallMap $firewallMap, ContainerInterface $loginLinkHandlerLocator, RequestStack $requestStack)
{
$this->firewallMap = $firewallMap;
$this->locator = $loginLinkHandlerLocator;
$this->requestStack = $requestStack;
}
public function createLoginLink(UserInterface $user, ?Request $request = null, ?int $lifetime = null): LoginLinkDetails
{
return $this->getForFirewall()->createLoginLink($user, $request, $lifetime);
}
public function consumeLoginLink(Request $request): UserInterface
{
return $this->getForFirewall()->consumeLoginLink($request);
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\RememberMe;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\RememberMe\RememberMeDetails;
use Symfony\Component\Security\Http\RememberMe\RememberMeHandlerInterface;
/**
* Used as a "workaround" for tagging aliases in the RememberMeFactory.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*
* @internal
*/
final class DecoratedRememberMeHandler implements RememberMeHandlerInterface
{
private RememberMeHandlerInterface $handler;
public function __construct(RememberMeHandlerInterface $handler)
{
$this->handler = $handler;
}
public function createRememberMeCookie(UserInterface $user): void
{
$this->handler->createRememberMeCookie($user);
}
public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails): UserInterface
{
return $this->handler->consumeRememberMeCookie($rememberMeDetails);
}
public function clearRememberMeCookie(): void
{
$this->handler->clearRememberMeCookie();
}
}

View File

@@ -0,0 +1,54 @@
<?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\RememberMe;
use Psr\Container\ContainerInterface;
use Symfony\Bundle\SecurityBundle\Security\FirewallAwareTrait;
use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\RememberMe\RememberMeDetails;
use Symfony\Component\Security\Http\RememberMe\RememberMeHandlerInterface;
/**
* Decorates {@see RememberMeHandlerInterface} for the current firewall.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
final class FirewallAwareRememberMeHandler implements RememberMeHandlerInterface
{
use FirewallAwareTrait;
private const FIREWALL_OPTION = 'remember_me';
public function __construct(FirewallMap $firewallMap, ContainerInterface $rememberMeHandlerLocator, RequestStack $requestStack)
{
$this->firewallMap = $firewallMap;
$this->locator = $rememberMeHandlerLocator;
$this->requestStack = $requestStack;
}
public function createRememberMeCookie(UserInterface $user): void
{
$this->getForFirewall()->createRememberMeCookie($user);
}
public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails): UserInterface
{
return $this->getForFirewall()->consumeRememberMeCookie($rememberMeDetails);
}
public function clearRememberMeCookie(): void
{
$this->getForFirewall()->clearRememberMeCookie();
}
}

View File

@@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Bundle\SecurityBundle\DataCollector\SecurityDataCollector;
return static function (ContainerConfigurator $container) {
$container->services()
->set('data_collector.security', SecurityDataCollector::class)
->args([
service('security.untracked_token_storage'),
service('security.role_hierarchy'),
service('security.logout_url_generator'),
service('security.access.decision_manager'),
service('security.firewall.map'),
service('debug.security.firewall')->nullOnInvalid(),
])
->tag('data_collector', [
'template' => '@Security/Collector/security.html.twig',
'id' => 'security',
'priority' => 270,
])
;
};

View File

@@ -0,0 +1,25 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\PasswordHasher\Command\UserPasswordHashCommand;
return static function (ContainerConfigurator $container) {
$container->services()
->set('security.command.user_password_hash', UserPasswordHashCommand::class)
->args([
service('security.password_hasher_factory'),
abstract_arg('list of user classes'),
])
->tag('console.command')
;
};

View File

@@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Bundle\SecurityBundle\Command\DebugFirewallCommand;
return static function (ContainerConfigurator $container) {
$container->services()
->set('security.command.debug_firewall', DebugFirewallCommand::class)
->args([
param('security.firewalls'),
service('security.firewall.context_locator'),
tagged_locator('event_dispatcher.dispatcher', 'name'),
[],
false,
])
->tag('console.command', ['command' => 'debug:firewall'])
;
};

View File

@@ -0,0 +1,53 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\PasswordHasher\EventListener\PasswordHasherListener;
use Symfony\Component\Form\Extension\PasswordHasher\Type\FormTypePasswordHasherExtension;
use Symfony\Component\Form\Extension\PasswordHasher\Type\PasswordTypePasswordHasherExtension;
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory;
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
return static function (ContainerConfigurator $container) {
$container->services()
->set('security.password_hasher_factory', PasswordHasherFactory::class)
->args([[]])
->alias(PasswordHasherFactoryInterface::class, 'security.password_hasher_factory')
->set('security.user_password_hasher', UserPasswordHasher::class)
->args([service('security.password_hasher_factory')])
->alias('security.password_hasher', 'security.user_password_hasher')
->alias(UserPasswordHasherInterface::class, 'security.password_hasher')
->set('form.listener.password_hasher', PasswordHasherListener::class)
->args([
service('security.password_hasher'),
service('property_accessor')->nullOnInvalid(),
])
->set('form.type_extension.form.password_hasher', FormTypePasswordHasherExtension::class)
->args([
service('form.listener.password_hasher'),
])
->tag('form.type_extension', ['extended-type' => FormType::class])
->set('form.type_extension.password.password_hasher', PasswordTypePasswordHasherExtension::class)
->args([
service('form.listener.password_hasher'),
])
->tag('form.type_extension', ['extended-type' => PasswordType::class])
;
};

View File

@@ -0,0 +1,470 @@
<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns="http://symfony.com/schema/dic/security"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://symfony.com/schema/dic/security"
elementFormDefault="qualified">
<xsd:element name="config" type="config" />
<xsd:complexType name="config">
<xsd:choice maxOccurs="unbounded">
<xsd:element name="access-decision-manager" type="access_decision_manager" minOccurs="0" maxOccurs="1" />
<xsd:element name="password_hashers" type="password_hashers" minOccurs="0" maxOccurs="1" />
<xsd:element name="password_hasher" type="password_hasher" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="providers" type="providers" minOccurs="0" maxOccurs="1" />
<xsd:element name="provider" type="provider" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="firewalls" type="firewalls" minOccurs="0" maxOccurs="1" />
<xsd:element name="firewall" type="firewall" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="rule" type="rule" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="role" type="role" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="access-denied-url" type="xsd:string" />
<xsd:attribute name="session-fixation-strategy" type="session_fixation_strategy" />
<xsd:attribute name="hide-user-not-found" type="xsd:boolean" />
<xsd:attribute name="always-authenticate-before-granting" type="xsd:boolean" />
<xsd:attribute name="erase-credentials" type="xsd:boolean" />
<xsd:attribute name="enable-authenticator-manager" type="xsd:boolean" />
</xsd:complexType>
<xsd:complexType name="password_hashers">
<xsd:sequence>
<xsd:element name="password_hasher" type="password_hasher" minOccurs="1" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="providers">
<xsd:sequence>
<xsd:element name="provider" type="provider" minOccurs="1" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="firewalls">
<xsd:sequence>
<xsd:element name="firewall" type="firewall" minOccurs="1" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
<xsd:simpleType name="session_fixation_strategy">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="none" />
<xsd:enumeration value="migrate" />
<xsd:enumeration value="invalidate" />
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="access_decision_manager">
<xsd:attribute name="strategy" type="access_decision_manager_strategy" />
<xsd:attribute name="service" type="xsd:string" />
<xsd:attribute name="strategy-service" type="xsd:string" />
<xsd:attribute name="allow-if-all-abstain" type="xsd:boolean" />
<xsd:attribute name="allow-if-equal-granted-denied" type="xsd:boolean" />
</xsd:complexType>
<xsd:simpleType name="access_decision_manager_strategy">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="affirmative" />
<xsd:enumeration value="consensus" />
<xsd:enumeration value="unanimous" />
<xsd:enumeration value="priority" />
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="password_hasher">
<xsd:sequence>
<xsd:element name="migrate-from" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="class" type="xsd:string" use="required" />
<xsd:attribute name="algorithm" type="xsd:string" />
<xsd:attribute name="hash-algorithm" type="xsd:string" />
<xsd:attribute name="key-length" type="xsd:string" />
<xsd:attribute name="ignore-case" type="xsd:boolean" />
<xsd:attribute name="encode-as-base64" type="xsd:boolean" />
<xsd:attribute name="iterations" type="xsd:string" />
<xsd:attribute name="cost" type="xsd:integer" />
<xsd:attribute name="memory-cost" type="xsd:string" />
<xsd:attribute name="time-cost" type="xsd:string" />
<xsd:attribute name="id" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="provider">
<xsd:choice minOccurs="0" maxOccurs="1">
<xsd:element name="chain" type="chain" />
<xsd:element name="memory" type="memory" />
<xsd:element name="ldap" type="ldap" />
<!-- allow factories to use dynamic elements -->
<xsd:any processContents="lax" namespace="##other" />
</xsd:choice>
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attribute name="id" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="chain">
<xsd:sequence minOccurs="0" maxOccurs="unbounded">
<xsd:element name="provider" type="xsd:string" />
</xsd:sequence>
<xsd:attribute name="providers" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="memory">
<xsd:sequence>
<xsd:element name="user" type="user" minOccurs="1" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="user">
<xsd:attribute name="identifier" type="xsd:string" />
<xsd:attribute name="password" type="xsd:string" />
<xsd:attribute name="roles" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="ldap">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="extra-field" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="default-role" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="service" type="xsd:string" use="required" />
<xsd:attribute name="base-dn" type="xsd:string" use="required" />
<xsd:attribute name="search-dn" type="xsd:string" />
<xsd:attribute name="search-password" type="xsd:string" />
<xsd:attribute name="uid-key" type="xsd:string" />
<xsd:attribute name="filter" type="xsd:string" />
<xsd:attribute name="password-attribute" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="firewall">
<xsd:choice maxOccurs="unbounded">
<xsd:element name="logout" type="logout" minOccurs="0" maxOccurs="1" />
<xsd:element name="switch-user" type="switch_user" minOccurs="0" maxOccurs="1" />
<xsd:element name="anonymous" type="anonymous" minOccurs="0" maxOccurs="1" />
<xsd:element name="form-login" type="form_login" minOccurs="0" maxOccurs="1" />
<xsd:element name="form-login-ldap" type="form_login_ldap" minOccurs="0" maxOccurs="1" />
<xsd:element name="guard" type="guard" minOccurs="0" maxOccurs="1" />
<xsd:element name="access-token" type="access_token" minOccurs="0" maxOccurs="1" />
<xsd:element name="http-basic" type="http_basic" minOccurs="0" maxOccurs="1" />
<xsd:element name="http-basic-ldap" type="http_basic_ldap" minOccurs="0" maxOccurs="1" />
<xsd:element name="json-login" type="json_login" minOccurs="0" maxOccurs="1" />
<xsd:element name="json-login-ldap" type="json_login_ldap" minOccurs="0" maxOccurs="1" />
<xsd:element name="login-link" type="login_link" minOccurs="0" maxOccurs="1" />
<xsd:element name="login-throttling" type="login_throttling" minOccurs="0" maxOccurs="1" />
<xsd:element name="remember-me" type="remember_me" minOccurs="0" maxOccurs="1" />
<xsd:element name="remote-user" type="remote_user" minOccurs="0" maxOccurs="1" />
<xsd:element name="x509" type="x509" minOccurs="0" maxOccurs="1" />
<xsd:element name="required-badge" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
<!-- allow factories to use dynamic elements -->
<xsd:any processContents="lax" minOccurs="0" maxOccurs="unbounded" namespace="##other" />
</xsd:choice>
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attribute name="pattern" type="xsd:string" />
<xsd:attribute name="host" type="xsd:string" />
<xsd:attribute name="methods" type="xsd:string" />
<xsd:attribute name="security" type="xsd:boolean" />
<xsd:attribute name="user-checker" type="xsd:string" />
<xsd:attribute name="request-matcher" type="xsd:string" />
<xsd:attribute name="access-denied-url" type="xsd:string" />
<xsd:attribute name="access-denied-handler" type="xsd:string" />
<xsd:attribute name="entry-point" type="xsd:string" />
<xsd:attribute name="provider" type="xsd:string" />
<xsd:attribute name="stateless" type="xsd:boolean" />
<xsd:attribute name="context" type="xsd:string" />
<xsd:attribute name="lazy" type="xsd:boolean" />
<!-- allow factories to use dynamic elements -->
<xsd:anyAttribute processContents="lax" />
</xsd:complexType>
<xsd:complexType name="logout">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="delete-cookie" type="delete_cookie" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="clear-site-data" type="clear_site_data" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="csrf-parameter" type="xsd:string" />
<xsd:attribute name="csrf-token-generator" type="xsd:string" />
<xsd:attribute name="csrf-token-manager" type="xsd:string" />
<xsd:attribute name="csrf-token-id" type="xsd:string" />
<xsd:attribute name="enable-csrf" type="xsd:boolean" />
<xsd:attribute name="path" type="xsd:string" />
<xsd:attribute name="target" type="xsd:string" />
<xsd:attribute name="invalidate-session" type="xsd:boolean" />
</xsd:complexType>
<xsd:complexType name="delete_cookie">
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attribute name="path" type="xsd:string" />
<xsd:attribute name="domain" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="switch_user">
<xsd:attribute name="provider" type="xsd:string" />
<xsd:attribute name="parameter" type="xsd:string" />
<xsd:attribute name="role" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="anonymous">
<xsd:attribute name="lazy" type="xsd:boolean" />
<xsd:attribute name="secret" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="factory" abstract="true">
<xsd:attribute name="check-path" type="xsd:string" />
<xsd:attribute name="use-forward" type="xsd:boolean" />
<xsd:attribute name="require-previous-session" type="xsd:boolean" />
<xsd:attribute name="provider" type="xsd:string" />
</xsd:complexType>
<xsd:attributeGroup name="success-handler-options">
<xsd:attribute name="always-use-default-target-path" type="xsd:boolean" />
<xsd:attribute name="default-target-path" type="xsd:string" />
<xsd:attribute name="target-path-parameter" type="xsd:string" />
<xsd:attribute name="use-referer" type="xsd:boolean" />
</xsd:attributeGroup>
<xsd:attributeGroup name="failure-handler-options">
<xsd:attribute name="failure-path" type="xsd:string" />
<xsd:attribute name="failure-forward" type="xsd:boolean" />
<xsd:attribute name="failure-path-parameter" type="xsd:string" />
</xsd:attributeGroup>
<xsd:attributeGroup name="ldap-factory">
<xsd:attribute name="service" type="xsd:string" />
<xsd:attribute name="dn-string" type="xsd:string" />
<xsd:attribute name="query-string" type="xsd:string" />
<xsd:attribute name="search-dn" type="xsd:string" />
<xsd:attribute name="search-password" type="xsd:string" />
</xsd:attributeGroup>
<xsd:complexType name="form_login">
<xsd:complexContent>
<xsd:extension base="factory">
<xsd:attribute name="login-path" type="xsd:string" />
<xsd:attribute name="username-parameter" type="xsd:string" />
<xsd:attribute name="password-parameter" type="xsd:string" />
<xsd:attribute name="csrf-parameter" type="xsd:string" />
<xsd:attribute name="csrf-token-id" type="xsd:string" />
<xsd:attribute name="post-only" type="xsd:boolean" />
<xsd:attribute name="csrf-token-generator" type="xsd:string" />
<xsd:attribute name="enable-csrf" type="xsd:boolean" />
<xsd:attributeGroup ref="success-handler-options" />
<xsd:attributeGroup ref="failure-handler-options" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="form_login_ldap">
<xsd:complexContent>
<xsd:extension base="form_login">
<xsd:attributeGroup ref="ldap-factory" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="guard">
<xsd:sequence>
<xsd:element name="authenticator" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="provider" type="xsd:string" />
<xsd:attribute name="entry-point" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="http_basic">
<xsd:attribute name="provider" type="xsd:string" />
<xsd:attribute name="realm" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="http_basic_ldap">
<xsd:complexContent>
<xsd:extension base="http_basic">
<xsd:attributeGroup ref="ldap-factory" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="json_login">
<xsd:complexContent>
<xsd:extension base="factory">
<xsd:attribute name="username-path" type="xsd:string" />
<xsd:attribute name="password-path" type="xsd:string" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="json_login_ldap">
<xsd:complexContent>
<xsd:extension base="json_login">
<xsd:attributeGroup ref="ldap-factory" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="login_link">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="signature-property" type="xsd:string" />
</xsd:choice>
<xsd:attribute name="check-route" type="xsd:string" />
<xsd:attribute name="check-post-only" type="xsd:boolean" />
<xsd:attribute name="lifetime" type="xsd:integer" />
<xsd:attribute name="max-uses" type="xsd:integer" />
<xsd:attribute name="used-link-cache" type="xsd:string" />
<xsd:attribute name="success-handler" type="xsd:string" />
<xsd:attribute name="failure-handler" type="xsd:string" />
<xsd:attribute name="provider" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="access_token">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="token-extractor" type="xsd:string" />
<xsd:element name="token-handler" type="oidc_token_handler" />
</xsd:choice>
<xsd:attribute name="token-handler" type="xsd:string" />
<xsd:attribute name="realm" type="xsd:string" />
<xsd:attribute name="success-handler" type="xsd:string" />
<xsd:attribute name="failure-handler" type="xsd:string" />
<xsd:attribute name="provider" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="oidc_token_handler">
<xsd:sequence>
<xsd:choice minOccurs="0" maxOccurs="1">
<xsd:element name="oidc-user-info" type="oidc_user_info"></xsd:element>
<xsd:element name="oidc" type="oidc"></xsd:element>
</xsd:choice>
</xsd:sequence>
<xsd:attribute name="oidc-user-info" type="xsd:anyURI"></xsd:attribute>
</xsd:complexType>
<xsd:complexType name="oidc_user_info">
<xsd:attribute name="base-uri" type="xsd:anyURI" use="required" />
<xsd:attribute name="claim" type="xsd:string" />
<xsd:attribute name="client" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="oidc">
<xsd:choice maxOccurs="unbounded">
<xsd:element name="issuers" type="oidc_issuers" minOccurs="0" maxOccurs="1" />
<xsd:element name="issuer" type="password_hasher" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="claim" type="xsd:string" />
<xsd:attribute name="audience" type="xsd:string" use="required" />
<xsd:attribute name="algorithm" type="xsd:string" use="required" />
<xsd:attribute name="key" type="xsd:string" use="required" />
</xsd:complexType>
<xsd:complexType name="oidc_issuers">
<xsd:sequence>
<xsd:element name="issuer" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="login_throttling">
<xsd:attribute name="limiter" type="xsd:string" />
<xsd:attribute name="max-attempts" type="xsd:integer" />
</xsd:complexType>
<xsd:complexType name="remember_me">
<xsd:sequence minOccurs="0">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="user-provider" type="xsd:string" />
</xsd:choice>
<xsd:element name="token-provider" type="remember_me_token_provider" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="lifetime" type="xsd:integer" />
<xsd:attribute name="path" type="xsd:string" />
<xsd:attribute name="domain" type="xsd:string" />
<xsd:attribute name="http-only" type="xsd:boolean" />
<xsd:attribute name="always-remember-me" type="xsd:boolean" />
<xsd:attribute name="remember-me-parameter" type="xsd:string" />
<xsd:attribute name="secret" type="xsd:string" use="required" />
<xsd:attribute name="service" type="xsd:string" />
<xsd:attribute name="token-provider" type="xsd:string" />
<xsd:attribute name="token-verifier" type="xsd:string" />
<xsd:attribute name="catch-exceptions" type="xsd:boolean" />
<xsd:attribute name="secure" type="remember_me_secure" />
<xsd:attribute name="samesite" type="remember_me_samesite" />
<xsd:attribute name="partitioned" type="xsd:boolean" />
</xsd:complexType>
<xsd:complexType name="remember_me_token_provider">
<xsd:sequence>
<xsd:element name="doctrine" type="remember_me_token_provider_doctrine" />
</xsd:sequence>
<xsd:attribute name="service" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="remember_me_token_provider_doctrine">
<xsd:attribute name="enabled" type="xsd:boolean" />
<xsd:attribute name="connection" type="xsd:string" />
</xsd:complexType>
<xsd:simpleType name="remember_me_secure">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="true" />
<xsd:enumeration value="false" />
<xsd:enumeration value="auto" />
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="remember_me_samesite">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="null" />
<xsd:enumeration value="lax" />
<xsd:enumeration value="strict" />
<xsd:enumeration value="none" />
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="remote_user">
<xsd:attribute name="provider" type="xsd:string" />
<xsd:attribute name="user" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="x509">
<xsd:attribute name="provider" type="xsd:string" />
<xsd:attribute name="user" type="xsd:string" />
<xsd:attribute name="credentials" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="rule">
<xsd:choice>
<xsd:element name="ip" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="method" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="role" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="allow-if" type="xsd:string" minOccurs="0" maxOccurs="1" />
<xsd:element name="attribute" type="rule_attribute" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attribute name="requires-channel" type="xsd:string" />
<xsd:attribute name="path" type="xsd:string" />
<xsd:attribute name="host" type="xsd:string" />
<xsd:attribute name="port" type="xsd:integer" />
<xsd:attribute name="role" type="xsd:string" />
<xsd:attribute name="methods" type="xsd:string" />
<xsd:attribute name="allow-if" type="xsd:string" />
<xsd:attribute name="route" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="role">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="id" type="xsd:string" use="required" />
<xsd:attribute name="value" type="xsd:string" />
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:complexType name="rule_attribute">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="key" type="xsd:string" use="required" />
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:simpleType name="clear_site_data">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="*" />
<xsd:enumeration value="cache" />
<xsd:enumeration value="cookies" />
<xsd:enumeration value="storage" />
<xsd:enumeration value="executionContexts" />
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>

View File

@@ -0,0 +1,313 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Bundle\SecurityBundle\CacheWarmer\ExpressionCacheWarmer;
use Symfony\Bundle\SecurityBundle\EventListener\FirewallListener;
use Symfony\Bundle\SecurityBundle\Routing\LogoutRouteLoader;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Bundle\SecurityBundle\Security\FirewallConfig;
use Symfony\Bundle\SecurityBundle\Security\FirewallContext;
use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
use Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage as BaseExpressionLanguage;
use Symfony\Component\Ldap\Security\LdapUserProvider;
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorage;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Authorization\ExpressionLanguage;
use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter;
use Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter;
use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter;
use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter;
use Symfony\Component\Security\Core\Role\RoleHierarchy;
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
use Symfony\Component\Security\Core\Security as LegacySecurity;
use Symfony\Component\Security\Core\User\ChainUserProvider;
use Symfony\Component\Security\Core\User\InMemoryUserChecker;
use Symfony\Component\Security\Core\User\InMemoryUserProvider;
use Symfony\Component\Security\Core\User\MissingUserProvider;
use Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Component\Security\Http\Controller\SecurityTokenValueResolver;
use Symfony\Component\Security\Http\Controller\UserValueResolver;
use Symfony\Component\Security\Http\EventListener\IsGrantedAttributeListener;
use Symfony\Component\Security\Http\Firewall;
use Symfony\Component\Security\Http\FirewallMapInterface;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\Impersonate\ImpersonateUrlGenerator;
use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
return static function (ContainerConfigurator $container) {
$container->parameters()
->set('security.role_hierarchy.roles', [])
;
$container->services()
->set('security.authorization_checker', AuthorizationChecker::class)
->args([
service('security.token_storage'),
service('security.access.decision_manager'),
])
->alias(AuthorizationCheckerInterface::class, 'security.authorization_checker')
->set('security.token_storage', UsageTrackingTokenStorage::class)
->args([
service('security.untracked_token_storage'),
service_locator([
'request_stack' => service('request_stack'),
]),
])
->tag('kernel.reset', ['method' => 'disableUsageTracking'])
->tag('kernel.reset', ['method' => 'setToken'])
->alias(TokenStorageInterface::class, 'security.token_storage')
->set('security.untracked_token_storage', TokenStorage::class)
->set('security.helper', Security::class)
->args([
service_locator([
'security.token_storage' => service('security.token_storage'),
'security.authorization_checker' => service('security.authorization_checker'),
'security.authenticator.managers_locator' => service('security.authenticator.managers_locator')->ignoreOnInvalid(),
'request_stack' => service('request_stack'),
'security.firewall.map' => service('security.firewall.map'),
'security.user_checker_locator' => service('security.user_checker_locator'),
'security.firewall.event_dispatcher_locator' => service('security.firewall.event_dispatcher_locator'),
'security.csrf.token_manager' => service('security.csrf.token_manager')->ignoreOnInvalid(),
]),
abstract_arg('authenticators'),
])
->alias(Security::class, 'security.helper')
->alias(LegacySecurity::class, 'security.helper')
->deprecate('symfony/security-bundle', '6.2', 'The "%alias_id%" service alias is deprecated, use "'.Security::class.'" instead.')
->set('security.user_value_resolver', UserValueResolver::class)
->args([
service('security.token_storage'),
])
->tag('controller.argument_value_resolver', ['priority' => 120, 'name' => UserValueResolver::class])
->set('security.security_token_value_resolver', SecurityTokenValueResolver::class)
->args([
service('security.token_storage'),
])
->tag('controller.argument_value_resolver', ['priority' => 120, 'name' => SecurityTokenValueResolver::class])
// Authentication related services
->set('security.authentication.trust_resolver', AuthenticationTrustResolver::class)
->set('security.authentication.session_strategy', SessionAuthenticationStrategy::class)
->args([
param('security.authentication.session_strategy.strategy'),
service('security.csrf.token_storage')->ignoreOnInvalid(),
])
->alias(SessionAuthenticationStrategyInterface::class, 'security.authentication.session_strategy')
->set('security.authentication.session_strategy_noop', SessionAuthenticationStrategy::class)
->args(['none'])
->set('security.user_checker', InMemoryUserChecker::class)
->set('security.user_checker_locator', ServiceLocator::class)
->args([[]])
->set('security.expression_language', ExpressionLanguage::class)
->args([service('cache.security_expression_language')->nullOnInvalid()])
->set('security.authentication_utils', AuthenticationUtils::class)
->args([service('request_stack')])
->alias(AuthenticationUtils::class, 'security.authentication_utils')
// Authorization related services
->set('security.access.decision_manager', AccessDecisionManager::class)
->args([[]])
->alias(AccessDecisionManagerInterface::class, 'security.access.decision_manager')
->set('security.role_hierarchy', RoleHierarchy::class)
->args([param('security.role_hierarchy.roles')])
->alias(RoleHierarchyInterface::class, 'security.role_hierarchy')
// Security Voters
->set('security.access.simple_role_voter', RoleVoter::class)
->tag('security.voter', ['priority' => 245])
->set('security.access.authenticated_voter', AuthenticatedVoter::class)
->args([service('security.authentication.trust_resolver')])
->tag('security.voter', ['priority' => 250])
->set('security.access.role_hierarchy_voter', RoleHierarchyVoter::class)
->args([service('security.role_hierarchy')])
->tag('security.voter', ['priority' => 245])
->set('security.access.expression_voter', ExpressionVoter::class)
->args([
service('security.expression_language'),
service('security.authentication.trust_resolver'),
service('security.authorization_checker'),
service('security.role_hierarchy')->nullOnInvalid(),
])
->tag('security.voter', ['priority' => 245])
->set('security.impersonate_url_generator', ImpersonateUrlGenerator::class)
->args([
service('request_stack'),
service('security.firewall.map'),
service('security.token_storage'),
])
// Firewall related services
->set('security.firewall', FirewallListener::class)
->args([
service('security.firewall.map'),
service('event_dispatcher'),
service('security.logout_url_generator'),
])
->tag('kernel.event_subscriber')
->alias(Firewall::class, 'security.firewall')
->set('security.firewall.map', FirewallMap::class)
->args([
abstract_arg('Firewall context locator'),
abstract_arg('Request matchers'),
])
->alias(FirewallMapInterface::class, 'security.firewall.map')
->set('security.firewall.context', FirewallContext::class)
->abstract()
->args([
[],
service('security.exception_listener'),
abstract_arg('LogoutListener'),
abstract_arg('FirewallConfig'),
])
->set('security.firewall.lazy_context', LazyFirewallContext::class)
->abstract()
->args([
[],
service('security.exception_listener'),
abstract_arg('LogoutListener'),
abstract_arg('FirewallConfig'),
service('security.untracked_token_storage'),
])
->set('security.firewall.config', FirewallConfig::class)
->abstract()
->args([
abstract_arg('name'),
abstract_arg('user_checker'),
abstract_arg('request_matcher'),
false, // security enabled
false, // stateless
null,
null,
null,
null,
null,
[], // listeners
null, // switch_user
null, // logout
])
->set('security.logout_url_generator', LogoutUrlGenerator::class)
->args([
service('request_stack')->nullOnInvalid(),
service('router')->nullOnInvalid(),
service('security.token_storage')->nullOnInvalid(),
])
->set('security.route_loader.logout', LogoutRouteLoader::class)
->args([
'%security.logout_uris%',
'security.logout_uris',
])
->tag('routing.route_loader')
// Provisioning
->set('security.user.provider.missing', MissingUserProvider::class)
->abstract()
->args([
abstract_arg('firewall'),
])
->set('security.user.provider.in_memory', InMemoryUserProvider::class)
->abstract()
->set('security.user.provider.ldap', LdapUserProvider::class)
->abstract()
->args([
abstract_arg('security.ldap.ldap'),
abstract_arg('base dn'),
abstract_arg('search dn'),
abstract_arg('search password'),
abstract_arg('default_roles'),
abstract_arg('uid key'),
abstract_arg('filter'),
abstract_arg('password_attribute'),
abstract_arg('extra_fields (email etc)'),
])
->set('security.user.provider.chain', ChainUserProvider::class)
->abstract()
->set('security.http_utils', HttpUtils::class)
->args([
service('router')->nullOnInvalid(),
service('router')->nullOnInvalid(),
])
->alias(HttpUtils::class, 'security.http_utils')
// Validator
->set('security.validator.user_password', UserPasswordValidator::class)
->args([
service('security.token_storage'),
service('security.password_hasher_factory'),
])
->tag('validator.constraint_validator', ['alias' => 'security.validator.user_password'])
// Cache
->set('cache.security_expression_language')
->parent('cache.system')
->private()
->tag('cache.pool')
// Cache Warmers
->set('security.cache_warmer.expression', ExpressionCacheWarmer::class)
->args([
[],
service('security.expression_language'),
])
->tag('kernel.cache_warmer')
->set('controller.is_granted_attribute_listener', IsGrantedAttributeListener::class)
->args([
service('security.authorization_checker'),
service('security.is_granted_attribute_expression_language')->nullOnInvalid(),
])
->tag('kernel.event_subscriber')
->set('security.is_granted_attribute_expression_language', BaseExpressionLanguage::class)
->args([service('cache.security_is_granted_attribute_expression_language')->nullOnInvalid()])
->set('cache.security_is_granted_attribute_expression_language')
->parent('cache.system')
->tag('cache.pool')
;
};

View File

@@ -0,0 +1,167 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Bundle\SecurityBundle\Security\UserAuthenticator;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\Security\Http\Authentication\AuthenticatorManager;
use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\FormLoginAuthenticator;
use Symfony\Component\Security\Http\Authenticator\HttpBasicAuthenticator;
use Symfony\Component\Security\Http\Authenticator\JsonLoginAuthenticator;
use Symfony\Component\Security\Http\Authenticator\RemoteUserAuthenticator;
use Symfony\Component\Security\Http\Authenticator\X509Authenticator;
use Symfony\Component\Security\Http\Event\CheckPassportEvent;
use Symfony\Component\Security\Http\EventListener\CheckCredentialsListener;
use Symfony\Component\Security\Http\EventListener\LoginThrottlingListener;
use Symfony\Component\Security\Http\EventListener\PasswordMigratingListener;
use Symfony\Component\Security\Http\EventListener\SessionStrategyListener;
use Symfony\Component\Security\Http\EventListener\UserCheckerListener;
use Symfony\Component\Security\Http\EventListener\UserProviderListener;
use Symfony\Component\Security\Http\Firewall\AuthenticatorManagerListener;
return static function (ContainerConfigurator $container) {
$container->services()
// Manager
->set('security.authenticator.manager', AuthenticatorManager::class)
->abstract()
->args([
abstract_arg('authenticators'),
service('security.token_storage'),
service('event_dispatcher'),
abstract_arg('provider key'),
service('logger')->nullOnInvalid(),
param('security.authentication.manager.erase_credentials'),
param('security.authentication.hide_user_not_found'),
abstract_arg('required badges'),
])
->tag('monolog.logger', ['channel' => 'security'])
->set('security.authenticator.managers_locator', ServiceLocator::class)
->args([[]])
->set('security.user_authenticator', UserAuthenticator::class)
->args([
service('security.firewall.map'),
service('security.authenticator.managers_locator'),
service('request_stack'),
])
->alias(UserAuthenticatorInterface::class, 'security.user_authenticator')
->set('security.firewall.authenticator', AuthenticatorManagerListener::class)
->abstract()
->args([
abstract_arg('authenticator manager'),
])
// Listeners
->set('security.listener.check_authenticator_credentials', CheckCredentialsListener::class)
->args([
service('security.password_hasher_factory'),
])
->tag('kernel.event_subscriber')
->set('security.listener.user_provider', UserProviderListener::class)
->args([
service('security.user_providers'),
])
->tag('kernel.event_listener', ['event' => CheckPassportEvent::class, 'priority' => 1024, 'method' => 'checkPassport'])
->set('security.listener.user_provider.abstract', UserProviderListener::class)
->abstract()
->args([
abstract_arg('user provider'),
])
->set('security.listener.password_migrating', PasswordMigratingListener::class)
->args([
service('security.password_hasher_factory'),
])
->tag('kernel.event_subscriber')
->set('security.listener.user_checker', UserCheckerListener::class)
->abstract()
->args([
abstract_arg('user checker'),
])
->set('security.listener.session', SessionStrategyListener::class)
->abstract()
->args([
service('security.authentication.session_strategy'),
])
->set('security.listener.login_throttling', LoginThrottlingListener::class)
->abstract()
->args([
service('request_stack'),
abstract_arg('request rate limiter'),
])
// Authenticators
->set('security.authenticator.http_basic', HttpBasicAuthenticator::class)
->abstract()
->args([
abstract_arg('realm name'),
abstract_arg('user provider'),
service('logger')->nullOnInvalid(),
])
->tag('monolog.logger', ['channel' => 'security'])
->set('security.authenticator.form_login', FormLoginAuthenticator::class)
->abstract()
->args([
service('security.http_utils'),
abstract_arg('user provider'),
abstract_arg('authentication success handler'),
abstract_arg('authentication failure handler'),
abstract_arg('options'),
])
->set('security.authenticator.json_login', JsonLoginAuthenticator::class)
->abstract()
->args([
service('security.http_utils'),
abstract_arg('user provider'),
abstract_arg('authentication success handler'),
abstract_arg('authentication failure handler'),
abstract_arg('options'),
service('property_accessor')->nullOnInvalid(),
])
->call('setTranslator', [service('translator')->ignoreOnInvalid()])
->set('security.authenticator.x509', X509Authenticator::class)
->abstract()
->args([
abstract_arg('user provider'),
service('security.token_storage'),
abstract_arg('firewall name'),
abstract_arg('user key'),
abstract_arg('credentials key'),
service('logger')->nullOnInvalid(),
abstract_arg('credentials user identifier'),
])
->tag('monolog.logger', ['channel' => 'security'])
->set('security.authenticator.remote_user', RemoteUserAuthenticator::class)
->abstract()
->args([
abstract_arg('user provider'),
service('security.token_storage'),
abstract_arg('firewall name'),
abstract_arg('user key'),
service('logger')->nullOnInvalid(),
])
->tag('monolog.logger', ['channel' => 'security'])
;
};

View File

@@ -0,0 +1,104 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Jose\Component\Core\Algorithm;
use Jose\Component\Core\JWK;
use Jose\Component\Signature\Algorithm\ES256;
use Jose\Component\Signature\Algorithm\ES384;
use Jose\Component\Signature\Algorithm\ES512;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SignatureAlgorithmFactory;
use Symfony\Component\Security\Http\AccessToken\ChainAccessTokenExtractor;
use Symfony\Component\Security\Http\AccessToken\FormEncodedBodyExtractor;
use Symfony\Component\Security\Http\AccessToken\HeaderAccessTokenExtractor;
use Symfony\Component\Security\Http\AccessToken\Oidc\OidcTokenHandler;
use Symfony\Component\Security\Http\AccessToken\Oidc\OidcUserInfoTokenHandler;
use Symfony\Component\Security\Http\AccessToken\QueryAccessTokenExtractor;
use Symfony\Component\Security\Http\Authenticator\AccessTokenAuthenticator;
use Symfony\Contracts\HttpClient\HttpClientInterface;
return static function (ContainerConfigurator $container) {
$container->services()
->set('security.access_token_extractor.header', HeaderAccessTokenExtractor::class)
->set('security.access_token_extractor.query_string', QueryAccessTokenExtractor::class)
->set('security.access_token_extractor.request_body', FormEncodedBodyExtractor::class)
->set('security.authenticator.access_token', AccessTokenAuthenticator::class)
->abstract()
->args([
abstract_arg('access token handler'),
abstract_arg('access token extractor'),
null,
null,
null,
null,
])
->set('security.authenticator.access_token.chain_extractor', ChainAccessTokenExtractor::class)
->abstract()
->args([
abstract_arg('access token extractors'),
])
// OIDC
->set('security.access_token_handler.oidc_user_info.http_client', HttpClientInterface::class)
->abstract()
->factory([service('http_client'), 'withOptions'])
->args([abstract_arg('http client options')])
->set('security.access_token_handler.oidc_user_info', OidcUserInfoTokenHandler::class)
->abstract()
->args([
abstract_arg('http client'),
service('logger')->nullOnInvalid(),
abstract_arg('claim'),
])
->set('security.access_token_handler.oidc', OidcTokenHandler::class)
->abstract()
->args([
abstract_arg('signature algorithm'),
abstract_arg('signature key'),
abstract_arg('audience'),
abstract_arg('issuers'),
'sub',
service('logger')->nullOnInvalid(),
service('clock'),
])
->set('security.access_token_handler.oidc.jwk', JWK::class)
->abstract()
->factory([JWK::class, 'createFromJson'])
->args([
abstract_arg('signature key'),
])
->set('security.access_token_handler.oidc.signature', Algorithm::class)
->abstract()
->factory([SignatureAlgorithmFactory::class, 'create'])
->args([
abstract_arg('signature algorithm'),
])
->set('security.access_token_handler.oidc.signature.ES256', ES256::class)
->parent('security.access_token_handler.oidc.signature')
->args(['index_0' => 'ES256'])
->set('security.access_token_handler.oidc.signature.ES384', ES384::class)
->parent('security.access_token_handler.oidc.signature')
->args(['index_0' => 'ES384'])
->set('security.access_token_handler.oidc.signature.ES512', ES512::class)
->parent('security.access_token_handler.oidc.signature')
->args(['index_0' => 'ES512'])
;
};

View File

@@ -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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Bundle\SecurityBundle\LoginLink\FirewallAwareLoginLinkHandler;
use Symfony\Component\Security\Core\Signature\ExpiredSignatureStorage;
use Symfony\Component\Security\Core\Signature\SignatureHasher;
use Symfony\Component\Security\Http\Authenticator\LoginLinkAuthenticator;
use Symfony\Component\Security\Http\LoginLink\LoginLinkHandler;
use Symfony\Component\Security\Http\LoginLink\LoginLinkHandlerInterface;
return static function (ContainerConfigurator $container) {
$container->services()
->set('security.authenticator.login_link', LoginLinkAuthenticator::class)
->abstract()
->args([
abstract_arg('the login link handler instance'),
service('security.http_utils'),
abstract_arg('authentication success handler'),
abstract_arg('authentication failure handler'),
abstract_arg('options'),
])
->set('security.authenticator.abstract_login_link_handler', LoginLinkHandler::class)
->abstract()
->args([
service('router'),
abstract_arg('user provider'),
abstract_arg('signature hasher'),
abstract_arg('options'),
])
->set('security.authenticator.abstract_login_link_signature_hasher', SignatureHasher::class)
->args([
service('property_accessor'),
abstract_arg('signature properties'),
'%kernel.secret%',
abstract_arg('expired signature storage'),
abstract_arg('max signature uses'),
])
->set('security.authenticator.expired_login_link_storage', ExpiredSignatureStorage::class)
->abstract()
->args([
abstract_arg('cache pool service'),
abstract_arg('expired login link storage'),
])
->set('security.authenticator.cache.expired_links')
->parent('cache.app')
->private()
->set('security.authenticator.firewall_aware_login_link_handler', FirewallAwareLoginLinkHandler::class)
->args([
service('security.firewall.map'),
tagged_locator('security.authenticator.login_linker', 'firewall'),
service('request_stack'),
])
->alias(LoginLinkHandlerInterface::class, 'security.authenticator.firewall_aware_login_link_handler')
;
};

View File

@@ -0,0 +1,101 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Bundle\SecurityBundle\RememberMe\FirewallAwareRememberMeHandler;
use Symfony\Component\Security\Core\Signature\SignatureHasher;
use Symfony\Component\Security\Http\Authenticator\RememberMeAuthenticator;
use Symfony\Component\Security\Http\EventListener\CheckRememberMeConditionsListener;
use Symfony\Component\Security\Http\EventListener\RememberMeListener;
use Symfony\Component\Security\Http\RememberMe\PersistentRememberMeHandler;
use Symfony\Component\Security\Http\RememberMe\RememberMeHandlerInterface;
use Symfony\Component\Security\Http\RememberMe\ResponseListener;
use Symfony\Component\Security\Http\RememberMe\SignatureRememberMeHandler;
return static function (ContainerConfigurator $container) {
$container->services()
->set('security.rememberme.response_listener', ResponseListener::class)
->tag('kernel.event_subscriber')
->set('security.authenticator.remember_me_signature_hasher', SignatureHasher::class)
->args([
service('property_accessor'),
abstract_arg('signature properties'),
'%kernel.secret%',
null,
null,
])
->set('security.authenticator.signature_remember_me_handler', SignatureRememberMeHandler::class)
->abstract()
->args([
abstract_arg('signature hasher'),
abstract_arg('user provider'),
service('request_stack'),
abstract_arg('options'),
service('logger')->nullOnInvalid(),
])
->tag('monolog.logger', ['channel' => 'security'])
->set('security.authenticator.persistent_remember_me_handler', PersistentRememberMeHandler::class)
->abstract()
->args([
abstract_arg('token provider'),
abstract_arg('user provider'),
service('request_stack'),
abstract_arg('options'),
service('logger')->nullOnInvalid(),
abstract_arg('token verifier'),
])
->tag('monolog.logger', ['channel' => 'security'])
->set('security.authenticator.firewall_aware_remember_me_handler', FirewallAwareRememberMeHandler::class)
->args([
service('security.firewall.map'),
tagged_locator('security.remember_me_handler', 'firewall'),
service('request_stack'),
])
->alias(RememberMeHandlerInterface::class, 'security.authenticator.firewall_aware_remember_me_handler')
->set('security.listener.check_remember_me_conditions', CheckRememberMeConditionsListener::class)
->abstract()
->args([
abstract_arg('options'),
service('logger')->nullOnInvalid(),
])
->set('security.listener.remember_me', RememberMeListener::class)
->abstract()
->args([
abstract_arg('remember me handler'),
service('logger')->nullOnInvalid(),
])
->tag('monolog.logger', ['channel' => 'security'])
->set('security.authenticator.remember_me', RememberMeAuthenticator::class)
->abstract()
->args([
abstract_arg('remember me handler'),
param('kernel.secret'),
service('security.token_storage'),
abstract_arg('options'),
service('logger')->nullOnInvalid(),
])
->tag('monolog.logger', ['channel' => 'security'])
// Cache
->set('cache.security_token_verifier')
->parent('cache.system')
->private()
->tag('cache.pool')
;
};

View File

@@ -0,0 +1,42 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener;
use Symfony\Bundle\SecurityBundle\EventListener\VoteListener;
use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager;
return static function (ContainerConfigurator $container) {
$container->services()
->set('debug.security.access.decision_manager', TraceableAccessDecisionManager::class)
->decorate('security.access.decision_manager')
->args([
service('debug.security.access.decision_manager.inner'),
])
->set('debug.security.voter.vote_listener', VoteListener::class)
->args([
service('debug.security.access.decision_manager'),
])
->tag('kernel.event_subscriber')
->set('debug.security.firewall', TraceableFirewallListener::class)
->args([
service('security.firewall.map'),
service('event_dispatcher'),
service('security.logout_url_generator'),
])
->tag('kernel.event_subscriber')
->tag('kernel.reset', ['method' => 'reset'])
->alias('security.firewall', 'debug.security.firewall')
;
};

View File

@@ -0,0 +1,174 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\Security\Http\AccessMap;
use Symfony\Component\Security\Http\Authentication\CustomAuthenticationFailureHandler;
use Symfony\Component\Security\Http\Authentication\CustomAuthenticationSuccessHandler;
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler;
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler;
use Symfony\Component\Security\Http\EventListener\ClearSiteDataLogoutListener;
use Symfony\Component\Security\Http\EventListener\CookieClearingLogoutListener;
use Symfony\Component\Security\Http\EventListener\DefaultLogoutListener;
use Symfony\Component\Security\Http\EventListener\SessionLogoutListener;
use Symfony\Component\Security\Http\Firewall\AccessListener;
use Symfony\Component\Security\Http\Firewall\ChannelListener;
use Symfony\Component\Security\Http\Firewall\ContextListener;
use Symfony\Component\Security\Http\Firewall\ExceptionListener;
use Symfony\Component\Security\Http\Firewall\LogoutListener;
use Symfony\Component\Security\Http\Firewall\SwitchUserListener;
return static function (ContainerConfigurator $container) {
$container->services()
->set('security.channel_listener', ChannelListener::class)
->args([
service('security.access_map'),
service('logger')->nullOnInvalid(),
inline_service('int')->factory([service('router.request_context'), 'getHttpPort']),
inline_service('int')->factory([service('router.request_context'), 'getHttpsPort']),
])
->tag('monolog.logger', ['channel' => 'security'])
->set('security.access_map', AccessMap::class)
->set('security.context_listener', ContextListener::class)
->args([
service('security.untracked_token_storage'),
[],
abstract_arg('Provider Key'),
service('logger')->nullOnInvalid(),
service('event_dispatcher')->nullOnInvalid(),
service('security.authentication.trust_resolver'),
])
->tag('monolog.logger', ['channel' => 'security'])
->set('security.logout_listener', LogoutListener::class)
->abstract()
->args([
service('security.token_storage'),
service('security.http_utils'),
abstract_arg('event dispatcher'),
[], // Options
])
->set('security.logout.listener.session', SessionLogoutListener::class)
->abstract()
->set('security.logout.listener.clear_site_data', ClearSiteDataLogoutListener::class)
->abstract()
->set('security.logout.listener.cookie_clearing', CookieClearingLogoutListener::class)
->abstract()
->set('security.logout.listener.default', DefaultLogoutListener::class)
->abstract()
->args([
service('security.http_utils'),
abstract_arg('target url'),
])
->set('security.authentication.listener.abstract')
->abstract()
->args([
service('security.token_storage'),
service('security.authentication.manager'),
service('security.authentication.session_strategy'),
service('security.http_utils'),
abstract_arg('Provider-shared Key'),
service('security.authentication.success_handler'),
service('security.authentication.failure_handler'),
[],
service('logger')->nullOnInvalid(),
service('event_dispatcher')->nullOnInvalid(),
])
->tag('monolog.logger', ['channel' => 'security'])
->set('security.authentication.custom_success_handler', CustomAuthenticationSuccessHandler::class)
->abstract()
->args([
abstract_arg('The custom success handler service'),
[], // Options
abstract_arg('Provider-shared Key'),
])
->set('security.authentication.success_handler', DefaultAuthenticationSuccessHandler::class)
->abstract()
->args([
service('security.http_utils'),
[], // Options
service('logger')->nullOnInvalid(),
])
->set('security.authentication.custom_failure_handler', CustomAuthenticationFailureHandler::class)
->abstract()
->args([
abstract_arg('The custom failure handler service'),
[], // Options
])
->set('security.authentication.failure_handler', DefaultAuthenticationFailureHandler::class)
->abstract()
->args([
service('http_kernel'),
service('security.http_utils'),
[], // Options
service('logger')->nullOnInvalid(),
])
->tag('monolog.logger', ['channel' => 'security'])
->set('security.exception_listener', ExceptionListener::class)
->abstract()
->args([
service('security.token_storage'),
service('security.authentication.trust_resolver'),
service('security.http_utils'),
abstract_arg('Provider-shared Key'),
service('security.authentication.entry_point')->nullOnInvalid(),
param('security.access.denied_url'),
service('security.access.denied_handler')->nullOnInvalid(),
service('logger')->nullOnInvalid(),
false, // Stateless
])
->tag('monolog.logger', ['channel' => 'security'])
->set('security.authentication.switchuser_listener', SwitchUserListener::class)
->abstract()
->args([
service('security.token_storage'),
abstract_arg('User Provider'),
abstract_arg('User Checker'),
abstract_arg('Provider Key'),
service('security.access.decision_manager'),
service('logger')->nullOnInvalid(),
'_switch_user',
'ROLE_ALLOWED_TO_SWITCH',
service('event_dispatcher')->nullOnInvalid(),
false, // Stateless
service('router')->nullOnInvalid(),
abstract_arg('Target Route'),
])
->tag('monolog.logger', ['channel' => 'security'])
->set('security.access_listener', AccessListener::class)
->args([
service('security.token_storage'),
service('security.access.decision_manager'),
service('security.access_map'),
])
->tag('monolog.logger', ['channel' => 'security'])
->set('security.firewall.event_dispatcher_locator', ServiceLocator::class)
->args([[]])
;
};

View File

@@ -0,0 +1,32 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Bridge\Twig\Extension\LogoutUrlExtension;
use Symfony\Bridge\Twig\Extension\SecurityExtension;
return static function (ContainerConfigurator $container) {
$container->services()
->set('twig.extension.logout_url', LogoutUrlExtension::class)
->args([
service('security.logout_url_generator'),
])
->tag('twig.extension')
->set('twig.extension.security', SecurityExtension::class)
->args([
service('security.authorization_checker')->ignoreOnInvalid(),
service('security.impersonate_url_generator')->ignoreOnInvalid(),
])
->tag('twig.extension')
;
};

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-user" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" role="img">
<title>Security</title>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<circle cx="12" cy="7" r="4"></circle>
<path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2"></path>
</svg>

After

Width:  |  Height:  |  Size: 434 B

View File

@@ -0,0 +1,506 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% block page_title 'Security' %}
{% block head %}
{{ parent() }}
<style>
#collector-content .decision-log .voter_result td {
border-top-width: 1px;
border-bottom-width: 0;
padding-bottom: 0;
}
#collector-content .decision-log .voter_details td {
border-top-width: 0;
border-bottom-width: 1px;
padding-bottom: 0;
}
#collector-content .decision-log .voter_details table {
border: 0;
margin: 0;
box-shadow: unset;
}
#collector-content .decision-log .voter_details table td {
border: 0;
padding: 0 0 8px 0;
}
#collector-content .authenticators .badge {
color: var(--white);
display: inline-block;
text-align: center;
}
#collector-content .authenticators .badge.badge-resolved {
background-color: var(--green-500);
}
#collector-content .authenticators .badge.badge-not_resolved {
background-color: var(--yellow-500);
}
#collector-content .authenticators svg[data-icon-name="icon-tabler-check"] {
color: var(--green-500);
}
#collector-content .authenticators svg[data-icon-name="icon-tabler-x"] {
color: var(--red-500);
}
</style>
{% endblock %}
{% block toolbar %}
{% if collector.firewall %}
{% set icon %}
{{ source('@Security/Collector/icon.svg') }}
<span class="sf-toolbar-value">{{ collector.user|default('n/a') }}</span>
{% endset %}
{% set text %}
{% if collector.impersonated %}
<div class="sf-toolbar-info-group">
<div class="sf-toolbar-info-piece">
<b>Impersonator</b>
<span>{{ collector.impersonatorUser }}</span>
</div>
</div>
{% endif %}
<div class="sf-toolbar-info-group">
{% if collector.enabled %}
{% if collector.token %}
<div class="sf-toolbar-info-piece">
<b>Logged in as</b>
<span>{{ collector.user }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Authenticated</b>
<span class="sf-toolbar-status sf-toolbar-status-{{ collector.authenticated ? 'green' : 'yellow' }}">{{ collector.authenticated ? 'Yes' : 'No' }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Roles</b>
<span>
{% set remainingRoles = collector.roles|slice(1) %}
{{ collector.roles|first }}
{% if remainingRoles is not empty %}
+
<abbr title="{{ remainingRoles|join(', ') }}">
{{ remainingRoles|length }} more
</abbr>
{% endif %}
</span>
</div>
{% if collector.supportsRoleHierarchy %}
<div class="sf-toolbar-info-piece">
<b>Inherited Roles</b>
<span>
{% if collector.inheritedRoles is empty %}
none
{% else %}
{% set remainingRoles = collector.inheritedRoles|slice(1) %}
{{ collector.inheritedRoles|first }}
{% if remainingRoles is not empty %}
+
<abbr title="{{ remainingRoles|join(', ') }}">
{{ remainingRoles|length }} more
</abbr>
{% endif %}
{% endif %}
</span>
</div>
{% endif %}
<div class="sf-toolbar-info-piece">
<b>Token class</b>
<span>{{ collector.tokenClass|abbr_class }}</span>
</div>
{% else %}
<div class="sf-toolbar-info-piece">
<b>Authenticated</b>
<span class="sf-toolbar-status sf-toolbar-status-yellow">No</span>
</div>
{% endif %}
{% if collector.firewall %}
<div class="sf-toolbar-info-piece">
<b>Firewall name</b>
<span>{{ collector.firewall.name }}</span>
</div>
{% endif %}
{% if collector.token and collector.logoutUrl %}
<div class="sf-toolbar-info-piece">
<b>Actions</b>
<span>
<a href="{{ collector.logoutUrl }}">Logout</a>
{% if collector.impersonated and collector.impersonationExitPath %}
| <a href="{{ collector.impersonationExitPath }}">Exit impersonation</a>
{% endif %}
</span>
</div>
{% endif %}
{% else %}
<div class="sf-toolbar-info-piece">
<span>The security is disabled.</span>
</div>
{% endif %}
</div>
{% endset %}
{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url }) }}
{% endif %}
{% endblock %}
{% block menu %}
<span class="label {{ not collector.firewall or not collector.token ? 'disabled' }}">
<span class="icon">{{ source('@Security/Collector/icon.svg') }}</span>
<strong>Security</strong>
</span>
{% endblock %}
{% block panel %}
<h2>Security</h2>
{% if collector.enabled %}
<div class="sf-tabs">
<div class="tab {{ collector.token is empty ? 'disabled' }}">
<h3 class="tab-title">Token</h3>
<div class="tab-content">
{% if collector.token %}
<div class="metrics">
<div class="metric">
<span class="value">{{ collector.user }}</span>
<span class="label">Username</span>
</div>
<div class="metric">
<span class="value">{{ source('@WebProfiler/Icon/' ~ (collector.authenticated ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="label">Authenticated</span>
</div>
</div>
<table>
<thead>
<tr>
<th scope="col" class="key">Property</th>
<th scope="col">Value</th>
</tr>
</thead>
<tbody>
<tr>
<th>Roles</th>
<td>
{{ collector.roles is empty ? 'none' : profiler_dump(collector.roles, maxDepth=1) }}
{% if not collector.authenticated and collector.roles is empty %}
<p class="help">User is not authenticated probably because they have no roles.</p>
{% endif %}
</td>
</tr>
{% if collector.supportsRoleHierarchy %}
<tr>
<th>Inherited Roles</th>
<td>{{ collector.inheritedRoles is empty ? 'none' : profiler_dump(collector.inheritedRoles, maxDepth=1) }}</td>
</tr>
{% endif %}
{% if collector.token %}
<tr>
<th>Token</th>
<td>{{ profiler_dump(collector.token) }}</td>
</tr>
{% endif %}
</tbody>
</table>
{% elseif collector.enabled %}
<div class="empty">
<p>There is no security token.</p>
</div>
{% endif %}
</div>
</div>
<div class="tab {{ (not collector.firewall or collector.firewall.security_enabled is empty) ? 'disabled' }}">
<h3 class="tab-title">Firewall</h3>
<div class="tab-content">
{% if collector.firewall %}
<div class="metrics">
<div class="metric">
<span class="value">{{ collector.firewall.name }}</span>
<span class="label">Name</span>
</div>
<div class="metric">
<span class="value">{{ source('@WebProfiler/Icon/' ~ (collector.firewall.security_enabled ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="label">Security enabled</span>
</div>
<div class="metric">
<span class="value">{{ source('@WebProfiler/Icon/' ~ (collector.firewall.stateless ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="label">Stateless</span>
</div>
</div>
{% if collector.firewall.security_enabled %}
<h4>Configuration</h4>
<table>
<thead>
<tr>
<th scope="col" class="key">Key</th>
<th scope="col">Value</th>
</tr>
</thead>
<tbody>
<tr>
<th>provider</th>
<td>{{ collector.firewall.provider ?: '(none)' }}</td>
</tr>
<tr>
<th>context</th>
<td>{{ collector.firewall.context ?: '(none)' }}</td>
</tr>
<tr>
<th>entry_point</th>
<td>{{ collector.firewall.entry_point ?: '(none)' }}</td>
</tr>
<tr>
<th>user_checker</th>
<td>{{ collector.firewall.user_checker ?: '(none)' }}</td>
</tr>
<tr>
<th>access_denied_handler</th>
<td>{{ collector.firewall.access_denied_handler ?: '(none)' }}</td>
</tr>
<tr>
<th>access_denied_url</th>
<td>{{ collector.firewall.access_denied_url ?: '(none)' }}</td>
</tr>
<tr>
<th>authenticators</th>
<td>{{ collector.firewall.authenticators is empty ? '(none)' : profiler_dump(collector.firewall.authenticators, maxDepth=1) }}</td>
</tr>
</tbody>
</table>
{% endif %}
{% endif %}
</div>
</div>
<div class="tab {{ collector.listeners|default([]) is empty ? 'disabled' }}">
<h3 class="tab-title">Listeners</h3>
<div class="tab-content">
{% if collector.listeners|default([]) is empty %}
<div class="empty">
<p>No security listeners have been recorded. Check that debugging is enabled in the kernel.</p>
</div>
{% else %}
<table>
<thead>
<tr>
<th>Listener</th>
<th>Duration</th>
<th>Response</th>
</tr>
</thead>
{% set previous_event = (collector.listeners|first) %}
{% for listener in collector.listeners %}
{% if loop.first or listener != previous_event %}
{% if not loop.first %}
</tbody>
{% endif %}
<tbody>
{% set previous_event = listener %}
{% endif %}
<tr>
<td class="font-normal">{{ profiler_dump(listener.stub) }}</td>
<td class="no-wrap">{{ '%0.2f'|format(listener.time * 1000) }} ms</td>
<td class="font-normal">{{ listener.response ? profiler_dump(listener.response) : '(none)' }}</td>
</tr>
{% if loop.last %}
</tbody>
{% endif %}
{% endfor %}
</table>
{% endif %}
</div>
</div>
<div class="tab {{ collector.authenticators|default([]) is empty ? 'disabled' }}">
<h3 class="tab-title">Authenticators</h3>
<div class="tab-content">
{% if collector.authenticators|default([]) is not empty %}
<table class="authenticators">
<thead>
<tr>
<th>Authenticator</th>
<th>Supports</th>
<th>Authenticated</th>
<th>Duration</th>
<th>Passport</th>
<th>Badges</th>
</tr>
</thead>
{% set previous_event = (collector.listeners|first) %}
{% for authenticator in collector.authenticators %}
{% if loop.first or authenticator != previous_event %}
{% if not loop.first %}
</tbody>
{% endif %}
<tbody>
{% set previous_event = authenticator %}
{% endif %}
<tr>
<td class="font-normal">{{ profiler_dump(authenticator.stub) }}</td>
<td class="no-wrap">{{ source('@WebProfiler/Icon/' ~ (authenticator.supports ? 'yes' : 'no') ~ '.svg') }}</td>
<td class="no-wrap">{{ authenticator.authenticated is not null ? source('@WebProfiler/Icon/' ~ (authenticator.authenticated ? 'yes' : 'no') ~ '.svg') : '' }}</td>
<td class="no-wrap">{{ '%0.2f'|format(authenticator.duration * 1000) }} ms</td>
<td class="font-normal">{{ authenticator.passport ? profiler_dump(authenticator.passport) : '(none)' }}</td>
<td class="font-normal">
{% for badge in authenticator.badges ?? [] %}
<span class="badge badge-{{ badge.resolved ? 'resolved' : 'not_resolved' }}">
{{ badge.stub|abbr_class }}
</span>
{% else %}
(none)
{% endfor %}
</td>
</tr>
{% if loop.last %}
</tbody>
{% endif %}
{% endfor %}
</table>
{% else %}
<div class="empty">
<p>No authenticators have been recorded. Check previous profiles on your authentication endpoint.</p>
</div>
{% endif %}
</div>
</div>
<div class="tab {{ collector.accessDecisionLog|default([]) is empty ? 'disabled' }}">
<h3 class="tab-title">Access Decision</h3>
<div class="tab-content">
{% if collector.voters|default([]) is not empty %}
<div class="metrics">
<div class="metric">
<span class="value">{{ collector.voterStrategy|default('unknown') }}</span>
<span class="label">Strategy</span>
</div>
</div>
<table class="voters">
<thead>
<tr>
<th>#</th>
<th>Voter class</th>
</tr>
</thead>
<tbody>
{% for voter in collector.voters %}
<tr>
<td class="font-normal text-small text-muted nowrap">{{ loop.index }}</td>
<td class="font-normal">{{ profiler_dump(voter) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if collector.accessDecisionLog|default([]) is not empty %}
<h2>Access decision log</h2>
<table class="decision-log">
<col style="width: 30px">
<col style="width: 120px">
<col style="width: 25%">
<col style="width: 60%">
<thead>
<tr>
<th>#</th>
<th>Result</th>
<th>Attributes</th>
<th>Object</th>
</tr>
</thead>
<tbody>
{% for decision in collector.accessDecisionLog %}
<tr class="voter_result">
<td class="font-normal text-small text-muted nowrap">{{ loop.index }}</td>
<td class="font-normal">
{{ decision.result
? '<span class="label status-success same-width">GRANTED</span>'
: '<span class="label status-error same-width">DENIED</span>'
}}
</td>
<td>
{% if decision.attributes|length == 1 %}
{% set attribute = decision.attributes|first %}
{% if attribute.expression is defined %}
Expression: <pre><code>{{ attribute.expression }}</code></pre>
{% elseif attribute.type == 'string' %}
{{ attribute }}
{% else %}
{{ profiler_dump(attribute) }}
{% endif %}
{% else %}
{{ profiler_dump(decision.attributes) }}
{% endif %}
</td>
<td>{{ profiler_dump(decision.seek('object')) }}</td>
</tr>
<tr class="voter_details">
<td></td>
<td colspan="3">
{% if decision.voter_details is not empty %}
{% set voter_details_id = 'voter-details-' ~ loop.index %}
<div id="{{ voter_details_id }}" class="sf-toggle-content sf-toggle-hidden">
<table>
<tbody>
{% for voter_detail in decision.voter_details %}
<tr>
<td class="font-normal">{{ profiler_dump(voter_detail['class']) }}</td>
{% if collector.voterStrategy == 'unanimous' %}
<td class="font-normal text-small">attribute {{ voter_detail['attributes'][0] }}</td>
{% endif %}
<td class="font-normal text-small">
{% if voter_detail['vote'] == constant('Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::ACCESS_GRANTED') %}
ACCESS GRANTED
{% elseif voter_detail['vote'] == constant('Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::ACCESS_ABSTAIN') %}
ACCESS ABSTAIN
{% elseif voter_detail['vote'] == constant('Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::ACCESS_DENIED') %}
ACCESS DENIED
{% else %}
unknown ({{ voter_detail['vote'] }})
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<a class="btn btn-link text-small sf-toggle" data-toggle-selector="#{{ voter_details_id }}" data-toggle-alt-content="Hide voter details">Show voter details</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
</div>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,49 @@
<?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\Routing;
use Symfony\Component\DependencyInjection\Config\ContainerParametersResource;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
final class LogoutRouteLoader
{
/**
* @param array<string, string> $logoutUris Logout URIs indexed by the corresponding firewall name
* @param string $parameterName Name of the container parameter containing {@see $logoutUris} value
*/
public function __construct(
private readonly array $logoutUris,
private readonly string $parameterName,
) {
}
public function __invoke(): RouteCollection
{
$collection = new RouteCollection();
$collection->addResource(new ContainerParametersResource([$this->parameterName => $this->logoutUris]));
$routeNames = [];
foreach ($this->logoutUris as $firewallName => $logoutPath) {
$routeName = '_logout_'.$firewallName;
if (isset($routeNames[$logoutPath])) {
$collection->addAlias($routeName, $routeNames[$logoutPath]);
} else {
$routeNames[$logoutPath] = $routeName;
$collection->add($routeName, new Route($logoutPath));
}
}
return $collection;
}
}

View File

@@ -0,0 +1,214 @@
<?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;
use Psr\Container\ContainerInterface;
use Symfony\Bundle\SecurityBundle\Security\FirewallConfig;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Exception\LogicException;
use Symfony\Component\Security\Core\Exception\LogoutException;
use Symfony\Component\Security\Core\Security as LegacySecurity;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface;
use Symfony\Component\Security\Http\Event\LogoutEvent;
use Symfony\Component\Security\Http\ParameterBagUtils;
use Symfony\Component\Security\Http\SecurityRequestAttributes;
use Symfony\Contracts\Service\ServiceProviderInterface;
if (class_exists(InternalSecurity::class, false)) {
return;
}
if (class_exists(LegacySecurity::class)) {
class_alias(LegacySecurity::class, InternalSecurity::class);
} else {
/**
* @internal
*/
class InternalSecurity
{
}
}
/**
* Helper class for commonly-needed security tasks.
*
* @author Ryan Weaver <ryan@symfonycasts.com>
* @author Robin Chalas <robin.chalas@gmail.com>
* @author Arnaud Frézet <arnaud@larriereguichet.fr>
*
* @final
*/
class Security extends InternalSecurity implements AuthorizationCheckerInterface
{
/**
* @deprecated since Symfony 6.4, use SecurityRequestAttributes::ACCESS_DENIED_ERROR instead
*/
public const ACCESS_DENIED_ERROR = SecurityRequestAttributes::ACCESS_DENIED_ERROR;
/**
* @deprecated since Symfony 6.4, use SecurityRequestAttributes::AUTHENTICATION_ERROR instead
*/
public const AUTHENTICATION_ERROR = SecurityRequestAttributes::AUTHENTICATION_ERROR;
/**
* @deprecated since Symfony 6.4, use SecurityRequestAttributes::LAST_USERNAME instead
*/
public const LAST_USERNAME = SecurityRequestAttributes::LAST_USERNAME;
public function __construct(
private readonly ContainerInterface $container,
private readonly array $authenticators = [],
) {
}
public function getUser(): ?UserInterface
{
if (!$token = $this->getToken()) {
return null;
}
return $token->getUser();
}
/**
* Checks if the attributes are granted against the current authentication token and optionally supplied subject.
*/
public function isGranted(mixed $attributes, mixed $subject = null): bool
{
return $this->container->get('security.authorization_checker')
->isGranted($attributes, $subject);
}
public function getToken(): ?TokenInterface
{
return $this->container->get('security.token_storage')->getToken();
}
public function getFirewallConfig(Request $request): ?FirewallConfig
{
return $this->container->get('security.firewall.map')->getFirewallConfig($request);
}
/**
* @param UserInterface $user The user to authenticate
* @param string|null $authenticatorName The authenticator name (e.g. "form_login") or service id (e.g. SomeApiKeyAuthenticator::class) - required only if multiple authenticators are configured
* @param string|null $firewallName The firewall name - required only if multiple firewalls are configured
* @param BadgeInterface[] $badges Badges to add to the user's passport
*
* @return Response|null The authenticator success response if any
*/
public function login(UserInterface $user, ?string $authenticatorName = null, ?string $firewallName = null, array $badges = []): ?Response
{
$request = $this->container->get('request_stack')->getCurrentRequest();
if (null === $request) {
throw new LogicException('Unable to login without a request context.');
}
$firewallName ??= $this->getFirewallConfig($request)?->getName();
if (!$firewallName) {
throw new LogicException('Unable to login as the current route is not covered by any firewall.');
}
$authenticator = $this->getAuthenticator($authenticatorName, $firewallName);
$userCheckerLocator = $this->container->get('security.user_checker_locator');
$userCheckerLocator->get($firewallName)->checkPreAuth($user);
return $this->container->get('security.authenticator.managers_locator')->get($firewallName)->authenticateUser($user, $authenticator, $request, $badges);
}
/**
* Logout the current user by dispatching the LogoutEvent.
*
* @param bool $validateCsrfToken Whether to look for a valid CSRF token based on the `logout` listener configuration
*
* @return Response|null The LogoutEvent's Response if any
*
* @throws LogoutException When $validateCsrfToken is true and the CSRF token is not found or invalid
*/
public function logout(bool $validateCsrfToken = true): ?Response
{
$request = $this->container->get('request_stack')->getMainRequest();
if (null === $request) {
throw new LogicException('Unable to logout without a request context.');
}
/** @var TokenStorageInterface $tokenStorage */
$tokenStorage = $this->container->get('security.token_storage');
if (!($token = $tokenStorage->getToken()) || !$token->getUser()) {
throw new LogicException('Unable to logout as there is no logged-in user.');
}
if (!$firewallConfig = $this->container->get('security.firewall.map')->getFirewallConfig($request)) {
throw new LogicException('Unable to logout as the request is not behind a firewall.');
}
if ($validateCsrfToken) {
if (!$this->container->has('security.csrf.token_manager') || !$logoutConfig = $firewallConfig->getLogout()) {
throw new LogicException(\sprintf('Unable to logout with CSRF token validation. Either make sure that CSRF protection is enabled and "logout" is configured on the "%s" firewall, or bypass CSRF token validation explicitly by passing false to the $validateCsrfToken argument of this method.', $firewallConfig->getName()));
}
$csrfToken = ParameterBagUtils::getRequestParameterValue($request, $logoutConfig['csrf_parameter']);
if (!\is_string($csrfToken) || !$this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($logoutConfig['csrf_token_id'], $csrfToken))) {
throw new LogoutException('Invalid CSRF token.');
}
}
$logoutEvent = new LogoutEvent($request, $token);
$this->container->get('security.firewall.event_dispatcher_locator')->get($firewallConfig->getName())->dispatch($logoutEvent);
$tokenStorage->setToken(null);
return $logoutEvent->getResponse();
}
private function getAuthenticator(?string $authenticatorName, string $firewallName): AuthenticatorInterface
{
if (!isset($this->authenticators[$firewallName])) {
throw new LogicException(\sprintf('No authenticators found for firewall "%s".', $firewallName));
}
/** @var ServiceProviderInterface $firewallAuthenticatorLocator */
$firewallAuthenticatorLocator = $this->authenticators[$firewallName];
if (!$authenticatorName) {
$authenticatorIds = array_filter(array_keys($firewallAuthenticatorLocator->getProvidedServices()), fn (string $authenticatorId) => $authenticatorId !== \sprintf('security.authenticator.remember_me.%s', $firewallName));
if (!$authenticatorIds) {
throw new LogicException(\sprintf('No authenticator was found for the firewall "%s".', $firewallName));
}
if (1 < \count($authenticatorIds)) {
throw new LogicException(\sprintf('Too many authenticators were found for the current firewall "%s". You must provide an instance of "%s" to login programmatically. The available authenticators for the firewall "%s" are "%s".', $firewallName, AuthenticatorInterface::class, $firewallName, implode('" ,"', $authenticatorIds)));
}
return $firewallAuthenticatorLocator->get($authenticatorIds[0]);
}
if ($firewallAuthenticatorLocator->has($authenticatorName)) {
return $firewallAuthenticatorLocator->get($authenticatorName);
}
$authenticatorId = 'security.authenticator.'.$authenticatorName.'.'.$firewallName;
if (!$firewallAuthenticatorLocator->has($authenticatorId)) {
throw new LogicException(\sprintf('Unable to find an authenticator named "%s" for the firewall "%s". Available authenticators: "%s".', $authenticatorName, $firewallName, implode('", "', array_keys($firewallAuthenticatorLocator->getProvidedServices()))));
}
return $firewallAuthenticatorLocator->get($authenticatorId);
}
}

View File

@@ -0,0 +1,55 @@
<?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\Security;
use Psr\Container\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Provides basic functionality for services mapped by the firewall name
* in a container locator.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*
* @internal
*/
trait FirewallAwareTrait
{
private ContainerInterface $locator;
private RequestStack $requestStack;
private FirewallMap $firewallMap;
private function getForFirewall(): object
{
$serviceIdentifier = str_replace('FirewallAware', '', static::class);
if (null === $request = $this->requestStack->getCurrentRequest()) {
throw new \LogicException('Cannot determine the correct '.$serviceIdentifier.' to use: there is no active Request and so, the firewall cannot be determined. Try using a specific '.$serviceIdentifier.' service.');
}
$firewall = $this->firewallMap->getFirewallConfig($request);
if (!$firewall) {
throw new \LogicException('No '.$serviceIdentifier.' found as the current route is not covered by a firewall.');
}
$firewallName = $firewall->getName();
if (!$this->locator->has($firewallName)) {
$message = 'No '.$serviceIdentifier.' found for this firewall.';
if (\defined(static::class.'::FIREWALL_OPTION')) {
$message .= \sprintf(' Did you forget to add a "'.static::FIREWALL_OPTION.'" key under your "%s" firewall?', $firewallName);
}
throw new \LogicException($message);
}
return $this->locator->get($firewallName);
}
}

View File

@@ -0,0 +1,107 @@
<?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\Security;
/**
* @author Robin Chalas <robin.chalas@gmail.com>
*/
final class FirewallConfig
{
public function __construct(
private readonly string $name,
private readonly string $userChecker,
private readonly ?string $requestMatcher = null,
private readonly bool $securityEnabled = true,
private readonly bool $stateless = false,
private readonly ?string $provider = null,
private readonly ?string $context = null,
private readonly ?string $entryPoint = null,
private readonly ?string $accessDeniedHandler = null,
private readonly ?string $accessDeniedUrl = null,
private readonly array $authenticators = [],
private readonly ?array $switchUser = null,
private readonly ?array $logout = null,
) {
}
public function getName(): string
{
return $this->name;
}
/**
* @return string|null The request matcher service id or null if neither the request matcher, pattern or host
* options were provided
*/
public function getRequestMatcher(): ?string
{
return $this->requestMatcher;
}
public function isSecurityEnabled(): bool
{
return $this->securityEnabled;
}
public function isStateless(): bool
{
return $this->stateless;
}
public function getProvider(): ?string
{
return $this->provider;
}
/**
* @return string|null The context key (will be null if the firewall is stateless)
*/
public function getContext(): ?string
{
return $this->context;
}
public function getEntryPoint(): ?string
{
return $this->entryPoint;
}
public function getUserChecker(): string
{
return $this->userChecker;
}
public function getAccessDeniedHandler(): ?string
{
return $this->accessDeniedHandler;
}
public function getAccessDeniedUrl(): ?string
{
return $this->accessDeniedUrl;
}
public function getAuthenticators(): array
{
return $this->authenticators;
}
public function getSwitchUser(): ?array
{
return $this->switchUser;
}
public function getLogout(): ?array
{
return $this->logout;
}
}

View File

@@ -0,0 +1,73 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Http\Firewall\ExceptionListener;
use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface;
use Symfony\Component\Security\Http\Firewall\LogoutListener;
/**
* This is a wrapper around the actual firewall configuration which allows us
* to lazy load the context for one specific firewall only when we need it.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class FirewallContext
{
private iterable $listeners;
private ?ExceptionListener $exceptionListener;
private ?LogoutListener $logoutListener;
private ?FirewallConfig $config;
/**
* @param iterable<mixed, callable|FirewallListenerInterface> $listeners
*/
public function __construct(iterable $listeners, ?ExceptionListener $exceptionListener = null, ?LogoutListener $logoutListener = null, ?FirewallConfig $config = null)
{
$this->listeners = $listeners;
$this->exceptionListener = $exceptionListener;
$this->logoutListener = $logoutListener;
$this->config = $config;
}
/**
* @return FirewallConfig|null
*/
public function getConfig()
{
return $this->config;
}
/**
* @return iterable<mixed, callable|FirewallListenerInterface>
*/
public function getListeners(): iterable
{
return $this->listeners;
}
/**
* @return ExceptionListener|null
*/
public function getExceptionListener()
{
return $this->exceptionListener;
}
/**
* @return LogoutListener|null
*/
public function getLogoutListener()
{
return $this->logoutListener;
}
}

View File

@@ -0,0 +1,81 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\Security;
use Psr\Container\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\FirewallMapInterface;
/**
* This is a lazy-loading firewall map implementation.
*
* Listeners will only be initialized if we really need them.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class FirewallMap implements FirewallMapInterface
{
private ContainerInterface $container;
private iterable $map;
public function __construct(ContainerInterface $container, iterable $map)
{
$this->container = $container;
$this->map = $map;
}
public function getListeners(Request $request): array
{
$context = $this->getFirewallContext($request);
if (null === $context) {
return [[], null, null];
}
return [$context->getListeners(), $context->getExceptionListener(), $context->getLogoutListener()];
}
public function getFirewallConfig(Request $request): ?FirewallConfig
{
$context = $this->getFirewallContext($request);
if (null === $context) {
return null;
}
return $context->getConfig();
}
private function getFirewallContext(Request $request): ?FirewallContext
{
if ($request->attributes->has('_firewall_context')) {
$storedContextId = $request->attributes->get('_firewall_context');
foreach ($this->map as $contextId => $requestMatcher) {
if ($contextId === $storedContextId) {
return $this->container->get($contextId);
}
}
$request->attributes->remove('_firewall_context');
}
foreach ($this->map as $contextId => $requestMatcher) {
if (null === $requestMatcher || $requestMatcher->matches($request)) {
$request->attributes->set('_firewall_context', $contextId);
return $this->container->get($contextId);
}
}
return null;
}
}

View File

@@ -0,0 +1,77 @@
<?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\Security;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Http\Event\LazyResponseEvent;
use Symfony\Component\Security\Http\Firewall\ExceptionListener;
use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface;
use Symfony\Component\Security\Http\Firewall\LogoutListener;
/**
* Lazily calls authentication listeners when actually required by the access listener.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class LazyFirewallContext extends FirewallContext
{
private TokenStorage $tokenStorage;
public function __construct(iterable $listeners, ?ExceptionListener $exceptionListener, ?LogoutListener $logoutListener, ?FirewallConfig $config, TokenStorage $tokenStorage)
{
parent::__construct($listeners, $exceptionListener, $logoutListener, $config);
$this->tokenStorage = $tokenStorage;
}
public function getListeners(): iterable
{
return [$this];
}
public function __invoke(RequestEvent $event): void
{
$listeners = [];
$request = $event->getRequest();
$lazy = $request->isMethodCacheable();
foreach (parent::getListeners() as $listener) {
if (!$lazy || !$listener instanceof FirewallListenerInterface) {
$listeners[] = $listener;
$lazy = $lazy && $listener instanceof FirewallListenerInterface;
} elseif (false !== $supports = $listener->supports($request)) {
$listeners[] = [$listener, 'authenticate'];
$lazy = null === $supports;
}
}
if (!$lazy) {
foreach ($listeners as $listener) {
$listener($event);
if ($event->hasResponse()) {
return;
}
}
return;
}
$this->tokenStorage->setInitializer(function () use ($event, $listeners) {
$event = new LazyResponseEvent($event);
foreach ($listeners as $listener) {
$listener($event);
}
});
}
}

View File

@@ -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\Security;
use Psr\Container\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
/**
* A decorator that delegates all method calls to the authenticator
* manager of the current firewall.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*
* @final
*/
class UserAuthenticator implements UserAuthenticatorInterface
{
use FirewallAwareTrait;
public function __construct(FirewallMap $firewallMap, ContainerInterface $userAuthenticators, RequestStack $requestStack)
{
$this->firewallMap = $firewallMap;
$this->locator = $userAuthenticators;
$this->requestStack = $requestStack;
}
public function authenticateUser(UserInterface $user, AuthenticatorInterface $authenticator, Request $request, array $badges = []): ?Response
{
return $this->getForFirewall()->authenticateUser($user, $authenticator, $request, $badges);
}
}

View File

@@ -0,0 +1,110 @@
<?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;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSessionDomainConstraintPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\CleanRememberMeVerifierPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\MakeFirewallsEventDispatcherTraceablePass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterCsrfFeaturesPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterEntryPointPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterGlobalSecurityEventListenersPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterLdapLocatorPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterTokenUsageTrackingPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\ReplaceDecoratedRememberMeHandlerPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\SortFirewallListenersPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\OidcTokenHandlerFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\OidcUserInfoTokenHandlerFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\ServiceTokenHandlerFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AccessTokenFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\CustomAuthenticatorFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginLdapFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpBasicFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpBasicLdapFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\JsonLoginFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\JsonLoginLdapFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\LoginLinkFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\LoginThrottlingFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RememberMeFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RemoteUserFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\X509Factory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\InMemoryFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\LdapFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\Security\Core\AuthenticationEvents;
use Symfony\Component\Security\Http\SecurityEvents;
/**
* Bundle.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SecurityBundle extends Bundle
{
/**
* @return void
*/
public function build(ContainerBuilder $container)
{
parent::build($container);
/** @var SecurityExtension $extension */
$extension = $container->getExtension('security');
$extension->addAuthenticatorFactory(new FormLoginFactory());
$extension->addAuthenticatorFactory(new FormLoginLdapFactory());
$extension->addAuthenticatorFactory(new JsonLoginFactory());
$extension->addAuthenticatorFactory(new JsonLoginLdapFactory());
$extension->addAuthenticatorFactory(new HttpBasicFactory());
$extension->addAuthenticatorFactory(new HttpBasicLdapFactory());
$extension->addAuthenticatorFactory(new RememberMeFactory());
$extension->addAuthenticatorFactory(new X509Factory());
$extension->addAuthenticatorFactory(new RemoteUserFactory());
$extension->addAuthenticatorFactory(new CustomAuthenticatorFactory());
$extension->addAuthenticatorFactory(new LoginThrottlingFactory());
$extension->addAuthenticatorFactory(new LoginLinkFactory());
$extension->addAuthenticatorFactory(new AccessTokenFactory([
new ServiceTokenHandlerFactory(),
new OidcUserInfoTokenHandlerFactory(),
new OidcTokenHandlerFactory(),
]));
$extension->addUserProviderFactory(new InMemoryFactory());
$extension->addUserProviderFactory(new LdapFactory());
$container->addCompilerPass(new AddExpressionLanguageProvidersPass());
$container->addCompilerPass(new AddSecurityVotersPass());
$container->addCompilerPass(new AddSessionDomainConstraintPass(), PassConfig::TYPE_BEFORE_REMOVING);
$container->addCompilerPass(new CleanRememberMeVerifierPass());
$container->addCompilerPass(new RegisterCsrfFeaturesPass());
$container->addCompilerPass(new RegisterTokenUsageTrackingPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 200);
$container->addCompilerPass(new RegisterLdapLocatorPass());
$container->addCompilerPass(new RegisterEntryPointPass());
// must be registered after RegisterListenersPass (in the FrameworkBundle)
$container->addCompilerPass(new RegisterGlobalSecurityEventListenersPass(), PassConfig::TYPE_BEFORE_REMOVING, -200);
// execute after ResolveChildDefinitionsPass optimization pass, to ensure class names are set
$container->addCompilerPass(new SortFirewallListenersPass(), PassConfig::TYPE_BEFORE_REMOVING);
$container->addCompilerPass(new ReplaceDecoratedRememberMeHandlerPass(), PassConfig::TYPE_OPTIMIZE);
$container->addCompilerPass(new AddEventAliasesPass(array_merge(
AuthenticationEvents::ALIASES,
SecurityEvents::ALIASES
)));
// must be registered before DecoratorServicePass
$container->addCompilerPass(new MakeFirewallsEventDispatcherTraceablePass(), PassConfig::TYPE_OPTIMIZE, 10);
}
}