Skip to content

Commit 5899953

Browse files
committed
[Security] Extract password hashing from security-core - using the right naming
1 parent 5a5eb86 commit 5899953

31 files changed

+306
-372
lines changed

Authentication/AuthenticationProviderManager.php

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

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

14+
use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException;
1415
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
1516
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
1617
use Symfony\Component\Security\Core\AuthenticationEvents;
1718
use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent;
1819
use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
1920
use Symfony\Component\Security\Core\Exception\AccountStatusException;
2021
use Symfony\Component\Security\Core\Exception\AuthenticationException;
22+
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
2123
use Symfony\Component\Security\Core\Exception\ProviderNotFoundException;
2224
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
2325

@@ -89,6 +91,8 @@ public function authenticate(TokenInterface $token)
8991
break;
9092
} catch (AuthenticationException $e) {
9193
$lastException = $e;
94+
} catch (InvalidPasswordException $e) {
95+
$lastException = new BadCredentialsException('Bad credentials.', 0, $e);
9296
}
9397
}
9498

Authentication/Provider/DaoAuthenticationProvider.php

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Symfony\Component\Security\Core\User\UserCheckerInterface;
2121
use Symfony\Component\Security\Core\User\UserInterface;
2222
use Symfony\Component\Security\Core\User\UserProviderInterface;
23+
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface;
2324

2425
/**
2526
* DaoAuthenticationProvider uses a UserProviderInterface to retrieve the user
@@ -29,14 +30,21 @@
2930
*/
3031
class DaoAuthenticationProvider extends UserAuthenticationProvider
3132
{
32-
private $encoderFactory;
33+
private $hasherFactory;
3334
private $userProvider;
3435

35-
public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, string $providerKey, EncoderFactoryInterface $encoderFactory, bool $hideUserNotFoundExceptions = true)
36+
/**
37+
* @param PasswordHasherFactoryInterface $hasherFactory
38+
*/
39+
public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, string $providerKey, $hasherFactory, bool $hideUserNotFoundExceptions = true)
3640
{
3741
parent::__construct($userChecker, $providerKey, $hideUserNotFoundExceptions);
3842

39-
$this->encoderFactory = $encoderFactory;
43+
if ($hasherFactory instanceof EncoderFactoryInterface) {
44+
trigger_deprecation('symfony/security-core', '5.3', 'Passing a "%s" instance to the "%s" constructor is deprecated, use "%s" instead.', EncoderFactoryInterface::class, __CLASS__, PasswordHasherFactoryInterface::class);
45+
}
46+
47+
$this->hasherFactory = $hasherFactory;
4048
$this->userProvider = $userProvider;
4149
}
4250

@@ -59,14 +67,29 @@ protected function checkAuthentication(UserInterface $user, UsernamePasswordToke
5967
throw new BadCredentialsException('The presented password is invalid.');
6068
}
6169

62-
$encoder = $this->encoderFactory->getEncoder($user);
70+
// deprecated since Symfony 5.3
71+
if ($this->hasherFactory instanceof EncoderFactoryInterface) {
72+
$encoder = $this->hasherFactory->getEncoder($user);
73+
74+
if (!$encoder->isPasswordValid($user->getPassword(), $presentedPassword, $user->getSalt())) {
75+
throw new BadCredentialsException('The presented password is invalid.');
76+
}
77+
78+
if ($this->userProvider instanceof PasswordUpgraderInterface && method_exists($encoder, 'needsRehash') && $encoder->needsRehash($user->getPassword())) {
79+
$this->userProvider->upgradePassword($user, $encoder->encodePassword($presentedPassword, $user->getSalt()));
80+
}
81+
82+
return;
83+
}
84+
85+
$hasher = $this->hasherFactory->getPasswordHasher($user);
6386

64-
if (!$encoder->isPasswordValid($user->getPassword(), $presentedPassword, $user->getSalt())) {
87+
if (!$hasher->verify($user->getPassword(), $presentedPassword, $user->getSalt())) {
6588
throw new BadCredentialsException('The presented password is invalid.');
6689
}
6790

68-
if ($this->userProvider instanceof PasswordUpgraderInterface && method_exists($encoder, 'needsRehash') && $encoder->needsRehash($user->getPassword())) {
69-
$this->userProvider->upgradePassword($user, $encoder->encodePassword($presentedPassword, $user->getSalt()));
91+
if ($this->userProvider instanceof PasswordUpgraderInterface && $hasher->needsRehash($user->getPassword())) {
92+
$this->userProvider->upgradePassword($user, $hasher->hash($presentedPassword, $user->getSalt()));
7093
}
7194
}
7295
}

