Skip to content

[SecurityBundle] Create a smooth upgrade path for security factories #41754

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions UPGRADE-5.4.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ Messenger
SecurityBundle
--------------

* Deprecate `SecurityFactoryInterface` and `SecurityExtension::addSecurityListenerFactory()` in favor of
`AuthenticatorFactoryInterface` and `SecurityExtension::addAuthenticatorFactory()`
* Add `AuthenticatorFactoryInterface::getPriority()` which replaces `SecurityFactoryInterface::getPosition()`.
Previous positions are mapped to the following priorities:

| Position | Constant | Priority |
| ----------- | ----------------------------------------------------- | -------- |
| pre_auth | `RemoteUserFactory::PRIORITY`/`X509Factory::PRIORITY` | -10 |
| form | `FormLoginFactory::PRIORITY` | -30 |
| http | `HttpBasicFactory::PRIORITY` | -50 |
| remember_me | `RememberMeFactory::PRIORITY` | -60 |
| anonymous | n/a | -70 |

* 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

Security
Expand Down
15 changes: 15 additions & 0 deletions UPGRADE-6.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,21 @@ Security
SecurityBundle
--------------

* Remove `SecurityFactoryInterface` and `SecurityExtension::addSecurityListenerFactory()` in favor of
`AuthenticatorFactoryInterface` and `SecurityExtension::addAuthenticatorFactory()`
* Add `AuthenticatorFactoryInterface::getPriority()` which replaces `SecurityFactoryInterface::getPosition()`.
Previous positions are mapped to the following priorities:

| Position | Constant | Priority |
| ----------- | ----------------------------------------------------- | -------- |
| pre_auth | `RemoteUserFactory::PRIORITY`/`X509Factory::PRIORITY` | -10 |
| form | `FormLoginFactory::PRIORITY` | -30 |
| http | `HttpBasicFactory::PRIORITY` | -50 |
| remember_me | `RememberMeFactory::PRIORITY` | -60 |
| anonymous | n/a | -70 |

* Remove passing an array of arrays as 1st argument to `MainConfiguration`, pass a sorted flat array of
factories instead.
* Remove the `always_authenticate_before_granting` option
* Remove the `UserPasswordEncoderCommand` class and the corresponding `user:encode-password` command,
use `UserPasswordHashCommand` and `user:hash-password` instead
Expand Down
5 changes: 5 additions & 0 deletions src/Symfony/Bundle/SecurityBundle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ CHANGELOG
5.4
---

* 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

5.3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
namespace Symfony\Bundle\SecurityBundle\DependencyInjection;

use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AbstractFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
Expand All @@ -31,8 +33,17 @@ class MainConfiguration implements ConfigurationInterface
private $factories;
private $userProviderFactories;

/**
* @param (SecurityFactoryInterface|AuthenticatorFactoryInterface)[] $factories
*/
public function __construct(array $factories, array $userProviderFactories)
{
if (\is_array(current($factories))) {
trigger_deprecation('symfony/security-bundle', '5.4', 'Passing an array of arrays as 1st argument to "%s" is deprecated, pass a sorted array of factories instead.', __METHOD__);

$factories = array_merge(...array_values($factories));
}

$this->factories = $factories;
$this->userProviderFactories = $userProviderFactories;
}
Expand Down Expand Up @@ -297,19 +308,17 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto
;

