maj: sémantique, révision vérification password #3

Merged
cmsassot merged 6 commits from maj into develop 2023-01-05 14:09:41 +01:00
19 changed files with 247 additions and 57 deletions
Showing only changes of commit bd1b035f1e - Show all commits

5
.env
View File

@ -29,8 +29,9 @@ BASE_URL='http://localhost:8080'
# connexion hydra # connexion hydra
HYDRA_ADMIN_BASE_URL='http://hydra:4445' HYDRA_ADMIN_BASE_URL='http://hydra:4445'
APP_LOCALES="fr,en" APP_LOCALES="fr,en"
SECURITY_PATTERN=
NEW_HASH_ALGO=
HASH_ALGO_LEGACY="sha256"
###> symfony/lock ### ###> symfony/lock ###
# Choose one of the stores below # Choose one of the stores below
# postgresql+advisory://db_user:db_password@localhost/db_name # postgresql+advisory://db_user:db_password@localhost/db_name

8
.gitignore vendored
View File

@ -6,9 +6,7 @@
/config/secrets/prod/prod.decrypt.private.php /config/secrets/prod/prod.decrypt.private.php
/public/bundles/ /public/bundles/
/var/ /var/
###< symfony/framework-bundle ###
supervisord.log
supervisord.pid
/vendor /vendor
/tools/php-cs-fixer/vendor /tools/php-cs-fixer/vendor
###> symfony/webpack-encore-bundle ### ###> symfony/webpack-encore-bundle ###
@ -22,5 +20,5 @@ yarn-error.log
/.config /.config
/.npm /.npm
/.local /.local
supervisord.log /supervisord.log
supervisord.pid /supervisord.pid

View File

@ -10,7 +10,7 @@ security:
# algorithm: 'sha256' # algorithm: 'sha256'
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers: providers:
pdo_user_provider: sql_login_provider:
id: App\Security\SQLLoginUserProvider id: App\Security\SQLLoginUserProvider
firewalls: firewalls:
dev: dev:
@ -19,7 +19,7 @@ security:
main: main:
# lazy: true # lazy: true
stateless: false stateless: false
provider: pdo_user_provider provider: sql_login_provider
custom_authenticators: custom_authenticators:
- App\Security\SQLLoginUserAuthenticator - App\Security\SQLLoginUserAuthenticator

View File

@ -9,8 +9,12 @@ parameters:
database.user: "%env(resolve:DB_USER)%" database.user: "%env(resolve:DB_USER)%"
database.password: "%env(resolve:DB_PASSWORD)%" database.password: "%env(resolve:DB_PASSWORD)%"
# algorythme de hahshage utilisé "md5", "sha256", "haval160,4", etc. # algorythme de hashage utilisé "md5", "sha256", "haval160,4", etc.
hashAlgo: "sha256" 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 # adresse du site hote
issuer_url: '%env(resolve:ISSUER_URL)%' issuer_url: '%env(resolve:ISSUER_URL)%'
@ -21,6 +25,7 @@ parameters:
default_locale: '%env(DEFAULT_LOCALE)%' default_locale: '%env(DEFAULT_LOCALE)%'
env(DEFAULT_LOCALE): 'fr' env(DEFAULT_LOCALE): 'fr'
security_pattern: '%env(resolve:SECURITY_PATTERN)%'

Hmm, je pense que par défaut le pepper devrait être vide.

Hmm, je pense que par défaut le pepper devrait être vide.
env(APP_LOCALES): "fr,en" env(APP_LOCALES): "fr,en"
locales: '%env(APP_LOCALES)%' locales: '%env(APP_LOCALES)%'
app.supported_locales: ~ app.supported_locales: ~
@ -65,6 +70,8 @@ services:
App\Security\Hasher\PasswordEncoder: App\Security\Hasher\PasswordEncoder:
arguments: arguments:
$pepper: '%pepper%' $pepper: '%pepper%'
$hashAlgo: '%hashAlgo%' $hashAlgoLegacy: '%hashAlgoLegacy%'
$newHashAlgo: '%newHashAlgo%'
$securityPattern: '%security_pattern%'
# add more service definitions when explicit configuration is needed # add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones # please note that last definitions always *replace* previous ones

