Skip to content
Permalink
Browse files

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
-------

50590dc [Security] add PasswordEncoderInterface::needsRehash()
  • Loading branch information...
chalasr committed Jun 4, 2019
2 parents 0119d21 + 50590dc commit 1768c9365c09faac13d2774ce3e65b6a267fcca6
@@ -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 @@ public function isPasswordValid($encoded, $raw, $salt)
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 @@
* PasswordEncoderInterface is the interface for all encoders.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @method bool needsRehash(string $encoded)
*/
interface PasswordEncoderInterface
{
@@ -99,4 +99,20 @@ public function isPasswordValid($encoded, $raw, $salt)
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 @@ public function isPasswordValid(UserInterface $user, $raw)
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 @@
* 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 @@ public function testIsPasswordTooLong()
$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 @@ public function testCheckPasswordLength()
$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 @@ public function testUserProvidedSaltIsNotUsed()
$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 @@ public function testIsPasswordValid()
$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));
}
}

0 comments on commit 1768c93

Please sign in to comment.
You can’t perform that action at this time.