Skip to content

Commit cc4cc00

Browse files
committed
feature #42423 [Security] Deprecate AnonymousToken, non-UserInterface users, and token credentials (wouterj)
This PR was squashed before being merged into the 5.4 branch. Discussion ---------- [Security] Deprecate AnonymousToken, non-UserInterface users, and token credentials | Q | A | ------------- | --- | Branch? | 5.4 | Bug fix? | no | New feature? | no | Deprecations? | yes | Tickets | Ref #41613, #34909 | License | MIT | Doc PR | - This is a continuation of `@xabbuh`'s experiment in #34909 and `@chalasr`'s work in #42050. This hopefully is the last cleanup of `TokenInterface`: * As tokens now always represent an authenticated user (and no longer e.g. the "username" input of the form), we can finally remove the weird `string|\Stringable` union from `Token::getUser()` and other helper methods and require a user to be an instance of `UserInterface`. * For the same reason, we can also deprecate token credentials. I didn't deprecate `Token::eraseCredentials()` as this is still used to remove credentials from `UserInterface`. * Meanwhile, this also deprecated the `AnonymousToken`, which we forgot in 5.3. This token is not used anymore in the new system (anonymous does no longer exists). This was also the only token in core that didn't fulfill the `UserInterface` requirement for authenticated tokens. Commits ------- 44b843a355 [Security] Deprecate AnonymousToken, non-UserInterface users, and token credentials
2 parents 3b3de64 + d01376f commit cc4cc00

21 files changed

+264
-89
lines changed

Authentication/Token/AbstractToken.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ public function setUser($user)
9999
throw new \InvalidArgumentException('$user must be an instanceof UserInterface, an object implementing a __toString method, or a primitive string.');
100100
}
101101

