<?php
namespace App\BackendBundle\Security\Authenticator;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
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\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
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\Authenticator\Passport\SelfValidatingPassport;
use App\BackendBundle\Helper\SecurityHelper;
use App\BackendBundle\Helper\UserHelper;
class OAuth2Authenticator extends AbstractLoginFormAuthenticator
{
private UserHelper $userHelper;
private LoggerInterface $logger;
private RequestStack $requestStack;
private RouterInterface $router;
private SessionInterface $session;
private SecurityHelper $securityHelper;
private string $sessionOAuthKey = 'oauth2_session_username';
private string $sessionEmail = 'oauth2_session_email';
public function __construct(
UserHelper $userHelper,
LoggerInterface $logger,
RouterInterface $router,
RequestStack $requestStack,
SecurityHelper $securityHelper
)
{
$this->userHelper = $userHelper;
$this->logger = $logger;
$this->requestStack = $requestStack;
$this->session = $this->requestStack->getSession();
$this->securityHelper = $securityHelper;
$this->router = $router;
}
public function supports(Request $request): bool
{
/*
* Important !!!
* The sessionOAuthKey contains the username after login success and
* the symfony authenticator is called before the AuthorizationRequestResolverListener
* which access the TokenStorage
*/
if ($this->session->has($this->sessionOAuthKey)) {
return true;
}
if (!$request->isMethod('POST')) {
return false;
}
$loginUrl = $this->router->generate('oauth2_login'); // need to create url without parameter
$fullPath = $request->getBaseUrl() . $request->getPathInfo();
if ($loginUrl !== $fullPath) {
return false;
}
return true;
}
protected function getLoginUrl(Request $request): string
{
$parameters = $request->query->all();
return $this->router->generate('oauth2_login', $parameters);
}
public function authenticate(Request $request): Passport
{
$userIdentifier = $this->session->get($this->sessionOAuthKey);
if (empty($userIdentifier)) {
return $this->passportLoginForm($request);
} else {
return new SelfValidatingPassport(new UserBadge($userIdentifier));
}
}
private function passportLoginForm(Request $request): Passport
{
$email = $request->request->get('email', '');
$plaintextPassword = $request->request->get('password', '');
if (empty($email) || empty($plaintextPassword)) {
throw new CustomUserMessageAuthenticationException('Benutzername oder Passwort wurden nicht ausgefüllt.');
}
$request->getSession()->set($this->sessionEmail, $email);
return new Passport(new UserBadge($email), new PasswordCredentials($plaintextPassword));
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
$request->getSession()->remove(Security::AUTHENTICATION_ERROR);
if ($this->session->has($this->sessionOAuthKey)) {
// on success, let the request continue
return null;
}
/* get username and set in session */
$user = $token->getUser();
$userName = $user->getUsername();
$this->session->set($this->sessionOAuthKey, $userName);
/* update last login */
$this->userHelper->updateLastLogin($user);
/* redirect back to oauth2 server */
$parameters = $request->query->all();
$url = $this->router->generate('oauth2_authorize', $parameters);
return new RedirectResponse($url);
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
{
if ($request->hasSession()) {
$errorText = $this->securityHelper->getLoginErrorText($exception);
$request->getSession()->set(Security::AUTHENTICATION_ERROR, $errorText);
}
$this->session->remove($this->sessionOAuthKey);
$url = $this->getLoginUrl($request);
return new RedirectResponse($url);
}
}