login consent app sql

This commit is contained in:
2022-05-03 08:54:45 +02:00
parent e7253acfd8
commit f9a6535906
1652 changed files with 187600 additions and 45 deletions

View File

@ -0,0 +1,25 @@
<?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\PasswordHasher\Hasher;
use Symfony\Component\PasswordHasher\PasswordHasherInterface;
/**
* @author Robin Chalas <robin.chalas@gmail.com>
*/
trait CheckPasswordLengthTrait
{
private function isPasswordTooLong(string $password): bool
{
return PasswordHasherInterface::MAX_PASSWORD_LENGTH < \strlen($password);
}
}

View File

@ -0,0 +1,98 @@
<?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\PasswordHasher\Hasher;
use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException;
use Symfony\Component\PasswordHasher\Exception\LogicException;
use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface;
/**
* MessageDigestPasswordHasher uses a message digest algorithm.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class MessageDigestPasswordHasher implements LegacyPasswordHasherInterface
{
use CheckPasswordLengthTrait;
private $algorithm;
private $encodeHashAsBase64;
private $iterations = 1;
private $hashLength = -1;
/**
* @param string $algorithm The digest algorithm to use
* @param bool $encodeHashAsBase64 Whether to base64 encode the password hash
* @param int $iterations The number of iterations to use to stretch the password hash
*/
public function __construct(string $algorithm = 'sha512', bool $encodeHashAsBase64 = true, int $iterations = 5000)
{
$this->algorithm = $algorithm;
$this->encodeHashAsBase64 = $encodeHashAsBase64;
try {
$this->hashLength = \strlen($this->hash('', 'salt'));
} catch (\LogicException $e) {
// ignore algorithm not supported
}
$this->iterations = $iterations;
}
public function hash(string $plainPassword, string $salt = null): string
{
if ($this->isPasswordTooLong($plainPassword)) {
throw new InvalidPasswordException();
}
if (!\in_array($this->algorithm, hash_algos(), true)) {
throw new LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm));
}
$salted = $this->mergePasswordAndSalt($plainPassword, $salt);
$digest = hash($this->algorithm, $salted, true);
// "stretch" hash
for ($i = 1; $i < $this->iterations; ++$i) {
$digest = hash($this->algorithm, $digest.$salted, true);
}
return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest);
}
public function verify(string $hashedPassword, string $plainPassword, string $salt = null): bool
{
if (\strlen($hashedPassword) !== $this->hashLength || false !== strpos($hashedPassword, '$')) {
return false;
}
return !$this->isPasswordTooLong($plainPassword) && hash_equals($hashedPassword, $this->hash($plainPassword, $salt));
}
public function needsRehash(string $hashedPassword): bool
{
return false;
}
private function mergePasswordAndSalt(string $password, ?string $salt): string
{
if (!$salt) {
return $password;
}
if (false !== strrpos($salt, '{') || false !== strrpos($salt, '}')) {
throw new \InvalidArgumentException('Cannot use { or } in salt.');
}
return $password.'{'.$salt.'}';
}
}

View File