View File

@ -10,6 +10,6 @@ INSERT INTO usager (email, password, salt, lastname, firstname) VALUES
('test3@test.com', '504ae1c3e2f5fdaf41f868164dabcef21e17059f5f388b452718a1ce92692c67', 'cesaltestunautreexemple', 'Dupont', 'Henri'); ('test3@test.com', '504ae1c3e2f5fdaf41f868164dabcef21e17059f5f388b452718a1ce92692c67', 'cesaltestunautreexemple', 'Dupont', 'Henri');
INSERT INTO usager (email, password, lastname, firstname) VALUES 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'); ('test2@test.com', '50626fa21f45a275cea0efff13ff78fd02234cade322da08b7191c7e9150141d', 'Dubois', 'Angela');
GRANT ALL PRIVILEGES ON DATABASE usager TO lasql GRANT ALL PRIVILEGES ON DATABASE usager TO lasql

View File

@ -37,6 +37,9 @@ services:
- DB_PASSWORD=lasql - DB_PASSWORD=lasql
- DEFAULT_LOCALE=fr - DEFAULT_LOCALE=fr
- DSN_REMOTE_DATABASE=pgsql:host='postgres';port=5432;dbname=lasql; - 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: oidc-test:

View File

@ -34,6 +34,10 @@ BASE_URL='http://localhost:8080'
HYDRA_ADMIN_BASE_URL='http://hydra:4445' HYDRA_ADMIN_BASE_URL='http://hydra:4445'
DSN_REMOTE_DATABASE="pgsql:host='postgres';port=5432;dbname=lasql" DSN_REMOTE_DATABASE="pgsql:host='postgres';port=5432;dbname=lasql"
APP_LOCALES="fr,en" APP_LOCALES="fr,en"
NEW_HASH_ALGO="argon2id"
HASH_ALGO_LEGACY="sha256, bcrypt"
SECURITY_PATTERN="password,salt,pepper"
PEPPER
``` ```
## Tests password ## Tests password
@ -69,7 +73,9 @@ DSN_REMOTE_DATABASE="mysql:host=mariadb;port=3306;dbname=lasql;"
|test1@test.com| 8ad4025044b77ae6a5e3fcf99e53e44b15db9a4ecf468be21cbc6b9fbdae6d9f| cesaltestunexemple| Locke|John| |test1@test.com| 8ad4025044b77ae6a5e3fcf99e53e44b15db9a4ecf468be21cbc6b9fbdae6d9f| cesaltestunexemple| Locke|John|
|test2@test.com| 50626fa21f45a275cea0efff13ff78fd02234cade322da08b7191c7e9150141d| NULL| Dubois| Angela| |test2@test.com| 50626fa21f45a275cea0efff13ff78fd02234cade322da08b7191c7e9150141d| NULL| Dubois| Angela|
|test3@test.com| 504ae1c3e2f5fdaf41f868164dabcef21e17059f5f388b452718a1ce92692c67| cesaltestunautreexemple| Dupont| Henri| |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) ### mariadb (sans salt)

View File

@ -36,9 +36,9 @@ class SecurityController extends AbstractController
$loginForm->get('password')->addError(new FormError($trans->trans('error.password', [], 'messages'))); $loginForm->get('password')->addError(new FormError($trans->trans('error.password', [], 'messages')));
$request->getSession()->remove(SQLLoginUserAuthenticator::ERROR_PASSWORD); $request->getSession()->remove(SQLLoginUserAuthenticator::ERROR_PASSWORD);
} }
if ($request->getSession()->has(SQLLoginUserAuthenticator::ERROR_PDO)) { if ($request->getSession()->has(SQLLoginUserAuthenticator::ERROR_SQL_LOGIN)) {
$loginForm->addError(new FormError($trans->trans('error.pdo', [], 'messages'))); $loginForm->addError(new FormError($trans->trans('error.sql_login', [], 'messages')));
$request->getSession()->remove(SQLLoginUserAuthenticator::ERROR_PDO); $request->getSession()->remove(SQLLoginUserAuthenticator::ERROR_SQL_LOGIN);
} }
} }

