<?php
namespace Platform\SecurityBundle\Service\Authenticator;
use App\Util\Json;
use Cms\ContainerBundle\Controller\DashboardController;
use Cms\CoreBundle\Util\Doctrine\EntityManager;
use Platform\SecurityBundle\Controller\LoginController;
use Platform\SecurityBundle\Controller\SingleSignOnController;
use Platform\SecurityBundle\Entity\Identity\Account;
use Platform\SecurityBundle\Service\OAuth\OAuthProviderService;
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\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\HttpUtils;
/**
*
*/
class OAuthLoginAuthenticator extends AbstractAuthenticator
{
/**
* @var EntityManager
*/
protected EntityManager $em;
/**
* @var OAuthProviderService
*/
protected OAuthProviderService $oAuthProviderService;
/**
* @var HttpUtils
*/
protected HttpUtils $httpUtils;
/**
* @param EntityManager $em
* @param OAuthProviderService $oAuthProviderService
* @param HttpUtils $httpUtils
*/
public function __construct(
EntityManager $em,
OAuthProviderService $oAuthProviderService,
HttpUtils $httpUtils
)
{
$this->em = $em;
$this->oAuthProviderService = $oAuthProviderService;
$this->httpUtils = $httpUtils;
}
/**
* {@inheritDoc}
*/
public function supports(Request $request): ?bool
{
return $this->httpUtils->checkRequestPath($request, SingleSignOnController::ROUTES__FINISH);
}
/**
* {@inheritDoc}
*/
public function authenticate(Request $request): Passport
{
// handle oauth issues
try {
// determine the provider
$provider = $this->oAuthProviderService->getProvider(
$this->getProvider($request),
);
// get the code
// one may not be given if the user rejected the access or there was some other problem
$code = $this->getCode($request);
if ( ! $code) {
throw new AuthenticationException(
$request->query->get('error_description')
?: $request->query->get('error')
?: '',
);
}
// read out the access token
$accessToken = $provider->getAccessToken(
$this->getCode($request),
);
// get the info from the provider using the access token
$identity = $provider->getMe($accessToken);
} catch (\Exception $e) {
throw new AuthenticationException(
($e instanceof AuthenticationException) ? $e->getMessage() : '',
0,
( ! $e instanceof AuthenticationException) ? $e : null,
);
}
// try to obtain the account
// TODO: do we need to worry about tenant stuff here?
$account = $this->em->getRepository(Account::class)->findOneByEmail(
$identity['email'],
);
if ( ! $account) {
throw new AuthenticationException();
}
// make a passport
$passport = new SelfValidatingPassport(
new UserBadge($account->getId())
);
// for debugging, add oauth information into the passport
$passport->setAttribute('oauth_identity', $identity);
$passport->setAttribute('oauth_provider', $provider->getId());
$passport->setAttribute('oauth_state', $this->getState($request));
$passport->setAttribute('oauth_data', $this->getData($request));
$passport->setAttribute('oauth_redirect', $this->getRedirect($request));
return $passport;
}
/**
* {@inheritDoc}
*/
public function createToken(Passport $passport, string $firewallName): TokenInterface
{
$token = parent::createToken(
$passport,
$firewallName
);
$token->setAttribute('redirect', $passport->getAttribute('oauth_redirect'));
return $token;
}
/**
* {@inheritDoc}
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
return $this->httpUtils->createRedirectResponse(
$request,
DashboardController::ROUTES__INDEX,
);
}
/**
* {@inheritDoc}
*/
public function onAuthenticationFailure(
Request $request,
AuthenticationException $exception,
): Response
{
$request->getSession()?->set(
Security::AUTHENTICATION_ERROR,
$exception,
);
return $this->httpUtils->createRedirectResponse(
$request,
LoginController::ROUTES__SELECT,
);
}
/**
* @param Request $request
* @return string|null
*/
public function getProvider(Request $request): ?string
{
return $request->query->get('provider', []);
}
/**
* @param Request $request
* @return string|null
*/
public function getCode(Request $request): ?string
{
return $request->query->get('code');
}
/**
* @param Request $request
* @return array
*/
protected function getState(Request $request): array
{
return $request->query->has('state') ? Json::decode(
base64_decode($request->query->get('state', [])),
true,
) : [];
}
/**
* @param Request $request
* @return array
*/
protected function getData(Request $request): array
{
$state = $this->getState($request);
return ( ! empty($state) && array_key_exists('data', $state)) ? $state['data'] : [];
}
/**
* @param Request $request
* @return string|null
*/
protected function getRedirect(Request $request): ?string
{
$data = $this->getData($request);
return ( ! empty($data) && array_key_exists('redirect', $data) && ! empty($data['redirect'])) ? $data['redirect'] : null;
}
}