authman-1.x-dev/src/Controller/AuthmanOauthAuthorizationCodeController.php
src/Controller/AuthmanOauthAuthorizationCodeController.php
<?php
declare(strict_types = 1);
namespace Drupal\authman\Controller;
use Drupal\authman\Entity\AuthmanAuthInterface;
use Drupal\authman\Token\AuthmanAccessToken;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Routing\TrustedRedirectResponse;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Provides a controller for sending user offsite or receiving tokens.
*/
class AuthmanOauthAuthorizationCodeController extends ControllerBase {
/**
* The private tempstore factory.
*
* @var \Drupal\Core\TempStore\PrivateTempStoreFactory
*/
protected $privateStoreFactory;
/**
* The OAuth provider instance factory.
*
* @var \Drupal\authman\AuthmanInstance\AuthmanOauthFactoryInterface
*/
protected $authmanOauthFactory;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
$instance = parent::create($container);
$instance->entityTypeManager = $container->get('entity_type.manager');
$instance->privateStoreFactory = $container->get('tempstore.private');
$instance->authmanOauthFactory = $container->get('authman.oauth');
$instance->messenger = $container->get('messenger');
$instance->currentUser = $container->get('current_user');
$instance->stringTranslation = $container->get('string_translation');
return $instance;
}
/**
* Starts the authorization code process.
*
* Starts the process to request an authorization code from the authorization
* server by sending the resource owner offsite.
*
* We request the appropriate permissions (scope) and hold onto a checksum
* value (state).
*
* Users are redirected back to the website thanks to the redirectUri
* setting in createProvider.
*
* @return \Symfony\Component\HttpFoundation\Response
* Redirect response offsite.
*/
public function start(AuthmanAuthInterface $authman_auth): Response {
// @todo #94/#95
$authmanInstance = $this->authmanOauthFactory->get($authman_auth->id());
// This needs to be called before getState().
$url = $authmanInstance->authorizationCodeUrl();
// Keep the state. We need to verify this on user return.
if (!$authman_auth->uuid()) {
throw new \LogicException('Instance does not have a UUID');
}
$store = $this->privateStoreFactory->get('authman.oauth.' . $authman_auth->uuid());
$store->set('state', $authmanInstance->getProvider()->getState());
// Never cache the response for this page.
$cacheable = (new CacheableMetadata())->setCacheMaxAge(0);
return (new TrustedRedirectResponse($url->toString()))->addCacheableDependency($cacheable);
}
/**
* For the authorization code process.
*
* Represents the redirect URI for a client.
*
* The authorization server sends the resource owner back to the site with
* an authorization code.
*
* Receives a redirect back from offsite.
*
* Will have query parameters for generating a token.
*
* Some parts copied/inspired by docs at
* https://github.com/thephpleague/oauth2-google.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* Redirects users to info page, displaying success or error message.
* @param \Drupal\authman\Entity\AuthmanAuthInterface $authman_auth
* The Authman instance.
*
* @return \Symfony\Component\HttpFoundation\Response
* A redirect response.
*/
public function receive(Request $request, AuthmanAuthInterface $authman_auth): Response {
$authmanConfigId = $authman_auth->id();
// @todo #94/#95
$tokenInfoUrl = Url::fromRoute('entity.authman_auth.information', ['authman_auth' => $authmanConfigId]);
// Code generated by authorization server and passed back to us by the
// user agent.
$authorizationCode = $request->query->get('code');
$authmanInstance = $this->authmanOauthFactory->get($authmanConfigId);
try {
// Get the token via the provided authorization code.
$token = $authmanInstance->getAccessToken(AuthmanAuthInterface::GRANT_AUTHORIZATION_CODE, ['code' => $authorizationCode]);
}
catch (\Exception $e) {
// Greedy catch.
$this->messenger->addError($this->t('Error getting access token: @message', ['@message' => $e->getMessage()]));
return new RedirectResponse($tokenInfoUrl->toString());
}
$accessKeyId = $authman_auth->getAccessTokenKeyId();
/** @var \Drupal\key\KeyInterface $access_key */
if (!$this->keyStorage()->load($accessKeyId)) {
$this->messenger->addError($this->t('The key %key does not exist.', ['%key' => $accessKeyId]));
throw new \LogicException(sprintf('The key %s does not exist', $accessKeyId));
}
$this->messenger->addMessage($this->t('Token received successfully.'));
$token = new AuthmanAccessToken($accessKeyId, $token);
$token->setKeyStorage($this->entityTypeManager->getStorage('key'));
$accessKey = $token->saveToKey();
$editFormUrl = $accessKey->toUrl('edit-form');
$message = $editFormUrl->access($this->currentUser)
? $this->t('<a href=":key_edit_url">Access token</a> key was updated.', [':key_edit_url' => $editFormUrl->toString()])
: $this->t('Access token key was updated.');
$this->messenger->addMessage($message);
return new RedirectResponse($tokenInfoUrl->toString());
}
/**
* Get key config storage.
*
* @return \Drupal\Core\Config\Entity\ConfigEntityStorageInterface
* The key config storage.
*/
protected function keyStorage(): EntityStorageInterface {
return $this->entityTypeManager->getStorage('key');
}
}