View File

@ -10,18 +10,18 @@ use Symfony\Component\DependencyInjection\Extension\Extension;
class SQLLoginExtension extends Extension implements CompilerPassInterface class SQLLoginExtension extends Extension implements CompilerPassInterface
{ {
/** @var array */ /** @var array */
protected $pdoConfig; protected $sqlLoginConfig;
public function load(array $configs, ContainerBuilder $container) public function load(array $configs, ContainerBuilder $container)
{ {
$configuration = new SQLLoginConfiguration(); $configuration = new SQLLoginConfiguration();
$config = $this->processConfiguration($configuration, $configs); $config = $this->processConfiguration($configuration, $configs);
$this->pdoConfig = $config; $this->sqlLoginConfig = $config;
} }
public function process(ContainerBuilder $container) public function process(ContainerBuilder $container)
{ {
$definition = $container->getDefinition(SQLLoginRequest::class); $definition = $container->getDefinition(SQLLoginRequest::class);
$definition->replaceArgument('$config', $this->pdoConfig); $definition->replaceArgument('$config', $this->sqlLoginConfig);
} }
} }

View File

@ -0,0 +1,9 @@
<?php
namespace App\SQLLogin\Exception;
use Exception;
class InvalidSQLLoginAlgoException extends Exception
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace App\SQLLogin\Exception;
use Exception;
class InvalidSQLLoginConfigurationException extends Exception
{
}

View File

