login consent app sql

This commit is contained in:
2022-05-03 08:54:45 +02:00
parent e7253acfd8
commit f9a6535906
1652 changed files with 187600 additions and 45 deletions

View File

@ -0,0 +1,287 @@
CHANGELOG
=========
5.4
---
* Deprecate `FirewallConfig::getListeners()`, use `FirewallConfig::getAuthenticators()` instead
* Deprecate `security.authentication.basic_entry_point` and `security.authentication.retry_entry_point` services, the logic is moved into the
`HttpBasicAuthenticator` and `ChannelListener` respectively
* Deprecate `FirewallConfig::allowsAnonymous()` and the `allows_anonymous` from the data collector data, there will be no anonymous concept as of version 6.
* Deprecate not setting `$authenticatorManagerEnabled` to `true` in `SecurityDataCollector` and `DebugFirewallCommand`
* Deprecate `SecurityFactoryInterface` and `SecurityExtension::addSecurityListenerFactory()` in favor of
`AuthenticatorFactoryInterface` and `SecurityExtension::addAuthenticatorFactory()`
* Add `AuthenticatorFactoryInterface::getPriority()` which replaces `SecurityFactoryInterface::getPosition()`
* Deprecate passing an array of arrays as 1st argument to `MainConfiguration`, pass a sorted flat array of
factories instead.
* Deprecate the `always_authenticate_before_granting` option
* Display the roles of the logged-in user in the Web Debug Toolbar
* Add the `security.access_decision_manager.strategy_service` option
* Deprecate not configuring explicitly a provider for custom_authenticators when there is more than one registered provider
5.3
---
* The authenticator system is no longer experimental
* Login Link functionality is no longer experimental
* Add `required_badges` firewall config option
* [BC break] Add `login_throttling.lock_factory` setting defaulting to `null` (instead of `lock.factory`)
* Add a `login_throttling.interval` (in `security.firewalls`) option to change the default throttling interval.
* Add the `debug:firewall` command.
* Deprecate `UserPasswordEncoderCommand` class and the corresponding `user:encode-password` command,
use `UserPasswordHashCommand` and `user:hash-password` instead
* Deprecate the `security.encoder_factory.generic` service, the `security.encoder_factory` and `Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface` aliases,
use `security.password_hasher_factory` and `Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface` instead
* Deprecate the `security.user_password_encoder.generic` service, the `security.password_encoder` and the `Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface` aliases,
use `security.user_password_hasher`, `security.password_hasher` and `Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface` instead
* Deprecate the public `security.authorization_checker` and `security.token_storage` services to private
* Not setting the `enable_authenticator_manager` config option to `true` is deprecated
* Deprecate the `security.authentication.provider.*` services, use the new authenticator system instead
* Deprecate the `security.authentication.listener.*` services, use the new authenticator system instead
* Deprecate the Guard component integration, use the new authenticator system instead
* Add `form_login.form_only` option
5.2.0
-----
* Added `FirewallListenerFactoryInterface`, which can be implemented by security factories to add firewall listeners
* Added `SortFirewallListenersPass` to make the execution order of firewall listeners configurable by
leveraging `Symfony\Component\Security\Http\Firewall\FirewallListenerInterface`
* Added ability to use comma separated ip address list for `security.access_control`
* [BC break] Removed `EntryPointFactoryInterface`, authenticators must now implement `AuthenticationEntryPointInterface` if
they require autoregistration of a Security entry point.
5.1.0
-----
* Added XSD for configuration
* Added security configuration for priority-based access decision strategy
* Marked the `AnonymousFactory`, `FormLoginFactory`, `FormLoginLdapFactory`, `GuardAuthenticationFactory`, `HttpBasicFactory`, `HttpBasicLdapFactory`, `JsonLoginFactory`, `JsonLoginLdapFactory`, `RememberMeFactory`, `RemoteUserFactory` and `X509Factory` as `@internal`
* Renamed method `AbstractFactory#createEntryPoint()` to `AbstractFactory#createDefaultEntryPoint()`
5.0.0
-----
* The `switch_user.stateless` firewall option has been removed.
* Removed the ability to configure encoders using `argon2i` or `bcrypt` as algorithm, use `auto` instead
* The `simple_form` and `simple_preauth` authentication listeners have been removed,
use Guard instead.
* The `SimpleFormFactory` and `SimplePreAuthenticationFactory` classes have been removed,
use Guard instead.
* Removed `LogoutUrlHelper` and `SecurityHelper` templating helpers, use Twig instead
* Removed the `logout_on_user_change` firewall option
* Removed the `threads` encoder option
* Removed the `security.authentication.trust_resolver.anonymous_class` parameter
* Removed the `security.authentication.trust_resolver.rememberme_class` parameter
* Removed the `security.user.provider.in_memory.user` service.
4.4.0
-----
* Added `anonymous: lazy` mode to firewalls to make them (not) start the session as late as possible
* Added `migrate_from` option to encoders configuration.
* Added new `argon2id` encoder, undeprecated the `bcrypt` and `argon2i` ones (using `auto` is still recommended by default.)
* Deprecated the usage of "query_string" without a "search_dn" and a "search_password" config key in Ldap factories.
* Marked the `SecurityDataCollector` class as `@final`.
4.3.0
-----
* Added new encoder types: `auto` (recommended), `native` and `sodium`
* The normalization of the cookie names configured in the `logout.delete_cookies`
option is deprecated and will be disabled in Symfony 5.0. This affects to cookies
with dashes in their names. For example, starting from Symfony 5.0, the `my-cookie`
name will delete `my-cookie` (with a dash) instead of `my_cookie` (with an underscore).
4.2.0
-----
* Using the `security.authentication.trust_resolver.anonymous_class` and
`security.authentication.trust_resolver.rememberme_class` parameters to define
the token classes is deprecated. To use custom tokens extend the existing
`Symfony\Component\Security\Core\Authentication\Token\AnonymousToken`.
or `Symfony\Component\Security\Core\Authentication\Token\RememberMeToken`.
* Added `Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass`
* Added `json_login_ldap` authentication provider to use LDAP authentication with a REST API.
* Made remember-me cookies inherit their default config from `framework.session.cookie_*`
and added an "auto" mode to their "secure" config option to make them secure on HTTPS automatically.
* Deprecated the `simple_form` and `simple_preauth` authentication listeners, use Guard instead.
* Deprecated the `SimpleFormFactory` and `SimplePreAuthenticationFactory` classes, use Guard instead.
* Added `port` in access_control
* Added individual voter decisions to the profiler
4.1.0
-----
* The `switch_user.stateless` firewall option is deprecated, use the `stateless` option instead.
* The `logout_on_user_change` firewall option is deprecated.
* deprecated `SecurityUserValueResolver`, use
`Symfony\Component\Security\Http\Controller\UserValueResolver` instead.
4.0.0
-----
* removed `FirewallContext::getContext()`
* made `FirewallMap::$container` and `::$map` private
* made the first `UserPasswordEncoderCommand::_construct()` argument mandatory
* `UserPasswordEncoderCommand` does not extend `ContainerAwareCommand` anymore
* removed support for voters that don't implement the `VoterInterface`
* removed HTTP digest authentication
* removed command `acl:set` along with `SetAclCommand` class
* removed command `init:acl` along with `InitAclCommand` class
* removed `acl` configuration key and related services, use symfony/acl-bundle instead
* removed auto picking the first registered provider when no configured provider on a firewall and ambiguous
* the firewall option `logout_on_user_change` is now always true, which will trigger a logout if the user changes
between requests
* the `switch_user.stateless` firewall option is `true` for stateless firewalls
3.4.0
-----
* Added new `security.helper` service that is an instance of `Symfony\Component\Security\Core\Security`
and provides shortcuts for common security tasks.
* Tagging voters with the `security.voter` tag without implementing the
`VoterInterface` on the class is now deprecated and will be removed in 4.0.
* [BC BREAK] `FirewallContext::getListeners()` now returns `\Traversable|array`
* added info about called security listeners in profiler
* Added `logout_on_user_change` to the firewall options. This config item will
trigger a logout when the user has changed. Should be set to true to avoid
deprecations in the configuration.
* deprecated HTTP digest authentication
* deprecated command `acl:set` along with `SetAclCommand` class
* deprecated command `init:acl` along with `InitAclCommand` class
* Added support for the new Argon2i password encoder
* added `stateless` option to the `switch_user` listener
* deprecated auto picking the first registered provider when no configured provider on a firewall and ambiguous
3.3.0
-----
* Deprecated instantiating `UserPasswordEncoderCommand` without its constructor
arguments fully provided.
* Deprecated `UserPasswordEncoderCommand::getContainer()` and relying on the
`ContainerAwareCommand` sub class or `ContainerAwareInterface` implementation for this command.
* Deprecated the `FirewallMap::$map` and `$container` properties.
* [BC BREAK] Keys of the `users` node for `in_memory` user provider are no longer normalized.
* deprecated `FirewallContext::getListeners()`
3.2.0
-----
* Added the `SecurityUserValueResolver` to inject the security users in actions via
`Symfony\Component\Security\Core\User\UserInterface` in the method signature.
3.0.0
-----
* Removed the `security.context` service.
2.8.0
-----
* deprecated the `key` setting of `anonymous`, `remember_me` and `http_digest`
in favor of the `secret` setting.
* deprecated the `intention` firewall listener setting in favor of the `csrf_token_id`.
2.6.0
-----
* Added the possibility to override the default success/failure handler
to get the provider key and the options injected
* Deprecated the `security.context` service for the `security.token_storage` and
`security.authorization_checker` services.
2.4.0
-----
* Added 'host' option to firewall configuration
* Added 'csrf_token_generator' and 'csrf_token_id' options to firewall logout
listener configuration to supersede/alias 'csrf_provider' and 'intention'
respectively
* Moved 'security.secure_random' service configuration to FrameworkBundle
2.3.0
-----
* allowed for multiple IP address in security access_control rules
2.2.0
-----
* Added PBKDF2 Password encoder
* Added BCrypt password encoder
2.1.0
-----
* [BC BREAK] The custom factories for the firewall configuration are now
registered during the build method of bundles instead of being registered
by the end-user (you need to remove the 'factories' keys in your security
configuration).
* [BC BREAK] The Firewall listener is now registered after the Router one. This
means that specific Firewall URLs (like /login_check and /logout must now
have proper route defined in your routing configuration)
* [BC BREAK] refactored the user provider configuration. The configuration
changed for the chain provider and the memory provider:
Before:
``` yaml
security:
providers:
my_chain_provider:
providers: [my_memory_provider, my_doctrine_provider]
my_memory_provider:
users:
toto: { password: foobar, roles: [ROLE_USER] }
foo: { password: bar, roles: [ROLE_USER, ROLE_ADMIN] }
```
After:
``` yaml
security:
providers:
my_chain_provider:
chain:
providers: [my_memory_provider, my_doctrine_provider]
my_memory_provider:
memory:
users:
toto: { password: foobar, roles: [ROLE_USER] }
foo: { password: bar, roles: [ROLE_USER, ROLE_ADMIN] }
```
* [BC BREAK] Method `equals` was removed from `UserInterface` to its own new
`EquatableInterface`. The user class can now implement this interface to override
the default implementation of users equality test.
* added a validator for the user password
* added 'erase_credentials' as a configuration key (true by default)
* added new events: `security.authentication.success` and `security.authentication.failure`
fired on authentication success/failure, regardless of authentication method,
events are defined in new event class: `Symfony\Component\Security\Core\AuthenticationEvents`.
* Added optional CSRF protection to LogoutListener:
``` yaml
security:
firewalls:
default:
logout:
path: /logout_path
target: /
csrf_parameter: _csrf_token # Optional (defaults to "_csrf_token")
csrf_provider: security.csrf.token_generator # Required to enable protection
intention: logout # Optional (defaults to "logout")
```
If the LogoutListener has CSRF protection enabled but cannot validate a token,
then a LogoutException will be thrown.
* Added `logout_url` templating helper and Twig extension, which may be used to
generate logout URL's within templates. The security firewall's config key
must be specified. If a firewall's logout listener has CSRF protection
enabled, a token will be automatically added to the generated URL.

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 $expressions;
private $expressionLanguage;
/**
* @param iterable<mixed, Expression> $expressions
*/
public function __construct(iterable $expressions, ExpressionLanguage $expressionLanguage)
{
$this->expressions = $expressions;
$this->expressionLanguage = $expressionLanguage;
}
public function isOptional()
{
return true;
}
/**
* @return string[]
*/
public function warmUp(string $cacheDir)
{
foreach ($this->expressions as $expression) {
$this->expressionLanguage->parse($expression, ['token', 'user', 'object', 'subject', 'role_names', 'request', 'trust_resolver']);
}
return [];
}
}

View File

