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
25 changes: 25 additions & 0 deletions UPGRADE-7.4.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,31 @@ Routing
Security
--------

* Deprecate extending the `RememberMeDetails` class with a constructor expecting the user FQCN

Before:

```php
class CustomRememberMeDetails extends RememberMeDetails
{
public function __construct(string $userFqcn, string $userIdentifier, int $expires, string $value)
{
parent::__construct($userFqcn, $userIdentifier, $expires, $value);
}
}
```

After:

```php
class CustomRememberMeDetails extends RememberMeDetails
{
public function __construct(string $userIdentifier, int $expires, string $value)
{
parent::__construct($userIdentifier, $expires, $value);
}
}
```
* Deprecate callable firewall listeners, extend `AbstractListener` or implement `FirewallListenerInterface` instead
* Deprecate `AbstractListener::__invoke`
* Deprecate `LazyFirewallContext::__invoke()`
Expand Down
25 changes: 25 additions & 0 deletions src/Symfony/Component/Security/Http/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,31 @@ CHANGELOG
7.4
---

* Deprecate extending the `RememberMeDetails` class with a constructor expecting the user FQCN

Before:

```php
class CustomRememberMeDetails extends RememberMeDetails
{
public function __construct(string $userFqcn, string $userIdentifier, int $expires, string $value)
{
parent::__construct($userFqcn, $userIdentifier, $expires, $value);
}
}
```

After:

```php
class CustomRememberMeDetails extends RememberMeDetails
{
public function __construct(string $userIdentifier, int $expires, string $value)
{
parent::__construct($userIdentifier, $expires, $value);
}
}
```
* Add support for union types with `#[CurrentUser]`
* Deprecate callable firewall listeners, extend `AbstractListener` or implement `FirewallListenerInterface` instead
* Deprecate `AbstractListener::__invoke`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,12 @@ public static function fromRawCookie(string $rawCookie): self
throw new AuthenticationException('The user identifier contains a character from outside the base64 alphabet.');
}

if ('' === $cookieParts[0]) {
unset($cookieParts[0]);
} else {
$cookieParts[0] = strtr($cookieParts[0], '.', '\\');
$cookieParts[4] = false;
}

return new static(...$cookieParts);
return new static(...self::collectConstructorArguments(strtr($cookieParts[0], '.', '\\'), $cookieParts[1], $cookieParts[2], $cookieParts[3]));
}

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

public function withValue(string $value): self
Expand Down Expand Up @@ -142,4 +135,21 @@ 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]);
}

