8000 [Security] Add a method in the security helper to ease programmatic l… · symfony/symfony@e1961dc · GitHub
[go: up one dir, main page]

Skip to content

Commit e1961dc

Browse files
committed
[Security] Add a method in the security helper to ease programmatic login (#40662)
1 parent ba67bc9 commit e1961dc

File tree

4 files changed

+152
-12
lines changed

4 files changed

+152
-12
lines changed

src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -267,16 +267,25 @@ private function createFirewalls(array $config, ContainerBuilder $container)
267267

268268
// load firewall map
269269
$mapDef = $container->getDefinition('security.firewall.map');
270-
$map = $authenticationProviders = $contextRefs = [];
270+
$map = $authenticationProviders = $contextRefs = $authenticators = [];
271271
foreach ($firewalls as $name => $firewall) {
272272
if (isset($firewall['user_checker']) && 'security.user_checker' !== $firewall['user_checker']) {
273273
$customUserChecker = true;
274274
}
275275

276276
$configId = 'security.firewall.map.config.'.$name;
277277

278-
[$matcher, $listeners, $exceptionListener, $logoutListener] = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds, $configId);
278+
[$matcher, $listeners, $exceptionListener, $logoutListener, $firewallAuthenticators] = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds, $configId);
279279

280+
if (!$firewallAuthenticators) {
281+
$authenticators[$name] = null;
282+
} else {
283+
$firewallAuthenticatorRefs = [];
284+
foreach ($firewallAuthe 8000 nticators as $authenticatorId) {
285+
$firewallAuthenticatorRefs[$authenticatorId] = new Reference($authenticatorId);
286+
}
287+
$authenticators[$name] = ServiceLocatorTagPass::register($container, $firewallAuthenticatorRefs);
288+
}
280289
$contextId = 'security.firewall.map.context.'.$name;
281290
$isLazy = !$firewall['stateless'] && (!empty($firewall['anonymous']['lazy']) || $firewall['lazy']);
282291
$context = new ChildDefinition($isLazy ? 'security.firewall.lazy_context' : 'security.firewall.context');
@@ -291,6 +300,10 @@ private function createFirewalls(array $config, ContainerBuilder $container)
291300
$contextRefs[$contextId] = new Reference($contextId);
292301
$map[$contextId] = $matcher;
293302
}
303+
$container
304+
->getDefinition('security.helper')
305+
->replaceArgument(1, $authenticators)
306+
;
294307

295308
$container->setAlias('security.firewall.context_locator', (string) ServiceLocatorTagPass::register($container, $contextRefs));
296309

