Skip to content

Commit 5eae900

Browse files
feature #61743 [Security] deprecate the FQCN properties of PersistentToken and RememberMeDetails (xabbuh)
This PR was merged into the 7.4 branch. Discussion ---------- [Security] deprecate the FQCN properties of `PersistentToken` and `RememberMeDetails` | Q | A | ------------- | --- | Branch? | 7.4 | Bug fix? | no | New feature? | no | Deprecations? | yes | Issues | | License | MIT while cleaning up the `8.0` branch from the deprecations introduced in #61654 I wondered if we shouldn't also deprecate passing the user FQCN to `PersistentToken` and `RememberMeDetails` in the first place Commits ------- 0e733fd deprecate the FQCN properties of PersistentToken and RememberMeDetails
2 parents 7a481c5 + 0e733fd commit 5eae900

File tree

13 files changed

+283
-63
lines changed

13 files changed

+283
-63
lines changed

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ public function loadTokenBySeries(string $series): PersistentTokenInterface
6363

6464
[$class, $username, $value, $last_used] = $row;
6565

66-
return new PersistentToken($class, $username, $series, $value, new \DateTimeImmutable($last_used));
66+
if (method_exists(PersistentToken::class, 'getClass')) {
67+
return new PersistentToken($class, $username, $series, $value, new \DateTimeImmutable($last_used), false);
68+
}
69+
70+
return new PersistentToken($username, $series, $value, new \DateTimeImmutable($last_used));
6771
}
6872

