Compare commits

..

No commits in common. "69f0a607a301b8e5d677c6a7fd9edfcc9de6ddc1" and "fc4cecf1065d3c73565beab6645e99e30cfa43be" have entirely different histories.

13 changed files with 82 additions and 192 deletions

View File

@ -17,7 +17,7 @@ use Symfony\Contracts\Translation\TranslatorInterface;
class SecurityController extends AbstractController class SecurityController extends AbstractController
{ {
#[Route('/login', name: 'app_login')] #[Route('/login', name: 'app_login')]
public function login(ParameterBagInterface $params, AuthenticationUtils $authenticationUtils, Request $request, TranslatorInterface $trans): Response|RedirectResponse public function login(ParameterBagInterface $params, AuthenticationUtils $authenticationUtils, Request $request, TranslatorInterface $trans): Response
{ {
// Si l'utilisateur est déjà connecté on le renvoie sur la page du site demandeur // Si l'utilisateur est déjà connecté on le renvoie sur la page du site demandeur
if ($this->getUser()) { if ($this->getUser()) {
@ -29,24 +29,16 @@ class SecurityController extends AbstractController
$error = $authenticationUtils->getLastAuthenticationError(); $error = $authenticationUtils->getLastAuthenticationError();
if ($error) { if ($error) {
if ($request->getSession()->has(SQLLoginUserAuthenticator::ERROR_LOGIN)) { if ($request->getSession()->has(SQLLoginUserAuthenticator::ERROR_LOGIN)) {
$loginForm->addError(new FormError($trans->trans('error.login', [], 'messages'))); $loginForm->get('login')->addError(new FormError($trans->trans('error.login', [], 'messages')));
$request->getSession()->remove(SQLLoginUserAuthenticator::ERROR_LOGIN); $request->getSession()->remove(SQLLoginUserAuthenticator::ERROR_LOGIN);
} }
if ($request->getSession()->has(SQLLoginUserAuthenticator::ERROR_PDO)) { if ($request->getSession()->has(SQLLoginUserAuthenticator::ERROR_PASSWORD)) {
$loginForm->addError(new FormError($trans->trans('error.pdo', [], 'messages'))); $loginForm->get('password')->addError(new FormError($trans->trans('error.password', [], 'messages')));
$request->getSession()->remove(SQLLoginUserAuthenticator::ERROR_PDO); $request->getSession()->remove(SQLLoginUserAuthenticator::ERROR_PASSWORD);
} }
if ($request->getSession()->has(SQLLoginUserAuthenticator::ERROR_CONFIGURATION)) { if ($request->getSession()->has(SQLLoginUserAuthenticator::ERROR_SQL_LOGIN)) {
$loginForm->addError(new FormError($trans->trans('error.configuration', [], 'messages'))); $loginForm->addError(new FormError($trans->trans('error.sql_login', [], 'messages')));
$request->getSession()->remove(SQLLoginUserAuthenticator::ERROR_CONFIGURATION); $request->getSession()->remove(SQLLoginUserAuthenticator::ERROR_SQL_LOGIN);
}
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);
} }
} }

View File

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

View File

@ -4,6 +4,6 @@ namespace App\SQLLogin\Exception;
use Exception; use Exception;
class DataToFetchConfigurationException extends Exception class InvalidSQLLoginConfigurationException extends Exception
{ {
} }

View File

@ -4,6 +4,6 @@ namespace App\SQLLogin\Exception;
use Exception; use Exception;
class NullDataToFetchException extends Exception class InvalidSQLLoginException extends Exception
{ {
} }

View File

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

View File

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

View File

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

View File

@ -2,8 +2,6 @@
namespace App\SQLLogin; namespace App\SQLLogin;
use App\SQLLogin\Exception\NullDataToFetchException;
class SQLLoginRequest class SQLLoginRequest
{ {
public const DATA_TO_FETCH = 'data_to_fetch'; public const DATA_TO_FETCH = 'data_to_fetch';
@ -69,30 +67,25 @@ class SQLLoginRequest
public function getRequestScope() public function getRequestScope()
{ {
$scope = ''; $scope = '';
if (!$this->config[self::DATA_TO_FETCH]) {
throw new NullDataToFetchException();
}
foreach ($this->config[self::DATA_TO_FETCH] as $data) { foreach ($this->config[self::DATA_TO_FETCH] as $data) {
$scope .= $data . ','; $scope .= $data.',';
} }
// On enlève la dernière virgule
$scope = substr($scope, 0, -1); $scope = substr($scope, 0, -1);
return 'SELECT ' . $scope . ' FROM ' . $this->getTableName() . ' WHERE ' . $this->getLoginColumnName() . ' = :' . $this->getLoginColumnName() . ';'; 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 public function getRequestPassword()
{ {
$fields = $this->getPasswordColumnName(); $fields = $this->getPasswordColumnName();
if (!empty($this->getSaltColumnName())) { if (!empty($this->getSaltColumnName())) {
$fields .= ', ' . $this->getSaltColumnName(); $fields .= ', '.$this->getSaltColumnName();
} }
return 'SELECT ' . $fields . ' FROM ' . $this->getTableName() . ' WHERE ' . $this->getLoginColumnName() . ' = :' . $this->getLoginColumnName() . ';'; return 'SELECT '.$fields.' FROM '.$this->getTableName().' WHERE '.$this->getLoginColumnName().' = :'.$this->getLoginColumnName().';';
} }
} }

View File

@ -2,9 +2,8 @@
namespace App\Security\Hasher; namespace App\Security\Hasher;
use App\SQLLogin\Exception\InvalidSQLLoginConfigurationException;
use App\SQLLogin\Exception\InvalidSQLPasswordException; use App\SQLLogin\Exception\InvalidSQLPasswordException;
use App\SQLLogin\Exception\SecurityPatternConfigurationException;
use Psr\Log\LoggerInterface;
use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException;
use Symfony\Component\PasswordHasher\Hasher\CheckPasswordLengthTrait; use Symfony\Component\PasswordHasher\Hasher\CheckPasswordLengthTrait;
use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface;
@ -20,7 +19,7 @@ class PasswordEncoder implements LegacyPasswordHasherInterface
protected array $hashAlgoLegacy; protected array $hashAlgoLegacy;
protected array $securityPattern; protected array $securityPattern;
public function __construct(?string $pepper, string $hashAlgoLegacy, string $securityPattern, private LoggerInterface $loggerInterface) public function __construct(?string $pepper, string $hashAlgoLegacy, string $securityPattern)
{ {
$this->pepper = $pepper; $this->pepper = $pepper;
$this->hashAlgoLegacy = explode(',', $hashAlgoLegacy); $this->hashAlgoLegacy = explode(',', $hashAlgoLegacy);
@ -89,8 +88,7 @@ class PasswordEncoder implements LegacyPasswordHasherInterface
foreach ($this->securityPattern as $term) { foreach ($this->securityPattern as $term) {
if (self::PEPPER_PATTERN !== $term && self::PASSWORD_PATTERN !== $term && self::SALT_PATTERN !== $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 InvalidSQLLoginConfigurationException();
throw new SecurityPatternConfigurationException();
} }
} }
$completedPlainPassword = ''; $completedPlainPassword = '';

View File

@ -5,31 +5,26 @@ namespace App\Security;
use App\Entity\User; use App\Entity\User;
use App\Security\Hasher\PasswordEncoder; use App\Security\Hasher\PasswordEncoder;
use App\Service\SQLLoginService; use App\Service\SQLLoginService;
use App\SQLLogin\Exception\DatabaseConnectionException;
use App\SQLLogin\Exception\DataToFetchConfigurationException;
use App\SQLLogin\Exception\InvalidSQLPasswordException; use App\SQLLogin\Exception\InvalidSQLPasswordException;
use App\SQLLogin\Exception\LoginElementsConfigurationException; use PDOException;
use App\SQLLogin\Exception\SecurityPatternConfigurationException;
use Exception;
use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator; use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
class SQLLoginUserAuthenticator extends AbstractLoginFormAuthenticator class SQLLoginUserAuthenticator extends AbstractLoginFormAuthenticator
{ {
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_PDO = 'error_pdo'; public const ERROR_PASSWORD = 'error_password';
public const ERROR_SQL_LOGIN = 'error_sql_login'; 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';
protected string $baseUrl; protected string $baseUrl;
private SQLLoginService $sqlLoginService; private SQLLoginService $sqlLoginService;
@ -52,41 +47,32 @@ class SQLLoginUserAuthenticator extends AbstractLoginFormAuthenticator
return self::LOGIN_ROUTE === $request->attributes->get('_route') && $request->isMethod('POST'); return self::LOGIN_ROUTE === $request->attributes->get('_route') && $request->isMethod('POST');
} }
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): RedirectResponse public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): ?Response
{ {
return new RedirectResponse($this->baseUrl . '/connect/login-accept'); return new RedirectResponse($this->baseUrl.'/connect/login-accept');
} }
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): RedirectResponse public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
{ {
$request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception); $request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);
return new RedirectResponse($this->baseUrl . '/login'); return new RedirectResponse($this->baseUrl.'/login');
} }
public function authenticate(Request $request): SelfValidatingPassport public function authenticate(Request $request): Passport
{ {
$form = $request->request->get('login'); $form = $request->request->get('login');
$login = $form['login']; $login = $form['login'];
$plaintextPassword = $form['password']; $plaintextPassword = $form['password'];
$rememberMe = isset($form['_remember_me']) ? true : false; $rememberMe = isset($form['_remember_me']) ? true : false;
$session = $request->getSession();
try { try {
// requête préparée // requête préparée
list($remoteHashedPassword, $remoteSalt) = $this->sqlLoginService->fetchPassword($login); list($remoteHashedPassword, $remoteSalt) = $this->sqlLoginService->fetchPassword($login);
} catch (DatabaseConnectionException $e) { } catch (PDOException $e) {
$session->set(self::ERROR_PDO, true); $request->getSession()->set(self::ERROR_SQL_LOGIN, true);
throw new AuthenticationException();
} catch (LoginElementsConfigurationException $e) {
$session->set(self::ERROR_CONFIGURATION, true);
throw new AuthenticationException();
} catch (Exception $exception) {
$request->getSession()->set(self::ERROR_LOGIN, true);
throw new AuthenticationException(); throw new AuthenticationException();
} }
if (!$remoteHashedPassword) { if ($remoteHashedPassword) {
throw new Exception('Erreur inconnue');
}
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);
@ -104,22 +90,19 @@ class SQLLoginUserAuthenticator extends AbstractLoginFormAuthenticator
return $passport; return $passport;
} catch (InvalidSQLPasswordException $e) { } catch (InvalidSQLPasswordException $e) {
$session->set(self::ERROR_LOGIN, true); $request->getSession()->set(self::ERROR_PASSWORD, true);
throw new AuthenticationException(); throw new AuthenticationException();
} catch (DataToFetchConfigurationException $e) { } catch (PDOException $e) {
$session->set(self::ERROR_DATA_TO_FETCH_CONFIGURATION, true); $request->getSession()->set(self::ERROR_SQL_LOGIN, true);
throw new AuthenticationException();
} catch (DatabaseConnectionException $e) {
$session->set(self::ERROR_PDO, true);
throw new AuthenticationException();
} catch (SecurityPatternConfigurationException $e) {
$session->set(self::ERROR_SECURITY_PATTERN_CONFIGURATION, true);
throw new AuthenticationException(); throw new AuthenticationException();
} }
} }
$request->getSession()->set(self::ERROR_LOGIN, true);
throw new AuthenticationException();
}
protected function getLoginUrl(Request $request): string protected function getLoginUrl(Request $request): string
{ {
return $this->baseUrl . '/login'; return $this->baseUrl.'/login';
} }
} }

