environnement complet autonome, révision complete de la méthode, ajout de configuration
Some checks failed
Cadoles/hydra-sql/pipeline/head There was a failure building this commit
Cadoles/hydra-sql/pipeline/pr-develop There was a failure building this commit

This commit is contained in:
2022-12-09 17:31:07 +01:00
parent b451566452
commit 6fc004a549
110 changed files with 1829 additions and 372 deletions

View File

@ -3,171 +3,55 @@
namespace App\Controller;
use App\Entity\User;
use App\Form\UserType;
use App\Services\PdoServices;
use App\Hydra\Client;
use App\Hydra\HydraService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class MainController extends AbstractController
{
/**
* @var Session
*/
private $session;
public HydraService $hydra;
public Client $client;
public SessionInterface $session;
/**
* @var UrlGeneratorInterface
*/
private $router;
/**
* @var HttpClientInterface
*/
public $client;
private $pdoServices;
public function __construct(PdoServices $pdoServices, HttpClientInterface $client, SessionInterface $session, UrlGeneratorInterface $router)
public function __construct(SessionInterface $session, HydraService $hydra, Client $client)
{
$this->pdoServices = $pdoServices;
$this->session = $session;
$this->client = $client;
$this->router = $router;
$this->hydra = $hydra;
}
/**
* @Route("/oauth/login", name="app_login")
* @Route("/", name="app_home")
*/
public function loginOidc(Request $request)
public function home(Request $request)
{
$challenge = $request->query->get('login_challenge');
// S'il n'y a pas de challenge, on déclenche une bad request
if (!$challenge) {
throw new BadRequestException('pas de challenge');
}
// On vérifie que la requête d'identification provient bien de hydra
$response = $this->client->request('GET', $this->getParameter('url_login_challenge').$challenge, [
'headers' => [
'Content-Type: application/json',
],
]);
if (200 !== $response->getStatusCode()) {
$this->session->clear();
throw new BadRequestException('pa de code 200');
}
// si le challenge est validé par hydra, on le stocke en session pour l'utiliser par la suite et on redirige vers une route interne protégée qui va déclencher l'identification FranceConnect
$this->session->set('challenge', $challenge);
return $this->redirectToRoute('oauth_login');
return $this->hydra->handleLoginRequest($request);
}
/**
* @Route("/oauth/connect", name="oauth_login")
* @Route("/connect/login-accept", name="app_login_accept")
*/
public function oauth(Request $request)
public function loginAccept(Request $request)
{
if ($request->headers->get('referer') !== $this->router->generate('oauth_login', [], 0) && !in_array($request->headers->get('referer'), $this->getParameter('urlIssuer'))) {
throw new BadRequestException('Vous devez passer par le issuer pour vous connecter');
}
/** @var User */
$user = $this->getUser();
$loginAcceptRes = $this->client->acceptLoginRequest($this->session->get('challenge'), [
'subject' => $user->getLogin(),
'remember' => true,
])->toArray();
$user = new User();
$loginForm = $this->createForm(UserType::class, $user);
$loginForm->handleRequest($request);
if ($loginForm->isSubmitted() && $loginForm->isValid()) {
$email = $loginForm->get('email')->getData();
try {
// requête préparée
$datas = $this->pdoServices->fetchDatas($email);
if (!$datas) {
// Si le hash du password n'est pas trouvé, c'est que l'email n'existe pas, on retourne la page de login avec une erreur
return $this->render('login.html.twig', [
'form' => $loginForm->createView(),
'error_mail' => 'mail non trouvé',
]);
}
$hashPassword = $datas[$this->getParameter('passwordColumnName')];
$password = $loginForm->get('password')->getData();
if ($this->pdoServices->verifyPassword($password, $hashPassword)) {
// On défait la mot de passe qui ne servira plus
unset($datas[$this->getParameter('passwordColumnName')]);
$this->session->set('datas', $datas);
$response = $this->client->request('PUT', $this->getParameter('url_login_challenge_accept').$this->session->get('challenge'), [
'json' => [
'subject' => $email,
'acr' => 'string',
],
]);
// On initie l'acceptation du login challenge émis par hydra et on récupère l'url de redirection
$redirect_to = $response->toArray()['redirect_to'];
return $this->redirect($redirect_to, 301);
} else {
return $this->render('login.html.twig', [
'form' => $loginForm->createView(),
'error_password' => 'Le mot de passe est incorrect',
]);
}
} catch (\Exception $e) {
dd($e);
}
}
return $this->render('login.html.twig', [
'form' => $loginForm->createView(),
]);
return new RedirectResponse($loginAcceptRes['redirect_to']);
}
/**
* @Route("/oauth/consent", name="consent")
* @Route("/connect/consent", name="app_consent")
*/
public function consent(Request $request)
{
$challenge = $request->query->get('consent_challenge');
if (!$challenge) {
throw new BadRequestException("Le challenge n'est pas disponible");
}
// Vérification du consent_challenge avec hydra
$response = $this->client->request('GET', $this->getParameter('url_consent_challenge').$challenge, [
'headers' => [
'Content-Type: application/json',
],
]);
if (200 !== $response->getStatusCode()) {
$this->session->clear();
throw new BadRequestException("Le challenge n'est pas authorisé");
}
$response = $this->client->request('PUT', $this->getParameter('url_consent_challenge_accept').$challenge, [
'headers' => [
'Content-Type: application/json',
],
'json' => [
'grant_scope' => ['openid', 'offline_access'],
'session' => [
'id_token' => [
'user' => $this->session->get('datas'),
],
],
],
]);
$redirect_to = $response->toArray()['redirect_to'];
return $this->redirect($redirect_to, 301);
}
/**
* @Route("/oauth/logout", name="app_logout")
*/
public function logout()
{
$this->session->clear();
return $this->redirect($this->getParameter('urlLogoutSuccess'));
return $this->hydra->handleConsentRequest($request);
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class SecurityController extends AbstractController
{
/**
* @Route("/login", name="app_login")
*/
public function login(AuthenticationUtils $authenticationUtils): Response
{
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
if ($error) {
}
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('login.html.twig', ['last_username' => $lastUsername, 'error' => $error]);
}
/**
* @Route("/logout", name="app_logout")
*/
public function logout(Request $request)
{
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\DependencyInjection;
use App\Pdo\PdoRequest;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class PdoConfiguration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('pdo');
$treeBuilder->getRootNode()->children()
->scalarNode(PdoRequest::COLUMN_LOGIN_NAME)->isRequired()->cannotBeEmpty()->end()
->scalarNode(PdoRequest::COLUMN_PASSWORD_NAME)->isRequired()->cannotBeEmpty()->end()
->scalarNode(PdoRequest::TABLE_NAME)->isRequired()->cannotBeEmpty()->end()
->arrayNode(PdoRequest::DATA_TO_FETCH)
->scalarPrototype()->end()
->end()
->end();
return $treeBuilder;
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\DependencyInjection;
use App\Pdo\PdoRequest;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
class PdoExtension extends Extension implements CompilerPassInterface
{
/** @var array */
protected $pdoConfig;
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new PdoConfiguration();
$config = $this->processConfiguration($configuration, $configs);
$this->pdoConfig = $config;
}
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition(PdoRequest::class);
$definition->replaceArgument('$config', $this->pdoConfig);
}
}

View File

@ -2,25 +2,27 @@
namespace App\Entity;
use App\Validator as AcmeAssert;
use Symfony\Component\Security\Core\User\UserInterface;
class User
class User implements UserInterface
{
private string $email;
/** @var array */
protected $attributes;
private string $login;
private string $password;
private string $rememberMe;
private bool $rememberMe;
public function getEmail(): ?string
public function __construct($login, $password, $attributes, $rememberMe = false)
{
return $this->email;
$this->password = $password;
$this->login = $login;
$this->attributes = $attributes;
$this->rememberMe = $rememberMe;
}
public function setEmail(string $email): self
public function getLogin(): ?string
{
$this->email = $email;
return $this;
return $this->login;
}
public function getPassword(): string
@ -28,22 +30,37 @@ class User
return $this->password;
}
public function setPassword(string $password): self
public function getAttributes(): array
{
$this->password = $password;
return $this;
return $this->attributes;
}
public function getRememberMe(): string
public function getRememberMe(): bool
{
return $this->rememberMe;
}
public function setRememberMe(bool $rememberMe): self
public function getRoles(): array
{
$this->rememberMe = $rememberMe;
return $this;
return ['ROLE_USER'];
}
}
public function getSalt(): ?string
{
return null;
}
public function eraseCredentials()
{
}
public function getUsername(): string
{
return $this->login;
}
public function getUserIdentifier(): string
{
return $this->login;
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\EventListener;
use App\Hydra\HydraService;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Http\Event\LogoutEvent;
class LogoutSubscriber implements EventSubscriberInterface
{
public function __construct(
private UrlGeneratorInterface $urlGenerator,
private HydraService $hydra
) {
}
public static function getSubscribedEvents(): array
{
return [LogoutEvent::class => 'onLogout'];
}
public function onLogout(LogoutEvent $event): void
{
// get the security token of the session that is about to be logged out
// get the current request
$request = $event->getRequest();
// get the current response, if it is already set by another listener
$response = $event->getResponse();
// configure a custom logout response to the homepage
$response = $this->hydra->handleLogoutRequest($request);
$event->setResponse($response);
}
}

View File

@ -1,41 +0,0 @@
<?php
namespace App\Form;
use App\Entity\User;
use App\Validator\ExistingEmail;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('email', EmailType::class, [
"required"=>true
])
->add("password", PasswordType::class, [
"attr" => ["class" => "password-field"],
"required" => true,
"label"=>"Mot de passe"
])
->add('rememberMe', CheckboxType::class, [
"required"=> false,
"label"=> "Se souvenir de moi"
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
"data_class" => User::class,
]);
}
}