@ -30,7 +30,7 @@ class SQLLoginConnect extends AbstractController
* *
* @param void * @param void
* *
* @return PdoConnect * @return SQLLoginConnect
*/ */
public static function getInstance() public static function getInstance()
{ {

View File

@ -8,6 +8,7 @@ class SQLLoginRequest
public const LOGIN_COLUMN_NAME = 'login_column_name'; public const LOGIN_COLUMN_NAME = 'login_column_name';
public const SALT_COLUMN_NAME = 'salt_column_name'; public const SALT_COLUMN_NAME = 'salt_column_name';
public const PASSWORD_COLUMN_NAME = 'password_column_name'; public const PASSWORD_COLUMN_NAME = 'password_column_name';
public const PASSWORD_NEED_UPGRADE = 'password_need_upgrade';
public const TABLE_NAME = 'table_name'; public const TABLE_NAME = 'table_name';
protected array $config; protected array $config;
@ -23,36 +24,46 @@ class SQLLoginRequest
$this->password = $password; $this->password = $password;
} }
public function getDatabaseDsn() public function getDatabaseDsn(): string
{ {
return $this->dsn; return $this->dsn;
} }
public function getDbUser() public function getDbUser(): string
{ {
return $this->user; return $this->user;
} }
public function getDbPassword() public function getDbPassword(): string
{ {
return $this->password; return $this->password;
} }
public function getLoginColumnName() public function getLoginColumnName(): string
{ {
return $this->config[self::LOGIN_COLUMN_NAME]; return $this->config[self::LOGIN_COLUMN_NAME];
} }
public function egtPasswordColumnName() public function getPasswordColumnName(): string
{ {
return $this->config[self::PASSWORD_COLUMN_NAME]; return $this->config[self::PASSWORD_COLUMN_NAME];
} }
public function getSaltColumnName() public function getSaltColumnName(): ?string
{ {
return $this->config[self::SALT_COLUMN_NAME]; 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() public function getRequestScope()
{ {
$scope = ''; $scope = '';
@ -60,18 +71,32 @@ class SQLLoginRequest
$scope .= $data.','; $scope .= $data.',';
} }
$scope = substr($scope, 0, -1); $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() public function getRequestPassword()
{ {
$passwordColumns = $this->config[self::PASSWORD_COLUMN_NAME]; $fields = $this->getPasswordColumnName();
if (!empty($this->config[self::SALT_COLUMN_NAME])) { if (!empty($this->getSaltColumnName())) {
$passwordColumns .= ', '.$this->config[self::SALT_COLUMN_NAME]; $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().';';
} }
} }

View File

@ -2,6 +2,8 @@
namespace App\Security\Hasher; namespace App\Security\Hasher;
use App\SQLLogin\Exception\InvalidSQLLoginAlgoException;
use App\SQLLogin\Exception\InvalidSQLLoginConfigurationException;
use App\SQLLogin\Exception\InvalidSQLPasswordException; use App\SQLLogin\Exception\InvalidSQLPasswordException;
use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException;
use Symfony\Component\PasswordHasher\Hasher\CheckPasswordLengthTrait; use Symfony\Component\PasswordHasher\Hasher\CheckPasswordLengthTrait;
@ -10,23 +12,37 @@ use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface;
class PasswordEncoder implements LegacyPasswordHasherInterface class PasswordEncoder implements LegacyPasswordHasherInterface
{ {
use CheckPasswordLengthTrait; use CheckPasswordLengthTrait;
protected ?string $pepper; public const PASSWORD_PATTERN = 'password';
protected string $hashAlgo; 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->pepper = $pepper;
$this->hashAlgo = $hashAlgo; $this->hashAlgoLegacy = explode(',', $hashAlgoLegacy);
rmasson marked this conversation as resolved Outdated

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.

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

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 public function hash(string $plainPassword, string $salt = null): string
{ {
if ($this->isPasswordTooLong($plainPassword)) { if ($this->isPasswordTooLong($plainPassword)) {
throw new InvalidPasswordException(); throw new InvalidPasswordException();

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.

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 public function verify(string $hashedPassword, string $plainPassword, string $salt = null): bool
@ -35,7 +51,36 @@ class PasswordEncoder implements LegacyPasswordHasherInterface
return false; 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; return true;
} else { } else {
throw new InvalidSQLPasswordException(); throw new InvalidSQLPasswordException();
@ -44,6 +89,74 @@ class PasswordEncoder implements LegacyPasswordHasherInterface
public function needsRehash(string $hashedPassword): bool 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; 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;
}
} }

View File

@ -25,17 +25,17 @@ class SQLLoginUserAuthenticator extends AbstractAuthenticator
public const LOGIN_ROUTE = 'app_login'; public const LOGIN_ROUTE = 'app_login';
public const ERROR_LOGIN = 'error_login'; public const ERROR_LOGIN = 'error_login';
public const ERROR_PASSWORD = 'error_password'; public const ERROR_PASSWORD = 'error_password';
public const ERROR_PDO = 'error_pdo'; public const ERROR_SQL_LOGIN = 'error_sql_login';
protected string $baseUrl; protected string $baseUrl;
private SQLLoginService $pdoService; private SQLLoginService $sqlLoginService;

Je pense que la variable n'a pas été renommée $pdoService en accord avec le changement de nom du service.

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 UrlGeneratorInterface $router;
private PasswordEncoder $passwordHasher; 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)
Idem, cf. [commentaire précédent](https://forge.cadoles.com/Cadoles/hydra-sql/pulls/3/files#issuecomment-55979)
{ {
$this->baseUrl = $baseUrl; $this->baseUrl = $baseUrl;
$this->pdoService = $pdoService; $this->sqlLoginService = $sqlLoginService;
Idem, cf. [commentaire précédent](https://forge.cadoles.com/Cadoles/hydra-sql/pulls/3/files#issuecomment-55979)
$this->router = $router; $this->router = $router;
$this->passwordHasher = $passwordHasher; $this->passwordHasher = $passwordHasher;
} }
@ -72,16 +72,21 @@ class SQLLoginUserAuthenticator extends AbstractAuthenticator
$rememberMe = isset($form['_remember_me']) ? true : false; $rememberMe = isset($form['_remember_me']) ? true : false;
try { try {
// requête préparée // requête préparée
list($remoteHashedPassword, $remoteSalt) = $this->pdoService->fetchPassword($login); list($remoteHashedPassword, $remoteSalt) = $this->sqlLoginService->fetchPassword($login);
} catch (PDOException $e) { } catch (PDOException $e) {
$request->getSession()->set(self::ERROR_PDO, true); $request->getSession()->set(self::ERROR_SQL_LOGIN, true);
throw new AuthenticationException(); throw new AuthenticationException();
} }
if ($remoteHashedPassword) { if ($remoteHashedPassword) {
try { try {
// Comparaison remote hash et hash du input password + salt // Comparaison remote hash et hash du input password + salt
// dump($remoteHashedPassword, $plaintextPassword, $remoteSalt, password_verify($plaintextPassword, $remoteHashedPassword));
$this->passwordHasher->verify($remoteHashedPassword, $plaintextPassword, $remoteSalt); $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); $user = new User($login, $remoteHashedPassword, $attributes, $rememberMe);
$loader = function (string $userIdentifier) use ($user) { $loader = function (string $userIdentifier) use ($user) {
@ -98,7 +103,7 @@ class SQLLoginUserAuthenticator extends AbstractAuthenticator
$request->getSession()->set(self::ERROR_PASSWORD, true); $request->getSession()->set(self::ERROR_PASSWORD, true);
throw new AuthenticationException(); throw new AuthenticationException();
} catch (PDOException $e) { } catch (PDOException $e) {
$request->getSession()->set(self::ERROR_PDO, true); $request->getSession()->set(self::ERROR_SQL_LOGIN, true);
throw new AuthenticationException(); throw new AuthenticationException();
} }
} }

View File

@ -4,7 +4,6 @@ namespace App\Security;
use App\Entity\User; use App\Entity\User;
use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException; use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Core\User\UserProviderInterface;
@ -13,10 +12,9 @@ class SQLLoginUserProvider implements UserProviderInterface
{ {
protected RequestStack $requestStack; protected RequestStack $requestStack;
public function __construct(RequestStack $requestStack, SessionInterface $session) public function __construct(RequestStack $requestStack)
{ {
$this->requestStack = $requestStack; $this->requestStack = $requestStack;
$this->session = $session;
} }
public function loadUserByIdentifier(string $identifier, ?User $user): ?UserInterface public function loadUserByIdentifier(string $identifier, ?User $user): ?UserInterface

View File

@ -56,12 +56,11 @@ class SQLLoginService extends AbstractController
$password = $query->fetch(PDO::FETCH_ASSOC); $password = $query->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $e) { } catch (PDOException $e) {
\Sentry\captureException($e); \Sentry\captureException($e);
dd($e);
throw new PDOException(); throw new PDOException();
} }
if ($password) { if ($password) {
return [ return [
$password[$this->sqlLoginRequest->egtPasswordColumnName()], $password[$this->sqlLoginRequest->getPasswordColumnName()],
isset($password[$this->sqlLoginRequest->getSaltColumnName()]) ? $password[$this->sqlLoginRequest->getSaltColumnName()] : null, isset($password[$this->sqlLoginRequest->getSaltColumnName()]) ? $password[$this->sqlLoginRequest->getSaltColumnName()] : null,
]; ];
} }
@ -69,11 +68,28 @@ class SQLLoginService extends AbstractController
return false; return false;
} }
public function updatePassword($login, $hashedPassword, $salt)
{
try {
$dbh = $this->getConnection();
$request = $this->sqlLoginRequest->getRequestUpdatePassword();
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() public function getConnection()
{ {
// Appel du singleton // Appel du singleton
$pdo = SQLLoginConnect::getInstance(); $sqlLogin = SQLLoginConnect::getInstance();
// Connection bdd // 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());
} }
} }

View File

@ -1,4 +1,4 @@
error: error:
login: 'Incorrect login' login: 'Incorrect login'
password: 'Incorrect password' password: 'Incorrect password'
pdo: 'Connection to database encountered a problem' sql_login: 'Connection to database encountered a problem'

View File

@ -1,4 +1,4 @@
error: error:
login: 'Login incorrect ou inconnu' login: 'Login incorrect ou inconnu'
password: 'Mot de passe incorrect' 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'