Skip to content

Commit 3713441

Browse files
Merge branch '7.4' into 8.0
* 7.4: Fix .github/expected-missing-return-types.diff deprecate the FQCN properties of PersistentToken and RememberMeDetails [Serializer] Fix unknown type in denormalization errors when union type used in constructor fix tests drop the ability to configure a signer with the IsSignatureValid attribute make createTable() public again [FrameworkBundle] Add support for configuring workflow places with glob patterns matching consts/backed enums replace HTTP response status code constants with their values [JsonPath] Add `Nothing` enum to support special nothing value [HttpKernel] Handle an array vary header in the http cache store for write [Config] Add generics on the config builder API [Console] Fix handling of `\E` in Bash completion
2 parents c086052 + 0293a81 commit 3713441

File tree

6 files changed

+179
-38
lines changed

6 files changed

+179
-38
lines changed

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

RememberMe/RememberMeDetails.php

Lines changed: 75 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,34 +21,95 @@ class RememberMeDetails
2121
{
2222
public const COOKIE_DELIMITER = ':';
2323

24+
private ?string $userFqcn = null;
25+
private string $userIdentifier;
26+
private int $expires;
27+
private string $value;
28+
29+
/**
30+
* @param string $userIdentifier
31+
* @param int $expires
32+
* @param string $value
33+
*/
2434
public function __construct(
25-
private string $userFqcn,
26-
private string $userIdentifier,
27-
private int $expires,
28-
private string $value,
35+
$userIdentifier,
36+
$expires,
37+
$value,
2938
) {
39+
if (\func_num_args() > 3) {
40+
if (\func_num_args() < 5 || func_get_arg(4)) {
41+
trigger_deprecation('symfony/security-http', '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->userFqcn = $userIdentifier;
49+
$userIdentifier = $expires;
50+
$expires = $value;
51+
52+
if (\func_num_args() <= 3) {
53+
throw new \TypeError(\sprintf('Argument 4 passed to "%s()" must be a string, the argument is missing.', __METHOD__));
54+
}
55+
56+
$value = func_get_arg(3);
57+
}
58+
59+
if (!\is_string($userIdentifier)) {
60+
throw new \TypeError(\sprintf('The $userIdentifier argument passed to "%s()" must be a string, "%s" given.', __METHOD__, get_debug_type($userIdentifier)));
61+
}
62+
63+
if (!\is_int($expires) && !preg_match('/^\d+$/', $expires)) {
64+
throw new \TypeError(\sprintf('$The $expires argument passed to "%s()" must be an integer, "%s" given.', __METHOD__, get_debug_type($expires)));
65+
}
66+
67+
if (!\is_string($value)) {
68+
throw new \TypeError(\sprintf('The $value argument passed to "%s()" must be a string, "%s" given.', __METHOD__, get_debug_type($value)));
69+
}
70+
71+
$this->userIdentifier = $userIdentifier;
72+
$this->expires = $expires;
73+
$this->value = $value;
3074
}
3175

3276
public static function fromRawCookie(string $rawCookie): self
3377
{
3478
if (!str_contains($rawCookie, self::COOKIE_DELIMITER)) {
3579
$rawCookie = base64_decode($rawCookie);
3680
}
37-
$cookieParts = explode(self::COOKIE_DELIMITER, $rawCookie, 4);
38-
if (4 !== \count($cookieParts)) {
39-
throw new AuthenticationException('The cookie contains invalid data.');
40-
}
41-
if (false === $cookieParts[1] = base64_decode(strtr($cookieParts[1], '-_~', '+/='), true)) {
42-
throw new AuthenticationException('The user identifier contains a character from outside the base64 alphabet.');
81+
$cookieParts = explode(self::COOKIE_DELIMITER, $rawCookie, 3);
82+
83+
if (isset($cookieParts[1]) && !preg_match('/^\d+$/', $cookieParts[1])) {
84+
// legacy (Symfony < 8.0) cookie format
85+
$cookieParts = explode(self::COOKIE_DELIMITER, $rawCookie, 4);
86+
87+
if (4 !== \count($cookieParts)) {
88+
throw new AuthenticationException('The cookie contains invalid data.');
89+
}
90+
91+
if (false === $cookieParts[1] = base64_decode(strtr($cookieParts[1], '-_~', '+/='), true)) {
92+
throw new AuthenticationException('The user identifier contains a character from outside the base64 alphabet.');
93+
}
94+
95+
$cookieParts[0] = strtr($cookieParts[0], '.', '\\');
96+
$cookieParts[4] = false;
97+
} else {
98+
if (3 !== \count($cookieParts)) {
99+
throw new AuthenticationException('The cookie contains invalid data.');
100+
}
101+
102+
if (false === $cookieParts[0] = base64_decode(strtr($cookieParts[0], '-_~', '+/='), true)) {
103+
throw new AuthenticationException('The user identifier contains a character from outside the base64 alphabet.');
104+
}
43105
}
44-
$cookieParts[0] = strtr($cookieParts[0], '.', '\\');
45106

46107
return new static(...$cookieParts);
47108
}
48109

49110
public static function fromPersistentToken(PersistentToken $token, int $expires): self
50111
{
51-
return new static(method_exists($token, 'getClass') ? $token->getClass(false) : '', $token->getUserIdentifier(), $expires, $token->getSeries().':'.$token->getTokenValue());
112+
return new static(method_exists($token, 'getClass') ? $token->getClass(false) : '', $token->getUserIdentifier(), $expires, $token->getSeries().':'.$token->getTokenValue(), false);
52113
}
53114

54115
public function withValue(string $value): self
@@ -66,7 +127,7 @@ public function getUserFqcn(): string
66127
{
67128
trigger_deprecation('symfony/security-http', '7.4', 'The "%s()" method is deprecated: the user FQCN will be removed from the remember-me cookie in 8.0.', __METHOD__);
68129

69-
return $this->userFqcn;
130+
return $this->userFqcn ?? '';
70131
}
71132

72133
public function getUserIdentifier(): string
@@ -87,6 +148,6 @@ public function getValue(): string
87148
public function toString(): string
88149
{
89150
// $userIdentifier is encoded because it might contain COOKIE_DELIMITER, we assume other values don't
90-
return implode(self::COOKIE_DELIMITER, [strtr($this->userFqcn, '\\', '.'), strtr(base64_encode($this->userIdentifier), '+/=', '-_~'), $this->expires, $this->value]);
151+
return implode(self::COOKIE_DELIMITER, [strtr($this->userFqcn ?? '', '\\', '.'), strtr(base64_encode($this->userIdentifier), '+/=', '-_~'), $this->expires, $this->value]);
91152
}
92153
}

RememberMe/SignatureRememberMeHandler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public function createRememberMeCookie(UserInterface $user): void
4747
$expires = time() + $this->options['lifetime'];
4848
$value = $this->signatureHasher->computeSignatureHash($user, $expires);
4949

50-
$details = new RememberMeDetails($user::class, $user->getUserIdentifier(), $expires, $value);
50+
$details = new RememberMeDetails($user::class, $user->getUserIdentifier(), $expires, $value, false);
5151
$this->createCookie($details);
5252
}
5353

Tests/Authenticator/RememberMeAuthenticatorTest.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,17 @@ public static function provideSupportsData()
6868

6969
public function testAuthenticate()
7070
{
71-
$rememberMeDetails = new RememberMeDetails(InMemoryUser::class, 'wouter', 1, 'secret');
71+
$rememberMeDetails = new RememberMeDetails('wouter', 1, 'secret');
72+
$request = Request::create('/', 'GET', [], ['_remember_me_cookie' => implode(RememberMeDetails::COOKIE_DELIMITER, \array_slice(explode(RememberMeDetails::COOKIE_DELIMITER, $rememberMeDetails->toString()), 1))]);
73+
$passport = $this->authenticator->authenticate($request);
74+
75+
$this->rememberMeHandler->expects($this->once())->method('consumeRememberMeCookie')->with($this->callback(fn ($arg) => $rememberMeDetails == $arg));
76+
$passport->getUser(); // trigger the user loader
77+
}
78+
79+
public function testAuthenticateLegacyCookieFormat()
80+
{
81+
$rememberMeDetails = new RememberMeDetails(InMemoryUser::class, 'wouter', 1, 'secret', false);
7282
$request = Request::create('/', 'GET', [], ['_remember_me_cookie' => $rememberMeDetails->toString()]);
7383
$passport = $this->authenticator->authenticate($request);
7484

0 commit comments

Comments
 (0)