The start of something beautiful
This commit is contained in:
+358
@@ -0,0 +1,358 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
7.1
|
||||
---
|
||||
|
||||
* Mark class `ExpressionCacheWarmer` as `final`
|
||||
* Support multiple signature algorithms for OIDC Token
|
||||
* Support JWK or JWKSet for OIDC Token
|
||||
|
||||
7.0
|
||||
---
|
||||
|
||||
* Enabling SecurityBundle and not configuring it is not allowed
|
||||
* Remove the `enable_authenticator_manager` config option
|
||||
* Remove the `security.firewalls.logout.csrf_token_generator` config option, use `security.firewalls.logout.csrf_token_manager` instead
|
||||
* Remove the `require_previous_session` config option from authenticators
|
||||
|
||||
6.4
|
||||
---
|
||||
|
||||
* Deprecate `Security::ACCESS_DENIED_ERROR`, `AUTHENTICATION_ERROR` and `LAST_USERNAME` constants, use the ones on `SecurityRequestAttributes` instead
|
||||
* Allow an array of `pattern` in firewall configuration
|
||||
* Add `$badges` argument to `Security::login`
|
||||
* Deprecate the `require_previous_session` config option. Setting it has no effect anymore
|
||||
* Add `LogoutRouteLoader`
|
||||
|
||||
6.3
|
||||
---
|
||||
|
||||
* Deprecate enabling bundle and not configuring it
|
||||
* Add `_stateless` attribute to the request when firewall is stateless and the attribute is not already set
|
||||
* Add `StatelessAuthenticatorFactoryInterface` for authenticators targeting `stateless` firewalls only and that don't require a user provider
|
||||
* Modify "icon.svg" to improve accessibility for blind/low vision users
|
||||
* Make `Security::login()` return the authenticator response
|
||||
* Deprecate the `security.firewalls.logout.csrf_token_generator` config option, use `security.firewalls.logout.csrf_token_manager` instead
|
||||
* Make firewalls event dispatcher traceable on debug mode
|
||||
* Add `TokenHandlerFactoryInterface`, `OidcUserInfoTokenHandlerFactory`, `OidcTokenHandlerFactory` and `ServiceTokenHandlerFactory` for `AccessTokenFactory`
|
||||
|
||||
6.2
|
||||
---
|
||||
|
||||
* Add the `Security` helper class
|
||||
* Deprecate the `Symfony\Component\Security\Core\Security` service alias, use `Symfony\Bundle\SecurityBundle\Security` instead
|
||||
* Add `Security::getFirewallConfig()` to help to get the firewall configuration associated to the Request
|
||||
* Add `Security::login()` to login programmatically
|
||||
* Add `Security::logout()` to logout programmatically
|
||||
* Add `security.firewalls.logout.enable_csrf` to enable CSRF protection using the default CSRF token generator
|
||||
* Add RFC6750 Access Token support to allow token-based authentication
|
||||
* Add `security.firewalls.switch_user.target_route` option to configure redirect target route on switch user
|
||||
* Deprecate the `security.enable_authenticator_manager` config option
|
||||
|
||||
6.1
|
||||
---
|
||||
|
||||
* The `security.access_control` now accepts a `RequestMatcherInterface` under the `request_matcher` option as scope configuration
|
||||
* The `security.access_control` now accepts an `attributes` array to match request attributes in the `RequestMatcher`
|
||||
* The `security.access_control` now accepts a `route` option to match request route in the `RequestMatcher`
|
||||
* Display the inherited roles of the logged-in user in the Web Debug Toolbar
|
||||
|
||||
6.0
|
||||
---
|
||||
|
||||
* The `security.authorization_checker` and `security.token_storage` services are now private
|
||||
* Remove `UserPasswordEncoderCommand` class and the corresponding `user:encode-password` command,
|
||||
use `UserPasswordHashCommand` and `user:hash-password` instead
|
||||
* Remove 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
|
||||
* Remove 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
|
||||
* Remove the `logout.success_handler` and `logout.handlers` config options, register a listener on the `LogoutEvent` event instead
|
||||
* Remove `FirewallConfig::getListeners()`, use `FirewallConfig::getAuthenticators()` instead
|
||||
|
||||
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.
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* @final since Symfony 7.1
|
||||
*/
|
||||
class ExpressionCacheWarmer implements CacheWarmerInterface
|
||||
{
|
||||
private iterable $expressions;
|
||||
private ExpressionLanguage $expressionLanguage;
|
||||
|
||||
/**
|
||||
* @param iterable<mixed, Expression|string> $expressions
|
||||
*/
|
||||
public function __construct(iterable $expressions, ExpressionLanguage $expressionLanguage)
|
||||
{
|
||||
$this->expressions = $expressions;
|
||||
$this->expressionLanguage = $expressionLanguage;
|
||||
}
|
||||
|
||||
public function isOptional(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function warmUp(string $cacheDir, ?string $buildDir = null): array
|
||||
{
|
||||
foreach ($this->expressions as $expression) {
|
||||
$this->expressionLanguage->parse($expression, ['token', 'user', 'object', 'subject', 'role_names', 'request', 'trust_resolver']);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\Command;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security\FirewallContext;
|
||||
use Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
|
||||
|
||||
/**
|
||||
* @author Timo Bakx <timobakx@gmail.com>
|
||||
*/
|
||||
#[AsCommand(name: 'debug:firewall', description: 'Display information about your security firewall(s)')]
|
||||
final class DebugFirewallCommand extends Command
|
||||
{
|
||||
private array $firewallNames;
|
||||
private ContainerInterface $contexts;
|
||||
private ContainerInterface $eventDispatchers;
|
||||
private array $authenticators;
|
||||
|
||||
/**
|
||||
* @param string[] $firewallNames
|
||||
* @param AuthenticatorInterface[][] $authenticators
|
||||
*/
|
||||
public function __construct(array $firewallNames, ContainerInterface $contexts, ContainerInterface $eventDispatchers, array $authenticators)
|
||||
{
|
||||
$this->firewallNames = $firewallNames;
|
||||
$this->contexts = $contexts;
|
||||
$this->eventDispatchers = $eventDispatchers;
|
||||
$this->authenticators = $authenticators;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$exampleName = $this->getExampleName();
|
||||
|
||||
$this
|
||||
->setHelp(<<<EOF
|
||||
The <info>%command.name%</info> command displays the firewalls that are configured
|
||||
in your application:
|
||||
|
||||
<info>php %command.full_name%</info>
|
||||
|
||||
You can pass a firewall name to display more detailed information about
|
||||
a specific firewall:
|
||||
|
||||
<info>php %command.full_name% $exampleName</info>
|
||||
|
||||
To include all events and event listeners for a specific firewall, use the
|
||||
<info>events</info> option:
|
||||
|
||||
<info>php %command.full_name% --events $exampleName</info>
|
||||
|
||||
EOF
|
||||
)
|
||||
->setDefinition([
|
||||
new InputArgument('name', InputArgument::OPTIONAL, sprintf('A firewall name (for example "%s")', $exampleName)),
|
||||
new InputOption('events', null, InputOption::VALUE_NONE, 'Include a list of event listeners (only available in combination with the "name" argument)'),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$name = $input->getArgument('name');
|
||||
|
||||
if (null === $name) {
|
||||
$this->displayFirewallList($io);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$serviceId = sprintf('security.firewall.map.context.%s', $name);
|
||||
|
||||
if (!$this->contexts->has($serviceId)) {
|
||||
$io->error(sprintf('Firewall %s was not found. Available firewalls are: %s', $name, implode(', ', $this->firewallNames)));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** @var FirewallContext $context */
|
||||
$context = $this->contexts->get($serviceId);
|
||||
|
||||
$io->title(sprintf('Firewall "%s"', $name));
|
||||
|
||||
$this->displayFirewallSummary($name, $context, $io);
|
||||
|
||||
$this->displaySwitchUser($context, $io);
|
||||
|
||||
if ($input->getOption('events')) {
|
||||
$this->displayEventListeners($name, $context, $io);
|
||||
}
|
||||
|
||||
$this->displayAuthenticators($name, $io);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected function displayFirewallList(SymfonyStyle $io): void
|
||||
{
|
||||
$io->title('Firewalls');
|
||||
$io->text('The following firewalls are defined:');
|
||||
|
||||
$io->listing($this->firewallNames);
|
||||
|
||||
$io->comment(sprintf('To view details of a specific firewall, re-run this command with a firewall name. (e.g. <comment>debug:firewall %s</comment>)', $this->getExampleName()));
|
||||
}
|
||||
|
||||
protected function displayFirewallSummary(string $name, FirewallContext $context, SymfonyStyle $io): void
|
||||
{
|
||||
if (null === $context->getConfig()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rows = [
|
||||
['Name', $name],
|
||||
['Context', $context->getConfig()->getContext()],
|
||||
['Lazy', $context instanceof LazyFirewallContext ? 'Yes' : 'No'],
|
||||
['Stateless', $context->getConfig()->isStateless() ? 'Yes' : 'No'],
|
||||
['User Checker', $context->getConfig()->getUserChecker()],
|
||||
['Provider', $context->getConfig()->getProvider()],
|
||||
['Entry Point', $context->getConfig()->getEntryPoint()],
|
||||
['Access Denied URL', $context->getConfig()->getAccessDeniedUrl()],
|
||||
['Access Denied Handler', $context->getConfig()->getAccessDeniedHandler()],
|
||||
];
|
||||
|
||||
$io->table(
|
||||
['Option', 'Value'],
|
||||
$rows
|
||||
);
|
||||
}
|
||||
|
||||
private function displaySwitchUser(FirewallContext $context, SymfonyStyle $io): void
|
||||
{
|
||||
if ((null === $config = $context->getConfig()) || (null === $switchUser = $config->getSwitchUser())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$io->section('User switching');
|
||||
|
||||
$io->table(['Option', 'Value'], [
|
||||
['Parameter', $switchUser['parameter'] ?? ''],
|
||||
['Provider', $switchUser['provider'] ?? $config->getProvider()],
|
||||
['User Role', $switchUser['role'] ?? ''],
|
||||
]);
|
||||
}
|
||||
|
||||
protected function displayEventListeners(string $name, FirewallContext $context, SymfonyStyle $io): void
|
||||
{
|
||||
$io->title(sprintf('Event listeners for firewall "%s"', $name));
|
||||
|
||||
$dispatcherId = sprintf('security.event_dispatcher.%s', $name);
|
||||
|
||||
if (!$this->eventDispatchers->has($dispatcherId)) {
|
||||
$io->text('No event dispatcher has been registered for this firewall.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var EventDispatcherInterface $dispatcher */
|
||||
$dispatcher = $this->eventDispatchers->get($dispatcherId);
|
||||
|
||||
foreach ($dispatcher->getListeners() as $event => $listeners) {
|
||||
$io->section(sprintf('"%s" event', $event));
|
||||
|
||||
$rows = [];
|
||||
foreach ($listeners as $order => $listener) {
|
||||
$rows[] = [
|
||||
sprintf('#%d', $order + 1),
|
||||
$this->formatCallable($listener),
|
||||
$dispatcher->getListenerPriority($event, $listener),
|
||||
];
|
||||
}
|
||||
|
||||
$io->table(
|
||||
['Order', 'Callable', 'Priority'],
|
||||
$rows
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function displayAuthenticators(string $name, SymfonyStyle $io): void
|
||||
{
|
||||
$io->title(sprintf('Authenticators for firewall "%s"', $name));
|
||||
|
||||
$authenticators = $this->authenticators[$name] ?? [];
|
||||
|
||||
if (0 === \count($authenticators)) {
|
||||
$io->text('No authenticators have been registered for this firewall.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$io->table(
|
||||
['Classname'],
|
||||
array_map(
|
||||
fn ($authenticator) => [$authenticator::class],
|
||||
$authenticators
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function formatCallable(mixed $callable): string
|
||||
{
|
||||
if (\is_array($callable)) {
|
||||
if (\is_object($callable[0])) {
|
||||
return sprintf('%s::%s()', $callable[0]::class, $callable[1]);
|
||||
}
|
||||
|
||||
return sprintf('%s::%s()', $callable[0], $callable[1]);
|
||||
}
|
||||
|
||||
if (\is_string($callable)) {
|
||||
return sprintf('%s()', $callable);
|
||||
}
|
||||
|
||||
if ($callable instanceof \Closure) {
|
||||
$r = new \ReflectionFunction($callable);
|
||||
if ($r->isAnonymous()) {
|
||||
return 'Closure()';
|
||||
}
|
||||
if ($class = $r->getClosureCalledClass()) {
|
||||
return sprintf('%s::%s()', $class->name, $r->name);
|
||||
}
|
||||
|
||||
return $r->name.'()';
|
||||
}
|
||||
|
||||
if (method_exists($callable, '__invoke')) {
|
||||
return sprintf('%s::__invoke()', $callable::class);
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException('Callable is not describable.');
|
||||
}
|
||||
|
||||
private function getExampleName(): string
|
||||
{
|
||||
$name = 'main';
|
||||
|
||||
if (!\in_array($name, $this->firewallNames, true)) {
|
||||
$name = reset($this->firewallNames);
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
if ($input->mustSuggestArgumentValuesFor('name')) {
|
||||
$suggestions->suggestValues($this->firewallNames);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DataCollector;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener;
|
||||
use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
|
||||
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
|
||||
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\TraceableVoter;
|
||||
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
|
||||
use Symfony\Component\Security\Http\Firewall\SwitchUserListener;
|
||||
use Symfony\Component\Security\Http\FirewallMapInterface;
|
||||
use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator;
|
||||
use Symfony\Component\VarDumper\Caster\ClassStub;
|
||||
use Symfony\Component\VarDumper\Cloner\Data;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class SecurityDataCollector extends DataCollector implements LateDataCollectorInterface
|
||||
{
|
||||
private ?TokenStorageInterface $tokenStorage;
|
||||
private ?RoleHierarchyInterface $roleHierarchy;
|
||||
private ?LogoutUrlGenerator $logoutUrlGenerator;
|
||||
private ?AccessDecisionManagerInterface $accessDecisionManager;
|
||||
private ?FirewallMapInterface $firewallMap;
|
||||
private ?TraceableFirewallListener $firewall;
|
||||
private bool $hasVarDumper;
|
||||
|
||||
public function __construct(?TokenStorageInterface $tokenStorage = null, ?RoleHierarchyInterface $roleHierarchy = null, ?LogoutUrlGenerator $logoutUrlGenerator = null, ?AccessDecisionManagerInterface $accessDecisionManager = null, ?FirewallMapInterface $firewallMap = null, ?TraceableFirewallListener $firewall = null)
|
||||
{
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
$this->roleHierarchy = $roleHierarchy;
|
||||
$this->logoutUrlGenerator = $logoutUrlGenerator;
|
||||
$this->accessDecisionManager = $accessDecisionManager;
|
||||
$this->firewallMap = $firewallMap;
|
||||
$this->firewall = $firewall;
|
||||
$this->hasVarDumper = class_exists(ClassStub::class);
|
||||
}
|
||||
|
||||
public function collect(Request $request, Response $response, ?\Throwable $exception = null): void
|
||||
{
|
||||
if (null === $this->tokenStorage) {
|
||||
$this->data = [
|
||||
'enabled' => false,
|
||||
'authenticated' => false,
|
||||
'impersonated' => false,
|
||||
'impersonator_user' => null,
|
||||
'impersonation_exit_path' => null,
|
||||
'token' => null,
|
||||
'token_class' => null,
|
||||
'logout_url' => null,
|
||||
'user' => '',
|
||||
'roles' => [],
|
||||
'inherited_roles' => [],
|
||||
'supports_role_hierarchy' => null !== $this->roleHierarchy,
|
||||
];
|
||||
} elseif (null === $token = $this->tokenStorage->getToken()) {
|
||||
$this->data = [
|
||||
'enabled' => true,
|
||||
'authenticated' => false,
|
||||
'impersonated' => false,
|
||||
'impersonator_user' => null,
|
||||
'impersonation_exit_path' => null,
|
||||
'token' => null,
|
||||
'token_class' => null,
|
||||
'logout_url' => null,
|
||||
'user' => '',
|
||||
'roles' => [],
|
||||
'inherited_roles' => [],
|
||||
'supports_role_hierarchy' => null !== $this->roleHierarchy,
|
||||
];
|
||||
} else {
|
||||
$inheritedRoles = [];
|
||||
$assignedRoles = $token->getRoleNames();
|
||||
|
||||
$impersonatorUser = null;
|
||||
if ($token instanceof SwitchUserToken) {
|
||||
$originalToken = $token->getOriginalToken();
|
||||
$impersonatorUser = $originalToken->getUserIdentifier();
|
||||
}
|
||||
|
||||
if (null !== $this->roleHierarchy) {
|
||||
foreach ($this->roleHierarchy->getReachableRoleNames($assignedRoles) as $role) {
|
||||
if (!\in_array($role, $assignedRoles, true)) {
|
||||
$inheritedRoles[] = $role;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$logoutUrl = null;
|
||||
try {
|
||||
$logoutUrl = $this->logoutUrlGenerator?->getLogoutPath();
|
||||
} catch (\Exception) {
|
||||
// fail silently when the logout URL cannot be generated
|
||||
}
|
||||
|
||||
$this->data = [
|
||||
'enabled' => true,
|
||||
'authenticated' => (bool) $token->getUser(),
|
||||
'impersonated' => null !== $impersonatorUser,
|
||||
'impersonator_user' => $impersonatorUser,
|
||||
'impersonation_exit_path' => null,
|
||||
'token' => $token,
|
||||
'token_class' => $this->hasVarDumper ? new ClassStub($token::class) : $token::class,
|
||||
'logout_url' => $logoutUrl,
|
||||
'user' => $token->getUserIdentifier(),
|
||||
'roles' => $assignedRoles,
|
||||
'inherited_roles' => array_unique($inheritedRoles),
|
||||
'supports_role_hierarchy' => null !== $this->roleHierarchy,
|
||||
];
|
||||
}
|
||||
|
||||
// collect voters and access decision manager information
|
||||
if ($this->accessDecisionManager instanceof TraceableAccessDecisionManager) {
|
||||
$this->data['voter_strategy'] = $this->accessDecisionManager->getStrategy();
|
||||
$this->data['voters'] = [];
|
||||
|
||||
foreach ($this->accessDecisionManager->getVoters() as $voter) {
|
||||
if ($voter instanceof TraceableVoter) {
|
||||
$voter = $voter->getDecoratedVoter();
|
||||
}
|
||||
|
||||
$this->data['voters'][] = $this->hasVarDumper ? new ClassStub($voter::class) : $voter::class;
|
||||
}
|
||||
|
||||
// collect voter details
|
||||
$decisionLog = $this->accessDecisionManager->getDecisionLog();
|
||||
foreach ($decisionLog as $key => $log) {
|
||||
$decisionLog[$key]['voter_details'] = [];
|
||||
foreach ($log['voterDetails'] as $voterDetail) {
|
||||
$voterClass = $voterDetail['voter']::class;
|
||||
$classData = $this->hasVarDumper ? new ClassStub($voterClass) : $voterClass;
|
||||
$decisionLog[$key]['voter_details'][] = [
|
||||
'class' => $classData,
|
||||
'attributes' => $voterDetail['attributes'], // Only displayed for unanimous strategy
|
||||
'vote' => $voterDetail['vote'],
|
||||
];
|
||||
}
|
||||
unset($decisionLog[$key]['voterDetails']);
|
||||
}
|
||||
|
||||
$this->data['access_decision_log'] = $decisionLog;
|
||||
} else {
|
||||
$this->data['access_decision_log'] = [];
|
||||
$this->data['voter_strategy'] = 'unknown';
|
||||
$this->data['voters'] = [];
|
||||
}
|
||||
|
||||
// collect firewall context information
|
||||
$this->data['firewall'] = null;
|
||||
if ($this->firewallMap instanceof FirewallMap) {
|
||||
$firewallConfig = $this->firewallMap->getFirewallConfig($request);
|
||||
if (null !== $firewallConfig) {
|
||||
$this->data['firewall'] = [
|
||||
'name' => $firewallConfig->getName(),
|
||||
'request_matcher' => $firewallConfig->getRequestMatcher(),
|
||||
'security_enabled' => $firewallConfig->isSecurityEnabled(),
|
||||
'stateless' => $firewallConfig->isStateless(),
|
||||
'provider' => $firewallConfig->getProvider(),
|
||||
'context' => $firewallConfig->getContext(),
|
||||
'entry_point' => $firewallConfig->getEntryPoint(),
|
||||
'access_denied_handler' => $firewallConfig->getAccessDeniedHandler(),
|
||||
'access_denied_url' => $firewallConfig->getAccessDeniedUrl(),
|
||||
'user_checker' => $firewallConfig->getUserChecker(),
|
||||
'authenticators' => $firewallConfig->getAuthenticators(),
|
||||
];
|
||||
|
||||
// generate exit impersonation path from current request
|
||||
if ($this->data['impersonated'] && null !== $switchUserConfig = $firewallConfig->getSwitchUser()) {
|
||||
$exitPath = $request->getRequestUri();
|
||||
$exitPath .= null === $request->getQueryString() ? '?' : '&';
|
||||
$exitPath .= sprintf('%s=%s', urlencode($switchUserConfig['parameter']), SwitchUserListener::EXIT_VALUE);
|
||||
|
||||
$this->data['impersonation_exit_path'] = $exitPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collect firewall listeners information
|
||||
$this->data['listeners'] = [];
|
||||
if ($this->firewall) {
|
||||
$this->data['listeners'] = $this->firewall->getWrappedListeners();
|
||||
}
|
||||
|
||||
$this->data['authenticators'] = $this->firewall ? $this->firewall->getAuthenticatorsInfo() : [];
|
||||
}
|
||||
|
||||
public function reset(): void
|
||||
{
|
||||
$this->data = [];
|
||||
}
|
||||
|
||||
public function lateCollect(): void
|
||||
{
|
||||
$this->data = $this->cloneVar($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if security is enabled.
|
||||
*/
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return $this->data['enabled'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user.
|
||||
*/
|
||||
public function getUser(): string
|
||||
{
|
||||
return $this->data['user'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the roles of the user.
|
||||
*/
|
||||
public function getRoles(): array|Data
|
||||
{
|
||||
return $this->data['roles'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the inherited roles of the user.
|
||||
*/
|
||||
public function getInheritedRoles(): array|Data
|
||||
{
|
||||
return $this->data['inherited_roles'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the data contains information about inherited roles. Still the inherited
|
||||
* roles can be an empty array.
|
||||
*/
|
||||
public function supportsRoleHierarchy(): bool
|
||||
{
|
||||
return $this->data['supports_role_hierarchy'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user is authenticated or not.
|
||||
*/
|
||||
public function isAuthenticated(): bool
|
||||
{
|
||||
return $this->data['authenticated'];
|
||||
}
|
||||
|
||||
public function isImpersonated(): bool
|
||||
{
|
||||
return $this->data['impersonated'];
|
||||
}
|
||||
|
||||
public function getImpersonatorUser(): ?string
|
||||
{
|
||||
return $this->data['impersonator_user'];
|
||||
}
|
||||
|
||||
public function getImpersonationExitPath(): ?string
|
||||
{
|
||||
return $this->data['impersonation_exit_path'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class name of the security token.
|
||||
*/
|
||||
public function getTokenClass(): string|Data|null
|
||||
{
|
||||
return $this->data['token_class'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full security token class as Data object.
|
||||
*/
|
||||
public function getToken(): ?Data
|
||||
{
|
||||
return $this->data['token'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the logout URL.
|
||||
*/
|
||||
public function getLogoutUrl(): ?string
|
||||
{
|
||||
return $this->data['logout_url'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the FQCN of the security voters enabled in the application.
|
||||
*
|
||||
* @return string[]|Data
|
||||
*/
|
||||
public function getVoters(): array|Data
|
||||
{
|
||||
return $this->data['voters'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the strategy configured for the security voters.
|
||||
*/
|
||||
public function getVoterStrategy(): string
|
||||
{
|
||||
return $this->data['voter_strategy'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the log of the security decisions made by the access decision manager.
|
||||
*/
|
||||
public function getAccessDecisionLog(): array|Data
|
||||
{
|
||||
return $this->data['access_decision_log'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration of the current firewall context.
|
||||
*/
|
||||
public function getFirewall(): array|Data|null
|
||||
{
|
||||
return $this->data['firewall'];
|
||||
}
|
||||
|
||||
public function getListeners(): array|Data
|
||||
{
|
||||
return $this->data['listeners'];
|
||||
}
|
||||
|
||||
public function getAuthenticators(): array|Data
|
||||
{
|
||||
return $this->data['authenticators'];
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return 'security';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\Debug;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\EventListener\FirewallListener;
|
||||
use Symfony\Bundle\SecurityBundle\Security\FirewallContext;
|
||||
use Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticatorManagerListener;
|
||||
use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface;
|
||||
use Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
/**
|
||||
* Firewall collecting called security listeners and authenticators.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
final class TraceableFirewallListener extends FirewallListener implements ResetInterface
|
||||
{
|
||||
private array $wrappedListeners = [];
|
||||
private array $authenticatorsInfo = [];
|
||||
|
||||
public function getWrappedListeners(): array
|
||||
{
|
||||
return $this->wrappedListeners;
|
||||
}
|
||||
|
||||
public function getAuthenticatorsInfo(): array
|
||||
{
|
||||
return $this->authenticatorsInfo;
|
||||
}
|
||||
|
||||
public function reset(): void
|
||||
{
|
||||
$this->wrappedListeners = [];
|
||||
$this->authenticatorsInfo = [];
|
||||
}
|
||||
|
||||
protected function callListeners(RequestEvent $event, iterable $listeners): void
|
||||
{
|
||||
$wrappedListeners = [];
|
||||
$wrappedLazyListeners = [];
|
||||
$authenticatorManagerListener = null;
|
||||
|
||||
foreach ($listeners as $listener) {
|
||||
if ($listener instanceof LazyFirewallContext) {
|
||||
\Closure::bind(function () use (&$wrappedLazyListeners, &$wrappedListeners, &$authenticatorManagerListener) {
|
||||
$listeners = [];
|
||||
foreach ($this->listeners as $listener) {
|
||||
if (!$authenticatorManagerListener && $listener instanceof TraceableAuthenticatorManagerListener) {
|
||||
$authenticatorManagerListener = $listener;
|
||||
}
|
||||
if ($listener instanceof FirewallListenerInterface) {
|
||||
$listener = new WrappedLazyListener($listener);
|
||||
$listeners[] = $listener;
|
||||
$wrappedLazyListeners[] = $listener;
|
||||
} else {
|
||||
$listeners[] = function (RequestEvent $event) use ($listener, &$wrappedListeners) {
|
||||
$wrappedListener = new WrappedListener($listener);
|
||||
$wrappedListener($event);
|
||||
$wrappedListeners[] = $wrappedListener->getInfo();
|
||||
};
|
||||
}
|
||||
}
|
||||
$this->listeners = $listeners;
|
||||
}, $listener, FirewallContext::class)();
|
||||
|
||||
$listener($event);
|
||||
} else {
|
||||
$wrappedListener = $listener instanceof FirewallListenerInterface ? new WrappedLazyListener($listener) : new WrappedListener($listener);
|
||||
$wrappedListener($event);
|
||||
$wrappedListeners[] = $wrappedListener->getInfo();
|
||||
if (!$authenticatorManagerListener && $listener instanceof TraceableAuthenticatorManagerListener) {
|
||||
$authenticatorManagerListener = $listener;
|
||||
}
|
||||
}
|
||||
|
||||
if ($event->hasResponse()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($wrappedLazyListeners) {
|
||||
foreach ($wrappedLazyListeners as $lazyListener) {
|
||||
$this->wrappedListeners[] = $lazyListener->getInfo();
|
||||
}
|
||||
}
|
||||
|
||||
$this->wrappedListeners = array_merge($this->wrappedListeners, $wrappedListeners);
|
||||
|
||||
if ($authenticatorManagerListener) {
|
||||
$this->authenticatorsInfo = $authenticatorManagerListener->getAuthenticatorsInfo();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\Debug;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticatorManagerListener;
|
||||
use Symfony\Component\VarDumper\Caster\ClassStub;
|
||||
|
||||
/**
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait TraceableListenerTrait
|
||||
{
|
||||
private ?Response $response = null;
|
||||
private mixed $listener;
|
||||
private ?float $time = null;
|
||||
private object $stub;
|
||||
|
||||
/**
|
||||
* Proxies all method calls to the original listener.
|
||||
*/
|
||||
public function __call(string $method, array $arguments): mixed
|
||||
{
|
||||
return $this->listener->{$method}(...$arguments);
|
||||
}
|
||||
|
||||
public function getWrappedListener(): mixed
|
||||
{
|
||||
return $this->listener;
|
||||
}
|
||||
|
||||
public function getInfo(): array
|
||||
{
|
||||
return [
|
||||
'response' => $this->response,
|
||||
'time' => $this->time,
|
||||
'stub' => $this->stub ??= ClassStub::wrapCallable($this->listener instanceof TraceableAuthenticatorManagerListener ? $this->listener->getAuthenticatorManagerListener() : $this->listener),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\Debug;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\Security\Core\Exception\LazyResponseException;
|
||||
use Symfony\Component\Security\Http\Firewall\AbstractListener;
|
||||
use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface;
|
||||
|
||||
/**
|
||||
* Wraps a lazy security listener.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class WrappedLazyListener extends AbstractListener
|
||||
{
|
||||
use TraceableListenerTrait;
|
||||
|
||||
public function __construct(FirewallListenerInterface $listener)
|
||||
{
|
||||
$this->listener = $listener;
|
||||
}
|
||||
|
||||
public function supports(Request $request): ?bool
|
||||
{
|
||||
return $this->listener->supports($request);
|
||||
}
|
||||
|
||||
public function authenticate(RequestEvent $event): void
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
|
||||
try {
|
||||
$this->listener->authenticate($event);
|
||||
} catch (LazyResponseException $e) {
|
||||
$this->response = $e->getResponse();
|
||||
|
||||
throw $e;
|
||||
} finally {
|
||||
$this->time = microtime(true) - $startTime;
|
||||
}
|
||||
|
||||
$this->response = $event->getResponse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\Debug;
|
||||
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
|
||||
/**
|
||||
* Wraps a security listener for calls record.
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class WrappedListener
|
||||
{
|
||||
use TraceableListenerTrait;
|
||||
|
||||
/**
|
||||
* @param callable(RequestEvent):void $listener
|
||||
*/
|
||||
public function __construct(callable $listener)
|
||||
{
|
||||
$this->listener = $listener;
|
||||
}
|
||||
|
||||
public function __invoke(RequestEvent $event): void
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
($this->listener)($event);
|
||||
$this->time = microtime(true) - $startTime;
|
||||
$this->response = $event->getResponse();
|
||||
}
|
||||
}
|
||||
Vendored
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* Registers the expression language providers.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class AddExpressionLanguageProvidersPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if ($container->has('security.expression_language')) {
|
||||
$definition = $container->findDefinition('security.expression_language');
|
||||
foreach ($container->findTaggedServiceIds('security.expression_language_provider', true) as $id => $attributes) {
|
||||
$definition->addMethodCall('registerProvider', [new Reference($id)]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$container->hasDefinition('cache.system')) {
|
||||
$container->removeDefinition('cache.security_expression_language');
|
||||
$container->removeDefinition('cache.security_is_granted_attribute_expression_language');
|
||||
}
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Exception\LogicException;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\TraceableVoter;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
|
||||
|
||||
/**
|
||||
* Adds all configured security voters to the access decision manager.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class AddSecurityVotersPass implements CompilerPassInterface
|
||||
{
|
||||
use PriorityTaggedServiceTrait;
|
||||
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if (!$container->hasDefinition('security.access.decision_manager')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$voters = $this->findAndSortTaggedServices('security.voter', $container);
|
||||
if (!$voters) {
|
||||
throw new LogicException('No security voters found. You need to tag at least one with "security.voter".');
|
||||
}
|
||||
|
||||
$debug = $container->getParameter('kernel.debug');
|
||||
$voterServices = [];
|
||||
foreach ($voters as $voter) {
|
||||
$voterServiceId = (string) $voter;
|
||||
$definition = $container->getDefinition($voterServiceId);
|
||||
|
||||
$class = $container->getParameterBag()->resolveValue($definition->getClass());
|
||||
|
||||
if (!is_a($class, VoterInterface::class, true)) {
|
||||
throw new LogicException(sprintf('"%s" must implement the "%s" when used as a voter.', $class, VoterInterface::class));
|
||||
}
|
||||
|
||||
if ($debug) {
|
||||
$voterServices[] = new Reference($debugVoterServiceId = '.debug.security.voter.'.$voterServiceId);
|
||||
|
||||
$container
|
||||
->register($debugVoterServiceId, TraceableVoter::class)
|
||||
->addArgument($voter)
|
||||
->addArgument(new Reference('event_dispatcher'));
|
||||
} else {
|
||||
$voterServices[] = $voter;
|
||||
}
|
||||
}
|
||||
|
||||
$container->getDefinition('security.access.decision_manager')
|
||||
->replaceArgument(0, new IteratorArgument($voterServices));
|
||||
}
|
||||
}
|
||||
Vendored
+45
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* Uses the session domain to restrict allowed redirection targets.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class AddSessionDomainConstraintPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if (!$container->hasParameter('session.storage.options') || !$container->has('security.http_utils')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sessionOptions = $container->getParameter('session.storage.options');
|
||||
$domainRegexp = empty($sessionOptions['cookie_domain']) ? '%%s' : sprintf('(?:%%%%s|(?:.+\.)?%s)', preg_quote(trim($sessionOptions['cookie_domain'], '.')));
|
||||
|
||||
if ('auto' === ($sessionOptions['cookie_secure'] ?? null)) {
|
||||
$secureDomainRegexp = sprintf('{^https://%s$}i', $domainRegexp);
|
||||
$domainRegexp = 'https?://'.$domainRegexp;
|
||||
} else {
|
||||
$secureDomainRegexp = null;
|
||||
$domainRegexp = (empty($sessionOptions['cookie_secure']) ? 'https?://' : 'https://').$domainRegexp;
|
||||
}
|
||||
|
||||
$container->findDefinition('security.http_utils')
|
||||
->addArgument(sprintf('{^%s$}i', $domainRegexp))
|
||||
->addArgument($secureDomainRegexp);
|
||||
}
|
||||
}
|
||||
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* Cleans up the remember me verifier cache if cache is missing.
|
||||
*
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class CleanRememberMeVerifierPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if (!$container->hasDefinition('cache.system')) {
|
||||
$container->removeDefinition('cache.security_token_verifier');
|
||||
}
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher;
|
||||
|
||||
/**
|
||||
* @author Mathieu Lechat <mathieu.lechat@les-tilleuls.coop>
|
||||
*/
|
||||
class MakeFirewallsEventDispatcherTraceablePass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if (!$container->has('event_dispatcher') || !$container->hasParameter('security.firewalls')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$container->getParameter('kernel.debug') || !$container->has('debug.stopwatch')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$dispatchersId = [];
|
||||
|
||||
foreach ($container->getParameter('security.firewalls') as $firewallName) {
|
||||
$dispatcherId = 'security.event_dispatcher.'.$firewallName;
|
||||
|
||||
if (!$container->has($dispatcherId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$dispatchersId[$dispatcherId] = 'debug.'.$dispatcherId;
|
||||
|
||||
$container->register($dispatchersId[$dispatcherId], TraceableEventDispatcher::class)
|
||||
->setDecoratedService($dispatcherId)
|
||||
->setArguments([
|
||||
new Reference($dispatchersId[$dispatcherId].'.inner'),
|
||||
new Reference('debug.stopwatch'),
|
||||
new Reference('logger', ContainerInterface::NULL_ON_INVALID_REFERENCE),
|
||||
new Reference('request_stack', ContainerInterface::NULL_ON_INVALID_REFERENCE),
|
||||
])
|
||||
->addTag('monolog.logger', ['channel' => 'event'])
|
||||
->addTag('kernel.reset', ['method' => 'reset']);
|
||||
}
|
||||
|
||||
foreach (['kernel.event_subscriber', 'kernel.event_listener'] as $tagName) {
|
||||
foreach ($container->findTaggedServiceIds($tagName) as $taggedServiceId => $tags) {
|
||||
$taggedServiceDefinition = $container->findDefinition($taggedServiceId);
|
||||
$taggedServiceDefinition->clearTag($tagName);
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
if ($dispatcherId = $tag['dispatcher'] ?? null) {
|
||||
$tag['dispatcher'] = $dispatchersId[$dispatcherId] ?? $dispatcherId;
|
||||
}
|
||||
$taggedServiceDefinition->addTag($tagName, $tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface;
|
||||
use Symfony\Component\Security\Http\EventListener\CsrfProtectionListener;
|
||||
use Symfony\Component\Security\Http\EventListener\CsrfTokenClearingLogoutListener;
|
||||
use Symfony\Component\Security\Http\EventListener\IsCsrfTokenValidAttributeListener;
|
||||
|
||||
/**
|
||||
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class RegisterCsrfFeaturesPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
$this->registerCsrfProtectionListener($container);
|
||||
$this->registerLogoutHandler($container);
|
||||
}
|
||||
|
||||
private function registerCsrfProtectionListener(ContainerBuilder $container): void
|
||||
{
|
||||
if (!$container->hasDefinition('cache.system')) {
|
||||
$container->removeDefinition('cache.security_is_csrf_token_valid_attribute_expression_language');
|
||||
}
|
||||
|
||||
if (!$container->has('security.authenticator.manager') || !$container->has('security.csrf.token_manager')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$container->register('security.listener.csrf_protection', CsrfProtectionListener::class)
|
||||
->addArgument(new Reference('security.csrf.token_manager'))
|
||||
->addTag('kernel.event_subscriber');
|
||||
|
||||
$container->register('controller.is_csrf_token_valid_attribute_listener', IsCsrfTokenValidAttributeListener::class)
|
||||
->addArgument(new Reference('security.csrf.token_manager'))
|
||||
->addArgument(new Reference('security.is_csrf_token_valid_attribute_expression_language', ContainerInterface::NULL_ON_INVALID_REFERENCE))
|
||||
->addTag('kernel.event_subscriber');
|
||||
}
|
||||
|
||||
protected function registerLogoutHandler(ContainerBuilder $container): void
|
||||
{
|
||||
if (!$container->has('security.logout_listener') || !$container->has('security.csrf.token_storage')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$csrfTokenStorage = $container->findDefinition('security.csrf.token_storage');
|
||||
$csrfTokenStorageClass = $container->getParameterBag()->resolveValue($csrfTokenStorage->getClass());
|
||||
|
||||
if (!is_subclass_of($csrfTokenStorageClass, ClearableTokenStorageInterface::class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$container->register('security.logout.listener.csrf_token_clearing', CsrfTokenClearingLogoutListener::class)
|
||||
->addArgument(new Reference('security.csrf.token_storage'))
|
||||
->addTag('kernel.event_subscriber');
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
|
||||
|
||||
/**
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*/
|
||||
class RegisterEntryPointPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if (!$container->hasParameter('security.firewalls')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$firewalls = $container->getParameter('security.firewalls');
|
||||
foreach ($firewalls as $firewallName) {
|
||||
if (!$container->hasDefinition('security.authenticator.manager.'.$firewallName) || !$container->hasParameter('security.'.$firewallName.'._indexed_authenticators')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$entryPoints = [];
|
||||
$indexedAuthenticators = $container->getParameter('security.'.$firewallName.'._indexed_authenticators');
|
||||
// this is a compile-only parameter, removing it cleans up space and avoids unintended usage
|
||||
$container->getParameterBag()->remove('security.'.$firewallName.'._indexed_authenticators');
|
||||
foreach ($indexedAuthenticators as $key => $authenticatorId) {
|
||||
if (!$container->has($authenticatorId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// because this pass runs before ResolveChildDefinitionPass, child definitions didn't inherit the parent class yet
|
||||
$definition = $container->findDefinition($authenticatorId);
|
||||
while (!($authenticatorClass = $definition->getClass()) && $definition instanceof ChildDefinition) {
|
||||
$definition = $container->findDefinition($definition->getParent());
|
||||
}
|
||||
|
||||
if (is_a($authenticatorClass, AuthenticationEntryPointInterface::class, true)) {
|
||||
$entryPoints[$key] = $authenticatorId;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$entryPoints) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$config = $container->getDefinition('security.firewall.map.config.'.$firewallName);
|
||||
$configuredEntryPoint = $config->getArgument(7);
|
||||
|
||||
if (null !== $configuredEntryPoint) {
|
||||
// allow entry points to be configured by authenticator key (e.g. "http_basic")
|
||||
$entryPoint = $entryPoints[$configuredEntryPoint] ?? $configuredEntryPoint;
|
||||
} elseif (1 === \count($entryPoints)) {
|
||||
$entryPoint = array_shift($entryPoints);
|
||||
} else {
|
||||
$entryPointNames = [];
|
||||
foreach ($entryPoints as $key => $serviceId) {
|
||||
$entryPointNames[] = is_numeric($key) ? $serviceId : $key;
|
||||
}
|
||||
|
||||
throw new InvalidConfigurationException(sprintf('Because you have multiple authenticators in firewall "%s", you need to set the "entry_point" key to one of your authenticators ("%s") or a service ID implementing "%s". The "entry_point" determines what should happen (e.g. redirect to "/login") when an anonymous user tries to access a protected page.', $firewallName, implode('", "', $entryPointNames), AuthenticationEntryPointInterface::class));
|
||||
}
|
||||
|
||||
$config->replaceArgument(7, $entryPoint);
|
||||
$container->getDefinition('security.exception_listener.'.$firewallName)->replaceArgument(4, new Reference($entryPoint));
|
||||
}
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\Security\Core\AuthenticationEvents;
|
||||
use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
|
||||
use Symfony\Component\Security\Http\Event\AuthenticationTokenCreatedEvent;
|
||||
use Symfony\Component\Security\Http\Event\CheckPassportEvent;
|
||||
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
|
||||
use Symfony\Component\Security\Http\Event\LoginFailureEvent;
|
||||
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
|
||||
use Symfony\Component\Security\Http\Event\LogoutEvent;
|
||||
use Symfony\Component\Security\Http\Event\TokenDeauthenticatedEvent;
|
||||
use Symfony\Component\Security\Http\SecurityEvents;
|
||||
|
||||
/**
|
||||
* Makes sure all event listeners on the global dispatcher are also listening
|
||||
* to events on the firewall-specific dispatchers.
|
||||
*
|
||||
* This compiler pass must be run after RegisterListenersPass of the
|
||||
* EventDispatcher component.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class RegisterGlobalSecurityEventListenersPass implements CompilerPassInterface
|
||||
{
|
||||
private const EVENT_BUBBLING_EVENTS = [
|
||||
CheckPassportEvent::class,
|
||||
LoginFailureEvent::class,
|
||||
LoginSuccessEvent::class,
|
||||
LogoutEvent::class,
|
||||
AuthenticationTokenCreatedEvent::class,
|
||||
AuthenticationSuccessEvent::class,
|
||||
InteractiveLoginEvent::class,
|
||||
TokenDeauthenticatedEvent::class,
|
||||
|
||||
// When events are registered by their name
|
||||
AuthenticationEvents::AUTHENTICATION_SUCCESS,
|
||||
SecurityEvents::INTERACTIVE_LOGIN,
|
||||
];
|
||||
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if (!$container->has('event_dispatcher') || !$container->hasParameter('security.firewalls')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$firewallDispatchers = [];
|
||||
foreach ($container->getParameter('security.firewalls') as $firewallName) {
|
||||
if (!$container->has('security.event_dispatcher.'.$firewallName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$firewallDispatchers[] = $container->findDefinition('security.event_dispatcher.'.$firewallName);
|
||||
}
|
||||
|
||||
$globalDispatcher = $container->findDefinition('event_dispatcher');
|
||||
foreach ($globalDispatcher->getMethodCalls() as $methodCall) {
|
||||
if ('addListener' !== $methodCall[0]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$methodCallArguments = $methodCall[1];
|
||||
if (!\in_array($methodCallArguments[0], self::EVENT_BUBBLING_EVENTS, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($firewallDispatchers as $firewallDispatcher) {
|
||||
$firewallDispatcher->addMethodCall('addListener', $methodCallArguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\ServiceLocator;
|
||||
|
||||
/**
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class RegisterLdapLocatorPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
$definition = $container->setDefinition('security.ldap_locator', new Definition(ServiceLocator::class));
|
||||
|
||||
$locators = [];
|
||||
foreach ($container->findTaggedServiceIds('ldap') as $serviceId => $tags) {
|
||||
$locators[$serviceId] = new ServiceClosureArgument(new Reference($serviceId));
|
||||
}
|
||||
|
||||
$definition->addArgument($locators);
|
||||
}
|
||||
}
|
||||
Vendored
+53
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Monolog\Processor\ProcessorInterface;
|
||||
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
|
||||
/**
|
||||
* Injects the session tracker enabler in "security.context_listener" + binds "security.untracked_token_storage" to ProcessorInterface instances.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class RegisterTokenUsageTrackingPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if (!$container->has('security.untracked_token_storage')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$processorAutoconfiguration = $container->registerForAutoconfiguration(ProcessorInterface::class);
|
||||
$processorAutoconfiguration->setBindings($processorAutoconfiguration->getBindings() + [
|
||||
TokenStorageInterface::class => new BoundArgument(new Reference('security.untracked_token_storage'), false),
|
||||
]);
|
||||
|
||||
if (!$container->has('session.factory')) {
|
||||
$container->setAlias('security.token_storage', 'security.untracked_token_storage')->setPublic(true);
|
||||
$container->getDefinition('security.untracked_token_storage')->addTag('kernel.reset', ['method' => 'reset']);
|
||||
} elseif ($container->hasDefinition('security.context_listener')) {
|
||||
$tokenStorageClass = $container->getParameterBag()->resolveValue($container->findDefinition('security.token_storage')->getClass());
|
||||
|
||||
if (method_exists($tokenStorageClass, 'enableUsageTracking')) {
|
||||
$container->getDefinition('security.context_listener')
|
||||
->setArgument(6, [new Reference('security.token_storage'), 'enableUsageTracking']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\RememberMe\DecoratedRememberMeHandler;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* Replaces the DecoratedRememberMeHandler services with the real definition.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ReplaceDecoratedRememberMeHandlerPass implements CompilerPassInterface
|
||||
{
|
||||
private const HANDLER_TAG = 'security.remember_me_handler';
|
||||
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
$handledFirewalls = [];
|
||||
foreach ($container->findTaggedServiceIds(self::HANDLER_TAG) as $definitionId => $rememberMeHandlerTags) {
|
||||
$definition = $container->findDefinition($definitionId);
|
||||
if (DecoratedRememberMeHandler::class !== $definition->getClass()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get the actual custom remember me handler definition (passed to the decorator)
|
||||
$realRememberMeHandler = $container->findDefinition((string) $definition->getArgument(0));
|
||||
if (null === $realRememberMeHandler) {
|
||||
throw new \LogicException(sprintf('Invalid service definition for custom remember me handler; no service found with ID "%s".', (string) $definition->getArgument(0)));
|
||||
}
|
||||
|
||||
foreach ($rememberMeHandlerTags as $rememberMeHandlerTag) {
|
||||
// some custom handlers may be used on multiple firewalls in the same application
|
||||
if (\in_array($rememberMeHandlerTag['firewall'], $handledFirewalls, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rememberMeHandler = clone $realRememberMeHandler;
|
||||
$rememberMeHandler->addTag(self::HANDLER_TAG, $rememberMeHandlerTag);
|
||||
$container->setDefinition('security.authenticator.remember_me_handler.'.$rememberMeHandlerTag['firewall'], $rememberMeHandler);
|
||||
|
||||
$handledFirewalls[] = $rememberMeHandlerTag['firewall'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface;
|
||||
|
||||
/**
|
||||
* Sorts firewall listeners based on the execution order provided by FirewallListenerInterface::getPriority().
|
||||
*
|
||||
* @author Christian Scheb <me@christianscheb.de>
|
||||
*/
|
||||
class SortFirewallListenersPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if (!$container->hasParameter('security.firewalls')) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($container->getParameter('security.firewalls') as $firewallName) {
|
||||
$firewallContextDefinition = $container->getDefinition('security.firewall.map.context.'.$firewallName);
|
||||
$this->sortFirewallContextListeners($firewallContextDefinition, $container);
|
||||
}
|
||||
}
|
||||
|
||||
private function sortFirewallContextListeners(Definition $definition, ContainerBuilder $container): void
|
||||
{
|
||||
/** @var IteratorArgument $listenerIteratorArgument */
|
||||
$listenerIteratorArgument = $definition->getArgument(0);
|
||||
$prioritiesByServiceId = $this->getListenerPriorities($listenerIteratorArgument, $container);
|
||||
|
||||
$listeners = $listenerIteratorArgument->getValues();
|
||||
usort($listeners, fn (Reference $a, Reference $b) => $prioritiesByServiceId[(string) $b] <=> $prioritiesByServiceId[(string) $a]);
|
||||
|
||||
$listenerIteratorArgument->setValues(array_values($listeners));
|
||||
}
|
||||
|
||||
private function getListenerPriorities(IteratorArgument $listeners, ContainerBuilder $container): array
|
||||
{
|
||||
$priorities = [];
|
||||
|
||||
foreach ($listeners->getValues() as $reference) {
|
||||
$id = (string) $reference;
|
||||
$def = $container->getDefinition($id);
|
||||
|
||||
// We must assume that the class value has been correctly filled, even if the service is created by a factory
|
||||
$class = $def->getClass();
|
||||
|
||||
if (!$r = $container->getReflectionClass($class)) {
|
||||
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
|
||||
}
|
||||
|
||||
$priority = 0;
|
||||
if ($r->isSubclassOf(FirewallListenerInterface::class)) {
|
||||
$priority = $r->getMethod('getPriority')->invoke(null);
|
||||
}
|
||||
|
||||
$priorities[$id] = $priority;
|
||||
}
|
||||
|
||||
return $priorities;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,463 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AbstractFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface;
|
||||
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
|
||||
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
|
||||
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
||||
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
|
||||
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
|
||||
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy;
|
||||
|
||||
/**
|
||||
* SecurityExtension configuration structure.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class MainConfiguration implements ConfigurationInterface
|
||||
{
|
||||
/** @internal */
|
||||
public const STRATEGY_AFFIRMATIVE = 'affirmative';
|
||||
/** @internal */
|
||||
public const STRATEGY_CONSENSUS = 'consensus';
|
||||
/** @internal */
|
||||
public const STRATEGY_UNANIMOUS = 'unanimous';
|
||||
/** @internal */
|
||||
public const STRATEGY_PRIORITY = 'priority';
|
||||
|
||||
private array $factories;
|
||||
private array $userProviderFactories;
|
||||
|
||||
/**
|
||||
* @param array<AuthenticatorFactoryInterface> $factories
|
||||
*/
|
||||
public function __construct(array $factories, array $userProviderFactories)
|
||||
{
|
||||
$this->factories = $factories;
|
||||
$this->userProviderFactories = $userProviderFactories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the configuration tree builder.
|
||||
*/
|
||||
public function getConfigTreeBuilder(): TreeBuilder
|
||||
{
|
||||
$tb = new TreeBuilder('security');
|
||||
$rootNode = $tb->getRootNode();
|
||||
|
||||
$rootNode
|
||||
->children()
|
||||
->scalarNode('access_denied_url')->defaultNull()->example('/foo/error403')->end()
|
||||
->enumNode('session_fixation_strategy')
|
||||
->values([SessionAuthenticationStrategy::NONE, SessionAuthenticationStrategy::MIGRATE, SessionAuthenticationStrategy::INVALIDATE])
|
||||
->defaultValue(SessionAuthenticationStrategy::MIGRATE)
|
||||
->end()
|
||||
->booleanNode('hide_user_not_found')->defaultTrue()->end()
|
||||
->booleanNode('erase_credentials')->defaultTrue()->end()
|
||||
->arrayNode('access_decision_manager')
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->enumNode('strategy')
|
||||
->values($this->getAccessDecisionStrategies())
|
||||
->end()
|
||||
->scalarNode('service')->end()
|
||||
->scalarNode('strategy_service')->end()
|
||||
->booleanNode('allow_if_all_abstain')->defaultFalse()->end()
|
||||
->booleanNode('allow_if_equal_granted_denied')->defaultTrue()->end()
|
||||
->end()
|
||||
->validate()
|
||||
->ifTrue(fn ($v) => isset($v['strategy'], $v['service']))
|
||||
->thenInvalid('"strategy" and "service" cannot be used together.')
|
||||
->end()
|
||||
->validate()
|
||||
->ifTrue(fn ($v) => isset($v['strategy'], $v['strategy_service']))
|
||||
->thenInvalid('"strategy" and "strategy_service" cannot be used together.')
|
||||
->end()
|
||||
->validate()
|
||||
->ifTrue(fn ($v) => isset($v['service'], $v['strategy_service']))
|
||||
->thenInvalid('"service" and "strategy_service" cannot be used together.')
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
|
||||
$this->addPasswordHashersSection($rootNode);
|
||||
$this->addProvidersSection($rootNode);
|
||||
$this->addFirewallsSection($rootNode, $this->factories);
|
||||
$this->addAccessControlSection($rootNode);
|
||||
$this->addRoleHierarchySection($rootNode);
|
||||
|
||||
return $tb;
|
||||
}
|
||||
|
||||
private function addRoleHierarchySection(ArrayNodeDefinition $rootNode): void
|
||||
{
|
||||
$rootNode
|
||||
->fixXmlConfig('role', 'role_hierarchy')
|
||||
->children()
|
||||
->arrayNode('role_hierarchy')
|
||||
->useAttributeAsKey('id')
|
||||
->prototype('array')
|
||||
->performNoDeepMerging()
|
||||
->beforeNormalization()->ifString()->then(fn ($v) => ['value' => $v])->end()
|
||||
->beforeNormalization()
|
||||
->ifTrue(fn ($v) => \is_array($v) && isset($v['value']))
|
||||
->then(fn ($v) => preg_split('/\s*,\s*/', $v['value']))
|
||||
->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
|
||||
private function addAccessControlSection(ArrayNodeDefinition $rootNode): void
|
||||
{
|
||||
$rootNode
|
||||
->fixXmlConfig('rule', 'access_control')
|
||||
->children()
|
||||
->arrayNode('access_control')
|
||||
->cannotBeOverwritten()
|
||||
->prototype('array')
|
||||
->fixXmlConfig('ip')
|
||||
->fixXmlConfig('method')
|
||||
->fixXmlConfig('attribute')
|
||||
->children()
|
||||
->scalarNode('request_matcher')->defaultNull()->end()
|
||||
->scalarNode('requires_channel')->defaultNull()->end()
|
||||
->scalarNode('path')
|
||||
->defaultNull()
|
||||
->info('use the urldecoded format')
|
||||
->example('^/path to resource/')
|
||||
->end()
|
||||
->scalarNode('host')->defaultNull()->end()
|
||||
->integerNode('port')->defaultNull()->end()
|
||||
->arrayNode('ips')
|
||||
->beforeNormalization()->ifString()->then(fn ($v) => [$v])->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->arrayNode('attributes')
|
||||
->useAttributeAsKey('key')
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->scalarNode('route')->defaultNull()->end()
|
||||
->arrayNode('methods')
|
||||
->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->scalarNode('allow_if')->defaultNull()->end()
|
||||
->end()
|
||||
->fixXmlConfig('role')
|
||||
->children()
|
||||
->arrayNode('roles')
|
||||
->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<AuthenticatorFactoryInterface> $factories
|
||||
*/
|
||||
private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $factories): void
|
||||
{
|
||||
$firewallNodeBuilder = $rootNode
|
||||
->fixXmlConfig('firewall')
|
||||
->children()
|
||||
->arrayNode('firewalls')
|
||||
->isRequired()
|
||||
->requiresAtLeastOneElement()
|
||||
->disallowNewKeysInSubsequentConfigs()
|
||||
->useAttributeAsKey('name')
|
||||
->prototype('array')
|
||||
->fixXmlConfig('required_badge')
|
||||
->children()
|
||||
;
|
||||
|
||||
$firewallNodeBuilder
|
||||
->scalarNode('pattern')
|
||||
->beforeNormalization()
|
||||
->ifArray()
|
||||
->then(fn ($v) => sprintf('(?:%s)', implode('|', $v)))
|
||||
->end()
|
||||
->end()
|
||||
->scalarNode('host')->end()
|
||||
->arrayNode('methods')
|
||||
->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->booleanNode('security')->defaultTrue()->end()
|
||||
->scalarNode('user_checker')
|
||||
->defaultValue('security.user_checker')
|
||||
->treatNullLike('security.user_checker')
|
||||
->info('The UserChecker to use when authenticating users in this firewall.')
|
||||
->end()
|
||||
->scalarNode('request_matcher')->end()
|
||||
->scalarNode('access_denied_url')->end()
|
||||
->scalarNode('access_denied_handler')->end()
|
||||
->scalarNode('entry_point')
|
||||
->info(sprintf('An enabled authenticator name or a service id that implements "%s"', AuthenticationEntryPointInterface::class))
|
||||
->end()
|
||||
->scalarNode('provider')->end()
|
||||
->booleanNode('stateless')->defaultFalse()->end()
|
||||
->booleanNode('lazy')->defaultFalse()->end()
|
||||
->scalarNode('context')->cannotBeEmpty()->end()
|
||||
->arrayNode('logout')
|
||||
->treatTrueLike([])
|
||||
->canBeUnset()
|
||||
->beforeNormalization()
|
||||
->ifTrue(fn ($v): bool => \is_array($v) && (isset($v['csrf_token_manager']) xor isset($v['enable_csrf'])))
|
||||
->then(function (array $v): array {
|
||||
if (isset($v['csrf_token_manager'])) {
|
||||
$v['enable_csrf'] = true;
|
||||
} elseif ($v['enable_csrf']) {
|
||||
$v['csrf_token_manager'] = 'security.csrf.token_manager';
|
||||
}
|
||||
|
||||
return $v;
|
||||
})
|
||||
->end()
|
||||
->children()
|
||||
->booleanNode('enable_csrf')->defaultNull()->end()
|
||||
->scalarNode('csrf_token_id')->defaultValue('logout')->end()
|
||||
->scalarNode('csrf_parameter')->defaultValue('_csrf_token')->end()
|
||||
->scalarNode('csrf_token_manager')->end()
|
||||
->scalarNode('path')->defaultValue('/logout')->end()
|
||||
->scalarNode('target')->defaultValue('/')->end()
|
||||
->booleanNode('invalidate_session')->defaultTrue()->end()
|
||||
->arrayNode('clear_site_data')
|
||||
->performNoDeepMerging()
|
||||
->beforeNormalization()->ifString()->then(fn ($v) => $v ? array_map('trim', explode(',', $v)) : [])->end()
|
||||
->enumPrototype()
|
||||
->values([
|
||||
'*', 'cache', 'cookies', 'storage', 'executionContexts',
|
||||
])
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->fixXmlConfig('delete_cookie')
|
||||
->children()
|
||||
->arrayNode('delete_cookies')
|
||||
->normalizeKeys(false)
|
||||
->beforeNormalization()
|
||||
->ifTrue(fn ($v) => \is_array($v) && \is_int(key($v)))
|
||||
->then(fn ($v) => array_map(fn ($v) => ['name' => $v], $v))
|
||||
->end()
|
||||
->useAttributeAsKey('name')
|
||||
->prototype('array')
|
||||
->children()
|
||||
->scalarNode('path')->defaultNull()->end()
|
||||
->scalarNode('domain')->defaultNull()->end()
|
||||
->scalarNode('secure')->defaultFalse()->end()
|
||||
->scalarNode('samesite')->defaultNull()->end()
|
||||
->scalarNode('partitioned')->defaultFalse()->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('switch_user')
|
||||
->canBeUnset()
|
||||
->children()
|
||||
->scalarNode('provider')->end()
|
||||
->scalarNode('parameter')->defaultValue('_switch_user')->end()
|
||||
->scalarNode('role')->defaultValue('ROLE_ALLOWED_TO_SWITCH')->end()
|
||||
->scalarNode('target_route')->defaultValue(null)->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('required_badges')
|
||||
->info('A list of badges that must be present on the authenticated passport.')
|
||||
->validate()
|
||||
->always()
|
||||
->then(function ($requiredBadges) {
|
||||
return array_map(function ($requiredBadge) {
|
||||
if (class_exists($requiredBadge)) {
|
||||
return $requiredBadge;
|
||||
}
|
||||
|
||||
if (!str_contains($requiredBadge, '\\')) {
|
||||
$fqcn = 'Symfony\Component\Security\Http\Authenticator\Passport\Badge\\'.$requiredBadge;
|
||||
if (class_exists($fqcn)) {
|
||||
return $fqcn;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidConfigurationException(sprintf('Undefined security Badge class "%s" set in "security.firewall.required_badges".', $requiredBadge));
|
||||
}, $requiredBadges);
|
||||
})
|
||||
->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
;
|
||||
|
||||
$abstractFactoryKeys = [];
|
||||
foreach ($factories as $factory) {
|
||||
$name = str_replace('-', '_', $factory->getKey());
|
||||
$factoryNode = $firewallNodeBuilder->arrayNode($name)
|
||||
->canBeUnset()
|
||||
;
|
||||
|
||||
if ($factory instanceof AbstractFactory) {
|
||||
$abstractFactoryKeys[] = $name;
|
||||
}
|
||||
|
||||
$factory->addConfiguration($factoryNode);
|
||||
}
|
||||
|
||||
// check for unreachable check paths
|
||||
$firewallNodeBuilder
|
||||
->end()
|
||||
->validate()
|
||||
->ifTrue(fn ($v) => true === $v['security'] && isset($v['pattern']) && !isset($v['request_matcher']))
|
||||
->then(function ($firewall) use ($abstractFactoryKeys) {
|
||||
foreach ($abstractFactoryKeys as $k) {
|
||||
if (!isset($firewall[$k]['check_path'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str_contains($firewall[$k]['check_path'], '/') && !preg_match('#'.$firewall['pattern'].'#', $firewall[$k]['check_path'])) {
|
||||
throw new \LogicException(sprintf('The check_path "%s" for login method "%s" is not matched by the firewall pattern "%s".', $firewall[$k]['check_path'], $k, $firewall['pattern']));
|
||||
}
|
||||
}
|
||||
|
||||
return $firewall;
|
||||
})
|
||||
->end()
|
||||
;
|
||||
}
|
||||
|
||||
private function addProvidersSection(ArrayNodeDefinition $rootNode): void
|
||||
{
|
||||
$providerNodeBuilder = $rootNode
|
||||
->fixXmlConfig('provider')
|
||||
->children()
|
||||
->arrayNode('providers')
|
||||
->example([
|
||||
'my_memory_provider' => [
|
||||
'memory' => [
|
||||
'users' => [
|
||||
'foo' => ['password' => 'foo', 'roles' => 'ROLE_USER'],
|
||||
'bar' => ['password' => 'bar', 'roles' => '[ROLE_USER, ROLE_ADMIN]'],
|
||||
],
|
||||
],
|
||||
],
|
||||
'my_entity_provider' => ['entity' => ['class' => 'SecurityBundle:User', 'property' => 'username']],
|
||||
])
|
||||
->requiresAtLeastOneElement()
|
||||
->useAttributeAsKey('name')
|
||||
->prototype('array')
|
||||
;
|
||||
|
||||
$providerNodeBuilder
|
||||
->children()
|
||||
->scalarNode('id')->end()
|
||||
->arrayNode('chain')
|
||||
->fixXmlConfig('provider')
|
||||
->children()
|
||||
->arrayNode('providers')
|
||||
->beforeNormalization()
|
||||
->ifString()
|
||||
->then(fn ($v) => preg_split('/\s*,\s*/', $v))
|
||||
->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
|
||||
foreach ($this->userProviderFactories as $factory) {
|
||||
$name = str_replace('-', '_', $factory->getKey());
|
||||
$factoryNode = $providerNodeBuilder->children()->arrayNode($name)->canBeUnset();
|
||||
|
||||
$factory->addConfiguration($factoryNode);
|
||||
}
|
||||
|
||||
$providerNodeBuilder
|
||||
->validate()
|
||||
->ifTrue(fn ($v) => \count($v) > 1)
|
||||
->thenInvalid('You cannot set multiple provider types for the same provider')
|
||||
->end()
|
||||
->validate()
|
||||
->ifTrue(fn ($v) => 0 === \count($v))
|
||||
->thenInvalid('You must set a provider definition for the provider.')
|
||||
->end()
|
||||
;
|
||||
}
|
||||
|
||||
private function addPasswordHashersSection(ArrayNodeDefinition $rootNode): void
|
||||
{
|
||||
$rootNode
|
||||
->fixXmlConfig('password_hasher')
|
||||
->children()
|
||||
->arrayNode('password_hashers')
|
||||
->example([
|
||||
'App\Entity\User1' => 'auto',
|
||||
'App\Entity\User2' => [
|
||||
'algorithm' => 'auto',
|
||||
'time_cost' => 8,
|
||||
'cost' => 13,
|
||||
],
|
||||
])
|
||||
->requiresAtLeastOneElement()
|
||||
->useAttributeAsKey('class')
|
||||
->prototype('array')
|
||||
->canBeUnset()
|
||||
->performNoDeepMerging()
|
||||
->beforeNormalization()->ifString()->then(fn ($v) => ['algorithm' => $v])->end()
|
||||
->children()
|
||||
->scalarNode('algorithm')
|
||||
->cannotBeEmpty()
|
||||
->validate()
|
||||
->ifTrue(fn ($v) => !\is_string($v))
|
||||
->thenInvalid('You must provide a string value.')
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('migrate_from')
|
||||
->prototype('scalar')->end()
|
||||
->beforeNormalization()->castToArray()->end()
|
||||
->end()
|
||||
->scalarNode('hash_algorithm')->info('Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms.')->defaultValue('sha512')->end()
|
||||
->scalarNode('key_length')->defaultValue(40)->end()
|
||||
->booleanNode('ignore_case')->defaultFalse()->end()
|
||||
->booleanNode('encode_as_base64')->defaultTrue()->end()
|
||||
->scalarNode('iterations')->defaultValue(5000)->end()
|
||||
->integerNode('cost')
|
||||
->min(4)
|
||||
->max(31)
|
||||
->defaultNull()
|
||||
->end()
|
||||
->scalarNode('memory_cost')->defaultNull()->end()
|
||||
->scalarNode('time_cost')->defaultNull()->end()
|
||||
->scalarNode('id')->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end();
|
||||
}
|
||||
|
||||
private function getAccessDecisionStrategies(): array
|
||||
{
|
||||
return [
|
||||
self::STRATEGY_AFFIRMATIVE,
|
||||
self::STRATEGY_CONSENSUS,
|
||||
self::STRATEGY_UNANIMOUS,
|
||||
self::STRATEGY_PRIORITY,
|
||||
];
|
||||
}
|
||||
}
|
||||
Vendored
+62
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\Security\Http\AccessToken\Cas\Cas2Handler;
|
||||
|
||||
class CasTokenHandlerFactory implements TokenHandlerFactoryInterface
|
||||
{
|
||||
public function create(ContainerBuilder $container, string $id, array|string $config): void
|
||||
{
|
||||
$container->setDefinition($id, new ChildDefinition('security.access_token_handler.cas'));
|
||||
|
||||
$container
|
||||
->register('security.access_token_handler.cas', Cas2Handler::class)
|
||||
->setArguments([
|
||||
new Reference('request_stack'),
|
||||
$config['validation_url'],
|
||||
$config['prefix'],
|
||||
$config['http_client'] ? new Reference($config['http_client']) : null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'cas';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeBuilder $node): void
|
||||
{
|
||||
$node
|
||||
->arrayNode($this->getKey())
|
||||
->fixXmlConfig($this->getKey())
|
||||
->children()
|
||||
->scalarNode('validation_url')
|
||||
->info('CAS server validation URL')
|
||||
->isRequired()
|
||||
->end()
|
||||
->scalarNode('prefix')
|
||||
->info('CAS prefix')
|
||||
->defaultValue('cas')
|
||||
->end()
|
||||
->scalarNode('http_client')
|
||||
->info('HTTP Client service')
|
||||
->defaultNull()
|
||||
->end()
|
||||
->end()
|
||||
->end();
|
||||
}
|
||||
}
|
||||
Vendored
+122
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken;
|
||||
|
||||
use Jose\Component\Core\Algorithm;
|
||||
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
|
||||
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* Configures a token handler for decoding and validating an OIDC token.
|
||||
*/
|
||||
class OidcTokenHandlerFactory implements TokenHandlerFactoryInterface
|
||||
{
|
||||
public function create(ContainerBuilder $container, string $id, array|string $config): void
|
||||
{
|
||||
$tokenHandlerDefinition = $container->setDefinition($id, (new ChildDefinition('security.access_token_handler.oidc'))
|
||||
->replaceArgument(2, $config['audience'])
|
||||
->replaceArgument(3, $config['issuers'])
|
||||
->replaceArgument(4, $config['claim'])
|
||||
);
|
||||
|
||||
if (!ContainerBuilder::willBeAvailable('web-token/jwt-library', Algorithm::class, ['symfony/security-bundle'])) {
|
||||
throw new LogicException('You cannot use the "oidc" token handler since "web-token/jwt-library" is not installed. Try running "composer require web-token/jwt-library".');
|
||||
}
|
||||
|
||||
$tokenHandlerDefinition->replaceArgument(0, (new ChildDefinition('security.access_token_handler.oidc.signature'))
|
||||
->replaceArgument(0, $config['algorithms']));
|
||||
|
||||
$tokenHandlerDefinition->replaceArgument(1, (new ChildDefinition('security.access_token_handler.oidc.jwkset'))
|
||||
->replaceArgument(0, $config['keyset'])
|
||||
);
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'oidc';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeBuilder $node): void
|
||||
{
|
||||
$node
|
||||
->arrayNode($this->getKey())
|
||||
->fixXmlConfig($this->getKey())
|
||||
->validate()
|
||||
->ifTrue(static fn ($v) => !isset($v['algorithm']) && !isset($v['algorithms']))
|
||||
->thenInvalid('You must set either "algorithm" or "algorithms".')
|
||||
->end()
|
||||
->validate()
|
||||
->ifTrue(static fn ($v) => !isset($v['key']) && !isset($v['keyset']))
|
||||
->thenInvalid('You must set either "key" or "keyset".')
|
||||
->end()
|
||||
->beforeNormalization()
|
||||
->ifTrue(static fn ($v) => isset($v['algorithm']) && \is_string($v['algorithm']))
|
||||
->then(static function ($v) {
|
||||
if (isset($v['algorithms'])) {
|
||||
throw new InvalidConfigurationException('You cannot use both "algorithm" and "algorithms" at the same time.');
|
||||
}
|
||||
$v['algorithms'] = [$v['algorithm']];
|
||||
unset($v['algorithm']);
|
||||
|
||||
return $v;
|
||||
})
|
||||
->end()
|
||||
->beforeNormalization()
|
||||
->ifTrue(static fn ($v) => isset($v['key']) && \is_string($v['key']))
|
||||
->then(static function ($v) {
|
||||
if (isset($v['keyset'])) {
|
||||
throw new InvalidConfigurationException('You cannot use both "key" and "keyset" at the same time.');
|
||||
}
|
||||
$v['keyset'] = sprintf('{"keys":[%s]}', $v['key']);
|
||||
|
||||
return $v;
|
||||
})
|
||||
->end()
|
||||
->children()
|
||||
->scalarNode('claim')
|
||||
->info('Claim which contains the user identifier (e.g.: sub, email..).')
|
||||
->defaultValue('sub')
|
||||
->end()
|
||||
->scalarNode('audience')
|
||||
->info('Audience set in the token, for validation purpose.')
|
||||
->isRequired()
|
||||
->end()
|
||||
->arrayNode('issuers')
|
||||
->info('Issuers allowed to generate the token, for validation purpose.')
|
||||
->isRequired()
|
||||
->scalarPrototype()->end()
|
||||
->end()
|
||||
->arrayNode('algorithm')
|
||||
->info('Algorithm used to sign the token.')
|
||||
->setDeprecated('symfony/security-bundle', '7.1', 'The "%node%" option is deprecated and will be removed in 8.0. Use the "algorithms" option instead.')
|
||||
->end()
|
||||
->arrayNode('algorithms')
|
||||
->info('Algorithms used to sign the token.')
|
||||
->isRequired()
|
||||
->scalarPrototype()->end()
|
||||
->end()
|
||||
->scalarNode('key')
|
||||
->info('JSON-encoded JWK used to sign the token (must contain a "kty" key).')
|
||||
->setDeprecated('symfony/security-bundle', '7.1', 'The "%node%" option is deprecated and will be removed in 8.0. Use the "keyset" option instead.')
|
||||
->end()
|
||||
->scalarNode('keyset')
|
||||
->info('JSON-encoded JWKSet used to sign the token (must contain a list of valid keys).')
|
||||
->isRequired()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Exception\LogicException;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
/**
|
||||
* Configures a token handler for an OIDC server.
|
||||
*/
|
||||
class OidcUserInfoTokenHandlerFactory implements TokenHandlerFactoryInterface
|
||||
{
|
||||
public function create(ContainerBuilder $container, string $id, array|string $config): void
|
||||
{
|
||||
$clientDefinition = (new ChildDefinition('security.access_token_handler.oidc_user_info.http_client'))
|
||||
->replaceArgument(0, ['base_uri' => $config['base_uri']]);
|
||||
|
||||
if (isset($config['client'])) {
|
||||
$clientDefinition->setFactory([new Reference($config['client']), 'withOptions']);
|
||||
} elseif (!ContainerBuilder::willBeAvailable('symfony/http-client', HttpClientInterface::class, ['symfony/security-bundle'])) {
|
||||
throw new LogicException('You cannot use the "oidc_user_info" token handler since the HttpClient component is not installed. Try running "composer require symfony/http-client".');
|
||||
}
|
||||
|
||||
$container->setDefinition($id, new ChildDefinition('security.access_token_handler.oidc_user_info'))
|
||||
->replaceArgument(0, $clientDefinition)
|
||||
->replaceArgument(2, $config['claim']);
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'oidc_user_info';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeBuilder $node): void
|
||||
{
|
||||
$node
|
||||
->arrayNode($this->getKey())
|
||||
->fixXmlConfig($this->getKey())
|
||||
->beforeNormalization()
|
||||
->ifString()
|
||||
->then(fn ($v) => ['claim' => 'sub', 'base_uri' => $v])
|
||||
->end()
|
||||
->children()
|
||||
->scalarNode('base_uri')
|
||||
->info('Base URI of the userinfo endpoint on the OIDC server.')
|
||||
->isRequired()
|
||||
->cannotBeEmpty()
|
||||
->end()
|
||||
->scalarNode('claim')
|
||||
->info('Claim which contains the user identifier (e.g. sub, email, etc.).')
|
||||
->defaultValue('sub')
|
||||
->cannotBeEmpty()
|
||||
->end()
|
||||
->scalarNode('client')
|
||||
->info('HttpClient service id to use to call the OIDC server.')
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* Configures a token handler from a service id.
|
||||
*
|
||||
* @see \Symfony\Bundle\SecurityBundle\Tests\DependencyInjection\Security\Factory\AccessTokenFactoryTest
|
||||
*/
|
||||
class ServiceTokenHandlerFactory implements TokenHandlerFactoryInterface
|
||||
{
|
||||
public function create(ContainerBuilder $container, string $id, array|string $config): void
|
||||
{
|
||||
$container->setDefinition($id, new ChildDefinition($config));
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'id';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeBuilder $node): void
|
||||
{
|
||||
$node->scalarNode($this->getKey())->end();
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* Allows creating configurable token handlers.
|
||||
*/
|
||||
interface TokenHandlerFactoryInterface
|
||||
{
|
||||
/**
|
||||
* Creates a generic token handler service.
|
||||
*/
|
||||
public function create(ContainerBuilder $container, string $id, array|string $config): void;
|
||||
|
||||
/**
|
||||
* Gets a generic token handler configuration key.
|
||||
*/
|
||||
public function getKey(): string;
|
||||
|
||||
/**
|
||||
* Adds a generic token handler configuration.
|
||||
*/
|
||||
public function addConfiguration(NodeBuilder $node): void;
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Lukas Kahwe Smith <smith@pooteeweet.org>
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
abstract class AbstractFactory implements AuthenticatorFactoryInterface
|
||||
{
|
||||
protected array $options = [
|
||||
'check_path' => '/login_check',
|
||||
'use_forward' => false,
|
||||
'login_path' => '/login',
|
||||
];
|
||||
|
||||
protected array $defaultSuccessHandlerOptions = [
|
||||
'always_use_default_target_path' => false,
|
||||
'default_target_path' => '/',
|
||||
'login_path' => '/login',
|
||||
'target_path_parameter' => '_target_path',
|
||||
'use_referer' => false,
|
||||
];
|
||||
|
||||
protected array $defaultFailureHandlerOptions = [
|
||||
'failure_path' => null,
|
||||
'failure_forward' => false,
|
||||
'login_path' => '/login',
|
||||
'failure_path_parameter' => '_failure_path',
|
||||
];
|
||||
|
||||
final public function addOption(string $name, mixed $default = null): void
|
||||
{
|
||||
$this->options[$name] = $default;
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeDefinition $node): void
|
||||
{
|
||||
$builder = $node->children();
|
||||
|
||||
$builder
|
||||
->scalarNode('provider')->end()
|
||||
->booleanNode('remember_me')->defaultTrue()->end()
|
||||
->scalarNode('success_handler')->end()
|
||||
->scalarNode('failure_handler')->end()
|
||||
;
|
||||
|
||||
foreach (array_merge($this->options, $this->defaultSuccessHandlerOptions, $this->defaultFailureHandlerOptions) as $name => $default) {
|
||||
if (\is_bool($default)) {
|
||||
$builder->booleanNode($name)->defaultValue($default);
|
||||
} else {
|
||||
$builder->scalarNode($name)->defaultValue($default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function createAuthenticationSuccessHandler(ContainerBuilder $container, string $id, array $config): string
|
||||
{
|
||||
$successHandlerId = $this->getSuccessHandlerId($id);
|
||||
$options = array_intersect_key($config, $this->defaultSuccessHandlerOptions);
|
||||
|
||||
if (isset($config['success_handler'])) {
|
||||
$successHandler = $container->setDefinition($successHandlerId, new ChildDefinition('security.authentication.custom_success_handler'));
|
||||
$successHandler->replaceArgument(0, new ChildDefinition($config['success_handler']));
|
||||
$successHandler->replaceArgument(1, $options);
|
||||
$successHandler->replaceArgument(2, $id);
|
||||
} else {
|
||||
$successHandler = $container->setDefinition($successHandlerId, new ChildDefinition('security.authentication.success_handler'));
|
||||
$successHandler->addMethodCall('setOptions', [$options]);
|
||||
$successHandler->addMethodCall('setFirewallName', [$id]);
|
||||
}
|
||||
|
||||
return $successHandlerId;
|
||||
}
|
||||
|
||||
protected function createAuthenticationFailureHandler(ContainerBuilder $container, string $id, array $config): string
|
||||
{
|
||||
$id = $this->getFailureHandlerId($id);
|
||||
$options = array_intersect_key($config, $this->defaultFailureHandlerOptions);
|
||||
|
||||
if (isset($config['failure_handler'])) {
|
||||
$failureHandler = $container->setDefinition($id, new ChildDefinition('security.authentication.custom_failure_handler'));
|
||||
$failureHandler->replaceArgument(0, new ChildDefinition($config['failure_handler']));
|
||||
$failureHandler->replaceArgument(1, $options);
|
||||
} else {
|
||||
$failureHandler = $container->setDefinition($id, new ChildDefinition('security.authentication.failure_handler'));
|
||||
$failureHandler->addMethodCall('setOptions', [$options]);
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
protected function getSuccessHandlerId(string $id): string
|
||||
{
|
||||
return 'security.authentication.success_handler.'.$id.'.'.str_replace('-', '_', $this->getKey());
|
||||
}
|
||||
|
||||
protected function getFailureHandlerId(string $id): string
|
||||
{
|
||||
return 'security.authentication.failure_handler.'.$id.'.'.str_replace('-', '_', $this->getKey());
|
||||
}
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\TokenHandlerFactoryInterface;
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* AccessTokenFactory creates services for Access Token authentication.
|
||||
*
|
||||
* @author Florent Morselli <florent.morselli@spomky-labs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class AccessTokenFactory extends AbstractFactory implements StatelessAuthenticatorFactoryInterface
|
||||
{
|
||||
private const PRIORITY = -40;
|
||||
|
||||
/**
|
||||
* @param array<TokenHandlerFactoryInterface> $tokenHandlerFactories
|
||||
*/
|
||||
public function __construct(private readonly array $tokenHandlerFactories)
|
||||
{
|
||||
$this->options = [];
|
||||
$this->defaultFailureHandlerOptions = [];
|
||||
$this->defaultSuccessHandlerOptions = [];
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeDefinition $node): void
|
||||
{
|
||||
parent::addConfiguration($node);
|
||||
|
||||
$builder = $node->children();
|
||||
$builder
|
||||
->scalarNode('realm')->defaultNull()->end()
|
||||
->arrayNode('token_extractors')
|
||||
->fixXmlConfig('token_extractors')
|
||||
->beforeNormalization()
|
||||
->ifString()
|
||||
->then(fn ($v) => [$v])
|
||||
->end()
|
||||
->cannotBeEmpty()
|
||||
->defaultValue([
|
||||
'security.access_token_extractor.header',
|
||||
])
|
||||
->scalarPrototype()->end()
|
||||
->end()
|
||||
;
|
||||
|
||||
$tokenHandlerNodeBuilder = $builder
|
||||
->arrayNode('token_handler')
|
||||
->example([
|
||||
'id' => 'App\Security\CustomTokenHandler',
|
||||
])
|
||||
|
||||
->beforeNormalization()
|
||||
->ifString()
|
||||
->then(fn ($v) => ['id' => $v])
|
||||
->end()
|
||||
|
||||
->beforeNormalization()
|
||||
->ifTrue(fn ($v) => \is_array($v) && 1 < \count($v))
|
||||
->then(fn () => throw new InvalidConfigurationException('You cannot configure multiple token handlers.'))
|
||||
->end()
|
||||
|
||||
// "isRequired" must be set otherwise the following custom validation is not called
|
||||
->isRequired()
|
||||
->beforeNormalization()
|
||||
->ifTrue(fn ($v) => \is_array($v) && !$v)
|
||||
->then(fn () => throw new InvalidConfigurationException('You must set a token handler.'))
|
||||
->end()
|
||||
|
||||
->children()
|
||||
;
|
||||
|
||||
foreach ($this->tokenHandlerFactories as $factory) {
|
||||
$factory->addConfiguration($tokenHandlerNodeBuilder);
|
||||
}
|
||||
|
||||
$tokenHandlerNodeBuilder->end();
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return self::PRIORITY;
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'access_token';
|
||||
}
|
||||
|
||||
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, ?string $userProviderId): string
|
||||
{
|
||||
$successHandler = isset($config['success_handler']) ? new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config)) : null;
|
||||
$failureHandler = isset($config['failure_handler']) ? new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config)) : null;
|
||||
$authenticatorId = sprintf('security.authenticator.access_token.%s', $firewallName);
|
||||
$extractorId = $this->createExtractor($container, $firewallName, $config['token_extractors']);
|
||||
$tokenHandlerId = $this->createTokenHandler($container, $firewallName, $config['token_handler'], $userProviderId);
|
||||
|
||||
$container
|
||||
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.access_token'))
|
||||
->replaceArgument(0, new Reference($tokenHandlerId))
|
||||
->replaceArgument(1, new Reference($extractorId))
|
||||
->replaceArgument(2, $userProviderId ? new Reference($userProviderId) : null)
|
||||
->replaceArgument(3, $successHandler)
|
||||
->replaceArgument(4, $failureHandler)
|
||||
->replaceArgument(5, $config['realm'])
|
||||
;
|
||||
|
||||
return $authenticatorId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $extractors
|
||||
*/
|
||||
private function createExtractor(ContainerBuilder $container, string $firewallName, array $extractors): string
|
||||
{
|
||||
$aliases = [
|
||||
'query_string' => 'security.access_token_extractor.query_string',
|
||||
'request_body' => 'security.access_token_extractor.request_body',
|
||||
'header' => 'security.access_token_extractor.header',
|
||||
];
|
||||
$extractors = array_map(fn ($extractor) => $aliases[$extractor] ?? $extractor, $extractors);
|
||||
|
||||
if (1 === \count($extractors)) {
|
||||
return current($extractors);
|
||||
}
|
||||
$extractorId = sprintf('security.authenticator.access_token.chain_extractor.%s', $firewallName);
|
||||
$container
|
||||
->setDefinition($extractorId, new ChildDefinition('security.authenticator.access_token.chain_extractor'))
|
||||
->replaceArgument(0, array_map(fn (string $extractorId): Reference => new Reference($extractorId), $extractors))
|
||||
;
|
||||
|
||||
return $extractorId;
|
||||
}
|
||||
|
||||
private function createTokenHandler(ContainerBuilder $container, string $firewallName, array $config, ?string $userProviderId): string
|
||||
{
|
||||
$key = array_keys($config)[0];
|
||||
$id = sprintf('security.access_token_handler.%s', $firewallName);
|
||||
|
||||
foreach ($this->tokenHandlerFactories as $factory) {
|
||||
if ($key !== $factory->getKey()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$factory->create($container, $id, $config[$key], $userProviderId);
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*/
|
||||
interface AuthenticatorFactoryInterface
|
||||
{
|
||||
/**
|
||||
* Defines the priority at which the authenticator is called.
|
||||
*/
|
||||
public function getPriority(): int;
|
||||
|
||||
/**
|
||||
* Defines the configuration key used to reference the provider
|
||||
* in the firewall configuration.
|
||||
*/
|
||||
public function getKey(): string;
|
||||
|
||||
public function addConfiguration(NodeDefinition $builder): void;
|
||||
|
||||
/**
|
||||
* Creates the authenticator service(s) for the provided configuration.
|
||||
*
|
||||
* @param array<string, mixed> $config
|
||||
*
|
||||
* @return string|string[] The authenticator service ID(s) to be used by the firewall
|
||||
*/
|
||||
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string|array;
|
||||
}
|
||||
Vendored
+64
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class CustomAuthenticatorFactory implements AuthenticatorFactoryInterface
|
||||
{
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'custom_authenticators';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ArrayNodeDefinition $builder
|
||||
*/
|
||||
public function addConfiguration(NodeDefinition $builder): void
|
||||
{
|
||||
$builder
|
||||
->info('An array of service ids for all of your "authenticators"')
|
||||
->requiresAtLeastOneElement()
|
||||
->prototype('scalar')->end();
|
||||
|
||||
// get the parent array node builder ("firewalls") from inside the children builder
|
||||
$factoryRootNode = $builder->end()->end();
|
||||
$factoryRootNode
|
||||
->fixXmlConfig('custom_authenticator')
|
||||
->validate()
|
||||
->ifTrue(fn ($v) => isset($v['custom_authenticators']) && empty($v['custom_authenticators']))
|
||||
->then(function ($v) {
|
||||
unset($v['custom_authenticators']);
|
||||
|
||||
return $v;
|
||||
})
|
||||
->end()
|
||||
;
|
||||
}
|
||||
|
||||
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): array
|
||||
{
|
||||
return $config;
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* Can be implemented by a security factory to add a listener to the firewall.
|
||||
*
|
||||
* @author Christian Scheb <me@christianscheb.de>
|
||||
*/
|
||||
interface FirewallListenerFactoryInterface
|
||||
{
|
||||
/**
|
||||
* Creates the firewall listener services for the provided configuration.
|
||||
*
|
||||
* @param array<string, mixed> $config
|
||||
*
|
||||
* @return string[] The listener service IDs to be used by the firewall
|
||||
*/
|
||||
public function createListeners(ContainerBuilder $container, string $firewallName, array $config): array;
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* FormLoginFactory creates services for form login authentication.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class FormLoginFactory extends AbstractFactory
|
||||
{
|
||||
public const PRIORITY = -30;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->addOption('username_parameter', '_username');
|
||||
$this->addOption('password_parameter', '_password');
|
||||
$this->addOption('csrf_parameter', '_csrf_token');
|
||||
$this->addOption('csrf_token_id', 'authenticate');
|
||||
$this->addOption('enable_csrf', false);
|
||||
$this->addOption('post_only', true);
|
||||
$this->addOption('form_only', false);
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return self::PRIORITY;
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'form-login';
|
||||
}
|
||||
|
||||
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string
|
||||
{
|
||||
$authenticatorId = 'security.authenticator.form_login.'.$firewallName;
|
||||
$options = array_intersect_key($config, $this->options);
|
||||
$authenticator = $container
|
||||
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.form_login'))
|
||||
->replaceArgument(1, new Reference($userProviderId))
|
||||
->replaceArgument(2, new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config)))
|
||||
->replaceArgument(3, new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config)))
|
||||
->replaceArgument(4, $options);
|
||||
|
||||
if ($options['use_forward'] ?? false) {
|
||||
$authenticator->addMethodCall('setHttpKernel', [new Reference('http_kernel')]);
|
||||
}
|
||||
|
||||
return $authenticatorId;
|
||||
}
|
||||
}
|
||||
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
|
||||
/**
|
||||
* FormLoginLdapFactory creates services for form login ldap authentication.
|
||||
*
|
||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||
* @author Charles Sarrazin <charles@sarraz.in>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class FormLoginLdapFactory extends FormLoginFactory
|
||||
{
|
||||
use LdapFactoryTrait;
|
||||
|
||||
public function addConfiguration(NodeDefinition $node): void
|
||||
{
|
||||
parent::addConfiguration($node);
|
||||
|
||||
$node
|
||||
->children()
|
||||
->scalarNode('service')->defaultValue('ldap')->end()
|
||||
->scalarNode('dn_string')->defaultValue('{user_identifier}')->end()
|
||||
->scalarNode('query_string')->end()
|
||||
->scalarNode('search_dn')->defaultValue('')->end()
|
||||
->scalarNode('search_password')->defaultValue('')->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* HttpBasicFactory creates services for HTTP basic authentication.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class HttpBasicFactory implements AuthenticatorFactoryInterface
|
||||
{
|
||||
public const PRIORITY = -50;
|
||||
|
||||
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string
|
||||
{
|
||||
$authenticatorId = 'security.authenticator.http_basic.'.$firewallName;
|
||||
$container
|
||||
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.http_basic'))
|
||||
->replaceArgument(0, $config['realm'])
|
||||
->replaceArgument(1, new Reference($userProviderId));
|
||||
|
||||
return $authenticatorId;
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return self::PRIORITY;
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'http-basic';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeDefinition $node): void
|
||||
{
|
||||
$node
|
||||
->children()
|
||||
->scalarNode('provider')->end()
|
||||
->scalarNode('realm')->defaultValue('Secured Area')->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
}
|
||||
Vendored
+87
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\Security\Core\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* HttpBasicFactory creates services for HTTP basic authentication.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||
* @author Charles Sarrazin <charles@sarraz.in>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class HttpBasicLdapFactory extends HttpBasicFactory
|
||||
{
|
||||
use LdapFactoryTrait;
|
||||
|
||||
public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint): array
|
||||
{
|
||||
$provider = 'security.authentication.provider.ldap_bind.'.$id;
|
||||
$definition = $container
|
||||
->setDefinition($provider, new ChildDefinition('security.authentication.provider.ldap_bind'))
|
||||
->replaceArgument(0, new Reference($userProvider))
|
||||
->replaceArgument(1, new Reference('security.user_checker.'.$id))
|
||||
->replaceArgument(2, $id)
|
||||
->replaceArgument(3, new Reference($config['service']))
|
||||
->replaceArgument(4, $config['dn_string'])
|
||||
->replaceArgument(6, $config['search_dn'])
|
||||
->replaceArgument(7, $config['search_password'])
|
||||
;
|
||||
|
||||
// entry point
|
||||
$entryPointId = $defaultEntryPoint;
|
||||
|
||||
if (null === $entryPointId) {
|
||||
$entryPointId = 'security.authentication.basic_entry_point.'.$id;
|
||||
$container
|
||||
->setDefinition($entryPointId, new ChildDefinition('security.authentication.basic_entry_point'))
|
||||
->addArgument($config['realm']);
|
||||
}
|
||||
|
||||
if (!empty($config['query_string'])) {
|
||||
if ('' === $config['search_dn'] || '' === $config['search_password']) {
|
||||
throw new LogicException('Using the "query_string" config without using a "search_dn" and a "search_password" is not supported.');
|
||||
}
|
||||
$definition->addMethodCall('setQueryString', [$config['query_string']]);
|
||||
}
|
||||
|
||||
// listener
|
||||
$listenerId = 'security.authentication.listener.basic.'.$id;
|
||||
$listener = $container->setDefinition($listenerId, new ChildDefinition('security.authentication.listener.basic'));
|
||||
$listener->replaceArgument(2, $id);
|
||||
$listener->replaceArgument(3, new Reference($entryPointId));
|
||||
|
||||
return [$provider, $listenerId, $entryPointId];
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeDefinition $node): void
|
||||
{
|
||||
parent::addConfiguration($node);
|
||||
|
||||
$node
|
||||
->children()
|
||||
->scalarNode('service')->defaultValue('ldap')->end()
|
||||
->scalarNode('dn_string')->defaultValue('{user_identifier}')->end()
|
||||
->scalarNode('query_string')->end()
|
||||
->scalarNode('search_dn')->defaultValue('')->end()
|
||||
->scalarNode('search_password')->defaultValue('')->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* JsonLoginFactory creates services for JSON login authentication.
|
||||
*
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class JsonLoginFactory extends AbstractFactory
|
||||
{
|
||||
public const PRIORITY = -40;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->addOption('username_path', 'username');
|
||||
$this->addOption('password_path', 'password');
|
||||
$this->defaultFailureHandlerOptions = [];
|
||||
$this->defaultSuccessHandlerOptions = [];
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return self::PRIORITY;
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'json-login';
|
||||
}
|
||||
|
||||
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string
|
||||
{
|
||||
$authenticatorId = 'security.authenticator.json_login.'.$firewallName;
|
||||
$options = array_intersect_key($config, $this->options);
|
||||
$container
|
||||
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.json_login'))
|
||||
->replaceArgument(1, new Reference($userProviderId))
|
||||
->replaceArgument(2, isset($config['success_handler']) ? new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config)) : null)
|
||||
->replaceArgument(3, isset($config['failure_handler']) ? new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config)) : null)
|
||||
->replaceArgument(4, $options);
|
||||
|
||||
return $authenticatorId;
|
||||
}
|
||||
}
|
||||
Vendored
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
|
||||
/**
|
||||
* JsonLoginLdapFactory creates services for json login ldap authentication.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class JsonLoginLdapFactory extends JsonLoginFactory
|
||||
{
|
||||
use LdapFactoryTrait;
|
||||
|
||||
public function addConfiguration(NodeDefinition $node): void
|
||||
{
|
||||
parent::addConfiguration($node);
|
||||
|
||||
$node
|
||||
->children()
|
||||
->scalarNode('service')->defaultValue('ldap')->end()
|
||||
->scalarNode('dn_string')->defaultValue('{user_identifier}')->end()
|
||||
->scalarNode('query_string')->end()
|
||||
->scalarNode('search_dn')->defaultValue('')->end()
|
||||
->scalarNode('search_password')->defaultValue('')->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\Ldap\Security\CheckLdapCredentialsListener;
|
||||
use Symfony\Component\Ldap\Security\LdapAuthenticator;
|
||||
|
||||
/**
|
||||
* A trait decorating the authenticator with LDAP functionality.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait LdapFactoryTrait
|
||||
{
|
||||
public function getKey(): string
|
||||
{
|
||||
return parent::getKey().'-ldap';
|
||||
}
|
||||
|
||||
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string
|
||||
{
|
||||
$key = str_replace('-', '_', $this->getKey());
|
||||
$authenticatorId = parent::createAuthenticator($container, $firewallName, $config, $userProviderId);
|
||||
|
||||
$container->setDefinition('security.listener.'.$key.'.'.$firewallName, new Definition(CheckLdapCredentialsListener::class))
|
||||
->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.'.$firewallName])
|
||||
->addArgument(new Reference('security.ldap_locator'))
|
||||
;
|
||||
|
||||
$ldapAuthenticatorId = 'security.authenticator.'.$key.'.'.$firewallName;
|
||||
$definition = $container->setDefinition($ldapAuthenticatorId, new Definition(LdapAuthenticator::class))
|
||||
->setArguments([
|
||||
new Reference($authenticatorId),
|
||||
$config['service'],
|
||||
$config['dn_string'],
|
||||
$config['search_dn'],
|
||||
$config['search_password'],
|
||||
]);
|
||||
|
||||
if (!empty($config['query_string'])) {
|
||||
if ('' === $config['search_dn'] || '' === $config['search_password']) {
|
||||
throw new InvalidConfigurationException('Using the "query_string" config without using a "search_dn" and a "search_password" is not supported.');
|
||||
}
|
||||
|
||||
$definition->addArgument($config['query_string']);
|
||||
}
|
||||
|
||||
return $ldapAuthenticatorId;
|
||||
}
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class LoginLinkFactory extends AbstractFactory
|
||||
{
|
||||
public const PRIORITY = -20;
|
||||
|
||||
public function addConfiguration(NodeDefinition $node): void
|
||||
{
|
||||
/** @var NodeBuilder $builder */
|
||||
$builder = $node->fixXmlConfig('signature_property', 'signature_properties')->children();
|
||||
|
||||
$builder
|
||||
->scalarNode('check_route')
|
||||
->isRequired()
|
||||
->info('Route that will validate the login link - e.g. "app_login_link_verify".')
|
||||
->end()
|
||||
->scalarNode('check_post_only')
|
||||
->defaultFalse()
|
||||
->info('If true, only HTTP POST requests to "check_route" will be handled by the authenticator.')
|
||||
->end()
|
||||
->arrayNode('signature_properties')
|
||||
->isRequired()
|
||||
->prototype('scalar')->end()
|
||||
->requiresAtLeastOneElement()
|
||||
->info('An array of properties on your User that are used to sign the link. If any of these change, all existing links will become invalid.')
|
||||
->example(['email', 'password'])
|
||||
->end()
|
||||
->integerNode('lifetime')
|
||||
->defaultValue(600)
|
||||
->info('The lifetime of the login link in seconds.')
|
||||
->end()
|
||||
->integerNode('max_uses')
|
||||
->defaultNull()
|
||||
->info('Max number of times a login link can be used - null means unlimited within lifetime.')
|
||||
->end()
|
||||
->scalarNode('used_link_cache')
|
||||
->info('Cache service id used to expired links of max_uses is set.')
|
||||
->end()
|
||||
->scalarNode('success_handler')
|
||||
->info(sprintf('A service id that implements %s.', AuthenticationSuccessHandlerInterface::class))
|
||||
->end()
|
||||
->scalarNode('failure_handler')
|
||||
->info(sprintf('A service id that implements %s.', AuthenticationFailureHandlerInterface::class))
|
||||
->end()
|
||||
->scalarNode('provider')
|
||||
->info('The user provider to load users from.')
|
||||
->end()
|
||||
;
|
||||
|
||||
foreach (array_merge($this->defaultSuccessHandlerOptions, $this->defaultFailureHandlerOptions) as $name => $default) {
|
||||
if (\is_bool($default)) {
|
||||
$builder->booleanNode($name)->defaultValue($default);
|
||||
} else {
|
||||
$builder->scalarNode($name)->defaultValue($default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'login-link';
|
||||
}
|
||||
|
||||
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string
|
||||
{
|
||||
if (!$container->hasDefinition('security.authenticator.login_link')) {
|
||||
$loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__).'/../../Resources/config'));
|
||||
$loader->load('security_authenticator_login_link.php');
|
||||
}
|
||||
|
||||
if (null !== $config['max_uses'] && !isset($config['used_link_cache'])) {
|
||||
$config['used_link_cache'] = 'security.authenticator.cache.expired_links';
|
||||
$defaultCacheDefinition = $container->getDefinition($config['used_link_cache']);
|
||||
if (!$defaultCacheDefinition->hasTag('cache.pool')) {
|
||||
$defaultCacheDefinition->addTag('cache.pool');
|
||||
}
|
||||
}
|
||||
|
||||
$expiredStorageId = null;
|
||||
if (isset($config['used_link_cache'])) {
|
||||
$expiredStorageId = 'security.authenticator.expired_login_link_storage.'.$firewallName;
|
||||
$container
|
||||
->setDefinition($expiredStorageId, new ChildDefinition('security.authenticator.expired_login_link_storage'))
|
||||
->replaceArgument(0, new Reference($config['used_link_cache']))
|
||||
->replaceArgument(1, $config['lifetime']);
|
||||
}
|
||||
|
||||
$signatureHasherId = 'security.authenticator.login_link_signature_hasher.'.$firewallName;
|
||||
$container
|
||||
->setDefinition($signatureHasherId, new ChildDefinition('security.authenticator.abstract_login_link_signature_hasher'))
|
||||
->replaceArgument(1, $config['signature_properties'])
|
||||
->replaceArgument(3, $expiredStorageId ? new Reference($expiredStorageId) : null)
|
||||
->replaceArgument(4, $config['max_uses'] ?? null)
|
||||
;
|
||||
|
||||
$linkerId = 'security.authenticator.login_link_handler.'.$firewallName;
|
||||
$linkerOptions = [
|
||||
'route_name' => $config['check_route'],
|
||||
'lifetime' => $config['lifetime'],
|
||||
];
|
||||
$container
|
||||
->setDefinition($linkerId, new ChildDefinition('security.authenticator.abstract_login_link_handler'))
|
||||
->replaceArgument(1, new Reference($userProviderId))
|
||||
->replaceArgument(2, new Reference($signatureHasherId))
|
||||
->replaceArgument(3, $linkerOptions)
|
||||
->addTag('security.authenticator.login_linker', ['firewall' => $firewallName])
|
||||
;
|
||||
|
||||
$authenticatorId = 'security.authenticator.login_link.'.$firewallName;
|
||||
$container
|
||||
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.login_link'))
|
||||
->replaceArgument(0, new Reference($linkerId))
|
||||
->replaceArgument(2, new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config)))
|
||||
->replaceArgument(3, new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config)))
|
||||
->replaceArgument(4, [
|
||||
'check_route' => $config['check_route'],
|
||||
'check_post_only' => $config['check_post_only'],
|
||||
]);
|
||||
|
||||
return $authenticatorId;
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return self::PRIORITY;
|
||||
}
|
||||
}
|
||||
Vendored
+120
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Exception\LogicException;
|
||||
use Symfony\Component\DependencyInjection\Parameter;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface;
|
||||
use Symfony\Component\Lock\LockInterface;
|
||||
use Symfony\Component\RateLimiter\RateLimiterFactory;
|
||||
use Symfony\Component\RateLimiter\Storage\CacheStorage;
|
||||
use Symfony\Component\Security\Http\RateLimiter\DefaultLoginRateLimiter;
|
||||
|
||||
/**
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class LoginThrottlingFactory implements AuthenticatorFactoryInterface
|
||||
{
|
||||
public function getPriority(): int
|
||||
{
|
||||
// this factory doesn't register any authenticators, this priority doesn't matter
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'login_throttling';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ArrayNodeDefinition $builder
|
||||
*/
|
||||
public function addConfiguration(NodeDefinition $builder): void
|
||||
{
|
||||
$builder
|
||||
->children()
|
||||
->scalarNode('limiter')->info(sprintf('A service id implementing "%s".', RequestRateLimiterInterface::class))->end()
|
||||
->integerNode('max_attempts')->defaultValue(5)->end()
|
||||
->scalarNode('interval')->defaultValue('1 minute')->end()
|
||||
->scalarNode('lock_factory')->info('The service ID of the lock factory used by the login rate limiter (or null to disable locking)')->defaultNull()->end()
|
||||
->end();
|
||||
}
|
||||
|
||||
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): array
|
||||
{
|
||||
if (!class_exists(RateLimiterFactory::class)) {
|
||||
throw new \LogicException('Login throttling requires the Rate Limiter component. Try running "composer require symfony/rate-limiter".');
|
||||
}
|
||||
|
||||
if (!isset($config['limiter'])) {
|
||||
$limiterOptions = [
|
||||
'policy' => 'fixed_window',
|
||||
'limit' => $config['max_attempts'],
|
||||
'interval' => $config['interval'],
|
||||
'lock_factory' => $config['lock_factory'],
|
||||
];
|
||||
$this->registerRateLimiter($container, $localId = '_login_local_'.$firewallName, $limiterOptions);
|
||||
|
||||
$limiterOptions['limit'] = 5 * $config['max_attempts'];
|
||||
$this->registerRateLimiter($container, $globalId = '_login_global_'.$firewallName, $limiterOptions);
|
||||
|
||||
$container->register($config['limiter'] = 'security.login_throttling.'.$firewallName.'.limiter', DefaultLoginRateLimiter::class)
|
||||
->addArgument(new Reference('limiter.'.$globalId))
|
||||
->addArgument(new Reference('limiter.'.$localId))
|
||||
->addArgument(new Parameter('container.build_hash'))
|
||||
;
|
||||
}
|
||||
|
||||
$container
|
||||
->setDefinition('security.listener.login_throttling.'.$firewallName, new ChildDefinition('security.listener.login_throttling'))
|
||||
->replaceArgument(1, new Reference($config['limiter']))
|
||||
->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.'.$firewallName]);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private function registerRateLimiter(ContainerBuilder $container, string $name, array $limiterConfig): void
|
||||
{
|
||||
// default configuration (when used by other DI extensions)
|
||||
$limiterConfig += ['lock_factory' => 'lock.factory', 'cache_pool' => 'cache.rate_limiter'];
|
||||
|
||||
$limiter = $container->setDefinition($limiterId = 'limiter.'.$name, new ChildDefinition('limiter'));
|
||||
|
||||
if (null !== $limiterConfig['lock_factory']) {
|
||||
if (!interface_exists(LockInterface::class)) {
|
||||
throw new LogicException(sprintf('Rate limiter "%s" requires the Lock component to be installed. Try running "composer require symfony/lock".', $name));
|
||||
}
|
||||
|
||||
$limiter->replaceArgument(2, new Reference($limiterConfig['lock_factory']));
|
||||
}
|
||||
unset($limiterConfig['lock_factory']);
|
||||
|
||||
if (null === $storageId = $limiterConfig['storage_service'] ?? null) {
|
||||
$container->register($storageId = 'limiter.storage.'.$name, CacheStorage::class)->addArgument(new Reference($limiterConfig['cache_pool']));
|
||||
}
|
||||
|
||||
$limiter->replaceArgument(1, new Reference($storageId));
|
||||
unset($limiterConfig['storage_service'], $limiterConfig['cache_pool']);
|
||||
|
||||
$limiterConfig['id'] = $name;
|
||||
$limiter->replaceArgument(0, $limiterConfig);
|
||||
|
||||
$container->registerAliasForArgument($limiterId, RateLimiterFactory::class, $name.'.limiter');
|
||||
}
|
||||
}
|
||||
+247
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Bridge\Doctrine\Security\RememberMe\DoctrineTokenProvider;
|
||||
use Symfony\Bundle\SecurityBundle\RememberMe\DecoratedRememberMeHandler;
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
|
||||
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\HttpFoundation\Cookie;
|
||||
use Symfony\Component\Security\Core\Authentication\RememberMe\CacheTokenVerifier;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class RememberMeFactory implements AuthenticatorFactoryInterface, PrependExtensionInterface
|
||||
{
|
||||
public const PRIORITY = -50;
|
||||
|
||||
protected array $options = [
|
||||
'name' => 'REMEMBERME',
|
||||
'lifetime' => 31536000,
|
||||
'path' => '/',
|
||||
'domain' => null,
|
||||
'secure' => false,
|
||||
'httponly' => true,
|
||||
'samesite' => null,
|
||||
'always_remember_me' => false,
|
||||
'remember_me_parameter' => '_remember_me',
|
||||
];
|
||||
|
||||
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string
|
||||
{
|
||||
if (!$container->hasDefinition('security.authenticator.remember_me')) {
|
||||
$loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__).'/../../Resources/config'));
|
||||
$loader->load('security_authenticator_remember_me.php');
|
||||
}
|
||||
|
||||
if ('auto' === $config['secure']) {
|
||||
$config['secure'] = null;
|
||||
}
|
||||
|
||||
// create remember me handler (which manage the remember-me cookies)
|
||||
$rememberMeHandlerId = 'security.authenticator.remember_me_handler.'.$firewallName;
|
||||
if (isset($config['service']) && isset($config['token_provider'])) {
|
||||
throw new InvalidConfigurationException(sprintf('You cannot use both "service" and "token_provider" in "security.firewalls.%s.remember_me".', $firewallName));
|
||||
}
|
||||
|
||||
if (isset($config['service'])) {
|
||||
$container->register($rememberMeHandlerId, DecoratedRememberMeHandler::class)
|
||||
->addArgument(new Reference($config['service']))
|
||||
->addTag('security.remember_me_handler', ['firewall' => $firewallName]);
|
||||
} elseif (isset($config['token_provider'])) {
|
||||
$tokenProviderId = $this->createTokenProvider($container, $firewallName, $config['token_provider']);
|
||||
$tokenVerifier = $this->createTokenVerifier($container, $firewallName, $config['token_verifier'] ?? null);
|
||||
$container->setDefinition($rememberMeHandlerId, new ChildDefinition('security.authenticator.persistent_remember_me_handler'))
|
||||
->replaceArgument(0, new Reference($tokenProviderId))
|
||||
->replaceArgument(1, new Reference($userProviderId))
|
||||
->replaceArgument(3, $config)
|
||||
->replaceArgument(5, $tokenVerifier)
|
||||
->addTag('security.remember_me_handler', ['firewall' => $firewallName]);
|
||||
} else {
|
||||
$signatureHasherId = 'security.authenticator.remember_me_signature_hasher.'.$firewallName;
|
||||
$container->setDefinition($signatureHasherId, new ChildDefinition('security.authenticator.remember_me_signature_hasher'))
|
||||
->replaceArgument(1, $config['signature_properties'])
|
||||
->replaceArgument(2, $config['secret'])
|
||||
;
|
||||
|
||||
$container->setDefinition($rememberMeHandlerId, new ChildDefinition('security.authenticator.signature_remember_me_handler'))
|
||||
->replaceArgument(0, new Reference($signatureHasherId))
|
||||
->replaceArgument(1, new Reference($userProviderId))
|
||||
->replaceArgument(3, $config)
|
||||
->addTag('security.remember_me_handler', ['firewall' => $firewallName]);
|
||||
}
|
||||
|
||||
// create check remember me conditions listener (which checks if a remember-me cookie is supported and requested)
|
||||
$rememberMeConditionsListenerId = 'security.listener.check_remember_me_conditions.'.$firewallName;
|
||||
$container->setDefinition($rememberMeConditionsListenerId, new ChildDefinition('security.listener.check_remember_me_conditions'))
|
||||
->replaceArgument(0, array_intersect_key($config, ['always_remember_me' => true, 'remember_me_parameter' => true]))
|
||||
->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.'.$firewallName])
|
||||
;
|
||||
|
||||
// create remember me listener (which executes the remember me services for other authenticators and logout)
|
||||
$rememberMeListenerId = 'security.listener.remember_me.'.$firewallName;
|
||||
$container->setDefinition($rememberMeListenerId, new ChildDefinition('security.listener.remember_me'))
|
||||
->replaceArgument(0, new Reference($rememberMeHandlerId))
|
||||
->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.'.$firewallName])
|
||||
;
|
||||
|
||||
// create remember me authenticator (which re-authenticates the user based on the remember-me cookie)
|
||||
$authenticatorId = 'security.authenticator.remember_me.'.$firewallName;
|
||||
$container
|
||||
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.remember_me'))
|
||||
->replaceArgument(0, new Reference($rememberMeHandlerId))
|
||||
->replaceArgument(3, $config['name'] ?? $this->options['name'])
|
||||
;
|
||||
|
||||
return $authenticatorId;
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return self::PRIORITY;
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'remember-me';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeDefinition $node): void
|
||||
{
|
||||
$builder = $node
|
||||
->fixXmlConfig('user_provider')
|
||||
->children()
|
||||
;
|
||||
|
||||
$builder
|
||||
->scalarNode('secret')
|
||||
->cannotBeEmpty()
|
||||
->defaultValue('%kernel.secret%')
|
||||
->end()
|
||||
->scalarNode('service')->end()
|
||||
->arrayNode('user_providers')
|
||||
->beforeNormalization()
|
||||
->ifString()->then(fn ($v) => [$v])
|
||||
->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->booleanNode('catch_exceptions')->defaultTrue()->end()
|
||||
->arrayNode('signature_properties')
|
||||
->prototype('scalar')->end()
|
||||
->requiresAtLeastOneElement()
|
||||
->info('An array of properties on your User that are used to sign the remember-me cookie. If any of these change, all existing cookies will become invalid.')
|
||||
->example(['email', 'password'])
|
||||
->defaultValue(['password'])
|
||||
->end()
|
||||
->arrayNode('token_provider')
|
||||
->beforeNormalization()
|
||||
->ifString()->then(fn ($v) => ['service' => $v])
|
||||
->end()
|
||||
->children()
|
||||
->scalarNode('service')->info('The service ID of a custom rememberme token provider.')->end()
|
||||
->arrayNode('doctrine')
|
||||
->canBeEnabled()
|
||||
->children()
|
||||
->scalarNode('connection')->defaultNull()->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->scalarNode('token_verifier')
|
||||
->info('The service ID of a custom rememberme token verifier.')
|
||||
->end();
|
||||
|
||||
foreach ($this->options as $name => $value) {
|
||||
if ('secure' === $name) {
|
||||
$builder->enumNode($name)->values([true, false, 'auto'])->defaultValue('auto' === $value ? null : $value);
|
||||
} elseif ('samesite' === $name) {
|
||||
$builder->enumNode($name)->values([null, Cookie::SAMESITE_LAX, Cookie::SAMESITE_STRICT, Cookie::SAMESITE_NONE])->defaultValue($value);
|
||||
} elseif (\is_bool($value)) {
|
||||
$builder->booleanNode($name)->defaultValue($value);
|
||||
} elseif (\is_int($value)) {
|
||||
$builder->integerNode($name)->defaultValue($value);
|
||||
} else {
|
||||
$builder->scalarNode($name)->defaultValue($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function createTokenProvider(ContainerBuilder $container, string $firewallName, array $config): string
|
||||
{
|
||||
$tokenProviderId = $config['service'] ?? false;
|
||||
if ($config['doctrine']['enabled'] ?? false) {
|
||||
if (!class_exists(DoctrineTokenProvider::class)) {
|
||||
throw new InvalidConfigurationException('Cannot use the "doctrine" token provider for "remember_me" because the Doctrine Bridge is not installed. Try running "composer require symfony/doctrine-bridge".');
|
||||
}
|
||||
|
||||
if (null === $config['doctrine']['connection']) {
|
||||
$connectionId = 'database_connection';
|
||||
} else {
|
||||
$connectionId = 'doctrine.dbal.'.$config['doctrine']['connection'].'_connection';
|
||||
}
|
||||
|
||||
$tokenProviderId = 'security.remember_me.doctrine_token_provider.'.$firewallName;
|
||||
$container->register($tokenProviderId, DoctrineTokenProvider::class)
|
||||
->addArgument(new Reference($connectionId));
|
||||
}
|
||||
|
||||
if (!$tokenProviderId) {
|
||||
throw new InvalidConfigurationException(sprintf('No token provider was set for firewall "%s". Either configure a service ID or set "remember_me.token_provider.doctrine" to true.', $firewallName));
|
||||
}
|
||||
|
||||
return $tokenProviderId;
|
||||
}
|
||||
|
||||
private function createTokenVerifier(ContainerBuilder $container, string $firewallName, ?string $serviceId): Reference
|
||||
{
|
||||
if ($serviceId) {
|
||||
return new Reference($serviceId);
|
||||
}
|
||||
|
||||
$tokenVerifierId = 'security.remember_me.token_verifier.'.$firewallName;
|
||||
|
||||
$container->register($tokenVerifierId, CacheTokenVerifier::class)
|
||||
->addArgument(new Reference('cache.security_token_verifier', ContainerInterface::NULL_ON_INVALID_REFERENCE))
|
||||
->addArgument(60)
|
||||
->addArgument('rememberme-'.$firewallName.'-stale-');
|
||||
|
||||
return new Reference($tokenVerifierId, ContainerInterface::NULL_ON_INVALID_REFERENCE);
|
||||
}
|
||||
|
||||
public function prepend(ContainerBuilder $container): void
|
||||
{
|
||||
$rememberMeSecureDefault = false;
|
||||
$rememberMeSameSiteDefault = null;
|
||||
|
||||
if (!isset($container->getExtensions()['framework'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($container->getExtensionConfig('framework') as $config) {
|
||||
if (isset($config['session']) && \is_array($config['session'])) {
|
||||
$rememberMeSecureDefault = $config['session']['cookie_secure'] ?? $rememberMeSecureDefault;
|
||||
$rememberMeSameSiteDefault = \array_key_exists('cookie_samesite', $config['session']) ? $config['session']['cookie_samesite'] : $rememberMeSameSiteDefault;
|
||||
}
|
||||
}
|
||||
|
||||
$this->options['secure'] = $rememberMeSecureDefault;
|
||||
$this->options['samesite'] = $rememberMeSameSiteDefault;
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* RemoteUserFactory creates services for REMOTE_USER based authentication.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Maxime Douailin <maxime.douailin@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class RemoteUserFactory implements AuthenticatorFactoryInterface
|
||||
{
|
||||
public const PRIORITY = -10;
|
||||
|
||||
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string
|
||||
{
|
||||
$authenticatorId = 'security.authenticator.remote_user.'.$firewallName;
|
||||
$container
|
||||
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.remote_user'))
|
||||
->replaceArgument(0, new Reference($userProviderId))
|
||||
->replaceArgument(2, $firewallName)
|
||||
->replaceArgument(3, $config['user'])
|
||||
;
|
||||
|
||||
return $authenticatorId;
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return self::PRIORITY;
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'remote-user';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeDefinition $node): void
|
||||
{
|
||||
$node
|
||||
->children()
|
||||
->scalarNode('provider')->end()
|
||||
->scalarNode('user')->defaultValue('REMOTE_USER')->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* Stateless authenticators are authenticators that can work without a user provider.
|
||||
*
|
||||
* This situation can only occur in stateless firewalls, as statefull firewalls
|
||||
* need the user provider to refresh the user in each subsequent request. A
|
||||
* stateless authenticator can be used on both stateless and statefull authenticators.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*/
|
||||
interface StatelessAuthenticatorFactoryInterface extends AuthenticatorFactoryInterface
|
||||
{
|
||||
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, ?string $userProviderId): string|array;
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* X509Factory creates services for X509 certificate authentication.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class X509Factory implements AuthenticatorFactoryInterface
|
||||
{
|
||||
public const PRIORITY = -10;
|
||||
|
||||
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string
|
||||
{
|
||||
$authenticatorId = 'security.authenticator.x509.'.$firewallName;
|
||||
$container
|
||||
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.x509'))
|
||||
->replaceArgument(0, new Reference($userProviderId))
|
||||
->replaceArgument(2, $firewallName)
|
||||
->replaceArgument(3, $config['user'])
|
||||
->replaceArgument(4, $config['credentials'])
|
||||
->replaceArgument(6, $config['user_identifier'])
|
||||
;
|
||||
|
||||
return $authenticatorId;
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return self::PRIORITY;
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'x509';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeDefinition $node): void
|
||||
{
|
||||
$node
|
||||
->children()
|
||||
->scalarNode('provider')->end()
|
||||
->scalarNode('user')->defaultValue('SSL_CLIENT_S_DN_Email')->end()
|
||||
->scalarNode('credentials')->defaultValue('SSL_CLIENT_S_DN')->end()
|
||||
->scalarNode('user_identifier')->defaultValue('emailAddress')->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
}
|
||||
Vendored
+66
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Parameter;
|
||||
|
||||
/**
|
||||
* InMemoryFactory creates services for the memory provider.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Christophe Coevoet <stof@notk.org>
|
||||
*/
|
||||
class InMemoryFactory implements UserProviderFactoryInterface
|
||||
{
|
||||
public function create(ContainerBuilder $container, string $id, array $config): void
|
||||
{
|
||||
$definition = $container->setDefinition($id, new ChildDefinition('security.user.provider.in_memory'));
|
||||
$defaultPassword = new Parameter('container.build_id');
|
||||
$users = [];
|
||||
|
||||
foreach ($config['users'] as $username => $user) {
|
||||
$users[$username] = ['password' => null !== $user['password'] ? (string) $user['password'] : $defaultPassword, 'roles' => $user['roles']];
|
||||
}
|
||||
|
||||
$definition->addArgument($users);
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'memory';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeDefinition $node): void
|
||||
{
|
||||
$node
|
||||
->fixXmlConfig('user')
|
||||
->children()
|
||||
->arrayNode('users')
|
||||
->useAttributeAsKey('identifier')
|
||||
->normalizeKeys(false)
|
||||
->prototype('array')
|
||||
->children()
|
||||
->scalarNode('password')->defaultNull()->end()
|
||||
->arrayNode('roles')
|
||||
->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* LdapFactory creates services for Ldap user provider.
|
||||
*
|
||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||
* @author Charles Sarrazin <charles@sarraz.in>
|
||||
*/
|
||||
class LdapFactory implements UserProviderFactoryInterface
|
||||
{
|
||||
public function create(ContainerBuilder $container, string $id, array $config): void
|
||||
{
|
||||
$container
|
||||
->setDefinition($id, new ChildDefinition('security.user.provider.ldap'))
|
||||
->replaceArgument(0, new Reference($config['service']))
|
||||
->replaceArgument(1, $config['base_dn'])
|
||||
->replaceArgument(2, $config['search_dn'])
|
||||
->replaceArgument(3, $config['search_password'])
|
||||
->replaceArgument(4, $config['default_roles'])
|
||||
->replaceArgument(5, $config['uid_key'])
|
||||
->replaceArgument(6, $config['filter'])
|
||||
->replaceArgument(7, $config['password_attribute'])
|
||||
->replaceArgument(8, $config['extra_fields'])
|
||||
;
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'ldap';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeDefinition $node): void
|
||||
{
|
||||
$node
|
||||
->fixXmlConfig('extra_field')
|
||||
->fixXmlConfig('default_role')
|
||||
->children()
|
||||
->scalarNode('service')->isRequired()->cannotBeEmpty()->defaultValue('ldap')->end()
|
||||
->scalarNode('base_dn')->isRequired()->cannotBeEmpty()->end()
|
||||
->scalarNode('search_dn')->defaultNull()->end()
|
||||
->scalarNode('search_password')->defaultNull()->end()
|
||||
->arrayNode('extra_fields')
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->arrayNode('default_roles')
|
||||
->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end()
|
||||
->requiresAtLeastOneElement()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->scalarNode('uid_key')->defaultValue('sAMAccountName')->end()
|
||||
->scalarNode('filter')->defaultValue('({uid_key}={user_identifier})')->end()
|
||||
->scalarNode('password_attribute')->defaultNull()->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* UserProviderFactoryInterface is the interface for all user provider factories.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Christophe Coevoet <stof@notk.org>
|
||||
*/
|
||||
interface UserProviderFactoryInterface
|
||||
{
|
||||
public function create(ContainerBuilder $container, string $id, array $config): void;
|
||||
|
||||
public function getKey(): string;
|
||||
|
||||
public function addConfiguration(NodeDefinition $builder): void;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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\EventListener;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
|
||||
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\Security\Http\Firewall;
|
||||
use Symfony\Component\Security\Http\FirewallMapInterface;
|
||||
use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
|
||||
*/
|
||||
class FirewallListener extends Firewall
|
||||
{
|
||||
private FirewallMapInterface $map;
|
||||
private LogoutUrlGenerator $logoutUrlGenerator;
|
||||
|
||||
public function __construct(FirewallMapInterface $map, EventDispatcherInterface $dispatcher, LogoutUrlGenerator $logoutUrlGenerator)
|
||||
{
|
||||
$this->map = $map;
|
||||
$this->logoutUrlGenerator = $logoutUrlGenerator;
|
||||
|
||||
parent::__construct($map, $dispatcher);
|
||||
}
|
||||
|
||||
public function configureLogoutUrlGenerator(RequestEvent $event): void
|
||||
{
|
||||
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): void
|
||||
{
|
||||
if ($event->isMainRequest()) {
|
||||
$this->logoutUrlGenerator->setCurrentFirewall(null);
|
||||
}
|
||||
|
||||
parent::onKernelFinishRequest($event);
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
KernelEvents::REQUEST => [
|
||||
['configureLogoutUrlGenerator', 8],
|
||||
['onKernelRequest', 8],
|
||||
],
|
||||
KernelEvents::FINISH_REQUEST => 'onKernelFinishRequest',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\EventListener;
|
||||
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager;
|
||||
use Symfony\Component\Security\Core\Event\VoteEvent;
|
||||
|
||||
/**
|
||||
* Listen to vote events from traceable voters.
|
||||
*
|
||||
* @author Laurent VOULLEMIER <laurent.voullemier@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class VoteListener implements EventSubscriberInterface
|
||||
{
|
||||
private TraceableAccessDecisionManager $traceableAccessDecisionManager;
|
||||
|
||||
public function __construct(TraceableAccessDecisionManager $traceableAccessDecisionManager)
|
||||
{
|
||||
$this->traceableAccessDecisionManager = $traceableAccessDecisionManager;
|
||||
}
|
||||
|
||||
public function onVoterVote(VoteEvent $event): void
|
||||
{
|
||||
$this->traceableAccessDecisionManager->addVoterVote($event->getVoter(), $event->getAttributes(), $event->getVote());
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return ['debug.security.authorization.vote' => 'onVoterVote'];
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2004-present Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\LoginLink;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security\FirewallAwareTrait;
|
||||
use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Http\LoginLink\LoginLinkDetails;
|
||||
use Symfony\Component\Security\Http\LoginLink\LoginLinkHandlerInterface;
|
||||
|
||||
/**
|
||||
* Decorates the login link handler for the current firewall.
|
||||
*
|
||||
* @author Ryan Weaver <ryan@symfonycasts.com>
|
||||
*/
|
||||
class FirewallAwareLoginLinkHandler implements LoginLinkHandlerInterface
|
||||
{
|
||||
use FirewallAwareTrait;
|
||||
|
||||
private const FIREWALL_OPTION = 'login_link';
|
||||
|
||||
public function __construct(FirewallMap $firewallMap, ContainerInterface $loginLinkHandlerLocator, RequestStack $requestStack)
|
||||
{
|
||||
$this->firewallMap = $firewallMap;
|
||||
$this->locator = $loginLinkHandlerLocator;
|
||||
$this->requestStack = $requestStack;
|
||||
}
|
||||
|
||||
public function createLoginLink(UserInterface $user, ?Request $request = null, ?int $lifetime = null): LoginLinkDetails
|
||||
{
|
||||
return $this->getForFirewall()->createLoginLink($user, $request, $lifetime);
|
||||
}
|
||||
|
||||
public function consumeLoginLink(Request $request): UserInterface
|
||||
{
|
||||
return $this->getForFirewall()->consumeLoginLink($request);
|
||||
}
|
||||
}
|
||||
+13
@@ -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)
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\RememberMe;
|
||||
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Http\RememberMe\RememberMeDetails;
|
||||
use Symfony\Component\Security\Http\RememberMe\RememberMeHandlerInterface;
|
||||
|
||||
/**
|
||||
* Used as a "workaround" for tagging aliases in the RememberMeFactory.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class DecoratedRememberMeHandler implements RememberMeHandlerInterface
|
||||
{
|
||||
private RememberMeHandlerInterface $handler;
|
||||
|
||||
public function __construct(RememberMeHandlerInterface $handler)
|
||||
{
|
||||
$this->handler = $handler;
|
||||
}
|
||||
|
||||
public function createRememberMeCookie(UserInterface $user): void
|
||||
{
|
||||
$this->handler->createRememberMeCookie($user);
|
||||
}
|
||||
|
||||
public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails): UserInterface
|
||||
{
|
||||
return $this->handler->consumeRememberMeCookie($rememberMeDetails);
|
||||
}
|
||||
|
||||
public function clearRememberMeCookie(): void
|
||||
{
|
||||
$this->handler->clearRememberMeCookie();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
])
|
||||
;
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
|
||||
|
||||
use Symfony\Component\PasswordHasher\Command\UserPasswordHashCommand;
|
||||
|
||||
return static function (ContainerConfigurator $container) {
|
||||
$container->services()
|
||||
->set('security.command.user_password_hash', UserPasswordHashCommand::class)
|
||||
->args([
|
||||
service('security.password_hasher_factory'),
|
||||
abstract_arg('list of user classes'),
|
||||
])
|
||||
->tag('console.command')
|
||||
;
|
||||
};
|
||||
@@ -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'])
|
||||
;
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
|
||||
|
||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||
use Symfony\Component\Form\Extension\PasswordHasher\EventListener\PasswordHasherListener;
|
||||
use Symfony\Component\Form\Extension\PasswordHasher\Type\FormTypePasswordHasherExtension;
|
||||
use Symfony\Component\Form\Extension\PasswordHasher\Type\PasswordTypePasswordHasherExtension;
|
||||
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory;
|
||||
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
|
||||
return static function (ContainerConfigurator $container) {
|
||||
$container->services()
|
||||
->set('security.password_hasher_factory', PasswordHasherFactory::class)
|
||||
->args([[]])
|
||||
->alias(PasswordHasherFactoryInterface::class, 'security.password_hasher_factory')
|
||||
|
||||
->set('security.user_password_hasher', UserPasswordHasher::class)
|
||||
->args([service('security.password_hasher_factory')])
|
||||
->alias('security.password_hasher', 'security.user_password_hasher')
|
||||
->alias(UserPasswordHasherInterface::class, 'security.password_hasher')
|
||||
|
||||
->set('form.listener.password_hasher', PasswordHasherListener::class)
|
||||
->args([
|
||||
service('security.password_hasher'),
|
||||
service('property_accessor')->nullOnInvalid(),
|
||||
])
|
||||
|
||||
->set('form.type_extension.form.password_hasher', FormTypePasswordHasherExtension::class)
|
||||
->args([
|
||||
service('form.listener.password_hasher'),
|
||||
])
|
||||
->tag('form.type_extension', ['extended-type' => FormType::class])
|
||||
|
||||
->set('form.type_extension.password.password_hasher', PasswordTypePasswordHasherExtension::class)
|
||||
->args([
|
||||
service('form.listener.password_hasher'),
|
||||
])
|
||||
->tag('form.type_extension', ['extended-type' => PasswordType::class])
|
||||
;
|
||||
};
|
||||
@@ -0,0 +1,468 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<xsd:schema xmlns="http://symfony.com/schema/dic/security"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://symfony.com/schema/dic/security"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xsd:element name="config" type="config" />
|
||||
|
||||
<xsd:complexType name="config">
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="access-decision-manager" type="access_decision_manager" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="password_hashers" type="password_hashers" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="password_hasher" type="password_hasher" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xsd:element name="providers" type="providers" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="provider" type="provider" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xsd:element name="firewalls" type="firewalls" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="firewall" type="firewall" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xsd:element name="rule" type="rule" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xsd:element name="role" type="role" minOccurs="0" maxOccurs="unbounded" />
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="access-denied-url" type="xsd:string" />
|
||||
<xsd:attribute name="session-fixation-strategy" type="session_fixation_strategy" />
|
||||
<xsd:attribute name="hide-user-not-found" type="xsd:boolean" />
|
||||
<xsd:attribute name="always-authenticate-before-granting" type="xsd:boolean" />
|
||||
<xsd:attribute name="erase-credentials" type="xsd:boolean" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="password_hashers">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="password_hasher" type="password_hasher" minOccurs="1" maxOccurs="unbounded" />
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="providers">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="provider" type="provider" minOccurs="1" maxOccurs="unbounded" />
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="firewalls">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="firewall" type="firewall" minOccurs="1" maxOccurs="unbounded" />
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:simpleType name="session_fixation_strategy">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="none" />
|
||||
<xsd:enumeration value="migrate" />
|
||||
<xsd:enumeration value="invalidate" />
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
<xsd:complexType name="access_decision_manager">
|
||||
<xsd:attribute name="strategy" type="access_decision_manager_strategy" />
|
||||
<xsd:attribute name="service" type="xsd:string" />
|
||||
<xsd:attribute name="strategy-service" type="xsd:string" />
|
||||
<xsd:attribute name="allow-if-all-abstain" type="xsd:boolean" />
|
||||
<xsd:attribute name="allow-if-equal-granted-denied" type="xsd:boolean" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:simpleType name="access_decision_manager_strategy">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="affirmative" />
|
||||
<xsd:enumeration value="consensus" />
|
||||
<xsd:enumeration value="unanimous" />
|
||||
<xsd:enumeration value="priority" />
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
<xsd:complexType name="password_hasher">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="migrate-from" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="class" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="algorithm" type="xsd:string" />
|
||||
<xsd:attribute name="hash-algorithm" type="xsd:string" />
|
||||
<xsd:attribute name="key-length" type="xsd:string" />
|
||||
<xsd:attribute name="ignore-case" type="xsd:boolean" />
|
||||
<xsd:attribute name="encode-as-base64" type="xsd:boolean" />
|
||||
<xsd:attribute name="iterations" type="xsd:string" />
|
||||
<xsd:attribute name="cost" type="xsd:integer" />
|
||||
<xsd:attribute name="memory-cost" type="xsd:string" />
|
||||
<xsd:attribute name="time-cost" type="xsd:string" />
|
||||
<xsd:attribute name="id" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="provider">
|
||||
<xsd:choice minOccurs="0" maxOccurs="1">
|
||||
<xsd:element name="chain" type="chain" />
|
||||
<xsd:element name="memory" type="memory" />
|
||||
<xsd:element name="ldap" type="ldap" />
|
||||
<!-- allow factories to use dynamic elements -->
|
||||
<xsd:any processContents="lax" namespace="##other" />
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="id" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="chain">
|
||||
<xsd:sequence minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="provider" type="xsd:string" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="providers" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="memory">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="user" type="user" minOccurs="1" maxOccurs="unbounded" />
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="user">
|
||||
<xsd:attribute name="identifier" type="xsd:string" />
|
||||
<xsd:attribute name="password" type="xsd:string" />
|
||||
<xsd:attribute name="roles" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="ldap">
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="extra-field" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xsd:element name="default-role" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="service" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="base-dn" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="search-dn" type="xsd:string" />
|
||||
<xsd:attribute name="search-password" type="xsd:string" />
|
||||
<xsd:attribute name="uid-key" type="xsd:string" />
|
||||
<xsd:attribute name="filter" type="xsd:string" />
|
||||
<xsd:attribute name="password-attribute" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="firewall">
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="logout" type="logout" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="switch-user" type="switch_user" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="anonymous" type="anonymous" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="form-login" type="form_login" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="form-login-ldap" type="form_login_ldap" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="guard" type="guard" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="access-token" type="access_token" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="http-basic" type="http_basic" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="http-basic-ldap" type="http_basic_ldap" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="json-login" type="json_login" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="json-login-ldap" type="json_login_ldap" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="login-link" type="login_link" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="login-throttling" type="login_throttling" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="remember-me" type="remember_me" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="remote-user" type="remote_user" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="x509" type="x509" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="required-badge" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
|
||||
<!-- allow factories to use dynamic elements -->
|
||||
<xsd:any processContents="lax" minOccurs="0" maxOccurs="unbounded" namespace="##other" />
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="pattern" type="xsd:string" />
|
||||
<xsd:attribute name="host" type="xsd:string" />
|
||||
<xsd:attribute name="methods" type="xsd:string" />
|
||||
<xsd:attribute name="security" type="xsd:boolean" />
|
||||
<xsd:attribute name="user-checker" type="xsd:string" />
|
||||
<xsd:attribute name="request-matcher" type="xsd:string" />
|
||||
<xsd:attribute name="access-denied-url" type="xsd:string" />
|
||||
<xsd:attribute name="access-denied-handler" type="xsd:string" />
|
||||
<xsd:attribute name="entry-point" type="xsd:string" />
|
||||
<xsd:attribute name="provider" type="xsd:string" />
|
||||
<xsd:attribute name="stateless" type="xsd:boolean" />
|
||||
<xsd:attribute name="context" type="xsd:string" />
|
||||
<xsd:attribute name="lazy" type="xsd:boolean" />
|
||||
<!-- allow factories to use dynamic elements -->
|
||||
<xsd:anyAttribute processContents="lax" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="logout">
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="delete-cookie" type="delete_cookie" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xsd:element name="clear-site-data" type="clear_site_data" minOccurs="0" maxOccurs="unbounded" />
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="csrf-parameter" type="xsd:string" />
|
||||
<xsd:attribute name="csrf-token-manager" type="xsd:string" />
|
||||
<xsd:attribute name="csrf-token-id" type="xsd:string" />
|
||||
<xsd:attribute name="enable-csrf" type="xsd:boolean" />
|
||||
<xsd:attribute name="path" type="xsd:string" />
|
||||
<xsd:attribute name="target" type="xsd:string" />
|
||||
<xsd:attribute name="invalidate-session" type="xsd:boolean" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="delete_cookie">
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="path" type="xsd:string" />
|
||||
<xsd:attribute name="domain" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="switch_user">
|
||||
<xsd:attribute name="provider" type="xsd:string" />
|
||||
<xsd:attribute name="parameter" type="xsd:string" />
|
||||
<xsd:attribute name="role" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="anonymous">
|
||||
<xsd:attribute name="lazy" type="xsd:boolean" />
|
||||
<xsd:attribute name="secret" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="factory" abstract="true">
|
||||
<xsd:attribute name="check-path" type="xsd:string" />
|
||||
<xsd:attribute name="use-forward" type="xsd:boolean" />
|
||||
<xsd:attribute name="require-previous-session" type="xsd:boolean" />
|
||||
<xsd:attribute name="provider" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:attributeGroup name="success-handler-options">
|
||||
<xsd:attribute name="always-use-default-target-path" type="xsd:boolean" />
|
||||
<xsd:attribute name="default-target-path" type="xsd:string" />
|
||||
<xsd:attribute name="target-path-parameter" type="xsd:string" />
|
||||
<xsd:attribute name="use-referer" type="xsd:boolean" />
|
||||
</xsd:attributeGroup>
|
||||
|
||||
<xsd:attributeGroup name="failure-handler-options">
|
||||
<xsd:attribute name="failure-path" type="xsd:string" />
|
||||
<xsd:attribute name="failure-forward" type="xsd:boolean" />
|
||||
<xsd:attribute name="failure-path-parameter" type="xsd:string" />
|
||||
</xsd:attributeGroup>
|
||||
|
||||
<xsd:attributeGroup name="ldap-factory">
|
||||
<xsd:attribute name="service" type="xsd:string" />
|
||||
<xsd:attribute name="dn-string" type="xsd:string" />
|
||||
<xsd:attribute name="query-string" type="xsd:string" />
|
||||
<xsd:attribute name="search-dn" type="xsd:string" />
|
||||
<xsd:attribute name="search-password" type="xsd:string" />
|
||||
</xsd:attributeGroup>
|
||||
|
||||
<xsd:complexType name="form_login">
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="factory">
|
||||
<xsd:attribute name="login-path" type="xsd:string" />
|
||||
<xsd:attribute name="username-parameter" type="xsd:string" />
|
||||
<xsd:attribute name="password-parameter" type="xsd:string" />
|
||||
<xsd:attribute name="csrf-parameter" type="xsd:string" />
|
||||
<xsd:attribute name="csrf-token-id" type="xsd:string" />
|
||||
<xsd:attribute name="post-only" type="xsd:boolean" />
|
||||
<xsd:attribute name="csrf-token-generator" type="xsd:string" />
|
||||
<xsd:attribute name="enable-csrf" type="xsd:boolean" />
|
||||
<xsd:attributeGroup ref="success-handler-options" />
|
||||
<xsd:attributeGroup ref="failure-handler-options" />
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="form_login_ldap">
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="form_login">
|
||||
<xsd:attributeGroup ref="ldap-factory" />
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="guard">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="authenticator" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="provider" type="xsd:string" />
|
||||
<xsd:attribute name="entry-point" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="http_basic">
|
||||
<xsd:attribute name="provider" type="xsd:string" />
|
||||
<xsd:attribute name="realm" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="http_basic_ldap">
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="http_basic">
|
||||
<xsd:attributeGroup ref="ldap-factory" />
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="json_login">
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="factory">
|
||||
<xsd:attribute name="username-path" type="xsd:string" />
|
||||
<xsd:attribute name="password-path" type="xsd:string" />
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="json_login_ldap">
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="json_login">
|
||||
<xsd:attributeGroup ref="ldap-factory" />
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="login_link">
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="signature-property" type="xsd:string" />
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="check-route" type="xsd:string" />
|
||||
<xsd:attribute name="check-post-only" type="xsd:boolean" />
|
||||
<xsd:attribute name="lifetime" type="xsd:integer" />
|
||||
<xsd:attribute name="max-uses" type="xsd:integer" />
|
||||
<xsd:attribute name="used-link-cache" type="xsd:string" />
|
||||
<xsd:attribute name="success-handler" type="xsd:string" />
|
||||
<xsd:attribute name="failure-handler" type="xsd:string" />
|
||||
<xsd:attribute name="provider" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="access_token">
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="token-extractor" type="xsd:string" />
|
||||
<xsd:element name="token-handler" type="oidc_token_handler" />
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="token-handler" type="xsd:string" />
|
||||
<xsd:attribute name="realm" type="xsd:string" />
|
||||
<xsd:attribute name="success-handler" type="xsd:string" />
|
||||
<xsd:attribute name="failure-handler" type="xsd:string" />
|
||||
<xsd:attribute name="provider" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="oidc_token_handler">
|
||||
<xsd:sequence>
|
||||
<xsd:choice minOccurs="0" maxOccurs="1">
|
||||
<xsd:element name="oidc-user-info" type="oidc_user_info"></xsd:element>
|
||||
<xsd:element name="oidc" type="oidc"></xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="oidc-user-info" type="xsd:anyURI"></xsd:attribute>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="oidc_user_info">
|
||||
<xsd:attribute name="base-uri" type="xsd:anyURI" use="required" />
|
||||
<xsd:attribute name="claim" type="xsd:string" />
|
||||
<xsd:attribute name="client" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="oidc">
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="issuers" type="oidc_issuers" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="issuer" type="password_hasher" minOccurs="0" maxOccurs="unbounded" />
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="claim" type="xsd:string" />
|
||||
<xsd:attribute name="audience" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="algorithm" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="key" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="oidc_issuers">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="issuer" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="login_throttling">
|
||||
<xsd:attribute name="limiter" type="xsd:string" />
|
||||
<xsd:attribute name="max-attempts" type="xsd:integer" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="remember_me">
|
||||
<xsd:sequence minOccurs="0">
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="user-provider" type="xsd:string" />
|
||||
</xsd:choice>
|
||||
<xsd:element name="token-provider" type="remember_me_token_provider" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="lifetime" type="xsd:integer" />
|
||||
<xsd:attribute name="path" type="xsd:string" />
|
||||
<xsd:attribute name="domain" type="xsd:string" />
|
||||
<xsd:attribute name="http-only" type="xsd:boolean" />
|
||||
<xsd:attribute name="always-remember-me" type="xsd:boolean" />
|
||||
<xsd:attribute name="remember-me-parameter" type="xsd:string" />
|
||||
<xsd:attribute name="secret" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="service" type="xsd:string" />
|
||||
<xsd:attribute name="token-provider" type="xsd:string" />
|
||||
<xsd:attribute name="token-verifier" type="xsd:string" />
|
||||
<xsd:attribute name="catch-exceptions" type="xsd:boolean" />
|
||||
<xsd:attribute name="secure" type="remember_me_secure" />
|
||||
<xsd:attribute name="samesite" type="remember_me_samesite" />
|
||||
<xsd:attribute name="partitioned" type="xsd:boolean" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="remember_me_token_provider">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="doctrine" type="remember_me_token_provider_doctrine" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="service" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="remember_me_token_provider_doctrine">
|
||||
<xsd:attribute name="enabled" type="xsd:boolean" />
|
||||
<xsd:attribute name="connection" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:simpleType name="remember_me_secure">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="true" />
|
||||
<xsd:enumeration value="false" />
|
||||
<xsd:enumeration value="auto" />
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
<xsd:simpleType name="remember_me_samesite">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="null" />
|
||||
<xsd:enumeration value="lax" />
|
||||
<xsd:enumeration value="strict" />
|
||||
<xsd:enumeration value="none" />
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
<xsd:complexType name="remote_user">
|
||||
<xsd:attribute name="provider" type="xsd:string" />
|
||||
<xsd:attribute name="user" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="x509">
|
||||
<xsd:attribute name="provider" type="xsd:string" />
|
||||
<xsd:attribute name="user" type="xsd:string" />
|
||||
<xsd:attribute name="credentials" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="rule">
|
||||
<xsd:choice>
|
||||
<xsd:element name="ip" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xsd:element name="method" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xsd:element name="role" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xsd:element name="allow-if" type="xsd:string" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="attribute" type="rule_attribute" minOccurs="0" maxOccurs="1" />
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="requires-channel" type="xsd:string" />
|
||||
<xsd:attribute name="path" type="xsd:string" />
|
||||
<xsd:attribute name="host" type="xsd:string" />
|
||||
<xsd:attribute name="port" type="xsd:integer" />
|
||||
<xsd:attribute name="role" type="xsd:string" />
|
||||
<xsd:attribute name="methods" type="xsd:string" />
|
||||
<xsd:attribute name="allow-if" type="xsd:string" />
|
||||
<xsd:attribute name="route" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="role">
|
||||
<xsd:simpleContent>
|
||||
<xsd:extension base="xsd:string">
|
||||
<xsd:attribute name="id" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="value" type="xsd:string" />
|
||||
</xsd:extension>
|
||||
</xsd:simpleContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="rule_attribute">
|
||||
<xsd:simpleContent>
|
||||
<xsd:extension base="xsd:string">
|
||||
<xsd:attribute name="key" type="xsd:string" use="required" />
|
||||
</xsd:extension>
|
||||
</xsd:simpleContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:simpleType name="clear_site_data">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="*" />
|
||||
<xsd:enumeration value="cache" />
|
||||
<xsd:enumeration value="cookies" />
|
||||
<xsd:enumeration value="storage" />
|
||||
<xsd:enumeration value="executionContexts" />
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
</xsd:schema>
|
||||
@@ -0,0 +1,317 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\CacheWarmer\ExpressionCacheWarmer;
|
||||
use Symfony\Bundle\SecurityBundle\EventListener\FirewallListener;
|
||||
use Symfony\Bundle\SecurityBundle\Routing\LogoutRouteLoader;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Bundle\SecurityBundle\Security\FirewallConfig;
|
||||
use Symfony\Bundle\SecurityBundle\Security\FirewallContext;
|
||||
use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
|
||||
use Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext;
|
||||
use Symfony\Component\DependencyInjection\ServiceLocator;
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionLanguage as BaseExpressionLanguage;
|
||||
use Symfony\Component\Ldap\Security\LdapUserProvider;
|
||||
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorage;
|
||||
use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
|
||||
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\ExpressionLanguage;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter;
|
||||
use Symfony\Component\Security\Core\Role\RoleHierarchy;
|
||||
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
|
||||
use Symfony\Component\Security\Core\User\ChainUserProvider;
|
||||
use Symfony\Component\Security\Core\User\InMemoryUserChecker;
|
||||
use Symfony\Component\Security\Core\User\InMemoryUserProvider;
|
||||
use Symfony\Component\Security\Core\User\MissingUserProvider;
|
||||
use Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||
use Symfony\Component\Security\Http\Controller\SecurityTokenValueResolver;
|
||||
use Symfony\Component\Security\Http\Controller\UserValueResolver;
|
||||
use Symfony\Component\Security\Http\EventListener\IsGrantedAttributeListener;
|
||||
use Symfony\Component\Security\Http\Firewall;
|
||||
use Symfony\Component\Security\Http\FirewallMapInterface;
|
||||
use Symfony\Component\Security\Http\HttpUtils;
|
||||
use Symfony\Component\Security\Http\Impersonate\ImpersonateUrlGenerator;
|
||||
use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator;
|
||||
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy;
|
||||
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
|
||||
|
||||
return static function (ContainerConfigurator $container) {
|
||||
$container->parameters()
|
||||
->set('security.role_hierarchy.roles', [])
|
||||
;
|
||||
|
||||
$container->services()
|
||||
->set('security.authorization_checker', AuthorizationChecker::class)
|
||||
->args([
|
||||
service('security.token_storage'),
|
||||
service('security.access.decision_manager'),
|
||||
])
|
||||
->alias(AuthorizationCheckerInterface::class, 'security.authorization_checker')
|
||||
|
||||
->set('security.token_storage', UsageTrackingTokenStorage::class)
|
||||
->args([
|
||||
service('security.untracked_token_storage'),
|
||||
service_locator([
|
||||
'request_stack' => service('request_stack'),
|
||||
]),
|
||||
])
|
||||
->tag('kernel.reset', ['method' => 'disableUsageTracking'])
|
||||
->tag('kernel.reset', ['method' => 'setToken'])
|
||||
->alias(TokenStorageInterface::class, 'security.token_storage')
|
||||
|
||||
->set('security.untracked_token_storage', TokenStorage::class)
|
||||
|
||||
->set('security.helper', Security::class)
|
||||
->args([
|
||||
service_locator([
|
||||
'security.token_storage' => service('security.token_storage'),
|
||||
'security.authorization_checker' => service('security.authorization_checker'),
|
||||
'security.authenticator.managers_locator' => service('security.authenticator.managers_locator')->ignoreOnInvalid(),
|
||||
'request_stack' => service('request_stack'),
|
||||
'security.firewall.map' => service('security.firewall.map'),
|
||||
'security.user_checker_locator' => service('security.user_checker_locator'),
|
||||
'security.firewall.event_dispatcher_locator' => service('security.firewall.event_dispatcher_locator'),
|
||||
'security.csrf.token_manager' => service('security.csrf.token_manager')->ignoreOnInvalid(),
|
||||
]),
|
||||
abstract_arg('authenticators'),
|
||||
])
|
||||
->alias(Security::class, 'security.helper')
|
||||
|
||||
->set('security.user_value_resolver', UserValueResolver::class)
|
||||
->args([
|
||||
service('security.token_storage'),
|
||||
])
|
||||
->tag('controller.argument_value_resolver', ['priority' => 120, 'name' => UserValueResolver::class])
|
||||
|
||||
->set('security.security_token_value_resolver', SecurityTokenValueResolver::class)
|
||||
->args([
|
||||
service('security.token_storage'),
|
||||
])
|
||||
->tag('controller.argument_value_resolver', ['priority' => 120, 'name' => SecurityTokenValueResolver::class])
|
||||
|
||||
// Authentication related services
|
||||
->set('security.authentication.trust_resolver', AuthenticationTrustResolver::class)
|
||||
|
||||
->set('security.authentication.session_strategy', SessionAuthenticationStrategy::class)
|
||||
->args([
|
||||
param('security.authentication.session_strategy.strategy'),
|
||||
service('security.csrf.token_storage')->ignoreOnInvalid(),
|
||||
])
|
||||
->alias(SessionAuthenticationStrategyInterface::class, 'security.authentication.session_strategy')
|
||||
|
||||
->set('security.authentication.session_strategy_noop', SessionAuthenticationStrategy::class)
|
||||
->args(['none'])
|
||||
|
||||
->set('security.user_checker', InMemoryUserChecker::class)
|
||||
->set('security.user_checker_locator', ServiceLocator::class)
|
||||
->args([[]])
|
||||
|
||||
->set('security.expression_language', ExpressionLanguage::class)
|
||||
->args([service('cache.security_expression_language')->nullOnInvalid()])
|
||||
|
||||
->set('security.authentication_utils', AuthenticationUtils::class)
|
||||
->args([service('request_stack')])
|
||||
->alias(AuthenticationUtils::class, 'security.authentication_utils')
|
||||
|
||||
// Authorization related services
|
||||
->set('security.access.decision_manager', AccessDecisionManager::class)
|
||||
->args([[]])
|
||||
->alias(AccessDecisionManagerInterface::class, 'security.access.decision_manager')
|
||||
|
||||
->set('security.role_hierarchy', RoleHierarchy::class)
|
||||
->args([param('security.role_hierarchy.roles')])
|
||||
->alias(RoleHierarchyInterface::class, 'security.role_hierarchy')
|
||||
|
||||
// Security Voters
|
||||
->set('security.access.simple_role_voter', RoleVoter::class)
|
||||
->tag('security.voter', ['priority' => 245])
|
||||
|
||||
->set('security.access.authenticated_voter', AuthenticatedVoter::class)
|
||||
->args([service('security.authentication.trust_resolver')])
|
||||
->tag('security.voter', ['priority' => 250])
|
||||
|
||||
->set('security.access.role_hierarchy_voter', RoleHierarchyVoter::class)
|
||||
->args([service('security.role_hierarchy')])
|
||||
->tag('security.voter', ['priority' => 245])
|
||||
|
||||
->set('security.access.expression_voter', ExpressionVoter::class)
|
||||
->args([
|
||||
service('security.expression_language'),
|
||||
service('security.authentication.trust_resolver'),
|
||||
service('security.authorization_checker'),
|
||||
service('security.role_hierarchy')->nullOnInvalid(),
|
||||
])
|
||||
->tag('security.voter', ['priority' => 245])
|
||||
|
||||
->set('security.impersonate_url_generator', ImpersonateUrlGenerator::class)
|
||||
->args([
|
||||
service('request_stack'),
|
||||
service('security.firewall.map'),
|
||||
service('security.token_storage'),
|
||||
])
|
||||
|
||||
// Firewall related services
|
||||
->set('security.firewall', FirewallListener::class)
|
||||
->args([
|
||||
service('security.firewall.map'),
|
||||
service('event_dispatcher'),
|
||||
service('security.logout_url_generator'),
|
||||
])
|
||||
->tag('kernel.event_subscriber')
|
||||
->alias(Firewall::class, 'security.firewall')
|
||||
|
||||
->set('security.firewall.map', FirewallMap::class)
|
||||
->args([
|
||||
abstract_arg('Firewall context locator'),
|
||||
abstract_arg('Request matchers'),
|
||||
])
|
||||
->alias(FirewallMapInterface::class, 'security.firewall.map')
|
||||
|
||||
->set('security.firewall.context', FirewallContext::class)
|
||||
->abstract()
|
||||
->args([
|
||||
[],
|
||||
service('security.exception_listener'),
|
||||
abstract_arg('LogoutListener'),
|
||||
abstract_arg('FirewallConfig'),
|
||||
])
|
||||
|
||||
->set('security.firewall.lazy_context', LazyFirewallContext::class)
|
||||
->abstract()
|
||||
->args([
|
||||
[],
|
||||
service('security.exception_listener'),
|
||||
abstract_arg('LogoutListener'),
|
||||
abstract_arg('FirewallConfig'),
|
||||
service('security.untracked_token_storage'),
|
||||
])
|
||||
|
||||
->set('security.firewall.config', FirewallConfig::class)
|
||||
->abstract()
|
||||
->args([
|
||||
abstract_arg('name'),
|
||||
abstract_arg('user_checker'),
|
||||
abstract_arg('request_matcher'),
|
||||
false, // security enabled
|
||||
false, // stateless
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
[], // listeners
|
||||
null, // switch_user
|
||||
null, // logout
|
||||
])
|
||||
|
||||
->set('security.logout_url_generator', LogoutUrlGenerator::class)
|
||||
->args([
|
||||
service('request_stack')->nullOnInvalid(),
|
||||
service('router')->nullOnInvalid(),
|
||||
service('security.token_storage')->nullOnInvalid(),
|
||||
])
|
||||
|
||||
->set('security.route_loader.logout', LogoutRouteLoader::class)
|
||||
->args([
|
||||
'%security.logout_uris%',
|
||||
'security.logout_uris',
|
||||
])
|
||||
->tag('routing.route_loader')
|
||||
|
||||
// Provisioning
|
||||
->set('security.user.provider.missing', MissingUserProvider::class)
|
||||
->abstract()
|
||||
->args([
|
||||
abstract_arg('firewall'),
|
||||
])
|
||||
|
||||
->set('security.user.provider.in_memory', InMemoryUserProvider::class)
|
||||
->abstract()
|
||||
|
||||
->set('security.user.provider.ldap', LdapUserProvider::class)
|
||||
->abstract()
|
||||
->args([
|
||||
abstract_arg('security.ldap.ldap'),
|
||||
abstract_arg('base dn'),
|
||||
abstract_arg('search dn'),
|
||||
abstract_arg('search password'),
|
||||
abstract_arg('default_roles'),
|
||||
abstract_arg('uid key'),
|
||||
abstract_arg('filter'),
|
||||
abstract_arg('password_attribute'),
|
||||
abstract_arg('extra_fields (email etc)'),
|
||||
])
|
||||
|
||||
->set('security.user.provider.chain', ChainUserProvider::class)
|
||||
->abstract()
|
||||
|
||||
->set('security.http_utils', HttpUtils::class)
|
||||
->args([
|
||||
service('router')->nullOnInvalid(),
|
||||
service('router')->nullOnInvalid(),
|
||||
])
|
||||
->alias(HttpUtils::class, 'security.http_utils')
|
||||
|
||||
// Validator
|
||||
->set('security.validator.user_password', UserPasswordValidator::class)
|
||||
->args([
|
||||
service('security.token_storage'),
|
||||
service('security.password_hasher_factory'),
|
||||
])
|
||||
->tag('validator.constraint_validator', ['alias' => 'security.validator.user_password'])
|
||||
|
||||
// Cache
|
||||
->set('cache.security_expression_language')
|
||||
->parent('cache.system')
|
||||
->private()
|
||||
->tag('cache.pool')
|
||||
|
||||
// Cache Warmers
|
||||
->set('security.cache_warmer.expression', ExpressionCacheWarmer::class)
|
||||
->args([
|
||||
[],
|
||||
service('security.expression_language'),
|
||||
])
|
||||
->tag('kernel.cache_warmer')
|
||||
|
||||
->set('controller.is_granted_attribute_listener', IsGrantedAttributeListener::class)
|
||||
->args([
|
||||
service('security.authorization_checker'),
|
||||
service('security.is_granted_attribute_expression_language')->nullOnInvalid(),
|
||||
])
|
||||
->tag('kernel.event_subscriber')
|
||||
|
||||
->set('security.is_granted_attribute_expression_language', BaseExpressionLanguage::class)
|
||||
->args([service('cache.security_is_granted_attribute_expression_language')->nullOnInvalid()])
|
||||
|
||||
->set('cache.security_is_granted_attribute_expression_language')
|
||||
->parent('cache.system')
|
||||
->tag('cache.pool')
|
||||
|
||||
->set('security.is_csrf_token_valid_attribute_expression_language', BaseExpressionLanguage::class)
|
||||
->args([service('cache.security_is_csrf_token_valid_attribute_expression_language')->nullOnInvalid()])
|
||||
|
||||
->set('cache.security_is_csrf_token_valid_attribute_expression_language')
|
||||
->parent('cache.system')
|
||||
->tag('cache.pool')
|
||||
;
|
||||
};
|
||||
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\Security\UserAuthenticator;
|
||||
use Symfony\Component\DependencyInjection\ServiceLocator;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticatorManager;
|
||||
use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\FormLoginAuthenticator;
|
||||
use Symfony\Component\Security\Http\Authenticator\HttpBasicAuthenticator;
|
||||
use Symfony\Component\Security\Http\Authenticator\JsonLoginAuthenticator;
|
||||
use Symfony\Component\Security\Http\Authenticator\RemoteUserAuthenticator;
|
||||
use Symfony\Component\Security\Http\Authenticator\X509Authenticator;
|
||||
use Symfony\Component\Security\Http\Event\CheckPassportEvent;
|
||||
use Symfony\Component\Security\Http\EventListener\CheckCredentialsListener;
|
||||
use Symfony\Component\Security\Http\EventListener\LoginThrottlingListener;
|
||||
use Symfony\Component\Security\Http\EventListener\PasswordMigratingListener;
|
||||
use Symfony\Component\Security\Http\EventListener\SessionStrategyListener;
|
||||
use Symfony\Component\Security\Http\EventListener\UserCheckerListener;
|
||||
use Symfony\Component\Security\Http\EventListener\UserProviderListener;
|
||||
use Symfony\Component\Security\Http\Firewall\AuthenticatorManagerListener;
|
||||
|
||||
return static function (ContainerConfigurator $container) {
|
||||
$container->services()
|
||||
|
||||
// Manager
|
||||
->set('security.authenticator.manager', AuthenticatorManager::class)
|
||||
->abstract()
|
||||
->args([
|
||||
abstract_arg('authenticators'),
|
||||
service('security.token_storage'),
|
||||
service('event_dispatcher'),
|
||||
abstract_arg('provider key'),
|
||||
service('logger')->nullOnInvalid(),
|
||||
param('security.authentication.manager.erase_credentials'),
|
||||
param('security.authentication.hide_user_not_found'),
|
||||
abstract_arg('required badges'),
|
||||
])
|
||||
->tag('monolog.logger', ['channel' => 'security'])
|
||||
|
||||
->set('security.authenticator.managers_locator', ServiceLocator::class)
|
||||
->args([[]])
|
||||
|
||||
->set('security.user_authenticator', UserAuthenticator::class)
|
||||
->args([
|
||||
service('security.firewall.map'),
|
||||
service('security.authenticator.managers_locator'),
|
||||
service('request_stack'),
|
||||
])
|
||||
->alias(UserAuthenticatorInterface::class, 'security.user_authenticator')
|
||||
|
||||
->set('security.firewall.authenticator', AuthenticatorManagerListener::class)
|
||||
->abstract()
|
||||
->args([
|
||||
abstract_arg('authenticator manager'),
|
||||
])
|
||||
|
||||
// Listeners
|
||||
->set('security.listener.check_authenticator_credentials', CheckCredentialsListener::class)
|
||||
->args([
|
||||
service('security.password_hasher_factory'),
|
||||
])
|
||||
->tag('kernel.event_subscriber')
|
||||
|
||||
->set('security.listener.user_provider', UserProviderListener::class)
|
||||
->args([
|
||||
service('security.user_providers'),
|
||||
])
|
||||
->tag('kernel.event_listener', ['event' => CheckPassportEvent::class, 'priority' => 1024, 'method' => 'checkPassport'])
|
||||
|
||||
->set('security.listener.user_provider.abstract', UserProviderListener::class)
|
||||
->abstract()
|
||||
->args([
|
||||
abstract_arg('user provider'),
|
||||
])
|
||||
|
||||
->set('security.listener.password_migrating', PasswordMigratingListener::class)
|
||||
->args([
|
||||
service('security.password_hasher_factory'),
|
||||
])
|
||||
->tag('kernel.event_subscriber')
|
||||
|
||||
->set('security.listener.user_checker', UserCheckerListener::class)
|
||||
->abstract()
|
||||
->args([
|
||||
abstract_arg('user checker'),
|
||||
])
|
||||
|
||||
->set('security.listener.session', SessionStrategyListener::class)
|
||||
->abstract()
|
||||
->args([
|
||||
service('security.authentication.session_strategy'),
|
||||
])
|
||||
|
||||
->set('security.listener.login_throttling', LoginThrottlingListener::class)
|
||||
->abstract()
|
||||
->args([
|
||||
service('request_stack'),
|
||||
abstract_arg('request rate limiter'),
|
||||
])
|
||||
|
||||
// Authenticators
|
||||
->set('security.authenticator.http_basic', HttpBasicAuthenticator::class)
|
||||
->abstract()
|
||||
->args([
|
||||
abstract_arg('realm name'),
|
||||
abstract_arg('user provider'),
|
||||
service('logger')->nullOnInvalid(),
|
||||
])
|
||||
->tag('monolog.logger', ['channel' => 'security'])
|
||||
|
||||
->set('security.authenticator.form_login', FormLoginAuthenticator::class)
|
||||
->abstract()
|
||||
->args([
|
||||
service('security.http_utils'),
|
||||
abstract_arg('user provider'),
|
||||
abstract_arg('authentication success handler'),
|
||||
abstract_arg('authentication failure handler'),
|
||||
abstract_arg('options'),
|
||||
])
|
||||
|
||||
->set('security.authenticator.json_login', JsonLoginAuthenticator::class)
|
||||
->abstract()
|
||||
->args([
|
||||
service('security.http_utils'),
|
||||
abstract_arg('user provider'),
|
||||
abstract_arg('authentication success handler'),
|
||||
abstract_arg('authentication failure handler'),
|
||||
abstract_arg('options'),
|
||||
service('property_accessor')->nullOnInvalid(),
|
||||
])
|
||||
->call('setTranslator', [service('translator')->ignoreOnInvalid()])
|
||||
|
||||
->set('security.authenticator.x509', X509Authenticator::class)
|
||||
->abstract()
|
||||
->args([
|
||||
abstract_arg('user provider'),
|
||||
service('security.token_storage'),
|
||||
abstract_arg('firewall name'),
|
||||
abstract_arg('user key'),
|
||||
abstract_arg('credentials key'),
|
||||
service('logger')->nullOnInvalid(),
|
||||
abstract_arg('credentials user identifier'),
|
||||
])
|
||||
->tag('monolog.logger', ['channel' => 'security'])
|
||||
|
||||
->set('security.authenticator.remote_user', RemoteUserAuthenticator::class)
|
||||
->abstract()
|
||||
->args([
|
||||
abstract_arg('user provider'),
|
||||
service('security.token_storage'),
|
||||
abstract_arg('firewall name'),
|
||||
abstract_arg('user key'),
|
||||
service('logger')->nullOnInvalid(),
|
||||
])
|
||||
->tag('monolog.logger', ['channel' => 'security'])
|
||||
;
|
||||
};
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
|
||||
|
||||
use Jose\Component\Core\AlgorithmManager;
|
||||
use Jose\Component\Core\AlgorithmManagerFactory;
|
||||
use Jose\Component\Core\JWK;
|
||||
use Jose\Component\Core\JWKSet;
|
||||
use Jose\Component\Signature\Algorithm\ES256;
|
||||
use Jose\Component\Signature\Algorithm\ES384;
|
||||
use Jose\Component\Signature\Algorithm\ES512;
|
||||
use Jose\Component\Signature\Algorithm\PS256;
|
||||
use Jose\Component\Signature\Algorithm\PS384;
|
||||
use Jose\Component\Signature\Algorithm\PS512;
|
||||
use Jose\Component\Signature\Algorithm\RS256;
|
||||
use Jose\Component\Signature\Algorithm\RS384;
|
||||
use Jose\Component\Signature\Algorithm\RS512;
|
||||
use Symfony\Component\Security\Http\AccessToken\ChainAccessTokenExtractor;
|
||||
use Symfony\Component\Security\Http\AccessToken\FormEncodedBodyExtractor;
|
||||
use Symfony\Component\Security\Http\AccessToken\HeaderAccessTokenExtractor;
|
||||
use Symfony\Component\Security\Http\AccessToken\Oidc\OidcTokenHandler;
|
||||
use Symfony\Component\Security\Http\AccessToken\Oidc\OidcUserInfoTokenHandler;
|
||||
use Symfony\Component\Security\Http\AccessToken\QueryAccessTokenExtractor;
|
||||
use Symfony\Component\Security\Http\Authenticator\AccessTokenAuthenticator;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
return static function (ContainerConfigurator $container) {
|
||||
$container->services()
|
||||
->set('security.access_token_extractor.header', HeaderAccessTokenExtractor::class)
|
||||
->set('security.access_token_extractor.query_string', QueryAccessTokenExtractor::class)
|
||||
->set('security.access_token_extractor.request_body', FormEncodedBodyExtractor::class)
|
||||
|
||||
->set('security.authenticator.access_token', AccessTokenAuthenticator::class)
|
||||
->abstract()
|
||||
->args([
|
||||
abstract_arg('access token handler'),
|
||||
abstract_arg('access token extractor'),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
])
|
||||
|
||||
->set('security.authenticator.access_token.chain_extractor', ChainAccessTokenExtractor::class)
|
||||
->abstract()
|
||||
->args([
|
||||
abstract_arg('access token extractors'),
|
||||
])
|
||||
|
||||
// OIDC
|
||||
->set('security.access_token_handler.oidc_user_info.http_client', HttpClientInterface::class)
|
||||
->abstract()
|
||||
->factory([service('http_client'), 'withOptions'])
|
||||
->args([abstract_arg('http client options')])
|
||||
|
||||
->set('security.access_token_handler.oidc_user_info', OidcUserInfoTokenHandler::class)
|
||||
->abstract()
|
||||
->args([
|
||||
abstract_arg('http client'),
|
||||
service('logger')->nullOnInvalid(),
|
||||
abstract_arg('claim'),
|
||||
])
|
||||
|
||||
->set('security.access_token_handler.oidc', OidcTokenHandler::class)
|
||||
->abstract()
|
||||
->args([
|
||||
abstract_arg('signature algorithm'),
|
||||
abstract_arg('signature key'),
|
||||
abstract_arg('audience'),
|
||||
abstract_arg('issuers'),
|
||||
'sub',
|
||||
service('logger')->nullOnInvalid(),
|
||||
service('clock'),
|
||||
])
|
||||
|
||||
->set('security.access_token_handler.oidc.jwk', JWK::class)
|
||||
->abstract()
|
||||
->deprecate('symfony/security-http', '7.1', 'The "%service_id%" service is deprecated. Please use "security.access_token_handler.oidc.jwkset" instead')
|
||||
->factory([JWK::class, 'createFromJson'])
|
||||
->args([
|
||||
abstract_arg('signature key'),
|
||||
])
|
||||
|
||||
->set('security.access_token_handler.oidc.jwkset', JWKSet::class)
|
||||
->abstract()
|
||||
->factory([JWKSet::class, 'createFromJson'])
|
||||
->args([
|
||||
abstract_arg('signature keyset'),
|
||||
])
|
||||
|
||||
->set('security.access_token_handler.oidc.algorithm_manager_factory', AlgorithmManagerFactory::class)
|
||||
->args([
|
||||
tagged_iterator('security.access_token_handler.oidc.signature_algorithm'),
|
||||
])
|
||||
|
||||
->set('security.access_token_handler.oidc.signature', AlgorithmManager::class)
|
||||
->abstract()
|
||||
->factory([service('security.access_token_handler.oidc.algorithm_manager_factory'), 'create'])
|
||||
->args([
|
||||
abstract_arg('signature algorithms'),
|
||||
])
|
||||
|
||||
->set('security.access_token_handler.oidc.signature.ES256', ES256::class)
|
||||
->tag('security.access_token_handler.oidc.signature_algorithm')
|
||||
|
||||
->set('security.access_token_handler.oidc.signature.ES384', ES384::class)
|
||||
->tag('security.access_token_handler.oidc.signature_algorithm')
|
||||
|
||||
->set('security.access_token_handler.oidc.signature.ES512', ES512::class)
|
||||
->tag('security.access_token_handler.oidc.signature_algorithm')
|
||||
|
||||
->set('security.access_token_handler.oidc.signature.RS256', RS256::class)
|
||||
->tag('security.access_token_handler.oidc.signature_algorithm')
|
||||
|
||||
->set('security.access_token_handler.oidc.signature.RS384', RS384::class)
|
||||
->tag('security.access_token_handler.oidc.signature_algorithm')
|
||||
|
||||
->set('security.access_token_handler.oidc.signature.RS512', RS512::class)
|
||||
->tag('security.access_token_handler.oidc.signature_algorithm')
|
||||
|
||||
->set('security.access_token_handler.oidc.signature.PS256', PS256::class)
|
||||
->tag('security.access_token_handler.oidc.signature_algorithm')
|
||||
|
||||
->set('security.access_token_handler.oidc.signature.PS384', PS384::class)
|
||||
->tag('security.access_token_handler.oidc.signature_algorithm')
|
||||
|
||||
->set('security.access_token_handler.oidc.signature.PS512', PS512::class)
|
||||
->tag('security.access_token_handler.oidc.signature_algorithm')
|
||||
;
|
||||
};
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\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')
|
||||
;
|
||||
};
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\RememberMe\FirewallAwareRememberMeHandler;
|
||||
use Symfony\Component\Security\Core\Signature\SignatureHasher;
|
||||
use Symfony\Component\Security\Http\Authenticator\RememberMeAuthenticator;
|
||||
use Symfony\Component\Security\Http\EventListener\CheckRememberMeConditionsListener;
|
||||
use Symfony\Component\Security\Http\EventListener\RememberMeListener;
|
||||
use Symfony\Component\Security\Http\RememberMe\PersistentRememberMeHandler;
|
||||
use Symfony\Component\Security\Http\RememberMe\RememberMeHandlerInterface;
|
||||
use Symfony\Component\Security\Http\RememberMe\ResponseListener;
|
||||
use Symfony\Component\Security\Http\RememberMe\SignatureRememberMeHandler;
|
||||
|
||||
return static function (ContainerConfigurator $container) {
|
||||
$container->services()
|
||||
->set('security.rememberme.response_listener', ResponseListener::class)
|
||||
->tag('kernel.event_subscriber')
|
||||
|
||||
->set('security.authenticator.remember_me_signature_hasher', SignatureHasher::class)
|
||||
->args([
|
||||
service('property_accessor'),
|
||||
abstract_arg('signature properties'),
|
||||
'%kernel.secret%',
|
||||
null,
|
||||
null,
|
||||
])
|
||||
|
||||
->set('security.authenticator.signature_remember_me_handler', SignatureRememberMeHandler::class)
|
||||
->abstract()
|
||||
->args([
|
||||
abstract_arg('signature hasher'),
|
||||
abstract_arg('user provider'),
|
||||
service('request_stack'),
|
||||
abstract_arg('options'),
|
||||
service('logger')->nullOnInvalid(),
|
||||
])
|
||||
->tag('monolog.logger', ['channel' => 'security'])
|
||||
|
||||
->set('security.authenticator.persistent_remember_me_handler', PersistentRememberMeHandler::class)
|
||||
->abstract()
|
||||
->args([
|
||||
abstract_arg('token provider'),
|
||||
abstract_arg('user provider'),
|
||||
service('request_stack'),
|
||||
abstract_arg('options'),
|
||||
service('logger')->nullOnInvalid(),
|
||||
abstract_arg('token verifier'),
|
||||
])
|
||||
->tag('monolog.logger', ['channel' => 'security'])
|
||||
|
||||
->set('security.authenticator.firewall_aware_remember_me_handler', FirewallAwareRememberMeHandler::class)
|
||||
->args([
|
||||
service('security.firewall.map'),
|
||||
tagged_locator('security.remember_me_handler', 'firewall'),
|
||||
service('request_stack'),
|
||||
])
|
||||
->alias(RememberMeHandlerInterface::class, 'security.authenticator.firewall_aware_remember_me_handler')
|
||||
|
||||
->set('security.listener.check_remember_me_conditions', CheckRememberMeConditionsListener::class)
|
||||
->abstract()
|
||||
->args([
|
||||
abstract_arg('options'),
|
||||
service('logger')->nullOnInvalid(),
|
||||
])
|
||||
|
||||
->set('security.listener.remember_me', RememberMeListener::class)
|
||||
->abstract()
|
||||
->args([
|
||||
abstract_arg('remember me handler'),
|
||||
service('logger')->nullOnInvalid(),
|
||||
])
|
||||
->tag('monolog.logger', ['channel' => 'security'])
|
||||
|
||||
->set('security.authenticator.remember_me', RememberMeAuthenticator::class)
|
||||
->abstract()
|
||||
->args([
|
||||
abstract_arg('remember me handler'),
|
||||
param('kernel.secret'),
|
||||
service('security.token_storage'),
|
||||
abstract_arg('options'),
|
||||
service('logger')->nullOnInvalid(),
|
||||
])
|
||||
->tag('monolog.logger', ['channel' => 'security'])
|
||||
|
||||
// Cache
|
||||
->set('cache.security_token_verifier')
|
||||
->parent('cache.system')
|
||||
->private()
|
||||
->tag('cache.pool')
|
||||
;
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener;
|
||||
use Symfony\Bundle\SecurityBundle\EventListener\VoteListener;
|
||||
use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager;
|
||||
|
||||
return static function (ContainerConfigurator $container) {
|
||||
$container->services()
|
||||
->set('debug.security.access.decision_manager', TraceableAccessDecisionManager::class)
|
||||
->decorate('security.access.decision_manager')
|
||||
->args([
|
||||
service('debug.security.access.decision_manager.inner'),
|
||||
])
|
||||
|
||||
->set('debug.security.voter.vote_listener', VoteListener::class)
|
||||
->args([
|
||||
service('debug.security.access.decision_manager'),
|
||||
])
|
||||
->tag('kernel.event_subscriber')
|
||||
|
||||
->set('debug.security.firewall', TraceableFirewallListener::class)
|
||||
->args([
|
||||
service('security.firewall.map'),
|
||||
service('event_dispatcher'),
|
||||
service('security.logout_url_generator'),
|
||||
])
|
||||
->tag('kernel.event_subscriber')
|
||||
->tag('kernel.reset', ['method' => 'reset'])
|
||||
->alias('security.firewall', 'debug.security.firewall')
|
||||
;
|
||||
};
|
||||
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ServiceLocator;
|
||||
use Symfony\Component\Security\Http\AccessMap;
|
||||
use Symfony\Component\Security\Http\Authentication\CustomAuthenticationFailureHandler;
|
||||
use Symfony\Component\Security\Http\Authentication\CustomAuthenticationSuccessHandler;
|
||||
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler;
|
||||
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler;
|
||||
use Symfony\Component\Security\Http\EventListener\ClearSiteDataLogoutListener;
|
||||
use Symfony\Component\Security\Http\EventListener\CookieClearingLogoutListener;
|
||||
use Symfony\Component\Security\Http\EventListener\DefaultLogoutListener;
|
||||
use Symfony\Component\Security\Http\EventListener\SessionLogoutListener;
|
||||
use Symfony\Component\Security\Http\Firewall\AccessListener;
|
||||
use Symfony\Component\Security\Http\Firewall\ChannelListener;
|
||||
use Symfony\Component\Security\Http\Firewall\ContextListener;
|
||||
use Symfony\Component\Security\Http\Firewall\ExceptionListener;
|
||||
use Symfony\Component\Security\Http\Firewall\LogoutListener;
|
||||
use Symfony\Component\Security\Http\Firewall\SwitchUserListener;
|
||||
|
||||
return static function (ContainerConfigurator $container) {
|
||||
$container->services()
|
||||
|
||||
->set('security.channel_listener', ChannelListener::class)
|
||||
->args([
|
||||
service('security.access_map'),
|
||||
service('logger')->nullOnInvalid(),
|
||||
inline_service('int')->factory([service('router.request_context'), 'getHttpPort']),
|
||||
inline_service('int')->factory([service('router.request_context'), 'getHttpsPort']),
|
||||
])
|
||||
->tag('monolog.logger', ['channel' => 'security'])
|
||||
|
||||
->set('security.access_map', AccessMap::class)
|
||||
|
||||
->set('security.context_listener', ContextListener::class)
|
||||
->args([
|
||||
service('security.untracked_token_storage'),
|
||||
[],
|
||||
abstract_arg('Provider Key'),
|
||||
service('logger')->nullOnInvalid(),
|
||||
service('event_dispatcher')->nullOnInvalid(),
|
||||
service('security.authentication.trust_resolver'),
|
||||
])
|
||||
->tag('monolog.logger', ['channel' => 'security'])
|
||||
|
||||
->set('security.logout_listener', LogoutListener::class)
|
||||
->abstract()
|
||||
->args([
|
||||
service('security.token_storage'),
|
||||
service('security.http_utils'),
|
||||
abstract_arg('event dispatcher'),
|
||||
[], // Options
|
||||
])
|
||||
|
||||
->set('security.logout.listener.session', SessionLogoutListener::class)
|
||||
->abstract()
|
||||
|
||||
->set('security.logout.listener.clear_site_data', ClearSiteDataLogoutListener::class)
|
||||
->abstract()
|
||||
|
||||
->set('security.logout.listener.cookie_clearing', CookieClearingLogoutListener::class)
|
||||
->abstract()
|
||||
|
||||
->set('security.logout.listener.default', DefaultLogoutListener::class)
|
||||
->abstract()
|
||||
->args([
|
||||
service('security.http_utils'),
|
||||
abstract_arg('target url'),
|
||||
])
|
||||
|
||||
->set('security.authentication.listener.abstract')
|
||||
->abstract()
|
||||
->args([
|
||||
service('security.token_storage'),
|
||||
service('security.authentication.manager'),
|
||||
service('security.authentication.session_strategy'),
|
||||
service('security.http_utils'),
|
||||
abstract_arg('Provider-shared Key'),
|
||||
service('security.authentication.success_handler'),
|
||||
service('security.authentication.failure_handler'),
|
||||
[],
|
||||
service('logger')->nullOnInvalid(),
|
||||
service('event_dispatcher')->nullOnInvalid(),
|
||||
])
|
||||
->tag('monolog.logger', ['channel' => 'security'])
|
||||
|
||||
->set('security.authentication.custom_success_handler', CustomAuthenticationSuccessHandler::class)
|
||||
->abstract()
|
||||
->args([
|
||||
abstract_arg('The custom success handler service'),
|
||||
[], // Options
|
||||
abstract_arg('Provider-shared Key'),
|
||||
])
|
||||
|
||||
->set('security.authentication.success_handler', DefaultAuthenticationSuccessHandler::class)
|
||||
->abstract()
|
||||
->args([
|
||||
service('security.http_utils'),
|
||||
[], // Options
|
||||
service('logger')->nullOnInvalid(),
|
||||
])
|
||||
|
||||
->set('security.authentication.custom_failure_handler', CustomAuthenticationFailureHandler::class)
|
||||
->abstract()
|
||||
->args([
|
||||
abstract_arg('The custom failure handler service'),
|
||||
[], // Options
|
||||
])
|
||||
|
||||
->set('security.authentication.failure_handler', DefaultAuthenticationFailureHandler::class)
|
||||
->abstract()
|
||||
->args([
|
||||
service('http_kernel'),
|
||||
service('security.http_utils'),
|
||||
[], // Options
|
||||
service('logger')->nullOnInvalid(),
|
||||
])
|
||||
->tag('monolog.logger', ['channel' => 'security'])
|
||||
|
||||
->set('security.exception_listener', ExceptionListener::class)
|
||||
->abstract()
|
||||
->args([
|
||||
service('security.token_storage'),
|
||||
service('security.authentication.trust_resolver'),
|
||||
service('security.http_utils'),
|
||||
abstract_arg('Provider-shared Key'),
|
||||
service('security.authentication.entry_point')->nullOnInvalid(),
|
||||
param('security.access.denied_url'),
|
||||
service('security.access.denied_handler')->nullOnInvalid(),
|
||||
service('logger')->nullOnInvalid(),
|
||||
false, // Stateless
|
||||
])
|
||||
->tag('monolog.logger', ['channel' => 'security'])
|
||||
|
||||
->set('security.authentication.switchuser_listener', SwitchUserListener::class)
|
||||
->abstract()
|
||||
->args([
|
||||
service('security.token_storage'),
|
||||
abstract_arg('User Provider'),
|
||||
abstract_arg('User Checker'),
|
||||
abstract_arg('Provider Key'),
|
||||
service('security.access.decision_manager'),
|
||||
service('logger')->nullOnInvalid(),
|
||||
'_switch_user',
|
||||
'ROLE_ALLOWED_TO_SWITCH',
|
||||
service('event_dispatcher')->nullOnInvalid(),
|
||||
false, // Stateless
|
||||
service('router')->nullOnInvalid(),
|
||||
abstract_arg('Target Route'),
|
||||
])
|
||||
->tag('monolog.logger', ['channel' => 'security'])
|
||||
|
||||
->set('security.access_listener', AccessListener::class)
|
||||
->args([
|
||||
service('security.token_storage'),
|
||||
service('security.access.decision_manager'),
|
||||
service('security.access_map'),
|
||||
])
|
||||
->tag('monolog.logger', ['channel' => 'security'])
|
||||
|
||||
->set('security.firewall.event_dispatcher_locator', ServiceLocator::class)
|
||||
->args([[]])
|
||||
;
|
||||
};
|
||||
@@ -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')
|
||||
;
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-user" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" role="img">
|
||||
<title>Security</title>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<circle cx="12" cy="7" r="4"></circle>
|
||||
<path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 434 B |
@@ -0,0 +1,506 @@
|
||||
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
|
||||
|
||||
{% block page_title 'Security' %}
|
||||
|
||||
{% block head %}
|
||||
{{ parent() }}
|
||||
|
||||
<style>
|
||||
#collector-content .decision-log .voter_result td {
|
||||
border-top-width: 1px;
|
||||
border-bottom-width: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
#collector-content .decision-log .voter_details td {
|
||||
border-top-width: 0;
|
||||
border-bottom-width: 1px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
#collector-content .decision-log .voter_details table {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
box-shadow: unset;
|
||||
}
|
||||
|
||||
#collector-content .decision-log .voter_details table td {
|
||||
border: 0;
|
||||
padding: 0 0 8px 0;
|
||||
}
|
||||
|
||||
#collector-content .authenticators .badge {
|
||||
color: var(--white);
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
#collector-content .authenticators .badge.badge-resolved {
|
||||
background-color: var(--green-500);
|
||||
}
|
||||
#collector-content .authenticators .badge.badge-not_resolved {
|
||||
background-color: var(--yellow-500);
|
||||
}
|
||||
|
||||
#collector-content .authenticators svg[data-icon-name="icon-tabler-check"] {
|
||||
color: var(--green-500);
|
||||
}
|
||||
#collector-content .authenticators svg[data-icon-name="icon-tabler-x"] {
|
||||
color: var(--red-500);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block toolbar %}
|
||||
{% if collector.firewall %}
|
||||
{% set icon %}
|
||||
{{ source('@Security/Collector/icon.svg') }}
|
||||
<span class="sf-toolbar-value">{{ collector.user|default('n/a') }}</span>
|
||||
{% endset %}
|
||||
|
||||
{% set text %}
|
||||
{% if collector.impersonated %}
|
||||
<div class="sf-toolbar-info-group">
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Impersonator</b>
|
||||
<span>{{ collector.impersonatorUser }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="sf-toolbar-info-group">
|
||||
{% if collector.enabled %}
|
||||
{% if collector.token %}
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Logged in as</b>
|
||||
<span>{{ collector.user }}</span>
|
||||
</div>
|
||||
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Authenticated</b>
|
||||
<span class="sf-toolbar-status sf-toolbar-status-{{ collector.authenticated ? 'green' : 'yellow' }}">{{ collector.authenticated ? 'Yes' : 'No' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Roles</b>
|
||||
<span>
|
||||
{% set remainingRoles = collector.roles|slice(1) %}
|
||||
{{ collector.roles|first }}
|
||||
{% if remainingRoles is not empty %}
|
||||
+
|
||||
<abbr title="{{ remainingRoles|join(', ') }}">
|
||||
{{ remainingRoles|length }} more
|
||||
</abbr>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{% if collector.supportsRoleHierarchy %}
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Inherited Roles</b>
|
||||
<span>
|
||||
{% if collector.inheritedRoles is empty %}
|
||||
none
|
||||
{% else %}
|
||||
{% set remainingRoles = collector.inheritedRoles|slice(1) %}
|
||||
{{ collector.inheritedRoles|first }}
|
||||
{% if remainingRoles is not empty %}
|
||||
+
|
||||
<abbr title="{{ remainingRoles|join(', ') }}">
|
||||
{{ remainingRoles|length }} more
|
||||
</abbr>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Token class</b>
|
||||
<span>{{ collector.tokenClass|abbr_class }}</span>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Authenticated</b>
|
||||
<span class="sf-toolbar-status sf-toolbar-status-yellow">No</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if collector.firewall %}
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Firewall name</b>
|
||||
<span>{{ collector.firewall.name }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if collector.token and collector.logoutUrl %}
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Actions</b>
|
||||
<span>
|
||||
<a href="{{ collector.logoutUrl }}">Logout</a>
|
||||
{% if collector.impersonated and collector.impersonationExitPath %}
|
||||
| <a href="{{ collector.impersonationExitPath }}">Exit impersonation</a>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<span>The security is disabled.</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url }) }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block menu %}
|
||||
<span class="label {{ not collector.firewall or not collector.token ? 'disabled' }}">
|
||||
<span class="icon">{{ source('@Security/Collector/icon.svg') }}</span>
|
||||
<strong>Security</strong>
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
<h2>Security</h2>
|
||||
{% if collector.enabled %}
|
||||
<div class="sf-tabs">
|
||||
<div class="tab {{ collector.token is empty ? 'disabled' }}">
|
||||
<h3 class="tab-title">Token</h3>
|
||||
|
||||
<div class="tab-content">
|
||||
{% if collector.token %}
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<span class="value">{{ collector.user }}</span>
|
||||
<span class="label">Username</span>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<span class="value">{{ source('@WebProfiler/Icon/' ~ (collector.authenticated ? 'yes' : 'no') ~ '.svg') }}</span>
|
||||
<span class="label">Authenticated</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="key">Property</th>
|
||||
<th scope="col">Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Roles</th>
|
||||
<td>
|
||||
{{ collector.roles is empty ? 'none' : profiler_dump(collector.roles, maxDepth=1) }}
|
||||
|
||||
{% if not collector.authenticated and collector.roles is empty %}
|
||||
<p class="help">User is not authenticated probably because they have no roles.</p>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{% if collector.supportsRoleHierarchy %}
|
||||
<tr>
|
||||
<th>Inherited Roles</th>
|
||||
<td>{{ collector.inheritedRoles is empty ? 'none' : profiler_dump(collector.inheritedRoles, maxDepth=1) }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
{% if collector.token %}
|
||||
<tr>
|
||||
<th>Token</th>
|
||||
<td>{{ profiler_dump(collector.token) }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% elseif collector.enabled %}
|
||||
<div class="empty">
|
||||
<p>There is no security token.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab {{ (not collector.firewall or collector.firewall.security_enabled is empty) ? 'disabled' }}">
|
||||
<h3 class="tab-title">Firewall</h3>
|
||||
<div class="tab-content">
|
||||
{% if collector.firewall %}
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<span class="value">{{ collector.firewall.name }}</span>
|
||||
<span class="label">Name</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="value">{{ source('@WebProfiler/Icon/' ~ (collector.firewall.security_enabled ? 'yes' : 'no') ~ '.svg') }}</span>
|
||||
<span class="label">Security enabled</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="value">{{ source('@WebProfiler/Icon/' ~ (collector.firewall.stateless ? 'yes' : 'no') ~ '.svg') }}</span>
|
||||
<span class="label">Stateless</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if collector.firewall.security_enabled %}
|
||||
<h4>Configuration</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="key">Key</th>
|
||||
<th scope="col">Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>provider</th>
|
||||
<td>{{ collector.firewall.provider ?: '(none)' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>context</th>
|
||||
<td>{{ collector.firewall.context ?: '(none)' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>entry_point</th>
|
||||
<td>{{ collector.firewall.entry_point ?: '(none)' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>user_checker</th>
|
||||
<td>{{ collector.firewall.user_checker ?: '(none)' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>access_denied_handler</th>
|
||||
<td>{{ collector.firewall.access_denied_handler ?: '(none)' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>access_denied_url</th>
|
||||
<td>{{ collector.firewall.access_denied_url ?: '(none)' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>authenticators</th>
|
||||
<td>{{ collector.firewall.authenticators is empty ? '(none)' : profiler_dump(collector.firewall.authenticators, maxDepth=1) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab {{ collector.listeners|default([]) is empty ? 'disabled' }}">
|
||||
<h3 class="tab-title">Listeners</h3>
|
||||
<div class="tab-content">
|
||||
{% if collector.listeners|default([]) is empty %}
|
||||
<div class="empty">
|
||||
<p>No security listeners have been recorded. Check that debugging is enabled in the kernel.</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Listener</th>
|
||||
<th>Duration</th>
|
||||
<th>Response</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
{% set previous_event = (collector.listeners|first) %}
|
||||
{% for listener in collector.listeners %}
|
||||
{% if loop.first or listener != previous_event %}
|
||||
{% if not loop.first %}
|
||||
</tbody>
|
||||
{% endif %}
|
||||
<tbody>
|
||||
{% set previous_event = listener %}
|
||||
{% endif %}
|
||||
|
||||
<tr>
|
||||
<td class="font-normal">{{ profiler_dump(listener.stub) }}</td>
|
||||
<td class="no-wrap">{{ '%0.2f'|format(listener.time * 1000) }} ms</td>
|
||||
<td class="font-normal">{{ listener.response ? profiler_dump(listener.response) : '(none)' }}</td>
|
||||
</tr>
|
||||
|
||||
{% if loop.last %}
|
||||
</tbody>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab {{ collector.authenticators|default([]) is empty ? 'disabled' }}">
|
||||
<h3 class="tab-title">Authenticators</h3>
|
||||
<div class="tab-content">
|
||||
{% if collector.authenticators|default([]) is not empty %}
|
||||
<table class="authenticators">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Authenticator</th>
|
||||
<th>Supports</th>
|
||||
<th>Authenticated</th>
|
||||
<th>Duration</th>
|
||||
<th>Passport</th>
|
||||
<th>Badges</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
{% set previous_event = (collector.listeners|first) %}
|
||||
{% for authenticator in collector.authenticators %}
|
||||
{% if loop.first or authenticator != previous_event %}
|
||||
{% if not loop.first %}
|
||||
</tbody>
|
||||
{% endif %}
|
||||
|
||||
<tbody>
|
||||
{% set previous_event = authenticator %}
|
||||
{% endif %}
|
||||
|
||||
<tr>
|
||||
<td class="font-normal">{{ profiler_dump(authenticator.stub) }}</td>
|
||||
<td class="no-wrap">{{ source('@WebProfiler/Icon/' ~ (authenticator.supports ? 'yes' : 'no') ~ '.svg') }}</td>
|
||||
<td class="no-wrap">{{ authenticator.authenticated is not null ? source('@WebProfiler/Icon/' ~ (authenticator.authenticated ? 'yes' : 'no') ~ '.svg') : '' }}</td>
|
||||
<td class="no-wrap">{{ '%0.2f'|format(authenticator.duration * 1000) }} ms</td>
|
||||
<td class="font-normal">{{ authenticator.passport ? profiler_dump(authenticator.passport) : '(none)' }}</td>
|
||||
<td class="font-normal">
|
||||
{% for badge in authenticator.badges ?? [] %}
|
||||
<span class="badge badge-{{ badge.resolved ? 'resolved' : 'not_resolved' }}">
|
||||
{{ badge.stub|abbr_class }}
|
||||
</span>
|
||||
{% else %}
|
||||
(none)
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{% if loop.last %}
|
||||
</tbody>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="empty">
|
||||
<p>No authenticators have been recorded. Check previous profiles on your authentication endpoint.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab {{ collector.accessDecisionLog|default([]) is empty ? 'disabled' }}">
|
||||
<h3 class="tab-title">Access Decision</h3>
|
||||
<div class="tab-content">
|
||||
{% if collector.voters|default([]) is not empty %}
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<span class="value">{{ collector.voterStrategy|default('unknown') }}</span>
|
||||
<span class="label">Strategy</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="voters">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Voter class</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for voter in collector.voters %}
|
||||
<tr>
|
||||
<td class="font-normal text-small text-muted nowrap">{{ loop.index }}</td>
|
||||
<td class="font-normal">{{ profiler_dump(voter) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% if collector.accessDecisionLog|default([]) is not empty %}
|
||||
<h2>Access decision log</h2>
|
||||
|
||||
<table class="decision-log">
|
||||
<col style="width: 30px">
|
||||
<col style="width: 120px">
|
||||
<col style="width: 25%">
|
||||
<col style="width: 60%">
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Result</th>
|
||||
<th>Attributes</th>
|
||||
<th>Object</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for decision in collector.accessDecisionLog %}
|
||||
<tr class="voter_result">
|
||||
<td class="font-normal text-small text-muted nowrap">{{ loop.index }}</td>
|
||||
<td class="font-normal">
|
||||
{{ decision.result
|
||||
? '<span class="label status-success same-width">GRANTED</span>'
|
||||
: '<span class="label status-error same-width">DENIED</span>'
|
||||
}}
|
||||
</td>
|
||||
<td>
|
||||
{% if decision.attributes|length == 1 %}
|
||||
{% set attribute = decision.attributes|first %}
|
||||
{% if attribute.expression is defined %}
|
||||
Expression: <pre><code>{{ attribute.expression }}</code></pre>
|
||||
{% elseif attribute.type == 'string' %}
|
||||
{{ attribute }}
|
||||
{% else %}
|
||||
{{ profiler_dump(attribute) }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{{ profiler_dump(decision.attributes) }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ profiler_dump(decision.seek('object')) }}</td>
|
||||
</tr>
|
||||
<tr class="voter_details">
|
||||
<td></td>
|
||||
<td colspan="3">
|
||||
{% if decision.voter_details is not empty %}
|
||||
{% set voter_details_id = 'voter-details-' ~ loop.index %}
|
||||
<div id="{{ voter_details_id }}" class="sf-toggle-content sf-toggle-hidden">
|
||||
<table>
|
||||
<tbody>
|
||||
{% for voter_detail in decision.voter_details %}
|
||||
<tr>
|
||||
<td class="font-normal">{{ profiler_dump(voter_detail['class']) }}</td>
|
||||
{% if collector.voterStrategy == 'unanimous' %}
|
||||
<td class="font-normal text-small">attribute {{ voter_detail['attributes'][0] }}</td>
|
||||
{% endif %}
|
||||
<td class="font-normal text-small">
|
||||
{% if voter_detail['vote'] == constant('Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::ACCESS_GRANTED') %}
|
||||
ACCESS GRANTED
|
||||
{% elseif voter_detail['vote'] == constant('Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::ACCESS_ABSTAIN') %}
|
||||
ACCESS ABSTAIN
|
||||
{% elseif voter_detail['vote'] == constant('Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::ACCESS_DENIED') %}
|
||||
ACCESS DENIED
|
||||
{% else %}
|
||||
unknown ({{ voter_detail['vote'] }})
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<a class="btn btn-link text-small sf-toggle" data-toggle-selector="#{{ voter_details_id }}" data-toggle-alt-content="Hide voter details">Show voter details</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\Routing;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Config\ContainerParametersResource;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
final class LogoutRouteLoader
|
||||
{
|
||||
/**
|
||||
* @param array<string, string> $logoutUris Logout URIs indexed by the corresponding firewall name
|
||||
* @param string $parameterName Name of the container parameter containing {@see $logoutUris} value
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly array $logoutUris,
|
||||
private readonly string $parameterName,
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(): RouteCollection
|
||||
{
|
||||
$collection = new RouteCollection();
|
||||
$collection->addResource(new ContainerParametersResource([$this->parameterName => $this->logoutUris]));
|
||||
|
||||
$routeNames = [];
|
||||
foreach ($this->logoutUris as $firewallName => $logoutPath) {
|
||||
$routeName = '_logout_'.$firewallName;
|
||||
|
||||
if (isset($routeNames[$logoutPath])) {
|
||||
$collection->addAlias($routeName, $routeNames[$logoutPath]);
|
||||
} else {
|
||||
$routeNames[$logoutPath] = $routeName;
|
||||
$collection->add($routeName, new Route($logoutPath));
|
||||
}
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
}
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security\FirewallConfig;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
use Symfony\Component\Security\Core\Exception\LogicException;
|
||||
use Symfony\Component\Security\Core\Exception\LogoutException;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Csrf\CsrfToken;
|
||||
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface;
|
||||
use Symfony\Component\Security\Http\Event\LogoutEvent;
|
||||
use Symfony\Component\Security\Http\ParameterBagUtils;
|
||||
use Symfony\Contracts\Service\ServiceProviderInterface;
|
||||
|
||||
/**
|
||||
* Helper class for commonly-needed security tasks.
|
||||
*
|
||||
* @author Ryan Weaver <ryan@symfonycasts.com>
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
* @author Arnaud Frézet <arnaud@larriereguichet.fr>
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class Security implements AuthorizationCheckerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ContainerInterface $container,
|
||||
private readonly array $authenticators = [],
|
||||
) {
|
||||
}
|
||||
|
||||
public function getUser(): ?UserInterface
|
||||
{
|
||||
if (!$token = $this->getToken()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $token->getUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the attributes are granted against the current authentication token and optionally supplied subject.
|
||||
*/
|
||||
public function isGranted(mixed $attributes, mixed $subject = null): bool
|
||||
{
|
||||
return $this->container->get('security.authorization_checker')
|
||||
->isGranted($attributes, $subject);
|
||||
}
|
||||
|
||||
public function getToken(): ?TokenInterface
|
||||
{
|
||||
return $this->container->get('security.token_storage')->getToken();
|
||||
}
|
||||
|
||||
public function getFirewallConfig(Request $request): ?FirewallConfig
|
||||
{
|
||||
return $this->container->get('security.firewall.map')->getFirewallConfig($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserInterface $user The user to authenticate
|
||||
* @param string|null $authenticatorName The authenticator name (e.g. "form_login") or service id (e.g. SomeApiKeyAuthenticator::class) - required only if multiple authenticators are configured
|
||||
* @param string|null $firewallName The firewall name - required only if multiple firewalls are configured
|
||||
* @param BadgeInterface[] $badges Badges to add to the user's passport
|
||||
*
|
||||
* @return Response|null The authenticator success response if any
|
||||
*/
|
||||
public function login(UserInterface $user, ?string $authenticatorName = null, ?string $firewallName = null, array $badges = []): ?Response
|
||||
{
|
||||
$request = $this->container->get('request_stack')->getCurrentRequest();
|
||||
if (null === $request) {
|
||||
throw new LogicException('Unable to login without a request context.');
|
||||
}
|
||||
|
||||
$firewallName ??= $this->getFirewallConfig($request)?->getName();
|
||||
|
||||
if (!$firewallName) {
|
||||
throw new LogicException('Unable to login as the current route is not covered by any firewall.');
|
||||
}
|
||||
|
||||
$authenticator = $this->getAuthenticator($authenticatorName, $firewallName);
|
||||
|
||||
$userCheckerLocator = $this->container->get('security.user_checker_locator');
|
||||
$userCheckerLocator->get($firewallName)->checkPreAuth($user);
|
||||
|
||||
return $this->container->get('security.authenticator.managers_locator')->get($firewallName)->authenticateUser($user, $authenticator, $request, $badges);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout the current user by dispatching the LogoutEvent.
|
||||
*
|
||||
* @param bool $validateCsrfToken Whether to look for a valid CSRF token based on the `logout` listener configuration
|
||||
*
|
||||
* @return Response|null The LogoutEvent's Response if any
|
||||
*
|
||||
* @throws LogoutException When $validateCsrfToken is true and the CSRF token is not found or invalid
|
||||
*/
|
||||
public function logout(bool $validateCsrfToken = true): ?Response
|
||||
{
|
||||
$request = $this->container->get('request_stack')->getMainRequest();
|
||||
if (null === $request) {
|
||||
throw new LogicException('Unable to logout without a request context.');
|
||||
}
|
||||
|
||||
/** @var TokenStorageInterface $tokenStorage */
|
||||
$tokenStorage = $this->container->get('security.token_storage');
|
||||
|
||||
if (!($token = $tokenStorage->getToken()) || !$token->getUser()) {
|
||||
throw new LogicException('Unable to logout as there is no logged-in user.');
|
||||
}
|
||||
|
||||
if (!$firewallConfig = $this->container->get('security.firewall.map')->getFirewallConfig($request)) {
|
||||
throw new LogicException('Unable to logout as the request is not behind a firewall.');
|
||||
}
|
||||
|
||||
if ($validateCsrfToken) {
|
||||
if (!$this->container->has('security.csrf.token_manager') || !$logoutConfig = $firewallConfig->getLogout()) {
|
||||
throw new LogicException(sprintf('Unable to logout with CSRF token validation. Either make sure that CSRF protection is enabled and "logout" is configured on the "%s" firewall, or bypass CSRF token validation explicitly by passing false to the $validateCsrfToken argument of this method.', $firewallConfig->getName()));
|
||||
}
|
||||
$csrfToken = ParameterBagUtils::getRequestParameterValue($request, $logoutConfig['csrf_parameter']);
|
||||
if (!\is_string($csrfToken) || !$this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($logoutConfig['csrf_token_id'], $csrfToken))) {
|
||||
throw new LogoutException('Invalid CSRF token.');
|
||||
}
|
||||
}
|
||||
|
||||
$logoutEvent = new LogoutEvent($request, $token);
|
||||
$this->container->get('security.firewall.event_dispatcher_locator')->get($firewallConfig->getName())->dispatch($logoutEvent);
|
||||
|
||||
$tokenStorage->setToken(null);
|
||||
|
||||
return $logoutEvent->getResponse();
|
||||
}
|
||||
|
||||
private function getAuthenticator(?string $authenticatorName, string $firewallName): AuthenticatorInterface
|
||||
{
|
||||
if (!isset($this->authenticators[$firewallName])) {
|
||||
throw new LogicException(sprintf('No authenticators found for firewall "%s".', $firewallName));
|
||||
}
|
||||
|
||||
/** @var ServiceProviderInterface $firewallAuthenticatorLocator */
|
||||
$firewallAuthenticatorLocator = $this->authenticators[$firewallName];
|
||||
|
||||
if (!$authenticatorName) {
|
||||
$authenticatorIds = array_keys($firewallAuthenticatorLocator->getProvidedServices());
|
||||
|
||||
if (!$authenticatorIds) {
|
||||
throw new LogicException(sprintf('No authenticator was found for the firewall "%s".', $firewallName));
|
||||
}
|
||||
if (1 < \count($authenticatorIds)) {
|
||||
throw new LogicException(sprintf('Too many authenticators were found for the current firewall "%s". You must provide an instance of "%s" to login programmatically. The available authenticators for the firewall "%s" are "%s".', $firewallName, AuthenticatorInterface::class, $firewallName, implode('" ,"', $authenticatorIds)));
|
||||
}
|
||||
|
||||
return $firewallAuthenticatorLocator->get($authenticatorIds[0]);
|
||||
}
|
||||
|
||||
if ($firewallAuthenticatorLocator->has($authenticatorName)) {
|
||||
return $firewallAuthenticatorLocator->get($authenticatorName);
|
||||
}
|
||||
|
||||
$authenticatorId = 'security.authenticator.'.$authenticatorName.'.'.$firewallName;
|
||||
|
||||
if (!$firewallAuthenticatorLocator->has($authenticatorId)) {
|
||||
throw new LogicException(sprintf('Unable to find an authenticator named "%s" for the firewall "%s". Available authenticators: "%s".', $authenticatorName, $firewallName, implode('", "', array_keys($firewallAuthenticatorLocator->getProvidedServices()))));
|
||||
}
|
||||
|
||||
return $firewallAuthenticatorLocator->get($authenticatorId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\Security;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* Provides basic functionality for services mapped by the firewall name
|
||||
* in a container locator.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait FirewallAwareTrait
|
||||
{
|
||||
private ContainerInterface $locator;
|
||||
private RequestStack $requestStack;
|
||||
private FirewallMap $firewallMap;
|
||||
|
||||
private function getForFirewall(): object
|
||||
{
|
||||
$serviceIdentifier = str_replace('FirewallAware', '', static::class);
|
||||
if (null === $request = $this->requestStack->getCurrentRequest()) {
|
||||
throw new \LogicException('Cannot determine the correct '.$serviceIdentifier.' to use: there is no active Request and so, the firewall cannot be determined. Try using a specific '.$serviceIdentifier.' service.');
|
||||
}
|
||||
|
||||
$firewall = $this->firewallMap->getFirewallConfig($request);
|
||||
if (!$firewall) {
|
||||
throw new \LogicException('No '.$serviceIdentifier.' found as the current route is not covered by a firewall.');
|
||||
}
|
||||
|
||||
$firewallName = $firewall->getName();
|
||||
if (!$this->locator->has($firewallName)) {
|
||||
$message = 'No '.$serviceIdentifier.' found for this firewall.';
|
||||
if (\defined(static::class.'::FIREWALL_OPTION')) {
|
||||
$message .= sprintf(' Did you forget to add a "'.static::FIREWALL_OPTION.'" key under your "%s" firewall?', $firewallName);
|
||||
}
|
||||
|
||||
throw new \LogicException($message);
|
||||
}
|
||||
|
||||
return $this->locator->get($firewallName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\Security;
|
||||
|
||||
/**
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
final class FirewallConfig
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $name,
|
||||
private readonly string $userChecker,
|
||||
private readonly ?string $requestMatcher = null,
|
||||
private readonly bool $securityEnabled = true,
|
||||
private readonly bool $stateless = false,
|
||||
private readonly ?string $provider = null,
|
||||
private readonly ?string $context = null,
|
||||
private readonly ?string $entryPoint = null,
|
||||
private readonly ?string $accessDeniedHandler = null,
|
||||
private readonly ?string $accessDeniedUrl = null,
|
||||
private readonly array $authenticators = [],
|
||||
private readonly ?array $switchUser = null,
|
||||
private readonly ?array $logout = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null The request matcher service id or null if neither the request matcher, pattern or host
|
||||
* options were provided
|
||||
*/
|
||||
public function getRequestMatcher(): ?string
|
||||
{
|
||||
return $this->requestMatcher;
|
||||
}
|
||||
|
||||
public function isSecurityEnabled(): bool
|
||||
{
|
||||
return $this->securityEnabled;
|
||||
}
|
||||
|
||||
public function isStateless(): bool
|
||||
{
|
||||
return $this->stateless;
|
||||
}
|
||||
|
||||
public function getProvider(): ?string
|
||||
{
|
||||
return $this->provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null The context key (will be null if the firewall is stateless)
|
||||
*/
|
||||
public function getContext(): ?string
|
||||
{
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
public function getEntryPoint(): ?string
|
||||
{
|
||||
return $this->entryPoint;
|
||||
}
|
||||
|
||||
public function getUserChecker(): string
|
||||
{
|
||||
return $this->userChecker;
|
||||
}
|
||||
|
||||
public function getAccessDeniedHandler(): ?string
|
||||
{
|
||||
return $this->accessDeniedHandler;
|
||||
}
|
||||
|
||||
public function getAccessDeniedUrl(): ?string
|
||||
{
|
||||
return $this->accessDeniedUrl;
|
||||
}
|
||||
|
||||
public function getAuthenticators(): array
|
||||
{
|
||||
return $this->authenticators;
|
||||
}
|
||||
|
||||
public function getSwitchUser(): ?array
|
||||
{
|
||||
return $this->switchUser;
|
||||
}
|
||||
|
||||
public function getLogout(): ?array
|
||||
{
|
||||
return $this->logout;
|
||||
}
|
||||
}
|
||||
@@ -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 iterable $listeners;
|
||||
private ?ExceptionListener $exceptionListener;
|
||||
private ?LogoutListener $logoutListener;
|
||||
private ?FirewallConfig $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(): ?FirewallConfig
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<mixed, callable>
|
||||
*/
|
||||
public function getListeners(): iterable
|
||||
{
|
||||
return $this->listeners;
|
||||
}
|
||||
|
||||
public function getExceptionListener(): ?ExceptionListener
|
||||
{
|
||||
return $this->exceptionListener;
|
||||
}
|
||||
|
||||
public function getLogoutListener(): ?LogoutListener
|
||||
{
|
||||
return $this->logoutListener;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\Security;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Http\FirewallMapInterface;
|
||||
|
||||
/**
|
||||
* This is a lazy-loading firewall map implementation.
|
||||
*
|
||||
* Listeners will only be initialized if we really need them.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class FirewallMap implements FirewallMapInterface
|
||||
{
|
||||
private ContainerInterface $container;
|
||||
private iterable $map;
|
||||
|
||||
public function __construct(ContainerInterface $container, iterable $map)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->map = $map;
|
||||
}
|
||||
|
||||
public function getListeners(Request $request): array
|
||||
{
|
||||
$context = $this->getFirewallContext($request);
|
||||
|
||||
if (null === $context) {
|
||||
return [[], null, null];
|
||||
}
|
||||
|
||||
return [$context->getListeners(), $context->getExceptionListener(), $context->getLogoutListener()];
|
||||
}
|
||||
|
||||
public function getFirewallConfig(Request $request): ?FirewallConfig
|
||||
{
|
||||
return $this->getFirewallContext($request)?->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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\Security;
|
||||
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
|
||||
use Symfony\Component\Security\Http\Event\LazyResponseEvent;
|
||||
use Symfony\Component\Security\Http\Firewall\ExceptionListener;
|
||||
use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface;
|
||||
use Symfony\Component\Security\Http\Firewall\LogoutListener;
|
||||
|
||||
/**
|
||||
* Lazily calls authentication listeners when actually required by the access listener.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class LazyFirewallContext extends FirewallContext
|
||||
{
|
||||
private TokenStorage $tokenStorage;
|
||||
|
||||
public function __construct(iterable $listeners, ?ExceptionListener $exceptionListener, ?LogoutListener $logoutListener, ?FirewallConfig $config, TokenStorage $tokenStorage)
|
||||
{
|
||||
parent::__construct($listeners, $exceptionListener, $logoutListener, $config);
|
||||
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
}
|
||||
|
||||
public function getListeners(): iterable
|
||||
{
|
||||
return [$this];
|
||||
}
|
||||
|
||||
public function __invoke(RequestEvent $event): void
|
||||
{
|
||||
$listeners = [];
|
||||
$request = $event->getRequest();
|
||||
$lazy = $request->isMethodCacheable();
|
||||
|
||||
foreach (parent::getListeners() as $listener) {
|
||||
if (!$lazy || !$listener instanceof FirewallListenerInterface) {
|
||||
$listeners[] = $listener;
|
||||
$lazy = $lazy && $listener instanceof FirewallListenerInterface;
|
||||
} elseif (false !== $supports = $listener->supports($request)) {
|
||||
$listeners[] = [$listener, 'authenticate'];
|
||||
$lazy = null === $supports;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$lazy) {
|
||||
foreach ($listeners as $listener) {
|
||||
$listener($event);
|
||||
|
||||
if ($event->hasResponse()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->tokenStorage->setInitializer(function () use ($event, $listeners) {
|
||||
$event = new LazyResponseEvent($event);
|
||||
foreach ($listeners as $listener) {
|
||||
$listener($event);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\Security;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
|
||||
|
||||
/**
|
||||
* A decorator that delegates all method calls to the authenticator
|
||||
* manager of the current firewall.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class UserAuthenticator implements UserAuthenticatorInterface
|
||||
{
|
||||
use FirewallAwareTrait;
|
||||
|
||||
public function __construct(FirewallMap $firewallMap, ContainerInterface $userAuthenticators, RequestStack $requestStack)
|
||||
{
|
||||
$this->firewallMap = $firewallMap;
|
||||
$this->locator = $userAuthenticators;
|
||||
$this->requestStack = $requestStack;
|
||||
}
|
||||
|
||||
public function authenticateUser(UserInterface $user, AuthenticatorInterface $authenticator, Request $request, array $badges = []): ?Response
|
||||
{
|
||||
return $this->getForFirewall()->authenticateUser($user, $authenticator, $request, $badges);
|
||||
}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSessionDomainConstraintPass;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\CleanRememberMeVerifierPass;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\MakeFirewallsEventDispatcherTraceablePass;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterCsrfFeaturesPass;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterEntryPointPass;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterGlobalSecurityEventListenersPass;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterLdapLocatorPass;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterTokenUsageTrackingPass;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\ReplaceDecoratedRememberMeHandlerPass;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\SortFirewallListenersPass;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\CasTokenHandlerFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\OidcTokenHandlerFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\OidcUserInfoTokenHandlerFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\ServiceTokenHandlerFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AccessTokenFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\CustomAuthenticatorFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginLdapFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpBasicFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpBasicLdapFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\JsonLoginFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\JsonLoginLdapFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\LoginLinkFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\LoginThrottlingFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RememberMeFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RemoteUserFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\X509Factory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\InMemoryFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\LdapFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
|
||||
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
use Symfony\Component\Security\Core\AuthenticationEvents;
|
||||
use Symfony\Component\Security\Http\SecurityEvents;
|
||||
|
||||
/**
|
||||
* Bundle.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class SecurityBundle extends Bundle
|
||||
{
|
||||
public function build(ContainerBuilder $container): void
|
||||
{
|
||||
parent::build($container);
|
||||
|
||||
/** @var SecurityExtension $extension */
|
||||
$extension = $container->getExtension('security');
|
||||
$extension->addAuthenticatorFactory(new FormLoginFactory());
|
||||
$extension->addAuthenticatorFactory(new FormLoginLdapFactory());
|
||||
$extension->addAuthenticatorFactory(new JsonLoginFactory());
|
||||
$extension->addAuthenticatorFactory(new JsonLoginLdapFactory());
|
||||
$extension->addAuthenticatorFactory(new HttpBasicFactory());
|
||||
$extension->addAuthenticatorFactory(new HttpBasicLdapFactory());
|
||||
$extension->addAuthenticatorFactory(new RememberMeFactory());
|
||||
$extension->addAuthenticatorFactory(new X509Factory());
|
||||
$extension->addAuthenticatorFactory(new RemoteUserFactory());
|
||||
$extension->addAuthenticatorFactory(new CustomAuthenticatorFactory());
|
||||
$extension->addAuthenticatorFactory(new LoginThrottlingFactory());
|
||||
$extension->addAuthenticatorFactory(new LoginLinkFactory());
|
||||
$extension->addAuthenticatorFactory(new AccessTokenFactory([
|
||||
new ServiceTokenHandlerFactory(),
|
||||
new OidcUserInfoTokenHandlerFactory(),
|
||||
new OidcTokenHandlerFactory(),
|
||||
new CasTokenHandlerFactory(),
|
||||
]));
|
||||
|
||||
$extension->addUserProviderFactory(new InMemoryFactory());
|
||||
$extension->addUserProviderFactory(new LdapFactory());
|
||||
$container->addCompilerPass(new AddExpressionLanguageProvidersPass());
|
||||
$container->addCompilerPass(new AddSecurityVotersPass());
|
||||
$container->addCompilerPass(new AddSessionDomainConstraintPass(), PassConfig::TYPE_BEFORE_REMOVING);
|
||||
$container->addCompilerPass(new CleanRememberMeVerifierPass());
|
||||
$container->addCompilerPass(new RegisterCsrfFeaturesPass());
|
||||
$container->addCompilerPass(new RegisterTokenUsageTrackingPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 200);
|
||||
$container->addCompilerPass(new RegisterLdapLocatorPass());
|
||||
$container->addCompilerPass(new RegisterEntryPointPass());
|
||||
// must be registered after RegisterListenersPass (in the FrameworkBundle)
|
||||
$container->addCompilerPass(new RegisterGlobalSecurityEventListenersPass(), PassConfig::TYPE_BEFORE_REMOVING, -200);
|
||||
// execute after ResolveChildDefinitionsPass optimization pass, to ensure class names are set
|
||||
$container->addCompilerPass(new SortFirewallListenersPass(), PassConfig::TYPE_BEFORE_REMOVING);
|
||||
$container->addCompilerPass(new ReplaceDecoratedRememberMeHandlerPass(), PassConfig::TYPE_OPTIMIZE);
|
||||
|
||||
$container->addCompilerPass(new AddEventAliasesPass(array_merge(
|
||||
AuthenticationEvents::ALIASES,
|
||||
SecurityEvents::ALIASES
|
||||
)));
|
||||
|
||||
// must be registered before DecoratorServicePass
|
||||
$container->addCompilerPass(new MakeFirewallsEventDispatcherTraceablePass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 10);
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"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": ">=8.2",
|
||||
"composer-runtime-api": ">=2.1",
|
||||
"ext-xml": "*",
|
||||
"symfony/clock": "^6.4|^7.0",
|
||||
"symfony/config": "^6.4|^7.0",
|
||||
"symfony/dependency-injection": "^6.4.11|^7.1.4",
|
||||
"symfony/event-dispatcher": "^6.4|^7.0",
|
||||
"symfony/http-kernel": "^6.4|^7.0",
|
||||
"symfony/http-foundation": "^6.4|^7.0",
|
||||
"symfony/password-hasher": "^6.4|^7.0",
|
||||
"symfony/security-core": "^6.4|^7.0",
|
||||
"symfony/security-csrf": "^6.4|^7.0",
|
||||
"symfony/security-http": "^7.1",
|
||||
"symfony/service-contracts": "^2.5|^3"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/asset": "^6.4|^7.0",
|
||||
"symfony/browser-kit": "^6.4|^7.0",
|
||||
"symfony/console": "^6.4|^7.0",
|
||||
"symfony/css-selector": "^6.4|^7.0",
|
||||
"symfony/dom-crawler": "^6.4|^7.0",
|
||||
"symfony/expression-language": "^6.4|^7.0",
|
||||
"symfony/form": "^6.4|^7.0",
|
||||
"symfony/framework-bundle": "^6.4|^7.0",
|
||||
"symfony/http-client": "^6.4|^7.0",
|
||||
"symfony/ldap": "^6.4|^7.0",
|
||||
"symfony/process": "^6.4|^7.0",
|
||||
"symfony/rate-limiter": "^6.4|^7.0",
|
||||
"symfony/serializer": "^6.4|^7.0",
|
||||
"symfony/translation": "^6.4|^7.0",
|
||||
"symfony/twig-bundle": "^6.4|^7.0",
|
||||
"symfony/twig-bridge": "^6.4|^7.0",
|
||||
"symfony/validator": "^6.4|^7.0",
|
||||
"symfony/yaml": "^6.4|^7.0",
|
||||
"twig/twig": "^3.0.4",
|
||||
"web-token/jwt-library": "^3.3.2|^4.0"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/browser-kit": "<6.4",
|
||||
"symfony/console": "<6.4",
|
||||
"symfony/framework-bundle": "<6.4",
|
||||
"symfony/http-client": "<6.4",
|
||||
"symfony/ldap": "<6.4",
|
||||
"symfony/serializer": "<6.4",
|
||||
"symfony/twig-bundle": "<6.4",
|
||||
"symfony/validator": "<6.4"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Bundle\\SecurityBundle\\": "" },
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
Reference in New Issue
Block a user