From d7724d2ad1c09e175ff35c758028da30aa5bc9ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Fr=C3=A9zet?= Date: Tue, 18 May 2021 17:07:37 +0200 Subject: [PATCH 1/2] [Security] Add a method in the security helper to ease programmatic login (#40662) --- .../Bundle/SecurityBundle/CHANGELOG.md | 1 + .../DependencyInjection/SecurityExtension.php | 21 ++++- .../Resources/config/security.php | 16 ++-- .../SecurityBundle/Security/Security.php | 60 +++++++++++++- .../Component/Security/Core/CHANGELOG.md | 6 ++ .../Security/Core/Tests/SecurityTest.php | 82 ++++++++++++++++++- 6 files changed, 173 insertions(+), 13 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index 7984b1783d984..55b08604f4e41 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Add the `Security` helper class * Deprecate the `Symfony\Component\Security\Core\Security` service alias, use `Symfony\Bundle\SecurityBundle\Security\Security` instead * Add `Security::getFirewallConfig()` to help to get the firewall configuration associated to the Request + * Add `Security::login()` to login programmatically 6.1 --- diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 803fc5ed5d796..e9432f8662933 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -277,7 +277,7 @@ private function createFirewalls(array $config, ContainerBuilder $container) // load firewall map $mapDef = $container->getDefinition('security.firewall.map'); - $map = $authenticationProviders = $contextRefs = []; + $map = $authenticationProviders = $contextRefs = $authenticators = []; foreach ($firewalls as $name => $firewall) { if (isset($firewall['user_checker']) && 'security.user_checker' !== $firewall['user_checker']) { $customUserChecker = true; @@ -285,8 +285,17 @@ private function createFirewalls(array $config, ContainerBuilder $container) $configId = 'security.firewall.map.config.'.$name; - [$matcher, $listeners, $exceptionListener, $logoutListener] = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds, $configId); + [$matcher, $listeners, $exceptionListener, $logoutListener, $firewallAuthenticators] = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds, $configId); + if (!$firewallAuthenticators) { + $authenticators[$name] = null; + } else { + $firewallAuthenticatorRefs = []; + foreach ($firewallAuthenticators as $authenticatorId) { + $firewallAuthenticatorRefs[$authenticatorId] = new Reference($authenticatorId); + } + $authenticators[$name] = ServiceLocatorTagPass::register($container, $firewallAuthenticatorRefs); + } $contextId = 'security.firewall.map.context.'.$name; $isLazy = !$firewall['stateless'] && (!empty($firewall['anonymous']['lazy']) || $firewall['lazy']); $context = new ChildDefinition($isLazy ? 'security.firewall.lazy_context' : 'security.firewall.context'); @@ -301,6 +310,10 @@ private function createFirewalls(array $config, ContainerBuilder $container) $contextRefs[$contextId] = new Reference($contextId); $map[$contextId] = $matcher; } + $container + ->getDefinition('security.helper') + ->replaceArgument(1, $authenticators) + ; $container->setAlias('security.firewall.context_locator', (string) ServiceLocatorTagPass::register($container, $contextRefs)); @@ -335,7 +348,7 @@ private function createFirewall(ContainerBuilder $container, string $id, array $ // Security disabled? if (false === $firewall['security']) { - return [$matcher, [], null, null]; + return [$matcher, [], null, null, []]; } $config->replaceArgument(4, $firewall['stateless']); @@ -528,7 +541,7 @@ private function createFirewall(ContainerBuilder $container, string $id, array $ $config->replaceArgument(10, $listenerKeys); $config->replaceArgument(11, $firewall['switch_user'] ?? null); - return [$matcher, $listeners, $exceptionListener, null !== $logoutListenerId ? new Reference($logoutListenerId) : null]; + return [$matcher, $listeners, $exceptionListener, null !== $logoutListenerId ? new Reference($logoutListenerId) : null, $firewallAuthenticationProviders]; } private function createContextListener(ContainerBuilder $container, string $contextKey, ?string $firewallEventDispatcherId) diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php index 25579742a0248..d4b8835194455 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php @@ -77,11 +77,17 @@ ->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.firewall.map' => service('security.firewall.map'), - ])]) + ->args([ + service_locator([ + 'security.token_storage' => service('security.token_storage'), + 'security.authorization_checker' => service('security.authorization_checker'), + 'security.user_authenticator' => service('security.user_authenticator')->ignoreOnInvalid(), + 'request_stack' => service('request_stack'), + 'security.firewall.map' => service('security.firewall.map'), + 'security.user_checker' => service('security.user_checker'), + ]), + abstract_arg('authenticators'), + ]) ->alias(Security::class, 'security.helper') ->alias(LegacySecurity::class, 'security.helper') ->deprecate('symfony/security-bundle', '6.2', 'The "%alias_id%" service alias is deprecated, use "'.Security::class.'" instead.') diff --git a/src/Symfony/Bundle/SecurityBundle/Security/Security.php b/src/Symfony/Bundle/SecurityBundle/Security/Security.php index 1f07c8ccd0bcd..651b0c9576b5d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/Security.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/Security.php @@ -13,7 +13,11 @@ use Psr\Container\ContainerInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Exception\LogicException; use Symfony\Component\Security\Core\Security as LegacySecurity; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; +use Symfony\Contracts\Service\ServiceProviderInterface; /** * Helper class for commonly-needed security tasks. @@ -22,7 +26,7 @@ */ class Security extends LegacySecurity { - public function __construct(private ContainerInterface $container) + public function __construct(private ContainerInterface $container, private array $authenticators = []) { parent::__construct($container, false); } @@ -31,4 +35,58 @@ public function getFirewallConfig(Request $request): ?FirewallConfig { return $this->container->get('security.firewall.map')->getFirewallConfig($request); } + + public function login(UserInterface $user, string $authenticatorName = null, string $firewallName = null): void + { + $request = $this->container->get('request_stack')->getCurrentRequest(); + + if (!class_exists(AuthenticatorInterface::class)) { + throw new \LogicException('Security HTTP is missing. Try running "composer require symfony/security-http".'); + } + $authenticator = $this->getAuthenticator($authenticatorName, $firewallName ?? $this->getFirewallName($request)); + + $this->container->get('security.user_checker')->checkPreAuth($user); + $this->container->get('security.user_authenticator')->authenticateUser($user, $authenticator, $request); + } + + private function getAuthenticator(?string $authenticatorName, string $firewallName): AuthenticatorInterface + { + if (!\array_key_exists($firewallName, $this->authenticators)) { + 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('No authenticator was found for the firewall "%s".'); + } + + if (1 < \count($authenticatorIds)) { + throw new LogicException(sprintf('Too much 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]); + } + $authenticatorId = 'security.authenticator.'.$authenticatorName.'.'.$firewallName; + + if (!$firewallAuthenticatorLocator->has($authenticatorId)) { + throw new LogicException(sprintf('Unable to find an authenticator named "%s" for the firewall "%s". Try to pass a firewall name in the Security::login() method.', $authenticatorName, $firewallName)); + } + + return $firewallAuthenticatorLocator->get($authenticatorId); + } + + private function getFirewallName(Request $request): string + { + $firewall = $this->container->get('security.firewall.map')->getFirewallConfig($request); + + if (null === $firewall) { + throw new LogicException('No firewall found as the current route is not covered by any firewall.'); + } + + return $firewall->getName(); + } } diff --git a/src/Symfony/Component/Security/Core/CHANGELOG.md b/src/Symfony/Component/Security/Core/CHANGELOG.md index 92f4cccaecb07..14e239069bd35 100644 --- a/src/Symfony/Component/Security/Core/CHANGELOG.md +++ b/src/Symfony/Component/Security/Core/CHANGELOG.md @@ -6,6 +6,12 @@ CHANGELOG * Deprecate the `Security` class, use `Symfony\Bundle\SecurityBundle\Security\Security` instead +6.1 +--- + +* Add `Security::login()` to login programmatically + + 6.0 --- diff --git a/src/Symfony/Component/Security/Core/Tests/SecurityTest.php b/src/Symfony/Component/Security/Core/Tests/SecurityTest.php index 63eca289cc287..980e226d2ca96 100644 --- a/src/Symfony/Component/Security/Core/Tests/SecurityTest.php +++ b/src/Symfony/Component/Security/Core/Tests/SecurityTest.php @@ -13,12 +13,21 @@ use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; +use Symfony\Bundle\SecurityBundle\Security\FirewallConfig; +use Symfony\Bundle\SecurityBundle\Security\FirewallMap; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\User\InMemoryUser; +use Symfony\Component\Security\Core\User\UserCheckerInterface; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface; +use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; +use Symfony\Contracts\Service\ServiceProviderInterface; /** * @group legacy @@ -36,7 +45,7 @@ public function testGetToken() $container = $this->createContainer('security.token_storage', $tokenStorage); - $security = new Security($container); + $security = new Security($container, []); $this->assertSame($token, $security->getToken()); } @@ -57,7 +66,7 @@ public function testGetUser($userInToken, $expectedUser) $container = $this->createContainer('security.token_storage', $tokenStorage); - $security = new Security($container); + $security = new Security($container, []); $this->assertSame($expectedUser, $security->getUser()); } @@ -80,10 +89,77 @@ public function testIsGranted() $container = $this->createContainer('security.authorization_checker', $authorizationChecker); - $security = new Security($container); + $security = new Security($container, []); $this->assertTrue($security->isGranted('SOME_ATTRIBUTE', 'SOME_SUBJECT')); } + public function testAutoLogin() + { + $request = new Request(); + $authenticator = $this->createMock(AuthenticatorInterface::class); + $requestStack = $this->createMock(RequestStack::class); + $firewallMap = $this->createMock(FirewallMap::class); + $firewall = new FirewallConfig('main', 'main'); + $userAuthenticator = $this->createMock(UserAuthenticatorInterface::class); + $user = $this->createMock(UserInterface::class); + $userChecker = $this->createMock(UserCheckerInterface::class); + + $container = $this->createMock(ContainerInterface::class); + $container + ->expects($this->atLeastOnce()) + ->method('get') + ->willReturnMap([ + ['request_stack', $requestStack], + ['security.firewall.map', $firewallMap], + ['security.user_authenticator', $userAuthenticator], + ['security.user_checker', $userChecker], + ]) + ; + + $requestStack + ->expects($this->once()) + ->method('getCurrentRequest') + ->willReturn($request) + ; + + $firewallMap + ->expects($this->once()) + ->method('getFirewallConfig') + ->willReturn($firewall) + ; + $userAuthenticator + ->expects($this->once()) + ->method('authenticateUser') + ->with($user, $authenticator, $request) + ; + $userChecker + ->expects($this->once()) + ->method('checkPreAuth') + ->with($user) + ; + + $firewallAuthenticatorLocator = $this->createMock(ServiceProviderInterface::class); + $firewallAuthenticatorLocator + ->expects($this->once()) + ->method('getProvidedServices') + ->willReturn([ + 'security.authenticator.custom.dev' => $authenticator, + ]) + ; + $firewallAuthenticatorLocator + ->expects($this->once()) + ->method('get') + ->with('security.authenticator.custom.dev') + ->willReturn($authenticator) + ; + + $security = new Security($container, [ + 'main' => $firewallAuthenticatorLocator, + ]); + + $security->login($user); + } + private function createContainer($serviceId, $serviceObject) { $container = $this->createMock(ContainerInterface::class); From 37efa7291a5dd127cde06cf14407bb5e48308155 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Mon, 4 Jul 2022 17:22:43 +0200 Subject: [PATCH 2/2] Add functional test & fix reviews --- .../SecurityBundle/Security/Security.php | 41 ++++++---- .../Tests/Functional/SecurityTest.php | 35 ++++++++ .../Functional/app/SecurityHelper/config.yml | 14 ++++ .../Functional/app/SecurityHelper/routing.yml | 3 + .../Tests/Security/SecurityTest.php | 52 ++++++++++++ .../Component/Security/Core/CHANGELOG.md | 6 -- .../Security/Core/Tests/SecurityTest.php | 82 +------------------ 7 files changed, 130 insertions(+), 103 deletions(-) create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/routing.yml diff --git a/src/Symfony/Bundle/SecurityBundle/Security/Security.php b/src/Symfony/Bundle/SecurityBundle/Security/Security.php index 651b0c9576b5d..f92e7c2c2b319 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/Security.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/Security.php @@ -22,11 +22,15 @@ /** * Helper class for commonly-needed security tasks. * + * @author Ryan Weaver + * @author Robin Chalas + * @author Arnaud Frézet + * * @final */ class Security extends LegacySecurity { - public function __construct(private ContainerInterface $container, private array $authenticators = []) + public function __construct(private readonly ContainerInterface $container, private readonly array $authenticators = []) { parent::__construct($container, false); } @@ -36,14 +40,21 @@ 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 + */ public function login(UserInterface $user, string $authenticatorName = null, string $firewallName = null): void { $request = $this->container->get('request_stack')->getCurrentRequest(); + $firewallName ??= $this->getFirewallConfig($request)?->getName(); - if (!class_exists(AuthenticatorInterface::class)) { - throw new \LogicException('Security HTTP is missing. Try running "composer require symfony/security-http".'); + if (!$firewallName) { + throw new LogicException('Unable to login as the current route is not covered by any firewall.'); } - $authenticator = $this->getAuthenticator($authenticatorName, $firewallName ?? $this->getFirewallName($request)); + + $authenticator = $this->getAuthenticator($authenticatorName, $firewallName); $this->container->get('security.user_checker')->checkPreAuth($user); $this->container->get('security.user_authenticator')->authenticateUser($user, $authenticator, $request); @@ -54,6 +65,7 @@ private function getAuthenticator(?string $authenticatorName, string $firewallNa if (!\array_key_exists($firewallName, $this->authenticators)) { throw new LogicException(sprintf('No authenticators found for firewall "%s".', $firewallName)); } + /** @var ServiceProviderInterface $firewallAuthenticatorLocator */ $firewallAuthenticatorLocator = $this->authenticators[$firewallName]; @@ -61,32 +73,25 @@ private function getAuthenticator(?string $authenticatorName, string $firewallNa $authenticatorIds = array_keys($firewallAuthenticatorLocator->getProvidedServices()); if (!$authenticatorIds) { - throw new LogicException('No authenticator was found for the firewall "%s".'); + throw new LogicException(sprintf('No authenticator was found for the firewall "%s".', $firewallName)); } - if (1 < \count($authenticatorIds)) { throw new LogicException(sprintf('Too much 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]); } - $authenticatorId = 'security.authenticator.'.$authenticatorName.'.'.$firewallName; - if (!$firewallAuthenticatorLocator->has($authenticatorId)) { - throw new LogicException(sprintf('Unable to find an authenticator named "%s" for the firewall "%s". Try to pass a firewall name in the Security::login() method.', $authenticatorName, $firewallName)); + if ($firewallAuthenticatorLocator->has($authenticatorName)) { + return $firewallAuthenticatorLocator->get($authenticatorName); } - return $firewallAuthenticatorLocator->get($authenticatorId); - } - - private function getFirewallName(Request $request): string - { - $firewall = $this->container->get('security.firewall.map')->getFirewallConfig($request); + $authenticatorId = 'security.authenticator.'.$authenticatorName.'.'.$firewallName; - if (null === $firewall) { - throw new LogicException('No firewall found as the current route is not covered by any firewall.'); + if (!$firewallAuthenticatorLocator->has($authenticatorId)) { + throw new LogicException(sprintf('Unable to find an authenticator named "%s" for the firewall "%s". Available authenticators: "%s".', $authenticatorName, implode('", "', $firewallAuthenticatorLocator->getProvidedServices()))); } - return $firewall->getName(); + return $firewallAuthenticatorLocator->get($authenticatorId); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php index ff77dadf5537d..de357a998df23 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php @@ -12,7 +12,9 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional; use Symfony\Bundle\SecurityBundle\Security\FirewallConfig; +use Symfony\Bundle\SecurityBundle\Security\Security; use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\SecuredPageBundle\Security\Core\User\ArrayUserProvider; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\User\InMemoryUser; @@ -81,6 +83,22 @@ public function userWillBeMarkedAsChangedIfRolesHasChangedProvider() ], ]; } + + /** + * @testWith ["json_login"] + * ["Symfony\\Bundle\\SecurityBundle\\Tests\\Functional\\Bundle\\AuthenticatorBundle\\ApiAuthenticator"] + */ + public function testLoginWithBuiltInAuthenticator(string $authenticator) + { + $client = $this->createClient(['test_case' => 'SecurityHelper', 'root_config' => 'config.yml', 'debug' => true]); + static::getContainer()->get(WelcomeController::class)->authenticator = $authenticator; + $client->request('GET', '/welcome'); + $response = $client->getResponse(); + + $this->assertInstanceOf(JsonResponse::class, $response); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame(['message' => 'Welcome @chalasr!'], json_decode($response->getContent(), true)); + } } final class UserWithoutEquatable implements UserInterface, PasswordAuthenticatedUserInterface @@ -189,3 +207,20 @@ public function eraseCredentials(): void { } } + +class WelcomeController +{ + public $authenticator = 'json_login'; + + public function __construct(private Security $security) + { + } + + public function welcome() + { + $user = new InMemoryUser('chalasr', '', ['ROLE_USER']); + $this->security->login($user, $this->authenticator); + + return new JsonResponse(['message' => sprintf('Welcome @%s!', $this->security->getUser()->getUserIdentifier())]); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config.yml index f2ac6ebde364f..f46a1fa682159 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config.yml @@ -11,6 +11,12 @@ services: alias: security.token_storage public: true + Symfony\Bundle\SecurityBundle\Tests\Functional\WelcomeController: + arguments: ['@security.helper'] + public: true + + Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ApiAuthenticator: ~ + security: enable_authenticator_manager: true providers: @@ -20,3 +26,11 @@ security: firewalls: default: + json_login: + username_path: user.login + password_path: user.password + custom_authenticators: + - 'Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ApiAuthenticator' + + access_control: + - { path: ^/foo, roles: PUBLIC_ACCESS } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/routing.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/routing.yml new file mode 100644 index 0000000000000..cbe982ae6f310 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/routing.yml @@ -0,0 +1,3 @@ +welcome: + path: /welcome + defaults: { _controller: Symfony\Bundle\SecurityBundle\Tests\Functional\WelcomeController::welcome } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Security/SecurityTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Security/SecurityTest.php index 1cae0827f7212..1929d688701c2 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Security/SecurityTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Security/SecurityTest.php @@ -18,11 +18,17 @@ use Symfony\Bundle\SecurityBundle\Security\Security; use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\User\InMemoryUser; +use Symfony\Component\Security\Core\User\UserCheckerInterface; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface; +use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; +use Symfony\Contracts\Service\ServiceProviderInterface; class SecurityTest extends TestCase { @@ -111,6 +117,52 @@ public function getFirewallConfigTests() yield [$request, new FirewallConfig('main', 'acme_user_checker')]; } + public function testAutoLogin() + { + $request = new Request(); + $authenticator = $this->createMock(AuthenticatorInterface::class); + $requestStack = $this->createMock(RequestStack::class); + $firewallMap = $this->createMock(FirewallMap::class); + $firewall = new FirewallConfig('main', 'main'); + $userAuthenticator = $this->createMock(UserAuthenticatorInterface::class); + $user = $this->createMock(UserInterface::class); + $userChecker = $this->createMock(UserCheckerInterface::class); + + $container = $this->createMock(ContainerInterface::class); + $container + ->expects($this->atLeastOnce()) + ->method('get') + ->willReturnMap([ + ['request_stack', $requestStack], + ['security.firewall.map', $firewallMap], + ['security.user_authenticator', $userAuthenticator], + ['security.user_checker', $userChecker], + ]) + ; + + $requestStack->expects($this->once())->method('getCurrentRequest')->willReturn($request); + $firewallMap->expects($this->once())->method('getFirewallConfig')->willReturn($firewall); + $userAuthenticator->expects($this->once())->method('authenticateUser')->with($user, $authenticator, $request); + $userChecker->expects($this->once())->method('checkPreAuth')->with($user); + + $firewallAuthenticatorLocator = $this->createMock(ServiceProviderInterface::class); + $firewallAuthenticatorLocator + ->expects($this->once()) + ->method('getProvidedServices') + ->willReturn(['security.authenticator.custom.dev' => $authenticator]) + ; + $firewallAuthenticatorLocator + ->expects($this->once()) + ->method('get') + ->with('security.authenticator.custom.dev') + ->willReturn($authenticator) + ; + + $security = new Security($container, ['main' => $firewallAuthenticatorLocator]); + + $security->login($user); + } + private function createContainer(string $serviceId, object $serviceObject): ContainerInterface { return new ServiceLocator([$serviceId => fn () => $serviceObject]); diff --git a/src/Symfony/Component/Security/Core/CHANGELOG.md b/src/Symfony/Component/Security/Core/CHANGELOG.md index 14e239069bd35..92f4cccaecb07 100644 --- a/src/Symfony/Component/Security/Core/CHANGELOG.md +++ b/src/Symfony/Component/Security/Core/CHANGELOG.md @@ -6,12 +6,6 @@ CHANGELOG * Deprecate the `Security` class, use `Symfony\Bundle\SecurityBundle\Security\Security` instead -6.1 ---- - -* Add `Security::login()` to login programmatically - - 6.0 --- diff --git a/src/Symfony/Component/Security/Core/Tests/SecurityTest.php b/src/Symfony/Component/Security/Core/Tests/SecurityTest.php index 980e226d2ca96..63eca289cc287 100644 --- a/src/Symfony/Component/Security/Core/Tests/SecurityTest.php +++ b/src/Symfony/Component/Security/Core/Tests/SecurityTest.php @@ -13,21 +13,12 @@ use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; -use Symfony\Bundle\SecurityBundle\Security\FirewallConfig; -use Symfony\Bundle\SecurityBundle\Security\FirewallMap; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\User\InMemoryUser; -use Symfony\Component\Security\Core\User\UserCheckerInterface; -use Symfony\Component\Security\Core\User\UserInterface; -use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface; -use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; -use Symfony\Contracts\Service\ServiceProviderInterface; /** * @group legacy @@ -45,7 +36,7 @@ public function testGetToken() $container = $this->createContainer('security.token_storage', $tokenStorage); - $security = new Security($container, []); + $security = new Security($container); $this->assertSame($token, $security->getToken()); } @@ -66,7 +57,7 @@ public function testGetUser($userInToken, $expectedUser) $container = $this->createContainer('security.token_storage', $tokenStorage); - $security = new Security($container, []); + $security = new Security($container); $this->assertSame($expectedUser, $security->getUser()); } @@ -89,77 +80,10 @@ public function testIsGranted() $container = $this->createContainer('security.authorization_checker', $authorizationChecker); - $security = new Security($container, []); + $security = new Security($container); $this->assertTrue($security->isGranted('SOME_ATTRIBUTE', 'SOME_SUBJECT')); } - public function testAutoLogin() - { - $request = new Request(); - $authenticator = $this->createMock(AuthenticatorInterface::class); - $requestStack = $this->createMock(RequestStack::class); - $firewallMap = $this->createMock(FirewallMap::class); - $firewall = new FirewallConfig('main', 'main'); - $userAuthenticator = $this->createMock(UserAuthenticatorInterface::class); - $user = $this->createMock(UserInterface::class); - $userChecker = $this->createMock(UserCheckerInterface::class); - - $container = $this->createMock(ContainerInterface::class); - $container - ->expects($this->atLeastOnce()) - ->method('get') - ->willReturnMap([ - ['request_stack', $requestStack], - ['security.firewall.map', $firewallMap], - ['security.user_authenticator', $userAuthenticator], - ['security.user_checker', $userChecker], - ]) - ; - - $requestStack - ->expects($this->once()) - ->method('getCurrentRequest') - ->willReturn($request) - ; - - $firewallMap - ->expects($this->once()) - ->method('getFirewallConfig') - ->willReturn($firewall) - ; - $userAuthenticator - ->expects($this->once()) - ->method('authenticateUser') - ->with($user, $authenticator, $request) - ; - $userChecker - ->expects($this->once()) - ->method('checkPreAuth') - ->with($user) - ; - - $firewallAuthenticatorLocator = $this->createMock(ServiceProviderInterface::class); - $firewallAuthenticatorLocator - ->expects($this->once()) - ->method('getProvidedServices') - ->willReturn([ - 'security.authenticator.custom.dev' => $authenticator, - ]) - ; - $firewallAuthenticatorLocator - ->expects($this->once()) - ->method('get') - ->with('security.authenticator.custom.dev') - ->willReturn($authenticator) - ; - - $security = new Security($container, [ - 'main' => $firewallAuthenticatorLocator, - ]); - - $security->login($user); - } - private function createContainer($serviceId, $serviceObject) { $container = $this->createMock(ContainerInterface::class);