102+
if (!$user instanceof UserInterface) {
103+
trigger_deprecation('symfony/security-core', '5.4', 'Using an object that is not an instance of "%s" as $user in "%s" is deprecated.', UserInterface::class, static::class);
104+
}
105+
102106
// @deprecated since Symfony 5.4, remove the whole block if/elseif/else block in 6.0
103107
if (1 < \func_num_args() && !func_get_arg(1)) {
104108
// ContextListener checks if the user has changed on its own and calls `setAuthenticated()` subsequently,

Authentication/Token/AnonymousToken.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
* AnonymousToken represents an anonymous token.
1818
*
1919
* @author Fabien Potencier <[email protected]>
20+
*
21+
* @deprecated since 5.4, anonymous is now represented by the absence of a token
2022
*/
2123
class AnonymousToken extends AbstractToken
2224
{
@@ -29,6 +31,8 @@ class AnonymousToken extends AbstractToken
2931
*/
3032
public function __construct(string $secret, $user, array $roles = [])
3133
{
34+
trigger_deprecation('symfony/security-core', '5.4', 'The "%s" class is deprecated.', __CLASS__);
35+
3236
parent::__construct($roles);
3337

3438
$this->secret = $secret;

Authentication/Token/PreAuthenticatedToken.php

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,28 @@ class PreAuthenticatedToken extends AbstractToken
2424
private $firewallName;
2525

2626
/**
27-
* @param string|\Stringable|UserInterface $user
28-
* @param mixed $credentials
29-
* @param string[] $roles
27+
* @param UserInterface $user
28+
* @param string $firewallName
29+
* @param string[] $roles
3030
*/
31-
public function __construct($user, $credentials, string $firewallName, array $roles = [])
31+
public function __construct($user, /*string*/ $firewallName, /*array*/ $roles = [])
3232
{
33+
if (\is_string($roles)) {
34+
trigger_deprecation('symfony/security-core', '5.4', 'Argument $credentials of "%s()" is deprecated.', __METHOD__);
35+
36+
$credentials = $firewallName;
37+
$firewallName = $roles;
38+
$roles = \func_num_args() > 3 ? func_get_arg(3) : [];
39+
}
40+
3341
parent::__construct($roles);
3442

3543
if ('' === $firewallName) {
3644
throw new \InvalidArgumentException('$firewallName must not be empty.');
3745
}
3846

3947
$this->setUser($user);
40-
$this->credentials = $credentials;
48+
$this->credentials = $credentials ?? null;
4149
$this->firewallName = $firewallName;
4250

4351
if ($roles) {
@@ -55,7 +63,7 @@ public function __construct($user, $credentials, string $firewallName, array $ro
5563
public function getProviderKey()
5664
{
5765
if (1 !== \func_num_args() || true !== func_get_arg(0)) {
58-
trigger_deprecation('symfony/security-core', '5.2', 'Method "%s" is deprecated, use "getFirewallName()" instead.', __METHOD__);
66+
trigger_deprecation('symfony/security-core', '5.2', 'Method "%s()" is deprecated, use "getFirewallName()" instead.', __METHOD__);
5967
}
6068

6169
return $this->firewallName;
@@ -71,6 +79,8 @@ public function getFirewallName(): string
7179
*/
7280
public function getCredentials()
7381
{
82+
trigger_deprecation('symfony/security-core', '5.4', 'Method "%s()" is deprecated.', __METHOD__);
83+
7484
return $this->credentials;
7585
}
7686

Authentication/Token/RememberMeToken.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public function setAuthenticated(bool $authenticated)
6969
public function getProviderKey()
7070
{
7171
if (1 !== \func_num_args() || true !== func_get_arg(0)) {
72-
trigger_deprecation('symfony/security-core', '5.2', 'Method "%s" is deprecated, use "getFirewallName()" instead.', __METHOD__);
72+
trigger_deprecation('symfony/security-core', '5.2', 'Method "%s()" is deprecated, use "getFirewallName()" instead.', __METHOD__);
7373
}
7474

7575
return $this->firewallName;
@@ -95,6 +95,8 @@ public function getSecret()
9595
*/
9696
public function getCredentials()
9797
{
98+
trigger_deprecation('symfony/security-core', '5.4', 'Method "%s()" is deprecated.', __METHOD__);
99+
98100
return '';
99101
}
100102

Authentication/Token/SwitchUserToken.php

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Component\Security\Core\Authentication\Token;
1313

14+
use Symfony\Component\Security\Core\User\UserInterface;
15+
1416
/**
1517
* Token representing a user who temporarily impersonates another one.
1618
*
@@ -22,15 +24,29 @@ class SwitchUserToken extends UsernamePasswordToken
2224
private $originatedFromUri;
2325

2426
/**
25-
* @param string|object $user The username (like a nickname, email address, etc.), or a UserInterface instance or an object implementing a __toString method
26-
* @param mixed $credentials This usually is the password of the user
27+
* @param UserInterface $user
2728
* @param string|null $originatedFromUri The URI where was the user at the switch
2829
*
2930
* @throws \InvalidArgumentException
3031
*/
31-
public function __construct($user, $credentials, string $firewallName, array $roles, TokenInterface $originalToken, string $originatedFromUri = null)
32+
public function __construct($user, /*string*/ $firewallName, /*array*/ $roles, /*TokenInterface*/ $originalToken, /*string*/ $originatedFromUri = null)
3233
{
33-
parent::__construct($user, $credentials, $firewallName, $roles);
34+
if (\is_string($roles)) {
35+
// @deprecated since 5.4, deprecation is triggered by UsernamePasswordToken::__construct()
36+
$credentials = $firewallName;
37+
$firewallName = $roles;
38+
$roles = $originalToken;
39+
$originalToken = $originatedFromUri;
40+
$originatedFromUri = \func_num_args() > 5 ? func_get_arg(5) : null;
41+
42+
parent::__construct($user, $credentials, $firewallName, $roles);
43+
} else {
44+
parent::__construct($user, $firewallName, $roles);
45+
}
46+
47+
if (!$originalToken instanceof TokenInterface) {
48+
throw new \TypeError(sprintf('Argument $originalToken of "%s" must be an instance of "%s", "%s" given.', __METHOD__, TokenInterface::class, get_debug_type($originalToken)));
49+
}
3450

3551
$this->originalToken = $originalToken;
3652
$this->originatedFromUri = $originatedFromUri;

Authentication/Token/TokenInterface.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,25 +43,24 @@ public function getRoleNames(): array;
4343
* Returns the user credentials.
4444
*
4545
* @return mixed The user credentials
46+
*
47+
* @deprecated since 5.4
4648
*/
4749
public function getCredentials();
4850

4951
/**
5052
* Returns a user representation.
5153
*
52-
* @return string|\Stringable|UserInterface
54+
* @return UserInterface
5355
*
5456
* @see AbstractToken::setUser()
5557
*/
5658
public function getUser();
5759

5860
/**
59-
* Sets the user in the token.
60-
*
61-
* The user can be a UserInterface instance, or an object implementing
62-
* a __toString method or the username as a regular string.
61+
* Sets the authenticated user in the token.
6362
*
64-
* @param string|\Stringable|UserInterface $user
63+
* @param UserInterface $user
6564
*
6665
* @throws \InvalidArgumentException
6766
*/

Authentication/Token/UsernamePasswordToken.php

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,29 @@ class UsernamePasswordToken extends AbstractToken
2424
private $firewallName;
2525

2626
/**
27-
* @param string|\Stringable|UserInterface $user The username (like a nickname, email address, etc.) or a UserInterface instance
28-
* @param mixed $credentials
29-
* @param string[] $roles
27+
* @param UserInterface $user
28+
* @param string[] $roles
3029
*
3130
* @throws \InvalidArgumentException
3231
*/
33-
public function __construct($user, $credentials, string $firewallName, array $roles = [])
32+
public function __construct($user, /*string*/ $firewallName, /*array*/ $roles = [])
3433
{
34+
if (\is_string($roles)) {
35+
trigger_deprecation('symfony/security-core', '5.4', 'The $credentials argument of "%s" is deprecated.', static::class.'::__construct');
36+
37+
$credentials = $firewallName;
38+
$firewallName = $roles;
39+
$roles = \func_num_args() > 3 ? func_get_arg(3) : [];
40+
}
41+
3542
parent::__construct($roles);
3643

3744
if ('' === $firewallName) {
3845
throw new \InvalidArgumentException('$firewallName must not be empty.');
3946
}
4047

4148
$this->setUser($user);
42-
$this->credentials = $credentials;
49+
$this->credentials = $credentials ?? null;
4350
$this->firewallName = $firewallName;
4451

4552
parent::setAuthenticated(\count($roles) > 0, false);
@@ -62,6 +69,8 @@ public function setAuthenticated(bool $isAuthenticated)
6269
*/
6370
public function getCredentials()
6471
{
72+
trigger_deprecation('symfony/security-core', '5.4', 'Method "%s" is deprecated.', __METHOD__);
73+
6574
return $this->credentials;
6675
}
6776

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ CHANGELOG
44
5.4
55
---
66

7+
* Deprecate `AnonymousToken`, as the related authenticator was deprecated in 5.3
8+
* Deprecate `Token::getCredentials()`, tokens should no longer contain credentials (as they represent authenticated sessions)
9+
* Deprecate returning `string|\Stringable` from `Token::getUser()` (it must return a `UserInterface`)
710
* Deprecate the `$authenticationManager` argument of the `AuthorizationChecker` constructor
811
* Deprecate setting the `$alwaysAuthenticate` argument to `true` and not setting the
912
`$exceptionOnNoToken` argument to `false` of `AuthorizationChecker`

Security.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,12 @@ public function getUser(): ?UserInterface
4242
}
4343

4444
$user = $token->getUser();
45+
// @deprecated since 5.4, $user will always be a UserInterface instance
4546
if (!\is_object($user)) {
4647
return null;
4748
}
4849

50+
// @deprecated since 5.4, $user will always be a UserInterface instance
4951
if (!$user instanceof UserInterface) {
5052
return null;
5153
}

Tests/Authentication/AuthenticationTrustResolverTest.php

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ public function testIsAnonymous()
2626
$this->assertFalse($resolver->isAnonymous($this->getToken()));
2727
$this->assertFalse($resolver->isAnonymous($this->getRememberMeToken()));
2828
$this->assertFalse($resolver->isAnonymous(new FakeCustomToken()));
29-
$this->assertTrue($resolver->isAnonymous(new RealCustomAnonymousToken()));
30-
$this->assertTrue($resolver->isAnonymous($this->getAnonymousToken()));
3129
}
3230

3331
public function testIsRememberMe()
@@ -36,7 +34,6 @@ public function testIsRememberMe()
3634

3735
$this->assertFalse($resolver->isRememberMe(null));
3836
$this->assertFalse($resolver->isRememberMe($this->getToken()));
39-
$this->assertFalse($resolver->isRememberMe($this->getAnonymousToken()));
4037
$this->assertFalse($resolver->isRememberMe(new FakeCustomToken()));
4138
$this->assertTrue($resolver->isRememberMe(new RealCustomRememberMeToken()));
4239
$this->assertTrue($resolver->isRememberMe($this->getRememberMeToken()));
@@ -47,9 +44,7 @@ public function testisFullFledged()
4744
$resolver = new AuthenticationTrustResolver();
4845

4946
$this->assertFalse($resolver->isFullFledged(null));
50-
$this->assertFalse($resolver->isFullFledged($this->getAnonymousToken()));
5147
$this->assertFalse($resolver->isFullFledged($this->getRememberMeToken()));
52-
$this->assertFalse($resolver->isFullFledged(new RealCustomAnonymousToken()));
5348
$this->assertFalse($resolver->isFullFledged(new RealCustomRememberMeToken()));
5449
$this->assertTrue($resolver->isFullFledged($this->getToken()));
5550
$this->assertTrue($resolver->isFullFledged(new FakeCustomToken()));
@@ -62,8 +57,6 @@ public function testIsAnonymousWithClassAsConstructorButStillExtending()
6257
$this->assertFalse($resolver->isAnonymous(null));
6358
$this->assertFalse($resolver->isAnonymous($this->getToken()));
6459
$this->assertFalse($resolver->isAnonymous($this->getRememberMeToken()));
65-
$this->assertTrue($resolver->isAnonymous($this->getAnonymousToken()));
66-
$this->assertTrue($resolver->isAnonymous(new RealCustomAnonymousToken()));
6760
}
6861

6962
public function testIsRememberMeWithClassAsConstructorButStillExtending()
@@ -72,7 +65,6 @@ public function testIsRememberMeWithClassAsConstructorButStillExtending()
7265

7366
$this->assertFalse($resolver->isRememberMe(null));
7467
$this->assertFalse($resolver->isRememberMe($this->getToken()));
75-
$this->assertFalse($resolver->isRememberMe($this->getAnonymousToken()));
7668
$this->assertTrue($resolver->isRememberMe($this->getRememberMeToken()));
7769
$this->assertTrue($resolver->isRememberMe(new RealCustomRememberMeToken()));
7870
}
@@ -82,13 +74,27 @@ public function testisFullFledgedWithClassAsConstructorButStillExtending()
8274
$resolver = $this->getResolver();
8375

8476
$this->assertFalse($resolver->isFullFledged(null));
85-
$this->assertFalse($resolver->isFullFledged($this->getAnonymousToken()));
8677
$this->assertFalse($resolver->isFullFledged($this->getRememberMeToken()));
87-
$this->assertFalse($resolver->isFullFledged(new RealCustomAnonymousToken()));
8878
$this->assertFalse($resolver->isFullFledged(new RealCustomRememberMeToken()));
8979
$this->assertTrue($resolver->isFullFledged($this->getToken()));
9080
}
9181

82+
/**
83+
* @group legacy
84+
*/
85+
public function testLegacy()
86+
{
87+
$resolver = $this->getResolver();
88+
89+
$this->assertTrue($resolver->isAnonymous($this->getAnonymousToken()));
90+
$this->assertTrue($resolver->isAnonymous($this->getRealCustomAnonymousToken()));
91+
92+
$this->assertFalse($resolver->isRememberMe($this->getAnonymousToken()));
93+
94+
$this->assertFalse($resolver->isFullFledged($this->getAnonymousToken()));
95+
$this->assertFalse($resolver->isFullFledged($this->getRealCustomAnonymousToken()));
96+
}
97+
9298
protected function getToken()
9399
{
94100
return $this->createMock(TokenInterface::class);
@@ -99,6 +105,15 @@ protected function getAnonymousToken()
99105
return $this->getMockBuilder(AnonymousToken::class)->setConstructorArgs(['', ''])->getMock();
100106
}
101107

108+
private function getRealCustomAnonymousToken()
109+
{
110+
return new class() extends AnonymousToken {
111+
public function __construct()
112+
{
113+
}
114+
};
115+
}
116+
102117
protected function getRememberMeToken()
103118
{
104119
return $this->getMockBuilder(RememberMeToken::class)->setMethods(['setPersistent'])->disableOriginalConstructor()->getMock();
@@ -192,13 +207,6 @@ public function setAttribute(string $name, $value)
192207
}
193208
}
194209

195-
class RealCustomAnonymousToken extends AnonymousToken
196-
{
197-
public function __construct()
198-
{
199-
}
200-
}
201-
202210
class RealCustomRememberMeToken extends RememberMeToken
203211
{
204212
public function __construct()

0 commit comments

Comments
 (0)