8000 Add functional test & fix reviews · symfony/symfony@37efa72 · GitHub
[go: up one dir, main page]

Skip to content

Commit 37efa72

Browse files
committed
Add functional test & fix reviews
1 parent d7724d2 commit 37efa72

File tree

7 files changed

+130
-103
lines changed

7 files changed

+130
-103
lines changed

src/Symfony/Bundle/SecurityBundle/Security/Security.php

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,15 @@
2222
/**
2323
* Helper class for commonly-needed security tasks.
2424
*
25+
* @author Ryan Weaver <ryan@symfonycasts.com>
26+
* @author Robin Chalas <robin.chalas@gmail.com>
27+
* @author Arnaud Frézet <arnaud@larriereguichet.fr>
28+
*
2529
* @final
2630
*/
2731
class Security extends LegacySecurity
2832
{
29-
public function __construct(private ContainerInterface $container, private array $authenticators = [])
33+
public function __construct(private readonly ContainerInterface $container, private readonly array $authenticators = [])
3034
{
3135
parent::__construct($container, false);
3236
}
@@ -36,14 +40,21 @@ public function getFirewallConfig(Request $request): ?FirewallConfig
3640
return $this->container->get('security.firewall.map')->getFirewallConfig($request);
3741
}
3842

43+
/**
44+
* @param UserInterface $user The user to authenticate
45+
* @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
46+
* @param string|null $firewallName The firewall name - required only if multiple firewalls are configured
47+
*/
3948
public function login(UserInterface $user, string $authenticatorName = null, string $firewallName = null): void
4049
{
4150
$request = $this->container->get('request_stack')->getCurrentRequest();
51+
$firewallName ??= $this->getFirewallConfig($request)?->getName();
4252

43-
if (!class_exists(AuthenticatorInterface::class)) {
44-
throw new \LogicException('Security HTTP is missing. Try running "composer require symfony/security-http".');
53+
if (!$firewallName) {
54+
throw new LogicException('Unable to login as the current route is not covered by any firewall.');
4555
}
46-
$authenticator = $this->getAuthenticator($authenticatorName, $firewallName ?? $this->getFirewallName($request));
56+
57+
$authenticator = $this->getAuthenticator($authenticatorName, $firewallName);
4758

4859
$this->container->get('security.user_checker')->checkPreAuth($user);
4960
$this->container->get('security.user_authenticator')->authenticateUser($user, $authenticator, $request);
@@ -54,39 +65,33 @@ private function getAuthenticator(?string $authenticatorName, string $firewallNa
5465
if (!\array_key_exists($firewallName, $this->authenticators)) {
5566
throw new LogicException(sprintf('No authenticators found for firewall "%s".', $firewallName));
5667
}
68+
5769
/** @var ServiceProviderInterface $firewallAuthenticatorLocator */
5870
$firewallAuthenticatorLocator = $this->authenticators[$firewallName];
5971

6072
if (!$authenticatorName) {
6173
$authenticatorIds = array_keys($firewallAuthenticatorLocator->getProvidedServices());
6274

6375
if (!$authenticatorIds) {
64-
throw new LogicException('No authenticator was found for the firewall "%s".');
76+
throw new LogicException(sprintf('No authenticator was found for the firewall "%s".', $firewallName));
6577
}
66-
6778
if (1 < \count($authenticatorIds)) {
6879
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)));
6980
}
7081

7182
return $firewallAuthenticatorLocator->get($authenticatorIds[0]);
7283
}
73-
$authenticatorId = 'security.authenticator.'.$authenticatorName.'.'.$firewallName;
7484

75-
if (!$firewallAuthenticatorLocator->has($authenticatorId)) {
76-
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));
85+
if ($firewallAuthenticatorLocator->has($authenticatorName)) {
86+
return $firewallAuthenticatorLocator->get($authenticatorName);
7787
}
7888

