Skip to content

Commit

Permalink
bug #37008 [Security] Fixed AbstractToken::hasUserChanged() (wouterj)
Browse files Browse the repository at this point in the history
This PR was squashed before being merged into the 4.4 branch.

Discussion
----------

[Security] Fixed AbstractToken::hasUserChanged()

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | Fix #36989
| License       | MIT
| Doc PR        | -

This PR completely reverts #35944.

That PR tried to fix a BC break (ref #35941, #35509) introduced by #31177. However, this broke many authentications (ref #36989), as the User is serialized in the session (as hinted by @stof). Many applications don't include the `roles` property in the serialization (at least, the MakerBundle doesn't include it).

In 5.2, we should probably deprecate having different roles in token and user, which fixes the BC breaks all together.

Commits
-------

f297beb [Security] Fixed AbstractToken::hasUserChanged()
  • Loading branch information
nicolas-grekas committed May 30, 2020
2 parents d9506ab + f297beb commit bdb01db
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 3 deletions.
Expand Up @@ -317,10 +317,13 @@ private function hasUserChanged(UserInterface $user): bool
return true;
}

$currentUserRoles = array_map('strval', (array) $this->user->getRoles());
$userRoles = array_map('strval', (array) $user->getRoles());

if (\count($userRoles) !== \count($currentUserRoles) || \count($userRoles) !== \count(array_intersect($userRoles, $currentUserRoles))) {
if ($this instanceof SwitchUserToken) {
$userRoles[] = 'ROLE_PREVIOUS_ADMIN';
}

if (\count($userRoles) !== \count($this->getRoleNames()) || \count($userRoles) !== \count(array_intersect($userRoles, $this->getRoleNames()))) {
return true;
}

Expand Down
Expand Up @@ -238,13 +238,28 @@ public function getUserChangesAdvancedUser()
*/
public function testSetUserDoesNotSetAuthenticatedToFalseWhenUserDoesNotChange($user)
{
$token = new ConcreteToken(['ROLE_FOO']);
$token = new ConcreteToken();
$token->setAuthenticated(true);
$this->assertTrue($token->isAuthenticated());

$token->setUser($user);
$this->assertTrue($token->isAuthenticated());

$token->setUser($user);
$this->assertTrue($token->isAuthenticated());
}

public function testIsUserChangedWhenSerializing()
{
$token = new ConcreteToken(['ROLE_ADMIN']);
$token->setAuthenticated(true);
$this->assertTrue($token->isAuthenticated());

$user = new SerializableUser('wouter', ['ROLE_ADMIN']);
$token->setUser($user);
$this->assertTrue($token->isAuthenticated());

$token = unserialize(serialize($token));
$token->setUser($user);
$this->assertTrue($token->isAuthenticated());
}
Expand All @@ -265,6 +280,56 @@ public function __toString(): string
}
}

class SerializableUser implements UserInterface, \Serializable
{
private $roles;
private $name;

public function __construct($name, array $roles = [])
{
$this->name = $name;
$this->roles = $roles;
}

public function getUsername()
{
return $this->name;
}

public function getPassword()
{
return '***';
}

public function getRoles()
{
if (empty($this->roles)) {
return ['ROLE_USER'];
}

return $this->roles;
}

public function eraseCredentials()
{
}

public function getSalt()
{
return null;
}

public function serialize()
{
return serialize($this->name);
}

public function unserialize($serialized)
{
$this->name = unserialize($serialized);
}
}

class ConcreteToken extends AbstractToken
{
private $credentials = 'credentials_value';
Expand Down

0 comments on commit bdb01db

Please sign in to comment.