Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 46faf11

Browse filesBrowse files
[Security] Support hashing the hashed password using xxh3 when putting the user in the session
1 parent 84d0b6a commit 46faf11
Copy full SHA for 46faf11

File tree

4 files changed

+45
-8
lines changed
Filter options

4 files changed

+45
-8
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Security/Core/User/PasswordAuthenticatedUserInterface.php
+18Lines changed: 18 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Security/Http/Firewall/ContextListener.php
+9-4Lines changed: 9 additions & 4 deletions
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) !== $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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php
+6-2Lines changed: 6 additions & 2 deletions
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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Security/Http/Tests/Fixtures/CustomUser.php
+12-2Lines changed: 12 additions & 2 deletions
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)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.