79-
return $firewallAuthenticatorLocator->get($authenticatorId);
80-
}
81-
82-
private function getFirewallName(Request $request): string
83-
{
84-
$firewall = $this->container->get('security.firewall.map')->getFirewallConfig($request);
89+
$authenticatorId = 'security.authenticator.'.$authenticatorName.'.'.$firewallName;
8590

86-
if (null === $firewall) {
87-
throw new LogicException('No firewall found as the current route is not covered by any firewall.');
91+
if (!$firewallAuthenticatorLocator->has($authenticatorId)) {
92+
throw new LogicException(sprintf('Unable to find an authenticator named "%s" for the firewall "%s". Available authenticators: "%s".', $authenticatorName, implode('", "', $firewallAuthenticatorLocator->getProvidedServices())));
8893
}
8994

90-
return $firewall->getName();
95+
return $firewallAuthenticatorLocator->get($authenticatorId);
9196
}
9297
}

src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
namespace Symfony\Bundle\SecurityBundle\Tests\Functional;
1313

1414
use Symfony\Bundle\SecurityBundle\Security\FirewallConfig;
15+
use Symfony\Bundle\SecurityBundle\Security\Security;
1516
use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\SecuredPageBundle\Security\Core\User\Arra 10000 yUserProvider;
17+
use Symfony\Component\HttpFoundation\JsonResponse;
1618
use Symfony\Component\HttpFoundation\Request;
1719
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
1820
use Symfony\Component\Security\Core\User\InMemoryUser;
@@ -81,6 +83,22 @@ public function userWillBeMarkedAsChangedIfRolesHasChangedProvider()
8183
],
8284
];
8385
}
86+
87+
/**
88+
* @testWith ["json_login"]
89+
* ["Symfony\\Bundle\\SecurityBundle\\Tests\\Functional\\Bundle\\AuthenticatorBundle\\ApiAuthenticator"]
90+
*/
91+
public function testLoginWithBuiltInAuthenticator(string $authenticator)
92+
{
93+
$client = $this->createClient(['test_case' => 'SecurityHelper', 'root_config' => 'config.yml', 'debug' => true]);
94+
static::getContainer()->get(WelcomeController::class)->authenticator = $authenticator;
95+
$client->request('GET', '/welcome');
96+
$response = $client->getResponse();
97+
98+
$this->assertInstanceOf(JsonResponse::class, $response);
99+
$this->assertSame(200, $response->getStatusCode());
100+
$this->assertSame(['message' => 'Welcome @chalasr!'], json_decode($response->getContent(), true));
101+
}
84102
}
85103

86104
final class UserWithoutEquatable implements UserInterface, PasswordAuthenticatedUserInterface
@@ -189,3 +207,20 @@ public function eraseCredentials(): void
189207
{
190208
}
191209
}
210+
211+
class WelcomeController
212+
{
213+
public $authenticator = 'json_login';
214+
215+
public function __construct(private Security $security)
216+
{
217+
}
218+
219+
public function welcome()
220+
{
221+
$user = new InMemoryUser('chalasr', '', ['ROLE_USER']);
222+
$this->security->login($user, $this->authenticator);
223+
224+
return new JsonResponse(['message' => sprintf('Welcome @%s!', $this->security->getUser()->getUserIdentifier())]);
225+
}
226+
}

src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ services:
1111
alias: security.token_storage
1212
public: true
1313

14+
Symfony\Bundle\SecurityBundle\Tests\Functional\WelcomeController:
15+
arguments: ['@security.helper']
16+
public: true
17+
18+
Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ApiAuthenticator: ~
19+
1420
security:
1521
enable_authenticator_manager: true
1622
providers:
@@ -20,3 +26,11 @@ security:
2026

2127
firewalls:
2228
default:
29+
json_login:
30+
username_path: user.login
31+
password_path: user.password
32+
custom_authenticators:
33+
- 'Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ApiAuthenticator'
34+
35+
access_control:
36+
- { path: ^/foo, roles: PUBLIC_ACCESS }
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
welcome:
2+
path: /welcome
3+
defaults: { _controller: Symfony\Bundle\SecurityBundle\Tests\Functional\WelcomeController::welcome }

