ajout de l'updatde hashage selon algo indiqué en ver env, fix typo
Some checks reported warnings
Cadoles/hydra-sql/pipeline/pr-develop This commit is unstable
Some checks reported warnings
Cadoles/hydra-sql/pipeline/pr-develop This commit is unstable
This commit is contained in:
@ -36,9 +36,9 @@ class SecurityController extends AbstractController
|
||||
$loginForm->get('password')->addError(new FormError($trans->trans('error.password', [], 'messages')));
|
||||
$request->getSession()->remove(SQLLoginUserAuthenticator::ERROR_PASSWORD);
|
||||
}
|
||||
if ($request->getSession()->has(SQLLoginUserAuthenticator::ERROR_PDO)) {
|
||||
$loginForm->addError(new FormError($trans->trans('error.pdo', [], 'messages')));
|
||||
$request->getSession()->remove(SQLLoginUserAuthenticator::ERROR_PDO);
|
||||
if ($request->getSession()->has(SQLLoginUserAuthenticator::ERROR_SQL_LOGIN)) {
|
||||
$loginForm->addError(new FormError($trans->trans('error.sql_login', [], 'messages')));
|
||||
$request->getSession()->remove(SQLLoginUserAuthenticator::ERROR_SQL_LOGIN);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,18 +10,18 @@ use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||
class SQLLoginExtension extends Extension implements CompilerPassInterface
|
||||
{
|
||||
/** @var array */
|
||||
protected $pdoConfig;
|
||||
protected $sqlLoginConfig;
|
||||
|
||||
public function load(array $configs, ContainerBuilder $container)
|
||||
{
|
||||
$configuration = new SQLLoginConfiguration();
|
||||
$config = $this->processConfiguration($configuration, $configs);
|
||||
$this->pdoConfig = $config;
|
||||
$this->sqlLoginConfig = $config;
|
||||
}
|
||||
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
$definition = $container->getDefinition(SQLLoginRequest::class);
|
||||
$definition->replaceArgument('$config', $this->pdoConfig);
|
||||
$definition->replaceArgument('$config', $this->sqlLoginConfig);
|
||||
}
|
||||
}
|
||||
|
9
src/SQLLogin/Exception/InvalidSQLLoginAlgoException.php
Normal file
9
src/SQLLogin/Exception/InvalidSQLLoginAlgoException.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\SQLLogin\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
class InvalidSQLLoginAlgoException extends Exception
|
||||
{
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\SQLLogin\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
class InvalidSQLLoginConfigurationException extends Exception
|
||||
{
|
||||
}
|
@ -30,7 +30,7 @@ class SQLLoginConnect extends AbstractController
|
||||
*
|
||||
* @param void
|
||||
*
|
||||
* @return PdoConnect
|
||||
* @return SQLLoginConnect
|
||||
*/
|
||||
public static function getInstance()
|
||||
{
|
||||
|
@ -8,6 +8,7 @@ class SQLLoginRequest
|
||||
public const LOGIN_COLUMN_NAME = 'login_column_name';
|
||||
public const SALT_COLUMN_NAME = 'salt_column_name';
|
||||
public const PASSWORD_COLUMN_NAME = 'password_column_name';
|
||||
public const PASSWORD_NEED_UPGRADE = 'password_need_upgrade';
|
||||
public const TABLE_NAME = 'table_name';
|
||||
|
||||
protected array $config;
|
||||
@ -23,36 +24,46 @@ class SQLLoginRequest
|
||||
$this->password = $password;
|
||||
}
|
||||
|
||||
public function getDatabaseDsn()
|
||||
public function getDatabaseDsn(): string
|
||||
{
|
||||
return $this->dsn;
|
||||
}
|
||||
|
||||
public function getDbUser()
|
||||
public function getDbUser(): string
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function getDbPassword()
|
||||
public function getDbPassword(): string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
public function getLoginColumnName()
|
||||
public function getLoginColumnName(): string
|
||||
{
|
||||
return $this->config[self::LOGIN_COLUMN_NAME];
|
||||
}
|
||||
|
||||
public function egtPasswordColumnName()
|
||||
public function getPasswordColumnName(): string
|
||||
{
|
||||
return $this->config[self::PASSWORD_COLUMN_NAME];
|
||||
}
|
||||
|
||||
public function getSaltColumnName()
|
||||
public function getSaltColumnName(): ?string
|
||||
{
|
||||
return $this->config[self::SALT_COLUMN_NAME];
|
||||
}
|
||||
|
||||
public function getTableName(): string
|
||||
{
|
||||
return $this->config[self::TABLE_NAME];
|
||||
}
|
||||
|
||||
public function getDataToFetch(): array
|
||||
{
|
||||
return $this->config[self::DATA_TO_FETCH];
|
||||
}
|
||||
|
||||
public function getRequestScope()
|
||||
{
|
||||
$scope = '';
|
||||
@ -60,18 +71,32 @@ class SQLLoginRequest
|
||||
$scope .= $data.',';
|
||||
}
|
||||
$scope = substr($scope, 0, -1);
|
||||
$request = 'SELECT '.$scope.' FROM '.$this->config[self::TABLE_NAME].' WHERE '.$this->config[self::LOGIN_COLUMN_NAME].' = :'.$this->config[self::LOGIN_COLUMN_NAME].';';
|
||||
|
||||
return $request;
|
||||
return 'SELECT '.$scope.' FROM '.$this->getTableName().' WHERE '.$this->getLoginColumnName().' = :'.$this->getLoginColumnName().';';
|
||||
}
|
||||
|
||||
/**
|
||||
* Construction de la string pour la requête préparée selon la configuration yaml
|
||||
* intègre la récupération du mot de passe hashé, du salt et de besoin d'upgrade de la méthode de hashage
|
||||
*/
|
||||
public function getRequestPassword()
|
||||
{
|
||||
$passwordColumns = $this->config[self::PASSWORD_COLUMN_NAME];
|
||||
if (!empty($this->config[self::SALT_COLUMN_NAME])) {
|
||||
$passwordColumns .= ', '.$this->config[self::SALT_COLUMN_NAME];
|
||||
$fields = $this->getPasswordColumnName();
|
||||
if (!empty($this->getSaltColumnName())) {
|
||||
$fields .= ', '.$this->getSaltColumnName();
|
||||
}
|
||||
|
||||
return 'SELECT '.$passwordColumns.' FROM '.$this->config[self::TABLE_NAME].' WHERE '.$this->config[self::LOGIN_COLUMN_NAME].' = :'.$this->config[self::LOGIN_COLUMN_NAME].';';
|
||||
return 'SELECT '.$fields.' FROM '.$this->getTableName().' WHERE '.$this->getLoginColumnName().' = :'.$this->getLoginColumnName().';';
|
||||
}
|
||||
|
||||
public function getRequestUpdatePassword()
|
||||
{
|
||||
$fieldsToUpdate = $this->getPasswordColumnName().'= :'.$this->getPasswordColumnName().',';
|
||||
if (!empty($this->getSaltColumnName())) {
|
||||
$fieldsToUpdate .= $this->getSaltColumnName().'= :'.$this->getSaltColumnName().',';
|
||||
}
|
||||
$fieldsToUpdate = substr($fieldsToUpdate, 0, -1);
|
||||
|
||||
return 'UPDATE '.$this->getTableName().' SET '.$fieldsToUpdate.' WHERE '.$this->getLoginColumnName().' = :'.$this->getLoginColumnName().';';
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Security\Hasher;
|
||||
|
||||
use App\SQLLogin\Exception\InvalidSQLLoginAlgoException;
|
||||
use App\SQLLogin\Exception\InvalidSQLLoginConfigurationException;
|
||||
use App\SQLLogin\Exception\InvalidSQLPasswordException;
|
||||
use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException;
|
||||
use Symfony\Component\PasswordHasher\Hasher\CheckPasswordLengthTrait;
|
||||
@ -10,23 +12,37 @@ use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface;
|
||||
class PasswordEncoder implements LegacyPasswordHasherInterface
|
||||
{
|
||||
use CheckPasswordLengthTrait;
|
||||
protected ?string $pepper;
|
||||
protected string $hashAlgo;
|
||||
public const PASSWORD_PATTERN = 'password';
|
||||
public const SALT_PATTERN = 'salt';
|
||||
public const PEPPER_PATTERN = 'pepper';
|
||||
|
||||
public function __construct(?string $pepper, string $hashAlgo)
|
||||
protected ?string $pepper;
|
||||
protected array $hashAlgoLegacy;
|
||||
protected ?string $newHashAlgo;
|
||||
protected array $securityPattern;
|
||||
|
||||
public function __construct(?string $pepper, string $hashAlgoLegacy, ?string $newHashAlgo, string $securityPattern)
|
||||
{
|
||||
$this->pepper = $pepper;
|
||||
$this->hashAlgo = $hashAlgo;
|
||||
$this->hashAlgoLegacy = explode(',', $hashAlgoLegacy);
|
||||
$this->newHashAlgo = $newHashAlgo;
|
||||
$this->securityPattern = explode(',', $securityPattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utilise l'algo legacy
|
||||
*/
|
||||
public function hash(string $plainPassword, string $salt = null): string
|
||||
{
|
||||
if ($this->isPasswordTooLong($plainPassword)) {
|
||||
throw new InvalidPasswordException();
|
||||
}
|
||||
$hash = hash($this->hashAlgo, $plainPassword.$salt.$this->pepper);
|
||||
$completedPlainPassword = $this->getPasswordToHash($plainPassword, $salt);
|
||||
if ($this->isObsoleteAlgo($this->newHashAlgo)) {
|
||||
throw new InvalidSQLLoginAlgoException();
|
||||
}
|
||||
|
||||
return $hash;
|
||||
return password_hash($completedPlainPassword, $this->getValidALgo($this->newHashAlgo));
|
||||
}
|
||||
|
||||
public function verify(string $hashedPassword, string $plainPassword, string $salt = null): bool
|
||||
@ -35,7 +51,36 @@ class PasswordEncoder implements LegacyPasswordHasherInterface
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hash($plainPassword, $salt) === $hashedPassword) {
|
||||
$isNewest = password_get_info($hashedPassword)['algo'] === $this->newHashAlgo;
|
||||
|
||||
$completedPassword = $this->getPasswordToHash($plainPassword, $salt);
|
||||
if ($isNewest) {
|
||||
if (password_verify($completedPassword, $hashedPassword)) {
|
||||
return true;
|
||||
} else {
|
||||
throw new InvalidSQLPasswordException();
|
||||
}
|
||||
}
|
||||
|
||||
// Si le mot de passe a besoin d'un rehash ou qu'il n'y a pas de nouvelle méthode indiquée, on sait qu'il faut utiliser l'une des méthodes legacy pour vérifier le mot de passe
|
||||
if ($this->needsRehash($hashedPassword) || empty($this->newHashAlgo)) {
|
||||
foreach ($this->hashAlgoLegacy as $algo) {
|
||||
if ($this->isObsoleteAlgo($algo)) {
|
||||
if (hash_equals(hash($algo, $completedPassword), $hashedPassword)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (password_verify($completedPassword, $hashedPassword)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// On vérifie si la méthode legacy est obsolète, si oui on ne peut pas utiliser password_verify, on doit hasher et comparer
|
||||
}
|
||||
// si on on n'a pas encore retourné de résultat, le mot de passe doit être incorrect
|
||||
throw new InvalidSQLPasswordException();
|
||||
}
|
||||
// Si on n'est pas rentré dans les conditions précedéntes, on peut utiliser password_verify
|
||||
if (password_verify($completedPassword, $hashedPassword)) {
|
||||
return true;
|
||||
} else {
|
||||
throw new InvalidSQLPasswordException();
|
||||
@ -44,6 +89,74 @@ class PasswordEncoder implements LegacyPasswordHasherInterface
|
||||
|
||||
public function needsRehash(string $hashedPassword): bool
|
||||
{
|
||||
// Il y a besoin de tester si on veut mettre à jour le hashage de mot de passe uniquement si le nouveau algo de hashage est indiqué et qu'il est moderne (BCRYPT, ARGON, ...)
|
||||
if (!empty($this->newHashAlgo) && !$this->isObsoleteAlgo($this->newHashAlgo)) {
|
||||
return password_needs_rehash($hashedPassword, $this->getValidAlgo($this->newHashAlgo));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $algo
|
||||
*
|
||||
* @return InvalidSQLLoginAlgoException|string
|
||||
*/
|
||||
public function getValidAlgo($algo)
|
||||
{
|
||||
if ($algo) {
|
||||
if (in_array($algo, hash_algos())) {
|
||||
return $algo;
|
||||
}
|
||||
$informalAlgos = [
|
||||
'default' => PASSWORD_DEFAULT,
|
||||
'bcrypt' => PASSWORD_BCRYPT,
|
||||
'argon2i' => PASSWORD_ARGON2I,
|
||||
'argon2id' => PASSWORD_ARGON2ID,
|
||||
];
|
||||
if (in_array($algo, array_keys($informalAlgos))) {
|
||||
return $informalAlgos[$algo];
|
||||
}
|
||||
if (in_array($algo, password_algos())) {
|
||||
return $algo;
|
||||
}
|
||||
|
||||
throw new InvalidSQLLoginAlgoException('Invalid Algorythme');
|
||||
}
|
||||
}
|
||||
|
||||
public function isObsoleteAlgo($algo): bool
|
||||
{
|
||||
return in_array($algo, hash_algos());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la string à hasher en fonction du pattern indiqué
|
||||
*
|
||||
* @param mixed $plainTextPassword
|
||||
* @param mixed $salt
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getPasswordToHash($plainTextPassword, $salt)
|
||||
{
|
||||
$arrayRef = [
|
||||
self::PASSWORD_PATTERN => $plainTextPassword,
|
||||
self::SALT_PATTERN => $salt,
|
||||
self::PEPPER_PATTERN => $this->pepper,
|
||||
];
|
||||
|
||||
foreach ($this->securityPattern as $term) {
|
||||
if (self::PEPPER_PATTERN !== $term && self::PASSWORD_PATTERN !== $term && self::SALT_PATTERN !== $term) {
|
||||
throw new InvalidSQLLoginConfigurationException();
|
||||
}
|
||||
}
|
||||
$completedArray = [];
|
||||
foreach ($this->securityPattern as $term) {
|
||||
$completedArray[] = $arrayRef[$term];
|
||||
}
|
||||
$completedPlainPassword = implode($completedArray);
|
||||
|
||||
return $completedPlainPassword;
|
||||
}
|
||||
}
|
||||
|
@ -25,17 +25,17 @@ class SQLLoginUserAuthenticator extends AbstractAuthenticator
|
||||
public const LOGIN_ROUTE = 'app_login';
|
||||
public const ERROR_LOGIN = 'error_login';
|
||||
public const ERROR_PASSWORD = 'error_password';
|
||||
public const ERROR_PDO = 'error_pdo';
|
||||
public const ERROR_SQL_LOGIN = 'error_sql_login';
|
||||
|
||||
protected string $baseUrl;
|
||||
private SQLLoginService $pdoService;
|
||||
private SQLLoginService $sqlLoginService;
|
||||
private UrlGeneratorInterface $router;
|
||||
private PasswordEncoder $passwordHasher;
|
||||
|
||||
public function __construct(string $baseUrl, SQLLoginService $pdoService, UrlGeneratorInterface $router, PasswordEncoder $passwordHasher)
|
||||
public function __construct(string $baseUrl, SQLLoginService $sqlLoginService, UrlGeneratorInterface $router, PasswordEncoder $passwordHasher)
|
||||
{
|
||||
$this->baseUrl = $baseUrl;
|
||||
$this->pdoService = $pdoService;
|
||||
$this->sqlLoginService = $sqlLoginService;
|
||||
$this->router = $router;
|
||||
$this->passwordHasher = $passwordHasher;
|
||||
}
|
||||
@ -72,16 +72,21 @@ class SQLLoginUserAuthenticator extends AbstractAuthenticator
|
||||
$rememberMe = isset($form['_remember_me']) ? true : false;
|
||||
try {
|
||||
// requête préparée
|
||||
list($remoteHashedPassword, $remoteSalt) = $this->pdoService->fetchPassword($login);
|
||||
list($remoteHashedPassword, $remoteSalt) = $this->sqlLoginService->fetchPassword($login);
|
||||
} catch (PDOException $e) {
|
||||
$request->getSession()->set(self::ERROR_PDO, true);
|
||||
$request->getSession()->set(self::ERROR_SQL_LOGIN, true);
|
||||
throw new AuthenticationException();
|
||||
}
|
||||
if ($remoteHashedPassword) {
|
||||
try {
|
||||
// Comparaison remote hash et hash du input password + salt
|
||||
// dump($remoteHashedPassword, $plaintextPassword, $remoteSalt, password_verify($plaintextPassword, $remoteHashedPassword));
|
||||
$this->passwordHasher->verify($remoteHashedPassword, $plaintextPassword, $remoteSalt);
|
||||
$attributes = $this->pdoService->fetchDatas($login);
|
||||
if ($this->passwordHasher->needsRehash($remoteHashedPassword)) {
|
||||
$hash = $this->passwordHasher->hash($plaintextPassword);
|
||||
$this->sqlLoginService->updatePassword($login, $hash, null);
|
||||
}
|
||||
$attributes = $this->sqlLoginService->fetchDatas($login);
|
||||
$user = new User($login, $remoteHashedPassword, $attributes, $rememberMe);
|
||||
|
||||
$loader = function (string $userIdentifier) use ($user) {
|
||||
@ -98,7 +103,7 @@ class SQLLoginUserAuthenticator extends AbstractAuthenticator
|
||||
$request->getSession()->set(self::ERROR_PASSWORD, true);
|
||||
throw new AuthenticationException();
|
||||
} catch (PDOException $e) {
|
||||
$request->getSession()->set(self::ERROR_PDO, true);
|
||||
$request->getSession()->set(self::ERROR_SQL_LOGIN, true);
|
||||
throw new AuthenticationException();
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ namespace App\Security;
|
||||
|
||||
use App\Entity\User;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
@ -13,10 +12,9 @@ class SQLLoginUserProvider implements UserProviderInterface
|
||||
{
|
||||
protected RequestStack $requestStack;
|
||||
|
||||
public function __construct(RequestStack $requestStack, SessionInterface $session)
|
||||
public function __construct(RequestStack $requestStack)
|
||||
{
|
||||
$this->requestStack = $requestStack;
|
||||
$this->session = $session;
|
||||
}
|
||||
|
||||
public function loadUserByIdentifier(string $identifier, ?User $user): ?UserInterface
|
||||
|
@ -56,12 +56,11 @@ class SQLLoginService extends AbstractController
|
||||
$password = $query->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
\Sentry\captureException($e);
|
||||
dd($e);
|
||||
throw new PDOException();
|
||||
}
|
||||
if ($password) {
|
||||
return [
|
||||
$password[$this->sqlLoginRequest->egtPasswordColumnName()],
|
||||
$password[$this->sqlLoginRequest->getPasswordColumnName()],
|
||||
isset($password[$this->sqlLoginRequest->getSaltColumnName()]) ? $password[$this->sqlLoginRequest->getSaltColumnName()] : null,
|
||||
];
|
||||
}
|
||||
@ -69,11 +68,28 @@ class SQLLoginService extends AbstractController
|
||||
return false;
|
||||
}
|
||||
|
||||
public function updatePassword($login, $hashedPassword, $salt)
|
||||
{
|
||||
try {
|
||||
$dbh = $this->getConnection();
|
||||
$request = $this->sqlLoginRequest->getRequestUpdatePassword();
|
||||
$query = $dbh->prepare($request);
|
||||
$query->execute([
|
||||
$this->sqlLoginRequest->getLoginColumnName() => $login,
|
||||
$this->sqlLoginRequest->getPasswordColumnName() => $hashedPassword,
|
||||
$this->sqlLoginRequest->getSaltColumnName() => $salt,
|
||||
]);
|
||||
} catch (PDOException $e) {
|
||||
\Sentry\captureException($e);
|
||||
throw new PDOException();
|
||||
}
|
||||
}
|
||||
|
||||
public function getConnection()
|
||||
{
|
||||
// Appel du singleton
|
||||
$pdo = SQLLoginConnect::getInstance();
|
||||
$sqlLogin = SQLLoginConnect::getInstance();
|
||||
// Connection bdd
|
||||
return $pdo->connect($this->sqlLoginRequest->getDatabaseDsn(), $this->sqlLoginRequest->getDbUser(), $this->sqlLoginRequest->getDbPassword());
|
||||
return $sqlLogin->connect($this->sqlLoginRequest->getDatabaseDsn(), $this->sqlLoginRequest->getDbUser(), $this->sqlLoginRequest->getDbPassword());
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user