13 Commits

Author SHA1 Message Date
f007dcf6d8 feat: add authentication success/failure log outputs
Some checks failed
Cadoles/hydra-sql/pipeline/pr-develop There was a failure building this commit
see CNOUS/mse#4707
2025-03-07 09:11:25 +01:00
075be9b0df Merge pull request 'recherche login lower dans requête de connexion' (#48) from issue-47 into develop
Some checks reported warnings
Cadoles/hydra-sql/pipeline/head This commit is unstable
Reviewed-on: #48
2024-11-06 11:13:10 +01:00
4e4c5d8e7b recherche login lower dans requête de connexion
Some checks failed
Cadoles/hydra-sql/pipeline/pr-develop There was a failure building this commit
Cadoles/hydra-sql/pipeline/head This commit is unstable
2024-11-06 10:22:48 +01:00
7032787d8c Merge pull request 'fix login sql : ajout d'un retry sur le login, suppression des options' (#45) from retour-43 into develop
Some checks reported warnings
Cadoles/hydra-sql/pipeline/head This commit was not built
Reviewed-on: #45
2024-10-14 10:40:12 +02:00
999e708ff7 modification gestion des exceptions
Some checks failed
Cadoles/hydra-sql/pipeline/pr-develop There was a failure building this commit
2024-10-11 15:09:52 +02:00
cb8361e7d1 optimisation appel pdo, retry consent
Some checks failed
Cadoles/hydra-sql/pipeline/pr-develop There was a failure building this commit
2024-10-11 13:25:21 +02:00
e3f406a8bb fix resultat null vaut login inconnu
Some checks failed
Cadoles/hydra-sql/pipeline/pr-develop There was a failure building this commit
2024-10-10 16:32:46 +02:00
d6d9e81df6 fix login sql : ajout d'un retry sur le login, suppression des options
Some checks failed
Cadoles/hydra-sql/pipeline/head This commit is unstable
Cadoles/hydra-sql/pipeline/pr-develop There was a failure building this commit
2024-10-10 12:01:15 +02:00
8e56433216 Merge pull request 'feat: ajout du packet xdebug à l'image' (#42) from xdebug into develop
Some checks reported warnings
Cadoles/hydra-sql/pipeline/head This commit is unstable
Reviewed-on: #42
2024-10-10 10:26:21 +02:00
19178bbe3b fix typo
Some checks are pending
Cadoles/hydra-sql/pipeline/pr-develop Build started...
2024-10-10 10:25:34 +02:00
14668150cb Merge pull request 'modification requete de fetch à la bdd #43' (#44) from issue-43 into develop
Some checks reported warnings
Cadoles/hydra-sql/pipeline/head This commit was not built
Reviewed-on: #44
Reviewed-by: Matthieu Lamalle <mlamalle@cadoles.com>
2024-10-10 10:01:29 +02:00
0903151f27 chore (authenticator) #43 : fix isset variable
Some checks failed
Cadoles/hydra-sql/pipeline/pr-develop There was a failure building this commit
2024-10-09 11:57:10 +02:00
f39ab1626e feat: ajout du packet xdebug à l'image
Some checks failed
Cadoles/hydra-sql/pipeline/pr-develop There was a failure building this commit
Cadoles/hydra-sql/pipeline/head This commit is unstable
2024-10-07 11:22:17 +02:00
23 changed files with 423 additions and 204 deletions

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
lts/iron

View File

@ -20,6 +20,7 @@
"symfony/form": "5.4.*",
"symfony/framework-bundle": "5.4.*",
"symfony/http-client": "5.4.*",
"symfony/monolog-bundle": "^3.10",
"symfony/rate-limiter": "5.4.*",
"symfony/runtime": "5.4.*",
"symfony/security-bundle": "5.4.*",

269
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "203398b3a4f3ff689ff3341c02460f23",
"content-hash": "9daf21762ed80ef11e74a53f5d27119f",
"packages": [
{
"name": "clue/stream-filter",
@ -589,6 +589,108 @@
},
"time": "2024-03-08T09:58:59+00:00"
},
{
"name": "monolog/monolog",
"version": "2.10.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "5cf826f2991858b54d5c3809bee745560a1042a7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/5cf826f2991858b54d5c3809bee745560a1042a7",
"reference": "5cf826f2991858b54d5c3809bee745560a1042a7",
"shasum": ""
},
"require": {
"php": ">=7.2",
"psr/log": "^1.0.1 || ^2.0 || ^3.0"
},
"provide": {
"psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0"
},
"require-dev": {
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
"doctrine/couchdb": "~1.0@dev",
"elasticsearch/elasticsearch": "^7 || ^8",
"ext-json": "*",
"graylog2/gelf-php": "^1.4.2 || ^2@dev",
"guzzlehttp/guzzle": "^7.4",
"guzzlehttp/psr7": "^2.2",
"mongodb/mongodb": "^1.8",
"php-amqplib/php-amqplib": "~2.4 || ^3",
"phpspec/prophecy": "^1.15",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^8.5.38 || ^9.6.19",
"predis/predis": "^1.1 || ^2.0",
"rollbar/rollbar": "^1.3 || ^2 || ^3",
"ruflin/elastica": "^7",
"swiftmailer/swiftmailer": "^5.3|^6.0",
"symfony/mailer": "^5.4 || ^6",
"symfony/mime": "^5.4 || ^6"
},
"suggest": {
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
"elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
"ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
"ext-mbstring": "Allow to work properly with unicode symbols",
"ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
"ext-openssl": "Required to send log messages using SSL",
"ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
"mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
"rollbar/rollbar": "Allow sending log messages to Rollbar",
"ruflin/elastica": "Allow sending log messages to an Elastic Search server"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.x-dev"
}
},
"autoload": {
"psr-4": {
"Monolog\\": "src/Monolog"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "https://seld.be"
}
],
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
"homepage": "https://github.com/Seldaek/monolog",
"keywords": [
"log",
"logging",
"psr-3"
],
"support": {
"issues": "https://github.com/Seldaek/monolog/issues",
"source": "https://github.com/Seldaek/monolog/tree/2.10.0"
},
"funding": [
{
"url": "https://github.com/Seldaek",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
"type": "tidelift"
}
],
"time": "2024-11-12T12:43:37+00:00"
},
{
"name": "php-http/client-common",
"version": "2.7.2",
@ -3566,6 +3668,171 @@
],
"time": "2024-05-31T14:33:22+00:00"
},
{
"name": "symfony/monolog-bridge",
"version": "v5.4.45",
"source": {
"type": "git",
"url": "https://github.com/symfony/monolog-bridge.git",
"reference": "cf7d75d4d64a41fbb1c0e92301bec404134fa84b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/cf7d75d4d64a41fbb1c0e92301bec404134fa84b",
"reference": "cf7d75d4d64a41fbb1c0e92301bec404134fa84b",
"shasum": ""
},
"require": {
"monolog/monolog": "^1.25.1|^2",
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1|^3",
"symfony/http-kernel": "^5.3|^6.0",
"symfony/polyfill-php80": "^1.16",
"symfony/service-contracts": "^1.1|^2|^3"
},
"conflict": {
"symfony/console": "<4.4",
"symfony/http-foundation": "<5.3"
},
"require-dev": {
"symfony/console": "^4.4|^5.0|^6.0",
"symfony/http-client": "^4.4|^5.0|^6.0",
"symfony/mailer": "^4.4|^5.0|^6.0",
"symfony/messenger": "^4.4|^5.0|^6.0",
"symfony/mime": "^4.4|^5.0|^6.0",
"symfony/security-core": "^4.4|^5.0|^6.0",
"symfony/var-dumper": "^4.4|^5.0|^6.0"
},
"suggest": {
"symfony/console": "For the possibility to show log messages in console commands depending on verbosity settings.",
"symfony/http-kernel": "For using the debugging handlers together with the response life cycle of the HTTP kernel.",
"symfony/var-dumper": "For using the debugging handlers like the console handler or the log server handler."
},
"type": "symfony-bridge",
"autoload": {
"psr-4": {
"Symfony\\Bridge\\Monolog\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides integration for Monolog with various Symfony components",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/monolog-bridge/tree/v5.4.45"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-10-10T06:37:45+00:00"
},
{
"name": "symfony/monolog-bundle",
"version": "v3.10.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/monolog-bundle.git",
"reference": "414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181",
"reference": "414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181",
"shasum": ""
},
"require": {
"monolog/monolog": "^1.25.1 || ^2.0 || ^3.0",
"php": ">=7.2.5",
"symfony/config": "^5.4 || ^6.0 || ^7.0",
"symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0",
"symfony/http-kernel": "^5.4 || ^6.0 || ^7.0",
"symfony/monolog-bridge": "^5.4 || ^6.0 || ^7.0"
},
"require-dev": {
"symfony/console": "^5.4 || ^6.0 || ^7.0",
"symfony/phpunit-bridge": "^6.3 || ^7.0",
"symfony/yaml": "^5.4 || ^6.0 || ^7.0"
},
"type": "symfony-bundle",
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Bundle\\MonologBundle\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony MonologBundle",
"homepage": "https://symfony.com",
"keywords": [
"log",
"logging"
],
"support": {
"issues": "https://github.com/symfony/monolog-bundle/issues",
"source": "https://github.com/symfony/monolog-bundle/tree/v3.10.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2023-11-06T17:08:13+00:00"
},
{
"name": "symfony/options-resolver",
"version": "v5.4.40",

View File

@ -8,4 +8,5 @@ return [
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
Sentry\SentryBundle\SentryBundle::class => ['all' => true],
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true],
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
];

View File

@ -0,0 +1,6 @@
monolog:
handlers:
stdout:
type: stream
path: "php://stdout"
level: "%env(string:LOGGER_LEVEL)%"

View File

@ -29,6 +29,9 @@ parameters:
env(PEPPER): ~
pepper: '%env(resolve:PEPPER)%'
env(LOGGER_LEVEL): "info"
services:
# default configuration for services in *this* file
_defaults:

View File

@ -13,7 +13,7 @@ services:
ports:
- 8082:8071
volumes:
- .:/app
- .:/app
tmpfs:
- /var/www/var/logs:uid=${FIXUID:-1000},gid=${FIXGID:-1000}
- /var/www/var/cache:uid=${FIXUID:-1000},gid=${FIXGID:-1000}
@ -40,6 +40,9 @@ services:
- HASH_ALGO_LEGACY="sha256"
- SECURITY_PATTERN=password,salt,pepper
- CADDY_HTTP_PORT=8071
- LOGGER_LEVEL=info
- PHP_FPM_DISPLAY_ERRORS=on
- PHP_FPM_CATCH_WORKERS_OUTPUT=1
oidc-test:
image: bornholm/oidc-test:v0.0.0-1-g936a77e

View File

@ -10,7 +10,8 @@ ARG ADDITIONAL_PACKAGES="bash=5.2.15-r0 \
php81-soap=${PHP_PKG_VERSION} \
php81-ldap=${PHP_PKG_VERSION} \
php81-pdo_mysql=${PHP_PKG_VERSION} \
php81-bcmath=${PHP_PKG_VERSION}"
php81-bcmath=${PHP_PKG_VERSION} \
php81-pecl-xdebug"
FROM reg.cadoles.com/cadoles/symfony:alpine-php-8.1-base-2024.7.25-stable.959.7896915
FROM reg.cadoles.com/cadoles/symfony:alpine-php-8.1-base-2024.10.4-stable.1529.b630c69
RUN chown 1000:www-data -R /app

View File

@ -10,8 +10,9 @@ ARG ADDITIONAL_PACKAGES="bash=5.2.15-r0 \
php81-soap=${PHP_PKG_VERSION} \
php81-ldap=${PHP_PKG_VERSION} \
php81-pdo_mysql=${PHP_PKG_VERSION} \
php81-bcmath=${PHP_PKG_VERSION}"
php81-bcmath=${PHP_PKG_VERSION} \
php81-pecl-xdebug"
FROM reg.cadoles.com/cadoles/symfony:alpine-php-8.1-standalone-2024.7.25-stable.959.7896915
FROM reg.cadoles.com/cadoles/symfony:alpine-php-8.1-standalone-2024.10.4-stable.1529.b630c69
RUN chown 1000:www-data -R /app
USER www-data

View File

@ -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);
}
}

