Skip to content

Commit 31bad80

Browse files
committed
feature #50290 [Security] Make PersistentToken immutable and tell TokenProviderInterface::updateToken() implementations should accept DateTimeInterface (nicolas-grekas)
This PR was merged into the 6.4 branch. Discussion ---------- [Security] Make `PersistentToken` immutable and tell `TokenProviderInterface::updateToken()` implementations should accept `DateTimeInterface` | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | no | New feature? | no | Deprecations? | no | Tickets | Part of #47580 | License | MIT | Doc PR | - Commits ------- 3df345c [Security] Make `PersistentToken` immutable and tell `TokenProviderInterface::updateToken()` implementations should accept `DateTimeInterface`
2 parents 3688c1b + 3df345c commit 31bad80

File tree

15 files changed

+59
-31
lines changed

15 files changed

+59
-31
lines changed

UPGRADE-6.4.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,5 @@ Security
6060
--------
6161

6262
* `UserValueResolver` no longer implements `ArgumentValueResolverInterface`
63+
* Make `PersistentToken` immutable
64+
* Deprecate accepting only `DateTime` for `TokenProviderInterface::updateToken()`, use `DateTimeInterface` instead

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
* `class` varchar(100) NOT NULL,
4141
* `username` varchar(200) NOT NULL
4242
* );
43+
*
44+
* @final since Symfony 6.4
4345
*/
4446
class DoctrineTokenProvider implements TokenProviderInterface, TokenVerifierInterface
4547
{
@@ -60,7 +62,7 @@ public function loadTokenBySeries(string $series): PersistentTokenInterface
6062
$row = $stmt instanceof Result || $stmt instanceof DriverResult ? $stmt->fetchAssociative() : $stmt->fetch(\PDO::FETCH_ASSOC);
6163

6264
if ($row) {
63-
return new PersistentToken($row['class'], $row['username'], $series, $row['value'], new \DateTime($row['last_used']));
65+
return new PersistentToken($row['class'], $row['username'], $series, $row['value'], new \DateTimeImmutable($row['last_used']));
6466
}
6567

6668
throw new TokenNotFoundException('No token found.');
@@ -82,6 +84,8 @@ public function deleteTokenBySeries(string $series)
8284
}
8385

