Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions UPGRADE-7.4.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ Security
* Deprecate callable firewall listeners, extend `AbstractListener` or implement `FirewallListenerInterface` instead
* Deprecate `AbstractListener::__invoke`
* Deprecate `LazyFirewallContext::__invoke()`
* Deprecate `PersistentTokenInterface::getClass()` and `RememberMeDetails::getUserFqcn()`, the user FQCN will be removed from the remember-me cookie in 8.0

Serializer
----------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@
* `series` char(88) UNIQUE PRIMARY KEY NOT NULL,
* `value` char(88) NOT NULL,
* `lastUsed` datetime NOT NULL,
* `class` varchar(100) NOT NULL,
* `class` varchar(100) DEFAULT '' NOT NULL,
* `username` varchar(200) NOT NULL
* );
*
* (the `class` column is for BC with tables created with before Symfony 8)
*/
final class DoctrineTokenProvider implements TokenProviderInterface, TokenVerifierInterface
{
Expand Down Expand Up @@ -95,7 +97,7 @@ public function createNewToken(PersistentTokenInterface $token): void
{
$sql = 'INSERT INTO rememberme_token (class, username, series, value, lastUsed) VALUES (:class, :username, :series, :value, :lastUsed)';
$paramValues = [
'class' => $token->getClass(),
'class' => method_exists($token, 'getClass') ? $token->getClass(false) : '',
'username' => $token->getUserIdentifier(),
'series' => $token->getSeries(),
'value' => $token->getTokenValue(),
Expand Down Expand Up @@ -164,7 +166,7 @@ public function updateExistingToken(PersistentTokenInterface $token, #[\Sensitiv
try {
$this->deleteTokenBySeries($tmpSeries);
$lastUsed = \DateTime::createFromInterface($lastUsed);
$this->createNewToken(new PersistentToken($token->getClass(), $token->getUserIdentifier(), $tmpSeries, $token->getTokenValue(), $lastUsed));
$this->createNewToken(new PersistentToken(method_exists($token, 'getClass') ? $token->getClass(false) : '', $token->getUserIdentifier(), $tmpSeries, $token->getTokenValue(), $lastUsed));

$this->conn->commit();
} catch (\Exception $e) {
Expand Down Expand Up @@ -195,7 +197,7 @@ private function addTableToSchema(Schema $schema): void
$table->addColumn('series', Types::STRING, ['length' => 88]);
$table->addColumn('value', Types::STRING, ['length' => 88]);
$table->addColumn('lastUsed', Types::DATETIME_IMMUTABLE);
$table->addColumn('class', Types::STRING, ['length' => 100]);
$table->addColumn('class', Types::STRING, ['length' => 100, 'default' => '']);
$table->addColumn('username', Types::STRING, ['length' => 200]);

if (class_exists(PrimaryKeyConstraint::class)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function updateToken(string $series, #[\SensitiveParameter] string $token
}

$token = new PersistentToken(
$this->tokens[$series]->getClass(),
$this->tokens[$series]->getClass(false),
$this->tokens[$series]->getUserIdentifier(),
$series,
$tokenValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,15 @@ public function __construct(
$this->lastUsed = \DateTimeImmutable::createFromInterface($lastUsed);
}

public function getClass(): string
/**
* @deprecated since Symfony 7.4
*/
public function getClass(bool $triggerDeprecation = true): string
{
if ($triggerDeprecation) {
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__);
}

return $this->class;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ interface PersistentTokenInterface
{
/**
* Returns the class of the user.
*
* @deprecated since Symfony 7.4, the user class will be removed from the remember-me cookie in 8.0
*/
public function getClass(): string;

Expand Down
3 changes: 2 additions & 1 deletion src/Symfony/Component/Security/Core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ CHANGELOG
7.4
---

* Add `MermaidDumper` to dump Role Hierarchy graphs in the Mermaid.js flowchart format
* Add `MermaidDumper` to dump Role Hierarchy graphs in the Mermaid.js flowchart format
* Deprecate `PersistentTokenInterface::getClass()`, the user class will be removed from the remember-me cookie in 8.0

7.3
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

namespace Symfony\Component\Security\Core\Tests\Authentication\RememberMe;

use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\IgnoreDeprecations;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken;

Expand All @@ -21,7 +23,6 @@ public function testConstructor()
$lastUsed = new \DateTimeImmutable();
$token = new PersistentToken('fooclass', 'fooname', 'fooseries', 'footokenvalue', $lastUsed);

$this->assertEquals('fooclass', $token->getClass());
$this->assertEquals('fooname', $token->getUserIdentifier());
$this->assertEquals('fooseries', $token->getSeries());
$this->assertEquals('footokenvalue', $token->getTokenValue());
Expand All @@ -35,4 +36,12 @@ public function testDateTime()

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

#[IgnoreDeprecations]
#[Group('legacy')]
public function testClassDeprecation()
{
$token = new PersistentToken('fooclass', 'fooname', 'fooseries', 'footokenvalue', new \DateTimeImmutable());
$this->assertSame('fooclass', $token->getClass());
}
}
1 change: 1 addition & 0 deletions src/Symfony/Component/Security/Http/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ CHANGELOG
* Deprecate `AbstractListener::__invoke`
* Add `$methods` argument to `#[IsGranted]` to restrict validation to specific HTTP methods
* Allow subclassing `#[IsGranted]`
* Deprecate `RememberMeDetails::getUserFqcn()`, the user FQCN will be removed from the remember-me cookie in 8.0

7.3
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,51 +65,51 @@ public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails): U
}

[$series, $tokenValue] = explode(':', $rememberMeDetails->getValue(), 2);
$persistentToken = $this->tokenProvider->loadTokenBySeries($series);
$token = $this->tokenProvider->loadTokenBySeries($series);

if ($persistentToken->getUserIdentifier() !== $rememberMeDetails->getUserIdentifier() || $persistentToken->getClass() !== $rememberMeDetails->getUserFqcn()) {
if ($token->getUserIdentifier() !== $rememberMeDetails->getUserIdentifier()) {
throw new AuthenticationException('The cookie\'s hash is invalid.');
}

// content of $rememberMeDetails is not trustable. this prevents use of this class
unset($rememberMeDetails);

if ($this->tokenVerifier) {
$isTokenValid = $this->tokenVerifier->verifyToken($persistentToken, $tokenValue);
$isTokenValid = $this->tokenVerifier->verifyToken($token, $tokenValue);
} else {
$isTokenValid = hash_equals($persistentToken->getTokenValue(), $tokenValue);
$isTokenValid = hash_equals($token->getTokenValue(), $tokenValue);
}
if (!$isTokenValid) {
throw new CookieTheftException('This token was already used. The account is possibly compromised.');
}

$expires = $persistentToken->getLastUsed()->getTimestamp() + $this->options['lifetime'];
$expires = $token->getLastUsed()->getTimestamp() + $this->options['lifetime'];
if ($expires < time()) {
throw new AuthenticationException('The cookie has expired.');
}

return parent::consumeRememberMeCookie(new RememberMeDetails(
$persistentToken->getClass(),
$persistentToken->getUserIdentifier(),
method_exists($token, 'getClass') ? $token->getClass(false) : '',
$token->getUserIdentifier(),
$expires,
$persistentToken->getLastUsed()->getTimestamp().':'.$series.':'.$tokenValue.':'.$persistentToken->getClass()
$token->getLastUsed()->getTimestamp().':'.$series.':'.$tokenValue.':'.(method_exists($token, 'getClass') ? $token->getClass(false) : '')
));
}

public function processRememberMe(RememberMeDetails $rememberMeDetails, UserInterface $user): void
{
[$lastUsed, $series, $tokenValue, $class] = explode(':', $rememberMeDetails->getValue(), 4);
$persistentToken = new PersistentToken($class, $rememberMeDetails->getUserIdentifier(), $series, $tokenValue, new \DateTimeImmutable('@'.$lastUsed));
$token = new PersistentToken($class, $rememberMeDetails->getUserIdentifier(), $series, $tokenValue, new \DateTimeImmutable('@'.$lastUsed));

// if a token was regenerated less than a minute ago, there is no need to regenerate it
// if multiple concurrent requests reauthenticate a user we do not want to update the token several times
if ($persistentToken->getLastUsed()->getTimestamp() + 60 >= time()) {
if ($token->getLastUsed()->getTimestamp() + 60 >= time()) {
return;
}

$tokenValue = strtr(base64_encode(random_bytes(33)), '+/=', '-_~');
$tokenLastUsed = new \DateTime();
$this->tokenVerifier?->updateExistingToken($persistentToken, $tokenValue, $tokenLastUsed);
$this->tokenVerifier?->updateExistingToken($token, $tokenValue, $tokenLastUsed);
$this->tokenProvider->updateToken($series, $tokenValue, $tokenLastUsed);

$this->createCookie($rememberMeDetails->withValue($series.':'.$tokenValue));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ public static function fromRawCookie(string $rawCookie): self
return new static(...$cookieParts);
}

public static function fromPersistentToken(PersistentToken $persistentToken, int $expires): self
public static function fromPersistentToken(PersistentToken $token, int $expires): self
{
return new static($persistentToken->getClass(), $persistentToken->getUserIdentifier(), $expires, $persistentToken->getSeries().':'.$persistentToken->getTokenValue());
return new static(method_exists($token, 'getClass') ? $token->getClass(false) : '', $token->getUserIdentifier(), $expires, $token->getSeries().':'.$token->getTokenValue());
}

public function withValue(string $value): self
Expand All @@ -59,8 +59,13 @@ public function withValue(string $value): self
return $details;
}

/**
* @deprecated since Symfony 7.4, the user FQCN will be removed from the remember-me cookie in 8.0
*/
public function getUserFqcn(): string
{
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__);

return $this->userFqcn;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,7 @@ public function testCreateRememberMeCookie()
{
$this->tokenProvider->expects($this->once())
->method('createNewToken')
->with($this->callback(fn ($persistentToken) => $persistentToken instanceof PersistentToken
&& 'wouter' === $persistentToken->getUserIdentifier()
&& InMemoryUser::class === $persistentToken->getClass()));
->with($this->callback(fn ($token) => $token instanceof PersistentToken && 'wouter' === $token->getUserIdentifier()));

$this->handler->createRememberMeCookie(new InMemoryUser('wouter', null));
}
Expand Down
Loading