Skip to content

Commit 87f177a

Browse files
committed
bug #41022 [PasswordHasher] Improved BC layer (derrabus)
This PR was merged into the 5.3-dev branch. Discussion ---------- [PasswordHasher] Improved BC layer | Q | A | ------------- | --- | Branch? | 5.3 | Bug fix? | yes | New feature? | no | Deprecations? | no | Tickets | Fix #41020, Fix #41024 | License | MIT | Doc PR | N/A Commits ------- 0caad4f72d [PasswordHasher] Improved BC layer
2 parents de9f910 + 9e5de7f commit 87f177a

File tree

5 files changed

+195
-5
lines changed

5 files changed

+195
-5
lines changed

Encoder/EncoderFactory.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface;
1515
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory;
16+
use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface;
17+
use Symfony\Component\PasswordHasher\PasswordHasherInterface;
1618
use Symfony\Component\Security\Core\Exception\LogicException;
1719

1820
trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', EncoderFactory::class, PasswordHasherFactory::class);
@@ -60,7 +62,13 @@ public function getEncoder($user)
6062
}
6163

6264
if (!$this->encoders[$encoderKey] instanceof PasswordEncoderInterface) {
63-
$this->encoders[$encoderKey] = $this->createEncoder($this->encoders[$encoderKey]);
65+
if ($this->encoders[$encoderKey] instanceof LegacyPasswordHasherInterface) {
66+
$this->encoders[$encoderKey] = new LegacyPasswordHasherEncoder($this->encoders[$encoderKey]);
67+
} elseif ($this->encoders[$encoderKey] instanceof PasswordHasherInterface) {
68+
$this->encoders[$encoderKey] = new PasswordHasherEncoder($this->encoders[$encoderKey]);
69+
} else {
70+
$this->encoders[$encoderKey] = $this->createEncoder($this->encoders[$encoderKey]);
71+
}
6472
}
6573

6674
return $this->encoders[$encoderKey];
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Security\Core\Exception\BadCredentialsException;
17+
18+
/**
19+
* Forward compatibility for new new PasswordHasher component.
20+
*
21+
* @author Alexander M. Turek <me@derrabus.de>
22+
*
23+
* @internal To be removed in Symfony 6
24+
*/
25+
final class LegacyPasswordHasherEncoder implements PasswordEncoderInterface
26+
{
27+
private $passwordHasher;
28+
29+
public function __construct(LegacyPasswordHasherInterface $passwordHasher)
30+
{
31+
$this->passwordHasher = $passwordHasher;
32+
}
33+
34+
public function encodePassword(string $raw, ?string $salt): string
35+
{
36+
try {
37+
return $this->passwordHasher->hash($raw, $salt);
38+
} catch (InvalidPasswordException $e) {
39+
throw new BadCredentialsException($e->getMessage(), $e->getCode(), $e);
40+
}
41+
}
42+
43+
public function isPasswordValid(string $encoded, string $raw, ?string $salt): bool
44+
{
45+
return $this->passwordHasher->verify($encoded, $raw, $salt);
46+
}
47+
48+
public function needsRehash(string $encoded): bool
49+
{
50+
return $this->passwordHasher->needsRehash($encoded);
51+
}
52+
}

Encoder/PasswordHasherAdapter.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\LegacyPasswordHasherInterface;
15+
16+
/**
17+
* Forward compatibility for new new PasswordHasher component.
18+
*
19+
* @author Alexander M. Turek <me@derrabus.de>
20+
*
21+
* @internal To be removed in Symfony 6
22+
*/
23+
final class PasswordHasherAdapter implements LegacyPasswordHasherInterface
24+
{
25+
private $passwordEncoder;
26+
27+
public function __construct(PasswordEncoderInterface $passwordEncoder)
28+
{
29+
$this->passwordEncoder = $passwordEncoder;
30+
}
31+
32+
public function hash(string $plainPassword, ?string $salt = null): string
33+
{
34+
return $this->passwordEncoder->encodePassword($plainPassword, $salt);
35+
}
36+
37+
public function verify(string $hashedPassword, string $plainPassword, ?string $salt = null): bool
38+
{
39+
return $this->passwordEncoder->isPasswordValid($hashedPassword, $plainPassword, $salt);
40+
}
41+
42+
public function needsRehash(string $hashedPassword): bool
43+
{
44+
return $this->passwordEncoder->needsRehash($hashedPassword);
45+
}
46+
}