8486
/**
87+
* @param \DateTimeInterface $lastUsed Accepting only DateTime is deprecated since Symfony 6.4
88+
*
8589
* @return void
8690
*/
8791
public function updateToken(string $series, #[\SensitiveParameter] string $tokenValue, \DateTime $lastUsed)

src/Symfony/Bridge/Doctrine/Tests/Security/RememberMe/DoctrineTokenProviderTest.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public function testCreateNewToken()
2929
{
3030
$provider = $this->bootstrapProvider();
3131

32-
$token = new PersistentToken('someClass', 'someUser', 'someSeries', 'tokenValue', new \DateTime('2013-01-26T18:23:51'));
32+
$token = new PersistentToken('someClass', 'someUser', 'someSeries', 'tokenValue', new \DateTimeImmutable('2013-01-26T18:23:51'));
3333
$provider->createNewToken($token);
3434

3535
$this->assertEquals($provider->loadTokenBySeries('someSeries'), $token);
@@ -47,7 +47,7 @@ public function testUpdateToken()
4747
{
4848
$provider = $this->bootstrapProvider();
4949

50-
$token = new PersistentToken('someClass', 'someUser', 'someSeries', 'tokenValue', new \DateTime('2013-01-26T18:23:51'));
50+
$token = new PersistentToken('someClass', 'someUser', 'someSeries', 'tokenValue', new \DateTimeImmutable('2013-01-26T18:23:51'));
5151
$provider->createNewToken($token);
5252
$provider->updateToken('someSeries', 'newValue', $lastUsed = new \DateTime('2014-06-26T22:03:46'));
5353
$token = $provider->loadTokenBySeries('someSeries');
@@ -59,7 +59,7 @@ public function testUpdateToken()
5959
public function testDeleteToken()
6060
{
6161
$provider = $this->bootstrapProvider();
62-
$token = new PersistentToken('someClass', 'someUser', 'someSeries', 'tokenValue', new \DateTime('2013-01-26T18:23:51'));
62+
$token = new PersistentToken('someClass', 'someUser', 'someSeries', 'tokenValue', new \DateTimeImmutable('2013-01-26T18:23:51'));
6363
$provider->createNewToken($token);
6464
$provider->deleteTokenBySeries('someSeries');
6565

@@ -76,7 +76,7 @@ public function testVerifyOutdatedTokenAfterParallelRequest()
7676
$newValue = 'newValue';
7777

7878
// setup existing token
79-
$token = new PersistentToken('someClass', 'someUser', $series, $oldValue, new \DateTime('2013-01-26T18:23:51'));
79+
$token = new PersistentToken('someClass', 'someUser', $series, $oldValue, new \DateTimeImmutable('2013-01-26T18:23:51'));
8080
$provider->createNewToken($token);
8181

8282
// new request comes in requiring remember-me auth, which updates the token
@@ -101,7 +101,7 @@ public function testVerifyOutdatedTokenAfterParallelRequestFailsAfter60Seconds()
101101
$newValue = 'newValue';
102102

103103
// setup existing token
104-
$token = new PersistentToken('someClass', 'someUser', $series, $oldValue, new \DateTime('2013-01-26T18:23:51'));
104+
$token = new PersistentToken('someClass', 'someUser', $series, $oldValue, new \DateTimeImmutable('2013-01-26T18:23:51'));
105105
$provider->createNewToken($token);
106106

107107
// new request comes in requiring remember-me auth, which updates the token

src/Symfony/Bridge/Doctrine/composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"symfony/property-access": "^5.4|^6.0|^7.0",
3838
"symfony/property-info": "^5.4|^6.0|^7.0",
3939
"symfony/proxy-manager-bridge": "^5.4|^6.0|^7.0",
40-
"symfony/security-core": "^6.0|^7.0",
40+
"symfony/security-core": "^6.4|^7.0",
4141
"symfony/stopwatch": "^5.4|^6.0|^7.0",
4242
"symfony/translation": "^5.4|^6.0|^7.0",
4343
"symfony/uid": "^5.4|^6.0|^7.0",
@@ -64,7 +64,7 @@
6464
"symfony/messenger": "<5.4",
6565
"symfony/property-info": "<5.4",
6666
"symfony/security-bundle": "<5.4",
67-
"symfony/security-core": "<6.0",
67+
"symfony/security-core": "<6.4",
6868
"symfony/validator": "<5.4.25|>=6,<6.2.12|>=6.3,<6.3.1"
6969
},
7070
"autoload": {

src/Symfony/Component/Security/Core/Authentication/RememberMe/InMemoryTokenProvider.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
* This class is used for testing purposes, and is not really suited for production.
1818
*
1919
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
20+
*
21+
* @final since Symfony 6.4
2022
*/
2123
class InMemoryTokenProvider implements TokenProviderInterface
2224
{
@@ -32,6 +34,8 @@ public function loadTokenBySeries(string $series): PersistentTokenInterface
3234
}
3335

3436
/**
37+
* @param \DateTimeInterface $lastUsed Accepting only DateTime is deprecated since Symfony 6.4
38+
*
3539
* @return void
3640
*/
3741
public function updateToken(string $series, #[\SensitiveParameter] string $tokenValue, \DateTime $lastUsed)

src/Symfony/Component/Security/Core/Authentication/RememberMe/PersistentToken.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ final class PersistentToken implements PersistentTokenInterface
2222
private string $userIdentifier;
2323
private string $series;
2424
private string $tokenValue;
25-
private \DateTime $lastUsed;
25+
private \DateTimeImmutable $lastUsed;
2626

27-
public function __construct(string $class, string $userIdentifier, string $series, #[\SensitiveParameter] string $tokenValue, \DateTime $lastUsed)
27+
public function __construct(string $class, string $userIdentifier, string $series, #[\SensitiveParameter] string $tokenValue, \DateTimeInterface $lastUsed)
2828
{
2929
if (empty($class)) {
3030
throw new \InvalidArgumentException('$class must not be empty.');
@@ -43,7 +43,7 @@ public function __construct(string $class, string $userIdentifier, string $serie
4343
$this->userIdentifier = $userIdentifier;
4444
$this->series = $series;
4545
$this->tokenValue = $tokenValue;
46-
$this->lastUsed = $lastUsed;
46+
$this->lastUsed = \DateTimeImmutable::createFromInterface($lastUsed);
4747
}
4848

4949
public function getClass(): string
@@ -68,6 +68,6 @@ public function getTokenValue(): string
6868

6969
public function getLastUsed(): \DateTime
7070
{
71-
return $this->lastUsed;
71+
return \DateTime::createFromImmutable($this->lastUsed);
7272
}
7373
}

src/Symfony/Component/Security/Core/Authentication/RememberMe/PersistentTokenInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ public function getTokenValue(): string;
3636

3737
/**
3838
* Returns the time the token was last used.
39+
*
40+
* Each call SHOULD return a new distinct DateTime instance.
3941
*/
4042
public function getLastUsed(): \DateTime;
4143

src/Symfony/Component/Security/Core/Authentication/RememberMe/TokenProviderInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public function deleteTokenBySeries(string $series);
3939
/**
4040
* Updates the token according to this data.
4141
*
42+
* @param \DateTimeInterface $lastUsed Accepting only DateTime is deprecated since Symfony 6.4
43+
*
4244
* @return void
4345
*
4446
* @throws TokenNotFoundException if the token is not found

src/Symfony/Component/Security/Core/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
CHANGELOG
22
=========
33

4+
6.4
5+
---
6+
7+
* Make `PersistentToken` immutable
8+
* Deprecate accepting only `DateTime` for `TokenProviderInterface::updateToken()`, use `DateTimeInterface` instead
9+
410
6.3
511
---
612

src/Symfony/Component/Security/Core/Tests/Authentication/RememberMe/CacheTokenVerifierTest.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,23 @@ class CacheTokenVerifierTest extends TestCase
2121
public function testVerifyCurrentToken()
2222
{
2323
$verifier = new CacheTokenVerifier(new ArrayAdapter());
24-
$token = new PersistentToken('class', 'user', 'series1@special:chars=/', 'value', new \DateTime());
24+
$token = new PersistentToken('class', 'user', 'series1@special:chars=/', 'value', new \DateTimeImmutable());
2525
$this->assertTrue($verifier->verifyToken($token, 'value'));
2626
}
2727

2828
public function testVerifyFailsInvalidToken()
2929
{
3030
$verifier = new CacheTokenVerifier(new ArrayAdapter());
31-
$token = new PersistentToken('class', 'user', 'series1@special:chars=/', 'value', new \DateTime());
31+
$token = new PersistentToken('class', 'user', 'series1@special:chars=/', 'value', new \DateTimeImmutable());
3232
$this->assertFalse($verifier->verifyToken($token, 'wrong-value'));
3333
}
3434

3535
public function testVerifyOutdatedToken()
3636
{
3737
$verifier = new CacheTokenVerifier(new ArrayAdapter());
38-
$outdatedToken = new PersistentToken('class', 'user', 'series1@special:chars=/', 'value', new \DateTime());
39-
$newToken = new PersistentToken('class', 'user', 'series1@special:chars=/', 'newvalue', new \DateTime());
40-
$verifier->updateExistingToken($outdatedToken, 'newvalue', new \DateTime());
38+
$outdatedToken = new PersistentToken('class', 'user', 'series1@special:chars=/', 'value', new \DateTimeImmutable());
39+
$newToken = new PersistentToken('class', 'user', 'series1@special:chars=/', 'newvalue', new \DateTimeImmutable());
40+
$verifier->updateExistingToken($outdatedToken, 'newvalue', new \DateTimeImmutable());
4141
$this->assertTrue($verifier->verifyToken($newToken, 'value'));
4242
}
4343
}

0 commit comments

Comments
 (0)