@ -0,0 +1,289 @@
<?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\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>
*/
final class DebugFirewallCommand extends Command
{
protected static $defaultName = 'debug:firewall';
protected static $defaultDescription = 'Display information about your security firewall(s)';
private $firewallNames;
private $contexts;
private $eventDispatchers;
private $authenticators;
private $authenticatorManagerEnabled;
/**
* @param string[] $firewallNames
* @param AuthenticatorInterface[][] $authenticators
*/
public function __construct(array $firewallNames, ContainerInterface $contexts, ContainerInterface $eventDispatchers, array $authenticators, bool $authenticatorManagerEnabled)
{
if (!$authenticatorManagerEnabled) {
trigger_deprecation('symfony/security-bundle', '5.4', 'Setting the $authenticatorManagerEnabled argument of "%s" to "false" is deprecated, use the new authenticator system instead.', __METHOD__);
}
$this->firewallNames = $firewallNames;
$this->contexts = $contexts;
$this->eventDispatchers = $eventDispatchers;
$this->authenticators = $authenticators;
$this->authenticatorManagerEnabled = $authenticatorManagerEnabled;
parent::__construct();
}
protected function configure(): void
{
$exampleName = $this->getExampleName();
$this
->setDescription(self::$defaultDescription)
->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);
}
if ($this->authenticatorManagerEnabled) {
$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)
{
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(
static function ($authenticator) {
return [
\get_class($authenticator),
];
},
$authenticators
)
);
}
private function formatCallable($callable): string
{
if (\is_array($callable)) {
if (\is_object($callable[0])) {
return sprintf('%s::%s()', \get_class($callable[0]), $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 (false !== strpos($r->name, '{closure}')) {
return 'Closure()';
}
if ($class = $r->getClosureScopeClass()) {
return sprintf('%s::%s()', $class->name, $r->name);
}
return $r->name.'()';
}
if (method_exists($callable, '__invoke')) {
return sprintf('%s::__invoke()', \get_class($callable));
}
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,216 @@
<?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 Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\PasswordHasher\Command\UserPasswordHashCommand;
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
use Symfony\Component\Security\Core\Encoder\SelfSaltingEncoderInterface;
/**
* Encode a user's password.
*
* @author Sarah Khalil <mkhalil.sarah@gmail.com>
*
* @final
*
* @deprecated since Symfony 5.3, use {@link UserPasswordHashCommand} instead
*/
class UserPasswordEncoderCommand extends Command
{
protected static $defaultName = 'security:encode-password';
protected static $defaultDescription = 'Encode a password';
private $encoderFactory;
private $userClasses;
public function __construct(EncoderFactoryInterface $encoderFactory, array $userClasses = [])
{
$this->encoderFactory = $encoderFactory;
$this->userClasses = $userClasses;
parent::__construct();
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDescription(self::$defaultDescription)
->addArgument('password', InputArgument::OPTIONAL, 'The plain password to encode.')
->addArgument('user-class', InputArgument::OPTIONAL, 'The User entity class path associated with the encoder used to encode the password.')
->addOption('empty-salt', null, InputOption::VALUE_NONE, 'Do not generate a salt or let the encoder generate one.')
->setHelp(<<<EOF
The <info>%command.name%</info> command encodes passwords according to your
security configuration. This command is mainly used to generate passwords for
the <comment>in_memory</comment> user provider type and for changing passwords
in the database while developing the application.
Suppose that you have the following security configuration in your application:
<comment>
# app/config/security.yml
security:
encoders:
Symfony\Component\Security\Core\User\InMemoryUser: plaintext
App\Entity\User: auto
</comment>
If you execute the command non-interactively, the first available configured
user class under the <comment>security.encoders</comment> key is used and a random salt is
generated to encode the password:
<info>php %command.full_name% --no-interaction [password]</info>
Pass the full user class path as the second argument to encode passwords for
your own entities:
<info>php %command.full_name% --no-interaction [password] 'App\Entity\User'</info>
Executing the command interactively allows you to generate a random salt for
encoding the password:
<info>php %command.full_name% [password] 'App\Entity\User'</info>
In case your encoder doesn't require a salt, add the <comment>empty-salt</comment> option:
<info>php %command.full_name% --empty-salt [password] 'App\Entity\User'</info>
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$errorIo = $output instanceof ConsoleOutputInterface ? new SymfonyStyle($input, $output->getErrorOutput()) : $io;
$errorIo->caution('The use of the "security:encode-password" command is deprecated since version 5.3 and will be removed in 6.0. Use "security:hash-password" instead.');
$input->isInteractive() ? $errorIo->title('Symfony Password Encoder Utility') : $errorIo->newLine();
$password = $input->getArgument('password');
$userClass = $this->getUserClass($input, $io);
$emptySalt = $input->getOption('empty-salt');
$encoder = $this->encoderFactory->getEncoder($userClass);
$saltlessWithoutEmptySalt = !$emptySalt && $encoder instanceof SelfSaltingEncoderInterface;
if ($saltlessWithoutEmptySalt) {
$emptySalt = true;
}
if (!$password) {
if (!$input->isInteractive()) {
$errorIo->error('The password must not be empty.');
return 1;
}
$passwordQuestion = $this->createPasswordQuestion();
$password = $errorIo->askQuestion($passwordQuestion);
}
$salt = null;
if ($input->isInteractive() && !$emptySalt) {
$emptySalt = true;
$errorIo->note('The command will take care of generating a salt for you. Be aware that some encoders advise to let them generate their own salt. If you\'re using one of those encoders, please answer \'no\' to the question below. '.\PHP_EOL.'Provide the \'empty-salt\' option in order to let the encoder handle the generation itself.');
if ($errorIo->confirm('Confirm salt generation ?')) {
$salt = $this->generateSalt();
$emptySalt = false;
}
} elseif (!$emptySalt) {
$salt = $this->generateSalt();
}
$encodedPassword = $encoder->encodePassword($password, $salt);
$rows = [
['Encoder used', \get_class($encoder)],
['Encoded password', $encodedPassword],
];
if (!$emptySalt) {
$rows[] = ['Generated salt', $salt];
}
$io->table(['Key', 'Value'], $rows);
if (!$emptySalt) {
$errorIo->note(sprintf('Make sure that your salt storage field fits the salt length: %s chars', \strlen($salt)));
} elseif ($saltlessWithoutEmptySalt) {
$errorIo->note('Self-salting encoder used: the encoder generated its own built-in salt.');
}
$errorIo->success('Password encoding succeeded');
return 0;
}
/**
* Create the password question to ask the user for the password to be encoded.
*/
private function createPasswordQuestion(): Question
{
$passwordQuestion = new Question('Type in your password to be encoded');
return $passwordQuestion->setValidator(function ($value) {
if ('' === trim($value)) {
throw new InvalidArgumentException('The password must not be empty.');
}
return $value;
})->setHidden(true)->setMaxAttempts(20);
}
private function generateSalt(): string
{
return base64_encode(random_bytes(30));
}
private function getUserClass(InputInterface $input, SymfonyStyle $io): string
{
if (null !== $userClass = $input->getArgument('user-class')) {
return $userClass;
}
if (empty($this->userClasses)) {
throw new RuntimeException('There are no configured encoders for the "security" extension.');
}
if (!$input->isInteractive() || 1 === \count($this->userClasses)) {
return reset($this->userClasses);
}
$userClasses = $this->userClasses;
natcasesort($userClasses);
$userClasses = array_values($userClasses);
return $io->choice('For which user class would you like to encode a password?', $userClasses, reset($userClasses));
}
}

View File

@ -0,0 +1,400 @@
<?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\AnonymousToken;
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 $tokenStorage;
private $roleHierarchy;
private $logoutUrlGenerator;
private $accessDecisionManager;
private $firewallMap;
private $firewall;
private $hasVarDumper;
private $authenticatorManagerEnabled;
public function __construct(TokenStorageInterface $tokenStorage = null, RoleHierarchyInterface $roleHierarchy = null, LogoutUrlGenerator $logoutUrlGenerator = null, AccessDecisionManagerInterface $accessDecisionManager = null, FirewallMapInterface $firewallMap = null, TraceableFirewallListener $firewall = null, bool $authenticatorManagerEnabled = false)
{
if (!$authenticatorManagerEnabled) {
trigger_deprecation('symfony/security-bundle', '5.4', 'Setting the $authenticatorManagerEnabled argument of "%s" to "false" is deprecated, use the new authenticator system instead.', __METHOD__);
}
$this->tokenStorage = $tokenStorage;
$this->roleHierarchy = $roleHierarchy;
$this->logoutUrlGenerator = $logoutUrlGenerator;
$this->accessDecisionManager = $accessDecisionManager;
$this->firewallMap = $firewallMap;
$this->firewall = $firewall;
$this->hasVarDumper = class_exists(ClassStub::class);
$this->authenticatorManagerEnabled = $authenticatorManagerEnabled;
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Throwable $exception = null)
{
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();
// @deprecated since Symfony 5.3, change to $originalToken->getUserIdentifier() in 6.0
$impersonatorUser = method_exists($originalToken, 'getUserIdentifier') ? $originalToken->getUserIdentifier() : $originalToken->getUsername();
}
if (null !== $this->roleHierarchy) {
foreach ($this->roleHierarchy->getReachableRoleNames($assignedRoles) as $role) {
if (!\in_array($role, $assignedRoles, true)) {
$inheritedRoles[] = $role;
}
}
}
$logoutUrl = null;
try {
if (null !== $this->logoutUrlGenerator && !$token instanceof AnonymousToken) {
$logoutUrl = $this->logoutUrlGenerator->getLogoutPath();
}
} catch (\Exception $e) {
// fail silently when the logout URL cannot be generated
}
$this->data = [
'enabled' => true,
'authenticated' => method_exists($token, 'isAuthenticated') ? $token->isAuthenticated(false) : (bool) $token->getUser(),
'impersonated' => null !== $impersonatorUser,
'impersonator_user' => $impersonatorUser,
'impersonation_exit_path' => null,
'token' => $token,
'token_class' => $this->hasVarDumper ? new ClassStub(\get_class($token)) : \get_class($token),
'logout_url' => $logoutUrl,
// @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0
'user' => method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(),
'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();
foreach ($this->accessDecisionManager->getVoters() as $voter) {
if ($voter instanceof TraceableVoter) {
$voter = $voter->getDecoratedVoter();
}
$this->data['voters'][] = $this->hasVarDumper ? new ClassStub(\get_class($voter)) : \get_class($voter);
}
// collect voter details
$decisionLog = $this->accessDecisionManager->getDecisionLog();
foreach ($decisionLog as $key => $log) {
$decisionLog[$key]['voter_details'] = [];
foreach ($log['voterDetails'] as $voterDetail) {
$voterClass = \get_class($voterDetail['voter']);
$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(),
'allows_anonymous' => $this->authenticatorManagerEnabled ? false : $firewallConfig->allowsAnonymous(),
'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(),
];
// in 6.0, always fill `$this->data['authenticators'] only
if ($this->authenticatorManagerEnabled) {
$this->data['firewall']['authenticators'] = $firewallConfig->getAuthenticators();
} else {
$this->data['firewall']['listeners'] = $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['authenticator_manager_enabled'] = $this->authenticatorManagerEnabled;
$this->data['authenticators'] = $this->firewall ? $this->firewall->getAuthenticatorsInfo() : [];
}
/**
* {@inheritdoc}
*/
public function reset()
{
$this->data = [];
}
public function lateCollect()
{
$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.
*
* @return array|Data
*/
public function getRoles()
{
return $this->data['roles'];
}
/**
* Gets the inherited roles of the user.
*
* @return array|Data
*/
public function getInheritedRoles()
{
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.
*
* @return string|Data|null
*/
public function getTokenClass()
{
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()
{
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.
*
* @return array|Data
*/
public function getAccessDecisionLog()
{
return $this->data['access_decision_log'];
}
/**
* Returns the configuration of the current firewall context.
*
* @return array|Data|null
*/
public function getFirewall()
{
return $this->data['firewall'];
}
/**
* @return array|Data
*/
public function getListeners()
{
return $this->data['listeners'];
}
/**
* @return array|Data
*/
public function getAuthenticators()
{
return $this->data['authenticators'];
}
/**
* {@inheritdoc}
*/
public function getName(): string
{
return 'security';
}
public function isAuthenticatorManagerEnabled(): bool
{
return $this->data['authenticator_manager_enabled'];
}
}

View File

@ -0,0 +1,97 @@
<?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;
/**
* Firewall collecting called security listeners and authenticators.
*
* @author Robin Chalas <robin.chalas@gmail.com>
*/
final class TraceableFirewallListener extends FirewallListener
{
private $wrappedListeners = [];
private $authenticatorsInfo = [];
public function getWrappedListeners()
{
return $this->wrappedListeners;
}
public function getAuthenticatorsInfo(): array
{
return $this->authenticatorsInfo;
}
protected function callListeners(RequestEvent $event, iterable $listeners)
{
$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,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\Debug;
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;
private $listener;
private $time;
private $stub;
/**
* Proxies all method calls to the original listener.
*/
public function __call(string $method, array $arguments)
{
return $this->listener->{$method}(...$arguments);
}
public function getWrappedListener()
{
return $this->listener;
}
public function getInfo(): array
{
return [
'response' => $this->response,
'time' => $this->time,
'stub' => $this->stub ?? $this->stub = ClassStub::wrapCallable($this->listener instanceof TraceableAuthenticatorManagerListener ? $this->listener->getAuthenticatorManagerListener() : $this->listener),
];
}
}

View File

@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\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);
}
/**
* {@inheritdoc}
*/
public function authenticate(RequestEvent $event)
{
$startTime = microtime(true);
try {
$ret = $this->listener->authenticate($event);
} catch (LazyResponseException $e) {
$this->response = $e->getResponse();
throw $e;
} finally {
$this->time = microtime(true) - $startTime;
}
$this->response = $event->getResponse();
return $ret;
}
}

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\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;
public function __construct(callable $listener)
{
$this->listener = $listener;
}
public function __invoke(RequestEvent $event)
{
$startTime = microtime(true);
($this->listener)($event);
$this->time = microtime(true) - $startTime;
$this->response = $event->getResponse();
}
}

View File

@ -0,0 +1,41 @@
<?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
{
/**
* {@inheritdoc}
*/
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');
}
}
}

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;
/**
* {@inheritdoc}
*/
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
{
/**
* {@inheritdoc}
*/
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
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('cache.system')) {
$container->removeDefinition('cache.security_token_verifier');
}
}
}

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\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
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)
{
$this->registerCsrfProtectionListener($container);
$this->registerLogoutHandler($container);
}
private function registerCsrfProtectionListener(ContainerBuilder $container)
{
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')
->setPublic(false);
}
protected function registerLogoutHandler(ContainerBuilder $container)
{
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, 'Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface')) {
return;
}
$container->register('security.logout.listener.csrf_token_clearing', CsrfTokenClearingLogoutListener::class)
->addArgument(new Reference('security.csrf.token_storage'))
->addTag('kernel.event_subscriber')
->setPublic(false);
}
}

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\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
trigger_deprecation('symfony/security-bundle', '5.1', 'The "%s" class is deprecated.', RegisterCsrfTokenClearingLogoutHandlerPass::class);
/**
* @deprecated since symfony/security-bundle 5.1
*/
class RegisterCsrfTokenClearingLogoutHandlerPass extends RegisterCsrfFeaturesPass
{
public function process(ContainerBuilder $container)
{
if (!$container->has('security.csrf.token_storage')) {
return;
}
$this->registerLogoutHandler($container);
}
}

View File

