8000 [SecurityBundle] Fixed entry point service ID resolving and multiple guard entry points by wouterj · Pull Request #36661 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[SecurityBundle] Fixed entry point service ID resolving and multiple guard entry points #36661

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 2 commits into from
May 3, 2020
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 10000
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion UPGRADE-5.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ Routing
SecurityBundle
--------------

* Marked the `AbstractFactory`, `AnonymousFactory`, `FormLoginFactory`, `FormLoginLdapFactory`, `GuardAuthenticationFactory`,
* Marked the `AnonymousFactory`, `FormLoginFactory`, `FormLoginLdapFactory`, `GuardAuthenticationFactory`,
`HttpBasicFactory`, `HttpBasicLdapFactory`, `JsonLoginFactory`, `JsonLoginLdapFactory`, `RememberMeFactory`, `RemoteUserFactory`
and `X509Factory` as `@internal`. Instead of extending these classes, create your own implementation based on
`SecurityFactoryInterface`.
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Bundle/SecurityBundle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ CHANGELOG

* Added XSD for configuration
* Added security configuration for priority-based access decision strategy
* Marked the `AbstractFactory`, `AnonymousFactory`, `FormLoginFactory`, `FormLoginLdapFactory`, `GuardAuthenticationFactory`, `HttpBasicFactory`, `HttpBasicLdapFactory`, `JsonLoginFactory`, `JsonLoginLdapFactory`, `RememberMeFactory`, `RemoteUserFactory` and `X509Factory` as `@internal`
* 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
* @author Fabien Potencier <fabien@symfony.com>
* @author Lukas Kahwe Smith <smith@pooteeweet.org>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*
* @internal
*/
abstract class AbstractFactory implements SecurityFactoryInterface
{
Expand Down Expand Up @@ -67,7 +65,7 @@ public function create(ContainerBuilder $container, string $id, array $config, s
}

// create entry point if applicable (optional)
$entryPointId = $this->createDefaultEntryPoint($container, $id, $config, $defaultEntryPointId);
$entryPointId = $this->createEntryPoint($container, $id, $config, $defaultEntryPointId);

return [$authProviderId, $listenerId, $entryPointId];
}
Expand Down Expand Up @@ -128,7 +126,7 @@ abstract protected function getListenerId();
*
* @return string|null the entry point id
*/
protected function createDefaultEntryPoint(ContainerBuilder $container, string $id, array $config, ?string $defaultEntryPointId)
protected function createEntryPoint(ContainerBuilder $container, string $id, array $config, ?string $defaultEntryPointId)
{
return $defaultEntryPointId;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
interface EntryPointFactoryInterface
{
/**
* Creates the entry point and returns the service ID.
* Register the entry point on the container and returns the service ID.
*
* This does not mean that the entry point is also used. This is managed
* by the "entry_point" firewall setting.
*/
public function createEntryPoint(ContainerBuilder $container, string $id, array $config): ?string;
public function registerEntryPoint(ContainerBuilder $container, string $id, array $config): ?string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,12 @@ protected function createListener(ContainerBuilder $container, string $id, array
return $listenerId;
}

protected function createDefaultEntryPoint(ContainerBuilder $container, string $id, array $config, ?string $defaultEntryPointId)
protected function createEntryPoint(ContainerBuilder $container, string $id, array $config, ?string $defaultEntryPointId)
{
return $this->createEntryPoint($container, $id, $config);
return $this->registerEntryPoint($container, $id, $config);
}

public function createEntryPoint(ContainerBuilder $container, string $id, array $config): string
public function registerEntryPoint(ContainerBuilder $container, string $id, array $config): string
{
$entryPointId = 'security.authentication.form_entry_point.'.$id;
$container
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;

use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
Expand Down Expand Up @@ -113,15 +114,13 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal
return $authenticatorIds;
}

public function createEntryPoint(ContainerBuilder $container, string $id, array $config): ?string
public function registerEntryPoint(ContainerBuilder $container, string $id, array $config): ?string
{
try {
return $this->determineEntryPoint(null, $config);
} catch (\LogicException $e) {
// ignore the exception, the new system prefers setting "entry_point" over "guard.entry_point"
throw new InvalidConfigurationException(sprintf('Because you have multiple guard authenticators, you need to set the "entry_point" key to one of your authenticators (%s).', implode(', ', $config['authenticators'])));
}

return null;
}

private function determineEntryPoint(?string $defaultEntryPointId, array $config): string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function create(ContainerBuilder $container, string $id, array $config, s
// entry point
$entryPointId = $defaultEntryPoint;
if (null === $entryPointId) {
$entryPointId = $this->createEntryPoint($container, $id, $config);
$entryPointId = $this->registerEntryPoint($container, $id, $config);
}

// listener
Expand Down Expand Up @@ -82,7 +82,7 @@ public function addConfiguration(NodeDefinition $node)
;
}

public function createEntryPoint(ContainerBuilder $container, string $id, array $config): string
public function registerEntryPoint(ContainerBuilder $container, string $id, array $config): string
{
$entryPointId = 'security.authentication.basic_entry_point.'.$id;
$container
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public function create(ContainerBuilder $container, string $id, array $config, s
;

// entry point
$entryPointId = $this->createEntryPoint($container, $id, $config, $defaultEntryPoint);
$entryPointId = $this->registerEntryPoint($container, $id, $config, $defaultEntryPoint);

if (!empty($config['query_string'])) {
if ('' === $config['search_dn'] || '' === $config['search_password']) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\Ldap\Entry;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder;
use Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder;
Expand Down Expand Up @@ -443,6 +444,9 @@ private function createFirewall(ContainerBuilder $container, string $id, array $
if (!$this->authenticatorManagerEnabled) {
$authenticationProviders = array_merge($authenticationProviders, $firewallAuthenticationProviders);
} else {
// $configuredEntryPoint is resolved into a service ID and stored in $defaultEntryPoint
$configuredEntryPoint = $defaultEntryPoint;

// authenticator manager
$authenticators = array_map(function ($id) {
return new Reference($id);
Expand Down Expand Up @@ -543,7 +547,7 @@ private function createAuthenticationListeners(ContainerBuilder $container, stri
$authenticationProviders[] = $authenticators;
}

if ($factory instanceof EntryPointFactoryInterface && ($entryPoint = $factory->createEntryPoint($container, $id, $firewall[$key], null))) {
if ($factory instanceof EntryPointFactoryInterface && ($entryPoint = $factory->registerEntryPoint($container, $id, $firewall[$key]))) {
$entryPoints[$key] = $entryPoint;
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ public function testAuthenticatorSystemCreate()
$authenticators = $factory->createAuthenticator($container, $firewallName, $config, $userProviderId);
$this->assertEquals('security.authenticator.guard.my_firewall.0', $authenticators[0]);

$entryPointId = $factory->createEntryPoint($container, $firewallName, $config, null);
$entryPointId = $factory->registerEntryPoint($container, $firewallName, $config, null);
$this->assertEquals('authenticator123', $entryPointId);

$authenticatorDefinition = $container->getDefinition('security.authenticator.guard.my_firewall.0');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,19 @@
use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Bundle\SecurityBundle\Tests\DependencyInjection\Fixtures\UserProvider\DummyProvider;
use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\FirewallEntryPointBundle\Security\EntryPointStub;
use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\GuardedBundle\AppCustomAuthenticator;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\AuthenticatorInterface;

class SecurityExtensionTest extends TestCase
{
Expand Down Expand Up @@ -413,6 +421,90 @@ public function testSwitchUserWithSeveralDefinedProvidersButNoFirewallRootProvid
$this->assertEquals(new Reference('security.user.provider.concrete.second'), $container->getDefinition('security.authentication.switchuser_listener.foobar')->getArgument(1));
}

/**
* @dataProvider provideEntryPointFirewalls
*/
public function testAuthenticatorManagerEnabledEntryPoint(array $firewall, $entryPointId)
{
$container = $this->getRawContainer();
$container->loadFromExtension('security', [
'enable_authenticator_manager' => true,
'providers' => [
'first' => ['id' => 'users'],
],

'firewalls' => [
'main' => $firewall,
],
]);

$container->compile();

$this->assertEquals($entryPointId, (string) $container->getDefinition('security.firewall.map.config.main')->getArgument(7));
$this->assertEquals($entryPointId, (string) $container->getDefinition('security.exception_listener.main')->getArgument(4));
}

public function provideEntryPointFirewalls()
{
// only one entry point available
yield [['http_basic' => true], 'security.authentication.basic_entry_point.main'];
// explicitly configured by authenticator key
yield [['form_login' => true, 'http_basic' => true, 'entry_point' => 'form_login'], 'security.authentication.form_entry_point.main'];
// explicitly configured another service
yield [['form_login' => true, 'entry_point' => EntryPointStub::class], EntryPointStub::class];
// no entry point required
yield [['json_login' => true], null];

// only one guard authenticator entry point available
yield [[
'guard' => ['authenticators' => [AppCustomAuthenticator::class]],
], AppCustomAuthenticator::class];
// explicitly configured guard authenticator entry point
yield [[
'guard' => [
'authenticators' => [AppCustomAuthenticator::class, NullAuthenticator::class],
'entry_point' => NullAuthenticator::class,
],
], NullAuthenticator::class];
}

/**
* @dataProvider provideEntryPointRequiredData
*/
public function testEntryPointRequired(array $firewall, $messageRegex)
{
$this->expectException(InvalidConfigurationException::class);
$this->expectExceptionMessageMatches($messageRegex);

$container = $this->getRawContainer();
$container->loadFromExtension('security', [
'enable_authenticator_manager' => true,
'providers' => [
'first' => ['id' => 'users'],
],

'firewalls' => [
'main' => $firewall,
],
]);

$container->compile();
}

public function provideEntryPointRequiredData()
{
// more than one entry point available and not explicitly set
yield [
['http_basic' => true, 'form_login' => true],
'/^Because you have multiple authenticators in firewall "main", you need to set the "entry_point" key to one of your authenticators/',
];
// more than one guard entry point available and not explicitly set
yield [
['guard' => ['authenticators' => [AppCustomAuthenticator::class, NullAuthenticator::class]]],
'/^Because you have multiple guard authenticators, you need to set the "entry_point" key to one of your authenticators/',
];
}

protected function getRawContainer()
{
$container = new ContainerBuilder();
Expand All @@ -439,3 +531,42 @@ protected function getContainer()
return $container;
}
}

class NullAuthenticator implements AuthenticatorInterface
{
public function start(Request $request, AuthenticationException $authException = null)
{
}

public function supports(Request $request)
{
}

public function getCredentials(Request $request)
{
}

public function getUser($credentials, UserProviderInterface $userProvider)
{
}

public function checkCredentials($credentials, UserInterface $user)
{
}

public function createAuthenticatedToken(UserInterface $user, string $providerKey)
{
}

public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
}

public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey)
{
}

public function supportsRememberMe()
{
}
}
2 changes: 1 addition & 1 deletion src/Symfony/Bundle/SecurityBundle/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"php": "^7.2.5",
"ext-xml": "*",
"symfony/config": "^4.4|^5.0",
"symfony/dependency-injection": "^4.4|^5.0",
"symfony/dependency-injection": "^5.1",
"symfony/event-dispatcher": "^5.1",
"symfony/http-kernel": "^5.0",
"symfony/polyfill-php80": "^1.15",
Expand Down
0