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

Skip to content

Commit f6742c6

Browse files
[Security] Support hashing the hashed password using crc32c when putting the user in the session
1 parent 20cdfab commit f6742c6

File tree

5 files changed

+47
-11
lines changed

5 files changed

+47
-11
lines changed

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

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,26 @@
1414
/**
1515
* For users that can be authenticated using a password.
1616
*
17+
* The __serialize/__unserialize() magic methods can be implemented on the user
18+
* class to prevent hashed passwords from being put in the session storage.
19+
* If the password is not stored at all in the session, getPassword() should
20+
* return null after unserialization, and then, changing the user's password
21+
* won't invalidate its sessions.
22+
* In order to invalidate the user sessions while not storing the password hash
23+
* in the session, it's also possible to hash the password hash before
24+
* serializing it; crc32c is the only algorithm supported.
25+
* For example:
26+
*
27+
* public function __serialize(): array
28+
* {
29+
* $data = (array) $this;
30+
* $data["\0".self::class."\0password"] = hash('crc32c', $this->password);
31+
*
32+
* return $data;
33+
* }
34+
*
35+
* Implement EquatableInteface if you need another logic.
36+
*
1737
* @author Robin Chalas <robin.chalas@gmail.com>
1838
* @author Wouter de Jong <wouter@wouterj.nl>
1939
*/
@@ -23,9 +43,6 @@ interface PasswordAuthenticatedUserInterface
2343
* Returns the hashed password used to authenticate the user.
2444
*
2545
* Usually on authentication, a plain-text password will be compared to this value.
26-
*
27-
* The __serialize/__unserialize() magic methods can be implemented on the user
28-
* class to prevent hashed passwords from being put in the session storage.
2946
*/
3047
public function getPassword(): ?string;
3148
}

src/Symfony/Component/Security/Http/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CHANGELOG
77
* Add encryption support to `OidcTokenHandler` (JWE)
88
* Replace `$hideAccountStatusExceptions` argument with `$exposeSecurityErrors` in `AuthenticatorManager` constructor
99
* Add argument `$identifierNormalizer` to `UserBadge::__construct()` to allow normalizing the identifier
10+
* Support hashing the hashed password using crc32c when putting the user in the session
1011

1112
7.2
1213
---

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