<?php
namespace Platform\SecurityBundle\Controller\System;
use Cms\CoreBundle\Util\Controller;
use Cms\TenantBundle\Entity\Tenant;
use Platform\SecurityBundle\Controller\SingleSignOnController;
use Platform\SecurityBundle\Model\OAuth\OAuthOptions;
use Platform\SecurityBundle\Service\OAuth\OAuthProviderService;
use Platform\SecurityBundle\Service\OAuth\Providers\AbstractOAuthProvider;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Laminas\Uri\Uri;
/**
* Class OAuthEndpointController
* @package Platform\SecurityBundle\Controller\System
*/
class OAuthController extends Controller
{
const ROUTES__CALLBACK = 'platform.security.oauth.callback';
const ROUTES__INSTANT_LOGIN = 'platform.security.oauth.instant_login';
/**
* @param Request $request
* @param string $provider
* @return RedirectResponse
* @throws \Exception
*
* @Route(
* "/{provider}",
* name = OAuthController::ROUTES__INSTANT_LOGIN
* )
*/
public function instantAction(Request $request, string $provider)
{
// obtain the provider
$provider = $this->getOAuthProviderService()->getProvider($provider);
// make sure the provider supports instant logins
if ( ! $provider->isInstant()) {
throw new \Exception(sprintf(
'OAuth provider "%s" does not support instant logins.',
$provider->getId()
));
}
// get a token
$token = $provider->getAccessToken(
$request->query->get('code'),
new OAuthOptions([
'callback' => strtok($request->getUri(), '?')
])
);
// get the identity
$identity = $provider->getMe($token);
// obtain the client id
// the format of this varies but uses the same field from the customized payload
$client = $identity['client'];
if (empty($client)) {
throw new \Exception(sprintf(
'Client ID not found in OAuth identity for provider "%s"',
$provider->getId()
));
}
// find the tenant for the district
$field = sprintf(
'%sCustomerId',
$provider->getId()
);
$tenant = $this->getEntityManager()->getRepository(Tenant::class)->findOneBy([
$field => $client,
]);
if (empty($tenant)) {
throw new \Exception('Could not associate tenant to instant login attempt.');
}
/*
* TODO: make this cleaner...
* currently this just restarts the login process by forcing the user through the full flow
* this was actually suggested by clever or gg4l docs
* however, since we have kind of processed a login by this point, we should be able to just let them go in
*/
// generate a new endpoint
$uri = new Uri($this->generateUrl(
SingleSignOnController::ROUTES__START,
[
'id' => $provider->getId(),
],
UrlGeneratorInterface::ABSOLUTE_URL
));
$uri->setHost(sprintf(
'%s.%s',
$tenant->getSlug(),
$this->getGlobalContext()->getDashboard(true)
));
// do the redirect
return new RedirectResponse($uri->toString());
}
/**
* Handles the OAuth callback endpoint.
* The job of this controller is to basically forward the request to the proper tenanted route.
*
* @param Request $request
* @return RedirectResponse
* @throws \Exception
*
* @Route(
* "",
* name = OAuthController::ROUTES__CALLBACK
* )
*/
public function callbackAction(Request $request)
{
// if there is no state, that means somebody hit this directly or there was an error
if ( ! $request->query->has('state')) {
throw new BadRequestException();
}
// we should always have a state bag, grab its details
$state = AbstractOauthProvider::parseState($request->query->get('state'));
// we should now have a redirect in the state
if (empty($state->get('redirect'))) {
throw new \Exception();
}
$redirect = new Uri($state->get('redirect'));
// need to merge in the query string from this request
$redirect->setQuery(array_merge(
$redirect->getQueryAsArray(),
$request->query->all()
));
// perform the redirect
return new RedirectResponse($redirect->toString());
}
/**
* @return OAuthProviderService|object
*/
private function getOAuthProviderService(): OAuthProviderService
{
return $this->get(__METHOD__);
}
}