Encoder/PasswordHasherEncoder.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\PasswordHasherInterface;
16+
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
17+
18+
/**
19+
* Forward compatibility for new new PasswordHasher component.
20+
*
21+
* @author Alexander M. Turek <me@derrabus.de>
22+
*
23+
* @internal To be removed in Symfony 6
24+
*/
25+
final class PasswordHasherEncoder implements PasswordEncoderInterface, SelfSaltingEncoderInterface
26+
{
27+
private $passwordHasher;
28+
29+
public function __construct(PasswordHasherInterface $passwordHasher)
30+
{
31+
$this->passwordHasher = $passwordHasher;
32+
}
33+
34+
public function encodePassword(string $raw, ?string $salt): string
35+
{
36+
if (null !== $salt) {
37+
throw new \InvalidArgumentException('This password hasher does not support passing a salt.');
38+
}
39+
40+
try {
41+
return $this->passwordHasher->hash($raw);
42+
} catch (InvalidPasswordException $e) {
43+
throw new BadCredentialsException($e->getMessage(), $e->getCode(), $e);
44+
}
45+
}
46+
47+
public function isPasswordValid(string $encoded, string $raw, ?string $salt): bool
48+
{
49+
if (null !== $salt) {
50+
throw new \InvalidArgumentException('This password hasher does not support passing a salt.');
51+
}
52+
53+
return $this->passwordHasher->verify($encoded, $raw);
54+
}
55+
56+
public function needsRehash(string $encoded): bool
57+
{
58+
return $this->passwordHasher->needsRehash($encoded);
59+
}
60+
}

Tests/Encoder/EncoderFactoryTest.php

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,20 @@
1212
namespace Symfony\Component\Security\Core\Tests\Encoder;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\PasswordHasher\Hasher\MessageDigestPasswordHasher;
16+
use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher;
17+
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface;
18+
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory;
19+
use Symfony\Component\PasswordHasher\Hasher\PlaintextPasswordHasher;
1520
use Symfony\Component\Security\Core\Encoder\EncoderAwareInterface;
1621
use Symfony\Component\Security\Core\Encoder\EncoderFactory;
1722
use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder;
1823
use Symfony\Component\Security\Core\Encoder\MigratingPasswordEncoder;
1924
use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder;
25+
use Symfony\Component\Security\Core\Encoder\SelfSaltingEncoderInterface;
2026
use Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder;
2127
use Symfony\Component\Security\Core\User\User;
2228
use Symfony\Component\Security\Core\User\UserInterface;
23-
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface;
24-
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory;
25-
use Symfony\Component\PasswordHasher\Hasher\MessageDigestPasswordHasher;
2629

2730
/**
2831
* @group legacy
@@ -193,6 +196,28 @@ public function testHasherAwareCompat()
193196
$expectedEncoder = new MessageDigestPasswordHasher('sha1');
194197
$this->assertEquals($expectedEncoder->hash('foo', ''), $encoder->hash('foo', ''));
195198
}
199+
200+
public function testLegacyPasswordHasher()
201+
{
202+
$factory = new EncoderFactory([
203+
SomeUser::class => new PlaintextPasswordHasher(),
204+
]);
205+
206+
$encoder = $factory->getEncoder(new SomeUser());
207+
self::assertNotInstanceOf(SelfSaltingEncoderInterface::class, $encoder);
208+
self::assertSame('foo{bar}', $encoder->encodePassword('foo', 'bar'));
209+
}
210+
211+
public function testPasswordHasher()
212+
{
213+
$factory = new EncoderFactory([
214+
SomeUser::class => new NativePasswordHasher(),
215+
]);
216+
217+
$encoder = $factory->getEncoder(new SomeUser());
218+
self::assertInstanceOf(SelfSaltingEncoderInterface::class, $encoder);
219+
self::assertTrue($encoder->isPasswordValid($encoder->encodePassword('foo', null), 'foo', null));
220+
}
196221
}
197222

198223
class SomeUser implements UserInterface
@@ -236,7 +261,6 @@ public function getEncoderName(): ?string
236261
}
237262
}
238263

239-
240264
class HasherAwareUser extends SomeUser implements PasswordHasherAwareInterface
241265
{
242266
public $hasherName = 'encoder_name';

0 commit comments

Comments
 (0)