@ -0,0 +1,64 @@
<?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\PasswordHasher\Hasher;
use Symfony\Component\PasswordHasher\PasswordHasherInterface;
/**
* Hashes passwords using the best available hasher.
* Verifies them using a chain of hashers.
*
* /!\ Don't put a PlaintextPasswordHasher in the list as that'd mean a leaked hash
* could be used to authenticate successfully without knowing the cleartext password.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
final class MigratingPasswordHasher implements PasswordHasherInterface
{
private $bestHasher;
private $extraHashers;
public function __construct(PasswordHasherInterface $bestHasher, PasswordHasherInterface ...$extraHashers)
{
$this->bestHasher = $bestHasher;
$this->extraHashers = $extraHashers;
}
public function hash(string $plainPassword, string $salt = null): string
{
return $this->bestHasher->hash($plainPassword, $salt);
}
public function verify(string $hashedPassword, string $plainPassword, string $salt = null): bool
{
if ($this->bestHasher->verify($hashedPassword, $plainPassword, $salt)) {
return true;
}
if (!$this->bestHasher->needsRehash($hashedPassword)) {
return false;
}
foreach ($this->extraHashers as $hasher) {
if ($hasher->verify($hashedPassword, $plainPassword, $salt)) {
return true;
}
}
return false;
}
public function needsRehash(string $hashedPassword): bool
{
return $this->bestHasher->needsRehash($hashedPassword);
}
}

View File

@ -0,0 +1,120 @@
<?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\PasswordHasher\Hasher;
use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException;
use Symfony\Component\PasswordHasher\PasswordHasherInterface;
/**
* Hashes passwords using password_hash().
*
* @author Elnur Abdurrakhimov <elnur@elnur.pro>
* @author Terje Bråten <terje@braten.be>
* @author Nicolas Grekas <p@tchwork.com>
*/
final class NativePasswordHasher implements PasswordHasherInterface
{
use CheckPasswordLengthTrait;
private $algorithm = \PASSWORD_BCRYPT;
private $options;
/**
* @param string|null $algorithm An algorithm supported by password_hash() or null to use the best available algorithm
*/
public function __construct(int $opsLimit = null, int $memLimit = null, int $cost = null, string $algorithm = null)
{
$cost = $cost ?? 13;
$opsLimit = $opsLimit ?? max(4, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE : 4);
$memLimit = $memLimit ?? max(64 * 1024 * 1024, \defined('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE : 64 * 1024 * 1024);
if (3 > $opsLimit) {
throw new \InvalidArgumentException('$opsLimit must be 3 or greater.');
}
if (10 * 1024 > $memLimit) {
throw new \InvalidArgumentException('$memLimit must be 10k or greater.');
}
if ($cost < 4 || 31 < $cost) {
throw new \InvalidArgumentException('$cost must be in the range of 4-31.');
}
if (null !== $algorithm) {
$algorithms = [1 => \PASSWORD_BCRYPT, '2y' => \PASSWORD_BCRYPT];
if (\defined('PASSWORD_ARGON2I')) {
$algorithms[2] = $algorithms['argon2i'] = \PASSWORD_ARGON2I;
}
if (\defined('PASSWORD_ARGON2ID')) {
$algorithms[3] = $algorithms['argon2id'] = \PASSWORD_ARGON2ID;
}
$this->algorithm = $algorithms[$algorithm] ?? $algorithm;
}
$this->options = [
'cost' => $cost,
'time_cost' => $opsLimit,
'memory_cost' => $memLimit >> 10,
'threads' => 1,
];
}
public function hash(string $plainPassword): string
{
if ($this->isPasswordTooLong($plainPassword)) {
throw new InvalidPasswordException();
}
if (\PASSWORD_BCRYPT === $this->algorithm && (72 < \strlen($plainPassword) || false !== strpos($plainPassword, "\0"))) {
$plainPassword = base64_encode(hash('sha512', $plainPassword, true));
}
return password_hash($plainPassword, $this->algorithm, $this->options);
}
public function verify(string $hashedPassword, string $plainPassword): bool
{
if ('' === $plainPassword || $this->isPasswordTooLong($plainPassword)) {
return false;
}
if (0 !== strpos($hashedPassword, '$argon')) {
// Bcrypt cuts on NUL chars and after 72 bytes
if (0 === strpos($hashedPassword, '$2') && (72 < \strlen($plainPassword) || false !== strpos($plainPassword, "\0"))) {
$plainPassword = base64_encode(hash('sha512', $plainPassword, true));
}
return password_verify($plainPassword, $hashedPassword);
}
if (\extension_loaded('sodium') && version_compare(\SODIUM_LIBRARY_VERSION, '1.0.14', '>=')) {
return sodium_crypto_pwhash_str_verify($hashedPassword, $plainPassword);
}
if (\extension_loaded('libsodium') && version_compare(phpversion('libsodium'), '1.0.14', '>=')) {
return \Sodium\crypto_pwhash_str_verify($hashedPassword, $plainPassword);
}
return password_verify($plainPassword, $hashedPassword);
}
/**
* {@inheritdoc}
*/
public function needsRehash(string $hashedPassword): bool
{
return password_needs_rehash($hashedPassword, $this->algorithm, $this->options);
}
}

View File

@ -0,0 +1,26 @@
<?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\PasswordHasher\Hasher;
/**
* @author Christophe Coevoet <stof@notk.org>
*/
interface PasswordHasherAwareInterface
{
/**
* Gets the name of the password hasher used to hash the password.
*
* If the method returns null, the standard way to retrieve the password hasher
* will be used instead.
*/
public function getPasswordHasherName(): ?string;
}

View File

@ -0,0 +1,242 @@
<?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\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.
*
* @author Nicolas Grekas <p@tchwork.com>
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class PasswordHasherFactory implements PasswordHasherFactoryInterface
{
private $passwordHashers;
/**
* @param array<string, PasswordHasherInterface|array> $passwordHashers
*/
public function __construct(array $passwordHashers)
{
$this->passwordHashers = $passwordHashers;
}
/**
* {@inheritdoc}
*/
public function getPasswordHasher($user): PasswordHasherInterface
{
$hasherKey = null;
if (($user instanceof PasswordHasherAwareInterface && null !== $hasherName = $user->getPasswordHasherName()) || ($user instanceof EncoderAwareInterface && null !== $hasherName = $user->getEncoderName())) {
if (!\array_key_exists($hasherName, $this->passwordHashers)) {
throw new \RuntimeException(sprintf('The password hasher "%s" was not configured.', $hasherName));
}
$hasherKey = $hasherName;
} else {
foreach ($this->passwordHashers as $class => $hasher) {
if ((\is_object($user) && $user instanceof $class) || (!\is_object($user) && (is_subclass_of($user, $class) || $user == $class))) {
$hasherKey = $class;
break;
}
}
}
if (null === $hasherKey) {
throw new \RuntimeException(sprintf('No password hasher has been configured for account "%s".', \is_object($user) ? get_debug_type($user) : $user));
}
return $this->createHasherUsingAdapter($hasherKey);
}
/**
* Creates the actual hasher instance.
*
* @throws \InvalidArgumentException
*/
private function createHasher(array $config, bool $isExtra = false): PasswordHasherInterface
{
if (isset($config['algorithm'])) {
$rawConfig = $config;
$config = $this->getHasherConfigFromAlgorithm($config);
}
if (!isset($config['class'])) {
throw new \InvalidArgumentException('"class" must be set in '.json_encode($config));
}
if (!isset($config['arguments'])) {
throw new \InvalidArgumentException('"arguments" must be set in '.json_encode($config));
}
$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;
}
if ($rawConfig ?? null) {
$extrapasswordHashers = array_map(function (string $algo) use ($rawConfig): PasswordHasherInterface {
$rawConfig['algorithm'] = $algo;
return $this->createHasher($rawConfig);
}, ['pbkdf2', $rawConfig['hash_algorithm'] ?? 'sha512']);
} else {
$extrapasswordHashers = [new Pbkdf2PasswordHasher(), new MessageDigestPasswordHasher()];
}
return new MigratingPasswordHasher($hasher, ...$extrapasswordHashers);
}
private function createHasherUsingAdapter(string $hasherKey): PasswordHasherInterface
{
if (!$this->passwordHashers[$hasherKey] instanceof PasswordHasherInterface) {
$this->passwordHashers[$hasherKey] = $this->passwordHashers[$hasherKey] instanceof PasswordEncoderInterface
? new PasswordHasherAdapter($this->passwordHashers[$hasherKey])
: $this->createHasher($this->passwordHashers[$hasherKey])
;
}
return $this->passwordHashers[$hasherKey];
}
private function getHasherConfigFromAlgorithm(array $config): array
{
if ('auto' === $config['algorithm']) {
// "plaintext" is not listed as any leaked hashes could then be used to authenticate directly
if (SodiumPasswordHasher::isSupported()) {
$algorithms = ['native', 'sodium', 'pbkdf2'];
} else {
$algorithms = ['native', 'pbkdf2'];
}
if ($config['hash_algorithm'] ?? '') {
$algorithms[] = $config['hash_algorithm'];
}
$hasherChain = [];
foreach ($algorithms as $algorithm) {
$config['algorithm'] = $algorithm;
$hasherChain[] = $this->createHasher($config, true);
}
return [
'class' => MigratingPasswordHasher::class,
'arguments' => $hasherChain,
];
}
if ($frompasswordHashers = ($config['migrate_from'] ?? false)) {
unset($config['migrate_from']);
$hasherChain = [$this->createHasher($config, true)];
foreach ($frompasswordHashers as $name) {
if (isset($this->passwordHashers[$name])) {
$hasher = $this->createHasherUsingAdapter($name);
} else {
$hasher = $this->createHasher(['algorithm' => $name], true);
}
$hasherChain[] = $hasher;
}
return [
'class' => MigratingPasswordHasher::class,
'arguments' => $hasherChain,
];
}
switch ($config['algorithm']) {
case 'plaintext':
return [
'class' => PlaintextPasswordHasher::class,
'arguments' => [$config['ignore_case'] ?? false],
];
case 'pbkdf2':
return [
'class' => Pbkdf2PasswordHasher::class,
'arguments' => [
$config['hash_algorithm'] ?? 'sha512',
$config['encode_as_base64'] ?? true,
$config['iterations'] ?? 1000,
$config['key_length'] ?? 40,
],
];
case 'bcrypt':
$config['algorithm'] = 'native';
$config['native_algorithm'] = \PASSWORD_BCRYPT;
return $this->getHasherConfigFromAlgorithm($config);
case 'native':
return [
'class' => NativePasswordHasher::class,
'arguments' => [
$config['time_cost'] ?? null,
(($config['memory_cost'] ?? 0) << 10) ?: null,
$config['cost'] ?? null,
] + (isset($config['native_algorithm']) ? [3 => $config['native_algorithm']] : []),
];
case 'sodium':
return [
'class' => SodiumPasswordHasher::class,
'arguments' => [
$config['time_cost'] ?? null,
(($config['memory_cost'] ?? 0) << 10) ?: null,
],
];
case 'argon2i':
if (SodiumPasswordHasher::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
$config['algorithm'] = 'sodium';
} elseif (\defined('PASSWORD_ARGON2I')) {
$config['algorithm'] = 'native';
$config['native_algorithm'] = \PASSWORD_ARGON2I;
} else {
throw new LogicException(sprintf('Algorithm "argon2i" is not available. Use "%s" instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? 'argon2id" or "auto' : 'auto'));
}
return $this->getHasherConfigFromAlgorithm($config);
case 'argon2id':
if (($hasSodium = SodiumPasswordHasher::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
$config['algorithm'] = 'sodium';
} elseif (\defined('PASSWORD_ARGON2ID')) {
$config['algorithm'] = 'native';
$config['native_algorithm'] = \PASSWORD_ARGON2ID;
} else {
throw new LogicException(sprintf('Algorithm "argon2id" is not available. Either use "%s", upgrade to PHP 7.3+ or use libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? 'argon2i", "auto' : 'auto'));
}
return $this->getHasherConfigFromAlgorithm($config);
}
return [
'class' => MessageDigestPasswordHasher::class,
'arguments' => [
$config['algorithm'],
$config['encode_as_base64'] ?? true,
$config['iterations'] ?? 5000,
],
];
}
}

View File

@ -0,0 +1,33 @@
<?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\PasswordHasher\Hasher;
use Symfony\Component\PasswordHasher\PasswordHasherInterface;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
/**
* PasswordHasherFactoryInterface to support different password hashers for different user accounts.
*
* @author Robin Chalas <robin.chalas@gmail.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface PasswordHasherFactoryInterface
{
/**
* Returns the password hasher to use for the given user.
*
* @param PasswordHasherAwareInterface|PasswordAuthenticatedUserInterface|string $user
*
* @throws \RuntimeException When no password hasher could be found for the user
*/
public function getPasswordHasher($user): PasswordHasherInterface;
}

View File

@ -0,0 +1,90 @@
<?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\PasswordHasher\Hasher;
use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException;
use Symfony\Component\PasswordHasher\Exception\LogicException;
use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface;
/**
* Pbkdf2PasswordHasher uses the PBKDF2 (Password-Based Key Derivation Function 2).
*
* Providing a high level of Cryptographic security,
* PBKDF2 is recommended by the National Institute of Standards and Technology (NIST).
*
* But also warrants a warning, using PBKDF2 (with a high number of iterations) slows down the process.
* PBKDF2 should be used with caution and care.
*
* @author Sebastiaan Stok <s.stok@rollerscapes.net>
* @author Andrew Johnson
* @author Fabien Potencier <fabien@symfony.com>
*/
final class Pbkdf2PasswordHasher implements LegacyPasswordHasherInterface
{
use CheckPasswordLengthTrait;
private $algorithm;
private $encodeHashAsBase64;
private $iterations = 1;
private $length;
private $encodedLength = -1;
/**
* @param string $algorithm The digest algorithm to use
* @param bool $encodeHashAsBase64 Whether to base64 encode the password hash
* @param int $iterations The number of iterations to use to stretch the password hash
* @param int $length Length of derived key to create
*/
public function __construct(string $algorithm = 'sha512', bool $encodeHashAsBase64 = true, int $iterations = 1000, int $length = 40)
{
$this->algorithm = $algorithm;
$this->encodeHashAsBase64 = $encodeHashAsBase64;
$this->length = $length;
try {
$this->encodedLength = \strlen($this->hash('', 'salt'));
} catch (\LogicException $e) {
// ignore unsupported algorithm
}
$this->iterations = $iterations;
}
public function hash(string $plainPassword, string $salt = null): string
{
if ($this->isPasswordTooLong($plainPassword)) {
throw new InvalidPasswordException();
}
if (!\in_array($this->algorithm, hash_algos(), true)) {
throw new LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm));
}
$digest = hash_pbkdf2($this->algorithm, $plainPassword, $salt, $this->iterations, $this->length, true);
return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest);
}
public function verify(string $hashedPassword, string $plainPassword, string $salt = null): bool
{
if (\strlen($hashedPassword) !== $this->encodedLength || false !== strpos($hashedPassword, '$')) {
return false;
}
return !$this->isPasswordTooLong($plainPassword) && hash_equals($hashedPassword, $this->hash($plainPassword, $salt));
}
public function needsRehash(string $hashedPassword): bool
{
return false;
}
}

View File

@ -0,0 +1,82 @@
<?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\PasswordHasher\Hasher;
use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException;
use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface;
/**
* PlaintextPasswordHasher does not do any hashing but is useful in testing environments.
*
* As this hasher is not cryptographically secure, usage of it in production environments is discouraged.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class PlaintextPasswordHasher implements LegacyPasswordHasherInterface
{
use CheckPasswordLengthTrait;
private $ignorePasswordCase;
/**
* @param bool $ignorePasswordCase Compare password case-insensitive
*/
public function __construct(bool $ignorePasswordCase = false)
{
$this->ignorePasswordCase = $ignorePasswordCase;
}
/**
* {@inheritdoc}
*/
public function hash(string $plainPassword, string $salt = null): string
{
if ($this->isPasswordTooLong($plainPassword)) {
throw new InvalidPasswordException();
}
return $this->mergePasswordAndSalt($plainPassword, $salt);
}
public function verify(string $hashedPassword, string $plainPassword, string $salt = null): bool
{
if ($this->isPasswordTooLong($plainPassword)) {
return false;
}
$pass2 = $this->mergePasswordAndSalt($plainPassword, $salt);
if (!$this->ignorePasswordCase) {
return hash_equals($hashedPassword, $pass2);
}
return hash_equals(strtolower($hashedPassword), strtolower($pass2));
}
public function needsRehash(string $hashedPassword): bool
{
return false;
}
private function mergePasswordAndSalt(string $password, ?string $salt): string
{
if (empty($salt)) {
return $password;
}
if (false !== strrpos($salt, '{') || false !== strrpos($salt, '}')) {
throw new \InvalidArgumentException('Cannot use { or } in salt.');
}
return $password.'{'.$salt.'}';
}
}

View File

@ -0,0 +1,114 @@
<?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\PasswordHasher\Hasher;
use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException;
use Symfony\Component\PasswordHasher\Exception\LogicException;
use Symfony\Component\PasswordHasher\PasswordHasherInterface;
/**
* Hashes passwords using libsodium.
*
* @author Robin Chalas <robin.chalas@gmail.com>
* @author Zan Baldwin <hello@zanbaldwin.com>
* @author Dominik Müller <dominik.mueller@jkweb.ch>
*/
final class SodiumPasswordHasher implements PasswordHasherInterface
{
use CheckPasswordLengthTrait;
private $opsLimit;
private $memLimit;
public function __construct(int $opsLimit = null, int $memLimit = null)
{
if (!self::isSupported()) {
throw new LogicException('Libsodium is not available. You should either install the sodium extension or use a different password hasher.');
}
$this->opsLimit = $opsLimit ?? max(4, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE : 4);
$this->memLimit = $memLimit ?? max(64 * 1024 * 1024, \defined('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE : 64 * 1024 * 1024);
if (3 > $this->opsLimit) {
throw new \InvalidArgumentException('$opsLimit must be 3 or greater.');
}
if (10 * 1024 > $this->memLimit) {
throw new \InvalidArgumentException('$memLimit must be 10k or greater.');
}
}
public static function isSupported(): bool
{
return version_compare(\extension_loaded('sodium') ? \SODIUM_LIBRARY_VERSION : phpversion('libsodium'), '1.0.14', '>=');
}
public function hash(string $plainPassword): string
{
if ($this->isPasswordTooLong($plainPassword)) {
throw new InvalidPasswordException();
}
if (\function_exists('sodium_crypto_pwhash_str')) {
return sodium_crypto_pwhash_str($plainPassword, $this->opsLimit, $this->memLimit);
}
if (\extension_loaded('libsodium')) {
return \Sodium\crypto_pwhash_str($plainPassword, $this->opsLimit, $this->memLimit);
}
throw new LogicException('Libsodium is not available. You should either install the sodium extension or use a different password hasher.');
}
public function verify(string $hashedPassword, string $plainPassword): bool
{
if ('' === $plainPassword) {
return false;
}
if ($this->isPasswordTooLong($plainPassword)) {
return false;
}
if (0 !== strpos($hashedPassword, '$argon')) {
if (0 === strpos($hashedPassword, '$2') && (72 < \strlen($plainPassword) || false !== strpos($plainPassword, "\0"))) {
$plainPassword = base64_encode(hash('sha512', $plainPassword, true));
}
// Accept validating non-argon passwords for seamless migrations
return password_verify($plainPassword, $hashedPassword);
}
if (\function_exists('sodium_crypto_pwhash_str_verify')) {
return sodium_crypto_pwhash_str_verify($hashedPassword, $plainPassword);
}
if (\extension_loaded('libsodium')) {
return \Sodium\crypto_pwhash_str_verify($hashedPassword, $plainPassword);
}
return false;
}
public function needsRehash(string $hashedPassword): bool
{
if (\function_exists('sodium_crypto_pwhash_str_needs_rehash')) {
return sodium_crypto_pwhash_str_needs_rehash($hashedPassword, $this->opsLimit, $this->memLimit);
}
if (\extension_loaded('libsodium')) {
return \Sodium\crypto_pwhash_str_needs_rehash($hashedPassword, $this->opsLimit, $this->memLimit);
}
throw new LogicException('Libsodium is not available. You should either install the sodium extension or use a different password hasher.');
}
}

View File

@ -0,0 +1,116 @@
<?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\PasswordHasher\Hasher;
use Symfony\Component\Security\Core\User\LegacyPasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* Hashes passwords based on the user and the PasswordHasherFactory.
*
* @author Ariel Ferrandini <arielferrandini@gmail.com>
*
* @final
*/
class UserPasswordHasher implements UserPasswordHasherInterface
{
private $hasherFactory;
public function __construct(PasswordHasherFactoryInterface $hasherFactory)
{
$this->hasherFactory = $hasherFactory;
}
/**
* @param PasswordAuthenticatedUserInterface $user
*/
public function hashPassword($user, string $plainPassword): string
{
if (!$user instanceof PasswordAuthenticatedUserInterface) {
if (!$user instanceof UserInterface) {
throw new \TypeError(sprintf('Expected an instance of "%s" as first argument, but got "%s".', UserInterface::class, get_debug_type($user)));
}
trigger_deprecation('symfony/password-hasher', '5.3', 'The "%s()" method expects a "%s" instance as first argument. Not implementing it in class "%s" is deprecated.', __METHOD__, PasswordAuthenticatedUserInterface::class, get_debug_type($user));
}
$salt = null;
if ($user instanceof LegacyPasswordAuthenticatedUserInterface) {
$salt = $user->getSalt();
} elseif ($user instanceof UserInterface) {
$salt = method_exists($user, 'getSalt') ? $user->getSalt() : null;
if ($salt) {
trigger_deprecation('symfony/password-hasher', '5.3', 'Returning a string from "getSalt()" without implementing the "%s" interface is deprecated, the "%s" class should implement it.', LegacyPasswordAuthenticatedUserInterface::class, get_debug_type($user));
}
}
$hasher = $this->hasherFactory->getPasswordHasher($user);
return $hasher->hash($plainPassword, $salt);
}
/**
* @param PasswordAuthenticatedUserInterface $user
*/
public function isPasswordValid($user, string $plainPassword): bool
{
if (!$user instanceof PasswordAuthenticatedUserInterface) {
if (!$user instanceof UserInterface) {
throw new \TypeError(sprintf('Expected an instance of "%s" as first argument, but got "%s".', UserInterface::class, get_debug_type($user)));
}
trigger_deprecation('symfony/password-hasher', '5.3', 'The "%s()" method expects a "%s" instance as first argument. Not implementing it in class "%s" is deprecated.', __METHOD__, PasswordAuthenticatedUserInterface::class, get_debug_type($user));
}
$salt = null;
if ($user instanceof LegacyPasswordAuthenticatedUserInterface) {
$salt = $user->getSalt();
} elseif ($user instanceof UserInterface) {
$salt = $user->getSalt();
if (null !== $salt) {
trigger_deprecation('symfony/password-hasher', '5.3', 'Returning a string from "getSalt()" without implementing the "%s" interface is deprecated, the "%s" class should implement it.', LegacyPasswordAuthenticatedUserInterface::class, get_debug_type($user));
}
}
if (null === $user->getPassword()) {
return false;
}
$hasher = $this->hasherFactory->getPasswordHasher($user);
return $hasher->verify($user->getPassword(), $plainPassword, $salt);
}
/**
* @param PasswordAuthenticatedUserInterface $user
*/
public function needsRehash($user): bool
{
if (null === $user->getPassword()) {
return false;
}
if (!$user instanceof PasswordAuthenticatedUserInterface) {
if (!$user instanceof UserInterface) {
throw new \TypeError(sprintf('Expected an instance of "%s" as first argument, but got "%s".', UserInterface::class, get_debug_type($user)));
}
trigger_deprecation('symfony/password-hasher', '5.3', 'The "%s()" method expects a "%s" instance as first argument. Not implementing it in class "%s" is deprecated.', __METHOD__, PasswordAuthenticatedUserInterface::class, get_debug_type($user));
}
$hasher = $this->hasherFactory->getPasswordHasher($user);
return $hasher->needsRehash($user->getPassword());
}
}

View File

@ -0,0 +1,27 @@
<?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\PasswordHasher\Hasher;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
/**
* Interface for the user password hasher service.
*
* @author Ariel Ferrandini <arielferrandini@gmail.com>
*
* @method string hashPassword(PasswordAuthenticatedUserInterface $user, string $plainPassword) Hashes the plain password for the given user.
* @method bool isPasswordValid(PasswordAuthenticatedUserInterface $user, string $plainPassword) Checks if the plaintext password matches the user's password.
* @method bool needsRehash(PasswordAuthenticatedUserInterface $user) Checks if an encoded password would benefit from rehashing.
*/
interface UserPasswordHasherInterface
{
}