8000 Added remember me functionality · symfony/symfony@ddf430f · GitHub
[go: up one dir, main page]

Skip to content

Commit ddf430f

Browse files
committed
Added remember me functionality
1 parent 1c810d5 commit ddf430f

15 files changed

+296
-105
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider,
4242
return [$providerId, $listenerId, $defaultEntryPoint];
4343
}
4444

45-
public function createAuthenticator( 23D3 ContainerBuilder $container, string $id, array $config, ?string $userProviderId): string
45+
public function createAuthenticator(ContainerBuilder $container, string $id, array $config, string $userProviderId): string
4646
{
4747
if (null === $config['secret']) {
4848
$config['secret'] = new Parameter('container.build_hash');

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,5 @@ interface AuthenticatorFactoryInterface
2525
*
2626
* @return string|string[] The authenticator service ID(s) to be used by the firewall
2727
*/
28-
public function createAuthenticator(ContainerBuilder $container, string $id, array $config, ?string $userProviderId);
28+
public function createAuthenticator(ContainerBuilder $container, string $id, array $config, string $userProviderId);
2929
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public function createEntryPoint(ContainerBuilder $container, string $id, array
9797
return $entryPointId;
9898
}
9999

100-
public function createAuthenticator(ContainerBuilder $container, string $id, array $config, ?string $userProviderId): string
100+
public function createAuthenticator(ContainerBuilder $container, string $id, array $config, string $userProviderId): string
101101
{
102102
$authenticatorId = 'security.authenticator.form_login.'.$id;
103103
$defaultOptions = array_merge($this->defaultSuccessHandlerOptions, $this->options);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public function create(ContainerBuilder $container, string $id, array $config, s
4646
return [$provider, $listenerId, $entryPointId];
4747
}
4848

49-
public function createAuthenticator(ContainerBuilder $container, string $id, array $config, ?string $userProviderId): string
49+
public function createAuthenticator(ContainerBuilder $container, string $id, array $config, string $userProviderId): string
5050
{
5151
$authenticatorId = 'security.authenticator.http_basic.'.$id;
5252
$container

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

Lines changed: 99 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
use Symfony\Component\HttpFoundation\Cookie;
2121
use Symfony\Component\Security\Http\EventListener\RememberMeLogoutListener;
2222

23-
class RememberMeFactory implements SecurityFactoryInterface
23+
class RememberMeFactory implements SecurityFactoryInterface, AuthenticatorFactoryInterface
2424
{
2525
protected $options = [
2626
'name' => 'REMEMBERME',
@@ -46,29 +46,8 @@ public function create(ContainerBuilder $container, string $id, array $config, ?
4646
;
4747

4848
// remember me services
49-
if (isset($config['service'])) {
50-
$templateId = $config['service'];
51-
$rememberMeServicesId = $templateId.'.'.$id;
52-
} elseif (isset($config['token_provider'])) {
53-
$templateId = 'security.authentication.rememberme.services.persistent';
54-
$rememberMeServicesId = $templateId.'.'.$id;
55-
} else {
56-
$templateId = 'security.authentication.rememberme.services.simplehash';
57-
$rememberMeServicesId = $templateId.'.'.$id;
58-
}
59-
60-
$rememberMeServices = $container->setDefinition($rememberMeServicesId, new ChildDefinition($templateId));
61-
$rememberMeServices->replaceArgument(1, $config['secret']);
62-
$rememberMeServices->replaceArgument(2, $id);
63-
64-
if (isset($config['token_provider'])) {
65-
$rememberMeServices->addMethodCall('setTokenProvider', [
66-
new Reference($config['token_provider']),
67-
]);
68-
}
69-
70-
// remember-me options
71-
$rememberMeServices->replaceArgument(3, array_intersect_key($config, $this->options));
49+
$templateId = $this->generateRememberMeServicesTemplateId($config, $id);
50+
$rememberMeServicesId = $templateId.'.'.$id;
7251

7352
// attach to remember-me aware listeners
7453
$userProviders = [];
@@ -93,17 +72,8 @@ public function create(ContainerBuilder $container, string $id, array $config, ?
9372
;
9473
}
9574
}
96-
if ($config['user_providers']) {
97-
$userProviders = [];
98-
foreach ($config['user_providers'] as $providerName) {
99-
$userProviders[] = new Reference('security.user.provider.concrete.'.$providerName);
100-
}
101-
}
102-
if (0 === \count($userProviders)) {
103-
throw new \RuntimeException('You must configure at least one remember-me aware listener (such as form-login) for each firewall that has remember-me enabled.');
104-
}
10575

106-
$rememberMeServices->replaceArgument(0, new IteratorArgument(array_unique($userProviders)));
76+
$this->createRememberMeServices($container, $id, $templateId, $userProviders, $config);
10777

10878
// remember-me listener
10979
$listenerId = 'security.authentication.listener.rememberme.'.$id;
@@ -119,6 +89,42 @@ public function create(ContainerBuilder $container, string $id, array $config, ?
11989
return [$authProviderId, $listenerId, $defaultEntryPoint];
12090
}
12191

92+
public function createAuthenticator(ContainerBuilder $container, string $id, array $config, string $userProviderId): string
93+
{
94+
$templateId = $this->generateRememberMeServicesTemplateId($config, $id);
95+
$rememberMeServicesId = $templateId.'.'.$id;
96+
97+
// create remember me services (which manage the remember me cookies)
98+
$this->createRememberMeServices($container, $id, $templateId, [new Reference($userProviderId)], $config);
99+
100+
// create remember me listener (which executes the remember me services for other authenticators and logout)
101+
$this->createRememberMeListener($container, $id, $rememberMeServicesId);
102+
103+
// create remember me authenticator (which re-authenticates the user based on the remember me cookie)
104+
$authenticatorId = 'security.authenticator.remember_me.'.$id;
105+
$container
106+
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.remember_me'))
107+
->replaceArgument(0, new Reference($rememberMeServicesId))
108+
->replaceArgument(3, array_intersect_key($config, $this->options))
109+
;
110+
111+
foreach ($container->findTaggedServiceIds('security.remember_me_aware') as $serviceId => $attributes) {
112+
// register ContextListener
113+
if ('security.context_listener' === substr($serviceId, 0, 25)) {
114+
$container
115+
->getDefinition($serviceId)
116+
->addMethodCall('setRememberMeServices', [new Reference($rememberMeServicesId)])
117+
;
118+
119+
continue;
120+
}
121+
122+
throw new \LogicException(sprintf('Symfony Authenticator Security dropped support for the "security.remember_me_aware" tag, service "%s" will no longer work as expected.', $serviceId));
123+
}
124+
125+
return $authenticatorId;
126+
}
127+
122128
public function getPosition()
123129
{
124130
return 'remember_me';
@@ -163,4 +169,63 @@ public function addConfiguration(NodeDefinition $node)
163169
}
164170
}
165171
}
172+
173+
private function generateRememberMeServicesTemplateId(array $config, string $id): string
174+
{
175+
if (isset($config['service'])) {
176+
return $config['service'];
177+
}
178+
179+
if (isset($config['token_provider'])) {
180+
return 'security.authentication.rememberme.services.persistent';
181+
}
182+
183+
return 'security.authentication.rememberme.services.simplehash';
184+
}
185+
186+
private function createRememberMeServices(ContainerBuilder $container, string $id, string $templateId, array $userProviders, array $config): void
187+
{
188+
$rememberMeServicesId = $templateId.'.'.$id;
189+
190+
$rememberMeServices = $container->setDefinition($rememberMeServicesId, new ChildDefinition($templateId));
191+
$rememberMeServices->replaceArgument(1, $config['secret']);
192+
$rememberMeServices->replaceArgument(2, $id);
193+
194+
if (isset($config['token_provider'])) {
195+
$rememberMeServices->addMethodCall('setTokenProvider', [
196+
new Reference($config['token_provider']),
197+
]);
198+
}
199+
200+
// remember-me options
201+
$rememberMeServices->replaceArgument(3, array_intersect_key($config, $this->options));
202+
203+
if ($config['user_providers']) {
204+
$userProviders = [];
205+
foreach ($config['user_providers'] as $providerName) {
206+
$userProviders[] = new Reference('security.user.provider.concrete.'.$providerName);
207+
}
208+
}
209+
210+
if (0 === \count($userProviders)) {
211+
throw new \RuntimeException('You must configure at least one remember-me aware listener (such as form-login) for each firewall that has remember-me enabled.');
212+
}
213+
214+
$rememberMeServices->replaceArgument(0, new IteratorArgument(array_unique($userProviders)));
215+
}
216+
217+
private function createRememberMeListener(ContainerBuilder $container, string $id, string $rememberMeServicesId): void
218+
{
219+
$container
220+
->setDefinition('security.listener.remember_me.'.$id, new ChildDefinition('security.listener.remember_me'))
221+
->addTag('kernel.event_subscriber')
222+
->replaceArgument(0, new Reference($rememberMeServicesId))
223+
->replaceArgument(1, $id)
224+
;
225+
226+
$container
227+
->setDefinition('security.logout.listener.remember_me.'.$id, new Definition(RememberMeLogoutListener::class))
228+
->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.'.$id])
229+
->addArgument(new Reference($rememberMeServicesId));
230+
}
166231
}

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

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use Symfony\Component\DependencyInjection\ChildDefinition;
2727
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
2828
use Symfony\Component\DependencyInjection\ContainerBuilder;
29+
use Symfony\Component\DependencyInjection\Definition;
2930
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
3031
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
3132
use Symfony\Component\DependencyInjection\Reference;
@@ -34,6 +35,7 @@
3435
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
3536
use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder;
3637
use Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder;
38+
use Symfony\Component\Security\Core\User\ChainUserProvider;
3739
use Symfony\Component\Security\Core\User\UserProviderInterface;
3840
use Symfony\Component\Security\Http\Controller\UserValueResolver;
3941
use Twig\Extension\AbstractExtension;
@@ -230,9 +232,16 @@ private function createFirewalls(array $config, ContainerBuilder $container)
230232
foreach ($providerIds as $userProviderId) {
231233
$userProviders[] = new Reference($userProviderId);
232234
}
233-
$arguments[1] = new IteratorArgument($userProviders);
235+
$arguments[1] = $userProviderIteratorsArgument = new IteratorArgument($userProviders);
234236
$contextListenerDefinition->setArguments($arguments);
235237

238+
if (\count($userProviders) > 1) {
239+
$container->setDefinition('security.user_providers', new Definition(ChainUserProvider::class, [$userProviderIteratorsArgument]))
240+
->setPublic(false);
241+
} else {
242+
$container->setAlias('security.user_providers', new Alias(current($providerIds)))->setPublic(false);
243+
}
244+
236245
if (1 === \count($providerIds)) {
237246
$container->setAlias(UserProviderInterface::class, current($providerIds));
238247
}
@@ -423,16 +432,6 @@ private function createFirewall(ContainerBuilder $container, string $id, array $
423432
// Determine default entry point
424433
$configuredEntryPoint = isset($firewall['entry_point']) ? $firewall['entry_point'] : null;
425434

426-
if ($this->authenticatorManagerEnabled) {
427-
// Remember me listener (must be before calling createAuthenticationListeners() to inject remember me services)
428-
$container
429-
->setDefinition('security.listener.remember_me.'.$id, new ChildDefinition('security.listener.remember_me'))
430-
->replaceArgument(0, $id)
431-
->addTag('kernel.event_subscriber')
432-
->addTag('security.remember_me_aware', ['id' => $id, 'provider' => 'none'])
433-
;
434-
}
435-
436435
// Authentication listeners
437436
$firewallAuthenticationProviders = [];
438437
list($authListeners, $defaultEntryPoint) = $this->createAuthenticationListeners($container, $id, $firewall, $firewallAuthenticationProviders, $defaultProvider, $providerIds, $configuredEntryPoint, $contextListenerId);
@@ -554,7 +553,7 @@ private function createAuthenticationListeners(ContainerBuilder $container, stri
554553
return [$listeners, $defaultEntryPoint];
555554
}
556555

557-
private function getUserProvider(ContainerBuilder $container, string $id, array $firewall, string $factoryKey, ?string $defaultProvider, array $providerIds, ?string $contextListenerId): ?string
556+
private function getUserProvider(ContainerBuilder $container, string $id, array $firewall, string $factoryKey, ?string $defaultProvider, array $providerIds, ?string $contextListenerId): string
558557
{
559558
if (isset($firewall[$factoryKey]['provider'])) {
560559
if (!isset($providerIds[$normalizedName = str_replace('-', '_', $firewall[$factoryKey]['provider'])])) {
@@ -564,13 +563,8 @@ private function getUserProvider(ContainerBuilder $container, string $id, array
564563
return $providerIds[$normalizedName];
565564
}
566565

567-
if ('remember_me' === $factoryKey || 'anonymous' === $factoryKey) {
568-
if ('remember_me' === $factoryKey && $contextListenerId) {
569-
$container->getDefinition($contextListenerId)->addTag('security.remember_me_aware', ['id' => $id, 'provider' => 'none']);
570-
}
571-
572-
// RememberMeFactory will use the firewall secret when created
573-
return null;
566+
if ('remember_me' === $factoryKey && $contextListenerId) {
567+
$container->getDefinition($contextListenerId)->addTag('security.remember_me_aware', ['id' => $id, 'provider' => 'none']);
574568
}
575569

576570
if ($defaultProvider) {
@@ -587,6 +581,10 @@ private function getUserProvider(ContainerBuilder $container, string $id, array
587581
return $userProvider;
588582
}
589583

584+
if ('remember_me' === $factoryKey || 'anonymous' === $factoryKey) {
585+
return 'security.user_providers';
586+
}
587+
590588
throw new InvalidConfigurationException(sprintf('Not configuring explicitly the provider for the "%s" listener on "%s" firewall is ambiguous as there is more than one registered provider.', $factoryKey, $id));
591589
}
592590

src/Symfony/Bundle/SecurityBundle/Resources/config/authenticators.xml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@
5252
class="Symfony\Component\Security\Http\EventListener\RememberMeListener"
5353
abstract="true">
5454
<tag name="monolog.logger" channel="security" />
55-
<argument/> <!-- provider key -->
55+
<argument type="abstract">remember me services</argument>
56+
<argument type="abstract">provider key</argument>
5657
<argument type="service" id="logger" on-invalid="null" />
5758
</service>
5859

@@ -82,5 +83,15 @@
8283
<argument type="abstract">secret</argument>
8384
<argument type="service" id="security.untracked_token_storage" />
8485
</service>
86+
87+
<service id="security.authenticator.remember_me"
88+
class="Symfony\Component\Security\Http\Authenticator\RememberMeAuthenticator"
89+
abstract="true">
90+
<argument type="abstract">remember me services</argument>
91+
<argument>%kernel.secret%</argument>
92+
<argument type="service" id="security.token_storage" />
93+
<argument type="abstract">options</argument>
94+
<argument type="service" id="security.authentication.session_strategy" />
95+
</service>
8596
</services>
8697
</container>

src/Symfony/Component/Security/Http/Authenticator/AbstractLoginFormAuthenticator.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
*
2626
* @experimental in 5.1
2727
*/
28-
abstract class AbstractLoginFormAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface
28+
abstract class AbstractLoginFormAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface, RememberMeAuthenticatorInterface
2929
{
3030
/**
3131
* Return the URL to the login page.
@@ -46,11 +46,6 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio
4646
return new RedirectResponse($url);
4747
}
4848

49-
public function supportsRememberMe(): bool
50-
{
51-
return true;
52-
}
53-
5449
/**
5550
* Override to control what happens when the user hits a secure page
5651
* but isn't logged in yet.
@@ -61,4 +56,9 @@ public function start(Request $request, AuthenticationException $authException =
6156

6257
return new RedirectResponse($url);
6358
}
59+
60+
public function supportsRememberMe(): bool
61+
{
62+
return true;
63+
}
6464
}

src/Symfony/Component/Security/Http/Authenticator/AnonymousAuthenticator.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,4 @@ public function onAuthenticationSuccess(Request $request, TokenInterface $token,
7575
{
7676
return null;
7777
}
78-
79-
public function supportsRememberMe(): bool
80-
{
81-
return false;
82-
}
8378
}

src/Symfony/Component/Security/Http/Authenticator/AuthenticatorInterface.php

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -102,18 +102,4 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio
102102
* will be authenticated. This makes sense, for example, with an API.
103103
*/
104104
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?Response;
105-
106-
/**
107-
* Does this method support remember me cookies?
108-
*
109-
* Remember me cookie will be set if *all* of the following are met:
110-
* A) This method returns true
111-
* B) The remember_me key under your firewall is configured
112-
* C) The "remember me" functionality is activated. This is usually
113-
* done by having a _remember_me checkbox in your form, but
114-
* can be configured by the "always_remember_me" and "remember_me_parameter"
115-
* parameters under the "remember_me" firewall key
116-
* D) The onAuthenticationSuccess method returns a Response object
117-
*/
118-
public function supportsRememberMe(): bool;
119105
}

src/Symfony/Component/Security/Http/Authenticator/HttpBasicAuthenticator.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,4 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio
9494

9595
return $this->start($request, $exception);
9696
}
97-
98-
public function supportsRememberMe(): bool
99-
{
100-
return false;
101-
}
10297
}

0 commit comments

Comments
 (0)
0