docusign_signature-1.0.x-dev/src/DocuSignAuth/Jwt.php
src/DocuSignAuth/Jwt.php
<?php
declare(strict_types=1);
namespace Drupal\docusign_signature\DocuSignAuth;
use DocuSign\eSign\Client\ApiClient;
use DocuSign\eSign\Client\Auth\OAuthToken;
use DocuSign\eSign\Configuration;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Drupal\Core\Url;
use Drupal\docusign_signature\Auth\DocuSign;
use Drupal\docusign_signature\AuthBase;
use Firebase\JWT\BeforeValidException;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* DocuSign authentication with JSON Web Token (JWT).
*
* @package Drupal\docusign_signature\DocuSignAuth
*
* @DocuSignAuth(
* id = "jwt",
* label = @Translation("JSON Web Token (JWT)")
* )
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Jwt extends AuthBase {
/**
* DocuSign OAuth token object.
*
* @var \DocuSign\eSign\Client\Auth\OAuthToken
*/
private static OAuthToken $accessToken;
/**
* The DocuSign API Client object.
*
* @var \DocuSign\eSign\Client\ApiClient
*/
private ApiClient $apiClient;
/**
* {@inheritdoc}
*/
public function __construct(
ConfigFactoryInterface $config_factory,
LoggerInterface $logger,
PrivateTempStoreFactory $temp_store_factory
) {
parent::__construct($config_factory, $logger, $temp_store_factory);
$config = new Configuration();
$this->apiClient = new ApiClient($config);
}
/**
* {@inheritdoc}
*
* @throws \DocuSign\eSign\Client\ApiException
* @throws \Drupal\Core\TempStore\TempStoreException
*/
public function authCallback(string $redirectUrl = NULL): RedirectResponse {
// Check given state against previously stored one to mitigate CSRF attack.
if (empty(self::$accessToken)) {
throw new BeforeValidException('Invalid JWT state.');
}
try {
// We have an access token, which we may use in authenticated
// requests against the service provider's API.
$this->tempStore->get('access_token', self::$accessToken->getAccessToken());
$this->tempStore->get('expiration',
\Drupal::time()->getCurrentTime() + self::$accessToken->getExpiresIn() * 60
);
/** @var \DocuSign\eSign\Client\Auth\UserInfo $user */
$user = $this->apiClient->getUserInfo(self::$accessToken->getAccessToken())[0];
/** @var \DocuSign\eSign\Client\Auth\Account $accountInfo */
$accountInfo = $user->getAccounts();
$this->tempStore->set('user', $user);
$this->tempStore->set('account_id', $accountInfo->getAccountId());
$this->tempStore->set('base_path', $accountInfo->getBaseUri() . self::BASE_URI_SUFFIX);
}
catch (IdentityProviderException $e) {
// Failed to get the access token or user details.
\Drupal::messenger()->addError($e->getMessage());
}
return parent::authCallback($redirectUrl);
}
/**
* Get JWT auth by RSA key.
*
* @return void|\DocuSign\eSign\Client\Auth\OAuthToken
* A OAuth token object.
*
* @throws \Drupal\Core\TempStore\TempStoreException
*/
private function configureJwtAuthorizationFlowByKey(): ?OAuthToken {
$this->apiClient->getOAuth()->setOAuthBasePath(self::AUTHORIZATION_URL);
$privateKey = file_get_contents($this->config->get('private_key_file'), TRUE);
$scope = (new DocuSign())->getDefaultScopes()[0];
// Make sure to add the "impersonation" scope when using JWT authorization.
$jwt_scope = $scope . ' impersonation';
$this->tempStore->set('jwt_scope', $jwt_scope);
try {
$response = $this->apiClient->requestJWTUserToken(
$this->config->get('client_id'),
$this->config->get('impersonated_user_id'),
$privateKey,
$jwt_scope,
);
return reset($response);
}
catch (\Throwable $th) {
// We found consent_required in the response body
// meaning first time consent is needed.
if (strpos($th->getMessage(), 'consent_required') !== FALSE) {
$this->tempStore->set('consent_set', TRUE);
}
}
return NULL;
}
/**
* {@inheritdoc}
*
* @throws \DocuSign\eSign\Client\ApiException
* @throws \Drupal\Core\TempStore\TempStoreException
*/
public function login(): RedirectResponse {
self::$accessToken = $this->configureJwtAuthorizationFlowByKey();
if (
!self::$accessToken &&
$this->tempStore->get('consent_set') === TRUE
) {
$authorizationURL = $this->getAutohorizationUrl() . 'oauth/http?' . http_build_query([
'scope' => $this->tempStore->get('jwt_scope'),
'redirect_uri' => Url::fromRoute('docusign_signature.callback.oauth', [], ['absolute' => TRUE])->toString(TRUE)->getGeneratedUrl(),
'client_id' => $this->config->get('client_id'),
'state' => $this->tempStore->get('oauth2state'),
'response_type' => 'code',
]);
return new RedirectResponse($authorizationURL);
}
return $this->authCallback(
Url::fromRoute('<current>', [], ['absolute' => TRUE])->toString(TRUE)->getGeneratedUrl()
);
}
}
