[PasswordHasher] Improved BC layer
This commit is contained in:
parent
2ba1f89a3a
commit
0caad4f72d
@ -14,6 +14,8 @@ namespace Symfony\Component\PasswordHasher\Hasher;
|
||||
use Symfony\Component\PasswordHasher\Exception\LogicException;
|
||||
use Symfony\Component\PasswordHasher\PasswordHasherInterface;
|
||||
use Symfony\Component\Security\Core\Encoder\EncoderAwareInterface;
|
||||
use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
|
||||
use Symfony\Component\Security\Core\Encoder\PasswordHasherAdapter;
|
||||
|
||||
/**
|
||||
* A generic hasher factory implementation.
|
||||
@ -25,6 +27,9 @@ class PasswordHasherFactory implements PasswordHasherFactoryInterface
|
||||
{
|
||||
private $passwordHashers;
|
||||
|
||||
/**
|
||||
* @param array<string, PasswordHasherInterface|array> $passwordHashers
|
||||
*/
|
||||
public function __construct(array $passwordHashers)
|
||||
{
|
||||
$this->passwordHashers = $passwordHashers;
|
||||
@ -57,7 +62,10 @@ class PasswordHasherFactory implements PasswordHasherFactoryInterface
|
||||
}
|
||||
|
||||
if (!$this->passwordHashers[$hasherKey] instanceof PasswordHasherInterface) {
|
||||
$this->passwordHashers[$hasherKey] = $this->createHasher($this->passwordHashers[$hasherKey]);
|
||||
$this->passwordHashers[$hasherKey] = $this->passwordHashers[$hasherKey] instanceof PasswordEncoderInterface
|
||||
? new PasswordHasherAdapter($this->passwordHashers[$hasherKey])
|
||||
: $this->createHasher($this->passwordHashers[$hasherKey])
|
||||
;
|
||||
}
|
||||
|
||||
return $this->passwordHashers[$hasherKey];
|
||||
@ -82,6 +90,9 @@ class PasswordHasherFactory implements PasswordHasherFactoryInterface
|
||||
}
|
||||
|
||||
$hasher = new $config['class'](...$config['arguments']);
|
||||
if (!$hasher instanceof PasswordHasherInterface && $hasher instanceof PasswordEncoderInterface) {
|
||||
$hasher = new PasswordHasherAdapter($hasher);
|
||||
}
|
||||
|
||||
if ($isExtra || !\in_array($config['class'], [NativePasswordHasher::class, SodiumPasswordHasher::class], true)) {
|
||||
return $hasher;
|
||||
|
@ -18,6 +18,7 @@ use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher;
|
||||
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface;
|
||||
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory;
|
||||
use Symfony\Component\PasswordHasher\Hasher\SodiumPasswordHasher;
|
||||
use Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder;
|
||||
use Symfony\Component\Security\Core\User\InMemoryUser;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
@ -176,6 +177,24 @@ class PasswordHasherFactoryTest extends TestCase
|
||||
(new PasswordHasherFactory([SomeUser::class => ['class' => SodiumPasswordHasher::class, 'arguments' => []]]))->getPasswordHasher(SomeUser::class)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
public function testLegacyEncoderObject()
|
||||
{
|
||||
$factory = new PasswordHasherFactory([SomeUser::class => new PlaintextPasswordEncoder()]);
|
||||
self::assertSame('foo{bar}', $factory->getPasswordHasher(SomeUser::class)->hash('foo', 'bar'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
public function testLegacyEncoderClass()
|
||||
{
|
||||
$factory = new PasswordHasherFactory([SomeUser::class => ['class' => PlaintextPasswordEncoder::class, 'arguments' => []]]);
|
||||
self::assertSame('foo{bar}', $factory->getPasswordHasher(SomeUser::class)->hash('foo', 'bar'));
|
||||
}
|
||||
}
|
||||
|
||||
class SomeUser implements UserInterface
|
||||
|
@ -23,6 +23,9 @@
|
||||
"symfony/security-core": "^5.3",
|
||||
"symfony/console": "^5"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/security-core": "<5.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Component\\PasswordHasher\\": "" },
|
||||
"exclude-from-classmap": [
|
||||
|
@ -13,6 +13,8 @@ namespace Symfony\Component\Security\Core\Encoder;
|
||||
|
||||
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface;
|
||||
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory;
|
||||
use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface;
|
||||
use Symfony\Component\PasswordHasher\PasswordHasherInterface;
|
||||
use Symfony\Component\Security\Core\Exception\LogicException;
|
||||
|
||||
trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', EncoderFactory::class, PasswordHasherFactory::class);
|
||||
@ -60,7 +62,13 @@ class EncoderFactory implements EncoderFactoryInterface
|
||||
}
|
||||
|
||||
if (!$this->encoders[$encoderKey] instanceof PasswordEncoderInterface) {
|
||||
$this->encoders[$encoderKey] = $this->createEncoder($this->encoders[$encoderKey]);
|
||||
if ($this->encoders[$encoderKey] instanceof LegacyPasswordHasherInterface) {
|
||||
$this->encoders[$encoderKey] = new LegacyPasswordHasherEncoder($this->encoders[$encoderKey]);
|
||||
} elseif ($this->encoders[$encoderKey] instanceof PasswordHasherInterface) {
|
||||
$this->encoders[$encoderKey] = new PasswordHasherEncoder($this->encoders[$encoderKey]);
|
||||
} else {
|
||||
$this->encoders[$encoderKey] = $this->createEncoder($this->encoders[$encoderKey]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->encoders[$encoderKey];
|
||||
|
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Security\Core\Encoder;
|
||||
|
||||
use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException;
|
||||
use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface;
|
||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
|
||||
/**
|
||||
* Forward compatibility for new new PasswordHasher component.
|
||||
*
|
||||
* @author Alexander M. Turek <me@derrabus.de>
|
||||
*
|
||||
* @internal To be removed in Symfony 6
|
||||
*/
|
||||
final class LegacyPasswordHasherEncoder implements PasswordEncoderInterface
|
||||
{
|
||||
private $passwordHasher;
|
||||
|
||||
public function __construct(LegacyPasswordHasherInterface $passwordHasher)
|
||||
{
|
||||
$this->passwordHasher = $passwordHasher;
|
||||
}
|
||||
|
||||
public function encodePassword(string $raw, ?string $salt): string
|
||||
{
|
||||
try {
|
||||
return $this->passwordHasher->hash($raw, $salt);
|
||||
} catch (InvalidPasswordException $e) {
|
||||
throw new BadCredentialsException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
public function isPasswordValid(string $encoded, string $raw, ?string $salt): bool
|
||||
{
|
||||
return $this->passwordHasher->verify($encoded, $raw, $salt);
|
||||
}
|
||||
|
||||
public function needsRehash(string $encoded): bool
|
||||
{
|
||||
return $this->passwordHasher->needsRehash($encoded);
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Security\Core\Encoder;
|
||||
|
||||
use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface;
|
||||
|
||||
/**
|
||||
* Forward compatibility for new new PasswordHasher component.
|
||||
*
|
||||
* @author Alexander M. Turek <me@derrabus.de>
|
||||
*
|
||||
* @internal To be removed in Symfony 6
|
||||
*/
|
||||
final class PasswordHasherAdapter implements LegacyPasswordHasherInterface
|
||||
{
|
||||
private $passwordEncoder;
|
||||
|
||||
public function __construct(PasswordEncoderInterface $passwordEncoder)
|
||||
{
|
||||
$this->passwordEncoder = $passwordEncoder;
|
||||
}
|
||||
|
||||
public function hash(string $plainPassword, ?string $salt = null): string
|
||||
{
|
||||
return $this->passwordEncoder->encodePassword($plainPassword, $salt);
|
||||
}
|
||||
|
||||
public function verify(string $hashedPassword, string $plainPassword, ?string $salt = null): bool
|
||||
{
|
||||
return $this->passwordEncoder->isPasswordValid($hashedPassword, $plainPassword, $salt);
|
||||
}
|
||||
|
||||
public function needsRehash(string $hashedPassword): bool
|
||||
{
|
||||
return $this->passwordEncoder->needsRehash($hashedPassword);
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Security\Core\Encoder;
|
||||
|
||||
use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException;
|
||||
use Symfony\Component\PasswordHasher\PasswordHasherInterface;
|
||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
|
||||
/**
|
||||
* Forward compatibility for new new PasswordHasher component.
|
||||
*
|
||||
* @author Alexander M. Turek <me@derrabus.de>
|
||||
*
|
||||
* @internal To be removed in Symfony 6
|
||||
*/
|
||||
final class PasswordHasherEncoder implements PasswordEncoderInterface, SelfSaltingEncoderInterface
|
||||
{
|
||||
private $passwordHasher;
|
||||
|
||||
public function __construct(PasswordHasherInterface $passwordHasher)
|
||||
{
|
||||
$this->passwordHasher = $passwordHasher;
|
||||
}
|
||||
|
||||
public function encodePassword(string $raw, ?string $salt): string
|
||||
{
|
||||
if (null !== $salt) {
|
||||
throw new \InvalidArgumentException('This password hasher does not support passing a salt.');
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->passwordHasher->hash($raw);
|
||||
} catch (InvalidPasswordException $e) {
|
||||
throw new BadCredentialsException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
public function isPasswordValid(string $encoded, string $raw, ?string $salt): bool
|
||||
{
|
||||
if (null !== $salt) {
|
||||
throw new \InvalidArgumentException('This password hasher does not support passing a salt.');
|
||||
}
|
||||
|
||||
return $this->passwordHasher->verify($encoded, $raw);
|
||||
}
|
||||
|
||||
public function needsRehash(string $encoded): bool
|
||||
{
|
||||
return $this->passwordHasher->needsRehash($encoded);
|
||||
}
|
||||
}
|
@ -12,17 +12,20 @@
|
||||
namespace Symfony\Component\Security\Core\Tests\Encoder;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\PasswordHasher\Hasher\MessageDigestPasswordHasher;
|
||||
use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher;
|
||||
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface;
|
||||
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory;
|
||||
use Symfony\Component\PasswordHasher\Hasher\PlaintextPasswordHasher;
|
||||
use Symfony\Component\Security\Core\Encoder\EncoderAwareInterface;
|
||||
use Symfony\Component\Security\Core\Encoder\EncoderFactory;
|
||||
use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder;
|
||||
use Symfony\Component\Security\Core\Encoder\MigratingPasswordEncoder;
|
||||
use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder;
|
||||
use Symfony\Component\Security\Core\Encoder\SelfSaltingEncoderInterface;
|
||||
use Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder;
|
||||
use Symfony\Component\Security\Core\User\User;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface;
|
||||
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory;
|
||||
use Symfony\Component\PasswordHasher\Hasher\MessageDigestPasswordHasher;
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
@ -193,6 +196,28 @@ class EncoderFactoryTest extends TestCase
|
||||
$expectedEncoder = new MessageDigestPasswordHasher('sha1');
|
||||
$this->assertEquals($expectedEncoder->hash('foo', ''), $encoder->hash('foo', ''));
|
||||
}
|
||||
|
||||
public function testLegacyPasswordHasher()
|
||||
{
|
||||
$factory = new EncoderFactory([
|
||||
SomeUser::class => new PlaintextPasswordHasher(),
|
||||
]);
|
||||
|
||||
$encoder = $factory->getEncoder(new SomeUser());
|
||||
self::assertNotInstanceOf(SelfSaltingEncoderInterface::class, $encoder);
|
||||
self::assertSame('foo{bar}', $encoder->encodePassword('foo', 'bar'));
|
||||
}
|
||||
|
||||
public function testPasswordHasher()
|
||||
{
|
||||
$factory = new EncoderFactory([
|
||||
SomeUser::class => new NativePasswordHasher(),
|
||||
]);
|
||||
|
||||
$encoder = $factory->getEncoder(new SomeUser());
|
||||
self::assertInstanceOf(SelfSaltingEncoderInterface::class, $encoder);
|
||||
self::assertTrue($encoder->isPasswordValid($encoder->encodePassword('foo', null), 'foo', null));
|
||||
}
|
||||
}
|
||||
|
||||
class SomeUser implements UserInterface
|
||||
@ -236,7 +261,6 @@ class EncAwareUser extends SomeUser implements EncoderAwareInterface
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class HasherAwareUser extends SomeUser implements PasswordHasherAwareInterface
|
||||
{
|
||||
public $hasherName = 'encoder_name';
|
||||
|
Reference in New Issue
Block a user