acquia_connector-8.x-1.22/src/AuthService.php

src/AuthService.php
<?php

declare(strict_types=1);

namespace Drupal\acquia_connector;

use Drupal\Component\Datetime\TimeInterface;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Access\CsrfTokenGenerator;
use Drupal\Core\Http\ClientFactory;
use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface;
use Drupal\Core\Site\Settings as CoreSettings;
use Drupal\Core\State\StateInterface;
use Drupal\Core\Url;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Psr7\Uri;
use Symfony\Component\HttpFoundation\Session\SessionInterface;

/**
 * Auth service for OAuth authentication with Acquia Cloud.
 */
final class AuthService {

  private const CLIENT_ID = '38357830-bacd-4b4d-a356-f508c6ddecf8';
  private const CSRF_TOKEN_KEY = 'acquia_connector_oauth_state';
  private const PKCE_KEY = 'acquia_connector_pkce_code';

  /**
   * The CSRF token generator.
   *
   * @var \Drupal\Core\Access\CsrfTokenGenerator
   */
  private CsrfTokenGenerator $csrfToken;

  /**
   * The HTTP client factory.
   *
   * @var \Drupal\Core\Http\ClientFactory
   */
  private ClientFactory $clientFactory;

  /**
   * The session.
   *
   * @var \Symfony\Component\HttpFoundation\Session\SessionInterface
   */
  private SessionInterface $session;

  /**
   * The factory for expirable key value stores.
   *
   * @var \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface
   */
  private KeyValueExpirableFactoryInterface $keyValueExpirableFactory;

  /**
   * The state service.
   *
   * @var \Drupal\Core\State\StateInterface
   */
  private StateInterface $state;

  /**
   * The time service.
   *
   * @var \Drupal\Component\Datetime\TimeInterface
   */
  private TimeInterface $time;

  /**
   * Constructs a new AuthService object.
   *
   * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
   *   The CSRF token generator.
   * @param \Drupal\Core\Http\ClientFactory $client_factory
   *   The HTTP client factory.
   * @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session
   *   The session.
   * @param \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface $key_value_expirable_factory
   *   The factory for expirable key value stores.
   * @param \Drupal\Core\State\StateInterface $state
   *   The state service.
   * @param \Drupal\Component\Datetime\TimeInterface $time
   *   The time service.
   */
  public function __construct(CsrfTokenGenerator $csrf_token, ClientFactory $client_factory, SessionInterface $session, KeyValueExpirableFactoryInterface $key_value_expirable_factory, StateInterface $state, TimeInterface $time) {
    $this->csrfToken = $csrf_token;
    $this->clientFactory = $client_factory;
    $this->session = $session;
    $this->keyValueExpirableFactory = $key_value_expirable_factory;
    $this->state = $state;
    $this->time = $time;
  }

  /**
   * Get the authorization URL.
   *
   * @return \Drupal\Core\Url
   *   The URL.
   */
  public function getAuthUrl(): Url {
    $params = [
      'response_type' => 'code',
      'client_id' => self::CLIENT_ID,
      'redirect_uri' => Url::fromRoute('acquia_connector.auth.return')->setAbsolute()->toString(),
      'state' => $this->getStateToken(),
      'code_challenge' => Crypt::hashBase64($this->getPkceCode()),
      'code_challenge_method' => 'S256',
    ];
    $uri = (new Uri())
      ->withScheme('https')
      ->withHost(self::getIdpHost())
      ->withPath('/api/auth/oauth/authorize');
    return Url::fromUri(
      (string) Uri::withQueryValues($uri, $params)
    );
  }

  /**
   * Finalizes the OAuth process.
   *
   * @param string $code
   *   The authorization code.
   * @param string $state
   *   The state token.
   */
  public function finalize(string $code, string $state): void {
    if ($state !== $this->getStateToken()) {
      throw new \RuntimeException('Could not verify state');
    }
    $client = $this->clientFactory->fromOptions([
      'base_uri' => (new Uri())
        ->withScheme('https')
        ->withHost(self::getIdpHost()),
    ]);
    $response = $client->post('/api/auth/oauth/token', [
      'json' => [
        'grant_type' => 'authorization_code',
        'code' => $code,
        'client_id' => self::CLIENT_ID,
        'redirect_uri' => Url::fromRoute('acquia_connector.auth.return')->setAbsolute()->toString(),
        'code_verifier' => $this->getPkceCode(),
      ],
    ]);
    $this->keyValueExpirableFactory->get('acquia_connector')->setWithExpire(
      'oauth',
      Json::decode((string) $response->getBody()),
      5400
    );
    $this->session->remove(self::PKCE_KEY);
  }

  /**
   * Refreshes the access token.
   */
  public function refreshAccessToken(): void {
    $access_data = $this->getAccessToken();
    $client = $this->clientFactory->fromOptions([
      'base_uri' => (new Uri())
        ->withScheme('https')
        ->withHost(self::getIdpHost()),
    ]);
    $response = $client->post('/api/auth/oauth/token', [
      'json' => [
        'grant_type' => 'refresh_token',
        'refresh_token' => $access_data['refresh_token'] ?? '',
        'client_id' => self::CLIENT_ID,
      ],
    ]);
    $access_data = Json::decode((string) $response->getBody());
    $this->keyValueExpirableFactory->get('acquia_connector')->setWithExpire(
      'oauth',
      $access_data,
      5400
    );
  }

  /**
   * Gets the access token data.
   *
   * @phpstan-return array{access_token: string, refresh_token: string, expires: int}
   *
   * @return array|null
   *   The access token data, or NULL if not set.
   */
  public function getAccessToken(): ?array {
    return $this->keyValueExpirableFactory->get('acquia_connector')->get('oauth');
  }

  /**
   * Cron refresh of the access token.
   */
  public function cronRefresh(): void {
    $last_refresh_timestamp = $this->state->get('acquia_connector.oauth_refresh.timestamp', 0);
    if ($this->time->getCurrentTime() - $last_refresh_timestamp > 1800) {
      try {
        $this->refreshAccessToken();
      }
      catch (RequestException $exception) {
      } finally {
        $this->state->set('acquia_connector.oauth_refresh.timestamp', $this->time->getRequestTime());
      }
    }
  }

  /**
   * Gets the state token value used in OAuth authorization.
   *
   * @return string
   *   The state token.
   */
  private function getStateToken(): string {
    return Crypt::hashBase64($this->csrfToken->get(self::CSRF_TOKEN_KEY));
  }

  /**
   * Get the PKCE code used in the OAuth authorization.
   *
   * @return string
   *   The PKCE code.
   */
  private function getPkceCode(): string {
    if (!$this->session->has(self::PKCE_KEY)) {
      $this->session->set(self::PKCE_KEY, Crypt::randomBytesBase64(64));
    }
    return $this->session->get(self::PKCE_KEY);
  }

  /**
   * Get the identity provider host.
   *
   * @return string
   *   The host.
   */
  private static function getIdpHost(): string {
    return CoreSettings::get('acquia_connector.idp_host', 'accounts.acquia.com');
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc