feature #31594 [Security] add PasswordEncoderInterface::needsRehash() (nicolas-grekas)
This PR was merged into the 4.4 branch.
Discussion
----------
[Security] add PasswordEncoderInterface::needsRehash()
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | yes
| Tests pass? | yes
| Fixed tickets | -
| License | MIT
| Doc PR | -
Split from #31153, with tests.
Commits
-------
50590dce81
[Security] add PasswordEncoderInterface::needsRehash()
This commit is contained in:
commit
1768c9365c
@ -41,6 +41,11 @@ MonologBridge
|
||||
|
||||
* The `RouteProcessor` has been marked final.
|
||||
|
||||
Security
|
||||
--------
|
||||
|
||||
* Implementations of `PasswordEncoderInterface` and `UserPasswordEncoderInterface` should add a new `needsRehash()` method
|
||||
|
||||
TwigBridge
|
||||
----------
|
||||
|
||||
|
@ -313,6 +313,7 @@ Routing
|
||||
Security
|
||||
--------
|
||||
|
||||
* Implementations of `PasswordEncoderInterface` and `UserPasswordEncoderInterface` must have a new `needsRehash()` method
|
||||
* The `Role` and `SwitchUserRole` classes have been removed.
|
||||
* The `getReachableRoles()` method of the `RoleHierarchy` class has been removed. It has been replaced by the new
|
||||
`getReachableRoleNames()` method.
|
||||
|
@ -1,6 +1,11 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
4.4.0
|
||||
-----
|
||||
|
||||
* Added method `needsRehash()` to `PasswordEncoderInterface` and `UserPasswordEncoderInterface`
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
||||
|
@ -20,6 +20,14 @@ abstract class BasePasswordEncoder implements PasswordEncoderInterface
|
||||
{
|
||||
const MAX_PASSWORD_LENGTH = 4096;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function needsRehash(string $encoded): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Demerges a merge password and salt string.
|
||||
*
|
||||
|
@ -87,4 +87,12 @@ final class NativePasswordEncoder implements PasswordEncoderInterface, SelfSalti
|
||||
|
||||
return \strlen($raw) <= self::MAX_PASSWORD_LENGTH && password_verify($raw, $encoded);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function needsRehash(string $encoded): bool
|
||||
{
|
||||
return password_needs_rehash($encoded, $this->algo, $this->options);
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
* PasswordEncoderInterface is the interface for all encoders.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @method bool needsRehash(string $encoded)
|
||||
*/
|
||||
interface PasswordEncoderInterface
|
||||
{
|
||||
|
@ -99,4 +99,20 @@ final class SodiumPasswordEncoder implements PasswordEncoderInterface, SelfSalti
|
||||
|
||||
throw new LogicException('Libsodium is not available. You should either install the sodium extension, upgrade to PHP 7.2+ or use a different encoder.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function needsRehash(string $encoded): bool
|
||||
{
|
||||
if (\function_exists('sodium_crypto_pwhash_str_needs_rehash')) {
|
||||
return \sodium_crypto_pwhash_str_needs_rehash($encoded, $this->opsLimit, $this->memLimit);
|
||||
}
|
||||
|
||||
if (\extension_loaded('libsodium')) {
|
||||
return \Sodium\crypto_pwhash_str_needs_rehash($encoded, $this->opsLimit, $this->memLimit);
|
||||
}
|
||||
|
||||
throw new LogicException('Libsodium is not available. You should either install the sodium extension, upgrade to PHP 7.2+ or use a different encoder.');
|
||||
}
|
||||
}
|
||||
|
@ -46,4 +46,14 @@ class UserPasswordEncoder implements UserPasswordEncoderInterface
|
||||
|
||||
return $encoder->isPasswordValid($user->getPassword(), $raw, $user->getSalt());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function needsRehash(UserInterface $user, string $encoded): bool
|
||||
{
|
||||
$encoder = $this->encoderFactory->getEncoder($user);
|
||||
|
||||
return method_exists($encoder, 'needsRehash') && $encoder->needsRehash($encoded);
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ use Symfony\Component\Security\Core\User\UserInterface;
|
||||
* UserPasswordEncoderInterface is the interface for the password encoder service.
|
||||
*
|
||||
* @author Ariel Ferrandini <arielferrandini@gmail.com>
|
||||
*
|
||||
* @method bool needsRehash(UserInterface $user, string $encoded)
|
||||
*/
|
||||
interface UserPasswordEncoderInterface
|
||||
{
|
||||
|
@ -60,6 +60,12 @@ class BasePasswordEncoderTest extends TestCase
|
||||
$this->assertFalse($this->invokeIsPasswordTooLong(str_repeat('a', 10)));
|
||||
}
|
||||
|
||||
public function testNeedsRehash()
|
||||
{
|
||||
$encoder = new PasswordEncoder();
|
||||
$this->assertFalse($encoder->needsRehash('foo'));
|
||||
}
|
||||
|
||||
protected function invokeDemergePasswordAndSalt($password)
|
||||
{
|
||||
$encoder = new PasswordEncoder();
|
||||
|
@ -67,4 +67,17 @@ class NativePasswordEncoderTest extends TestCase
|
||||
$this->assertFalse($encoder->isPasswordValid($result, str_repeat('a', 73), 'salt'));
|
||||
$this->assertTrue($encoder->isPasswordValid($result, str_repeat('a', 72), 'salt'));
|
||||
}
|
||||
|
||||
public function testNeedsRehash()
|
||||
{
|
||||
$encoder = new NativePasswordEncoder(4, 11000, 4);
|
||||
|
||||
$this->assertTrue($encoder->needsRehash('dummyhash'));
|
||||
|
||||
$hash = $encoder->encodePassword('foo', 'salt');
|
||||
$this->assertFalse($encoder->needsRehash($hash));
|
||||
|
||||
$encoder = new NativePasswordEncoder(5, 11000, 5);
|
||||
$this->assertTrue($encoder->needsRehash($hash));
|
||||
}
|
||||
}
|
||||
|
@ -60,4 +60,17 @@ class SodiumPasswordEncoderTest extends TestCase
|
||||
$result = $encoder->encodePassword('password', 'salt');
|
||||
$this->assertTrue($encoder->isPasswordValid($result, 'password', 'anotherSalt'));
|
||||
}
|
||||
|
||||
public function testNeedsRehash()
|
||||
{
|
||||
$encoder = new SodiumPasswordEncoder(4, 11000);
|
||||
|
||||
$this->assertTrue($encoder->needsRehash('dummyhash'));
|
||||
|
||||
$hash = $encoder->encodePassword('foo', 'salt');
|
||||
$this->assertFalse($encoder->needsRehash($hash));
|
||||
|
||||
$encoder = new SodiumPasswordEncoder(5, 11000);
|
||||
$this->assertTrue($encoder->needsRehash($hash));
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,10 @@
|
||||
namespace Symfony\Component\Security\Core\Tests\Encoder;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
|
||||
use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder;
|
||||
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoder;
|
||||
use Symfony\Component\Security\Core\User\User;
|
||||
|
||||
class UserPasswordEncoderTest extends TestCase
|
||||
{
|
||||
@ -68,4 +71,23 @@ class UserPasswordEncoderTest extends TestCase
|
||||
$isValid = $passwordEncoder->isPasswordValid($userMock, 'plainPassword');
|
||||
$this->assertTrue($isValid);
|
||||
}
|
||||
|
||||
public function testNeedsRehash()
|
||||
{
|
||||
$user = new User('username', null);
|
||||
$encoder = new NativePasswordEncoder(4, 20000, 4);
|
||||
|
||||
$mockEncoderFactory = $this->getMockBuilder(EncoderFactoryInterface::class)->getMock();
|
||||
$mockEncoderFactory->expects($this->any())
|
||||
->method('getEncoder')
|
||||
->with($user)
|
||||
->will($this->onConsecutiveCalls($encoder, $encoder, new NativePasswordEncoder(5, 20000, 5), $encoder));
|
||||
|
||||
$passwordEncoder = new UserPasswordEncoder($mockEncoderFactory);
|
||||
|
||||
$hash = $passwordEncoder->encodePassword($user, 'foo', 'salt');
|
||||
$this->assertFalse($passwordEncoder->needsRehash($user, $hash));
|
||||
$this->assertTrue($passwordEncoder->needsRehash($user, $hash));
|
||||
$this->assertFalse($passwordEncoder->needsRehash($user, $hash));
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user