8000 [Security] Support hashing the hashed password using xxh3 when puttin… · symfony/symfony@b8f77ae · GitHub
[go: up one dir, main page]

Skip to content

Commit b8f77ae

Browse files
[Security] Support hashing the hashed password using xxh3 when putting the user in the session
1 parent 84d0b6a commit b8f77ae

File tree

4 files changed

+45
-8
lines changed

4 files changed

+45
-8
lines changed

src/Symfony/Component/Security/Core/User/PasswordAuthenticatedUserInterface.php

+18
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,24 @@
1414
/**
1515
* For users that can be authenticated using a password.
1616
*
17+
* The __serialize/__unserialize() magic methods can be used on the user class to prevent the password hash from being
18+
* stored in the session. If the password is not stored at all in the session, getPassword() should return null after
19+
* unserialization, and then, changing the user's password won't invalidate its sessions.
20+
* In order to invalidate the user sessions while not storing the password hash in the session, it's also possible to
21+
* hash the password hash before serializing it; xxh3 is the only algorithm supported. For example:
22+
*
23+
* public function __serialize(): array
24+
* {
25+
* return [$this->username, hash('xxh3', $this->password)];
26+
* }
27+
*
28+
* public function __unserialize(array $data): void
29+
* {
30+
* [$this->username, $this->password] = $data;
31+
* }
32+
*
33+
* Implement EquatableInteface if you need another logic.
34+
*
1735
* @author Robin Chalas <robin.chalas@gmail.com>
1836
* @author Wouter de Jong <wouter@wouterj.nl>
1937
*/

src/Symfony/Component/Security/Http/Firewall/ContextListener.php

+9-4
Original file line numberDiff line numberDiff line change
@@ -288,18 +288,23 @@ private static function hasUserChanged(UserInterface $originalUser, TokenInterfa
288288
}
289289

290290
if ($originalUser instanceof PasswordAuthenticatedUserInterface || $refreshedUser instanceof PasswordAuthenticatedUserInterface) {
291-
if (!$originalUser instanceof PasswordAuthenticatedUserInterface
292-
|| !$refreshedUser instanceof PasswordAuthenticatedUserInterface
293-
|| $refreshedUser->getPassword() !== ($originalUser->getPassword() ?? $refreshedUser->getPassword())
291+
if (!$originalUser instanceof PasswordAuthenticatedUserInterface || !$refreshedUser instanceof PasswordAuthenticatedUserInterface) {
292+
return true;
293+
}
294+
295+
if (null !== ($originalPassword = $originalUser->getPassword())
296+
&& ($refreshedPassword = $refreshedUser->getPassword()) !== $originalPassword
297+
&& (16 !== \strlen($originalPassword) || hash('xxh3', $refreshedPassword) !== $originalPassword)
294298
) {
295299
return true;
296300
}
297301

302+
298303
if ($originalUser instanceof LegacyPasswordAuthenticatedUserInterface xor $refreshedUser instanceof LegacyPasswordAuthenticatedUserInterface) {
299304
return true;
300305
}
301306

302-
if ($originalUser instanceof LegacyPasswordAuthenticatedUserInterface && $refreshedUser instanceof LegacyPasswordAuthenticatedUserInterface && $originalUser->getSalt() !== $refreshedUser->getSalt()) {
307+
if ($originalUser instanceof LegacyPasswordAuthenticatedUserInterface && $originalUser->getSalt() !== $refreshedUser->getSalt()) {
303308
return true;
304309
}
305310
}

src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php

+6-2
Original file line numberDiff line numberDiff line change
@@ -352,9 +352,13 @@ public function testOnKernelResponseRemoveListener()
352352
$this->assertEmpty($dispatcher->getListeners());
353353
}
354354

355-
public function testRemovingPasswordFromSessionDoesntInvalidateTheToken()
355+
/**
356+
* @testWith [true]
357+
* [false]
358+
*/
359+
public function testNullOrHashedPasswordInSessionDoesntInvalidateTheToken(bool $hashPassword)
356360
{
357-
$user = new CustomUser('user', ['ROLE_USER'], 'pass');
361+
$user = new CustomUser('user', ['ROLE_USER'], 'pass', $hashPassword);
358362

359363
$userProvider = $this->createMock(UserProviderInterface::class);
360364
$userProvider->expects($this->once())

src/Symfony/Component/Security/Http/Tests/Fixtures/CustomUser.php

+12-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ final class CustomUser implements UserInterface, PasswordAuthenticatedUserInterf
1919
public function __construct(
2020
private string $username,
2121
private array $roles,
22-
private ?string $password = null,
22+
private ?string $password,
23+
private bool $hashPassword,
2324
) {
2425
}
2526

@@ -44,6 +45,15 @@ public function eraseCredentials(): void
4445

4546
public function __serialize(): array
4647
{
47-
return [\sprintf("\0%s\0username", self::class) => $this->username];
48+
$data = (array) $this;
49+
$passwordKey = \sprintf("\0%s\0password", self::class);
50+
51+
if ($this->hashPassword) {
52+
$data[$passwordKey] = hash('xxh3', $this->password);
53+
} else {
54+
unset($data[$passwordKey]);
55+
}
56+
57+
return $data;
4858
}
4959
}

0 commit comments

Comments
 (0)
0