8000 [Security] Replace RememberMeServices with RememberMeHandlers · symfony/symfony@e45b25f · GitHub
[go: up one dir, main page]

Skip to content

Commit e45b25f

Browse files
committed
[Security] Replace RememberMeServices with RememberMeHandlers
* Add TokenDeauthenticatedEvent to reset remember me cookies in RememberMeListener * Add SignatureRememberMeHandler (replacing TokenBasedRememberMeServices) * Add PersistentRememberMeHandler (replacing PersistentTokenBasedRememberMeServices)
1 parent 89a87aa commit e45b25f

29 files changed

+1203
-192
lines changed

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPass.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Symfony\Component\Security\Http\Event\LoginFailureEvent;
2222
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
2323
use Symfony\Component\Security\Http\Event\LogoutEvent;
24+
use Symfony\Component\Security\Http\Event\TokenDeauthenticatedEvent;
2425
use Symfony\Component\Security\Http\SecurityEvents;
2526

2627
/**
@@ -44,6 +45,7 @@ class RegisterGlobalSecurityEventListenersPass implements CompilerPassInterface
4445
AuthenticationTokenCreatedEvent::class,
4546
AuthenticationSuccessEvent::class,
4647
InteractiveLoginEvent::class,
48+
TokenDeauthenticatedEvent::class,
4749

4850
// When events are registered by their name
4951
AuthenticationEvents::AUTHENTICATION_SUCCESS,

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginLinkFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal
111111

112112
$signatureHasherId = 'security.authenticator.login_link_signature_hasher.'.$firewallName;
113113
$container
114-
->setDefinition($signatureHasherId, new ChildDefinition('security.authenticator.abstract_signature_hasher'))
114+
->setDefinition($signatureHasherId, new ChildDefinition('security.authenticator.abstract_login_link_signature_hasher'))
115115
->replaceArgument(1, $config['signature_properties'])
116116
->replaceArgument(3, $expiredStorageId ? new Reference($expiredStorageId) : null)
117117
->replaceArgument(4, $config['max_uses'] ?? null)

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
1313

1414
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
15+
use Symfony\Component\Config\FileLocator;
1516
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
1617
use Symfony\Component\DependencyInjection\ChildDefinition;
1718
use Symfony\Component\DependencyInjection\ContainerBuilder;
1819
use Symfony\Component\DependencyInjection\Definition;
20+
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
1921
use Symfony\Component\DependencyInjection\Reference;
2022
use Symfony\Component\HttpFoundation\Cookie;
2123
use Symfony\Component\Security\Http\EventListener\RememberMeLogoutListener;
@@ -94,31 +96,56 @@ public function create(ContainerBuilder $container, string $id, array $config, ?
9496

9597
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string
9698
{
97-
$templateId = $this->generateRememberMeServicesTemplateId($config, $firewallName);
98-
$rememberMeServicesId = $templateId.'.'.$firewallName;
99+
if (!$container->hasDefinition('security.authenticator.remember_me')) {
100+
$loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__).'/../../Resources/config'));
101+
$loader->load('security_authenticator_remember_me.php');
102+
}
99103

100-
// create remember me services (which manage the remember me cookies)
101-
$this->createRememberMeServices($container, $firewallName, $templateId, [new Reference($userProviderId)], $config);
104+
$options = $config + $this->options;
105+
106+
// create remember me handler (which manage the remember me cookies)
107+
$rememberMeHandlerId = 'security.authenticator.remember_me_handler.'.$firewallName;
108+
if (isset($options['service'])) {
109+
$container->setDefinition($rememberMeHandlerId, $container->getDefinition($options['service']))
110+
->addTag('security.remember_me_handler', ['firewall' => $firewallName]);
111+
} elseif (isset($options['token_provider'])) {
112+
$container->setDefinition($rememberMeHandlerId, new ChildDefinition('security.authenticator.persistent_remember_me_handler'))
113+
->replaceArgument(0, new Reference($options['token_provider']))
114+
->replaceArgument(2, new Reference($userProviderId))
115+
->replaceArgument(4, $options)
116+
->addTag('security.remember_me_handler', ['firewall' => $firewallName]);
117+
} else {
118+
$signatureHasherId = 'security.authenticator.remember_me_signature_hasher.'.$firewallName;
119+
$container->setDefinition($signatureHasherId, new ChildDefinition('security.authenticator.remember_me_signature_hasher'))
120+
->replaceArgument(1, $options['signature_properties'])
121+
;
122+
123+
$container->setDefinition($rememberMeHandlerId, new ChildDefinition('security.authenticator.signature_remember_me_handler'))
124+
->replaceArgument(0, new Reference($signatureHasherId))
125+
->replaceArgument(1, new Reference($userProviderId))
126+
->replaceArgument(3, $options)
127+
->addTag('security.remember_me_handler', ['firewall' => $firewallName]);
128+
}
102129

103130
// create remember me listener (which executes the remember me services for other authenticators and logout)
104-
$this->createRememberMeListener($container, $firewallName, $rememberMeServicesId);
131+
$rememberMeListenerId = 'security.listener.remember_me.'.$firewallName;
132+
$container->setDefinition($rememberMeListenerId, new ChildDefinition('security.listener.remember_me'))
133+
->replaceArgument(0, new Reference($rememberMeHandlerId))
134+
->replaceArgument(1, array_intersect_key($options, ['always_remember_me' => true, 'remember_me_parameter' => true]))
135+
->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.'.$firewallName])
136+
;
105137

106138
// create remember me authenticator (which re-authenticates the user based on the remember me cookie)
107139
$authenticatorId = 'security.authenticator.remember_me.'.$firewallName;
108140
$container
109141
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.remember_me'))
110-
->replaceArgument(0, new Reference($rememberMeServicesId))
111-
->replaceArgument(3, array_intersect_key($config, $this->options))
142+
->replaceArgument(0, new Reference($rememberMeHandlerId))
143+
->replaceArgument(3, $config['name'] ?? $this->options['name'])
112144
;
113145

114146
foreach ($container->findTaggedServiceIds('security.remember_me_aware') as $serviceId => $attributes) {
115147
// register ContextListener
116148
if ('security.context_listener' === substr($serviceId, 0, 25)) {
117-
$container
118-
->getDefinition($serviceId)
119-
->addMethodCall('setRememberMeServices', [new Reference($rememberMeServicesId)])
120-
;
121-
122149
continue;
123150
}
124151

@@ -156,6 +183,12 @@ public function addConfiguration(NodeDefinition $node)
156183
->prototype('scalar')->end()
157184
->end()
158185
->booleanNode('catch_exceptions')->defaultTrue()->end()
186+
->arrayNode('signature_properties')
187+
->prototype('scalar')->end()
188+
->requiresAtLeastOneElement()
189+
->info('An array of properties on your User that are used to sign the rememberme cookie. If any of these change, all existing cookies will become invalid.')
190+
->example(['email', 'password'])
191+
->end()
159192
;
160193

161194
foreach ($this->options as $name => $value) {
@@ -216,18 +249,4 @@ private function createRememberMeServices(ContainerBuilder $container, string $i
216249

217250
$rememberMeServices->replaceArgument(0, new IteratorArgument(array_unique($userProviders)));
218251
}
219-
220-
private function createRememberMeListener(ContainerBuilder $container, string $id, string $rememberMeServicesId): void
221-
{
222-
$container
223-
->setDefinition('security.listener.remember_me.'.$id, new ChildDefinition('security.listener.remember_me'))
224-
->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.'.$id])
225-
->replaceArgument(0, new Reference($rememberMeServicesId))
226-
;
227-
228-
$container
229-
->setDefinition('security.logout.listener.remember_me.'.$id, new Definition(RememberMeLogoutListener::class))
230-
->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.'.$id])
231-
->addArgument(new Reference($rememberMeServicesId));
232-
}
233252
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ private function createFirewall(ContainerBuilder $container, string $id, array $
381381
// Context serializer listener
382382
if (false === $firewall['stateless']) {
383383
$contextKey = $firewall['context'] ?? $id;
384-
$listeners[] = new Reference($contextListenerId = $this->createContextListener($container, $contextKey));
384+
$listeners[] = new Reference($contextListenerId = $this->createContextListener($container, $contextKey, $firewallEventDispatcherId));
385385
$sessionStrategyId = 'security.authentication.session_strategy';
386386

387387
if ($this->authenticatorManagerEnabled) {
@@ -541,7 +541,7 @@ private function createFirewall(ContainerBuilder $container, string $id, array $
541541
return [$matcher, $listeners, $exceptionListener, null !== $logoutListenerId ? new Reference($logoutListenerId) : null];
542542
}
543543

544-
private function createContextListener(ContainerBuilder $container, string $contextKey)
544+
private function createContextListener(ContainerBuilder $container, string $contextKey, string $firewallEventDispatcherId)
545545
{
546546
if (isset($this->contextListeners[$contextKey])) {
547547
return $this->contextListeners[$contextKey];
@@ -550,6 +550,7 @@ private function createContextListener(ContainerBuilder $container, string $cont
550550
$listenerId = 'security.context_listener.'.\count($this->contextListeners);
551551
$listener = $container->setDefinition($listenerId, new ChildDefinition('security.context_listener'));
552552
$listener->replaceArgument(2, $contextKey);
553+
$listener->addArgument(new Reference($firewallEventDispatcherId));
553554

554555
return $this->contextListeners[$contextKey] = $listenerId;
555556
}

src/Symfony/Bundle/SecurityBundle/LoginLink/FirewallAwareLoginLinkHandler.php

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Bundle\SecurityBundle\LoginLink;
1313

1414
use Psr\Container\ContainerInterface;
15+
use Symfony\Bundle\SecurityBundle\Security\FirewallAwareTrait;
1516
use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
1617
use Symfony\Component\HttpFoundation\Request;
1718
use Symfony\Component\HttpFoundation\RequestStack;
@@ -26,43 +27,24 @@
2627
*/
2728
class FirewallAwareLoginLinkHandler implements LoginLinkHandlerInterface
2829
{
29-
private $firewallMap;
30-
private $loginLinkHandlerLocator;
31-
private $requestStack;
30+
private const FIREWALL_OPTION = 'login_link';
31+
32+
use FirewallAwareTrait;
3233

3334
public function __construct(FirewallMap $firewallMap, ContainerInterface $loginLinkHandlerLocator, RequestStack $requestStack)
3435
{
3536
$this->firewallMap = $firewallMap;
36-
$this->loginLinkHandlerLocator = $loginLinkHandlerLocator;
37+
$this->locator = $loginLinkHandlerLocator;
3738
$this->requestStack = $requestStack;
3839
}
3940

4041
public function createLoginLink(UserInterface $user): LoginLinkDetails
4142
{
42-
return $this->getLoginLinkHandler()->createLoginLink($user);
43+
return $this->getForFirewall()->createLoginLink($user);
4344
}
4445

4546
public function consumeLoginLink(Request $request): UserInterface
4647
{
47-
return $this->getLoginLinkHandler()->consumeLoginLink($request);
48-
}
49-
50-
private function getLoginLinkHandler(): LoginLinkHandlerInterface
51-
{
52-
if (null === $request = $this->requestStack->getCurrentRequest()) {
53-
throw new \LogicException('Cannot determine the correct LoginLinkHandler to use: there is no active Request and so, the firewall cannot be determined. Try using the specific login link handler service.');
54-
}
55-
56-
$firewall = $this->firewallMap->getFirewallConfig($request);
57-
if (!$firewall) {
58-
throw new \LogicException('No login link handler found as the current route is not covered by a firewall.');
59-
}
60-
61-
$firewallName = $firewall->getName();
62-
if (!$this->loginLinkHandlerLocator->has($firewallName)) {
63-
throw new \LogicException(sprintf('No login link handler found. Did you add a login_link key under your "%s" firewall?', $firewallName));
64-
}
65-
66-
return $this->loginLinkHandlerLocator->get($firewallName);
48+
return $this->getForFirewall()->consumeLoginLink($request);
6749
}
6850
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Po 341A tencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\SecurityBundle\RememberMe;
13+
14+
use Psr\Container\ContainerInterface;
15+
use Symfony\Bundle\SecurityBundle\Security\FirewallAwareTrait;
16+
use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
17+
use Symfony\Component\HttpFoundation\RequestStack;
18+
use Symfony\Component\Security\Core\User\UserInterface;
19+
use Symfony\Component\Security\Http\RememberMe\RememberMeDetails;
20+
use Symfony\Component\Security\Http\RememberMe\RememberMeHandlerInterface;
21+
22+
/**
23+
* Decorates {@see RememberMeHandlerInterface} for the current firewall.
24+
*
25+
* @author Wouter de Jong <wouter@wouterj.nl>
26+
*
27+
* @experimental in 5.3
28+
*/
29+
final class FirewallAwareRememberMeHandler implements RememberMeHandlerInterface
30+
{
31+
private const FIREWALL_OPTION = 'remember_me';
32+
33+
use FirewallAwareTrait;
34+
35+
public function __construct(FirewallMap $firewallMap, ContainerInterface $rememberMeHandlerLocator, RequestStack $requestStack)
36+
{
37+
$this->firewallMap = $firewallMap;
38+
$this->locator = $rememberMeHandlerLocator;
39+
$this->requestStack = $requestStack;
40+
}
41+
42+
public function createRememberMeCookie(UserInterface $user): void
43+
{
44+
$this->getForFirewall()->createRememberMeCookie($user);
45+
}
46+
47+
public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails): UserInterface
48+
{
49+
return $this->getForFirewall()->consumeRememberMeCookie($rememberMeDetails);
50+
}
51+
52+
public function clearRememberMeCookie(): void
53+
{
54+
$this->getForFirewall()->clearRememberMeCookie();
55+
}
56+
}

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

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,12 @@
2020
use Symfony\Component\Security\Http\Authenticator\FormLoginAuthenticator;
2121
use Symfony\Component\Security\Http\Authenticator\HttpBasicAuthenticator;
2222
use Symfony\Component\Security\Http\Authenticator\JsonLoginAuthenticator;
23-
use Symfony\Component\Security\Http\Authenticator\RememberMeAuthenticator;
2423
use Symfony\Component\Security\Http\Authenticator\RemoteUserAuthenticator;
2524
use Symfony\Component\Security\Http\Authenticator\X509Authenticator;
2625
use Symfony\Component\Security\Http\Event\CheckPassportEvent;
2726
use Symfony\Component\Security\Http\EventListener\CheckCredentialsListener;
2827
use Symfony\Component\Security\Http\EventListener\LoginThrottlingListener;
2928
use Symfony\Component\Security\Http\EventListener\PasswordMigratingListener;
30-
use Symfony\Component\Security\Http\EventListener\RememberMeListener;
3129
use Symfony\Component\Security\Http\EventListener\SessionStrategyListener;
3230
use Symfony\Component\Security\Http\EventListener\UserCheckerListener;
3331
use Symfony\Component\Security\Http\EventListener\UserProviderListener;
@@ -106,14 +104,6 @@
106104
service('security.authentication.session_strategy'),
107105
])
108106