View File

@ -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',
],
]
);

View File

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

View File

@ -1,9 +0,0 @@
<?php
namespace App\SQLLogin\Exception;
use Exception;
class InvalidSQLLoginAlgoException 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

@ -24,12 +24,6 @@ class SQLLoginConnect extends AbstractController
public function connect($urlDatabase, $dbUser, $dbPassword): PDO
{
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_TIMEOUT => 5,
PDO::ATTR_PERSISTENT => false,
];
return new PDO($urlDatabase, $dbUser, $dbPassword, $options);
return new PDO($urlDatabase, $dbUser, $dbPassword);
}
}

View File

@ -103,6 +103,6 @@ class SQLLoginRequest
{
$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 LOWER('.$this->getLoginColumnName().') = LOWER(:'.$this->getLoginColumnName().');';
}
}

View File

@ -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 = '';

View File

@ -5,13 +5,13 @@ 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 Exception;
use PDOException;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
@ -26,27 +26,22 @@ 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;
private SQLLoginService $sqlLoginService;
private PasswordEncoder $passwordHasher;
private SQLLoginRequest $sqlLoginRequest;
public function __construct(
string $baseUrl,
SQLLoginService $sqlLoginService,
PasswordEncoder $passwordHasher,
SQLLoginRequest $sqlLoginRequest
private SQLLoginService $sqlLoginService,
private PasswordEncoder $passwordHasher,
private SQLLoginRequest $sqlLoginRequest,
private LoggerInterface $logger
) {
$this->baseUrl = $baseUrl;
$this->sqlLoginService = $sqlLoginService;
$this->passwordHasher = $passwordHasher;
$this->sqlLoginRequest = $sqlLoginRequest;
$this->logger = $logger;
}
/**
@ -61,14 +56,14 @@ class SQLLoginUserAuthenticator extends AbstractLoginFormAuthenticator
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): RedirectResponse
{
return new RedirectResponse($this->baseUrl.'/connect/login-accept');
return new RedirectResponse($this->baseUrl . '/connect/login-accept');
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): RedirectResponse
{
$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
@ -80,60 +75,54 @@ class SQLLoginUserAuthenticator extends AbstractLoginFormAuthenticator
$session = $request->getSession();
try {
$datas = $this->sqlLoginService->fetchPasswordAndDatas($login);
$remoteHashedPassword = $datas[$this->sqlLoginRequest->getPasswordColumnName()];
unset($datas[$this->sqlLoginRequest->getPasswordColumnName()]);
$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);
$this->logger->warning("authentication failed", ['username' => $login, "remote_address" => $request->getClientIp()]);
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);
throw new AuthenticationException();
} catch (Exception $exception) {
$request->getSession()->set(self::ERROR_LOGIN, 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 (DataToFetchConfigurationException $e) {
$session->set(self::ERROR_DATA_TO_FETCH_CONFIGURATION, true);
throw new AuthenticationException();
} catch (DatabaseConnectionException $e) {
$session->set(self::ERROR_PDO, true);
$this->logger->warning("authentication failed", ['username' => $login, "remote_address" => $request->getClientIp()]);
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());
$this->logger->warning("authentication succeeded", ['username' => $login, "remote_address" => $request->getClientIp()]);
return $passport;
}
protected function getLoginUrl(Request $request): string
{
return $this->baseUrl.'/login';
return $this->baseUrl . '/login';
}
}

View File

@ -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;
}

View File

@ -114,6 +114,18 @@
"config/packages/lock.yaml"
]
},
"symfony/monolog-bundle": {
"version": "3.10",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "3.7",
"ref": "aff23899c4440dd995907613c1dd709b6f59503f"
},
"files": [
"config/packages/monolog.yaml"
]
},
"symfony/routing": {
"version": "5.4",
"recipe": {

View File

@ -9,25 +9,9 @@
<source>error.login</source>
<target>Incorrect login or password</target>
</trans-unit>
<trans-unit id="36t19qm" resname="error.sql_login">
<source>error.sql_login</source>
<target>Connection to database encountered a problem</target>
</trans-unit>
<trans-unit id="lBole_G" resname="error.pdo">
<source>error.pdo</source>
<target>Connection to database encountered a problem</target>
</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 id="1QRR4uA" resname="error.technical">
<source>error.technical</source>
<target>A technical error happened, try again later</target>
</trans-unit>
</body>
</file>

View File

@ -9,25 +9,9 @@
<source>error.login</source>
<target>Login ou mot de passe inconnu</target>
</trans-unit>
<trans-unit id="36t19qm" resname="error.sql_login">
<source>error.sql_login</source>
<target>La connexion à la base de données a rencontré un problème</target>
</trans-unit>
<trans-unit id="lBole_G" resname="error.pdo">
<source>error.pdo</source>
<target>La connexion à la base de données a 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 id="1QRR4uA" resname="error.technical">
<source>error.technical</source>
<target>Une erreur technique s'est produite, rééssayez plus tard</target>
</trans-unit>
</body>
</file>