Encoder/BasePasswordEncoder.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,16 @@
1111

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

14+
trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', BasePasswordEncoder::class, CheckPasswordLengthTrait::class));
15+
16+
use Symfony\Component\PasswordHasher\Hasher\CheckPasswordLengthTrait;
17+
1418
/**
1519
* BasePasswordEncoder is the base class for all password encoders.
1620
*
1721
* @author Fabien Potencier <[email protected]>
22+
*
23+
* @deprecated since Symfony 5.3, use CheckPasswordLengthTrait instead
1824
*/
1925
abstract class BasePasswordEncoder implements PasswordEncoderInterface
2026
{

Encoder/EncoderAwareInterface.php

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

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

14+
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface;
15+
1416
/**
1517
* @author Christophe Coevoet <[email protected]>
18+
*
19+
* @deprecated since Symfony 5.3, use {@link PasswordHasherAwareInterface} instead.
1620
*/
1721
interface EncoderAwareInterface
1822
{

Encoder/EncoderFactory.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,18 @@
1111

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

14+
trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', EncoderFactory::class, PasswordHasherFactory::class));
15+
1416
use Symfony\Component\Security\Core\Exception\LogicException;
17+
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface;
18+
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory;
1519

1620
/**
1721
* A generic encoder factory implementation.
1822
*
1923
* @author Johannes M. Schmitt <[email protected]>
24+
*
25+
* @deprecated since Symfony 5.3, use {@link PasswordHasherFactory} instead
2026
*/
2127
class EncoderFactory implements EncoderFactoryInterface
2228
{
@@ -34,7 +40,7 @@ public function getEncoder($user)
3440
{
3541
$encoderKey = null;
3642

37-
if ($user instanceof EncoderAwareInterface && (null !== $encoderName = $user->getEncoderName())) {
43+
if (($user instanceof PasswordHasherAwareInterface && null !== $encoderName = $user->getPasswordHasherName()) || ($user instanceof EncoderAwareInterface && null !== $encoderName = $user->getEncoderName())) {
3844
if (!\array_key_exists($encoderName, $this->encoders)) {
3945
throw new \RuntimeException(sprintf('The encoder "%s" was not configured.', $encoderName));
4046
}

Encoder/EncoderFactoryInterface.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,17 @@
1111

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

14+
trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', EncoderFactoryInterface::class, PasswordHasherFactoryInterface::class));
15+
1416
use Symfony\Component\Security\Core\User\UserInterface;
17+
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface;
1518

1619
/**
1720
* EncoderFactoryInterface to support different encoders for different accounts.
1821
*
1922
* @author Johannes M. Schmitt <[email protected]>
23+
*
24+
* @deprecated since Symfony 5.3, use {@link PasswordHasherFactoryInterface} instead
2025
*/
2126
interface EncoderFactoryInterface
2227
{

Encoder/LegacyEncoderTrait.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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+
use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException;
15+
use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface;
16+
use Symfony\Component\PasswordHasher\PasswordHasherInterface;
17+
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
18+
19+
/**
20+
* @internal
21+
*/
22+
trait LegacyEncoderTrait
23+
{
24+
/**
25+
* @var PasswordHasherInterface|LegacyPasswordHasherInterface
26+
*/
27+
private $hasher;
28+
29+
/**
30+
* {@inheritdoc}
31+
*/
32+
public function encodePassword(string $raw, ?string $salt): string
33+
{
34+
try {
35+
return $this->hasher->hash($raw, $salt);
36+
} catch (InvalidPasswordException $e) {
37+
throw new BadCredentialsException('Bad credentials.');
38+
}
39+
}
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
public function isPasswordValid(string $encoded, string $raw, ?string $salt): bool
45+
{
46+
return $this->hasher->verify($encoded, $raw, $salt);
47+
}
48+
49+
/**
50+
* {@inheritdoc}
51+
*/
52+
public function needsRehash(string $encoded): bool
53+
{
54+
return $this->hasher->needsRehash($encoded);
55+
}
56+
}

Encoder/MessageDigestPasswordEncoder.php

Lines changed: 7 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,20 @@
1111

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

14-
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
14+
trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', MessageDigestPasswordEncoder::class, MessageDigestPasswordHasher::class));
15+
16+
use Symfony\Component\PasswordHasher\Hasher\MessageDigestPasswordHasher;
1517

1618
/**
1719
* MessageDigestPasswordEncoder uses a message digest algorithm.
1820
*
1921
* @author Fabien Potencier <[email protected]>
22+
*
23+
* @deprecated since Symfony 5.3, use {@link MessageDigestPasswordHasher} instead
2024
*/
2125
class MessageDigestPasswordEncoder extends BasePasswordEncoder
2226
{
23-
private $algorithm;
24-
private $encodeHashAsBase64;
25-
private $iterations = 1;
26-
private $encodedLength = -1;
27+
use LegacyEncoderTrait;
2728

2829
/**
2930
* @param string $algorithm The digest algorithm to use
@@ -32,51 +33,6 @@ class MessageDigestPasswordEncoder extends BasePasswordEncoder
3233
*/
3334
public function __construct(string $algorithm = 'sha512', bool $encodeHashAsBase64 = true, int $iterations = 5000)
3435
{
35-
$this->algorithm = $algorithm;
36-
$this->encodeHashAsBase64 = $encodeHashAsBase64;
37-
38-
try {
39-
$this->encodedLength = \strlen($this->encodePassword('', 'salt'));
40-
} catch (\LogicException $e) {
41-
// ignore algorithm not supported
42-
}
43-
44-
$this->iterations = $iterations;
45-
}
46-
47-
/**
48-
* {@inheritdoc}
49-
*/
50-
public function encodePassword(string $raw, ?string $salt)
51-
{
52-
if ($this->isPasswordTooLong($raw)) {
53-
throw new BadCredentialsException('Invalid password.');
54-
}
55-
56-
if (!\in_array($this->algorithm, hash_algos(), true)) {
57-
throw new \LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm));
58-
}
59-
60-
$salted = $this->mergePasswordAndSalt($raw, $salt);
61-
$digest = hash($this->algorithm, $salted, true);
62-
63-
// "stretch" hash
64-
for ($i = 1; $i < $this->iterations; ++$i) {
65-
$digest = hash($this->algorithm, $digest.$salted, true);
66-
}
67-
68-
return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest);
69-
}
70-
71-
/**
72-
* {@inheritdoc}
73-
*/
74-
public function isPasswordValid(string $encoded, string $raw, ?string $salt)
75-
{
76-
if (\strlen($encoded) !== $this->encodedLength || false !== strpos($encoded, '$')) {
77-
return false;
78-
}
79-
80-
return !$this->isPasswordTooLong($raw) && $this->comparePasswords($encoded, $this->encodePassword($raw, $salt));
36+
$this->hasher = new MessageDigestPasswordHasher($algorithm, $encodeHashAsBase64, $iterations);
8137
}
8238
}

Encoder/MigratingPasswordEncoder.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111

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

14+
trigger_deprecation('symfony/security-core', '5.3', sprintf('The "%s" class is deprecated, use "%s" instead.', MigratingPasswordEncoder::class, MigratingPasswordHasher::class));
15+
16+
use Symfony\Component\PasswordHasher\Hasher\MigratingPasswordHasher;
17+
1418
/**
1519
* Hashes passwords using the best available encoder.
1620
* Validates them using a chain of encoders.
@@ -19,12 +23,11 @@
1923
* could be used to authenticate successfully without knowing the cleartext password.
2024
*
2125
* @author Nicolas Grekas <[email protected]>
26+
*
27+
* @deprecated since Symfony 5.3, use {@link MigratingPasswordHasher} instead
2228
*/
2329
final class MigratingPasswordEncoder extends BasePasswordEncoder implements SelfSaltingEncoderInterface
2430
{
25-
private $bestEncoder;
26-
private $extraEncoders;
27-
2831
public function __construct(PasswordEncoderInterface $bestEncoder, PasswordEncoderInterface ...$extraEncoders)
2932
{
3033
$this->bestEncoder = $bestEncoder;

0 commit comments

Comments
 (0)