src/Symfony/Bundle/SecurityBundle/Tests/Security/SecurityTest.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,17 @@
1818
use Symfony\Bundle\SecurityBundle\Security\Security;
1919
use Symfony\Component\DependencyInjection\ServiceLocator;
2020
use Symfony\Component\HttpFoundation\Request;
21+
use Symfony\Component\HttpFoundation\RequestStack;
2122
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
2223
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
2324
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
2425
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
2526
use Symfony\Component\Security\Core\User\InMemoryUser;
27+
use Symfony\Component\Security\Core\User\UserCheckerInterface;
28+
use Symfony\Component\Security\Core\User\UserInterface;
29+
use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface;
30+
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
31+
use Symfony\Contracts\Service\ServiceProviderInterface;
2632

2733
class SecurityTest extends TestCase
2834
{
@@ -111,6 +117,52 @@ public function getFirewallConfigTests()
111117
yield [$request, new FirewallConfig('main', 'acme_user_checker')];
112118
}
113119

120+
public function testAutoLogin()
121+
{
122+
$request = new Request();
123+
$authenticator = $this->createMock(AuthenticatorInterface::class);
124+
$requestStack = $this->createMock(RequestStack::class);
125+
$firewallMap = $this->createMock(FirewallMap::class);
126+
$firewall = new FirewallConfig('main', 'main');
127+
$userAuthenticator = $this->createMock(UserAuthenticatorInterface::class);
128+
$user = $this->createMock(UserInterface::class);
129+
$userChecker = $this->createMock(UserCheckerInterface::class);
130+
131+
$container = $this->createMock(ContainerInterface::class);
132+
$container
133+
->expects($this->atLeastOnce())
134+
->method('get')
135+
->willReturnMap([
136+
['request_stack', $requestStack],
137+
['security.firewall.map', $firewallMap],
138+
['security.user_authenticator', $userAuthenticator],
139+
['security.user_checker', $userChecker],
140+
])
141+
;
142+
143+
$requestStack->expects($this->once())->method('getCurrentRequest')->willReturn($request);
144+
$firewallMap->expects($this->once())->method('getFirewallConfig')->willReturn($firewall);
145+
$userAuthenticator->expects($this->once())->method('authenticateUser')->with($user, $authenticator, $request);
146+
$userChecker->expects($this->once())->method('checkPreAuth')->with($user);
147+
148+
$firewallAuthenticatorLocator = $this->createMock(ServiceProviderInterface::class);
149+
$firewallAuthenticatorLocator
150+
->expects($this->once())
151+
->method('getProvidedServices')
152+
->willReturn(['security.authenticator.custom.dev' => $authenticator])
153+
;
154+
$firewallAuthenticatorLocator
155+
->expects($this->once())
156+
->method('get')
157+
->with('security.authenticator.custom.dev')
158+
->willReturn($authenticator)
159+
;
160+
161+
$security = new Security($container, ['main' => $firewallAuthenticatorLocator]);
162+
163+
$security->login($user);
164+
}
165+
114166
private function createContainer(string $serviceId, object $serviceObject): ContainerInterface
115167
{
116168
return new ServiceLocator([$serviceId => fn () => $serviceObject]);

src/Symfony/Component/Security/Core/CHANGELOG.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,6 @@ CHANGELOG
66

77
* Deprecate the `Security` class, use `Symfony\Bundle\SecurityBundle\Security\Security` instead
88

9-
6.1
10-
---
11-
12-
* Add `Security::login()` to login programmatically
13-
14-
159
6.0
1610
---
1711

src/Symfony/Component/Security/Core/Tests/SecurityTest.php

Lines changed: 3 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,12 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515
use Psr\Container\ContainerInterface;
16-
use Symfony\Bundle\SecurityBundle\Security\FirewallConfig;
17-
use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
18-
use Symfony\Component\HttpFoundation\Request;
19-
use Symfony\Component\HttpFoundation\RequestStack;
2016
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
2117
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
2218
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
2319
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
2420
use Symfony\Component\Security\Core\Security;
2521
use Symfony\Component\Security\Core\User\InMemoryUser;
26-
use Symfony\Component\Security\Core\User\UserCheckerInterface;
27-
use Symfony\Component\Security\Core\User\UserInterface;
28-
use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface;
29-
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
30-
use Symfony\Contracts\Service\ServiceProviderInterface;
3122

3223
/**
3324
* @group legacy
@@ -45,7 +36,7 @@ public function testGetToken()
4536

4637
$container = $this->createContainer('security.token_storage', $tokenStorage);
4738

48-
$security = new Security($container, []);
39+
$security = new Security($container);
4940
$this->assertSame($token, $security->getToken());
5041
}
5142

@@ -66,7 +57,7 @@ public function testGetUser($userInToken, $expectedUser)
6657

6758
$container = $this->createContainer('security.token_storage', $tokenStorage);
6859

69-
$security = new Security($container, []);
60+
$security = new Security($container);
7061
$this->assertSame($expectedUser, $security->getUser());
7162
}
7263

@@ -89,77 +80,10 @@ public function testIsGranted()
8980

9081
$container = $this->createContainer('security.authorization_checker', $authorizationChecker);
9182

92-
$security = new Security($container, []);
83+
$security = new Security($container);
9384
$this->assertTrue($security->isGranted('SOME_ATTRIBUTE', 'SOME_SUBJECT'));
9485
}
9586

96-
public function testAutoLogin()
97-
{
98-
$request = new Request();
99-
$authenticator = $this->createMock(AuthenticatorInterface::class);
100-
$requestStack = $this->createMock(RequestStack::class);
101-
$firewallMap = $this->createMock(FirewallMap::class);
102-
$firewall = new FirewallConfig('main', 'main');
103-
$userAuthenticator = $this->createMock(UserAuthenticatorInterface::class);
104-
$user = $this->createMock(UserInterface::class);
105-
$userChecker = $this->createMock(UserCheckerInterface::class);
106-
107-
$container = $this->createMock(ContainerInterface::class);
108-
$container
109-
->expects($this->atLeastOnce())
110-
->method('get')
111-
->willReturnMap([
112-
['request_stack', $requestStack],
113-
['security.firewall.map', $firewallMap],
114-
['security.user_authenticator', $userAuthenticator],
115-
['security.user_checker', $userChecker],
116-
])
117-
;
118-
119-
$requestStack
120-
->expects($this->once())
121-
->method('getCurrentRequest')
122-
->willReturn($request)
123-
;
124-
125-
$firewallMap
126-
->expects($this->once())
127-
->method('getFirewallConfig')
128-
->willReturn($firewall)
129-
;
130-
$userAuthenticator
131-
->expects($this->once())
132-
->method('authenticateUser')
133-
->with($user, $authenticator, $request)
134-
;
135-
$userChecker
136-
->expects($this->once())
137-
->method('checkPreAuth')
138-
->with($user)
139-
;
140-
141-
$firewallAuthenticatorLocator = $this->createMock(ServiceProviderInterface::class);
142-
$firewallAuthenticatorLocator
143-
->expects($this->once())
144-
->method('getProvidedServices')
145-
->willReturn([
146-
'security.authenticator.custom.dev' => $authenticator,
147-
])
148-
;
149-
$firewallAuthenticatorLocator
150-
->expects($this->once())
151-
->method('get')
152-
->with('security.authenticator.custom.dev')
153-
->willReturn($authenticator)
154-
;
155-
156-
$security = new Security($container, [
157-
'main' => $firewallAuthenticatorLocator,
158-
]);
159-
160-
$security->login($user);
161-
}
162-
16387
private function createContainer($serviceId, $serviceObject)
16488
{
16589
$container = $this->createMock(ContainerInterface::class);

0 commit comments

Comments
 (0)
0