Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ff71522

Browse files
[Security] add password rehashing capabilities
1 parent 04de8fe commit ff71522

11 files changed

+134
-2
lines changed

src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,8 @@ private function createEncoder($config, ContainerBuilder $container)
558558

559559
// bcrypt encoder
560560
if ('bcrypt' === $config['algorithm']) {
561+
@trigger_error('Configuring an encoder with "bcrypt" as algorithm is deprecated since Symfony 4.4, use "auto" instead.', E_USER_DEPRECATED);
562+
561563
return [
562564
'class' => 'Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder',
563565
'arguments' => [$config['cost'] ?? 13],

src/Symfony/Component/Security/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
CHANGELOG
22
=========
33

4+
4.4.0
5+
-----
6+
7+
* Added `ChainPasswordEncoder`
8+
* Added method `PasswordEncoderInterface::needsRehash()`
9+
* Added method `UserProviderInterface::upgradePassword()`
10+
* Deprecated `BCryptPasswordEncoder`, use `NativePasswordEncoder` instead
11+
412
4.3.0
513
-----
614

src/Symfony/Component/Security/Core/Authentication/Provider/DaoAuthenticationProvider.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,15 @@ class DaoAuthenticationProvider extends UserAuthenticationProvider
3030
{
3131
private $encoderFactory;
3232
private $userProvider;
33+
private $canUpgradePassword;
3334

3435
public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, string $providerKey, EncoderFactoryInterface $encoderFactory, bool $hideUserNotFoundExceptions = true)
3536
{
3637
parent::__construct($userChecker, $providerKey, $hideUserNotFoundExceptions);
3738

3839
$this->encoderFactory = $encoderFactory;
3940
$this->userProvider = $userProvider;
41+
$this->canUpgradePassword = method_exists($userProvider, 'upgradePassword');
4042
}
4143

4244
/**
@@ -54,9 +56,15 @@ protected function checkAuthentication(UserInterface $user, UsernamePasswordToke
5456
throw new BadCredentialsException('The presented password cannot be empty.');
5557
}
5658

57-
if (!$this->encoderFactory->getEncoder($user)->isPasswordValid($user->getPassword(), $presentedPassword, $user->getSalt())) {
59+
$encoder = $this->encoderFactory->getEncoder($user);
60+
61+
if (!$encoder->isPasswordValid($user->getPassword(), $presentedPassword, $user->getSalt())) {
5862
throw new BadCredentialsException('The presented password is invalid.');
5963
}
64+
65+
if ($this->canUpgradePassword && method_exists($encoder, 'needsRehash') && $encoder->needsRehash($user->getPassword())) {
66+
$this->userProvider->upgradePassword($user, $encoder->encodePassword($presentedPassword, $user->getSalt()));
67+
}
6068
}
6169
}
6270

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@
1111

1212
namespace Symfony\Component\Security\Core\Encoder;
1313

14+
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', BCryptPasswordEncoder::class, NativePasswordEncoder::class), E_USER_DEPRECATED);
15+
1416
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
1517

1618
/**
1719
* @author Elnur Abdurrakhimov <[email protected]>
1820
* @author Terje Bråten <[email protected]>
21+
*
22+
* @deprecated since Symfony 4.4, use NativePasswordEncoder instead
1923
*/
2024
class BCryptPasswordEncoder extends BasePasswordEncoder implements SelfSaltingEncoderInterface
2125
{

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

Lines changed: 8 additions & 0 deletions
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
*
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Core\Encoder;
13+
14+
/**
15+
* Hashes passwords using the best available encoder.
16+
* Validates them using a chain of encoders.
17+
*
18+
* @author Nicolas Grekas <[email protected]>
19+
*/
20+
final class ChainPasswordEncoder extends BasePasswordEncoder implements SelfSaltingEncoderInterface
21+
{
22+
private $bestEncoder;
23+
private $extraEncoders;
24+
25+
public function __construct(PasswordEncoderInterface $bestEncoder, PasswordEncoderInterface ...$extraEncoders)
26+
{
27+
$this->bestEncoder = $bestEncoder;
28+
$this->extraEncoder = $extraEncoder;
29+
}
30+
31+
/**
32+
* {@inheritdoc}
33+
*/
34+
public function encodePassword($raw, $salt)
35+
{
36+
return $this->bestEncoder->encodePassword($raw, $salt);
37+
}
38+
39+
/**
40+
* {@inheritdoc}
41+
*/
42+
public function isPasswordValid($encoded, $raw, $salt)
43+
{
44+
if ($this->bestEncoder->isPasswordValid($encoded, $raw, $salt)) {
45+
return true;
46+
}
47+
48+
foreach ($this->extraEncoders as $encoder) {
49+
if ($encoder->isPasswordValid($encoded, $raw, $salt)) {
50+
return true;
51+
}
52+
}
53+
54+
return false;
55+
}
56+
57+
/**
58+
* {@inheritdoc}
59+
*/
60+
public function needsRehash(string $encoded): bool
61+
{
62+
return $this->bestEncoder->needsRehash($encoded);
63+
}
64+
}

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,16 @@ private function createEncoder(array $config)
8585
private function getEncoderConfigFromAlgorithm($config)
8686
{
8787
if ('auto' === $config['algorithm']) {
88-
$config['algorithm'] = SodiumPasswordEncoder::isSupported() ? 'sodium' : 'native';
88+
$encoderChain = [];
89+
foreach ([SodiumPasswordEncoder::isSupported() ? 'sodium' : 'native', 'pbkdf2', $config['hash_algorithm'], 'plaintext'] as $algo) {
90+
$config['algorithm'] = $algo;
91+
$encoderChain[] = $this->createEncoder($config);
92+
}
93+
94+
return [
95+
'class' => ChainPasswordEncoder::class,
96+
'arguments' => $encoderChain,
97+
];
8998
}
9099

91100
switch ($config['algorithm']) {
@@ -106,6 +115,7 @@ private function getEncoderConfigFromAlgorithm($config)
106115
],
107116
];
108117

118+
/* @deprecated since Symfony 4.4 */
109119
case 'bcrypt':
110120
return [
111121
'class' => BCryptPasswordEncoder::class,

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,12 @@ public function isPasswordValid($encoded, $raw, $salt)
8989

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

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

Lines changed: 2 additions & 0 deletions
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 <[email protected]>
20+
*
21+
* @method bool needsRehash(string $encoded)
2022
*/
2123
interface PasswordEncoderInterface
2224
{

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

Lines changed: 16 additions & 0 deletions
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/User/UserProviderInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
* @see UserInterface
3131
*
3232
* @author Fabien Potencier <[email protected]>
33+
*
34+
* @method bool upgradePassword(UserInterface $user, string $newEncodedPassword)
3335
*/
3436
interface UserProviderInterface
3537
{

0 commit comments

Comments
 (0)