<?php
namespace App\Security;
use App\Object\Admin\User;
use App\Services\Admin\CustomerService;
use App\Services\Admin\UserService;
use App\Services\Main\ApiService;
use App\Services\Main\SecurityService;
use App\Services\Util\ClockService;
use App\Services\Util\JsonService;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* Class LoginFormAuthenticator.
*/
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
use TargetPathTrait;
private const ERROR_DISABLED = 'disabledUser';
private const ERROR_DELETED = 'deletedUser';
private const ERROR_EXPIRED = 'expiredCustomer';
private const ERROR_TWO_AUTH_REQUIRED = 'twoAuthRequired';
/**
* @var UrlGeneratorInterface
*/
private UrlGeneratorInterface $urlGenerator;
/**
* @var CsrfTokenManagerInterface
*/
private CsrfTokenManagerInterface $csrfTokenManager;
/**
* @var SecurityService
*/
private SecurityService $securityService;
/**
* @var CustomerService
*/
private CustomerService $customerService;
private UserService $userService;
/**
* @var ApiService
*/
private ApiService $apiService;
/**
* @var Request
*/
private Request $request;
/**
* @var TranslatorInterface
*/
private TranslatorInterface $translator;
/**
* @var ClockService
*/
private ClockService $clockService;
/**
* @var JsonService
*/
private JsonService $jsonService;
/**
* @var RequestStack
*/
private RequestStack $requestStack;
/**
* LoginFormAuthenticator constructor.
*
* @param UrlGeneratorInterface $urlGenerator
* @param SessionInterface $session
* @param CsrfTokenManagerInterface $csrfTokenManager
* @param SecurityService $securityService
* @param CustomerService $customerService
* @param UserService $userService
* @param ApiService $apiService
* @param RequestStack $request
* @param ClockService $clockService
* @param JsonService $jsonService
* @param RequestStack $requestStack
*/
public function __construct(
UrlGeneratorInterface $urlGenerator,
CsrfTokenManagerInterface $csrfTokenManager,
SecurityService $securityService,
CustomerService $customerService,
UserService $userService,
ApiService $apiService,
RequestStack $request,
TranslatorInterface $translator,
ClockService $clockService,
JsonService $jsonService,
RequestStack $requestStack
) {
$this->urlGenerator = $urlGenerator;
$this->csrfTokenManager = $csrfTokenManager;
$this->securityService = $securityService;
$this->customerService = $customerService;
$this->userService = $userService;
$this->apiService = $apiService;
$this->request = $request->getCurrentRequest();
$this->translator = $translator;
$this->clockService = $clockService;
$this->jsonService = $jsonService;
$this->requestStack = $requestStack;
}
/**
* @param Request $request
*
* @return bool
*/
public function supports(Request $request): bool
{
//Root is translations and default landing page route
return in_array($request->attributes->get('_route'), ['app.login', 'root'])
&& $request->isMethod('POST');
}
/**
* @param Request $request
*
* @return array|mixed
*/
public function getCredentials(Request $request)
{
$credentials = [
'username' => $request->request->get('login')['login'],
'password' => $request->request->get('login')['password'],
'twoAuthToken' => $request->request->get('login')['twoAuthToken'] ?? null,
'csrf_token' => $request->request->get('_csrf_token'),
];
$request->getSession()->set(
Security::LAST_USERNAME,
$credentials['username']
);
return $credentials;
}
/**
* @param mixed $credentials
* @param UserProviderInterface $userProvider
* @param null|array $tokens
*
* @throws DecodingExceptionInterface
*
* @return null|RedirectResponse|UserInterface
*/
public function getUser($credentials, UserProviderInterface $userProvider, ?array $tokens = null)
{
if (null === $tokens) {
if (isset($credentials['ssoToken'])) {
$response = $this->securityService->logUserSSO($credentials['ssoToken']);
} else {
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
if (!$this->csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}
$options = [
'json' => [
'username' => $credentials['username'],
'password' => $credentials['password'],
],
];
if (null !== $credentials['twoAuthToken']) {
$options['headers']['TwoAuthToken'] = $credentials['twoAuthToken'];
}
$response = $this->apiService->post('login', $options);
}
$loginResponse = $response->getArrayResponse();
if ($loginResponse['error']) {
throw new CustomUserMessageAuthenticationException('security.invalid');
}
$loginResult = $response->getContent();
$token = $loginResult['token'];
if (null === $token) {
if (in_array($loginResult['errorMessage'], [self::ERROR_DISABLED, self::ERROR_EXPIRED])) {
throw new CustomUserMessageAuthenticationException('security.disabled');
}
if (self::ERROR_TWO_AUTH_REQUIRED === $loginResult['errorMessage']) {
$credentials['mailError'] = $loginResult['mailError'];
throw new CustomUserMessageAuthenticationException('security.twoAuth.required.' . mb_strtolower($loginResult['twoAuthType']), $credentials);
}
throw new CustomUserMessageAuthenticationException('security.invalid');
}
$refreshToken = $loginResult['refresh_token'];
} else {
$token = $tokens['token'];
$refreshToken = $tokens['refresh_token'];
}
$this->securityService->setToken($token);
$this->securityService->setRefreshToken($refreshToken);
$response = $this->apiService->get('user/me', [
'auth_bearer' => $token,
]);
$userResponse = $response->getArrayResponse();
if ($userResponse['error']) {
throw new CustomUserMessageAuthenticationException('security.invalid');
}
$userArray = $response->getContent();
$user = new User();
$user->setId($userArray['id']);
$user->setLogin($userArray['login']);
$user->setTwoFactorAuth($userArray['two_factor_auth']);
$user->setCustomer($this->jsonService->denormalize(
$userArray['customer'],
'App\Object\Admin\Customer'
));
$user->setCustomerType($userArray['customer']['customer_type']);
$user->setLastPasswordUpdateDate($userArray['last_password_update_date']);
$user->setRole($this->jsonService->denormalize(
$userArray['role'],
'App\Object\Admin\Role'
));
if (!$user) {
// fail authentication with a custom error
throw new CustomUserMessageAuthenticationException('Username could not be found.');
}
return $user;
}
/**
* @param mixed $credentials
* @param UserInterface $user
*
* @return bool
*/
public function checkCredentials($credentials, UserInterface $user): bool
{
return true;
}
/**
* Used to upgrade (rehash) the user's password automatically over time.
*
* @param $credentials
*
* @return null|string
*/
public function getPassword($credentials): ?string
{
return $credentials['password'];
}
/**
* @param Request $request
* @param TokenInterface $token
* @param string $providerKey
*
* @return null|RedirectResponse|Response
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey)
{
$user = $token->getUser();
$rgpdInformation = $this->userService->getRgpdInformation();
if ($rgpdInformation['isExpired']) {
return new RedirectResponse(
$this->urlGenerator->generate('app.definePassword', [
'rgpdOptions' => $rgpdInformation['rgpdOptions'],
'login' => $user->getLogin(),
])
);
}
if (null === $user->getTwoFactorAuth() && $rgpdInformation['rgpdOptions']['forceTwoAuth']) {
$loginInformations = $request->request->get('login');
$session = $this->requestStack->getSession();
//Store in session to get it back after without passing it as request parameters
$session->set('login-informations', $loginInformations);
return new RedirectResponse(
$this->urlGenerator->generate('app.defineTwoAuth')
);
}
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}
return new RedirectResponse(
$this->urlGenerator->generate('dashboard.index')
);
}
/**
* @return string
*/
protected function getLoginUrl(): string
{
return $this->urlGenerator->generate('app.login');
}
}