maj: sémantique, révision vérification password #3
5
.env
|
@ -29,8 +29,9 @@ BASE_URL='http://localhost:8080'
|
|||
# connexion hydra
|
||||
HYDRA_ADMIN_BASE_URL='http://hydra:4445'
|
||||
APP_LOCALES="fr,en"
|
||||
|
||||
|
||||
SECURITY_PATTERN=
|
||||
NEW_HASH_ALGO=
|
||||
HASH_ALGO_LEGACY="sha256"
|
||||
###> symfony/lock ###
|
||||
# Choose one of the stores below
|
||||
# postgresql+advisory://db_user:db_password@localhost/db_name
|
||||
|
|
|
@ -6,9 +6,7 @@
|
|||
/config/secrets/prod/prod.decrypt.private.php
|
||||
/public/bundles/
|
||||
/var/
|
||||
###< symfony/framework-bundle ###
|
||||
supervisord.log
|
||||
supervisord.pid
|
||||
|
||||
/vendor
|
||||
/tools/php-cs-fixer/vendor
|
||||
###> symfony/webpack-encore-bundle ###
|
||||
|
@ -22,5 +20,5 @@ yarn-error.log
|
|||
/.config
|
||||
/.npm
|
||||
/.local
|
||||
supervisord.log
|
||||
supervisord.pid
|
||||
/supervisord.log
|
||||
/supervisord.pid
|
|
@ -10,7 +10,7 @@ security:
|
|||
# algorithm: 'sha256'
|
||||
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
|
||||
providers:
|
||||
pdo_user_provider:
|
||||
sql_login_provider:
|
||||
id: App\Security\SQLLoginUserProvider
|
||||
firewalls:
|
||||
dev:
|
||||
|
@ -19,7 +19,7 @@ security:
|
|||
main:
|
||||
# lazy: true
|
||||
stateless: false
|
||||
provider: pdo_user_provider
|
||||
provider: sql_login_provider
|
||||
custom_authenticators:
|
||||
- App\Security\SQLLoginUserAuthenticator
|
||||
|
||||
|
|
|
@ -9,8 +9,12 @@ parameters:
|
|||
database.user: "%env(resolve:DB_USER)%"
|
||||
database.password: "%env(resolve:DB_PASSWORD)%"
|
||||
|
||||
# algorythme de hahshage utilisé "md5", "sha256", "haval160,4", etc.
|
||||
hashAlgo: "sha256"
|
||||
# algorythme de hashage utilisé "md5", "sha256", "haval160,4", etc.
|
||||
env(HASH_ALGO_LEGACY): "sha256"
|
||||
hashAlgoLegacy: '%env(resolve:HASH_ALGO_LEGACY)%'
|
||||
|
||||
env(NEW_HASH_ALGO): "sha256"
|
||||
newHashAlgo: '%env(resolve:NEW_HASH_ALGO)%'
|
||||
|
||||
# adresse du site hote
|
||||
issuer_url: '%env(resolve:ISSUER_URL)%'
|
||||
|
@ -21,6 +25,7 @@ parameters:
|
|||
default_locale: '%env(DEFAULT_LOCALE)%'
|
||||
env(DEFAULT_LOCALE): 'fr'
|
||||
|
||||
security_pattern: '%env(resolve:SECURITY_PATTERN)%'
|
||||
|
||||
env(APP_LOCALES): "fr,en"
|
||||
locales: '%env(APP_LOCALES)%'
|
||||
app.supported_locales: ~
|
||||
|
@ -65,6 +70,8 @@ services:
|
|||
App\Security\Hasher\PasswordEncoder:
|
||||
arguments:
|
||||
$pepper: '%pepper%'
|
||||
$hashAlgo: '%hashAlgo%'
|
||||
$hashAlgoLegacy: '%hashAlgoLegacy%'
|
||||
$newHashAlgo: '%newHashAlgo%'
|
||||
$securityPattern: '%security_pattern%'
|
||||
# add more service definitions when explicit configuration is needed
|
||||
# please note that last definitions always *replace* previous ones
|
||||
|
|
|
@ -10,6 +10,6 @@ INSERT INTO usager (email, password, salt, lastname, firstname) VALUES
|
|||
('test3@test.com', '504ae1c3e2f5fdaf41f868164dabcef21e17059f5f388b452718a1ce92692c67', 'cesaltestunautreexemple', 'Dupont', 'Henri');
|
||||
|
||||
INSERT INTO usager (email, password, lastname, firstname) VALUES
|
||||
('test4@test.com', '50626fa21f45a275cea0efff13ff78fd02234cade322da08b7191c7e9150141d', 'Durand', 'Isabelle'),
|
||||
('test4@test.com', '$2a$12$zFN0VJ..Cuu.2itWQwmHJe5EUhNHazbMfCSJFpNiEfdwpLzjjDM0u', 'Durand', 'Isabelle'),
|
||||
('test2@test.com', '50626fa21f45a275cea0efff13ff78fd02234cade322da08b7191c7e9150141d', 'Dubois', 'Angela');
|
||||
GRANT ALL PRIVILEGES ON DATABASE usager TO lasql
|
||||
|
|
|
@ -37,6 +37,9 @@ services:
|
|||
- DB_PASSWORD=lasql
|
||||
- DEFAULT_LOCALE=fr
|
||||
- DSN_REMOTE_DATABASE=pgsql:host='postgres';port=5432;dbname=lasql;
|
||||
- HASH_ALGO_LEGACY=sha256
|
||||
- NEW_HASH_ALGO=bcrypt
|
||||
- SECURITY_PATTERN=password,salt,pepper
|
||||
|
||||
|
||||
oidc-test:
|
||||
|
|
|
@ -34,6 +34,10 @@ BASE_URL='http://localhost:8080'
|
|||
HYDRA_ADMIN_BASE_URL='http://hydra:4445'
|
||||
DSN_REMOTE_DATABASE="pgsql:host='postgres';port=5432;dbname=lasql"
|
||||
APP_LOCALES="fr,en"
|
||||
NEW_HASH_ALGO="argon2id"
|
||||
HASH_ALGO_LEGACY="sha256, bcrypt"
|
||||
SECURITY_PATTERN="password,salt,pepper"
|
||||
PEPPER
|
||||
```
|
||||
## Tests password
|
||||
|
||||
|
@ -69,7 +73,9 @@ DSN_REMOTE_DATABASE="mysql:host=mariadb;port=3306;dbname=lasql;"
|
|||
|test1@test.com| 8ad4025044b77ae6a5e3fcf99e53e44b15db9a4ecf468be21cbc6b9fbdae6d9f| cesaltestunexemple| Locke|John|
|
||||
|test2@test.com| 50626fa21f45a275cea0efff13ff78fd02234cade322da08b7191c7e9150141d| NULL| Dubois| Angela|
|
||||
|test3@test.com| 504ae1c3e2f5fdaf41f868164dabcef21e17059f5f388b452718a1ce92692c67| cesaltestunautreexemple| Dupont| Henri|
|
||||
|test4@test.com| 50626fa21f45a275cea0efff13ff78fd02234cade322da08b7191c7e9150141d| NULL| Durand|Isabelle|
|
||||
|test4@test.com| $2a$12$zFN0VJ..Cuu.2itWQwmHJe5EUhNHazbMfCSJFpNiEfdwpLzjjDM0u| NULL| Durand|Isabelle|
|
||||
|
||||
A noter que le hash de test4 est en bcrypt
|
||||
```
|
||||
|
||||
### mariadb (sans salt)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
rmasson marked this conversation as resolved
Outdated
wpetit
commented
Je pense que ce code ne sera pas compatible avec les applications ayant choisies d'utiliser les algorithmes Normalement, en PHP on utilise les méthodes Autre chose: la méthode assume un certain format pour la concaténation des différents éléments à hacher ( Je pense que ce code ne sera pas compatible avec les applications ayant choisies d'utiliser les algorithmes `argon2id`, `scrypt` et `bcrypt` (qui sont d'ailleurs les recommandations OWASP [^1] aujourd'hui).
Normalement, en PHP on utilise les méthodes `password_hash()` [^2] et `password_verify()` [^3] pour utiliser ces algorithmes (et d'ailleurs le salt est directement stocké dans le hash final [^4]).
Autre chose: la méthode assume un certain format pour la concaténation des différents éléments à hacher (`$plainPassword.$salt.$this->pepper`). Il est fort peu probable que cette séquence soit toujours respectée dans toutes les applications. À mon avis il serait certainement préférable d'utiliser un patron (avec la méthode `strstr()` [^5] par exemple) pour permettre à l'utilisateur de spécifiquer le format de concaténation.
[^1]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
[^2]: https://www.php.net/manual/en/function.password-hash.php
[^3]: https://www.php.net/manual/en/function.password-verify.php
[^4]: https://stackoverflow.com/questions/40993645/understanding-bcrypt-salt-as-used-by-php-password-hash
[^5]: https://www.php.net/manual/en/function.strtr.php
rmasson
commented
j'ai utilisé les fonction password_algos() et hash_algos() pour faire la différence entre les algos, pour utiliser hash() ou password_hash() Mais effectivement le pattern est pas pris en compte j'ai utilisé les fonction password_algos() et hash_algos() pour faire la différence entre les algos, pour utiliser hash() ou password_hash()
Mais effectivement le pattern est pas pris en compte
|
||||
$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();
|
||||
wpetit
commented
Il ne faut pas utiliser l'opérateur Il serait mieux d'utiliser https://www.php.net/manual/en/function.hash-equals.php je pense dans ce cas. Il ne faut pas utiliser l'opérateur `===` pour faire des comparaisons de mots de passe (ça ouvre à des failles du type "Timing attack" [^1]).
Il serait mieux d'utiliser https://www.php.net/manual/en/function.hash-equals.php je pense dans ce cas.
[^1]: https://www.chosenplaintext.ca/articles/beginners-guide-constant-time-cryptography.html
|
||||
}
|
||||
$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;
|
||||
wpetit
commented
Je pense que la variable n'a pas été renommée Je pense que la variable n'a pas été renommée `$pdoService` en accord avec le changement de nom du service.
|
||||
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)
|
||||
wpetit
commented
Idem, cf. commentaire précédent Idem, cf. [commentaire précédent](https://forge.cadoles.com/Cadoles/hydra-sql/pulls/3/files#issuecomment-55979)
|
||||
{
|
||||
$this->baseUrl = $baseUrl;
|
||||
$this->pdoService = $pdoService;
|
||||
$this->sqlLoginService = $sqlLoginService;
|
||||
wpetit
commented
Idem, cf. commentaire précédent Idem, cf. [commentaire précédent](https://forge.cadoles.com/Cadoles/hydra-sql/pulls/3/files#issuecomment-55979)
|
||||
$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();
|
||||
wpetit
commented
Idem, cf. commentaire précédent Idem, cf. [commentaire précédent](https://forge.cadoles.com/Cadoles/hydra-sql/pulls/3/files#issuecomment-55979)
|
||||
$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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
error:
|
||||
login: 'Incorrect login'
|
||||
password: 'Incorrect password'
|
||||
pdo: 'Connection to database encountered a problem'
|
||||
sql_login: 'Connection to database encountered a problem'
|
|
@ -1,4 +1,4 @@
|
|||
error:
|
||||
login: 'Login incorrect ou inconnu'
|
||||
password: 'Mot de passe incorrect'
|
||||
pdo: 'La connexion à la base de données a rencontré un problème'
|
||||
sql_login: 'La connexion à la base de données a rencontré un problème'
|
Hmm, je pense que par défaut le pepper devrait être vide.