acquia_dam-1.0.0-rc1/src/AcquiadamAuthService.php
src/AcquiadamAuthService.php
<?php namespace Drupal\acquia_dam; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Site\Settings; use Drupal\Core\State\State; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\user\UserData; use GuzzleHttp\ClientInterface; use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\ServerException; use Psr\Log\LoggerInterface; /** * The service to authenticate on Acquia DAM. */ final class AcquiadamAuthService { use StringTranslationTrait; /** * The client_id used to identify Drupal module. * * @var string */ const CLIENT_ID = '3b41085e6ff4d9f87307f4418bfce7ef6ed12860.app.widen.com'; /** * The logger service. * * @var \Psr\Log\LoggerInterface */ protected $logger; /** * The client_secret used to identify Drupal module. * * @var string */ const CLIENT_SECRET = 'ec216e0b87f9fa5b5828da524e360196ac74ed69'; /** * The field plugin manager service. * * @var \Drupal\Core\Extension\ModuleHandler */ protected $moduleHandler; /** * User data. * * @var \Drupal\user\UserData */ protected $userData; /** * Acquia Dam configuration. * * @var \Drupal\Core\Config\ImmutableConfig */ protected $config; /** * Guzzle client. * * @var \GuzzleHttp\Client */ protected $httpClient; /** * State storage. * * @var \Drupal\Core\State\State */ protected $state; /** * Media Acquia Dam configuration. * * @var \Drupal\Core\Config\ImmutableConfig */ protected $mediaAcquiadamConfig; /** * AuthenticationPage construct. * * @param \Drupal\user\UserData $userData * User data. * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory * Config factory. * @param \GuzzleHttp\ClientInterface $httpClient * Http client. * @param \Drupal\Core\State\State $state * State storage. * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler * Module handler. * @param \Psr\Log\LoggerInterface $logger * The logger. */ public function __construct(UserData $userData, ConfigFactoryInterface $configFactory, ClientInterface $httpClient, State $state, ModuleHandlerInterface $moduleHandler, LoggerInterface $logger) { $this->userData = $userData; $this->config = $configFactory->get('acquia_dam.settings'); $this->mediaAcquiadamConfig = $configFactory->getEditable('media_acquiadam.settings'); $this->httpClient = $httpClient; $this->state = $state; $this->moduleHandler = $moduleHandler; $this->logger = $logger; } /** * Checks if the DAM domain has been configured. * * @return bool * Returns TRUE if the domain has been set. */ public function isConfigured(): bool { return !empty($this->config->get('domain')); } /** * Returns TRUE if configuration is set to use refresh token. */ public function isUsingRefreshToken(): bool { return $this->config->get('auth_type') === 'refresh_token'; } /** * Provides the authorization link with Acquia DAM. * * @param string $return_link * The url where it should redirect after the authentication. * * @return string * The absolute URL used for authorization. */ public function generateAuthUrl(string $return_link): string { $acquia_dam_domain = $this->config->get('domain'); $client_id = $this->isUsingRefreshToken() ? $this->config->get('client_id') : Settings::get('acquia_dam_client_id', self::CLIENT_ID); return $acquia_dam_domain ? $this->generateEndpointUrl('/allowaccess', "?client_id=$client_id&redirect_uri=$return_link") : ''; } /** * Purge Acquia DAM user authorization connection. * * @param string $access_token * Acquia DAM user token. * @param int $user_id * User id. */ public function cancelUserRegistration(string $access_token, int $user_id): void { $this->sendLogoutRequest($access_token); $this->userData->delete('acquia_dam', $user_id); } /** * Purge Acquia DAM site authorization connection. * * @param string $access_token * Acquia DAM user token. */ public function cancelSiteRegistration(string $access_token): void { $this->sendLogoutRequest($access_token); $this->state->delete('acquia_dam_token'); $this->state->delete('acquia_dam_refresh_token'); } /** * Sends a logout request. * * @param string $access_token * Access token. * * @return bool * Returns TRUE on success. * * @throws \Exception */ protected function sendLogoutRequest(string $access_token): bool { if (empty($access_token)) { throw new \RuntimeException('No access token was provided.'); } // Initiate and process the response of the HTTP request. try { if ($this->isUsingRefreshToken()) { $method = 'DELETE'; $uri = 'https://api.widencollective.com/v2/oauth/access-token'; } else { $method = 'POST'; $uri = $this->generateEndpointUrl('/api/rest/oauth/logout'); } $this->httpClient->request($method, $uri, [ 'headers' => [ 'Authorization' => 'Bearer ' . $access_token, ], ]); } catch (ClientException $e) { } catch (ServerException $e) { $this->logger->error($e->getMessage()); throw new \RuntimeException('Something wrong at the server end, try again after sometime. If the issue persist contact the site admin.'); } return TRUE; } /** * Authenticate with Acquia DAM and saves access and refresh tokens. * * @param string $auth_code * Authorization code. * @param int|null $user_id * User id. Only provide for user authentication. */ public function authenticateDam(string $auth_code, ?int $user_id = NULL): void { $response = $this->sendAuthRequest($auth_code); $auth_info = [ 'acquia_dam_token' => $response->access_token, 'acquia_dam_refresh_token' => $response->refresh_token ?? NULL, 'acquia_dam_username' => $response->username ?? NULL, ]; if ($user_id) { $this->setUserData($user_id, $auth_info); return; } $this->state->setMultiple($auth_info); if ($this->moduleHandler->moduleExists('media_acquiadam')) { $this->mediaAcquiadamConfig->set('token', $response->access_token)->save(); } } /** * Sends authentication request. * * @param string $auth_code * Authorization code. * * @return object * Response. */ public function sendAuthRequest(string $auth_code): object { $options = [ 'headers' => [ 'Accept' => 'application/json', 'Content-Type' => 'application/json', ], ]; if ($this->isUsingRefreshToken()) { $uri = 'https://api.widencollective.com/v2/oauth/access-token'; $options['json'] = [ "grant_type" => "authorization_code", "authorization_code" => $auth_code, "client_id" => $this->config->get('client_id'), "client_secret" => $this->config->get('client_secret'), ]; } else { $uri = $this->generateEndpointUrl('/api/rest/oauth/token'); $options['json'] = [ 'authorization_code' => $auth_code, 'grant_type' => 'authorization_code', ]; $options['auth'] = [ Settings::get('acquia_dam_client_id', self::CLIENT_ID), Settings::get('acquia_dam_client_secret', self::CLIENT_SECRET), ]; } try { $response = $this->httpClient->post($uri, $options); } catch (ClientException $e) { $this->logger->error($e->getMessage()); throw new \RuntimeException('Something went wrong with the request, and your account can’t be connected. Contact the site administrator.'); } catch (ServerException $e) { $this->logger->error($e->getMessage()); throw new \RuntimeException('Something went wrong contacting Acquia DAM, and your account can’t be connected. Contact the site administrator.'); } $body = json_decode($response->getBody()); if (!isset($body->access_token)) { throw new \RuntimeException('Authentication response does not contain necessary information.'); } return $body; } /** * Refreshes access token. * * @param string $refresh_token * Refresh token. * @param int|null $user_id * Which user is refreshing token. Only use if user refresh token provided. */ public function refreshAccessToken(string $refresh_token, ?int $user_id = NULL) { try { $response = $this->httpClient->post('https://api.widencollective.com/v2/oauth/access-token', [ 'json' => [ "grant_type" => "refresh_token", "refresh_token" => $refresh_token, "client_id" => $this->config->get('client_id'), "client_secret" => $this->config->get('client_secret'), ], 'headers' => [ 'Accept' => 'application/json', 'Content-Type' => 'application/json', ], ]); } catch (ClientException $e) { $this->logger->error($e->getMessage()); throw new \RuntimeException('Something went wrong with the request, and your account can’t be connected. Contact the site administrator.'); } catch (ServerException $e) { $this->logger->error($e->getMessage()); throw new \RuntimeException('Something went wrong contacting Acquia DAM, and your account can’t be connected. Contact the site administrator.'); } catch (\Exception $e) { $this->logger->error($e->getMessage()); } $body = json_decode($response->getBody()); $info = [ 'acquia_dam_token' => $body->access_token, 'acquia_dam_refresh_token' => $body->refresh_token, ]; if ($user_id === NULL) { $this->state->setMultiple($info); if ($this->moduleHandler->moduleExists('media_acquiadam')) { $this->mediaAcquiadamConfig->set('token', $body->access_token)->save(); } } else { $this->setUserData($user_id, $info); } } /** * Checks if the media library should show the authorization prompt. * * This authorization prompt is only shown to the user once and if they are * not authenticated to the DAM with their user account. * * @param int $user_id * The user ID. * * @return bool * Returns TRUE if the authorization prompt should be shown. */ public function shouldShowAuthorizationPrompt(int $user_id): bool { if (!$this->isConfigured()) { return FALSE; } $seen_prompt = $this->userData->get('acquia_dam', $user_id, 'seen_prompt') ?: FALSE; return !$seen_prompt && !$this->isAuthenticated($user_id); } /** * Marks that the user has seen the authorization prompt. * * @param int $user_id * The user ID. */ public function markAuthorizationPromptShown(int $user_id): void { $this->userData->set('acquia_dam', $user_id, 'seen_prompt', TRUE); } /** * Checks if user has stored authentication information. * * @param int $user_id * User id. * * @return bool * TRUE if necessary authentication information are set in user data. */ public function isAuthenticated(int $user_id): bool { return !empty($this->userData->get('acquia_dam', $user_id, 'account')); } /** * Checks if site has stored authentication information. * * @return bool * TRUE if necessary authentication information are set in state storage. */ public function isSiteAuthenticated(): bool { if (!$this->isConfigured()) { return FALSE; } return $this->isUsingRefreshToken() ? (bool) $this->getRefreshToken() : (bool) $this->getSiteToken(); } /** * Returns site token from state storage. */ public function getSiteToken(): ?string { // We do not care how we get it, it will always be on the same key. return $this->state->get('acquia_dam_token'); } /** * Get token from user data. * * @return string * DAM token. */ public function getUserAccessToken(int $user_id): ?string { $acquia_dam_account = $this->getUserData($user_id); return $acquia_dam_account['acquia_dam_token'] ?? NULL; } /** * Returns user refresh token. */ public function getUserRefreshToken(int $user_id): string { $oauth_info = $this->getUserData($user_id); return $oauth_info['acquia_dam_refresh_token'] ?? ''; } /** * Returns site refresh token. */ public function getRefreshToken(): string { return $this->state->get('acquia_dam_refresh_token') ?? ''; } /** * Sets data in user_data. * * @param int $user_id * User Id. * @param array $data * Data array to store on user object. */ public function setUserData(int $user_id, array $data): void { if ($this->moduleHandler->moduleExists('media_acquiadam')) { $account = [ 'acquiadam_username' => $data['acquia_dam_username'], 'acquiadam_token' => $data['acquia_dam_token'], ]; // Store acquiadam account details. $this ->userData ->set('media_acquiadam', $user_id, 'account', $account); } $this->userData->set('acquia_dam', $user_id, 'account', $data); } /** * Gets data from user_data. * * @param int $user_id * The user ID. * * @return array * The user data. */ public function getUserData(int $user_id): array { return $this->userData->get('acquia_dam', $user_id, 'account') ?: []; } /** * Generates DAM endpoint to ping. * * @param string $path * Path string to concatenate to domain. * @param string $query_string * Query string to concatenate to path. Optional parameter. * Example: "?client_id=13". * * @return string * Endpoint to send request to. */ private function generateEndpointUrl(string $path, string $query_string = ''): string { return 'https://' . $this->config->get('domain') . $path . $query_string; } /** * Disconnect site before new domain register, user tokens as well. */ public function disconnectSiteAndUsers(): void { $this->cancelSiteRegistration($this->getSiteToken()); foreach ($this->userData->get('acquia_dam') as $uid => $account_info) { // Only delete from local storage. $this->userData->delete('acquia_dam', (int) $uid); } } }