Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 1567041

Browse filesBrowse files
wouterjchalasr
authored andcommitted
[Security] Rework the remember me system
1 parent 0f96ac7 commit 1567041
Copy full SHA for 1567041

File tree

68 files changed

+2240
-505
lines changed
Filter options

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Dismiss banner

68 files changed

+2240
-505
lines changed
+62Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <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\Bridge\Doctrine\SchemaListener;
13+
14+
use Doctrine\Common\EventSubscriber;
15+
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
16+
use Doctrine\ORM\Tools\ToolEvents;
17+
use Symfony\Bridge\Doctrine\Security\RememberMe\DoctrineTokenProvider;
18+
use Symfony\Component\Security\Http\RememberMe\PersistentRememberMeHandler;
19+
use Symfony\Component\Security\Http\RememberMe\RememberMeHandlerInterface;
20+
21+
/**
22+
* Automatically adds the rememberme table needed for the {@see DoctrineTokenProvider}.
23+
*
24+
* @author Wouter de Jong <wouter@wouterj.nl>
25+
*/
26+
final class RememberMeTokenProviderDoctrineSchemaSubscriber implements EventSubscriber
27+
{
28+
private $rememberMeHandlers;
29+
30+
/**
31+
* @param iterable|RememberMeHandlerInterface[] $rememberMeHandlers
32+
*/
33+
public function __construct(iterable $rememberMeHandlers)
34+
{
35+
$this->rememberMeHandlers = $rememberMeHandlers;
36+
}
37+
38+
public function postGenerateSchema(GenerateSchemaEventArgs $event): void
39+
{
40+
$dbalConnection = $event->getEntityManager()->getConnection();
41+
42+
foreach ($this->rememberMeHandlers as $rememberMeHandler) {
43+
if (
44+
$rememberMeHandler instanceof PersistentRememberMeHandler
45+
&& ($tokenProvider = $rememberMeHandler->getTokenProvider()) instanceof DoctrineTokenProvider
46+
) {
47+
$tokenProvider->configureSchema($event->getSchema(), $dbalConnection);
48+
}
49+
}
50+
}
51+
52+
public function getSubscribedEvents(): array
53+
{
54+
if (!class_exists(ToolEvents::class)) {
55+
return [];
56+
}
57+
58+
return [
59+
ToolEvents::postGenerateSchema,
60+
];
61+
}
62+
}

‎src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php

Copy file name to clipboardExpand all lines: src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php
+33-8Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@
1414
use Doctrine\DBAL\Connection;
1515
use Doctrine\DBAL\Driver\Result as DriverResult;
1616
use Doctrine\DBAL\Result;
17+
use Doctrine\DBAL\Schema\Schema;
1718
use Doctrine\DBAL\Types\Types;
1819
use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken;
1920
use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentTokenInterface;
2021
use Symfony\Component\Security\Core\Authentication\RememberMe\TokenProviderInterface;
2122
use Symfony\Component\Security\Core\Exception\TokenNotFoundException;
2223