@ -0,0 +1,83 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
/**
* @author Wouter de Jong <wouter@wouterj.nl>
*/
class RegisterEntryPointPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
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,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\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,
];
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
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)
{
$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,56 @@
<?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
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
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->has('session.storage')) {
$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,61 @@
<?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';
/**
* {@inheritdoc}
*/
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,80 @@
<?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, function (Reference $a, Reference $b) use ($prioritiesByServiceId) {
return $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,521 @@
<?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\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
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\Event\LogoutEvent;
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 $factories;
private $userProviderFactories;
/**
* @param array<array-key, SecurityFactoryInterface|AuthenticatorFactoryInterface> $factories
*/
public function __construct(array $factories, array $userProviderFactories)
{
if (\is_array(current($factories))) {
trigger_deprecation('symfony/security-bundle', '5.4', 'Passing an array of arrays as 1st argument to "%s" is deprecated, pass a sorted array of factories instead.', __METHOD__);
$factories = array_merge(...array_values($factories));
}
$this->factories = $factories;
$this->userProviderFactories = $userProviderFactories;
}
/**
* Generates the configuration tree builder.
*
* @return TreeBuilder
*/
public function getConfigTreeBuilder()
{
$tb = new TreeBuilder('security');
$rootNode = $tb->getRootNode();
$rootNode
->beforeNormalization()
->ifTrue(function ($v) {
if ($v['encoders'] ?? false) {
trigger_deprecation('symfony/security-bundle', '5.3', 'The child node "encoders" at path "security" is deprecated, use "password_hashers" instead.');
return true;
}
return $v['password_hashers'] ?? false;
})
->then(function ($v) {
$v['password_hashers'] = array_merge($v['password_hashers'] ?? [], $v['encoders'] ?? []);
$v['encoders'] = $v['password_hashers'];
return $v;
})
->end()
->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('always_authenticate_before_granting')
->defaultFalse()
->setDeprecated('symfony/security-bundle', '5.4')
->end()
->booleanNode('erase_credentials')->defaultTrue()->end()
->booleanNode('enable_authenticator_manager')->defaultFalse()->info('Enables the new Symfony Security system based on Authenticators, all used authenticators must support this before enabling this.')->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(function ($v) { return isset($v['strategy'], $v['service']); })
->thenInvalid('"strategy" and "service" cannot be used together.')
->end()
->validate()
->ifTrue(function ($v) { return isset($v['strategy'], $v['strategy_service']); })
->thenInvalid('"strategy" and "strategy_service" cannot be used together.')
->end()
->validate()
->ifTrue(function ($v) { return isset($v['service'], $v['strategy_service']); })
->thenInvalid('"service" and "strategy_service" cannot be used together.')
->end()
->end()
->end()
;
$this->addEncodersSection($rootNode);
$this->addPasswordHashersSection($rootNode);
$this->addProvidersSection($rootNode);
$this->addFirewallsSection($rootNode, $this->factories);
$this->addAccessControlSection($rootNode);
$this->addRoleHierarchySection($rootNode);
return $tb;
}
private function addRoleHierarchySection(ArrayNodeDefinition $rootNode)
{
$rootNode
->fixXmlConfig('role', 'role_hierarchy')
->children()
->arrayNode('role_hierarchy')
->useAttributeAsKey('id')
->prototype('array')
->performNoDeepMerging()
->beforeNormalization()->ifString()->then(function ($v) { return ['value' => $v]; })->end()
->beforeNormalization()
->ifTrue(function ($v) { return \is_array($v) && isset($v['value']); })
->then(function ($v) { return preg_split('/\s*,\s*/', $v['value']); })
->end()
->prototype('scalar')->end()
->end()
->end()
->end()
;
}
private function addAccessControlSection(ArrayNodeDefinition $rootNode)
{
$rootNode
->fixXmlConfig('rule', 'access_control')
->children()
->arrayNode('access_control')
->cannotBeOverwritten()
->prototype('array')
->fixXmlConfig('ip')
->fixXmlConfig('method')
->children()
->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(function ($v) { return [$v]; })->end()
->prototype('scalar')->end()
->end()
->arrayNode('methods')
->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/', $v); })->end()
->prototype('scalar')->end()
->end()
->scalarNode('allow_if')->defaultNull()->end()
->end()
->fixXmlConfig('role')
->children()
->arrayNode('roles')
->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/', $v); })->end()
->prototype('scalar')->end()
->end()
->end()
->end()
->end()
->end()
;
}
/**
* @param array<array-key, SecurityFactoryInterface|AuthenticatorFactoryInterface> $factories
*/
private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $factories)
{
$firewallNodeBuilder = $rootNode
->fixXmlConfig('firewall')
->children()
->arrayNode('firewalls')
->isRequired()
->requiresAtLeastOneElement()
->disallowNewKeysInSubsequentConfigs()
->useAttributeAsKey('name')
->prototype('array')
->fixXmlConfig('required_badge')
->children()
;
$firewallNodeBuilder
->scalarNode('pattern')->end()
->scalarNode('host')->end()
->arrayNode('methods')
->beforeNormalization()->ifString()->then(function ($v) { return 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()
->children()
->scalarNode('csrf_parameter')->defaultValue('_csrf_token')->end()
->scalarNode('csrf_token_generator')->cannotBeEmpty()->end()
->scalarNode('csrf_token_id')->defaultValue('logout')->end()
->scalarNode('path')->defaultValue('/logout')->end()
->scalarNode('target')->defaultValue('/')->end()
->scalarNode('success_handler')->setDeprecated('symfony/security-bundle', '5.1', sprintf('The "%%node%%" at path "%%path%%" is deprecated, register a listener on the "%s" event instead.', LogoutEvent::class))->end()
->booleanNode('invalidate_session')->defaultTrue()->end()
->end()
->fixXmlConfig('delete_cookie')
->children()
->arrayNode('delete_cookies')
->normalizeKeys(false)
->beforeNormalization()
->ifTrue(function ($v) { return \is_array($v) && \is_int(key($v)); })
->then(function ($v) { return array_map(function ($v) { return ['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()
->end()
->end()
->end()
->end()
->fixXmlConfig('handler')
->children()
->arrayNode('handlers')
->prototype('scalar')->setDeprecated('symfony/security-bundle', '5.1', sprintf('The "%%node%%" at path "%%path%%" is deprecated, register a listener on the "%s" event instead.', LogoutEvent::class))->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()
->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 (false === strpos($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(function ($v) {
return 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)
{
$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(function ($v) { return 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(function ($v) { return \count($v) > 1; })
->thenInvalid('You cannot set multiple provider types for the same provider')
->end()
->validate()
->ifTrue(function ($v) { return 0 === \count($v); })
->thenInvalid('You must set a provider definition for the provider.')
->end()
;
}
private function addEncodersSection(ArrayNodeDefinition $rootNode)
{
$rootNode
->fixXmlConfig('encoder')
->children()
->arrayNode('encoders')
->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(function ($v) { return ['algorithm' => $v]; })->end()
->children()
->scalarNode('algorithm')
->cannotBeEmpty()
->validate()
->ifTrue(function ($v) { return !\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 addPasswordHashersSection(ArrayNodeDefinition $rootNode)
{
$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(function ($v) { return ['algorithm' => $v]; })->end()
->children()
->scalarNode('algorithm')
->cannotBeEmpty()
->validate()
->ifTrue(function ($v) { return !\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,205 @@
<?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;
/**
* AbstractFactory is the base class for all classes inheriting from
* AbstractAuthenticationListener.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Lukas Kahwe Smith <smith@pooteeweet.org>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
abstract class AbstractFactory implements SecurityFactoryInterface
{
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',
];
public function create(ContainerBuilder $container, string $id, array $config, string $userProviderId, ?string $defaultEntryPointId)
{
// authentication provider
$authProviderId = $this->createAuthProvider($container, $id, $config, $userProviderId);
// authentication listener
$listenerId = $this->createListener($container, $id, $config, $userProviderId);
// add remember-me aware tag if requested
if ($this->isRememberMeAware($config)) {
$container
->getDefinition($listenerId)
->addTag('security.remember_me_aware', ['id' => $id, 'provider' => $userProviderId])
;
}
// create entry point if applicable (optional)
$entryPointId = $this->createEntryPoint($container, $id, $config, $defaultEntryPointId);
return [$authProviderId, $listenerId, $entryPointId];
}
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 (\is_bool($default)) {
$builder->booleanNode($name)->defaultValue($default);
} else {
$builder->scalarNode($name)->defaultValue($default);
}
}
}
final public function addOption(string $name, $default = null)
{
$this->options[$name] = $default;
}
/**
* Subclasses must return the id of a service which implements the
* AuthenticationProviderInterface.
*
* @return string
*/
abstract protected function createAuthProvider(ContainerBuilder $container, string $id, array $config, string $userProviderId);
/**
* Subclasses must return the id of the abstract listener template.
*
* Listener definitions should inherit from the AbstractAuthenticationListener
* like this:
*
* <service id="my.listener.id"
* class="My\Concrete\Classname"
* parent="security.authentication.listener.abstract"
* abstract="true" />
*
* In the above case, this method would return "my.listener.id".
*
* @return string
*/
abstract protected function getListenerId();
/**
* Subclasses may create an entry point of their as they see fit. The
* default implementation does not change the default entry point.
*
* @return string|null the entry point id
*/
protected function createEntryPoint(ContainerBuilder $container, string $id, array $config, ?string $defaultEntryPointId)
{
return $defaultEntryPointId;
}
/**
* Subclasses may disable remember-me features for the listener, by
* always returning false from this method.
*
* @return bool Whether a possibly configured RememberMeServices should be set for this listener
*/
protected function isRememberMeAware(array $config)
{
return $config['remember_me'];
}
protected function createListener(ContainerBuilder $container, string $id, array $config, string $userProvider)
{
$listenerId = $this->getListenerId();
$listener = new ChildDefinition($listenerId);
$listener->replaceArgument(4, $id);
$listener->replaceArgument(5, new Reference($this->createAuthenticationSuccessHandler($container, $id, $config)));
$listener->replaceArgument(6, new Reference($this->createAuthenticationFailureHandler($container, $id, $config)));
$listener->replaceArgument(7, array_intersect_key($config, $this->options));
$listenerId .= '.'.$id;
$container->setDefinition($listenerId, $listener);
return $listenerId;
}
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 Reference($config['success_handler']));
$successHandler->replaceArgument(1, $options);
$successHandler->replaceArgument(2, $id);
} else {
$successHandler = $container->setDefinition($successHandlerId, new ChildDefinition('security.authentication.success_handler'));
$successHandler->addMethodCall('setOptions', [$options]);
$successHandler->addMethodCall('setFirewallName', [$id]);
}
return $successHandlerId;
}
protected function createAuthenticationFailureHandler(ContainerBuilder $container, string $id, array $config)
{
$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 Reference($config['failure_handler']));
$failureHandler->replaceArgument(1, $options);
} else {
$failureHandler = $container->setDefinition($id, new ChildDefinition('security.authentication.failure_handler'));
$failureHandler->addMethodCall('setOptions', [$options]);
}
return $id;
}
protected function getSuccessHandlerId(string $id)
{
return 'security.authentication.success_handler.'.$id.'.'.str_replace('-', '_', $this->getKey());
}
protected function getFailureHandlerId(string $id)
{
return 'security.authentication.failure_handler.'.$id.'.'.str_replace('-', '_', $this->getKey());
}
}

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\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\Parameter;
/**
* @author Wouter de Jong <wouter@wouterj.nl>
*
* @deprecated since Symfony 5.3, use the new authenticator system instead
*/
class AnonymousFactory implements SecurityFactoryInterface, AuthenticatorFactoryInterface
{
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
{
if (null === $config['secret']) {
$config['secret'] = new Parameter('container.build_hash');
}
$listenerId = 'security.authentication.listener.anonymous.'.$id;
$container
->setDefinition($listenerId, new ChildDefinition('security.authentication.listener.anonymous'))
->replaceArgument(1, $config['secret'])
;
$providerId = 'security.authentication.provider.anonymous.'.$id;
$container
->setDefinition($providerId, new ChildDefinition('security.authentication.provider.anonymous'))
->replaceArgument(0, $config['secret'])
;
return [$providerId, $listenerId, $defaultEntryPoint];
}
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string
{
throw new InvalidConfigurationException(sprintf('The authenticator manager no longer has "anonymous" security. Please remove this option under the "%s" firewall'.($config['lazy'] ? ' and add "lazy: true"' : '').'.', $firewallName));
}
public function getPriority()
{
return -60;
}
public function getPosition()
{
return 'anonymous';
}
public function getKey()
{
return 'anonymous';
}
public function addConfiguration(NodeDefinition $builder)
{
$builder
->beforeNormalization()
->ifTrue(function ($v) { return 'lazy' === $v; })
->then(function ($v) { return ['lazy' => true]; })
->end()
->children()
->booleanNode('lazy')->defaultFalse()->setDeprecated('symfony/security-bundle', '5.1', 'Using "anonymous: lazy" to make the firewall lazy is deprecated, use "anonymous: true" and "lazy: true" instead.')->end()
->scalarNode('secret')->defaultNull()->end()
->end()
;
}
}

View File

@ -0,0 +1,40 @@
<?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;
/**
* @method int getPriority() defines the position at which the authenticator is called
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
interface AuthenticatorFactoryInterface
{
/**
* Creates the authenticator service(s) for the provided configuration.
*
* @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);
/**
* Defines the configuration key used to reference the authenticator
* in the firewall configuration.
*
* @return string
*/
public function getKey();
public function addConfiguration(NodeDefinition $builder);
}

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\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, SecurityFactoryInterface
{
public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint): array
{
throw new \LogicException('Custom authenticators are not supported when "security.enable_authenticator_manager" is not set to true.');
}
public function getPriority(): int
{
return 0;
}
public function getPosition(): string
{
return 'pre_auth';
}
public function getKey(): string
{
return 'custom_authenticators';
}
/**
* @param ArrayNodeDefinition $builder
*/
public function addConfiguration(NodeDefinition $builder)
{
$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(function ($v) { return 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,29 @@
<?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.
*
* @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,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\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 implements AuthenticatorFactoryInterface
{
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 getPosition(): string
{
return 'form';
}
public function getKey(): string
{
return 'form-login';
}
public function addConfiguration(NodeDefinition $node)
{
parent::addConfiguration($node);
$node
->children()
->scalarNode('csrf_token_generator')->cannotBeEmpty()->end()
->end()
;
}
protected function getListenerId(): string
{
return 'security.authentication.listener.form';
}
protected function createAuthProvider(ContainerBuilder $container, string $id, array $config, string $userProviderId): string
{
if ($config['enable_csrf'] ?? false) {
throw new InvalidConfigurationException('The "enable_csrf" option of "form_login" is only available when "security.enable_authenticator_manager" is set to "true", use "csrf_token_generator" instead.');
}
$provider = 'security.authentication.provider.dao.'.$id;
$container
->setDefinition($provider, new ChildDefinition('security.authentication.provider.dao'))
->replaceArgument(0, new Reference($userProviderId))
->replaceArgument(1, new Reference('security.user_checker.'.$id))
->replaceArgument(2, $id)
;
return $provider;
}
protected function createListener(ContainerBuilder $container, string $id, array $config, string $userProvider)
{
$listenerId = parent::createListener($container, $id, $config, $userProvider);
$container
->getDefinition($listenerId)
->addArgument(isset($config['csrf_token_generator']) ? new Reference($config['csrf_token_generator']) : null)
;
return $listenerId;
}
protected function createEntryPoint(ContainerBuilder $container, string $id, array $config, ?string $defaultEntryPointId): ?string
{
$entryPointId = 'security.authentication.form_entry_point.'.$id;
$container
->setDefinition($entryPointId, new ChildDefinition('security.authentication.form_entry_point'))
->addArgument(new Reference('security.http_utils'))
->addArgument($config['login_path'])
->addArgument($config['use_forward'])
;
return $entryPointId;
}
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" option of "form_login" is only available when "security.enable_authenticator_manager" is set to "false", 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,70 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\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;
/**
* 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;
protected function createAuthProvider(ContainerBuilder $container, string $id, array $config, string $userProviderId): string
{
$provider = 'security.authentication.provider.ldap_bind.'.$id;
$definition = $container
->setDefinition($provider, new ChildDefinition('security.authentication.provider.ldap_bind'))
->replaceArgument(0, new Reference($userProviderId))
->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'])
;
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']]);
}
return $provider;
}
public function addConfiguration(NodeDefinition $node)
{
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,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\NodeDefinition;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Security\Guard\Authenticator\GuardBridgeAuthenticator;
/**
* Configures the "guard" authentication provider key under a firewall.
*
* @author Ryan Weaver <ryan@knpuniversity.com>
*
* @internal
*/
class GuardAuthenticationFactory implements SecurityFactoryInterface, AuthenticatorFactoryInterface
{
public function getPosition(): string
{
return 'pre_auth';
}
public function getPriority(): int
{
return 0;
}
public function getKey(): string
{
return 'guard';
}
public function addConfiguration(NodeDefinition $node)
{
$node
->fixXmlConfig('authenticator')
->children()
->scalarNode('provider')
->info('A key from the "providers" section of your security config, in case your user provider is different than the firewall')
->end()
->scalarNode('entry_point')
->info('A service id (of one of your authenticators) whose start() method should be called when an anonymous user hits a page that requires authentication')
->defaultValue(null)
->end()
->arrayNode('authenticators')
->info('An array of service ids for all of your "authenticators"')
->requiresAtLeastOneElement()
->prototype('scalar')->end()
->end()
->end()
;
}
public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint): array
{
$authenticatorIds = $config['authenticators'];
$authenticatorReferences = [];
foreach ($authenticatorIds as $authenticatorId) {
$authenticatorReferences[] = new Reference($authenticatorId);
}
$authenticators = new IteratorArgument($authenticatorReferences);
// configure the GuardAuthenticationFactory to have the dynamic constructor arguments
$providerId = 'security.authentication.provider.guard.'.$id;
$container
->setDefinition($providerId, new ChildDefinition('security.authentication.provider.guard'))
->replaceArgument(0, $authenticators)
->replaceArgument(1, new Reference($userProvider))
->replaceArgument(2, $id)
->replaceArgument(3, new Reference('security.user_checker.'.$id))
;
// listener
$listenerId = 'security.authentication.listener.guard.'.$id;
$listener = $container->setDefinition($listenerId, new ChildDefinition('security.authentication.listener.guard'));
$listener->replaceArgument(2, $id);
$listener->replaceArgument(3, $authenticators);
// determine the entryPointId to use
$entryPointId = $this->determineEntryPoint($defaultEntryPoint, $config);
// this is always injected - then the listener decides if it should be used
$container
->getDefinition($listenerId)
->addTag('security.remember_me_aware', ['id' => $id, 'provider' => $userProvider]);
return [$providerId, $listenerId, $entryPointId];
}
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId)
{
$userProvider = new Reference($userProviderId);
$authenticatorIds = [];
if (isset($config['entry_point'])) {
throw new InvalidConfigurationException('The "security.firewall.'.$firewallName.'.guard.entry_point" option has no effect in the new authenticator system, configure "security.firewall.'.$firewallName.'.entry_point" instead.');
}
$guardAuthenticatorIds = $config['authenticators'];
foreach ($guardAuthenticatorIds as $i => $guardAuthenticatorId) {
$container->setDefinition($authenticatorIds[] = 'security.authenticator.guard.'.$firewallName.'.'.$i, new Definition(GuardBridgeAuthenticator::class))
->setArguments([
new Reference($guardAuthenticatorId),
$userProvider,
]);
}
return $authenticatorIds;
}
private function determineEntryPoint(?string $defaultEntryPointId, array $config): string
{
if ($defaultEntryPointId) {
// explode if they've configured the entry_point, but there is already one
if ($config['entry_point']) {
throw new \LogicException(sprintf('The guard authentication provider cannot use the "%s" entry_point because another entry point is already configured by another provider! Either remove the other provider or move the entry_point configuration as a root key under your firewall (i.e. at the same level as "guard").', $config['entry_point']));
}
return $defaultEntryPointId;
}
if ($config['entry_point']) {
// if it's configured explicitly, use it!
return $config['entry_point'];
}
$authenticatorIds = $config['authenticators'];
if (1 == \count($authenticatorIds)) {
// if there is only one authenticator, use that as the entry point
return array_shift($authenticatorIds);
}
// we have multiple entry points - we must ask them to configure one
throw new \LogicException(sprintf('Because you have multiple guard authenticators, you need to set the "guard.entry_point" key to one of your authenticators (%s).', implode(', ', $authenticatorIds)));
}
}

View File

@ -0,0 +1,95 @@
<?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 SecurityFactoryInterface, AuthenticatorFactoryInterface
{
public const PRIORITY = -50;
public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint): array
{
$provider = 'security.authentication.provider.dao.'.$id;
$container
->setDefinition($provider, new ChildDefinition('security.authentication.provider.dao'))
->replaceArgument(0, new Reference($userProvider))
->replaceArgument(1, new Reference('security.user_checker.'.$id))
->replaceArgument(2, $id)
;
// 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'])
;
}
// 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));
$listener->addMethodCall('setSessionAuthenticationStrategy', [new Reference('security.authentication.session_strategy.'.$id)]);
return [$provider, $listenerId, $entryPointId];
}
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 getPosition(): string
{
return 'http';
}
public function getKey(): string
{
return 'http-basic';
}
public function addConfiguration(NodeDefinition $node)
{
$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)
{
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,122 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\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 implements AuthenticatorFactoryInterface
{
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;
}
/**
* {@inheritdoc}
*/
public function getPosition(): string
{
return 'form';
}
/**
* {@inheritdoc}
*/
public function getKey(): string
{
return 'json-login';
}
/**
* {@inheritdoc}
*/
protected function createAuthProvider(ContainerBuilder $container, string $id, array $config, string $userProviderId): string
{
$provider = 'security.authentication.provider.dao.'.$id;
$container
->setDefinition($provider, new ChildDefinition('security.authentication.provider.dao'))
->replaceArgument(0, new Reference($userProviderId))
->replaceArgument(1, new Reference('security.user_checker.'.$id))
->replaceArgument(2, $id)
;
return $provider;
}
/**
* {@inheritdoc}
*/
protected function getListenerId(): string
{
return 'security.authentication.listener.json';
}
/**
* {@inheritdoc}
*/
protected function isRememberMeAware(array $config): bool
{
return false;
}
/**
* {@inheritdoc}
*/
protected function createListener(ContainerBuilder $container, string $id, array $config, string $userProvider)
{
$listenerId = $this->getListenerId();
$listener = new ChildDefinition($listenerId);
$listener->replaceArgument(3, $id);
$listener->replaceArgument(4, isset($config['success_handler']) ? new Reference($this->createAuthenticationSuccessHandler($container, $id, $config)) : null);
$listener->replaceArgument(5, isset($config['failure_handler']) ? new Reference($this->createAuthenticationFailureHandler($container, $id, $config)) : null);
$listener->replaceArgument(6, array_intersect_key($config, $this->options));
$listener->addMethodCall('setSessionAuthenticationStrategy', [new Reference('security.authentication.session_strategy.'.$id)]);
$listenerId .= '.'.$id;
$container->setDefinition($listenerId, $listener);
return $listenerId;
}
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId)
{
$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,67 @@
<?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;
/**
* JsonLoginLdapFactory creates services for json login ldap authentication.
*
* @internal
*/
class JsonLoginLdapFactory extends JsonLoginFactory
{
use LdapFactoryTrait;
protected function createAuthProvider(ContainerBuilder $container, string $id, array $config, string $userProviderId): string
{
$provider = 'security.authentication.provider.ldap_bind.'.$id;
$definition = $container
->setDefinition($provider, new ChildDefinition('security.authentication.provider.ldap_bind'))
->replaceArgument(0, new Reference($userProviderId))
->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'])
;
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']]);
}
return $provider;
}
public function addConfiguration(NodeDefinition $node)
{
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,69 @@
<?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());
if (!class_exists(LdapAuthenticator::class)) {
throw new \LogicException(sprintf('The "%s" authenticator requires the "symfony/ldap" package version "5.1" or higher.', $key));
}
$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,181 @@
<?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;
use Symfony\Component\Security\Http\LoginLink\LoginLinkHandler;
/**
* @internal
*/
class LoginLinkFactory extends AbstractFactory implements AuthenticatorFactoryInterface
{
public const PRIORITY = -20;
public function addConfiguration(NodeDefinition $node)
{
/** @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 (!class_exists(LoginLinkHandler::class)) {
throw new \LogicException('Login login link requires symfony/security-http:^5.2.');
}
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;
}
public function getPosition(): string
{
return 'form';
}
protected function createAuthProvider(ContainerBuilder $container, string $id, array $config, string $userProviderId): string
{
throw new \Exception('The old authentication system is not supported with login_link.');
}
protected function getListenerId(): string
{
throw new \Exception('The old authentication system is not supported with login_link.');
}
protected function createListener(ContainerBuilder $container, string $id, array $config, string $userProvider)
{
throw new \Exception('The old authentication system is not supported with login_link.');
}
protected function createEntryPoint(ContainerBuilder $container, string $id, array $config, ?string $defaultEntryPointId): ?string
{
throw new \Exception('The old authentication system is not supported with login_link.');
}
}

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\DependencyInjection\Security\Factory;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension;
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\Reference;
use Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface;
use Symfony\Component\RateLimiter\RateLimiterFactory;
use Symfony\Component\Security\Http\EventListener\LoginThrottlingListener;
use Symfony\Component\Security\Http\RateLimiter\DefaultLoginRateLimiter;
/**
* @author Wouter de Jong <wouter@wouterj.nl>
*
* @internal
*/
class LoginThrottlingFactory implements AuthenticatorFactoryInterface, SecurityFactoryInterface
{
public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint): array
{
throw new \LogicException('Login throttling is not supported when "security.enable_authenticator_manager" is not set to true.');
}
public function getPriority(): int
{
// this factory doesn't register any authenticators, this priority doesn't matter
return 0;
}
public function getPosition(): string
{
// this factory doesn't register any authenticators, this position doesn't matter
return 'pre_auth';
}
public function getKey(): string
{
return 'login_throttling';
}
/**
* @param ArrayNodeDefinition $builder
*/
public function addConfiguration(NodeDefinition $builder)
{
$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(LoginThrottlingListener::class)) {
throw new \LogicException('Login throttling requires symfony/security-http:^5.2.');
}
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'])) {
if (!class_exists(FrameworkExtension::class) || !method_exists(FrameworkExtension::class, 'registerRateLimiter')) {
throw new \LogicException('You must either configure a rate limiter for "security.firewalls.'.$firewallName.'.login_throttling" or install symfony/framework-bundle:^5.2.');
}
$limiterOptions = [
'policy' => 'fixed_window',
'limit' => $config['max_attempts'],
'interval' => $config['interval'],
'lock_factory' => $config['lock_factory'],
];
FrameworkExtension::registerRateLimiter($container, $localId = '_login_local_'.$firewallName, $limiterOptions);
$limiterOptions['limit'] = 5 * $config['max_attempts'];
FrameworkExtension::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))
;
}
$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 [];
}
}

View File

@ -0,0 +1,374 @@
<?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\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
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;
use Symfony\Component\Security\Http\EventListener\RememberMeLogoutListener;
/**
* @internal
*/
class RememberMeFactory implements SecurityFactoryInterface, 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 create(ContainerBuilder $container, string $id, array $config, ?string $userProvider, ?string $defaultEntryPoint): array
{
// authentication provider
$authProviderId = 'security.authentication.provider.rememberme.'.$id;
$container
->setDefinition($authProviderId, new ChildDefinition('security.authentication.provider.rememberme'))
->replaceArgument(0, new Reference('security.user_checker.'.$id))
->addArgument($config['secret'])
->addArgument($id)
;
// remember me services
$templateId = $this->generateRememberMeServicesTemplateId($config, $id);
$rememberMeServicesId = $templateId.'.'.$id;
// attach to remember-me aware listeners
$userProviders = [];
foreach ($container->findTaggedServiceIds('security.remember_me_aware') as $serviceId => $attributes) {
foreach ($attributes as $attribute) {
if (!isset($attribute['id']) || $attribute['id'] !== $id) {
continue;
}
if (!isset($attribute['provider'])) {
throw new \RuntimeException('Each "security.remember_me_aware" tag must have a provider attribute.');
}
// context listeners don't need a provider
if ('none' !== $attribute['provider']) {
$userProviders[] = new Reference($attribute['provider']);
}
$container
->getDefinition($serviceId)
->addMethodCall('setRememberMeServices', [new Reference($rememberMeServicesId)])
;
}
}
$this->createRememberMeServices($container, $id, $templateId, $userProviders, $config);
// remember-me listener
$listenerId = 'security.authentication.listener.rememberme.'.$id;
$listener = $container->setDefinition($listenerId, new ChildDefinition('security.authentication.listener.rememberme'));
$listener->replaceArgument(1, new Reference($rememberMeServicesId));
$listener->replaceArgument(5, $config['catch_exceptions']);
// remember-me logout listener
$container->setDefinition('security.logout.listener.remember_me.'.$id, new Definition(RememberMeLogoutListener::class))
->addArgument(new Reference($rememberMeServicesId))
->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.'.$id]);
return [$authProviderId, $listenerId, $defaultEntryPoint];
}
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, $config['secret'])
->replaceArgument(2, new Reference($userProviderId))
->replaceArgument(4, $config)
->replaceArgument(6, $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'])
;
foreach ($container->findTaggedServiceIds('security.remember_me_aware') as $serviceId => $attributes) {
// register ContextListener
if ('security.context_listener' === substr($serviceId, 0, 25)) {
continue;
}
throw new \LogicException(sprintf('Symfony Authenticator Security dropped support for the "security.remember_me_aware" tag, service "%s" will no longer work as expected.', $serviceId));
}
return $authenticatorId;
}
public function getPosition(): string
{
return 'remember_me';
}
/**
* {@inheritDoc}
*/
public function getPriority(): int
{
return self::PRIORITY;
}
public function getKey(): string
{
return 'remember-me';
}
public function addConfiguration(NodeDefinition $node)
{
$builder = $node
->fixXmlConfig('user_provider')
->children()
;
$builder
->scalarNode('secret')
->cannotBeEmpty()
->defaultValue('%kernel.secret%')
->end()
->scalarNode('service')->end()
->arrayNode('user_providers')
->beforeNormalization()
->ifString()->then(function ($v) { return [$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(function ($v) { return ['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 generateRememberMeServicesTemplateId(array $config, string $id): string
{
if (isset($config['service'])) {
return $config['service'];
}
if (isset($config['token_provider'])) {
return 'security.authentication.rememberme.services.persistent';
}
return 'security.authentication.rememberme.services.simplehash';
}
private function createRememberMeServices(ContainerBuilder $container, string $id, string $templateId, array $userProviders, array $config): void
{
$rememberMeServicesId = $templateId.'.'.$id;
$rememberMeServices = $container->setDefinition($rememberMeServicesId, new ChildDefinition($templateId));
$rememberMeServices->replaceArgument(1, $config['secret']);
$rememberMeServices->replaceArgument(2, $id);
if (isset($config['token_provider'])) {
$tokenProviderId = $this->createTokenProvider($container, $id, $config['token_provider']);
$rememberMeServices->addMethodCall('setTokenProvider', [new Reference($tokenProviderId)]);
}
// remember-me options
$mergedOptions = array_intersect_key($config, $this->options);
if ('auto' === $mergedOptions['secure']) {
$mergedOptions['secure'] = null;
}
$rememberMeServices->replaceArgument(3, $mergedOptions);
if ($config['user_providers']) {
$userProviders = [];
foreach ($config['user_providers'] as $providerName) {
$userProviders[] = new Reference('security.user.provider.concrete.'.$providerName);
}
}
if (0 === \count($userProviders)) {
throw new \RuntimeException('You must configure at least one remember-me aware listener (such as form-login) for each firewall that has remember-me enabled.');
}
$rememberMeServices->replaceArgument(0, new IteratorArgument(array_unique($userProviders)));
}
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);
}
/**
* {@inheritdoc}
*/
public function prepend(ContainerBuilder $container)
{
$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,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;
/**
* 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 SecurityFactoryInterface, AuthenticatorFactoryInterface
{
public const PRIORITY = -10;
public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint): array
{
$providerId = 'security.authentication.provider.pre_authenticated.'.$id;
$container
->setDefinition($providerId, new ChildDefinition('security.authentication.provider.pre_authenticated'))
->replaceArgument(0, new Reference($userProvider))
->replaceArgument(1, new Reference('security.user_checker.'.$id))
->addArgument($id)
;
$listenerId = 'security.authentication.listener.remote_user.'.$id;
$listener = $container->setDefinition($listenerId, new ChildDefinition('security.authentication.listener.remote_user'));
$listener->replaceArgument(2, $id);
$listener->replaceArgument(3, $config['user']);
$listener->addMethodCall('setSessionAuthenticationStrategy', [new Reference('security.authentication.session_strategy.'.$id)]);
return [$providerId, $listenerId, $defaultEntryPoint];
}
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId)
{
$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 getPosition(): string
{
return 'pre_auth';
}
public function getKey(): string
{
return 'remote-user';
}
public function addConfiguration(NodeDefinition $node)
{
$node
->children()
->scalarNode('provider')->end()
->scalarNode('user')->defaultValue('REMOTE_USER')->end()
->end()
;
}
}

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\Security\Factory;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* SecurityFactoryInterface is the interface for all security authentication listener.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since Symfony 5.3, use AuthenticatorFactoryInterface instead.
*/
interface SecurityFactoryInterface
{
/**
* Configures the container services required to use the authentication listener.
*
* @return array containing three values:
* - the provider id
* - the listener id
* - the entry point id
*/
public function create(ContainerBuilder $container, string $id, array $config, string $userProviderId, ?string $defaultEntryPointId);
/**
* Defines the position at which the provider is called.
* Possible values: pre_auth, form, http, and remember_me.
*
* @return string
*/
public function getPosition();
/**
* Defines the configuration key used to reference the provider
* in the firewall configuration.
*
* @return string
*/
public function getKey();
public function addConfiguration(NodeDefinition $builder);
}

View File

@ -0,0 +1,90 @@
<?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 SecurityFactoryInterface, AuthenticatorFactoryInterface
{
public const PRIORITY = -10;
public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint): array
{
$providerId = 'security.authentication.provider.pre_authenticated.'.$id;
$container
->setDefinition($providerId, new ChildDefinition('security.authentication.provider.pre_authenticated'))
->replaceArgument(0, new Reference($userProvider))
->replaceArgument(1, new Reference('security.user_checker.'.$id))
->addArgument($id)
;
// listener
$listenerId = 'security.authentication.listener.x509.'.$id;
$listener = $container->setDefinition($listenerId, new ChildDefinition('security.authentication.listener.x509'));
$listener->replaceArgument(2, $id);
$listener->replaceArgument(3, $config['user']);
$listener->replaceArgument(4, $config['credentials']);
$listener->addMethodCall('setSessionAuthenticationStrategy', [new Reference('security.authentication.session_strategy.'.$id)]);
return [$providerId, $listenerId, $defaultEntryPoint];
}
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId)
{
$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'])
;
return $authenticatorId;
}
public function getPriority(): int
{
return self::PRIORITY;
}
public function getPosition(): string
{
return 'pre_auth';
}
public function getKey(): string
{
return 'x509';
}
public function addConfiguration(NodeDefinition $node)
{
$node
->children()
->scalarNode('provider')->end()
->scalarNode('user')->defaultValue('SSL_CLIENT_S_DN_Email')->end()
->scalarNode('credentials')->defaultValue('SSL_CLIENT_S_DN')->end()
->end()
;
}
}

View File

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

View File

@ -0,0 +1,72 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* LdapFactory creates services for Ldap user provider.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
* @author Charles Sarrazin <charles@sarraz.in>
*/
class LdapFactory implements UserProviderFactoryInterface
{
public function create(ContainerBuilder $container, string $id, array $config)
{
$container
->setDefinition($id, new ChildDefinition('security.user.provider.ldap'))
->replaceArgument(0, new Reference($config['service']))
->replaceArgument(1, $config['base_dn'])
->replaceArgument(2, $config['search_dn'])
->replaceArgument(3, $config['search_password'])
->replaceArgument(4, $config['default_roles'])
->replaceArgument(5, $config['uid_key'])
->replaceArgument(6, $config['filter'])
->replaceArgument(7, $config['password_attribute'])
->replaceArgument(8, $config['extra_fields'])
;
}
public function getKey()
{
return 'ldap';
}
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(function ($v) { return 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,30 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* UserProviderFactoryInterface is the interface for all user provider factories.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Christophe Coevoet <stof@notk.org>
*/
interface UserProviderFactoryInterface
{
public function create(ContainerBuilder $container, string $id, array $config);
public function getKey();
public function addConfiguration(NodeDefinition $builder);
}

View File

@ -0,0 +1,1210 @@
<?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 Composer\InstalledVersions;
use Symfony\Bridge\Twig\Extension\LogoutUrlExtension;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FirewallListenerFactoryInterface;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface;
use Symfony\Bundle\SecurityBundle\Security\LegacyLogoutHandlerListener;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Console\Application;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher;
use Symfony\Component\PasswordHasher\Hasher\Pbkdf2PasswordHasher;
use Symfony\Component\PasswordHasher\Hasher\PlaintextPasswordHasher;
use Symfony\Component\PasswordHasher\Hasher\SodiumPasswordHasher;
use Symfony\Component\Security\Core\Authorization\Strategy\AffirmativeStrategy;
use Symfony\Component\Security\Core\Authorization\Strategy\ConsensusStrategy;
use Symfony\Component\Security\Core\Authorization\Strategy\PriorityStrategy;
use Symfony\Component\Security\Core\Authorization\Strategy\UnanimousStrategy;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder;
use Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder;
use Symfony\Component\Security\Core\User\ChainUserProvider;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticatorManagerListener;
use Symfony\Component\Security\Http\Event\CheckPassportEvent;
/**
* SecurityExtension.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class SecurityExtension extends Extension implements PrependExtensionInterface
{
private $requestMatchers = [];
private $expressions = [];
private $contextListeners = [];
/** @var list<array{int, AuthenticatorFactoryInterface|SecurityFactoryInterface}> */
private $factories = [];
/** @var list<AuthenticatorFactoryInterface|SecurityFactoryInterface> */
private $sortedFactories = [];
private $userProviderFactories = [];
private $statelessFirewallKeys = [];
private $authenticatorManagerEnabled = false;
public function prepend(ContainerBuilder $container)
{
foreach ($this->getSortedFactories() as $factory) {
if ($factory instanceof PrependExtensionInterface) {
$factory->prepend($container);
}
}
}
public function load(array $configs, ContainerBuilder $container)
{
if (!class_exists(InstalledVersions::class)) {
trigger_deprecation('symfony/security-bundle', '5.4', 'Configuring Symfony without the Composer Runtime API is deprecated. Consider upgrading to Composer 2.1 or later.');
}
if (!array_filter($configs)) {
return;
}
$mainConfig = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($mainConfig, $configs);
// load services
$loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__).'/Resources/config'));
$loader->load('security.php');
$loader->load('password_hasher.php');
$loader->load('security_listeners.php');
$loader->load('security_rememberme.php');
if ($this->authenticatorManagerEnabled = $config['enable_authenticator_manager']) {
if ($config['always_authenticate_before_granting']) {
throw new InvalidConfigurationException('The security option "always_authenticate_before_granting" cannot be used when "enable_authenticator_manager" is set to true. If you rely on this behavior, set it to false.');
}
$loader->load('security_authenticator.php');
// The authenticator system no longer has anonymous tokens. This makes sure AccessListener
// and AuthorizationChecker do not throw AuthenticationCredentialsNotFoundException when no
// token is available in the token storage.
$container->getDefinition('security.access_listener')->setArgument(3, false);
$container->getDefinition('security.authorization_checker')->setArgument(3, false);
$container->getDefinition('security.authorization_checker')->setArgument(4, false);
} else {
trigger_deprecation('symfony/security-bundle', '5.3', 'Not setting the "security.enable_authenticator_manager" config option to true is deprecated.');
if ($config['always_authenticate_before_granting']) {
$authorizationChecker = $container->getDefinition('security.authorization_checker');
$authorizationCheckerArgs = $authorizationChecker->getArguments();
array_splice($authorizationCheckerArgs, 1, 0, [new Reference('security.authentication.manager')]);
$authorizationChecker->setArguments($authorizationCheckerArgs);
}
$loader->load('security_legacy.php');
}
if ($container::willBeAvailable('symfony/twig-bridge', LogoutUrlExtension::class, ['symfony/security-bundle'], true)) {
$loader->load('templating_twig.php');
}
$loader->load('collectors.php');
$loader->load('guard.php');
$container->getDefinition('data_collector.security')->addArgument($this->authenticatorManagerEnabled);
if ($container->hasParameter('kernel.debug') && $container->getParameter('kernel.debug')) {
$loader->load('security_debug.php');
}
if (!$container::willBeAvailable('symfony/expression-language', ExpressionLanguage::class, ['symfony/security-bundle'], true)) {
$container->removeDefinition('security.expression_language');
$container->removeDefinition('security.access.expression_voter');
}
// set some global scalars
$container->setParameter('security.access.denied_url', $config['access_denied_url']);
$container->setParameter('security.authentication.manager.erase_credentials', $config['erase_credentials']);
$container->setParameter('security.authentication.session_strategy.strategy', $config['session_fixation_strategy']);
if (isset($config['access_decision_manager']['service'])) {
$container->setAlias('security.access.decision_manager', $config['access_decision_manager']['service']);
} elseif (isset($config['access_decision_manager']['strategy_service'])) {
$container
->getDefinition('security.access.decision_manager')
->addArgument(new Reference($config['access_decision_manager']['strategy_service']));
} else {
$container
->getDefinition('security.access.decision_manager')
->addArgument($this->createStrategyDefinition(
$config['access_decision_manager']['strategy'] ?? MainConfiguration::STRATEGY_AFFIRMATIVE,
$config['access_decision_manager']['allow_if_all_abstain'],
$config['access_decision_manager']['allow_if_equal_granted_denied']
));
}
$container->setParameter('security.access.always_authenticate_before_granting', $config['always_authenticate_before_granting']);
$container->setParameter('security.authentication.hide_user_not_found', $config['hide_user_not_found']);
if (class_exists(Application::class)) {
$loader->load('debug_console.php');
$debugCommand = $container->getDefinition('security.command.debug_firewall');
$debugCommand->replaceArgument(4, $this->authenticatorManagerEnabled);
}
$this->createFirewalls($config, $container);
$this->createAuthorization($config, $container);
$this->createRoleHierarchy($config, $container);
$container->getDefinition('security.authentication.guard_handler')
->replaceArgument(2, $this->statelessFirewallKeys);
// @deprecated since Symfony 5.3
if ($config['encoders']) {
$this->createEncoders($config['encoders'], $container);
}
if ($config['password_hashers']) {
$this->createHashers($config['password_hashers'], $container);
}
if (class_exists(Application::class)) {
$loader->load('console.php');
// @deprecated since Symfony 5.3
$container->getDefinition('security.command.user_password_encoder')->replaceArgument(1, array_keys($config['encoders']));
$container->getDefinition('security.command.user_password_hash')->replaceArgument(1, array_keys($config['password_hashers']));
}
$container->registerForAutoconfiguration(VoterInterface::class)
->addTag('security.voter');
}
/**
* @throws \InvalidArgumentException if the $strategy is invalid
*/
private function createStrategyDefinition(string $strategy, bool $allowIfAllAbstainDecisions, bool $allowIfEqualGrantedDeniedDecisions): Definition
{
switch ($strategy) {
case MainConfiguration::STRATEGY_AFFIRMATIVE:
return new Definition(AffirmativeStrategy::class, [$allowIfAllAbstainDecisions]);
case MainConfiguration::STRATEGY_CONSENSUS:
return new Definition(ConsensusStrategy::class, [$allowIfAllAbstainDecisions, $allowIfEqualGrantedDeniedDecisions]);
case MainConfiguration::STRATEGY_UNANIMOUS:
return new Definition(UnanimousStrategy::class, [$allowIfAllAbstainDecisions]);
case MainConfiguration::STRATEGY_PRIORITY:
return new Definition(PriorityStrategy::class, [$allowIfAllAbstainDecisions]);
}
throw new \InvalidArgumentException(sprintf('The strategy "%s" is not supported.', $strategy));
}
private function createRoleHierarchy(array $config, ContainerBuilder $container)
{
if (!isset($config['role_hierarchy']) || 0 === \count($config['role_hierarchy'])) {
$container->removeDefinition('security.access.role_hierarchy_voter');
return;
}
$container->setParameter('security.role_hierarchy.roles', $config['role_hierarchy']);
$container->removeDefinition('security.access.simple_role_voter');
}
private function createAuthorization(array $config, ContainerBuilder $container)
{
foreach ($config['access_control'] as $access) {
$matcher = $this->createRequestMatcher(
$container,
$access['path'],
$access['host'],
$access['port'],
$access['methods'],
$access['ips']
);
$attributes = $access['roles'];
if ($access['allow_if']) {
$attributes[] = $this->createExpression($container, $access['allow_if']);
}
$emptyAccess = 0 === \count(array_filter($access));
if ($emptyAccess) {
throw new InvalidConfigurationException('One or more access control items are empty. Did you accidentally add lines only containing a "-" under "security.access_control"?');
}
$container->getDefinition('security.access_map')
->addMethodCall('add', [$matcher, $attributes, $access['requires_channel']]);
}
// allow cache warm-up for expressions
if (\count($this->expressions)) {
$container->getDefinition('security.cache_warmer.expression')
->replaceArgument(0, new IteratorArgument(array_values($this->expressions)));
} else {
$container->removeDefinition('security.cache_warmer.expression');
}
}
private function createFirewalls(array $config, ContainerBuilder $container)
{
if (!isset($config['firewalls'])) {
return;
}
$firewalls = $config['firewalls'];
$providerIds = $this->createUserProviders($config, $container);
$container->setParameter('security.firewalls', array_keys($firewalls));
// make the ContextListener aware of the configured user providers
$contextListenerDefinition = $container->getDefinition('security.context_listener');
$arguments = $contextListenerDefinition->getArguments();
$userProviders = [];
foreach ($providerIds as $userProviderId) {
$userProviders[] = new Reference($userProviderId);
}
$arguments[1] = $userProviderIteratorsArgument = new IteratorArgument($userProviders);
$contextListenerDefinition->setArguments($arguments);
$nbUserProviders = \count($userProviders);
if ($nbUserProviders > 1) {
$container->setDefinition('security.user_providers', new Definition(ChainUserProvider::class, [$userProviderIteratorsArgument]))
->setPublic(false);
} elseif (0 === $nbUserProviders) {
$container->removeDefinition('security.listener.user_provider');
} else {
$container->setAlias('security.user_providers', new Alias(current($providerIds)))->setPublic(false);
}
if (1 === \count($providerIds)) {
$container->setAlias(UserProviderInterface::class, current($providerIds));
}
$customUserChecker = false;
// load firewall map
$mapDef = $container->getDefinition('security.firewall.map');
$map = $authenticationProviders = $contextRefs = [];
foreach ($firewalls as $name => $firewall) {
if (isset($firewall['user_checker']) && 'security.user_checker' !== $firewall['user_checker']) {
$customUserChecker = true;
}
$configId = 'security.firewall.map.config.'.$name;
[$matcher, $listeners, $exceptionListener, $logoutListener] = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds, $configId);
$contextId = 'security.firewall.map.context.'.$name;
$isLazy = !$firewall['stateless'] && (!empty($firewall['anonymous']['lazy']) || $firewall['lazy']);
$context = new ChildDefinition($isLazy ? 'security.firewall.lazy_context' : 'security.firewall.context');
$context = $container->setDefinition($contextId, $context);
$context
->replaceArgument(0, new IteratorArgument($listeners))
->replaceArgument(1, $exceptionListener)
->replaceArgument(2, $logoutListener)
->replaceArgument(3, new Reference($configId))
;
$contextRefs[$contextId] = new Reference($contextId);
$map[$contextId] = $matcher;
}
$container->setAlias('security.firewall.context_locator', (string) ServiceLocatorTagPass::register($container, $contextRefs));
$mapDef->replaceArgument(0, new Reference('security.firewall.context_locator'));
$mapDef->replaceArgument(1, new IteratorArgument($map));
if (!$this->authenticatorManagerEnabled) {
// add authentication providers to authentication manager
$authenticationProviders = array_map(function ($id) {
return new Reference($id);
}, array_values(array_unique($authenticationProviders)));
$container
->getDefinition('security.authentication.manager')
->replaceArgument(0, new IteratorArgument($authenticationProviders));
}
// register an autowire alias for the UserCheckerInterface if no custom user checker service is configured
if (!$customUserChecker) {
$container->setAlias('Symfony\Component\Security\Core\User\UserCheckerInterface', new Alias('security.user_checker', false));
}
}
private function createFirewall(ContainerBuilder $container, string $id, array $firewall, array &$authenticationProviders, array $providerIds, string $configId)
{
$config = $container->setDefinition($configId, new ChildDefinition('security.firewall.config'));
$config->replaceArgument(0, $id);
$config->replaceArgument(1, $firewall['user_checker']);
// Matcher
$matcher = null;
if (isset($firewall['request_matcher'])) {
$matcher = new Reference($firewall['request_matcher']);
} elseif (isset($firewall['pattern']) || isset($firewall['host'])) {
$pattern = $firewall['pattern'] ?? null;
$host = $firewall['host'] ?? null;
$methods = $firewall['methods'] ?? [];
$matcher = $this->createRequestMatcher($container, $pattern, $host, null, $methods);
}
$config->replaceArgument(2, $matcher ? (string) $matcher : null);
$config->replaceArgument(3, $firewall['security']);
// Security disabled?
if (false === $firewall['security']) {
return [$matcher, [], null, null];
}
$config->replaceArgument(4, $firewall['stateless']);
$firewallEventDispatcherId = 'security.event_dispatcher.'.$id;
// Provider id (must be configured explicitly per firewall/authenticator if more than one provider is set)
$defaultProvider = null;
if (isset($firewall['provider'])) {
if (!isset($providerIds[$normalizedName = str_replace('-', '_', $firewall['provider'])])) {
throw new InvalidConfigurationException(sprintf('Invalid firewall "%s": user provider "%s" not found.', $id, $firewall['provider']));
}
$defaultProvider = $providerIds[$normalizedName];
if ($this->authenticatorManagerEnabled) {
$container->setDefinition('security.listener.'.$id.'.user_provider', new ChildDefinition('security.listener.user_provider.abstract'))
->addTag('kernel.event_listener', ['dispatcher' => $firewallEventDispatcherId, 'event' => CheckPassportEvent::class, 'priority' => 2048, 'method' => 'checkPassport'])
->replaceArgument(0, new Reference($defaultProvider));
}
} elseif (1 === \count($providerIds)) {
$defaultProvider = reset($providerIds);
}
$config->replaceArgument(5, $defaultProvider);
// Register Firewall-specific event dispatcher
$container->register($firewallEventDispatcherId, EventDispatcher::class)
->addTag('event_dispatcher.dispatcher', ['name' => $firewallEventDispatcherId]);
// Register listeners
$listeners = [];
$listenerKeys = [];
// Channel listener
$listeners[] = new Reference('security.channel_listener');
$contextKey = null;
$contextListenerId = null;
// Context serializer listener
if (false === $firewall['stateless']) {
$contextKey = $firewall['context'] ?? $id;
$listeners[] = new Reference($contextListenerId = $this->createContextListener($container, $contextKey, $this->authenticatorManagerEnabled ? $firewallEventDispatcherId : null));
$sessionStrategyId = 'security.authentication.session_strategy';
if ($this->authenticatorManagerEnabled) {
$container
->setDefinition('security.listener.session.'.$id, new ChildDefinition('security.listener.session'))
->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
}
} else {
$this->statelessFirewallKeys[] = $id;
$sessionStrategyId = 'security.authentication.session_strategy_noop';
}
$container->setAlias(new Alias('security.authentication.session_strategy.'.$id, false), $sessionStrategyId);
$config->replaceArgument(6, $contextKey);
// Logout listener
$logoutListenerId = null;
if (isset($firewall['logout'])) {
$logoutListenerId = 'security.logout_listener.'.$id;
$logoutListener = $container->setDefinition($logoutListenerId, new ChildDefinition('security.logout_listener'));
$logoutListener->replaceArgument(2, new Reference($firewallEventDispatcherId));
$logoutListener->replaceArgument(3, [
'csrf_parameter' => $firewall['logout']['csrf_parameter'],
'csrf_token_id' => $firewall['logout']['csrf_token_id'],
'logout_path' => $firewall['logout']['path'],
]);
// add default logout listener
if (isset($firewall['logout']['success_handler'])) {
// deprecated, to be removed in Symfony 6.0
$logoutSuccessHandlerId = $firewall['logout']['success_handler'];
$container->register('security.logout.listener.legacy_success_listener.'.$id, LegacyLogoutHandlerListener::class)
->setArguments([new Reference($logoutSuccessHandlerId)])
->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
} else {
$logoutSuccessListenerId = 'security.logout.listener.default.'.$id;
$container->setDefinition($logoutSuccessListenerId, new ChildDefinition('security.logout.listener.default'))
->replaceArgument(1, $firewall['logout']['target'])
->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
}
// add CSRF provider
if (isset($firewall['logout']['csrf_token_generator'])) {
$logoutListener->addArgument(new Reference($firewall['logout']['csrf_token_generator']));
}
// add session logout listener
if (true === $firewall['logout']['invalidate_session'] && false === $firewall['stateless']) {
$container->setDefinition('security.logout.listener.session.'.$id, new ChildDefinition('security.logout.listener.session'))
->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
}
// add cookie logout listener
if (\count($firewall['logout']['delete_cookies']) > 0) {
$container->setDefinition('security.logout.listener.cookie_clearing.'.$id, new ChildDefinition('security.logout.listener.cookie_clearing'))
->addArgument($firewall['logout']['delete_cookies'])
->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
}
// add custom listeners (deprecated)
foreach ($firewall['logout']['handlers'] as $i => $handlerId) {
$container->register('security.logout.listener.legacy_handler.'.$i, LegacyLogoutHandlerListener::class)
->addArgument(new Reference($handlerId))
->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
}
// register with LogoutUrlGenerator
$container
->getDefinition('security.logout_url_generator')
->addMethodCall('registerListener', [
$id,
$firewall['logout']['path'],
$firewall['logout']['csrf_token_id'],
$firewall['logout']['csrf_parameter'],
isset($firewall['logout']['csrf_token_generator']) ? new Reference($firewall['logout']['csrf_token_generator']) : null,
false === $firewall['stateless'] && isset($firewall['context']) ? $firewall['context'] : null,
])
;
}
// Determine default entry point
$configuredEntryPoint = $firewall['entry_point'] ?? null;
// Authentication listeners
$firewallAuthenticationProviders = [];
[$authListeners, $defaultEntryPoint] = $this->createAuthenticationListeners($container, $id, $firewall, $firewallAuthenticationProviders, $defaultProvider, $providerIds, $configuredEntryPoint, $contextListenerId);
if (!$this->authenticatorManagerEnabled) {
$authenticationProviders = array_merge($authenticationProviders, $firewallAuthenticationProviders);
} else {
// $configuredEntryPoint is resolved into a service ID and stored in $defaultEntryPoint
$configuredEntryPoint = $defaultEntryPoint;
// authenticator manager
$authenticators = array_map(function ($id) {
return new Reference($id);
}, $firewallAuthenticationProviders);
$container
->setDefinition($managerId = 'security.authenticator.manager.'.$id, new ChildDefinition('security.authenticator.manager'))
->replaceArgument(0, $authenticators)
->replaceArgument(2, new Reference($firewallEventDispatcherId))
->replaceArgument(3, $id)
->replaceArgument(7, $firewall['required_badges'] ?? [])
->addTag('monolog.logger', ['channel' => 'security'])
;
$managerLocator = $container->getDefinition('security.authenticator.managers_locator');
$managerLocator->replaceArgument(0, array_merge($managerLocator->getArgument(0), [$id => new ServiceClosureArgument(new Reference($managerId))]));
// authenticator manager listener
$container
->setDefinition('security.firewall.authenticator.'.$id, new ChildDefinition('security.firewall.authenticator'))
->replaceArgument(0, new Reference($managerId))
;
if ($container->hasDefinition('debug.security.firewall') && $this->authenticatorManagerEnabled) {
$container
->register('debug.security.firewall.authenticator.'.$id, TraceableAuthenticatorManagerListener::class)
->setDecoratedService('security.firewall.authenticator.'.$id)
->setArguments([new Reference('debug.security.firewall.authenticator.'.$id.'.inner')])
;
}
// user checker listener
$container
->setDefinition('security.listener.user_checker.'.$id, new ChildDefinition('security.listener.user_checker'))
->replaceArgument(0, new Reference('security.user_checker.'.$id))
->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
$listeners[] = new Reference('security.firewall.authenticator.'.$id);
// Add authenticators to the debug:firewall command
if ($container->hasDefinition('security.command.debug_firewall')) {
$debugCommand = $container->getDefinition('security.command.debug_firewall');
$debugCommand->replaceArgument(3, array_merge($debugCommand->getArgument(3), [$id => $authenticators]));
}
}
$config->replaceArgument(7, $configuredEntryPoint ?: $defaultEntryPoint);
$listeners = array_merge($listeners, $authListeners);
// Switch user listener
if (isset($firewall['switch_user'])) {
$listenerKeys[] = 'switch_user';
$listeners[] = new Reference($this->createSwitchUserListener($container, $id, $firewall['switch_user'], $defaultProvider, $firewall['stateless']));
}
// Access listener
$listeners[] = new Reference('security.access_listener');
// Exception listener
$exceptionListener = new Reference($this->createExceptionListener($container, $firewall, $id, $configuredEntryPoint ?: $defaultEntryPoint, $firewall['stateless']));
$config->replaceArgument(8, $firewall['access_denied_handler'] ?? null);
$config->replaceArgument(9, $firewall['access_denied_url'] ?? null);
$container->setAlias('security.user_checker.'.$id, new Alias($firewall['user_checker'], false));
foreach ($this->getSortedFactories() as $factory) {
$key = str_replace('-', '_', $factory->getKey());
if ('custom_authenticators' !== $key && \array_key_exists($key, $firewall)) {
$listenerKeys[] = $key;
}
}
if ($firewall['custom_authenticators'] ?? false) {
foreach ($firewall['custom_authenticators'] as $customAuthenticatorId) {
$listenerKeys[] = $customAuthenticatorId;
}
}
$config->replaceArgument(10, $listenerKeys);
$config->replaceArgument(11, $firewall['switch_user'] ?? null);
return [$matcher, $listeners, $exceptionListener, null !== $logoutListenerId ? new Reference($logoutListenerId) : null];
}
private function createContextListener(ContainerBuilder $container, string $contextKey, ?string $firewallEventDispatcherId)
{
if (isset($this->contextListeners[$contextKey])) {
return $this->contextListeners[$contextKey];
}
$listenerId = 'security.context_listener.'.\count($this->contextListeners);
$listener = $container->setDefinition($listenerId, new ChildDefinition('security.context_listener'));
$listener->replaceArgument(2, $contextKey);
if (null !== $firewallEventDispatcherId) {
$listener->replaceArgument(4, new Reference($firewallEventDispatcherId));
$listener->addTag('kernel.event_listener', ['event' => KernelEvents::RESPONSE, 'method' => 'onKernelResponse']);
}
return $this->contextListeners[$contextKey] = $listenerId;
}
private function createAuthenticationListeners(ContainerBuilder $container, string $id, array $firewall, array &$authenticationProviders, ?string $defaultProvider, array $providerIds, ?string $defaultEntryPoint, string $contextListenerId = null)
{
$listeners = [];
$hasListeners = false;
$entryPoints = [];
foreach ($this->getSortedFactories() as $factory) {
$key = str_replace('-', '_', $factory->getKey());
if (isset($firewall[$key])) {
$userProvider = $this->getUserProvider($container, $id, $firewall, $key, $defaultProvider, $providerIds, $contextListenerId);
if ($this->authenticatorManagerEnabled) {
if (!$factory instanceof AuthenticatorFactoryInterface) {
throw new InvalidConfigurationException(sprintf('Cannot configure AuthenticatorManager as "%s" authentication does not support it, set "security.enable_authenticator_manager" to `false`.', $key));
}
$authenticators = $factory->createAuthenticator($container, $id, $firewall[$key], $userProvider);
if (\is_array($authenticators)) {
foreach ($authenticators as $authenticator) {
$authenticationProviders[] = $authenticator;
$entryPoints[] = $authenticator;
}
} else {
$authenticationProviders[] = $authenticators;
$entryPoints[$key] = $authenticators;
}
} else {
[$provider, $listenerId, $defaultEntryPoint] = $factory->create($container, $id, $firewall[$key], $userProvider, $defaultEntryPoint);
$listeners[] = new Reference($listenerId);
$authenticationProviders[] = $provider;
}
if ($factory instanceof FirewallListenerFactoryInterface) {
$firewallListenerIds = $factory->createListeners($container, $id, $firewall[$key]);
foreach ($firewallListenerIds as $firewallListenerId) {
$listeners[] = new Reference($firewallListenerId);
}
}
$hasListeners = true;
}
}
// the actual entry point is configured by the RegisterEntryPointPass
$container->setParameter('security.'.$id.'._indexed_authenticators', $entryPoints);
if (false === $hasListeners && !$this->authenticatorManagerEnabled) {
throw new InvalidConfigurationException(sprintf('No authentication listener registered for firewall "%s".', $id));
}
return [$listeners, $defaultEntryPoint];
}
private function getUserProvider(ContainerBuilder $container, string $id, array $firewall, string $factoryKey, ?string $defaultProvider, array $providerIds, ?string $contextListenerId): string
{
if (isset($firewall[$factoryKey]['provider'])) {
if (!isset($providerIds[$normalizedName = str_replace('-', '_', $firewall[$factoryKey]['provider'])])) {
throw new InvalidConfigurationException(sprintf('Invalid firewall "%s": user provider "%s" not found.', $id, $firewall[$factoryKey]['provider']));
}
return $providerIds[$normalizedName];
}
if ('remember_me' === $factoryKey && $contextListenerId) {
$container->getDefinition($contextListenerId)->addTag('security.remember_me_aware', ['id' => $id, 'provider' => 'none']);
}
if ($defaultProvider) {
return $defaultProvider;
}
if (!$providerIds) {
$userProvider = sprintf('security.user.provider.missing.%s', $factoryKey);
$container->setDefinition(
$userProvider,
(new ChildDefinition('security.user.provider.missing'))->replaceArgument(0, $id)
);
return $userProvider;
}
if ('remember_me' === $factoryKey || 'anonymous' === $factoryKey || 'custom_authenticators' === $factoryKey) {
if ('custom_authenticators' === $factoryKey) {
trigger_deprecation('symfony/security-bundle', '5.4', 'Not configuring explicitly the provider for the "%s" firewall is deprecated because it\'s ambiguous as there is more than one registered provider. Set the "provider" key to one of the configured providers, even if your custom authenticators don\'t use it.', $id);
}
return 'security.user_providers';
}
throw new InvalidConfigurationException(sprintf('Not configuring explicitly the provider for the "%s" %s on "%s" firewall is ambiguous as there is more than one registered provider.', $factoryKey, $this->authenticatorManagerEnabled ? 'authenticator' : 'listener', $id));
}
private function createEncoders(array $encoders, ContainerBuilder $container)
{
$encoderMap = [];
foreach ($encoders as $class => $encoder) {
if (class_exists($class) && !is_a($class, PasswordAuthenticatedUserInterface::class, true)) {
trigger_deprecation('symfony/security-bundle', '5.3', 'Configuring an encoder for a user class that does not implement "%s" is deprecated, class "%s" should implement it.', PasswordAuthenticatedUserInterface::class, $class);
}
$encoderMap[$class] = $this->createEncoder($encoder);
}
$container
->getDefinition('security.encoder_factory.generic')
->setArguments([$encoderMap])
;
}
private function createEncoder(array $config)
{
// a custom encoder service
if (isset($config['id'])) {
return new Reference($config['id']);
}
if ($config['migrate_from'] ?? false) {
return $config;
}
// plaintext encoder
if ('plaintext' === $config['algorithm']) {
$arguments = [$config['ignore_case']];
return [
'class' => 'Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder',
'arguments' => $arguments,
];
}
// pbkdf2 encoder
if ('pbkdf2' === $config['algorithm']) {
return [
'class' => 'Symfony\Component\Security\Core\Encoder\Pbkdf2PasswordEncoder',
'arguments' => [
$config['hash_algorithm'],
$config['encode_as_base64'],
$config['iterations'],
$config['key_length'],
],
];
}
// bcrypt encoder
if ('bcrypt' === $config['algorithm']) {
$config['algorithm'] = 'native';
$config['native_algorithm'] = \PASSWORD_BCRYPT;
return $this->createEncoder($config);
}
// Argon2i encoder
if ('argon2i' === $config['algorithm']) {
if (SodiumPasswordHasher::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
$config['algorithm'] = 'sodium';
} elseif (\defined('PASSWORD_ARGON2I')) {
$config['algorithm'] = 'native';
$config['native_algorithm'] = \PASSWORD_ARGON2I;
} else {
throw new InvalidConfigurationException(sprintf('Algorithm "argon2i" is not available. Use "%s" instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? 'argon2id", "auto' : 'auto'));
}
return $this->createEncoder($config);
}
if ('argon2id' === $config['algorithm']) {
if (($hasSodium = SodiumPasswordHasher::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
$config['algorithm'] = 'sodium';
} elseif (\defined('PASSWORD_ARGON2ID')) {
$config['algorithm'] = 'native';
$config['native_algorithm'] = \PASSWORD_ARGON2ID;
} else {
throw new InvalidConfigurationException(sprintf('Algorithm "argon2id" is not available. Either use "%s", upgrade to PHP 7.3+ or use libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? 'argon2i", "auto' : 'auto'));
}
return $this->createEncoder($config);
}
if ('native' === $config['algorithm']) {
return [
'class' => NativePasswordEncoder::class,
'arguments' => [
$config['time_cost'],
(($config['memory_cost'] ?? 0) << 10) ?: null,
$config['cost'],
] + (isset($config['native_algorithm']) ? [3 => $config['native_algorithm']] : []),
];
}
if ('sodium' === $config['algorithm']) {
if (!SodiumPasswordHasher::isSupported()) {
throw new InvalidConfigurationException('Libsodium is not available. Install the sodium extension or use "auto" instead.');
}
return [
'class' => SodiumPasswordEncoder::class,
'arguments' => [
$config['time_cost'],
(($config['memory_cost'] ?? 0) << 10) ?: null,
],
];
}
// run-time configured encoder
return $config;
}
private function createHashers(array $hashers, ContainerBuilder $container)
{
$hasherMap = [];
foreach ($hashers as $class => $hasher) {
// @deprecated since Symfony 5.3, remove the check in 6.0
if (class_exists($class) && !is_a($class, PasswordAuthenticatedUserInterface::class, true)) {
trigger_deprecation('symfony/security-bundle', '5.3', 'Configuring a password hasher for a user class that does not implement "%s" is deprecated, class "%s" should implement it.', PasswordAuthenticatedUserInterface::class, $class);
}
$hasherMap[$class] = $this->createHasher($hasher);
}
$container
->getDefinition('security.password_hasher_factory')
->setArguments([$hasherMap])
;
}
private function createHasher(array $config)
{
// a custom hasher service
if (isset($config['id'])) {
return new Reference($config['id']);
}
if ($config['migrate_from'] ?? false) {
return $config;
}
// plaintext hasher
if ('plaintext' === $config['algorithm']) {
$arguments = [$config['ignore_case']];
return [
'class' => PlaintextPasswordHasher::class,
'arguments' => $arguments,
];
}
// pbkdf2 hasher
if ('pbkdf2' === $config['algorithm']) {
return [
'class' => Pbkdf2PasswordHasher::class,
'arguments' => [
$config['hash_algorithm'],
$config['encode_as_base64'],
$config['iterations'],
$config['key_length'],
],
];
}
// bcrypt hasher
if ('bcrypt' === $config['algorithm']) {
$config['algorithm'] = 'native';
$config['native_algorithm'] = \PASSWORD_BCRYPT;
return $this->createHasher($config);
}
// Argon2i hasher
if ('argon2i' === $config['algorithm']) {
if (SodiumPasswordHasher::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
$config['algorithm'] = 'sodium';
} elseif (\defined('PASSWORD_ARGON2I')) {
$config['algorithm'] = 'native';
$config['native_algorithm'] = \PASSWORD_ARGON2I;
} else {
throw new InvalidConfigurationException(sprintf('Algorithm "argon2i" is not available. Either use "%s" or upgrade to PHP 7.2+ instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? 'argon2id", "auto' : 'auto'));
}
return $this->createHasher($config);
}
if ('argon2id' === $config['algorithm']) {
if (($hasSodium = SodiumPasswordHasher::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
$config['algorithm'] = 'sodium';
} elseif (\defined('PASSWORD_ARGON2ID')) {
$config['algorithm'] = 'native';
$config['native_algorithm'] = \PASSWORD_ARGON2ID;
} else {
throw new InvalidConfigurationException(sprintf('Algorithm "argon2id" is not available. Either use "%s", upgrade to PHP 7.3+ or use libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? 'argon2i", "auto' : 'auto'));
}
return $this->createHasher($config);
}
if ('native' === $config['algorithm']) {
return [
'class' => NativePasswordHasher::class,
'arguments' => [
$config['time_cost'],
(($config['memory_cost'] ?? 0) << 10) ?: null,
$config['cost'],
] + (isset($config['native_algorithm']) ? [3 => $config['native_algorithm']] : []),
];
}
if ('sodium' === $config['algorithm']) {
if (!SodiumPasswordHasher::isSupported()) {
throw new InvalidConfigurationException('Libsodium is not available. Install the sodium extension or use "auto" instead.');
}
return [
'class' => SodiumPasswordHasher::class,
'arguments' => [
$config['time_cost'],
(($config['memory_cost'] ?? 0) << 10) ?: null,
],
];
}
// run-time configured hasher
return $config;
}
// Parses user providers and returns an array of their ids
private function createUserProviders(array $config, ContainerBuilder $container): array
{
$providerIds = [];
foreach ($config['providers'] as $name => $provider) {
$id = $this->createUserDaoProvider($name, $provider, $container);
$providerIds[str_replace('-', '_', $name)] = $id;
}
return $providerIds;
}
// Parses a <provider> tag and returns the id for the related user provider service
private function createUserDaoProvider(string $name, array $provider, ContainerBuilder $container): string
{
$name = $this->getUserProviderId($name);
// Doctrine Entity and In-memory DAO provider are managed by factories
foreach ($this->userProviderFactories as $factory) {
$key = str_replace('-', '_', $factory->getKey());
if (!empty($provider[$key])) {
$factory->create($container, $name, $provider[$key]);
return $name;
}
}
// Existing DAO service provider
if (isset($provider['id'])) {
$container->setAlias($name, new Alias($provider['id'], false));
return $provider['id'];
}
// Chain provider
if (isset($provider['chain'])) {
$providers = [];
foreach ($provider['chain']['providers'] as $providerName) {
$providers[] = new Reference($this->getUserProviderId($providerName));
}
$container
->setDefinition($name, new ChildDefinition('security.user.provider.chain'))
->addArgument(new IteratorArgument($providers));
return $name;
}
throw new InvalidConfigurationException(sprintf('Unable to create definition for "%s" user provider.', $name));
}
private function getUserProviderId(string $name): string
{
return 'security.user.provider.concrete.'.strtolower($name);
}
private function createExceptionListener(ContainerBuilder $container, array $config, string $id, ?string $defaultEntryPoint, bool $stateless): string
{
$exceptionListenerId = 'security.exception_listener.'.$id;
$listener = $container->setDefinition($exceptionListenerId, new ChildDefinition('security.exception_listener'));
$listener->replaceArgument(3, $id);
$listener->replaceArgument(4, null === $defaultEntryPoint ? null : new Reference($defaultEntryPoint));
$listener->replaceArgument(8, $stateless);
// access denied handler setup
if (isset($config['access_denied_handler'])) {
$listener->replaceArgument(6, new Reference($config['access_denied_handler']));
} elseif (isset($config['access_denied_url'])) {
$listener->replaceArgument(5, $config['access_denied_url']);
}
return $exceptionListenerId;
}
private function createSwitchUserListener(ContainerBuilder $container, string $id, array $config, ?string $defaultProvider, bool $stateless): string
{
$userProvider = isset($config['provider']) ? $this->getUserProviderId($config['provider']) : $defaultProvider;
if (!$userProvider) {
throw new InvalidConfigurationException(sprintf('Not configuring explicitly the provider for the "switch_user" listener on "%s" firewall is ambiguous as there is more than one registered provider.', $id));
}
$switchUserListenerId = 'security.authentication.switchuser_listener.'.$id;
$listener = $container->setDefinition($switchUserListenerId, new ChildDefinition('security.authentication.switchuser_listener'));
$listener->replaceArgument(1, new Reference($userProvider));
$listener->replaceArgument(2, new Reference('security.user_checker.'.$id));
$listener->replaceArgument(3, $id);
$listener->replaceArgument(6, $config['parameter']);
$listener->replaceArgument(7, $config['role']);
$listener->replaceArgument(9, $stateless);
return $switchUserListenerId;
}
private function createExpression(ContainerBuilder $container, string $expression): Reference
{
if (isset($this->expressions[$id = '.security.expression.'.ContainerBuilder::hash($expression)])) {
return $this->expressions[$id];
}
if (!$container::willBeAvailable('symfony/expression-language', ExpressionLanguage::class, ['symfony/security-bundle'], true)) {
throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".');
}
$container
->register($id, 'Symfony\Component\ExpressionLanguage\Expression')
->setPublic(false)
->addArgument($expression)
;
return $this->expressions[$id] = new Reference($id);
}
private function createRequestMatcher(ContainerBuilder $container, string $path = null, string $host = null, int $port = null, array $methods = [], array $ips = null, array $attributes = []): Reference
{
if ($methods) {
$methods = array_map('strtoupper', $methods);
}
if (null !== $ips) {
foreach ($ips as $ip) {
$container->resolveEnvPlaceholders($ip, null, $usedEnvs);
if (!$usedEnvs && !$this->isValidIps($ip)) {
throw new \LogicException(sprintf('The given value "%s" in the "security.access_control" config option is not a valid IP address.', $ip));
}
$usedEnvs = null;
}
}
$id = '.security.request_matcher.'.ContainerBuilder::hash([$path, $host, $port, $methods, $ips, $attributes]);
if (isset($this->requestMatchers[$id])) {
return $this->requestMatchers[$id];
}
// only add arguments that are necessary
$arguments = [$path, $host, $methods, $ips, $attributes, null, $port];
while (\count($arguments) > 0 && !end($arguments)) {
array_pop($arguments);
}
$container
->register($id, 'Symfony\Component\HttpFoundation\RequestMatcher')
->setPublic(false)
->setArguments($arguments)
;
return $this->requestMatchers[$id] = new Reference($id);
}
/**
* @deprecated since Symfony 5.4, use "addAuthenticatorFactory()" instead
*/
public function addSecurityListenerFactory(SecurityFactoryInterface $factory)
{
trigger_deprecation('symfony/security-bundle', '5.4', 'Method "%s()" is deprecated, use "addAuthenticatorFactory()" instead.', __METHOD__);
$this->factories[] = [[
'pre_auth' => -10,
'form' => -30,
'http' => -40,
'remember_me' => -50,
'anonymous' => -60,
][$factory->getPosition()], $factory];
$this->sortedFactories = [];
}
public function addAuthenticatorFactory(AuthenticatorFactoryInterface $factory)
{
$this->factories[] = [method_exists($factory, 'getPriority') ? $factory->getPriority() : 0, $factory];
$this->sortedFactories = [];
}
public function addUserProviderFactory(UserProviderFactoryInterface $factory)
{
$this->userProviderFactories[] = $factory;
}
/**
* {@inheritdoc}
*/
public function getXsdValidationBasePath()
{
return __DIR__.'/../Resources/config/schema';
}
public function getNamespace()
{
return 'http://symfony.com/schema/dic/security';
}
public function getConfiguration(array $config, ContainerBuilder $container)
{
// first assemble the factories
return new MainConfiguration($this->getSortedFactories(), $this->userProviderFactories);
}
private function isValidIps($ips): bool
{
$ipsList = array_reduce((array) $ips, static function (array $ips, string $ip) {
return array_merge($ips, preg_split('/\s*,\s*/', $ip));
}, []);
if (!$ipsList) {
return false;
}
foreach ($ipsList as $cidr) {
if (!$this->isValidIp($cidr)) {
return false;
}
}
return true;
}
private function isValidIp(string $cidr): bool
{
$cidrParts = explode('/', $cidr);
if (1 === \count($cidrParts)) {
return false !== filter_var($cidrParts[0], \FILTER_VALIDATE_IP);
}
$ip = $cidrParts[0];
$netmask = $cidrParts[1];
if (!ctype_digit($netmask)) {
return false;
}
if (filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) {
return $netmask <= 32;
}
if (filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
return $netmask <= 128;
}
return false;
}
/**
* @return array<int, SecurityFactoryInterface|AuthenticatorFactoryInterface>
*/
private function getSortedFactories(): array
{
if (!$this->sortedFactories) {
$factories = [];
foreach ($this->factories as $i => $factory) {
$factories[] = array_merge($factory, [$i]);
}
usort($factories, function ($a, $b) {
return $b[0] <=> $a[0] ?: $a[2] <=> $b[2];
});
$this->sortedFactories = array_column($factories, 1);
}
return $this->sortedFactories;
}
}

View File

@ -0,0 +1,72 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\SecurityBundle\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 $map;
private $logoutUrlGenerator;
public function __construct(FirewallMapInterface $map, EventDispatcherInterface $dispatcher, LogoutUrlGenerator $logoutUrlGenerator)
{
$this->map = $map;
$this->logoutUrlGenerator = $logoutUrlGenerator;
parent::__construct($map, $dispatcher);
}
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());
}
}
public function onKernelFinishRequest(FinishRequestEvent $event)
{
if ($event->isMainRequest()) {
$this->logoutUrlGenerator->setCurrentFirewall(null);
}
parent::onKernelFinishRequest($event);
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
KernelEvents::REQUEST => [
['configureLogoutUrlGenerator', 8],
['onKernelRequest', 8],
],
KernelEvents::FINISH_REQUEST => 'onKernelFinishRequest',
];
}
}

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\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;
public function __construct(TraceableAccessDecisionManager $traceableAccessDecisionManager)
{
$this->traceableAccessDecisionManager = $traceableAccessDecisionManager;
}
/**
* Event dispatched by a voter during access manager decision.
*/
public function onVoterVote(VoteEvent $event)
{
$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-2022 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): LoginLinkDetails
{
return $this->getForFirewall()->createLoginLink($user, $request);
}
public function consumeLoginLink(Request $request): UserInterface
{
return $this->getForFirewall()->consumeLoginLink($request);
}
}

View File

@ -0,0 +1,13 @@
SecurityBundle
==============
SecurityBundle provides a tight integration of the Security component into the
Symfony full-stack framework.
Resources
---------
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)

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\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 $handler;
public function __construct(RememberMeHandlerInterface $handler)
{
$this->handler = $handler;
}
/**
* {@inheritDoc}
*/
public function createRememberMeCookie(UserInterface $user): void
{
$this->handler->createRememberMeCookie($user);
}
/**
* {@inheritDoc}
*/
public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails): UserInterface
{
return $this->handler->consumeRememberMeCookie($rememberMeDetails);
}
/**
* {@inheritDoc}
*/
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,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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Bundle\SecurityBundle\Command\UserPasswordEncoderCommand;
use Symfony\Component\PasswordHasher\Command\UserPasswordHashCommand;
return static function (ContainerConfigurator $container) {
$container->services()
->set('security.command.user_password_encoder', UserPasswordEncoderCommand::class)
->args([
service('security.encoder_factory'),
abstract_arg('encoders user classes'),
])
->tag('console.command', ['command' => 'security:encode-password'])
->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use "security.command.user_password_hash" instead.')
;
$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,56 @@
<?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\Security\Guard\Firewall\GuardAuthenticationListener;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
use Symfony\Component\Security\Guard\Provider\GuardAuthenticationProvider;
return static function (ContainerConfigurator $container) {
$container->services()
->set('security.authentication.guard_handler', GuardAuthenticatorHandler::class)
->args([
service('security.token_storage'),
service('event_dispatcher')->nullOnInvalid(),
abstract_arg('stateless firewall keys'),
])
->call('setSessionAuthenticationStrategy', [service('security.authentication.session_strategy')])
->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')
->alias(GuardAuthenticatorHandler::class, 'security.authentication.guard_handler')
->deprecate('symfony/security-bundle', '5.3', 'The "%alias_id%" alias is deprecated, use the new authenticator system instead.')
->set('security.authentication.provider.guard', GuardAuthenticationProvider::class)
->abstract()
->args([
abstract_arg('Authenticators'),
abstract_arg('User Provider'),
abstract_arg('Provider-shared Key'),
abstract_arg('User Checker'),
service('security.password_hasher'),
])
->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')
->set('security.authentication.listener.guard', GuardAuthenticationListener::class)
->abstract()
->args([
service('security.authentication.guard_handler'),
service('security.authentication.manager'),
abstract_arg('Provider-shared Key'),
abstract_arg('Authenticators'),
service('logger')->nullOnInvalid(),
param('security.authentication.hide_user_not_found'),
])
->tag('monolog.logger', ['channel' => 'security'])
->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')
;
};

View File

@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
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')
;
};

View File

@ -0,0 +1,425 @@
<?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="encoders" type="encoders" minOccurs="0" maxOccurs="1" />
<xsd:element name="encoder" type="encoder" minOccurs="0" maxOccurs="unbounded" />
<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="encoders">
<xsd:sequence>
<xsd:element name="encoder" type="encoder" minOccurs="1" maxOccurs="unbounded" />
</xsd:sequence>
</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="encoder">
<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="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" />
</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="name" 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="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-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" />
</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:sequence>
<xsd:element name="delete-cookie" type="delete_cookie" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="csrf-parameter" type="xsd:string" />
<xsd:attribute name="csrf-token-generator" type="xsd:string" />
<xsd:attribute name="csrf-token-id" type="xsd:string" />
<xsd:attribute name="path" type="xsd:string" />
<xsd:attribute name="target" type="xsd:string" />
<xsd:attribute name="success-handler" 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: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="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: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: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: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:schema>

View File

@ -0,0 +1,293 @@
<?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\Security\FirewallConfig;
use Symfony\Bundle\SecurityBundle\Security\FirewallContext;
use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
use Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext;
use Symfony\Component\Ldap\Security\LdapUserProvider;
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
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\Encoder\EncoderFactory;
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoder;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Role\RoleHierarchy;
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
use Symfony\Component\Security\Core\Security;
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\UserValueResolver;
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)
->public()
->args([
service('security.token_storage'),
service('security.access.decision_manager'),
param('security.access.always_authenticate_before_granting'),
])
->tag('container.private', ['package' => 'symfony/security-bundle', 'version' => '5.3'])
->alias(AuthorizationCheckerInterface::class, 'security.authorization_checker')
->set('security.token_storage', UsageTrackingTokenStorage::class)
->public()
->args([
service('security.untracked_token_storage'),
service_locator([
'request_stack' => service('request_stack'),
]),
])
->tag('kernel.reset', ['method' => 'disableUsageTracking'])
->tag('kernel.reset', ['method' => 'setToken'])
->tag('container.private', ['package' => 'symfony/security-bundle', 'version' => '5.3'])
->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'),
])])
->alias(Security::class, 'security.helper')
->set('security.user_value_resolver', UserValueResolver::class)
->args([
service('security.token_storage'),
])
->tag('controller.argument_value_resolver', ['priority' => 40])
// Authentication related services
->set('security.authentication.trust_resolver', AuthenticationTrustResolver::class)
->set('security.authentication.session_strategy', SessionAuthenticationStrategy::class)
->args([param('security.authentication.session_strategy.strategy')])
->alias(SessionAuthenticationStrategyInterface::class, 'security.authentication.session_strategy')
->set('security.authentication.session_strategy_noop', SessionAuthenticationStrategy::class)
->args(['none'])
->set('security.encoder_factory.generic', EncoderFactory::class)
->args([
[],
])
->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use "security.password_hasher_factory" instead.')
->alias('security.encoder_factory', 'security.encoder_factory.generic')
->deprecate('symfony/security-bundle', '5.3', 'The "%alias_id%" service is deprecated, use "security.password_hasher_factory" instead.')
->alias(EncoderFactoryInterface::class, 'security.encoder_factory')
->deprecate('symfony/security-bundle', '5.3', 'The "%alias_id%" service is deprecated, use "'.PasswordHasherFactoryInterface::class.'" instead.')
->set('security.user_password_encoder.generic', UserPasswordEncoder::class)
->args([service('security.encoder_factory')])
->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use "security.user_password_hasher" instead.')
->alias('security.password_encoder', 'security.user_password_encoder.generic')
->public()
->deprecate('symfony/security-bundle', '5.3', 'The "%alias_id%" service is deprecated, use "security.password_hasher"" instead.')
->alias(UserPasswordEncoderInterface::class, 'security.password_encoder')
->deprecate('symfony/security-bundle', '5.3', 'The "%alias_id%" service is deprecated, use "'.UserPasswordHasherInterface::class.'" instead.')
->set('security.user_checker', InMemoryUserChecker::class)
->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
])
->set('security.logout_url_generator', LogoutUrlGenerator::class)
->args([
service('request_stack')->nullOnInvalid(),
service('router')->nullOnInvalid(),
service('security.token_storage')->nullOnInvalid(),
])
// 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')
;
};

View File

@ -0,0 +1,172 @@
<?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\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticatorManager;
use Symfony\Component\Security\Http\Authentication\NoopAuthenticationManager;
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.authentication.manager', NoopAuthenticationManager::class)
->alias(AuthenticationManagerInterface::class, 'security.authentication.manager')
->deprecate('symfony/security-bundle', '5.3', 'The "%alias_id%" alias is deprecated, use the new authenticator system instead.')
->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(),
])
->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,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,98 @@
<?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\SignatureRememberMeHandler;
return static function (ContainerConfigurator $container) {
$container->services()
->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'),
param('kernel.secret'),
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,41 @@
<?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')
->alias('security.firewall', 'debug.security.firewall')
;
};

View File

@ -0,0 +1,150 @@
<?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\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager;
use Symfony\Component\Security\Core\Authentication\Provider\AnonymousAuthenticationProvider;
use Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider;
use Symfony\Component\Security\Core\Authentication\Provider\LdapBindAuthenticationProvider;
use Symfony\Component\Security\Core\Authentication\Provider\PreAuthenticatedAuthenticationProvider;
use Symfony\Component\Security\Http\Firewall\AnonymousAuthenticationListener;
use Symfony\Component\Security\Http\Firewall\BasicAuthenticationListener;
use Symfony\Component\Security\Http\Firewall\RemoteUserAuthenticationListener;
use Symfony\Component\Security\Http\Firewall\UsernamePasswordFormAuthenticationListener;
use Symfony\Component\Security\Http\Firewall\UsernamePasswordJsonAuthenticationListener;
use Symfony\Component\Security\Http\Firewall\X509AuthenticationListener;
return static function (ContainerConfigurator $container) {
$container->services()
// Authentication related services
->set('security.authentication.manager', AuthenticationProviderManager::class)
->args([
abstract_arg('providers'),
param('security.authentication.manager.erase_credentials'),
])
->call('setEventDispatcher', [service('event_dispatcher')])
->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')
->alias(AuthenticationManagerInterface::class, 'security.authentication.manager')
->deprecate('symfony/security-bundle', '5.3', 'The "%alias_id%" alias is deprecated, use the new authenticator system instead.')
->set('security.authentication.listener.anonymous', AnonymousAuthenticationListener::class)
->args([
service('security.untracked_token_storage'),
abstract_arg('Key'),
service('logger')->nullOnInvalid(),
service('security.authentication.manager'),
])
->tag('monolog.logger', ['channel' => 'security'])
->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')
->set('security.authentication.provider.anonymous', AnonymousAuthenticationProvider::class)
->args([abstract_arg('Key')])
->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')
->set('security.authentication.listener.form', UsernamePasswordFormAuthenticationListener::class)
->parent('security.authentication.listener.abstract')
->abstract()
->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')
->set('security.authentication.listener.x509', X509AuthenticationListener::class)
->abstract()
->args([
service('security.token_storage'),
service('security.authentication.manager'),
abstract_arg('Provider-shared Key'),
abstract_arg('x509 user'),
abstract_arg('x509 credentials'),
service('logger')->nullOnInvalid(),
service('event_dispatcher')->nullOnInvalid(),
])
->tag('monolog.logger', ['channel' => 'security'])
->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')
->set('security.authentication.listener.json', UsernamePasswordJsonAuthenticationListener::class)
->abstract()
->args([
service('security.token_storage'),
service('security.authentication.manager'),
service('security.http_utils'),
abstract_arg('Provider-shared Key'),
abstract_arg('Failure handler'),
abstract_arg('Success Handler'),
[], // Options
service('logger')->nullOnInvalid(),
service('event_dispatcher')->nullOnInvalid(),
service('property_accessor')->nullOnInvalid(),
])
->call('setTranslator', [service('translator')->ignoreOnInvalid()])
->tag('monolog.logger', ['channel' => 'security'])
->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')
->set('security.authentication.listener.remote_user', RemoteUserAuthenticationListener::class)
->abstract()
->args([
service('security.token_storage'),
service('security.authentication.manager'),
abstract_arg('Provider-shared Key'),
abstract_arg('REMOTE_USER server env var'),
service('logger')->nullOnInvalid(),
service('event_dispatcher')->nullOnInvalid(),
])
->tag('monolog.logger', ['channel' => 'security'])
->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')
->set('security.authentication.listener.basic', BasicAuthenticationListener::class)
->abstract()
->args([
service('security.token_storage'),
service('security.authentication.manager'),
abstract_arg('Provider-shared Key'),
abstract_arg('Entry Point'),
service('logger')->nullOnInvalid(),
])
->tag('monolog.logger', ['channel' => 'security'])
->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')
->set('security.authentication.provider.dao', DaoAuthenticationProvider::class)
->abstract()
->args([
abstract_arg('User Provider'),
abstract_arg('User Checker'),
abstract_arg('Provider-shared Key'),
service('security.password_hasher_factory'),
param('security.authentication.hide_user_not_found'),
])
->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')
->set('security.authentication.provider.ldap_bind', LdapBindAuthenticationProvider::class)
->abstract()
->args([
abstract_arg('User Provider'),
abstract_arg('UserChecker'),
abstract_arg('Provider-shared Key'),
abstract_arg('LDAP'),
abstract_arg('Base DN'),
param('security.authentication.hide_user_not_found'),
abstract_arg('search dn'),
abstract_arg('search password'),
])
->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')
->set('security.authentication.provider.pre_authenticated', PreAuthenticatedAuthenticationProvider::class)
->abstract()
->args([
abstract_arg('User Provider'),
abstract_arg('UserChecker'),
])
->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')
;
};

View File

@ -0,0 +1,183 @@
<?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\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\EntryPoint\BasicAuthenticationEntryPoint;
use Symfony\Component\Security\Http\EntryPoint\FormAuthenticationEntryPoint;
use Symfony\Component\Security\Http\EntryPoint\RetryAuthenticationEntryPoint;
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.authentication.basic_entry_point', BasicAuthenticationEntryPoint::class)
->deprecate('symfony/security-bundle', '5.4', 'The "%service_id%" service is deprecated, the logic is contained in the authenticators.')
->set('security.authentication.retry_entry_point', RetryAuthenticationEntryPoint::class)
->deprecate('symfony/security-bundle', '5.4', 'The "%service_id%" service is deprecated, the logic is integrated directly in "security.channel_listener".')
->args([
inline_service('int')->factory([service('router.request_context'), 'getHttpPort']),
inline_service('int')->factory([service('router.request_context'), 'getHttpsPort']),
])
->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.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.form_entry_point', FormAuthenticationEntryPoint::class)
->abstract()
->args([
service('http_kernel'),
])
->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
])
->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
])
->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'),
service('security.authentication.manager'),
])
->tag('monolog.logger', ['channel' => 'security'])
;
};

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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\Security\Core\Authentication\Provider\RememberMeAuthenticationProvider;
use Symfony\Component\Security\Core\Authentication\RememberMe\InMemoryTokenProvider;
use Symfony\Component\Security\Http\Firewall\RememberMeListener;
use Symfony\Component\Security\Http\RememberMe\PersistentTokenBasedRememberMeServices;
use Symfony\Component\Security\Http\RememberMe\ResponseListener;
use Symfony\Component\Security\Http\RememberMe\TokenBasedRememberMeServices;
return static function (ContainerConfigurator $container) {
$container->services()
->set('security.authentication.listener.rememberme', RememberMeListener::class)
->abstract()
->args([
service('security.untracked_token_storage'),
service('security.authentication.rememberme'),
service('security.authentication.manager'),
service('logger')->nullOnInvalid(),
service('event_dispatcher')->nullOnInvalid(),
abstract_arg('Catch exception flag set in RememberMeFactory'),
service('security.authentication.session_strategy'),
])
->tag('monolog.logger', ['channel' => 'security'])
->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')
->set('security.authentication.provider.rememberme', RememberMeAuthenticationProvider::class)
->abstract()
->args([abstract_arg('User Checker')])
->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')
->set('security.rememberme.token.provider.in_memory', InMemoryTokenProvider::class)
->set('security.authentication.rememberme.services.abstract')
->abstract()
->args([
[], // User Providers
abstract_arg('Shared Token Key'),
abstract_arg('Shared Provider Key'),
[], // Options
service('logger')->nullOnInvalid(),
])
->tag('monolog.logger', ['channel' => 'security'])
->set('security.authentication.rememberme.services.persistent', PersistentTokenBasedRememberMeServices::class)
->parent('security.authentication.rememberme.services.abstract')
->abstract()
->set('security.authentication.rememberme.services.simplehash', TokenBasedRememberMeServices::class)
->parent('security.authentication.rememberme.services.abstract')
->abstract()
->set('security.rememberme.response_listener', ResponseListener::class)
->tag('kernel.event_subscriber')
;
};

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 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#AAA" d="M21 20.4V22H3v-1.6c0-3.7 2.4-6.9 5.8-8-1.7-1.1-2.9-3-2.9-5.2 0-3.4 2.7-6.1 6.1-6.1s6.1 2.7 6.1 6.1c0 2.2-1.2 4.1-2.9 5.2 3.4 1.1 5.8 4.3 5.8 8z"/></svg>