private static function collectConstructorArguments(string $userFqcn, string $userIdentifier, int $expires, string $value): array
{
$constructor = new \ReflectionMethod(static::class, '__construct');

if (self::class === $constructor->class) {
return [$userFqcn, $userIdentifier, $expires, $value, false];
}

if (3 < $constructor->getNumberOfRequiredParameters()) {
trigger_deprecation('symfony/security-http', '7.4', 'Extending the "%s" class and overriding the constructor with four required arguments is deprecated. Change the constructor signature to __construct(string $userIdentifier, int $expires, string $value).', self::class);

return [$userFqcn, $userIdentifier, $expires, $value];
}

return [$userIdentifier, $expires, $value];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Security\Http\Tests\RememberMe;

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

class RememberMeDetailsTest extends TestCase
{
public function testFromRawCookie()
{
$rememberMeDetails = RememberMeDetails::fromRawCookie(self::getRememberMeCookieValue());

$this->assertSame(RememberMeDetails::class, $rememberMeDetails::class);
$this->assertSame('wouter', $rememberMeDetails->getUserIdentifier());
$this->assertSame(360, $rememberMeDetails->getExpires());
$this->assertSame('series1:token_value', $rememberMeDetails->getValue());
}

public function testFromRawCookieChildClassWithNewConstructorSignature()
{
$rememberMeDetails = RememberMeDetailsChild::fromRawCookie(self::getRememberMeCookieValue());

$this->assertSame(RememberMeDetailsChild::class, $rememberMeDetails::class);
$this->assertSame('wouter', $rememberMeDetails->getUserIdentifier());
$this->assertSame(360, $rememberMeDetails->getExpires());
$this->assertSame('series1:token_value', $rememberMeDetails->getValue());
}

#[Group('legacy')]
#[IgnoreDeprecations]
public function testFromRawCookieChildClassWithLegacyConstructorSignature()
{
$this->expectUserDeprecationMessage(\sprintf('Since symfony/security-http 7.4: Extending the "%s" class and overriding the constructor with four required arguments is deprecated. Change the constructor signature to __construct(string $userIdentifier, int $expires, string $value).', RememberMeDetails::class));

$rememberMeDetails = RememberMeDetailsChildLegacyConstructorSignature::fromRawCookie(self::getRememberMeCookieValue());

$this->assertSame(RememberMeDetailsChildLegacyConstructorSignature::class, $rememberMeDetails::class);
$this->assertSame('wouter', $rememberMeDetails->getUserIdentifier());
$this->assertSame(360, $rememberMeDetails->getExpires());
$this->assertSame('series1:token_value', $rememberMeDetails->getValue());
}

public function testFromRawCookieChildClassWithoutConstructor()
{
$rememberMeDetails = RememberMeDetailsChildWithoutConstructor::fromRawCookie(self::getRememberMeCookieValue());

$this->assertSame(RememberMeDetailsChildWithoutConstructor::class, $rememberMeDetails::class);
$this->assertSame('wouter', $rememberMeDetails->getUserIdentifier());
$this->assertSame(360, $rememberMeDetails->getExpires());
$this->assertSame('series1:token_value', $rememberMeDetails->getValue());
}

#[Group('legacy')]
#[IgnoreDeprecations]
public function testFromLegacyRawCookie()
{
$rememberMeDetails = RememberMeDetails::fromRawCookie(self::getLegacyRememberMeCookieValue());

$this->assertSame(RememberMeDetails::class, $rememberMeDetails::class);
$this->assertSame(InMemoryUser::class, $rememberMeDetails->getUserFqcn());
$this->assertSame('wouter', $rememberMeDetails->getUserIdentifier());
$this->assertSame(360, $rememberMeDetails->getExpires());
$this->assertSame('series1:token_value', $rememberMeDetails->getValue());
}

#[Group('legacy')]
#[IgnoreDeprecations]
public function testFromLegacyRawCookieChildClassWithNewConstructorSignature()
{
$rememberMeDetails = RememberMeDetailsChild::fromRawCookie(self::getLegacyRememberMeCookieValue());

$this->assertSame(RememberMeDetailsChild::class, $rememberMeDetails::class);
$this->assertSame('', $rememberMeDetails->getUserFqcn());
$this->assertSame('wouter', $rememberMeDetails->getUserIdentifier());
$this->assertSame(360, $rememberMeDetails->getExpires());
$this->assertSame('series1:token_value', $rememberMeDetails->getValue());
}

#[Group('legacy')]
#[IgnoreDeprecations]
public function testFromLegacyRawCookieChildClassWithLegacyConstructorSignature()
{
$this->expectUserDeprecationMessage(\sprintf('Since symfony/security-http 7.4: Extending the "%s" class and overriding the constructor with four required arguments is deprecated. Change the constructor signature to __construct(string $userIdentifier, int $expires, string $value).', RememberMeDetails::class));

$rememberMeDetails = RememberMeDetailsChildLegacyConstructorSignature::fromRawCookie(self::getLegacyRememberMeCookieValue());

$this->assertSame(RememberMeDetailsChildLegacyConstructorSignature::class, $rememberMeDetails::class);
$this->assertSame(InMemoryUser::class, $rememberMeDetails->getUserFqcn());
$this->assertSame('wouter', $rememberMeDetails->getUserIdentifier());
$this->assertSame(360, $rememberMeDetails->getExpires());
$this->assertSame('series1:token_value', $rememberMeDetails->getValue());
}

#[Group('legacy')]
#[IgnoreDeprecations]
public function testFromLegacyRawCookieChildClassWithoutConstructor()
{
$rememberMeDetails = RememberMeDetailsChildWithoutConstructor::fromRawCookie(self::getLegacyRememberMeCookieValue());

$this->assertSame(RememberMeDetailsChildWithoutConstructor::class, $rememberMeDetails::class);
$this->assertSame(InMemoryUser::class, $rememberMeDetails->getUserFqcn());
$this->assertSame('wouter', $rememberMeDetails->getUserIdentifier());
$this->assertSame(360, $rememberMeDetails->getExpires());
$this->assertSame('series1:token_value', $rememberMeDetails->getValue());
}

public function testFromPersistentToken()
{
if (method_exists(PersistentToken::class, 'getClass')) {
$token = new PersistentToken(InMemoryUser::class, 'wouter', 'series1', 'token_value', new \DateTimeImmutable(), false);
} else {
$token = new PersistentToken('wouter', 'series1', 'token_value', new \DateTimeImmutable());
}

$rememberMeDetails = RememberMeDetails::fromPersistentToken($token, 360);

$this->assertSame(RememberMeDetails::class, $rememberMeDetails::class);
$this->assertSame('wouter', $rememberMeDetails->getUserIdentifier());
$this->assertSame(360, $rememberMeDetails->getExpires());
$this->assertSame('series1:token_value', $rememberMeDetails->getValue());
}

public function testFromPersistentTokenChildClassWithNewConstructorSignature()
{
if (method_exists(PersistentToken::class, 'getClass')) {
$token = new PersistentToken(InMemoryUser::class, 'wouter', 'series1', 'token_value', new \DateTimeImmutable(), false);
} else {
$token = new PersistentToken('wouter', 'series1', 'token_value', new \DateTimeImmutable());
}

$rememberMeDetails = RememberMeDetailsChild::fromPersistentToken($token, 360);

$this->assertSame(RememberMeDetailsChild::class, $rememberMeDetails::class);
$this->assertSame('wouter', $rememberMeDetails->getUserIdentifier());
$this->assertSame(360, $rememberMeDetails->getExpires());
$this->assertSame('series1:token_value', $rememberMeDetails->getValue());
}

#[Group('legacy')]
#[IgnoreDeprecations]
public function testFromPersistentTokenChildClassWithLegacyConstructorSignature()
{
$this->expectUserDeprecationMessage(\sprintf('Since symfony/security-http 7.4: Extending the "%s" class and overriding the constructor with four required arguments is deprecated. Change the constructor signature to __construct(string $userIdentifier, int $expires, string $value).', RememberMeDetails::class));

if (method_exists(PersistentToken::class, 'getClass')) {
$token = new PersistentToken(InMemoryUser::class, 'wouter', 'series1', 'token_value', new \DateTimeImmutable(), false);
} else {
$token = new PersistentToken('wouter', 'series1', 'token_value', new \DateTimeImmutable());
}

$rememberMeDetails = RememberMeDetailsChildLegacyConstructorSignature::fromPersistentToken($token, 360);

$this->assertSame(RememberMeDetailsChildLegacyConstructorSignature::class, $rememberMeDetails::class);
$this->assertSame('wouter', $rememberMeDetails->getUserIdentifier());
$this->assertSame(360, $rememberMeDetails->getExpires());
$this->assertSame('series1:token_value', $rememberMeDetails->getValue());
}

public function testFromPersistentTokenChildClassWithoutConstructor()
{
if (method_exists(PersistentToken::class, 'getClass')) {
$token = new PersistentToken(InMemoryUser::class, 'wouter', 'series1', 'token_value', new \DateTimeImmutable(), false);
} else {
$token = new PersistentToken('wouter', 'series1', 'token_value', new \DateTimeImmutable());
}

$rememberMeDetails = RememberMeDetailsChildWithoutConstructor::fromPersistentToken($token, 360);

$this->assertSame(RememberMeDetailsChildWithoutConstructor::class, $rememberMeDetails::class);
$this->assertSame('wouter', $rememberMeDetails->getUserIdentifier());
$this->assertSame(360, $rememberMeDetails->getExpires());
$this->assertSame('series1:token_value', $rememberMeDetails->getValue());
}

private static function getRememberMeCookieValue(): string
{
return base64_encode((new RememberMeDetails('wouter', 360, 'series1:token_value'))->toString());
}

private static function getLegacyRememberMeCookieValue(): string
{
return base64_encode((new RememberMeDetails(InMemoryUser::class, 'wouter', 360, 'series1:token_value', false))->toString());
}
}

class RememberMeDetailsChild extends RememberMeDetails
{
public function __construct(string $userIdentifier, int $expires, string $value)
{
parent::__construct($userIdentifier, $expires, $value);
}
}

class RememberMeDetailsChildLegacyConstructorSignature extends RememberMeDetails
{
public function __construct(string $userFqcn, string $userIdentifier, int $expires, string $value)
{
parent::__construct($userFqcn, $userIdentifier, $expires, $value);
}
}

class RememberMeDetailsChildWithoutConstructor extends RememberMeDetails
{
}
Loading