2324
/**
24-
* This class provides storage for the tokens that is set in "remember me"
25+
* This class provides storage for the tokens that is set in "remember-me"
2526
* cookies. This way no password secrets will be stored in the cookies on
2627
* the client machine, and thus the security is improved.
2728
*
@@ -53,8 +54,7 @@ public function __construct(Connection $conn)
5354
public function loadTokenBySeries(string $series)
5455
{
5556
// the alias for lastUsed works around case insensitivity in PostgreSQL
56-
$sql = 'SELECT class, username, value, lastUsed AS last_used'
57-
.' FROM rememberme_token WHERE series=:series';
57+
$sql = 'SELECT class, username, value, lastUsed AS last_used FROM rememberme_token WHERE series=:series';
5858
$paramValues = ['series' => $series];
5959
$paramTypes = ['series' => \PDO::PARAM_STR];
6060
$stmt = $this->conn->executeQuery($sql, $paramValues, $paramTypes);
@@ -87,8 +87,7 @@ public function deleteTokenBySeries(string $series)
8787
*/
8888
public function updateToken(string $series, string $tokenValue, \DateTime $lastUsed)
8989
{
90-
$sql = 'UPDATE rememberme_token SET value=:value, lastUsed=:lastUsed'
91-
.' WHERE series=:series';
90+
$sql = 'UPDATE rememberme_token SET value=:value, lastUsed=:lastUsed WHERE series=:series';
9291
$paramValues = [
9392
'value' => $tokenValue,
9493
'lastUsed' => $lastUsed,
@@ -114,9 +113,7 @@ public function updateToken(string $series, string $tokenValue, \DateTime $lastU
114113
*/
115114
public function createNewToken(PersistentTokenInterface $token)
116115
{
117-
$sql = 'INSERT INTO rememberme_token'
118-
.' (class, username, series, value, lastUsed)'
119-
.' VALUES (:class, :username, :series, :value, :lastUsed)';
116+
$sql = 'INSERT INTO rememberme_token (class, username, series, value, lastUsed) VALUES (:class, :username, :series, :value, :lastUsed)';
120117
$paramValues = [
121118
'class' => $token->getClass(),
122119
// @deprecated since 5.3, change to $token->getUserIdentifier() in 6.0
@@ -138,4 +135,32 @@ public function createNewToken(PersistentTokenInterface $token)
138135
$this->conn->executeUpdate($sql, $paramValues, $paramTypes);
139136
}
140137
}
138+
139+
/**
140+
* Adds the Table to the Schema if "remember me" uses this Connection.
141+
*/
142+
public function configureSchema(Schema $schema, Connection $forConnection): void
143+
{
144+
// only update the schema for this connection
145+
if ($forConnection !== $this->conn) {
146+
return;
147+
}
148+
149+
if ($schema->hasTable('rememberme_token')) {
150+
return;
151+
}
152+
153+
$this->addTableToSchema($schema);
154+
}
155+
156+
private function addTableToSchema(Schema $schema): void
157+
{
158+
$table = $schema->createTable('rememberme_token');
159+
$table->addColumn('series', Types::STRING, ['length' => 88]);
160+
$table->addColumn('value', Types::STRING, ['length' => 88]);
161+
$table->addColumn('lastUsed', Types::DATETIME_MUTABLE);
162+
$table->addColumn('class', Types::STRING, ['length' => 100]);
163+
$table->addColumn('username', Types::STRING, ['length' => 200]);
164+
$table->setPrimaryKey(['series']);
165+
}
141166
}

‎src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class UnusedTagsPass implements CompilerPassInterface
7777
'security.authenticator.login_linker',
7878
'security.expression_language_provider',
7979
'security.remember_me_aware',
80+
'security.remember_me_handler',
8081
'security.voter',
8182
'serializer.encoder',
8283
'serializer.normalizer',

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPass.php
+2Lines 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,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <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\DependencyInjection\Compiler;
13+
14+
use Symfony\Bundle\SecurityBundle\RememberMe\DecoratedRememberMeHandler;
15+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
16+
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
18+
/**
19+
* Replaces the DecoratedRememberMeHandler services with the real definition.
20+
*
21+
* @author Wouter de Jong <wouter@wouterj.nl>
22+
*
23+
* @internal
24+
*/
25+
final class ReplaceDecoratedRememberMeHandlerPass implements CompilerPassInterface
26+
{
27+
private const HANDLER_TAG = 'security.remember_me_handler';
28+
29+
/**
30+
* {@inheritdoc}
31+
*/
32+
public function process(ContainerBuilder $container): void
33+
{
34+
$handledFirewalls = [];
35+
foreach ($container->findTaggedServiceIds(self::HANDLER_TAG) as $definitionId => $rememberMeHandlerTags) {
36+
$definition = $container->findDefinition($definitionId);
37+
if (DecoratedRememberMeHandler::class !== $definition->getClass()) {
38+
continue;
39+
}
40+
41+
// get the actual custom remember me handler definition (passed to the decorator)
42+
$realRememberMeHandler = $container->findDefinition((string) $definition->getArgument(0));
43+
if (null === $realRememberMeHandler) {
44+
throw new \LogicException(sprintf('Invalid service definition for custom remember me handler; no service found with ID "%s".', (string) $definition->getArgument(0)));
45+
}
46+
47+
foreach ($rememberMeHandlerTags as $rememberMeHandlerTag) {
48+
// some custom handlers may be used on multiple firewalls in the same application
49+
if (\in_array($rememberMeHandlerTag['firewall'], $handledFirewalls, true)) {
50+
continue;
51+
}
52+
53+
$rememberMeHandler = clone $realRememberMeHandler;
54+
$rememberMeHandler->addTag(self::HANDLER_TAG, $rememberMeHandlerTag);
55+
$container->setDefinition('security.authenticator.remember_me_handler.'.$rememberMeHandlerTag['firewall'], $rememberMeHandler);
56+
57+
$handledFirewalls[] = $rememberMeHandlerTag['firewall'];
58+
}
59+
}
60+
}
61+
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginLinkFactory.php
+10-4Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,18 +113,24 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal
113113
->replaceArgument(1, $config['lifetime']);
114114
}
115115

116+
$signatureHasherId = 'security.authenticator.login_link_signature_hasher.'.$firewallName;
117+
$container
118+
->setDefinition($signatureHasherId, new ChildDefinition('security.authenticator.abstract_login_link_signature_hasher'))
119+
->replaceArgument(1, $config['signature_properties'])
120+
->replaceArgument(3, $expiredStorageId ? new Reference($expiredStorageId) : null)
121+
->replaceArgument(4, $config['max_uses'] ?? null)
122+
;
123+
116124
$linkerId = 'security.authenticator.login_link_handler.'.$firewallName;
117125
$linkerOptions = [
118126
'route_name' => $config['check_route'],
119127
'lifetime' => $config['lifetime'],
120-
'max_uses' => $config['max_uses'] ?? null,
121128
];
122129
$container
123130
->setDefinition($linkerId, new ChildDefinition('security.authenticator.abstract_login_link_handler'))
124131
->replaceArgument(1, new Reference($userProviderId))
125-
->replaceArgument(3, $config['signature_properties'])
126-
->replaceArgument(5, $linkerOptions)
127-
->replaceArgument(6, $expiredStorageId ? new Reference($expiredStorageId) : null)
132+
->replaceArgument(2, new Reference($signatureHasherId))
133+
->replaceArgument(3, $linkerOptions)
128134
->addTag('security.authenticator.login_linker', ['firewall' => $firewallName])
129135
;
130136

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.