139
src/Hydra/Client.php Normal file
View File

@ -0,0 +1,139 @@
<?php
namespace App\Hydra;
use App\Hydra\Exception\InvalidChallengeException;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
class Client
{
protected $client;
protected $hydraAdminBaseUrl;
public function __construct(HttpClientInterface $client, string $hydraAdminBaseUrl)
{
$this->client = $client;
$this->hydraAdminBaseUrl = $hydraAdminBaseUrl;
}
public function fetchLoginRequestInfo(string $loginChallenge): ResponseInterface
{
$response = $this->client->request(
'GET',
$this->hydraAdminBaseUrl . '/oauth2/auth/requests/login',
[
'query' => [
'login_challenge' => $loginChallenge,
]
]
);
switch ($response->getStatusCode()) {
case 404:
throw new InvalidChallengeException();
}
return $response;
}
public function fetchLogoutRequestInfo(string $logoutChallenge): ResponseInterface
{
$response = $this->client->request(
'GET',
$this->hydraAdminBaseUrl . '/oauth2/auth/requests/logout',
[
'query' => [
'logout_challenge' => $logoutChallenge,
]
]
);
switch ($response->getStatusCode()) {
case 404:
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,
]
]
);
switch ($response->getStatusCode()) {
case 404:
throw new InvalidChallengeException();
}
return $response;
}
public function acceptLoginRequest(string $loginChallenge, array $payload): ResponseInterface
{
$response = $this->client->request(
'PUT',
$this->hydraAdminBaseUrl . '/oauth2/auth/requests/login/accept',
[
'query' => [
'login_challenge' => $loginChallenge,
],
'headers' => [
'Content-Type' => 'application/json'
],
'body' => json_encode($payload),
]
);
return $response;
}
public function acceptConsentRequest(string $consentChallenge, array $payload): ResponseInterface
{
$response = $this->client->request(
'PUT',
$this->hydraAdminBaseUrl . '/oauth2/auth/requests/consent/accept',
[
'query' => [
'consent_challenge' => $consentChallenge,
],
'headers' => [
'Content-Type' => 'application/json'
],
'body' => json_encode($payload),
]
);
return $response;
}
public function acceptLogoutRequest(string $logoutChallenge): ResponseInterface
{
$response = $this->client->request(
'PUT',
$this->hydraAdminBaseUrl . '/oauth2/auth/requests/logout/accept',
[
'query' => [
'logout_challenge' => $logoutChallenge,
],
'headers' => [
'Content-Type' => 'application/json'
],
]
);
return $response;
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace App\Hydra\Exception;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
class InvalidChallengeException extends BadRequestException
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace App\Hydra\Exception;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
class InvalidIssuerException extends BadRequestException
{
}

View File

@ -0,0 +1,85 @@
<?php
namespace App\Hydra;
use App\Hydra\Exception\InvalidChallengeException;
use App\Services\PdoService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class HydraService extends AbstractController
{
public SessionInterface $session;
public UrlGeneratorInterface $router;
public Client $client;
public PdoService $pdoServices;
public TokenStorageInterface $tokenStorage;
public function __construct(PdoService $pdoServices, Client $client, SessionInterface $session, UrlGeneratorInterface $router, TokenStorageInterface $tokenStorage)
{
$this->pdoServices = $pdoServices;
$this->session = $session;
$this->client = $client;
$this->router = $router;
$this->tokenStorage = $tokenStorage;
}
public function handleLoginRequest(Request $request)
{
$challenge = $request->query->get('login_challenge');
// S'il n'y a pas de challenge, on déclenche une bad request
if (empty($challenge)) {
throw new InvalidChallengeException();
}
// Fetch Hydra login request info
$res = $this->client->fetchLoginRequestInfo($challenge);
$loginRequestInfo = $res->toArray();
if (200 !== $res->getStatusCode()) {
$this->session->clear();
throw new BadRequestException('pas de code 200');
}
// si le challenge est validé par hydra, on le stocke en session pour l'utiliser par la suite et on redirige vers une route interne protégée qui va déclencher l'identification FranceConnect
$this->session->set('challenge', $loginRequestInfo['challenge']);
return $this->redirectToRoute('app_login');
}
public function handleConsentRequest(Request $request)
{
$challenge = $request->query->get('consent_challenge');
if (!$challenge) {
throw new BadRequestException("Le challenge n'est pas disponible");
}
$consentRequestInfo = $this->client->fetchConsentRequestInfo($challenge)->toArray();
/** @var User */
$user = $this->getUser();
$consentAcceptResponse = $this->client->acceptConsentRequest($consentRequestInfo['challenge'], [
'grant_scope' => $consentRequestInfo['requested_scope'],
'session' => [
'id_token' => $user->getAttributes(),
],
])->toArray();
return new RedirectResponse($consentAcceptResponse['redirect_to']);
}
public function handleLogoutRequest(Request $request)
{
$logoutChallenge = $request->get('logout_challenge');
if (empty($logoutChallenge)) {
throw new InvalidChallengeException();
}
$logoutRequestInfo = $this->client->fetchLogoutRequestInfo($logoutChallenge)->toArray();
$logoutAcceptRes = $this->client->acceptLogoutRequest($logoutRequestInfo['challenge'])->toArray();
$this->session->clear();
$this->tokenStorage->setToken();
return new RedirectResponse($logoutAcceptRes['redirect_to']);
}
}

View File

@ -2,10 +2,31 @@
namespace App;
use App\DependencyInjection\PdoExtension;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
class Kernel extends BaseKernel
{
use MicroKernelTrait;
use MicroKernelTrait {
registerContainerConfiguration as microKernelConfigureContainer;
}
/**
* {@inheritdoc}
*/
public function registerContainerConfiguration(LoaderInterface $loader)
{
$this->microKernelConfigureContainer($loader);
$loader->load(function (ContainerBuilder $container) use ($loader) {
$envLanguage = \getenv('APP_LOCALES');
$container->registerExtension(new PdoExtension());
$loader->load($this->getConfigDir().'/pdo_configuration/*.{yml,yaml}', 'glob');
});
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace App\Pdo\Exception;
class InvalidPasswordException
{
}

View File

@ -9,32 +9,35 @@ class PdoConnect extends AbstractController
{
/**
* @var Singleton
* @access private
* @static
*/
private static $_instance = null;
/**
* Constructeur de la classe
*
* @param void
* @return void
*/
private function __construct() {
* Constructeur de la classe
*
* @param void
*
* @return void
*/
private function __construct()
{
}
/**
* Méthode qui crée l'unique instance de la classe
* si elle n'existe pas encore puis la retourne.
*
* @param void
*
* @return PdoConnect
*/
public static function getInstance() {
if(is_null(self::$_instance)) {
self::$_instance = new PdoConnect();
public static function getInstance()
{
if (is_null(self::$_instance)) {
self::$_instance = new PdoConnect();
}
return self::$_instance;
}
@ -42,4 +45,4 @@ class PdoConnect extends AbstractController
{
return new PDO($urlDatabase, $dbUser, $dbPassword);
}
}
}

66
src/Pdo/PdoRequest.php Normal file
View File

@ -0,0 +1,66 @@
<?php
namespace App\Pdo;
class PdoRequest
{
public const DATA_TO_FETCH = 'data_to_fetch';
public const COLUMN_LOGIN_NAME = 'column_login_name';
public const COLUMN_PASSWORD_NAME = 'column_password_name';
public const TABLE_NAME = 'table_name';
protected array $config;
protected string $dsn;
protected string $user;
protected string $password;
public function __construct(array $config = [], string $dsn, string $user, string $password)
{
$this->config = $config;
$this->dsn = $dsn;
$this->user = $user;
$this->password = $password;
}
public function getDatabaseDsn()
{
return $this->dsn;
}
public function getDbUser()
{
return $this->user;
}
public function getDbPassword()
{
return $this->password;
}
public function getNameLogin()
{
return $this->config[self::COLUMN_LOGIN_NAME];
}
public function getNamePassword()
{
return $this->config[self::COLUMN_PASSWORD_NAME];
}
public function getRequestScope()
{
$scope = '';
foreach ($this->config[self::DATA_TO_FETCH] as $data) {
$scope .= $data.',';
}
$scope = substr($scope, 0, -1);
$request = 'SELECT '.$scope.' FROM '.$this->config[self::TABLE_NAME].' WHERE '.$this->config[self::COLUMN_LOGIN_NAME].' = :'.$this->config[self::COLUMN_LOGIN_NAME].';';
return $request;
}
public function getRequestLogin()
{
return 'SELECT '.$this->config[self::COLUMN_PASSWORD_NAME].' FROM '.$this->config[self::TABLE_NAME].' WHERE '.$this->config[self::COLUMN_LOGIN_NAME].' = :'.$this->config[self::COLUMN_LOGIN_NAME].';';
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace App\Security;
use App\Entity\User;
use App\Hydra\Client;
use App\Pdo\Exception\InvalidPasswordException;
use App\Services\PdoService;
use PDOException;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
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\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
class PdoUserAuthenticator extends AbstractAuthenticator
{
public const LOGIN_ROUTE = 'app_login';
private PdoService $pdoService;
protected string $baseUrl;
private Client $client;
private SessionInterface $session;
public function __construct(string $baseUrl, PdoService $pdoService, Client $client, SessionInterface $session)
{
$this->baseUrl = $baseUrl;
$this->pdoService = $pdoService;
$this->client = $client;
$this->session = $session;
}
/**
* Called on every request to decide if this authenticator should be
* used for the request. Returning `false` will cause this authenticator
* to be skipped.
*/
public function supports(Request $request): bool
{
return self::LOGIN_ROUTE === $request->attributes->get('_route') && $request->isMethod('POST');
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): ?Response
{
return new RedirectResponse($this->baseUrl.'/connect/login-accept');
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
$message = strtr($exception->getMessageKey(), $exception->getMessageData());
return new Response($message, Response::HTTP_FORBIDDEN);
}
public function authenticate(Request $request): Passport
{
$login = $request->request->get('login');
$password = $request->request->get('password');
$rememberMe = $request->request->get('_remember_me') ? true : false;
try {
// requête préparée
$remoteHashedPassword = $this->pdoService->fetchPassword($login);
} catch (PDOException $e) {
dd($e);
}
try {
$this->pdoService->verifyPassword($password, $remoteHashedPassword);
$attributes = $this->pdoService->fetchDatas($login);
$user = new User($login, $password, $attributes, $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 (InvalidPasswordException $e) {
throw new AuthenticationException();
}
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace App\Security;
use App\Entity\User;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
class PdoUserProvider implements UserProviderInterface
{
protected RequestStack $requestStack;
public function __construct(RequestStack $requestStack, SessionInterface $session)
{
$this->requestStack = $requestStack;
$this->session = $session;
}
public function loadUserByIdentifier(string $identifier, ?User $user): ?UserInterface
{
if ($user->getUserIdentifier() === $identifier) {
return $user;
}
return null;
}
public function loadUserByUsername(string $username): ?UserInterface
{
return $this->loadUserByIdentifier($username, null);
}
public function refreshUser(UserInterface $user)
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Invalid user class "%s".', get_class($user)));
}
return $this->loadUserByIdentifier($user->getUserIdentifier(), $user);
}
public function supportsClass(string $class)
{
return User::class === $class || is_subclass_of($class, User::class);
}
}

View File

@ -0,0 +1,83 @@
<?php
namespace App\Services;
use App\Pdo\Exception\InvalidPasswordException;
use App\Pdo\PdoConnect;
use App\Pdo\PdoRequest;
use PDO;
use PDOException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class PdoService extends AbstractController
{
private $params;
public PdoRequest $pdoRequest;
public function __construct(ParameterBagInterface $params, PdoRequest $pdoRequest)
{
$this->params = $params;
$this->pdoRequest = $pdoRequest;
}
public function fetchDatas($login)
{
try {
$dbh = $this->getConnection();
// forge de la requête
$request = $this->pdoRequest->getRequestScope();
// Préparation de la requête
$query = $dbh->prepare($request);
$query->execute([$this->pdoRequest->getNameLogin() => $login]);
$datas = $query->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
echo 'Erreur fetch Datas !: '.$e->getMessage().'<br/>';
exit();
}
return $datas;
}
/**
* @param mixed $login
*
* @return bool
*/
public function fetchPassword($login)
{
try {
$dbh = $this->getConnection();
$request = $this->pdoRequest->getRequestLogin();
$query = $dbh->prepare($request);
$query->execute([$this->pdoRequest->getNameLogin() => $login]);
$password = $query->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
echo 'Erreur fetch Password!: '.$e->getMessage().'<br/>';
exit();
}
return $password[$this->pdoRequest->getNamePassword()];
}
public function getConnection()
{
// Appel du singleton
$pdo = PdoConnect::getInstance();
// Connection bdd
return $pdo->connect($this->pdoRequest->getDatabaseDsn(), $this->pdoRequest->getDbUser(), $this->pdoRequest->getDbPassword());
}
public function verifyPassword($password, $hashedPassword)
{
$hashAlgo = $this->params->get('hashAlgo') ?? 'sha256';
if ($hashedPassword === hash($hashAlgo, $password)) {
return true;
} else {
throw new InvalidPasswordException();
}
}
}

View File

@ -1,53 +0,0 @@
<?php
namespace App\Services;
use App\Pdo\PdoConnect;
use PDO;
use PDOException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class PdoServices extends AbstractController
{
private $params;
public function __construct(ParameterBagInterface $params)
{
$this->params = $params;
}
public function fetchDatas($email)
{
try {
$pdo = PdoConnect::getInstance();
$dbh = $pdo->connect($this->params->get('urlDatabase'), $this->params->get('dbUser'), $this->params->get('dbPassword'));
$query = $dbh->prepare($this->getParameter('queryFetchDatas'));
$query->execute(['email'=> $email]);
$datas = $query->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
print "Erreur !: " . $e->getMessage() . "<br/>";
die();
}
return $datas;
}
public function verifyPassword($password, $hashedPassword)
{
$hashMethod = $this->params->get('hashMethod');
switch ($hashMethod){
case "sha1":
return $hashedPassword === sha1($password, false);
break;
case "md5":
return $hashedPassword === md5($password);
break;
case "BCRYPT":
default:
return password_verify($password, $hashedPassword);
break;
}
}
}