Skip to content

Commit 5c3836a

Browse files
wouterjchalasr
authored andcommitted
[Security] Rework the remember me system
1 parent acbf86e commit 5c3836a

File tree

2 files changed

+95
-8
lines changed

2 files changed

+95
-8
lines changed
Lines 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 <[email protected]>
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 <[email protected]>
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+
}

Security/RememberMe/DoctrineTokenProvider.php

Lines 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
}

0 commit comments

Comments
 (0)