diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php index 8ed8253..0aeb305 100644 --- a/src/Controller/SecurityController.php +++ b/src/Controller/SecurityController.php @@ -32,21 +32,9 @@ class SecurityController extends AbstractController $loginForm->addError(new FormError($trans->trans('error.login', [], 'messages'))); $request->getSession()->remove(SQLLoginUserAuthenticator::ERROR_LOGIN); } - 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_CONFIGURATION)) { - $loginForm->addError(new FormError($trans->trans('error.configuration', [], 'messages'))); - $request->getSession()->remove(SQLLoginUserAuthenticator::ERROR_CONFIGURATION); - } - if ($request->getSession()->has(SQLLoginUserAuthenticator::ERROR_DATA_TO_FETCH_CONFIGURATION)) { - $loginForm->addError(new FormError($trans->trans('error.data_to_fetch_configuration', [], 'messages'))); - $request->getSession()->remove(SQLLoginUserAuthenticator::ERROR_DATA_TO_FETCH_CONFIGURATION); - } - if ($request->getSession()->has(SQLLoginUserAuthenticator::ERROR_SECURITY_PATTERN_CONFIGURATION)) { - $loginForm->addError(new FormError($trans->trans('error.security_pattern_configuration', [], 'messages'))); - $request->getSession()->remove(SQLLoginUserAuthenticator::ERROR_SECURITY_PATTERN_CONFIGURATION); + if ($request->getSession()->has(SQLLoginUserAuthenticator::TECHNICAL_ERROR)) { + $loginForm->addError(new FormError($trans->trans('error.technical', [], 'messages'))); + $request->getSession()->remove(SQLLoginUserAuthenticator::TECHNICAL_ERROR); } } diff --git a/src/Hydra/Client.php b/src/Hydra/Client.php index 82011f1..00592f9 100644 --- a/src/Hydra/Client.php +++ b/src/Hydra/Client.php @@ -3,14 +3,20 @@ namespace App\Hydra; use App\Hydra\Exception\InvalidChallengeException; +use Exception; use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\ResponseInterface; class Client { - protected $client; - - protected $hydraAdminBaseUrl; + private HttpClientInterface $client; + private const MAX_RETRY = 3; + private const SLEEP_TIME = [ + 5, + 500, + 5000, + ]; + private string $hydraAdminBaseUrl; public function __construct(HttpClientInterface $client, string $hydraAdminBaseUrl) { @@ -22,11 +28,11 @@ class Client { $response = $this->client->request( 'GET', - $this->hydraAdminBaseUrl . '/oauth2/auth/requests/login', + $this->hydraAdminBaseUrl.'/oauth2/auth/requests/login', [ 'query' => [ 'login_challenge' => $loginChallenge, - ] + ], ] ); @@ -35,7 +41,6 @@ class Client throw new InvalidChallengeException(); } - return $response; } @@ -43,11 +48,11 @@ class Client { $response = $this->client->request( 'GET', - $this->hydraAdminBaseUrl . '/oauth2/auth/requests/logout', + $this->hydraAdminBaseUrl.'/oauth2/auth/requests/logout', [ 'query' => [ 'logout_challenge' => $logoutChallenge, - ] + ], ] ); @@ -56,27 +61,38 @@ class Client throw new InvalidChallengeException(); } - return $response; } public function fetchConsentRequestInfo(string $consentChallenge): ResponseInterface { - $response = $this->client->request( - 'GET', - $this->hydraAdminBaseUrl . '/oauth2/auth/requests/consent', - [ - 'query' => [ - 'consent_challenge' => $consentChallenge, + $attempt = 0; + while ($attempt < self::MAX_RETRY) { + $response = $this->client->request( + 'GET', + $this->hydraAdminBaseUrl.'/oauth2/auth/requests/consent', + [ + 'query' => [ + 'consent_challenge' => $consentChallenge, + ], ] - ] - ); + ); - switch ($response->getStatusCode()) { - case 404: - throw new InvalidChallengeException(); + $status = $response->getStatusCode(); + if (503 === $status) { + ++$attempt; + usleep(1000 * self::SLEEP_TIME[$attempt] + rand(1, 5) * 1000); + continue; + } + switch ($status) { + case 404: + throw new InvalidChallengeException(); + } + break; + } + if (self::MAX_RETRY === $attempt) { + throw new Exception(sprintf('Fetch consent a rencontré une erreur %s après %s tentatives', $response->getStatusCode(), self::MAX_RETRY)); } - return $response; } @@ -85,18 +101,18 @@ class Client { $response = $this->client->request( 'PUT', - $this->hydraAdminBaseUrl . '/oauth2/auth/requests/login/accept', + $this->hydraAdminBaseUrl.'/oauth2/auth/requests/login/accept', [ 'query' => [ 'login_challenge' => $loginChallenge, ], 'headers' => [ - 'Content-Type' => 'application/json' + 'Content-Type' => 'application/json', ], 'body' => json_encode($payload), ] ); - + return $response; } @@ -104,13 +120,13 @@ class Client { $response = $this->client->request( 'PUT', - $this->hydraAdminBaseUrl . '/oauth2/auth/requests/consent/accept', + $this->hydraAdminBaseUrl.'/oauth2/auth/requests/consent/accept', [ 'query' => [ 'consent_challenge' => $consentChallenge, ], 'headers' => [ - 'Content-Type' => 'application/json' + 'Content-Type' => 'application/json', ], 'body' => json_encode($payload), ] @@ -123,13 +139,13 @@ class Client { $response = $this->client->request( 'PUT', - $this->hydraAdminBaseUrl . '/oauth2/auth/requests/logout/accept', + $this->hydraAdminBaseUrl.'/oauth2/auth/requests/logout/accept', [ 'query' => [ 'logout_challenge' => $logoutChallenge, ], 'headers' => [ - 'Content-Type' => 'application/json' + 'Content-Type' => 'application/json', ], ] ); diff --git a/src/SQLLogin/Exception/DatabaseConnectionException.php b/src/SQLLogin/Exception/EmptyResultException.php similarity index 54% rename from src/SQLLogin/Exception/DatabaseConnectionException.php rename to src/SQLLogin/Exception/EmptyResultException.php index 770ea6b..4ec746d 100644 --- a/src/SQLLogin/Exception/DatabaseConnectionException.php +++ b/src/SQLLogin/Exception/EmptyResultException.php @@ -4,6 +4,6 @@ namespace App\SQLLogin\Exception; use Exception; -class DatabaseConnectionException extends Exception +class EmptyResultException extends Exception { } diff --git a/src/SQLLogin/Exception/InvalidSQLLoginAlgoException.php b/src/SQLLogin/Exception/InvalidSQLLoginAlgoException.php deleted file mode 100644 index ee9ac4c..0000000 --- a/src/SQLLogin/Exception/InvalidSQLLoginAlgoException.php +++ /dev/null @@ -1,9 +0,0 @@ - PDO::ERRMODE_EXCEPTION, - PDO::ATTR_TIMEOUT => 5, - PDO::ATTR_PERSISTENT => false, - ]; - - return new PDO($urlDatabase, $dbUser, $dbPassword, $options); + return new PDO($urlDatabase, $dbUser, $dbPassword); } } diff --git a/src/Security/Hasher/PasswordEncoder.php b/src/Security/Hasher/PasswordEncoder.php index 19dd2f3..7501f95 100644 --- a/src/Security/Hasher/PasswordEncoder.php +++ b/src/Security/Hasher/PasswordEncoder.php @@ -88,7 +88,7 @@ class PasswordEncoder implements LegacyPasswordHasherInterface foreach ($this->securityPattern as $term) { if (self::PEPPER_PATTERN !== $term && self::PASSWORD_PATTERN !== $term && self::SALT_PATTERN !== $term) { $this->loggerInterface->critical('La configuration du security pattern est invalide, les termes autorisés sont : '.self::PASSWORD_PATTERN.', '.self::SALT_PATTERN.' et '.self::PEPPER_PATTERN); - throw new SecurityPatternConfigurationException(); + throw new SecurityPatternConfigurationException('La configuration du security pattern est invalide, les termes autorisés sont : '.self::PASSWORD_PATTERN.', '.self::SALT_PATTERN.' et '.self::PEPPER_PATTERN); } } $completedPlainPassword = ''; diff --git a/src/Security/SQLLoginUserAuthenticator.php b/src/Security/SQLLoginUserAuthenticator.php index 0c64c59..71848b2 100644 --- a/src/Security/SQLLoginUserAuthenticator.php +++ b/src/Security/SQLLoginUserAuthenticator.php @@ -5,12 +5,12 @@ namespace App\Security; use App\Entity\User; use App\Security\Hasher\PasswordEncoder; use App\Service\SQLLoginService; -use App\SQLLogin\Exception\DatabaseConnectionException; use App\SQLLogin\Exception\DataToFetchConfigurationException; +use App\SQLLogin\Exception\EmptyResultException; use App\SQLLogin\Exception\InvalidSQLPasswordException; -use App\SQLLogin\Exception\LoginElementsConfigurationException; use App\SQLLogin\Exception\SecurityPatternConfigurationException; use App\SQLLogin\SQLLoginRequest; +use PDOException; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; @@ -25,11 +25,7 @@ class SQLLoginUserAuthenticator extends AbstractLoginFormAuthenticator { public const LOGIN_ROUTE = 'app_login'; public const ERROR_LOGIN = 'error_login'; - public const ERROR_PDO = 'error_pdo'; - public const ERROR_SQL_LOGIN = 'error_sql_login'; - public const ERROR_CONFIGURATION = 'error_configuration'; - public const ERROR_DATA_TO_FETCH_CONFIGURATION = 'error_data_to_fetch_configuration'; - public const ERROR_SECURITY_PATTERN_CONFIGURATION = 'error_security_pattern_configuration'; + public const TECHNICAL_ERROR = 'technical_error'; private string $baseUrl; @@ -76,48 +72,46 @@ class SQLLoginUserAuthenticator extends AbstractLoginFormAuthenticator $session = $request->getSession(); try { $datas = $this->sqlLoginService->fetchPasswordAndDatas($login); - $remoteHashedPassword = $datas[$this->sqlLoginRequest->getPasswordColumnName()]; - unset($datas[$this->sqlLoginRequest->getPasswordColumnName()]); - $remoteSalt = null; - if ($this->sqlLoginRequest->getSaltColumnName() && isset($datas[$this->sqlLoginRequest->getSaltColumnName()])) { - $remoteSalt = $datas[$this->sqlLoginRequest->getSaltColumnName()]; - unset($datas[$this->sqlLoginRequest->getSaltColumnName()]); - } - } catch (DatabaseConnectionException $e) { - $session->set(self::ERROR_PDO, true); + } catch (EmptyResultException $e) { + $session->set(self::ERROR_LOGIN, true); throw new AuthenticationException(); - } catch (LoginElementsConfigurationException $e) { - $session->set(self::ERROR_CONFIGURATION, true); - throw new AuthenticationException(); - } catch (DataToFetchConfigurationException $e) { - $session->set(self::ERROR_DATA_TO_FETCH_CONFIGURATION, true); + } catch (DataToFetchConfigurationException|PDOException $e) { + \Sentry\captureException($e); + $session->set(self::TECHNICAL_ERROR, true); throw new AuthenticationException(); } - + $remoteHashedPassword = $datas[$this->sqlLoginRequest->getPasswordColumnName()]; + unset($datas[$this->sqlLoginRequest->getPasswordColumnName()]); + $remoteSalt = null; + if ($this->sqlLoginRequest->getSaltColumnName() && isset($datas[$this->sqlLoginRequest->getSaltColumnName()])) { + $remoteSalt = $datas[$this->sqlLoginRequest->getSaltColumnName()]; + unset($datas[$this->sqlLoginRequest->getSaltColumnName()]); + } if (null === $remoteHashedPassword) { $remoteHashedPassword = ''; } try { // Comparaison remote hash et hash du input password + salt $this->passwordHasher->verify($remoteHashedPassword, $plaintextPassword, $remoteSalt); - $user = new User($login, $remoteHashedPassword, $datas, $rememberMe); - $loader = function (string $userIdentifier) use ($user) { - return $user->getLogin() == $userIdentifier ? $user : null; - }; - $passport = new SelfValidatingPassport(new UserBadge($login, $loader)); - if ($rememberMe) { - $passport->addBadge(new RememberMeBadge()); - } - $passport->setAttribute('attributes', $user->getAttributes()); - - return $passport; } catch (InvalidSQLPasswordException $e) { $session->set(self::ERROR_LOGIN, true); throw new AuthenticationException(); } catch (SecurityPatternConfigurationException $e) { - $session->set(self::ERROR_SECURITY_PATTERN_CONFIGURATION, true); + \Sentry\captureException($e); + $session->set(self::TECHNICAL_ERROR, true); throw new AuthenticationException(); } + $user = new User($login, $remoteHashedPassword, $datas, $rememberMe); + $loader = function (string $userIdentifier) use ($user) { + return $user->getLogin() == $userIdentifier ? $user : null; + }; + $passport = new SelfValidatingPassport(new UserBadge($login, $loader)); + if ($rememberMe) { + $passport->addBadge(new RememberMeBadge()); + } + $passport->setAttribute('attributes', $user->getAttributes()); + + return $passport; } protected function getLoginUrl(Request $request): string diff --git a/src/Service/SQLLoginService.php b/src/Service/SQLLoginService.php index b411823..aeb1477 100644 --- a/src/Service/SQLLoginService.php +++ b/src/Service/SQLLoginService.php @@ -2,20 +2,16 @@ namespace App\Service; -use App\SQLLogin\Exception\DatabaseConnectionException; -use App\SQLLogin\Exception\DataToFetchConfigurationException; -use App\SQLLogin\Exception\LoginElementsConfigurationException; -use App\SQLLogin\Exception\NullDataToFetchException; +use App\SQLLogin\Exception\EmptyResultException; use App\SQLLogin\SQLLoginConnect; use App\SQLLogin\SQLLoginRequest; use PDO; -use PDOException; use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; class SQLLoginService extends AbstractController { - private PDO $pdo; + public const MAX_RETRY = 3; public function __construct( private SQLLoginRequest $sqlLoginRequest, @@ -23,31 +19,35 @@ class SQLLoginService extends AbstractController ) { $this->sqlLoginRequest = $sqlLoginRequest; $this->loggerInterface = $loggerInterface; - $this->pdo = $this->getConnection(); } public function fetchPasswordAndDatas(string $login): array { - try { - $dataRequest = $this->sqlLoginRequest->getDatasRequest(); - $datas = $this->executeRequestWithLogin($dataRequest, $login); - } catch (NullDataToFetchException $e) { - throw new DataToFetchConfigurationException($e->getMessage()); - } catch (PDOException $e) { - $this->loggerInterface->critical($e->getMessage()); - throw new LoginElementsConfigurationException($e->getMessage()); - } + $dataRequest = $this->sqlLoginRequest->getDatasRequest(); + $datas = $this->executeRequestWithLogin($dataRequest, $login); return $datas; } private function executeRequestWithLogin(string $request, string $login): array { - $query = $this->pdo->prepare($request); - $query->bindParam($this->sqlLoginRequest->getLoginColumnName(), $login, PDO::PARAM_STR); - $query->execute(); - $datas = $query->fetch(PDO::FETCH_ASSOC); - $query->closeCursor(); + $attempt = 0; + while ($attempt < self::MAX_RETRY) { + $pdo = $this->getConnection(); + $query = $pdo->prepare($request); + $query->execute([$this->sqlLoginRequest->getLoginColumnName() => $login]); + $datas = $query->fetch(PDO::FETCH_ASSOC); + $query->closeCursor(); + if (false === $datas) { + usleep(3000); + ++$attempt; + } else { + break; + } + } + if (self::MAX_RETRY === $attempt) { + throw new EmptyResultException(); + } return $datas; } @@ -55,13 +55,8 @@ class SQLLoginService extends AbstractController private function getConnection(): PDO { // Appel du singleton - try { - $sqlLogin = SQLLoginConnect::getInstance(); - $pdo = $sqlLogin->connect($this->sqlLoginRequest->getDatabaseDsn(), $this->sqlLoginRequest->getDbUser(), $this->sqlLoginRequest->getDbPassword()); - } catch (PDOException $e) { - $this->loggerInterface->critical($e->getMessage()); - throw new DatabaseConnectionException($e->getMessage()); - } + $sqlLogin = SQLLoginConnect::getInstance(); + $pdo = $sqlLogin->connect($this->sqlLoginRequest->getDatabaseDsn(), $this->sqlLoginRequest->getDbUser(), $this->sqlLoginRequest->getDbPassword()); return $pdo; } diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index 8617499..4d9bcad 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -9,25 +9,9 @@ error.login Incorrect login or password - - error.sql_login - Connection to database encountered a problem - - - error.pdo - Connection to database encountered a problem - - - error.configuration - Identification data references do not exist in the database - - - error.data_to_fetch_configuration - Data references to be transmitted do not exist - - - error.security_pattern_configuration - The security pattern is not allowed + + error.technical + A technical error happened, try again later diff --git a/translations/messages.fr.xlf b/translations/messages.fr.xlf index 39a5e8d..0c84774 100644 --- a/translations/messages.fr.xlf +++ b/translations/messages.fr.xlf @@ -9,25 +9,9 @@ error.login Login ou mot de passe inconnu - - error.sql_login - La connexion à la base de données a rencontré un problème - - - error.pdo - La connexion à la base de données a rencontré un problème - - - error.configuration - Les références de données d'identification n'existent pas dans la base de données - - - error.data_to_fetch_configuration - Les références de données à transmettre n'existent pas - - - error.security_pattern_configuration - Le patron de sécurité n'est pas autorisé + + error.technical + Une erreur technique s'est produite, rééssayez plus tard