After

Width:  |  Height:  |  Size: 257 B

View File

@ -0,0 +1,441 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% block page_title 'Security' %}
{% block toolbar %}
{% if collector.firewall %}
{% set color_code = collector.enabled and not collector.authenticatorManagerEnabled ? 'yellow' %}
{% set icon %}
{{ include('@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>
<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, status: color_code }) }}
{% endif %}
{% endblock %}
{% block menu %}
<span class="label {{ not collector.firewall or not collector.token ? 'disabled' }}">
<span class="icon">{{ include('@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 == 'anon.' ? 'Anonymous' : collector.user }}</span>
<span class="label">Username</span>
</div>
<div class="metric">
<span class="value">{{ include('@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 {{ 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">{{ include('@WebProfiler/Icon/' ~ (collector.firewall.security_enabled ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="label">Security enabled</span>
</div>
<div class="metric">
<span class="value">{{ include('@WebProfiler/Icon/' ~ (collector.firewall.stateless ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="label">Stateless</span>
</div>
{% if collector.authenticatorManagerEnabled == false %}
<div class="metric">
<span class="value">{{ include('@WebProfiler/Icon/' ~ (collector.firewall.allows_anonymous ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="label">Allows anonymous</span>
</div>
{% endif %}
</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>
{% if collector.authenticatorManagerEnabled %}
<tr>
<th>authenticators</th>
<td>{{ collector.firewall.authenticators is empty ? '(none)' : profiler_dump(collector.firewall.authenticators, maxDepth=1) }}</td>
</tr>
{% else %}
<tr>
<th>listeners</th>
<td>{{ collector.firewall.listeners is empty ? '(none)' : profiler_dump(collector.firewall.listeners, maxDepth=1) }}</td>
</tr>
{% endif %}
</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>
<thead>
<tr>
<th>Authenticator</th>
<th>Supports</th>
<th>Duration</th>
<th>Passport</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">{{ include('@WebProfiler/Icon/' ~ (authenticator.supports ? '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>
</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,52 @@
<?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;
/**
* 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 $locator;
private $requestStack;
private $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,134 @@
<?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
{
private $name;
private $userChecker;
private $requestMatcher;
private $securityEnabled;
private $stateless;
private $provider;
private $context;
private $entryPoint;
private $accessDeniedHandler;
private $accessDeniedUrl;
private $authenticators;
private $switchUser;
public function __construct(string $name, string $userChecker, string $requestMatcher = null, bool $securityEnabled = true, bool $stateless = false, string $provider = null, string $context = null, string $entryPoint = null, string $accessDeniedHandler = null, string $accessDeniedUrl = null, array $authenticators = [], array $switchUser = null)
{
$this->name = $name;
$this->userChecker = $userChecker;
$this->requestMatcher = $requestMatcher;
$this->securityEnabled = $securityEnabled;
$this->stateless = $stateless;
$this->provider = $provider;
$this->context = $context;
$this->entryPoint = $entryPoint;
$this->accessDeniedHandler = $accessDeniedHandler;
$this->accessDeniedUrl = $accessDeniedUrl;
$this->authenticators = $authenticators;
$this->switchUser = $switchUser;
}
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;
}
/**
* @deprecated since Symfony 5.4
*/
public function allowsAnonymous(): bool
{
trigger_deprecation('symfony/security-bundle', '5.4', 'The "%s()" method is deprecated.', __METHOD__);
return \in_array('anonymous', $this->authenticators, true);
}
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;
}
/**
* @deprecated since Symfony 5.4, use {@see getAuthenticators()} instead
*/
public function getListeners(): array
{
trigger_deprecation('symfony/security-bundle', '5.4', 'Method "%s()" is deprecated, use "%s::getAuthenticators()" instead.', __METHOD__, __CLASS__);
return $this->getAuthenticators();
}
public function getAuthenticators(): array
{
return $this->authenticators;
}
public function getSwitchUser(): ?array
{
return $this->switchUser;
}
}

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\Security;
use Symfony\Component\Security\Http\Firewall\ExceptionListener;
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 $listeners;
private $exceptionListener;
private $logoutListener;
private $config;
/**
* @param iterable<mixed, callable> $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;
}
public function getConfig()
{
return $this->config;
}
/**
* @return iterable<mixed, callable>
*/
public function getListeners(): iterable
{
return $this->listeners;
}
public function getExceptionListener()
{
return $this->exceptionListener;
}
public function getLogoutListener()
{
return $this->logoutListener;
}
}

View File

@ -0,0 +1,84 @@
<?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 $container;
private $map;
public function __construct(ContainerInterface $container, iterable $map)
{
$this->container = $container;
$this->map = $map;
}
public function getListeners(Request $request)
{
$context = $this->getFirewallContext($request);
if (null === $context) {
return [[], null, null];
}
return [$context->getListeners(), $context->getExceptionListener(), $context->getLogoutListener()];
}
/**
* @return FirewallConfig|null
*/
public function getFirewallConfig(Request $request)
{
$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;
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)
{
$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,52 @@
<?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\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\Event\LogoutEvent;
use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface;
use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface;
/**
* @author Wouter de Jong <wouter@wouterj.nl>
*
* @internal
*/
class LegacyLogoutHandlerListener implements EventSubscriberInterface
{
private $logoutHandler;
public function __construct(object $logoutHandler)
{
if (!$logoutHandler instanceof LogoutSuccessHandlerInterface && !$logoutHandler instanceof LogoutHandlerInterface) {
throw new \InvalidArgumentException(sprintf('An instance of "%s" or "%s" must be passed to "%s", "%s" given.', LogoutHandlerInterface::class, LogoutSuccessHandlerInterface::class, __METHOD__, get_debug_type($logoutHandler)));
}
$this->logoutHandler = $logoutHandler;
}
public function onLogout(LogoutEvent $event): void
{
if ($this->logoutHandler instanceof LogoutSuccessHandlerInterface) {
$event->setResponse($this->logoutHandler->onLogoutSuccess($event->getRequest()));
} elseif ($this->logoutHandler instanceof LogoutHandlerInterface) {
$this->logoutHandler->logout($event->getRequest(), $event->getResponse(), $event->getToken());
}
}
public static function getSubscribedEvents(): array
{
return [
LogoutEvent::class => 'onLogout',
];
}
}

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\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;
}
/**
* {@inheritdoc}
*/
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,98 @@
<?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\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\Factory\AnonymousFactory;
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\GuardAuthenticationFactory;
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
{
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 GuardAuthenticationFactory());
$extension->addAuthenticatorFactory(new AnonymousFactory());
$extension->addAuthenticatorFactory(new CustomAuthenticatorFactory());
$extension->addAuthenticatorFactory(new LoginThrottlingFactory());
$extension->addAuthenticatorFactory(new LoginLinkFactory());
$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
)));
}
}

View File

@ -0,0 +1,69 @@
{
"name": "symfony/security-bundle",
"type": "symfony-bundle",
"description": "Provides a tight integration of the Security component into the Symfony full-stack framework",
"keywords": [],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=7.2.5",
"ext-xml": "*",
"symfony/config": "^4.4|^5.0|^6.0",
"symfony/dependency-injection": "^5.3|^6.0",
"symfony/deprecation-contracts": "^2.1|^3",
"symfony/event-dispatcher": "^5.1|^6.0",
"symfony/http-kernel": "^5.3|^6.0",
"symfony/http-foundation": "^5.3|^6.0",
"symfony/password-hasher": "^5.3|^6.0",
"symfony/polyfill-php80": "^1.16",
"symfony/security-core": "^5.4|^6.0",
"symfony/security-csrf": "^4.4|^5.0|^6.0",
"symfony/security-guard": "^5.3",
"symfony/security-http": "^5.4|^6.0"
},
"require-dev": {
"doctrine/annotations": "^1.10.4",
"symfony/asset": "^4.4|^5.0|^6.0",
"symfony/browser-kit": "^4.4|^5.0|^6.0",
"symfony/console": "^4.4|^5.0|^6.0",
"symfony/css-selector": "^4.4|^5.0|^6.0",
"symfony/dom-crawler": "^4.4|^5.0|^6.0",
"symfony/expression-language": "^4.4|^5.0|^6.0",
"symfony/form": "^4.4|^5.0|^6.0",
"symfony/framework-bundle": "^5.3|^6.0",
"symfony/ldap": "^5.3|^6.0",
"symfony/process": "^4.4|^5.0|^6.0",
"symfony/rate-limiter": "^5.2|^6.0",
"symfony/serializer": "^4.4|^5.0|^6.0",
"symfony/translation": "^4.4|^5.0|^6.0",
"symfony/twig-bundle": "^4.4|^5.0|^6.0",
"symfony/twig-bridge": "^4.4|^5.0|^6.0",
"symfony/validator": "^4.4|^5.0|^6.0",
"symfony/yaml": "^4.4|^5.0|^6.0",
"twig/twig": "^2.13|^3.0.4"
},
"conflict": {
"symfony/browser-kit": "<4.4",
"symfony/console": "<4.4",
"symfony/framework-bundle": "<4.4",
"symfony/ldap": "<5.1",
"symfony/twig-bundle": "<4.4"
},
"autoload": {
"psr-4": { "Symfony\\Bundle\\SecurityBundle\\": "" },
"exclude-from-classmap": [
"/Tests/"
]
},
"minimum-stability": "dev"
}