@@ -325,7 +338,7 @@ private function createFirewall(ContainerBuilder $container, string $id, array $
325338

326339
// Security disabled?
327340
if (false === $firewall['security']) {
328-
return [$matcher, [], null, null];
341+
return [$matcher, [], null, null, []];
329342
}
330343

331344
$config->replaceArgument(4, $firewall['stateless']);
@@ -518,7 +531,7 @@ private function createFirewall(ContainerBuilder $container, string $id, array $
518531
$config->replaceArgument(10, $listenerKeys);
519532
$config->replaceArgument(11, $firewall['switch_user'] ?? null);
520533

521-
return [$matcher, $listeners, $exceptionListener, null !== $logoutListenerId ? new Reference($logoutListenerId) : null];
534+
return [$matcher, $listeners, $exceptionListener, null !== $logoutListenerId ? new Reference($logoutListenerId) : null, $firewallAuthenticationProviders];
522535
}
523536

524537
private function createContextListener(ContainerBuilder $container, string $contextKey, ?string $firewallEventDispatcherId)

src/Symfony/Bundle/SecurityBundle/Resources/config/security.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,17 @@
7676
->set('security.untracked_token_storage', TokenStorage::class)
7777

7878
->set('security.helper', Security::class)
79-
->args([service_locator([
80-
'security.token_storage' => service('security.token_storage'),
81-
'security.authorization_checker' => service('security.authorization_checker'),
82-
])])
79+
->args([
80+
service_locator([
81+
'security.token_storage' => service('security.token_storage'),
82+
'security.authorization_checker' => service('security.authorization_checker'),
83+
'security.user_authenticator' => service('security.user_authenticator')->ignoreOnInvalid(),
84+
'request_stack' => service('request_stack'),
85+
'security.firewall.map' => service('security.firewall.map'),
86+
'security.user_checker' => service('security.user_checker'),
87+
]),
88+
abstract_arg('authenticators'),
89+
])
8390
->alias(Security::class, 'security.helper')
8491

8592
->set('security.user_value_resolver', UserValueResolver::class)

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

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
use Psr\Container\ContainerInterface;
1515
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
1616
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
17+
use Symfony\Component\Security\Core\Exception\LogicException;
1718
use Symfony\Component\Security\Core\User\UserInterface;
19+
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
20+
use Symfony\Contracts\Service\ServiceProviderInterface;
1821

1922
/**
2023
* Helper class for commonly-needed security tasks.
@@ -30,9 +33,15 @@ class Security implements AuthorizationCheckerInterface
3033

3134
private ContainerInterface $container;
3235

33-
public function __construct(ContainerInterface $container)
36+
/**
37+
* @param array<string, ServiceProviderInterface> $authenticators An array of authenticator service locator indexed by firewall name
38+
*/
39+
private $authenticators;
40+
41+
public function __construct(ContainerInterface $container, array $authenticators = [])
3442
{
3543
$this->container = $container;
44+
$this->authenticators = $authenticators;
3645
}
3746

3847
public function getUser(): ?UserInterface
@@ -57,4 +66,39 @@ public function getToken(): ?TokenInterface
5766
{
5867
return $this->container->get('security.token_storage')->getToken();
5968
}
69+
70+
public function autoLogin(UserInterface $user, AuthenticatorInterface $authenticator = null, string $firewallName = null): void
71+
{
72+
$request = $this->container->get('request_stack')->getCurrentRequest();
73+
74+
if (null === $authenticator) {
75+
if (null === $firewallName) {
76+
$firewall = $this->container->get('security.firewall.map')->getFirewallConfig($request);
77+
78+
if (null === $firewall) {
79+
throw new LogicException('No firewall found as the current route is not covered by any firewall.');
80+
}
81+
$firewallName = $firewall->getName();
82+
}
83+
84+
if (!isset($firewallName, $this->authenticators)) {
85+
throw new LogicException(sprintf('No authenticators found for firewall "%s".', $firewallName));
86+
}
87+
88+
/** @var ServiceProviderInterface $firewallAuthenticatorLocator */
89+
$firewallAuthenticatorLocator = $this->authenticators[$firewallName];
90+
$authenticatorIds = array_keys($firewallAuthenticatorLocator->getProvidedServices());
91+
92+
if (!$authenticatorIds) {
93+
throw new LogicException('No authenticator was found for the firewall "%s".');
94+
}
95+
96+
if (1 < \count($authenticatorIds)) {
97+
throw new LogicException(sprintf('Too much authenticators were found for the current firewall "%s". You must provide an instance of "%s" to allow an auto login. The available authenticators for the firewall "%s" are "%s".', $firewallName, AuthenticatorInterface::class, $firewallN F987 ame, implode('" ,"', $authenticatorIds)));
98+
}
99+
$authenticator = $firewallAuthenticatorLocator->get($authenticatorIds[0]);
100+
}
101+
$this->container->get('security.user_checker')->checkPreAuth($user);
102+
$this->container->get('security.user_authenticator')->authenticateUser($user, $authenticator, $request);
103+
}
60104
}

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

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,21 @@
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;
1620
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
1721
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
1822
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
1923
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
2024
use Symfony\Component\Security\Core\Security;
2125
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;
2231

2332
class SecurityTest extends TestCase
2433
{
@@ -33,7 +42,7 @@ public function testGetToken()
3342

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

36-
$security = new Security($container);
45+
$security = new F438 Security($container, []);
3746
$this->assertSame($token, $security->getToken());
3847
}
3948

@@ -54,7 +63,7 @@ public function testGetUser($userInToken, $expectedUser)
5463

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

57-
$security = new Security($container);
66+
$security = new Security($container, []);
5867
$this->assertSame($expectedUser, $security->getUser());
5968
}
6069

@@ -77,10 +86,77 @@ public function testIsGranted()
7786

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

80-
$security = new Security($container);
89+
$security = new Security($container, []);
8190
$this->assertTrue($security->isGranted('SOME_ATTRIBUTE', 'SOME_SUBJECT'));
8291
}
8392

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

0 commit comments

Comments
 (0)
0