$abstractFactoryKeys = [];
foreach ($factories as $factoriesAtPosition) {
foreach ($factoriesAtPosition as $factory) {
$name = str_replace('-', '_', $factory->getKey());
$factoryNode = $firewallNodeBuilder->arrayNode($name)
->canBeUnset()
;

if ($factory instanceof AbstractFactory) {
$abstractFactoryKeys[] = $name;
}

$factory->addConfiguration($factoryNode);
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal
throw new InvalidConfigurationException(sprintf('The authenticator manager no longer has "anonymous" security. Please remove this option under the "%s" firewall'.($config['lazy'] ? ' and add "lazy: true"' : '').'.', $firewallName));
}

public function getPriority()
{
return -60;
}

public function getPosition()
{
return 'anonymous';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@

namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;

use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;

/**
* @method int getPriority() defines the position at which the authenticator is called
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
interface AuthenticatorFactoryInterface
Expand All @@ -24,4 +27,14 @@ interface AuthenticatorFactoryInterface
* @return string|string[] The authenticator service ID(s) to be used by the firewall
*/
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId);

/**
* Defines the configuration key used to reference the authenticator
* in the firewall configuration.
*
* @return string
*/
public function getKey();

public function addConfiguration(NodeDefinition $builder);
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public function create(ContainerBuilder $container, string $id, array $config, s
throw new \LogicException('Custom authenticators are not supported when "security.enable_authenticator_manager" is not set to true.');
}

public function getPriority(): int
{
return 0;
}

public function getPosition(): string
{
return 'pre_auth';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
*/
class FormLoginFactory extends AbstractFactory implements AuthenticatorFactoryInterface
{
public const PRIORITY = -30;

public function __construct()
{
$this->addOption('username_parameter', '_username');
Expand All @@ -37,6 +39,11 @@ public function __construct()
$this->addOption('post_only', true);
}

public function getPriority(): int
{
return self::PRIORITY;
}

public function getPosition()
{
return 'form';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ public function getPosition()
return 'pre_auth';
}

public function getPriority(): int
{
return 0;
}

public function getKey()
{
return 'guard';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
*/
class HttpBasicFactory implements SecurityFactoryInterface, AuthenticatorFactoryInterface
{
public const PRIORITY = -50;

public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint)
{
$provider = 'security.authentication.provider.dao.'.$id;
Expand Down Expand Up @@ -66,6 +68,11 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal
return $authenticatorId;
}

public function getPriority(): int
{
return self::PRIORITY;
}

public function getPosition()
{
return 'http';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
*/
class JsonLoginFactory extends AbstractFactory implements AuthenticatorFactoryInterface
{
public const PRIORITY = -40;

public function __construct()
{
$this->addOption('username_path', 'username');
Expand All @@ -32,6 +34,11 @@ public function __construct()
$this->defaultSuccessHandlerOptions = [];
}

public function getPriority(): int
{
return self::PRIORITY;
}

/**
* {@inheritdoc}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
*/
class LoginLinkFactory extends AbstractFactory implements AuthenticatorFactoryInterface
{
public const PRIORITY = -20;

public function addConfiguration(NodeDefinition $node)
{
/** @var NodeBuilder $builder */
Expand Down Expand Up @@ -147,6 +149,11 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal
return $authenticatorId;
}

public function getPriority(): int
{
return self::PRIORITY;
}

public function getPosition()
{
return 'form';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ public function create(ContainerBuilder $container, string $id, array $config, s
throw new \LogicException('Login throttling is not supported when "security.enable_authenticator_manager" is not set to true.');
}

public function getPriority(): int
{
// this factory doesn't register any authenticators, this priority doesn't matter
return 0;
}

public function getPosition(): string
{
// this factory doesn't register any authenticators, this position doesn't matter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpFoundation\Cookie;
Expand All @@ -30,8 +31,10 @@
/**
* @internal
*/
class RememberMeFactory implements SecurityFactoryInterface, AuthenticatorFactoryInterface
class RememberMeFactory implements SecurityFactoryInterface, AuthenticatorFactoryInterface, PrependExtensionInterface
{
public const PRIORITY = -50;

protected $options = [
'name' => 'REMEMBERME',
'lifetime' => 31536000,
Expand Down Expand Up @@ -181,6 +184,14 @@ public function getPosition()
return 'remember_me';
}

/**
* {@inheritDoc}
*/
public function getPriority(): int
{
return self::PRIORITY;
}

public function getKey()
{
return 'remember-me';
Expand Down Expand Up @@ -331,4 +342,27 @@ private function createTokenVerifier(ContainerBuilder $container, string $firewa

return new Reference($tokenVerifierId, ContainerInterface::NULL_ON_INVALID_REFERENCE);
}

/**
* {@inheritdoc}
*/
public function prepend(ContainerBuilder $container)
{
$rememberMeSecureDefault = false;
$rememberMeSameSiteDefault = null;

if (!isset($container->getExtensions()['framework'])) {
return;
}

foreach ($container->getExtensionConfig('framework') as $config) {
if (isset($config['session']) && \is_array($config['session'])) {
$rememberMeSecureDefault = $config['session']['cookie_secure'] ?? $rememberMeSecureDefault;
$rememberMeSameSiteDefault = \array_key_exists('cookie_samesite', $config['session']) ? $config['session']['cookie_samesite'] : $rememberMeSameSiteDefault;
}
}

$this->options['secure'] = $rememberMeSecureDefault;
$this->options['samesite'] = $rememberMeSameSiteDefault;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
*/
class RemoteUserFactory implements SecurityFactoryInterface, AuthenticatorFactoryInterface
{
public const PRIORITY = -10;

public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint)
{
$providerId = 'security.authentication.provider.pre_authenticated.'.$id;
Expand Down Expand Up @@ -58,6 +60,11 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal
return $authenticatorId;
}

public function getPriority(): int
{
return self::PRIORITY;
}

public function getPosition()
{
return 'pre_auth';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
* SecurityFactoryInterface is the interface for all security authentication listener.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since Symfony 5.3, use AuthenticatorFactoryInterface instead.
*/
interface SecurityFactoryInterface
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
*/
class X509Factory implements SecurityFactoryInterface, AuthenticatorFactoryInterface
{
public const PRIORITY = -10;

public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint)
{
$providerId = 'security.authentication.provider.pre_authenticated.'.$id;
Expand Down Expand Up @@ -60,6 +62,11 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal
return $authenticatorId;
}

public function getPriority(): int
{
return self::PRIORITY;
}

public function getPosition()
{
return 'pre_auth';
Expand Down
Loading