8000 [Security] Support hashing the hashed password using crc32c when putt… · symfony/symfony@9cab9e1 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9cab9e1

Browse files
[Security] Support hashing the hashed password using crc32c when putting the user in the session
1 parent 0134078 commit 9cab9e1

File tree

4 files changed

+44
-8
lines changed

4 files changed

+44
-8
lines changed

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

Lines 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; crc32c is the only algorithm supported. For example:
22+
*
23+
* public function __serialize(): array
24+
* {
25+
* return [$this->username, hash('crc32c', $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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -292,9 +292,13 @@ private static function hasUserChanged(UserInterface $originalUser, TokenInterfa
292292
}
293293

294294
if ($originalUser instanceof PasswordAuthenticatedUserInterface || $refreshedUser instanceof PasswordAuthenticatedUserInterface) {
295-
if (!$originalUser instanceof PasswordAuthenticatedUserInterface
296-
|| !$refreshedUser instanceof PasswordAuthenticatedUserInterface
297-
|| $refreshedUser->getPassword() !== ($originalUser->getPassword() ?? $refreshedUser->getPassword())
295+
if (!$originalUser instanceof PasswordAuthenticatedUserInterface || !$refreshedUser instanceof PasswordAuthenticatedUserInterface) {
296+
return true;
297+
}
298+
299+
if (null !== ($originalPassword = $originalUser->getPassword())
300+
&& ($refreshedPassword = $refreshedUser->getPassword()) !== $originalPassword
301+
&& (8 !== \strlen($originalPassword) || hash('crc32c', $refreshedPassword ?? $originalPassword) !== $originalPassword)
298302
) {
299303
return true;
300304
}
@@ -303,7 +307,7 @@ private static function hasUserChanged(UserInterface $originalUser, TokenInterfa
303307
return true;
304308
}
305309

306-
if ($originalUser instanceof LegacyPasswordAuthenticatedUserInterface && $refreshedUser instanceof LegacyPasswordAuthenticatedUserInterface && $originalUser->getSalt() !== $refreshedUser->getSalt()) {
310+
if ($originalUser instanceof LegacyPasswordAuthenticatedUserInterface && $originalUser->getSalt() !== $refreshedUser->getSalt()) {
307311
return true;
308312
}
309313
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,9 +377,13 @@ public function testOnKernelResponseRemoveListener()
377377
$this->assertEmpty($dispatcher->getListeners());
378378
}
379379

380-
public function testRemovingPasswordFromSessionDoesntInvalidateTheToken()
380+
/**
381+
* @testWith [true]
382+
* [false]
383+
*/
384+
public function testNullOrHashedPasswordInSessionDoesntInvalidateTheToken(bool $hashPassword)
381385
{
382-
$user = new CustomUser('user', ['ROLE_USER'], 'pass');
386+
$user = new CustomUser('user', ['ROLE_USER'], 'pass', $hashPassword);
383387

384388
$userProvider = $this->createMock(UserProviderInterface::class);
385389
$userProvider->expects($this->once())

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

Lines 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('crc32c', $this->password);
53+
} else {
54+
unset($data[$passwordKey]);
55+
}
56+
57+
return $data;
4858
}
4959
}

0 commit comments

Comments
 (0)
0