109-
->set('security.listener.remember_me', RememberMeListener::class)
110-
->abstract()
111-
->args([
112-
abstract_arg('remember me services'),
113-
service('logger')->nullOnInvalid(),
114-
])
115-
->tag('monolog.logger', ['channel' => 'security'])
116-
117107
->set('security.listener.login_throttling', LoginThrottlingListener::class)
118108
->abstract()
119109
->args([
@@ -153,16 +143,6 @@
153143
])
154144
->call('setTranslator', [service('translator')->ignoreOnInvalid()])
155145

156-
->set('security.authenticator.remember_me', RememberMeAuthenticator::class)
157-
->abstract()
158-
->args([
159-
abstract_arg('remember me services'),
160-
param('kernel.secret'),
161-
service('security.token_storage'),
162-
abstract_arg('options'),
163-
service('security.authentication.session_strategy'),
164-
])
165-
166146
->set('security.authenticator.x509', X509Authenticator::class)
167147
->abstract()
168148
->args([

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
abstract_arg('options'),
4040
])
4141

42-
->set('security.authenticator.abstract_signature_hasher', SignatureHasher::class)
42+
->set('security.authenticator.abstract_login_link_signature_hasher', SignatureHasher::class)
4343
->args([
4444
service('property_accessor'),
4545
abstract_arg('signature properties'),

0 commit comments

Comments
 (0)
0