chore (login) #43 : remaniement connexion sql, passage à 1 requête au lieu de 2 pour performances

This commit is contained in:
Rudy Masson 2024-10-08 14:36:12 +02:00
parent fe4d683c20
commit d707a91694
4 changed files with 67 additions and 79 deletions

View File

@ -12,7 +12,6 @@ class SQLLoginConnect extends AbstractController
/** /**
* Méthode qui crée l'unique instance de la classe * Méthode qui crée l'unique instance de la classe
* si elle n'existe pas encore puis la retourne. * si elle n'existe pas encore puis la retourne.
*
*/ */
public static function getInstance(): SQLLoginConnect public static function getInstance(): SQLLoginConnect
{ {
@ -25,6 +24,12 @@ class SQLLoginConnect extends AbstractController
public function connect($urlDatabase, $dbUser, $dbPassword): PDO public function connect($urlDatabase, $dbUser, $dbPassword): PDO
{ {
return new PDO($urlDatabase, $dbUser, $dbPassword); $options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_TIMEOUT => 5,
PDO::ATTR_PERSISTENT => false,
];
return new PDO($urlDatabase, $dbUser, $dbPassword, $options);
} }
} }

View File

@ -72,33 +72,37 @@ class SQLLoginRequest
return $this->config[self::SUBJECT_REWRITE_EXPRESSION]; return $this->config[self::SUBJECT_REWRITE_EXPRESSION];
} }
public function getRequestScope(): string private function getDataFields(): array
{ {
$scope = '';
if (!$this->config[self::DATA_TO_FETCH]) { if (!$this->config[self::DATA_TO_FETCH]) {
throw new NullDataToFetchException(); throw new NullDataToFetchException();
} }
foreach ($this->config[self::DATA_TO_FETCH] as $data) { return $this->config[self::DATA_TO_FETCH];
$scope .= $data.',';
}
// On enlève la dernière virgule
$scope = substr($scope, 0, -1);
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 * 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 * 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(): string private function getPasswordFields(): array
{ {
$fields = $this->getPasswordColumnName(); $fields[] = $this->getPasswordColumnName();
if (!empty($this->getSaltColumnName())) { if (!empty($this->getSaltColumnName())) {
$fields .= ', '.$this->getSaltColumnName(); $fields[] = $this->getSaltColumnName();
} }
return $fields;
}
/**
* 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 getDatasRequest(): string
{
$fields = join(',', array_merge($this->getPasswordFields(), $this->getDataFields()));
return 'SELECT '.$fields.' FROM '.$this->getTableName().' WHERE '.$this->getLoginColumnName().' = :'.$this->getLoginColumnName().';'; return 'SELECT '.$fields.' FROM '.$this->getTableName().' WHERE '.$this->getLoginColumnName().' = :'.$this->getLoginColumnName().';';
} }
} }

View File

@ -10,6 +10,7 @@ use App\SQLLogin\Exception\DataToFetchConfigurationException;
use App\SQLLogin\Exception\InvalidSQLPasswordException; use App\SQLLogin\Exception\InvalidSQLPasswordException;
use App\SQLLogin\Exception\LoginElementsConfigurationException; use App\SQLLogin\Exception\LoginElementsConfigurationException;
use App\SQLLogin\Exception\SecurityPatternConfigurationException; use App\SQLLogin\Exception\SecurityPatternConfigurationException;
use App\SQLLogin\SQLLoginRequest;
use Exception; use Exception;
use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@ -34,12 +35,18 @@ class SQLLoginUserAuthenticator extends AbstractLoginFormAuthenticator
private string $baseUrl; private string $baseUrl;
private SQLLoginService $sqlLoginService; private SQLLoginService $sqlLoginService;
private PasswordEncoder $passwordHasher; private PasswordEncoder $passwordHasher;
private SQLLoginRequest $sqlLoginRequest;
public function __construct(string $baseUrl, SQLLoginService $sqlLoginService, PasswordEncoder $passwordHasher) public function __construct(
{ string $baseUrl,
SQLLoginService $sqlLoginService,
PasswordEncoder $passwordHasher,
SQLLoginRequest $sqlLoginRequest
) {
$this->baseUrl = $baseUrl; $this->baseUrl = $baseUrl;
$this->sqlLoginService = $sqlLoginService; $this->sqlLoginService = $sqlLoginService;
$this->passwordHasher = $passwordHasher; $this->passwordHasher = $passwordHasher;
$this->sqlLoginRequest = $sqlLoginRequest;
} }
/** /**
@ -72,14 +79,20 @@ class SQLLoginUserAuthenticator extends AbstractLoginFormAuthenticator
$rememberMe = isset($form['_remember_me']) ? true : false; $rememberMe = isset($form['_remember_me']) ? true : false;
$session = $request->getSession(); $session = $request->getSession();
try { try {
// requête préparée $datas = $this->sqlLoginService->fetchPasswordAndDatas($login);
list($remoteHashedPassword, $remoteSalt) = $this->sqlLoginService->fetchPassword($login); $remoteHashedPassword = $datas[$this->sqlLoginRequest->getPasswordColumnName()];
unset($datas[$this->sqlLoginRequest->getPasswordColumnName()]);
$remoteSalt = $datas[$this->sqlLoginRequest->getSaltColumnName()];
unset($datas[$this->sqlLoginRequest->getSaltColumnName()]);
} catch (DatabaseConnectionException $e) { } catch (DatabaseConnectionException $e) {
$session->set(self::ERROR_PDO, true); $session->set(self::ERROR_PDO, true);
throw new AuthenticationException(); throw new AuthenticationException();
} catch (LoginElementsConfigurationException $e) { } catch (LoginElementsConfigurationException $e) {
$session->set(self::ERROR_CONFIGURATION, true); $session->set(self::ERROR_CONFIGURATION, true);
throw new AuthenticationException(); throw new AuthenticationException();
} catch (DataToFetchConfigurationException $e) {
$session->set(self::ERROR_DATA_TO_FETCH_CONFIGURATION, true);
throw new AuthenticationException();
} catch (Exception $exception) { } catch (Exception $exception) {
$request->getSession()->set(self::ERROR_LOGIN, true); $request->getSession()->set(self::ERROR_LOGIN, true);
throw new AuthenticationException(); throw new AuthenticationException();
@ -92,8 +105,7 @@ class SQLLoginUserAuthenticator extends AbstractLoginFormAuthenticator
try { try {
// Comparaison remote hash et hash du input password + salt // Comparaison remote hash et hash du input password + salt
$this->passwordHasher->verify($remoteHashedPassword, $plaintextPassword, $remoteSalt); $this->passwordHasher->verify($remoteHashedPassword, $plaintextPassword, $remoteSalt);
$attributes = $this->sqlLoginService->fetchDatas($login); $user = new User($login, $remoteHashedPassword, $datas, $rememberMe);
$user = new User($login, $remoteHashedPassword, $attributes, $rememberMe);
$loader = function (string $userIdentifier) use ($user) { $loader = function (string $userIdentifier) use ($user) {
return $user->getLogin() == $userIdentifier ? $user : null; return $user->getLogin() == $userIdentifier ? $user : null;

View File

@ -8,7 +8,6 @@ use App\SQLLogin\Exception\LoginElementsConfigurationException;
use App\SQLLogin\Exception\NullDataToFetchException; use App\SQLLogin\Exception\NullDataToFetchException;
use App\SQLLogin\SQLLoginConnect; use App\SQLLogin\SQLLoginConnect;
use App\SQLLogin\SQLLoginRequest; use App\SQLLogin\SQLLoginRequest;
use Exception;
use PDO; use PDO;
use PDOException; use PDOException;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
@ -16,86 +15,54 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class SQLLoginService extends AbstractController class SQLLoginService extends AbstractController
{ {
private SQLLoginRequest $sqlLoginRequest; private PDO $pdo;
public function __construct(SQLLoginRequest $sqlLoginRequest, private LoggerInterface $loggerInterface) public function __construct(
{ private SQLLoginRequest $sqlLoginRequest,
private LoggerInterface $loggerInterface
) {
$this->sqlLoginRequest = $sqlLoginRequest; $this->sqlLoginRequest = $sqlLoginRequest;
$this->loggerInterface = $loggerInterface; $this->loggerInterface = $loggerInterface;
$this->pdo = $this->getConnection();
} }
public function fetchDatas(string $login): array public function fetchPasswordAndDatas(string $login): array
{ {
if (empty($login)) {
throw new Exception('Connexion échouée, le login ne peut pas être vide');
}
try { try {
$dbh = $this->getConnection(); $dataRequest = $this->sqlLoginRequest->getDatasRequest();
} catch (PDOException $e) { $datas = $this->executeRequestWithLogin($dataRequest, $login);
$this->loggerInterface->critical($e->getMessage());
throw new DatabaseConnectionException($e->getMessage());
}
try {
// forge de la requête
$request = $this->sqlLoginRequest->getRequestScope();
} catch (NullDataToFetchException $e) { } catch (NullDataToFetchException $e) {
throw new DataToFetchConfigurationException($e->getMessage()); throw new DataToFetchConfigurationException($e->getMessage());
}
try {
// Préparation de la requête
$query = $dbh->prepare($request);
$query->execute([$this->sqlLoginRequest->getLoginColumnName() => $login]);
$datas = $query->fetch(PDO::FETCH_ASSOC);
$query->closeCursor();
} catch (PDOException $e) { } catch (PDOException $e) {
$this->loggerInterface->critical($e->getMessage()); $this->loggerInterface->critical($e->getMessage());
throw new DataToFetchConfigurationException($e->getMessage()); throw new LoginElementsConfigurationException($e->getMessage());
}
if (false === $datas) {
throw new Exception(sprintf('La requête sql "%s" a renvoyé un résultat incorrect.', $request));
} }
return $datas; return $datas;
} }
public function fetchPassword(string $login): array private function executeRequestWithLogin(string $request, string $login): array
{ {
try { $query = $this->pdo->prepare($request);
$dbh = $this->getConnection(); $query->bindParam($this->sqlLoginRequest->getLoginColumnName(), $login, PDO::PARAM_STR);
} catch (PDOException $e) { $query->execute();
$this->loggerInterface->critical($e->getMessage()); $datas = $query->fetch(PDO::FETCH_ASSOC);
throw new DatabaseConnectionException($e->getMessage());
}
// forge de la requête
$request = $this->sqlLoginRequest->getRequestPassword();
try {
$query = $dbh->prepare($request);
$query->execute([$this->sqlLoginRequest->getLoginColumnName() => $login]);
$password = $query->fetch(PDO::FETCH_ASSOC);
$query->closeCursor(); $query->closeCursor();
} catch (PDOException $e) {
$this->loggerInterface->critical($e->getMessage());
throw new LoginElementsConfigurationException($e->getMessage());
}
if (!$password) {
throw new Exception('Une erreur est survenue lors de la récupération des données');
}
return [ return $datas;
$password[$this->sqlLoginRequest->getPasswordColumnName()],
isset($password[$this->sqlLoginRequest->getSaltColumnName()]) ? $password[$this->sqlLoginRequest->getSaltColumnName()] : null,
];
} }
private function getConnection(): PDO private function getConnection(): PDO
{ {
// Appel du singleton // Appel du singleton
try {
$sqlLogin = SQLLoginConnect::getInstance(); $sqlLogin = SQLLoginConnect::getInstance();
// Connection bdd $pdo = $sqlLogin->connect($this->sqlLoginRequest->getDatabaseDsn(), $this->sqlLoginRequest->getDbUser(), $this->sqlLoginRequest->getDbPassword());
return $sqlLogin->connect($this->sqlLoginRequest->getDatabaseDsn(), $this->sqlLoginRequest->getDbUser(), $this->sqlLoginRequest->getDbPassword()); } catch (PDOException $e) {
$this->loggerInterface->critical($e->getMessage());
throw new DatabaseConnectionException($e->getMessage());
}
return $pdo;
} }
} }