View File

@ -2,87 +2,63 @@
namespace App\Service; 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\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 Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class SQLLoginService extends AbstractController class SQLLoginService extends AbstractController
{ {
public SQLLoginRequest $sqlLoginRequest; public SQLLoginRequest $sqlLoginRequest;
public function __construct(SQLLoginRequest $sqlLoginRequest, private LoggerInterface $loggerInterface) public function __construct(SQLLoginRequest $sqlLoginRequest)
{ {
$this->sqlLoginRequest = $sqlLoginRequest; $this->sqlLoginRequest = $sqlLoginRequest;
$this->loggerInterface = $loggerInterface;
} }
public function fetchDatas(string $login): array public function fetchDatas(string $login)
{ {
try { try {
$dbh = $this->getConnection(); $dbh = $this->getConnection();
} catch (PDOException $e) {
$this->loggerInterface->critical($e->getMessage());
throw new DatabaseConnectionException($e->getMessage());
}
try {
// forge de la requête // forge de la requête
$request = $this->sqlLoginRequest->getRequestScope(); $request = $this->sqlLoginRequest->getRequestScope();
} catch (NullDataToFetchException $e) {
throw new DataToFetchConfigurationException($e->getMessage());
}
try {
// Préparation de la requête // Préparation de la requête
$query = $dbh->prepare($request); $query = $dbh->prepare($request);
$query->execute([$this->sqlLoginRequest->getLoginColumnName() => $login]); $query->execute([$this->sqlLoginRequest->getLoginColumnName() => $login]);
$datas = $query->fetch(PDO::FETCH_ASSOC); $datas = $query->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $e) { } catch (PDOException $e) {
$this->loggerInterface->critical($e->getMessage()); \Sentry\captureException($e);
throw new DataToFetchConfigurationException($e->getMessage());
throw new PDOException();
} }
return $datas; return $datas;
} }
public function fetchPassword(string $login): array public function fetchPassword(string $login)
{ {
try { try {
$dbh = $this->getConnection(); $dbh = $this->getConnection();
} catch (PDOException $e) {
$this->loggerInterface->critical($e->getMessage());
throw new DatabaseConnectionException($e->getMessage());
}
// forge de la requête
$request = $this->sqlLoginRequest->getRequestPassword(); $request = $this->sqlLoginRequest->getRequestPassword();
try {
$query = $dbh->prepare($request); $query = $dbh->prepare($request);
$query->execute([$this->sqlLoginRequest->getLoginColumnName() => $login]); $query->execute([$this->sqlLoginRequest->getLoginColumnName() => $login]);
$password = $query->fetch(PDO::FETCH_ASSOC); $password = $query->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $e) { } catch (PDOException $e) {
$this->loggerInterface->critical($e->getMessage()); \Sentry\captureException($e);
throw new LoginElementsConfigurationException($e->getMessage()); throw new PDOException();
} }
if (!$password) { if ($password) {
throw new Exception('Une erreur est survenue lors de la récupération des données');
}
return [ return [
$password[$this->sqlLoginRequest->getPasswordColumnName()], $password[$this->sqlLoginRequest->getPasswordColumnName()],
isset($password[$this->sqlLoginRequest->getSaltColumnName()]) ? $password[$this->sqlLoginRequest->getSaltColumnName()] : null, isset($password[$this->sqlLoginRequest->getSaltColumnName()]) ? $password[$this->sqlLoginRequest->getSaltColumnName()] : null,
]; ];
} }
public function getConnection(): PDO return false;
}
public function getConnection()
{ {
// Appel du singleton // Appel du singleton
$sqlLogin = SQLLoginConnect::getInstance(); $sqlLogin = SQLLoginConnect::getInstance();

View File

@ -7,7 +7,11 @@
<body> <body>
<trans-unit id="fXVg5Zq" resname="error.login"> <trans-unit id="fXVg5Zq" resname="error.login">
<source>error.login</source> <source>error.login</source>
<target>Incorrect login or password</target> <target>Incorrect login</target>
</trans-unit>
<trans-unit id="8VJKwdK" resname="error.password">
<source>error.password</source>
<target>Incorrect password</target>
</trans-unit> </trans-unit>
<trans-unit id="36t19qm" resname="error.sql_login"> <trans-unit id="36t19qm" resname="error.sql_login">
<source>error.sql_login</source> <source>error.sql_login</source>
@ -17,18 +21,6 @@
<source>error.pdo</source> <source>error.pdo</source>
<target>Connection to database encountered a problem</target> <target>Connection to database encountered a problem</target>
</trans-unit> </trans-unit>
<trans-unit id="1QRR4uA" resname="error.configuration">
<source>error.configuration</source>
<target>Identification data references do not exist in the database</target>
</trans-unit>
<trans-unit id="4EPIhsV" resname="error.data_to_fetch_configuration">
<source>error.data_to_fetch_configuration</source>
<target>Data references to be transmitted do not exist</target>
</trans-unit>
<trans-unit id="6iuTNs3" resname="error.security_pattern_configuration">
<source>error.security_pattern_configuration</source>
<target>The security pattern is not allowed</target>
</trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>

View File

@ -7,7 +7,11 @@
<body> <body>
<trans-unit id="fXVg5Zq" resname="error.login"> <trans-unit id="fXVg5Zq" resname="error.login">
<source>error.login</source> <source>error.login</source>
<target>Login ou mot de passe inconnu</target> <target>Login incorrect ou inconnu</target>
</trans-unit>
<trans-unit id="8VJKwdK" resname="error.password">
<source>error.password</source>
<target>Mot de passe incorrect</target>
</trans-unit> </trans-unit>
<trans-unit id="36t19qm" resname="error.sql_login"> <trans-unit id="36t19qm" resname="error.sql_login">
<source>error.sql_login</source> <source>error.sql_login</source>
@ -15,19 +19,7 @@
</trans-unit> </trans-unit>
<trans-unit id="lBole_G" resname="error.pdo"> <trans-unit id="lBole_G" resname="error.pdo">
<source>error.pdo</source> <source>error.pdo</source>
<target>La connexion à la base de données a rencontré un problème</target> <target>La connexion à la base de déonnées à rencontré un problème</target>
</trans-unit>
<trans-unit id="1QRR4uA" resname="error.configuration">
<source>error.configuration</source>
<target>Les références de données d'identification n'existent pas dans la base de données</target>
</trans-unit>
<trans-unit id="4EPIhsV" resname="error.data_to_fetch_configuration">
<source>error.data_to_fetch_configuration</source>
<target>Les références de données à transmettre n'existent pas</target>
</trans-unit>
<trans-unit id="6iuTNs3" resname="error.security_pattern_configuration">
<source>error.security_pattern_configuration</source>
<target>Le patron de sécurité n'est pas autorisé</target>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>