id4me-2.0.x-dev/src/Controller/RedirectController.php
src/Controller/RedirectController.php
<?php
namespace Drupal\id4me\Controller;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Messenger\Messenger;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Url;
use Drupal\id4me\AuthmapService;
use Drupal\id4me\Id4meService;
use Drupal\id4me\StateToken;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Returns responses for ID4me routes.
*/
final class RedirectController extends ControllerBase {
/**
* The request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* The ID4me service.
*
* @var \Drupal\id4me\Id4meService
*/
protected $id4meService;
/**
* The Authmap service.
*
* @var \Drupal\id4me\AuthmapService
*/
protected $authmapService;
/**
* The messenger service.
*
* @var \Drupal\Core\Messenger\Messenger
* The messenger service object.
*/
protected $messengerService;
/**
* The current user object.
*
* @var \Drupal\Core\Session\AccountProxyInterface
*/
protected $currentUser;
/**
* RedirectController constructor.
*
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
* @param \Drupal\id4me\Id4meService $id4me_service
* The ID4me service object.
*/
/**
* RedirectController constructor.
*
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
* @param \Drupal\id4me\Id4meService $id4me_service
* The ID4me service object.
* @param \Drupal\id4me\AuthmapService $authmap_service
* The Authmap service object.
* @param \Drupal\Core\Messenger\Messenger $messenger_service
* The messenger service object.
* @param \Drupal\Core\Session\AccountProxyInterface $current_user
* The current user object.
*/
public function __construct(
RequestStack $request_stack,
Id4meService $id4me_service,
AuthmapService $authmap_service,
Messenger $messenger_service,
AccountProxyInterface $current_user
) {
$this->requestStack = $request_stack;
$this->id4meService = $id4me_service;
$this->authmapService = $authmap_service;
$this->messengerService = $messenger_service;
$this->currentUser = $current_user;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new self(
$container->get('request_stack'),
$container->get('id4me'),
$container->get('id4me.authmap'),
$container->get('messenger'),
$container->get('current_user')
);
}
/**
* Access callback: Redirect page.
*
* @return \Drupal\Core\Access\AccessResultInterface
* Whether the state token matches the previously created one that is stored
* in the session.
*/
public function access() {
// Confirm anti-forgery state token. This round-trip verification helps to
// ensure that the user, not a malicious script, is making the request.
$query = $this->requestStack->getCurrentRequest()->query;
$state_token = $query->get('state');
if ($state_token && StateToken::confirm($state_token)) {
return AccessResult::allowed();
}
return AccessResult::forbidden();
}
/**
* Authenticate with the Id4me service.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* A redirect response.
*
* @throws \Drupal\Core\Entity\EntityStorageException
* @throws \Id4me\RP\Exception\InvalidAuthorityIssuerException
* @throws \Id4me\RP\Exception\InvalidIDTokenException
*/
public function authenticate() {
$query = $this->requestStack->getCurrentRequest()->query;
// Delete the state token, since it's already been confirmed.
StateToken::delete();
if (!$query->get('error') && !$query->get('code')) {
// In case we don't have an error, but the client could not be loaded or
// there is no state token specified, the URI is probably being visited
// outside of the login flow.
throw new NotFoundHttpException();
}
if ($query->get('error')) {
if (in_array($query->get('error'), [
'interaction_required',
'login_required',
'account_selection_required',
'consent_required',
])) {
// If we have an one of the above errors, that means the user hasn't
// granted the authorization for the claims.
$this->messengerService->addWarning(t('Logging in with Id4me has been canceled.'));
}
else {
// Any other error should be logged. E.g. invalid scope.
$variables = [
'@error' => $query->get('error'),
'@details' => $query->get('error_description') ? $query->get('error_description') : $this->t('Unknown error.'),
];
$message = 'Authorization failed: @error. Details: @details';
$this->loggerFactory->get('id4me')->error($message, $variables);
$this->messengerService->addWarning(t('Could not authenticate with Id4me.'));
}
}
else {
// Process the login or connect operations.
$this->id4meService->setState($query->get('state'));
$authorizationTokens = $this->id4meService->getAuthorizationTokens($query->get('code'));
$userInfo = $this->id4meService->getUserInfo();
$account = $this->authmapService->userLoadByIdentifier(
$authorizationTokens->getIdTokenDecoded()->getIss(),
$authorizationTokens->getIdTokenDecoded()->getSub()
);
if ($this->currentUser->isAuthenticated() || !$account) {
$account = User::create([
'name' => $userInfo->getPreferredUsername(),
'mail' => $userInfo->getEmail(),
'status' => 1,
]);
$account->save();
$this->authmapService->createAssociation(
$account,
$authorizationTokens->getIdTokenDecoded()->getIss(),
$authorizationTokens->getIdTokenDecoded()->getSub()
);
user_login_finalize($account);
}
elseif ($account instanceof UserInterface) {
user_login_finalize($account);
}
}
return new RedirectResponse(Url::fromUserInput('/')->toString());
}
}
