<?php
namespace Platform\SecurityBundle\Service\Authenticator;
use Doctrine\ORM\EntityManagerInterface;
use Platform\SecurityBundle\Controller\LoginController;
use Platform\SecurityBundle\Doctrine\Identity\AccountRepository;
use Platform\SecurityBundle\Entity\Identity\Account;
use Platform\SecurityBundle\Entity\Login\Attempt;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
use Exception;
class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
use TargetPathTrait;
/**
* @var EntityManagerInterface
*/
protected EntityManagerInterface $em;
/**
* @var RouterInterface
*/
protected RouterInterface $router;
/**
* @var AccountRepository
*/
protected AccountRepository $accountRepository;
/**
* @var HttpUtils
*/
protected HttpUtils $httpUtils;
/**
* @param EntityManagerInterface $em
* @param RouterInterface $router
* @param HttpUtils $httpUtils
*/
public function __construct(
EntityManagerInterface $em,
RouterInterface $router,
HttpUtils $httpUtils
)
{
$this->em = $em;
$this->router = $router;
$this->httpUtils = $httpUtils;
/** @var AccountRepository $accountRepository */
$accountRepository = $this->em->getRepository(Account::class);
$this->accountRepository = $accountRepository;
}
/**
* @param Request $request
* @return string
*/
protected function getLoginUrl(Request $request): string
{
return $this->router->generate(LoginController::ROUTES__SELECT);
}
/**
* @param Request $request
* @return Passport
*/
public function authenticate(Request $request): Passport
{
$email = trim($request->request->get('login')['username']);
$password = trim($request->request->get('login')['password']);
$csrfToken = trim($request->request->get('login')['_token']);
if (empty($email) || empty($password) || empty($csrfToken)) {
throw new AuthenticationException('Improper login.');
}
return new Passport(
new UserBadge($email, function (string $userIdentifier) {
$account = $this->accountRepository->findOneBy(['email' => $userIdentifier]);
if ( ! $account instanceof Account) {
throw new UserNotFoundException();
}
return $account;
}),
new PasswordCredentials($password),
[
new CsrfTokenBadge('login', $csrfToken),
]
);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
/** @var Account $account */
$account = ($token->getUser() instanceof Account) ? $token->getUser() : null;
$this->logSuccessfulAttempt($account, $request->getClientIp());
return null;
}
/**
* @param Request $request
* @param AuthenticationException $exception
* @return Response
* @throws Exception
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
{
$message = ( ! empty($exception->getMessage())) ? trim(substr($exception->getMessage(), 0, 250)) : null;
$this->logFailedAttempt($request->getClientIp(), $message);
$request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);
return new RedirectResponse(
$this->router->generate(LoginController::ROUTES__SELECT)
);
}
/**
* @param Account $account
* @param string|null $ip
* @return void
* @throws Exception
*/
private function logSuccessfulAttempt(Account $account, ?string $ip): void
{
$attempt = (new Attempt())
->setAccount($account)
->setRedirect(null)
->setIp($ip)
->setStatus(true)
->setAuthType(Attempt::AUTH_TYPE__WEB)
;
$this->em->persist($attempt);
$this->em->flush();
}
/**
* @param string|null $ip
* @param string|null $message
* @return void
* @throws Exception
*/
private function logFailedAttempt(?string $ip, ?string $message): void
{
$attempt = (new Attempt())
->setRedirect(null)
->setIp($ip)
->setStatus(false)
->setAuthType(Attempt::AUTH_TYPE__WEB)
;
if ( ! empty($message)) {
$attempt->setMessage($message);
}
$this->em->persist($attempt);
$this->em->flush();
}
}