<?php namespace App\Security; use App\Entity\User; use App\Security\Hasher\PasswordEncoder; use App\Service\SQLLoginService; use App\SQLLogin\Exception\InvalidSQLPasswordException; use PDOException; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Security; 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 SQLLoginUserAuthenticator extends AbstractAuthenticator { public const LOGIN_ROUTE = 'app_login'; public const ERROR_LOGIN = 'error_login'; public const ERROR_PASSWORD = 'error_password'; public const ERROR_SQL_LOGIN = 'error_sql_login'; protected string $baseUrl; private SQLLoginService $sqlLoginService; private PasswordEncoder $passwordHasher; public function __construct(string $baseUrl, SQLLoginService $sqlLoginService, PasswordEncoder $passwordHasher) { $this->baseUrl = $baseUrl; $this->sqlLoginService = $sqlLoginService; $this->passwordHasher = $passwordHasher; } /** * 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 { $request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception); return new RedirectResponse($this->baseUrl.'/connect/login-accept'); } public function authenticate(Request $request): Passport { $form = $request->request->get('login'); $login = $form['login']; $plaintextPassword = $form['password']; $rememberMe = isset($form['_remember_me']) ? true : false; try { // requête préparée list($remoteHashedPassword, $remoteSalt) = $this->sqlLoginService->fetchPassword($login); } catch (PDOException $e) { $request->getSession()->set(self::ERROR_SQL_LOGIN, true); throw new AuthenticationException(); } if ($remoteHashedPassword) { try { // Comparaison remote hash et hash du input password + salt $this->passwordHasher->verify($remoteHashedPassword, $plaintextPassword, $remoteSalt); $attributes = $this->sqlLoginService->fetchDatas($login); $user = new User($login, $remoteHashedPassword, $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 (InvalidSQLPasswordException $e) { $request->getSession()->set(self::ERROR_PASSWORD, true); throw new AuthenticationException(); } catch (PDOException $e) { $request->getSession()->set(self::ERROR_SQL_LOGIN, true); throw new AuthenticationException(); } } $request->getSession()->set(self::ERROR_LOGIN, true); throw new AuthenticationException(); } }