Skip to content

Commit 7daff22

Browse files
[Security] add PasswordEncoderInterface::needsRehash()
1 parent c315767 commit 7daff22

11 files changed

+105
-0
lines changed

src/Symfony/Component/Security/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
4.4.0
5+
-----
6+
7+
* Added methods `PasswordEncoderInterface::needsRehash()` and `UserPasswordEncoderInterface::needsRehash()`
8+
49
4.3.0
510
-----
611

src/Symfony/Component/Security/Core/Encoder/BasePasswordEncoder.php

+8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ abstract class BasePasswordEncoder implements PasswordEncoderInterface
2020
{
2121
const MAX_PASSWORD_LENGTH = 4096;
2222

23+
/**
24+
* {@inheritdoc}
25+
*/
26+
public function needsRehash(string $encoded): bool
27+
{
28+
return false;
29+
}
30+
2331
/**
2432
* Demerges a merge password and salt string.
2533
*

src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php

+8
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,12 @@ public function isPasswordValid($encoded, $raw, $salt)
8787

8888
return \strlen($raw) <= self::MAX_PASSWORD_LENGTH && password_verify($raw, $encoded);
8989
}
90+
91+
/**
92+
* {@inheritdoc}
93+
*/
94+
public function needsRehash(string $encoded): bool
95+
{
96+
return password_needs_rehash($encoded, $this->algo, $this->options);
97+
}
9098
}

src/Symfony/Component/Security/Core/Encoder/PasswordEncoderInterface.php

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
* PasswordEncoderInterface is the interface for all encoders.
1818
*
1919
* @author Fabien Potencier <fabien@symfony.com>
20+
*
21+
* @method bool needsRehash(string $encoded)
2022
*/
2123
interface PasswordEncoderInterface
2224
{

src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php

+16
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,20 @@ public function isPasswordValid($encoded, $raw, $salt)
9494

9595
throw new LogicException('Libsodium is not available. You should either install the sodium extension, upgrade to PHP 7.2+ or use a different encoder.');
9696
}
97+
98+
/**
99+
* {@inheritdoc}
100+
*/
101+
public function needsRehash(string $encoded): bool
102+
{
103+
if (\function_exists('sodium_crypto_pwhash_str_needs_rehash')) {
104+
return \sodium_crypto_pwhash_str_needs_rehash($encoded, $this->opsLimit, $this->memLimit);
105+
}
106+
107+
if (\extension_loaded('libsodium')) {
108+
return \Sodium\crypto_pwhash_str_needs_rehash($encoded, $this->opsLimit, $this->memLimit);
109+
}
110+
111+
throw new LogicException('Libsodium is not available. You should either install the sodium extension, upgrade to PHP 7.2+ or use a different encoder.');
112+
}
97113
}

src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoder.php

+10
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,14 @@ public function isPasswordValid(UserInterface $user, $raw)
4646

4747
return $encoder->isPasswordValid($user->getPassword(), $raw, $user->getSalt());
4848
}
49+
50+
/**
51+
* {@inheritdoc}
52+
*/
53+
public function needsRehash(UserInterface $user, string $encoded): bool
54+
{
55+
$encoder = $this->encoderFactory->getEncoder($user);
56+
57+
return method_exists($encoder, 'needsRehash') && $encoder->needsRehash($encoded);
58+
}
4959
}

src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoderInterface.php

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
* UserPasswordEncoderInterface is the interface for the password encoder service.
1818
*
1919
* @author Ariel Ferrandini <arielferrandini@gmail.com>
20+
*
21+
* @method bool needsRehash(UserInterface $user, string $encoded)
2022
*/
2123
interface UserPasswordEncoderInterface
2224
{

src/Symfony/Component/Security/Core/Tests/Encoder/BasePasswordEncoderTest.php

+6
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ public function testIsPasswordTooLong()
6060
$this->assertFalse($this->invokeIsPasswordTooLong(str_repeat('a', 10)));
6161
}
6262

63+
public function testNeedsRehash()
64+
{
65+
$encoder = new PasswordEncoder();
66+
$this->assertFalse($encoder->needsRehash('foo'));
67+
}
68+
6369
protected function invokeDemergePasswordAndSalt($password)
6470
{
6571
$encoder = new PasswordEncoder();

src/Symfony/Component/Security/Core/Tests/Encoder/NativePasswordEncoderTest.php

+13
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,17 @@ public function testCheckPasswordLength()
6767
$this->assertFalse($encoder->isPasswordValid($result, str_repeat('a', 73), 'salt'));
6868
$this->assertTrue($encoder->isPasswordValid($result, str_repeat('a', 72), 'salt'));
6969
}
70+
71+
public function testNeedsRehash()
72+
{
73+
$encoder = new NativePasswordEncoder(4, 11000, 4);
74+
75+
$this->assertTrue($encoder->needsRehash('dummyhash'));
76+
77+
$hash = $encoder->encodePassword('foo', 'salt');
78+
$this->assertFalse($encoder->needsRehash($hash));
79+
80+
$encoder = new NativePasswordEncoder(5, 11000, 5);
81+
$this->assertTrue($encoder->needsRehash($hash));
82+
}
7083
}

src/Symfony/Component/Security/Core/Tests/Encoder/SodiumPasswordEncoderTest.php

+13
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,17 @@ public function testUserProvidedSaltIsNotUsed()
5454
$result = $encoder->encodePassword('password', 'salt');
5555
$this->assertTrue($encoder->isPasswordValid($result, 'password', 'anotherSalt'));
5656
}
57+
58+
public function testNeedsRehash()
59+
{
60+
$encoder = new SodiumPasswordEncoder(4, 11000);
61+
62+
$this->assertTrue($encoder->needsRehash('dummyhash'));
63+
64+
$hash = $encoder->encodePassword('foo', 'salt');
65+
$this->assertFalse($encoder->needsRehash($hash));
66+
67+
$encoder = new SodiumPasswordEncoder(5, 11000);
68+
$this->assertTrue($encoder->needsRehash($hash));
69+
}
5770
}

src/Symfony/Component/Security/Core/Tests/Encoder/UserPasswordEncoderTest.php

+22
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
namespace Symfony\Component\Security\Core\Tests\Encoder;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
16+
use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder;
1517
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoder;
18+
use Symfony\Component\Security\Core\User\User;
1619

1720
class UserPasswordEncoderTest extends TestCase
1821
{
@@ -68,4 +71,23 @@ public function testIsPasswordValid()
6871
$isValid = $passwordEncoder->isPasswordValid($userMock, 'plainPassword');
6972
$this->assertTrue($isValid);
7073
}
74+
75+
public function testNeedsRehash()
76+
{
77+
$user = new User('username', null);
78+
$encoder = new NativePasswordEncoder(4, 20000, 4);
79+
80+
$mockEncoderFactory = $this->getMockBuilder(EncoderFactoryInterface::class)->getMock();
81+
$mockEncoderFactory->expects($this->any())
82+
->method('getEncoder')
83+
->with($user)
84+
->will($this->onConsecutiveCalls($encoder, $encoder, new NativePasswordEncoder(5, 20000, 5), $encoder));
85+
86+
$passwordEncoder = new UserPasswordEncoder($mockEncoderFactory);
87+
88+
$hash = $passwordEncoder->encodePassword($user, 'foo', 'salt');
89+
$this->assertFalse($passwordEncoder->needsRehash($user, $hash));
90+
$this->assertTrue($passwordEncoder->needsRehash($user, $hash));
91+
$this->assertFalse($passwordEncoder->needsRehash($user, $hash));
92+
}
7193
}

0 commit comments

Comments
 (0)