diff --git a/src/Symfony/Component/Security/Http/CHANGELOG.md b/src/Symfony/Component/Security/Http/CHANGELOG.md index 8f6902f29c0e0..ae34c518feda6 100644 --- a/src/Symfony/Component/Security/Http/CHANGELOG.md +++ b/src/Symfony/Component/Security/Http/CHANGELOG.md @@ -3,7 +3,7 @@ CHANGELOG 7.3 --- - + * Rename property userFqcn to userFqcnHash, remove method getUserFqcn, add method getUserFqcnHash. * Add encryption support to `OidcTokenHandler` (JWE) 7.2 diff --git a/src/Symfony/Component/Security/Http/RememberMe/PersistentRememberMeHandler.php b/src/Symfony/Component/Security/Http/RememberMe/PersistentRememberMeHandler.php index 9e92f3cf50507..8ffdfcea6fa58 100644 --- a/src/Symfony/Component/Security/Http/RememberMe/PersistentRememberMeHandler.php +++ b/src/Symfony/Component/Security/Http/RememberMe/PersistentRememberMeHandler.php @@ -67,7 +67,7 @@ public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails): U [$series, $tokenValue] = explode(':', $rememberMeDetails->getValue(), 2); $persistentToken = $this->tokenProvider->loadTokenBySeries($series); - if ($persistentToken->getUserIdentifier() !== $rememberMeDetails->getUserIdentifier() || $persistentToken->getClass() !== $rememberMeDetails->getUserFqcn()) { + if ($persistentToken->getUserIdentifier() !== $rememberMeDetails->getUserIdentifier() || !hash_equals(RememberMeDetails::computeUserFqcnHash($persistentToken->getClass()), $rememberMeDetails->getUserFqcnHash())) { throw new AuthenticationException('The cookie\'s hash is invalid.'); } @@ -89,7 +89,7 @@ public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails): U } return parent::consumeRememberMeCookie(new RememberMeDetails( - $persistentToken->getClass(), + RememberMeDetails::computeUserFqcnHash($persistentToken->getClass()), $persistentToken->getUserIdentifier(), $expires, $persistentToken->getLastUsed()->getTimestamp().':'.$series.':'.$tokenValue.':'.$persistentToken->getClass() diff --git a/src/Symfony/Component/Security/Http/RememberMe/RememberMeDetails.php b/src/Symfony/Component/Security/Http/RememberMe/RememberMeDetails.php index 66f9a8f6731c9..5e45bcfbc166d 100644 --- a/src/Symfony/Component/Security/Http/RememberMe/RememberMeDetails.php +++ b/src/Symfony/Component/Security/Http/RememberMe/RememberMeDetails.php @@ -22,7 +22,7 @@ class RememberMeDetails public const COOKIE_DELIMITER = ':'; public function __construct( - private string $userFqcn, + private string $userFqcnHash, private string $userIdentifier, private int $expires, private string $value, @@ -48,7 +48,12 @@ public static function fromRawCookie(string $rawCookie): self public static function fromPersistentToken(PersistentToken $persistentToken, int $expires): self { - return new static($persistentToken->getClass(), $persistentToken->getUserIdentifier(), $expires, $persistentToken->getSeries().':'.$persistentToken->getTokenValue()); + return new static(self::computeUserFqcnHash($persistentToken->getClass()), $persistentToken->getUserIdentifier(), $expires, $persistentToken->getSeries().':'.$persistentToken->getTokenValue()); + } + + public static function computeUserFqcnHash(string $userFqcn): string + { + return hash('sha256', $userFqcn); } public function withValue(string $value): self @@ -59,9 +64,9 @@ public function withValue(string $value): self return $details; } - public function getUserFqcn(): string + public function getUserFqcnHash(): string { - return $this->userFqcn; + return $this->userFqcnHash; } public function getUserIdentifier(): string @@ -82,6 +87,6 @@ public function getValue(): string public function toString(): string { // $userIdentifier is encoded because it might contain COOKIE_DELIMITER, we assume other values don't - return implode(self::COOKIE_DELIMITER, [strtr($this->userFqcn, '\\', '.'), strtr(base64_encode($this->userIdentifier), '+/=', '-_~'), $this->expires, $this->value]); + return implode(self::COOKIE_DELIMITER, [$this->userFqcnHash, strtr(base64_encode($this->userIdentifier), '+/=', '-_~'), $this->expires, $this->value]); } } diff --git a/src/Symfony/Component/Security/Http/RememberMe/SignatureRememberMeHandler.php b/src/Symfony/Component/Security/Http/RememberMe/SignatureRememberMeHandler.php index a8cfa25ca9cf1..875af41684196 100644 --- a/src/Symfony/Component/Security/Http/RememberMe/SignatureRememberMeHandler.php +++ b/src/Symfony/Component/Security/Http/RememberMe/SignatureRememberMeHandler.php @@ -47,7 +47,7 @@ public function createRememberMeCookie(UserInterface $user): void $expires = time() + $this->options['lifetime']; $value = $this->signatureHasher->computeSignatureHash($user, $expires); - $details = new RememberMeDetails($user::class, $user->getUserIdentifier(), $expires, $value); + $details = new RememberMeDetails(RememberMeDetails::computeUserFqcnHash($user::class), $user->getUserIdentifier(), $expires, $value); $this->createCookie($details); } diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/RememberMeAuthenticatorTest.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/RememberMeAuthenticatorTest.php index fe262c22dd863..7c1490b2fd1f9 100644 --- a/src/Symfony/Component/Security/Http/Tests/Authenticator/RememberMeAuthenticatorTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/RememberMeAuthenticatorTest.php @@ -69,7 +69,7 @@ public static function provideSupportsData() public function testAuthenticate() { - $rememberMeDetails = new RememberMeDetails(InMemoryUser::class, 'wouter', 1, 'secret'); + $rememberMeDetails = new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', 1, 'secret'); $request = Request::create('/', 'GET', [], ['_remember_me_cookie' => $rememberMeDetails->toString()]); $passport = $this->authenticator->authenticate($request); diff --git a/src/Symfony/Component/Security/Http/Tests/RememberMe/PersistentRememberMeHandlerTest.php b/src/Symfony/Component/Security/Http/Tests/RememberMe/PersistentRememberMeHandlerTest.php index 14cc8f684cda9..a228149a339c3 100644 --- a/src/Symfony/Component/Security/Http/Tests/RememberMe/PersistentRememberMeHandlerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/RememberMe/PersistentRememberMeHandlerTest.php @@ -63,7 +63,7 @@ public function testClearRememberMeCookie() ->method('deleteTokenBySeries') ->with('series1'); - $this->request->cookies->set('REMEMBERME', (new RememberMeDetails(InMemoryUser::class, 'wouter', 0, 'series1:tokenvalue'))->toString()); + $this->request->cookies->set('REMEMBERME', (new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', 0, 'series1:tokenvalue'))->toString()); $this->handler->clearRememberMeCookie(); @@ -84,7 +84,7 @@ public function testConsumeRememberMeCookieValid() $this->tokenProvider->expects($this->once())->method('updateToken')->with('series1'); - $rememberMeDetails = new RememberMeDetails(InMemoryUser::class, 'wouter', 360, 'series1:tokenvalue'); + $rememberMeDetails = new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', 360, 'series1:tokenvalue'); $this->handler->consumeRememberMeCookie($rememberMeDetails); // assert that the cookie has been updated with a new base64 encoded token value @@ -110,7 +110,7 @@ public function testConsumeRememberMeCookieInvalidOwner() ->willReturn(new PersistentToken(InMemoryUser::class, 'wouter', 'series1', 'tokenvalue', new \DateTime('-10 min'))) ; - $rememberMeDetails = new RememberMeDetails(InMemoryUser::class, 'jeremy', 360, 'series1:tokenvalue'); + $rememberMeDetails = new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'jeremy', 360, 'series1:tokenvalue'); $this->expectException(AuthenticationException::class); $this->expectExceptionMessage('The cookie\'s hash is invalid.'); @@ -125,7 +125,7 @@ public function testConsumeRememberMeCookieInvalidValue() ->willReturn(new PersistentToken(InMemoryUser::class, 'wouter', 'series1', 'tokenvalue', new \DateTime('-10 min'))) ; - $rememberMeDetails = new RememberMeDetails(InMemoryUser::class, 'wouter', 360, 'series1:tokenvalue:somethingelse'); + $rememberMeDetails = new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', 360, 'series1:tokenvalue:somethingelse'); $this->expectException(AuthenticationException::class); $this->expectExceptionMessage('This token was already used. The account is possibly compromised.'); @@ -151,7 +151,7 @@ public function testConsumeRememberMeCookieValidByValidatorWithoutUpdate() ->willReturn(true) ; - $rememberMeDetails = new RememberMeDetails(InMemoryUser::class, 'wouter', 360, 'series1:oldTokenValue'); + $rememberMeDetails = new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', 360, 'series1:oldTokenValue'); $handler->consumeRememberMeCookie($rememberMeDetails); $this->assertFalse($this->request->attributes->has(ResponseListener::COOKIE_ATTR_NAME)); @@ -168,7 +168,7 @@ public function testConsumeRememberMeCookieInvalidToken() $this->expectException(CookieTheftException::class); - $this->handler->consumeRememberMeCookie(new RememberMeDetails(InMemoryUser::class, 'wouter', 360, 'series1:tokenvalue')); + $this->handler->consumeRememberMeCookie(new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', 360, 'series1:tokenvalue')); } public function testConsumeRememberMeCookieExpired() @@ -183,7 +183,7 @@ public function testConsumeRememberMeCookieExpired() $this->expectException(AuthenticationException::class); $this->expectExceptionMessage('The cookie has expired.'); - $this->handler->consumeRememberMeCookie(new RememberMeDetails(InMemoryUser::class, 'wouter', 360, 'series1:tokenvalue')); + $this->handler->consumeRememberMeCookie(new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', 360, 'series1:tokenvalue')); } public function testBase64EncodedTokens() @@ -196,7 +196,7 @@ public function testBase64EncodedTokens() $this->tokenProvider->expects($this->once())->method('updateToken')->with('series1'); - $rememberMeDetails = new RememberMeDetails(InMemoryUser::class, 'wouter', 360, 'series1:tokenvalue'); + $rememberMeDetails = new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', 360, 'series1:tokenvalue'); $rememberMeDetails = RememberMeDetails::fromRawCookie(base64_encode($rememberMeDetails->toString())); $this->handler->consumeRememberMeCookie($rememberMeDetails); } diff --git a/src/Symfony/Component/Security/Http/Tests/RememberMe/SignatureRememberMeHandlerTest.php b/src/Symfony/Component/Security/Http/Tests/RememberMe/SignatureRememberMeHandlerTest.php index 5a61d3aa6b3c2..6551685eb29f3 100644 --- a/src/Symfony/Component/Security/Http/Tests/RememberMe/SignatureRememberMeHandlerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/RememberMe/SignatureRememberMeHandlerTest.php @@ -57,7 +57,7 @@ public function testCreateRememberMeCookie() /** @var Cookie $cookie */ $cookie = $this->request->attributes->get(ResponseListener::COOKIE_ATTR_NAME); - $this->assertEquals(strtr(InMemoryUser::class, '\\', '.').':d291dGVy:'.$expire.':'.$signature, $cookie->getValue()); + $this->assertEquals(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class).':d291dGVy:'.$expire.':'.$signature, $cookie->getValue()); } public function testClearRememberMeCookie() @@ -77,21 +77,21 @@ public function testConsumeRememberMeCookieValid() $signature = $this->signatureHasher->computeSignatureHash($user, $expire = time() + 3600); $this->userProvider->createUser(new InMemoryUser('wouter', null)); - $rememberMeDetails = new RememberMeDetails(InMemoryUser::class, 'wouter', $expire, $signature); + $rememberMeDetails = new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', $expire, $signature); $this->handler->consumeRememberMeCookie($rememberMeDetails); $this->assertTrue($this->request->attributes->has(ResponseListener::COOKIE_ATTR_NAME)); /** @var Cookie $cookie */ $cookie = $this->request->attributes->get(ResponseListener::COOKIE_ATTR_NAME); - $this->assertNotEquals((new RememberMeDetails(InMemoryUser::class, 'wouter', $expire, $signature))->toString(), $cookie->getValue()); + $this->assertNotEquals((new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', $expire, $signature))->toString(), $cookie->getValue()); } public function testConsumeRememberMeCookieInvalidHash() { $this->expectException(AuthenticationException::class); $this->expectExceptionMessage('The cookie\'s hash is invalid.'); - $this->handler->consumeRememberMeCookie(new RememberMeDetails(InMemoryUser::class, 'wouter', time() + 600, 'badsignature')); + $this->handler->consumeRememberMeCookie(new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', time() + 600, 'badsignature')); } public function testConsumeRememberMeCookieExpired() @@ -101,6 +101,6 @@ public function testConsumeRememberMeCookieExpired() $this->expectException(AuthenticationException::class); $this->expectExceptionMessage('The cookie has expired.'); - $this->handler->consumeRememberMeCookie(new RememberMeDetails(InMemoryUser::class, 'wouter', 360, $signature)); + $this->handler->consumeRememberMeCookie(new RememberMeDetails(RememberMeDetails::computeUserFqcnHash(InMemoryUser::class), 'wouter', 360, $signature)); } }