6973
public function deleteTokenBySeries(string $series): void
@@ -166,7 +170,14 @@ public function updateExistingToken(PersistentTokenInterface $token, #[\Sensitiv
166170
try {
167171
$this->deleteTokenBySeries($tmpSeries);
168172
$lastUsed = \DateTime::createFromInterface($lastUsed);
169-
$this->createNewToken(new PersistentToken(method_exists($token, 'getClass') ? $token->getClass(false) : '', $token->getUserIdentifier(), $tmpSeries, $token->getTokenValue(), $lastUsed));
173+
174+
if (method_exists(PersistentToken::class, 'getClass')) {
175+
$persistentToken = new PersistentToken($token->getClass(false), $token->getUserIdentifier(), $tmpSeries, $token->getTokenValue(), $lastUsed, false);
176+
} else {
177+
$persistentToken = new PersistentToken($token->getUserIdentifier(), $tmpSeries, $token->getTokenValue(), $lastUsed);
178+
}
179+
180+
$this->createNewToken($persistentToken);
170181

171182
$this->conn->commit();
172183
} catch (\Exception $e) {

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

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ public function testCreateNewToken()
2727
{
2828
$provider = $this->bootstrapProvider();
2929

30-
$token = new PersistentToken('someClass', 'someUser', 'someSeries', 'tokenValue', new \DateTimeImmutable('2013-01-26T18:23:51'));
30+
if (method_exists(PersistentToken::class, 'getClass')) {
31+
$token = new PersistentToken('someClass', 'someUser', 'someSeries', 'tokenValue', new \DateTimeImmutable('2013-01-26T18:23:51'), false);
32+
} else {
33+
$token = new PersistentToken('someUser', 'someSeries', 'tokenValue', new \DateTimeImmutable('2013-01-26T18:23:51'));
34+
}
3135
$provider->createNewToken($token);
3236

3337
$this->assertEquals($provider->loadTokenBySeries('someSeries'), $token);
@@ -45,7 +49,12 @@ public function testUpdateToken()
4549
{
4650
$provider = $this->bootstrapProvider();
4751

48-
$token = new PersistentToken('someClass', 'someUser', 'someSeries', 'tokenValue', new \DateTimeImmutable('2013-01-26T18:23:51'));
52+
if (method_exists(PersistentToken::class, 'getClass')) {
53+
$token = new PersistentToken('someClass', 'someUser', 'someSeries', 'tokenValue', new \DateTimeImmutable('2013-01-26T18:23:51'), false);
54+
} else {
55+
$token = new PersistentToken('someUser', 'someSeries', 'tokenValue', new \DateTimeImmutable('2013-01-26T18:23:51'));
56+
}
57+
4958
$provider->createNewToken($token);
5059
$provider->updateToken('someSeries', 'newValue', $lastUsed = new \DateTime('2014-06-26T22:03:46'));
5160
$token = $provider->loadTokenBySeries('someSeries');
@@ -57,7 +66,11 @@ public function testUpdateToken()
5766
public function testDeleteToken()
5867
{
5968
$provider = $this->bootstrapProvider();
60-
$token = new PersistentToken('someClass', 'someUser', 'someSeries', 'tokenValue', new \DateTimeImmutable('2013-01-26T18:23:51'));
69+
if (method_exists(PersistentToken::class, 'getClass')) {
70+
$token = new PersistentToken('someClass', 'someUser', 'someSeries', 'tokenValue', new \DateTimeImmutable('2013-01-26T18:23:51'), false);
71+
} else {
72+
$token = new PersistentToken('someUser', 'someSeries', 'tokenValue', new \DateTimeImmutable('2013-01-26T18:23:51'));
73+
}
6174
$provider->createNewToken($token);
6275
$provider->deleteTokenBySeries('someSeries');
6376

@@ -74,7 +87,11 @@ public function testVerifyOutdatedTokenAfterParallelRequest()
7487
$newValue = 'newValue';
7588

7689
// setup existing token
77-
$token = new PersistentToken('someClass', 'someUser', $series, $oldValue, new \DateTimeImmutable('2013-01-26T18:23:51'));
90+
if (method_exists(PersistentToken::class, 'getClass')) {
91+
$token = new PersistentToken('someClass', 'someUser', $series, $oldValue, new \DateTimeImmutable('2013-01-26T18:23:51'), false);
92+
} else {
93+
$token = new PersistentToken('someUser', $series, $oldValue, new \DateTimeImmutable('2013-01-26T18:23:51'));
94+
}
7895
$provider->createNewToken($token);
7996

8097
// new request comes in requiring remember-me auth, which updates the token
@@ -99,7 +116,11 @@ public function testVerifyOutdatedTokenAfterParallelRequestFailsAfter60Seconds()
99116
$newValue = 'newValue';
100117

101118
// setup existing token
102-
$token = new PersistentToken('someClass', 'someUser', $series, $oldValue, new \DateTimeImmutable('2013-01-26T18:23:51'));
119+
if (method_exists(PersistentToken::class, 'getClass')) {
120+
$token = new PersistentToken('someClass', 'someUser', $series, $oldValue, new \DateTimeImmutable('2013-01-26T18:23:51'), false);
121+
} else {
122+
$token = new PersistentToken('someUser', $series, $oldValue, new \DateTimeImmutable('2013-01-26T18:23:51'));
123+
}
103124
$provider->createNewToken($token);
104125

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

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ public function updateToken(string $series, #[\SensitiveParameter] string $token
4242
$this->tokens[$series]->getUserIdentifier(),
4343
$series,
4444
$tokenValue,
45-
$lastUsed
45+
$lastUsed,
46+
false
4647
);
4748
$this->tokens[$series] = $token;
4849
}

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

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,61 @@
1818
*/
1919
final class PersistentToken implements PersistentTokenInterface
2020
{
21+
private ?string $class = null;
22+
private string $userIdentifier;
23+
private string $series;
24+
private string $tokenValue;
2125
private \DateTimeImmutable $lastUsed;
2226

27+
/**
28+
* @param string $userIdentifier
29+
* @param string $series
30+
* @param string $tokenValue
31+
* @param \DateTimeInterface $lastUsed
32+
*/
2333
public function __construct(
24-
private string $class,
25-
private string $userIdentifier,
26-
private string $series,
27-
#[\SensitiveParameter] private string $tokenValue,
28-
\DateTimeInterface $lastUsed,
34+
$userIdentifier,
35+
$series,
36+
#[\SensitiveParameter] $tokenValue,
37+
#[\SensitiveParameter] $lastUsed,
2938
) {
30-
if (!$class) {
31-
throw new \InvalidArgumentException('$class must not be empty.');
39+
if (\func_num_args() > 4) {
40+
if (\func_num_args() < 6 || func_get_arg(5)) {
41+
trigger_deprecation('symfony/security-core', '7.4', 'Passing a user FQCN to %s() is deprecated. The user class will be removed from the remember-me cookie in 8.0.', __CLASS__, __NAMESPACE__);
42+
}
43+
44+
if (!\is_string($userIdentifier)) {
45+
throw new \TypeError(\sprintf('Argument 1 passed to "%s()" must be a string, "%s" given.', __METHOD__, get_debug_type($userIdentifier)));
46+
}
47+
48+
$this->class = $userIdentifier;
49+
$userIdentifier = $series;
50+
$series = $tokenValue;
51+
$tokenValue = $lastUsed;
52+
53+
if (\func_num_args() <= 4) {
54+
throw new \TypeError(\sprintf('Argument 5 passed to "%s()" must be an instance of "%s", the argument is missing.', __METHOD__, \DateTimeInterface::class));
55+
}
56+
57+
$lastUsed = func_get_arg(4);
58+
}
59+
60+
if (!\is_string($userIdentifier)) {
61+
throw new \TypeError(\sprintf('The $userIdentifier argument passed to "%s()" must be a string, "%s" given.', __METHOD__, get_debug_type($userIdentifier)));
3262
}
63+
64+
if (!\is_string($series)) {
65+
throw new \TypeError(\sprintf('The $series argument passed to "%s()" must be a string, "%s" given.', __METHOD__, get_debug_type($series)));
66+
}
67+
68+
if (!\is_string($tokenValue)) {
69+
throw new \TypeError(\sprintf('The $tokenValue argument passed to "%s()" must be a string, "%s" given.', __METHOD__, get_debug_type($tokenValue)));
70+
}
71+
72+
if (!$lastUsed instanceof \DateTimeInterface) {
73+
throw new \TypeError(\sprintf('The $lastUsed argument passed to "%s()" must be an instance of "%s", "%s" given.', __METHOD__, \DateTimeInterface::class, get_debug_type($lastUsed)));
74+
}
75+
3376
if ('' === $userIdentifier) {
3477
throw new \InvalidArgumentException('$userIdentifier must not be empty.');
3578
}
@@ -40,6 +83,9 @@ public function __construct(
4083
throw new \InvalidArgumentException('$tokenValue must not be empty.');
4184
}
4285

86+
$this->userIdentifier = $userIdentifier;
87+
$this->series = $series;
88+
$this->tokenValue = $tokenValue;
4389
$this->lastUsed = \DateTimeImmutable::createFromInterface($lastUsed);
4490
}
4591

@@ -52,7 +98,7 @@ public function getClass(bool $triggerDeprecation = true): string
5298
trigger_deprecation('symfony/security-core', '7.4', 'The "%s()" method is deprecated: the user class will be removed from the remember-me cookie in 8.0.', __METHOD__);
5399
}
54100

55-
return $this->class;
101+
return $this->class ?? '';
56102
}
57103

58104
public function getUserIdentifier(): string

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,22 @@ 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 \DateTimeImmutable());
24+
$token = new PersistentToken('class', 'user', 'series1@special:chars=/', 'value', new \DateTimeImmutable(), false);
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 \DateTimeImmutable());
31+
$token = new PersistentToken('class', 'user', 'series1@special:chars=/', 'value', new \DateTimeImmutable(), false);
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 \DateTimeImmutable());
39-
$newToken = new PersistentToken('class', 'user', 'series1@special:chars=/', 'newvalue', new \DateTimeImmutable());
38+
$outdatedToken = new PersistentToken('class', 'user', 'series1@special:chars=/', 'value', new \DateTimeImmutable(), false);
39+
$newToken = new PersistentToken('class', 'user', 'series1@special:chars=/', 'newvalue', new \DateTimeImmutable(), false);
4040
$verifier->updateExistingToken($outdatedToken, 'newvalue', new \DateTimeImmutable());
4141
$this->assertTrue($verifier->verifyToken($newToken, 'value'));
4242
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public function testCreateNewToken()
2222
{
2323
$provider = new InMemoryTokenProvider();
2424

25-
$token = new PersistentToken('foo', 'foo', 'foo', 'foo', new \DateTimeImmutable());
25+
$token = new PersistentToken('foo', 'foo', 'foo', 'foo', new \DateTimeImmutable(), false);
2626
$provider->createNewToken($token);
2727

2828
$this->assertSame($provider->loadTokenBySeries('foo'), $token);
@@ -38,7 +38,7 @@ public function testUpdateToken()
3838
{
3939
$provider = new InMemoryTokenProvider();
4040

41-
$token = new PersistentToken('foo', 'foo', 'foo', 'foo', new \DateTimeImmutable());
41+
$token = new PersistentToken('foo', 'foo', 'foo', 'foo', new \DateTimeImmutable(), false);
4242
$provider->createNewToken($token);
4343
$provider->updateToken('foo', 'newFoo', $lastUsed = new \DateTime());
4444
$token = $provider->loadTokenBySeries('foo');
@@ -51,7 +51,7 @@ public function testDeleteToken()
5151
{
5252
$provider = new InMemoryTokenProvider();
5353

54-
$token = new PersistentToken('foo', 'foo', 'foo', 'foo', new \DateTimeImmutable());
54+
$token = new PersistentToken('foo', 'foo', 'foo', 'foo', new \DateTimeImmutable(), false);
5555
$provider->createNewToken($token);
5656
$provider->deleteTokenBySeries('foo');
5757

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class PersistentTokenTest extends TestCase
2121
public function testConstructor()
2222
{
2323
$lastUsed = new \DateTimeImmutable();
24-
$token = new PersistentToken('fooclass', 'fooname', 'fooseries', 'footokenvalue', $lastUsed);
24+
$token = new PersistentToken('fooname', 'fooseries', 'footokenvalue', $lastUsed);
2525

2626
$this->assertEquals('fooname', $token->getUserIdentifier());
2727
$this->assertEquals('fooseries', $token->getSeries());
@@ -32,7 +32,7 @@ public function testConstructor()
3232
public function testDateTime()
3333
{
3434
$lastUsed = new \DateTime();
35-
$token = new PersistentToken('fooclass', 'fooname', 'fooseries', 'footokenvalue', $lastUsed);
35+
$token = new PersistentToken('fooname', 'fooseries', 'footokenvalue', $lastUsed);
3636

3737
$this->assertEquals($lastUsed, $token->getLastUsed());
3838
}

src/Symfony/Component/Security/Http/RememberMe/PersistentRememberMeHandler.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,11 @@ public function createRememberMeCookie(UserInterface $user): void
5252
$series = random_bytes(66);
5353
$tokenValue = strtr(base64_encode(substr($series, 33)), '+/=', '-_~');
5454
$series = strtr(base64_encode(substr($series, 0, 33)), '+/=', '-_~');
55-
$token = new PersistentToken($user::class, $user->getUserIdentifier(), $series, $tokenValue, new \DateTimeImmutable());
55+
if (method_exists(PersistentToken::class, 'getClass')) {
56+
$token = new PersistentToken($user::class, $user->getUserIdentifier(), $series, $tokenValue, new \DateTimeImmutable(), false);
57+
} else {
58+
$token = new PersistentToken($user->getUserIdentifier(), $series, $tokenValue, new \DateTimeImmutable());
59+
}
5660

5761
$this->tokenProvider->createNewToken($token);
5862
$this->createCookie(RememberMeDetails::fromPersistentToken($token, time() + $this->options['lifetime']));
@@ -92,14 +96,19 @@ public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails): U
9296
method_exists($token, 'getClass') ? $token->getClass(false) : '',
9397
$token->getUserIdentifier(),
9498
$expires,
95-
$token->getLastUsed()->getTimestamp().':'.$series.':'.$tokenValue.':'.(method_exists($token, 'getClass') ? $token->getClass(false) : '')
99+
$token->getLastUsed()->getTimestamp().':'.$series.':'.$tokenValue.':'.(method_exists($token, 'getClass') ? $token->getClass(false) : ''),
100+
false
96101
));
97102
}
98103

99104
public function processRememberMe(RememberMeDetails $rememberMeDetails, UserInterface $user): void
100105
{
101106
[$lastUsed, $series, $tokenValue, $class] = explode(':', $rememberMeDetails->getValue(), 4);
102-
$token = new PersistentToken($class, $rememberMeDetails->getUserIdentifier(), $series, $tokenValue, new \DateTimeImmutable('@'.$lastUsed));
107+
if (method_exists(PersistentToken::class, 'getClass')) {
108+
$token = new PersistentToken($class, $rememberMeDetails->getUserIdentifier(), $series, $tokenValue, new \DateTimeImmutable('@'.$lastUsed), false);
109+
} else {
110+
$token = new PersistentToken($rememberMeDetails->getUserIdentifier(), $series, $tokenValue, new \DateTimeImmutable('@'.$lastUsed));
111+
}
103112

104113
// if a token was regenerated less than a minute ago, there is no need to regenerate it
105114
// if multiple concurrent requests reauthenticate a user we do not want to update the token several times

0 commit comments

Comments
 (0)