open_connect-8.x-1.x-dev/src/Plugin/OpenConnect/Provider/ProviderBase.php

src/Plugin/OpenConnect/Provider/ProviderBase.php
<?php

namespace Drupal\open_connect\Plugin\OpenConnect\Provider;

use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\PluginBase;
use Drupal\Core\Url;
use Drupal\open_connect\Exception\OpenConnectException;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Symfony\Component\DependencyInjection\ContainerInterface;

abstract class ProviderBase extends PluginBase implements ProviderInterface, ContainerFactoryPluginInterface {

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * The http client.
   *
   * @var \GuzzleHttp\Client
   */
  protected $httpClient;

  /**
   * The language manager.
   *
   * @var \Drupal\Core\Language\LanguageManagerInterface
   */
  protected $languageManager;

  /**
   * The logger.
   *
   * @var \Drupal\Core\Logger\LoggerChannelInterface
   */
  protected $logger;

  /**
   * Constructs a new ProviderBase object.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \GuzzleHttp\Client $http_client
   *   The http client object.
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
   *   The language manager.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
   *   The logger factory.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, Client $http_client, LanguageManagerInterface $language_manager, LoggerChannelFactoryInterface $logger_factory) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->entityTypeManager = $entity_type_manager;
    $this->httpClient = $http_client;
    $this->languageManager = $language_manager;
    $this->logger = $logger_factory->get('open_connect');
    $this->setConfiguration($configuration);
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('entity_type.manager'),
      $container->get('http_client'),
      $container->get('language_manager'),
      $container->get('logger.factory')
    );
  }

  public function calculateDependencies() {
    // TODO: dependencies
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return [
      'mode' => 'test',
      'client_id' => '',
      'client_secret' => '',
      'scope' => '',
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getConfiguration() {
    return $this->configuration;
  }

  /**
   * {@inheritdoc}
   */
  public function setConfiguration(array $configuration) {
    $this->configuration = NestedArray::mergeDeep($this->defaultConfiguration(), $configuration);
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
    $form['mode'] = [
      '#type' => 'radios',
      '#title' => $this->t('Mode'),
      '#options' => [
        'test' => t('Test'),
        'live' => t('Live'),
      ],
      '#default_value' => $this->configuration['mode'],
    ];
    $form['client_id'] = [
      '#title' => $this->t('Client ID'),
      '#type' => 'textfield',
      '#maxlength' => 255,
      '#default_value' => $this->configuration['client_id'],
    ];
    $form['client_secret'] = [
      '#title' => $this->t('Client Secret'),
      '#type' => 'textfield',
      '#maxlength' => 255,
      '#default_value' => $this->configuration['client_secret'],
    ];
    $form['scope'] = [
      '#title' => $this->t('Scope'),
      '#type' => 'textfield',
      '#maxlength' => 255,
      '#default_value' => $this->configuration['scope'],
    ];
    $form['redirect_uri'] = [
      '#title' => $this->t('Redirect URI'),
      '#type' => 'item',
      '#input' => FALSE,
      '#markup' => $this->getRedirectUri(),
    ];
    if ($homepage = $this->pluginDefinition['homepage']) {
      $params = [
        '@homepage' => $homepage,
        '@description' => $this->pluginDefinition['description'],
      ];
      $form['description'] = [
        '#markup' => '<div class="description">' . $this->t('Set up your app on <a href="@homepage" target="_blank">@description</a>.', $params) . '</div>',
      ];
    }

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
    $values = $form_state->getValues();
    if (empty($values['client_id'])) {
      $form_state->setError($form['client_id'], 'Client ID cannot be empty.');
    }
    if (empty($values['client_secret'])) {
      $form_state->setError($form['client_secret'], 'Client secret cannot be empty.');
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
    $values = $form_state->getValues();
    $this->configuration['mode'] = $values['mode'];
    $this->configuration['client_id'] = $values['client_id'];
    $this->configuration['client_secret'] = $values['client_secret'];
    $this->configuration['scope'] = $values['scope'];
  }

  /**
   * {@inheritdoc}
   */
  public function getAuthorizeUrl($state) {
    $this->assertConfiguration();

    $options = [
      'query' => [
        'response_type' => 'code',
        $this->getKey('client_id') => $this->configuration['client_id'],
        'redirect_uri' => $this->getRedirectUri(),
        'state' => $state,
        'scope' => $this->configuration['scope'],
      ],
    ];
    $options['query'] = array_filter($options['query']);
    $this->processRedirectUrlOptions($options);
    return Url::fromUri($this->getUrl('authorization'), $options);
  }

  /**
   * Processes the redirect url options.
   *
   * @param array $options
   *   The url options.
   */
  protected function processRedirectUrlOptions(array &$options) {
  }

  /**
   * {@inheritdoc}
   */
  public function authenticate($code) {
    $this->assertConfiguration();
    $this->logResponse('Authorization code', ['code' => $code]);

    // Fetch authorization token.
    $token_response = $this->fetchToken($code);

    // Get openid from the token response or fetch it if not found.
    // Providers like WeChat and Weibo return an openid in the token response.
    $openid_key = $this->getKey('openid');
    $openid = isset($token_response[$openid_key]) ? $token_response[$openid_key] : $this->fetchOpenid($token_response['access_token']);
    // WeChat providers may return an unionid.
    $unionid = isset($token_response['unionid']) ? $token_response['unionid'] : '';

    /** @var \Drupal\open_connect\OpenConnectStorageInterface $open_connect_storage */
    $open_connect_storage = $this->entityTypeManager->getStorage('open_connect');
    $open_connect = $open_connect_storage->loadByOpenid($this->pluginId, $openid);
    if (!$open_connect) {
      // Try to load by unionid.
      if($unionid && $open_connect = $open_connect_storage->loadByUnionid($unionid)) {
        // Get the existing user.
        $user = $open_connect->getAccount();
      }
      else {
        // Create a new user to authenticate.
        $user = $this->createUser();
      }

      // Create an open connect entity with the new provider, openid and
      // possible unionid for the existing user or a new user.
      $open_connect = $open_connect_storage->create([
        'provider' => $this->pluginId,
        'openid' => $openid,
        'unionid' => $unionid,
        'uid' => $user->id(),
      ]);
      $open_connect->save();
    }
    elseif ($unionid && !$open_connect->getUnionid()) {
      // Save the unionid.
      $open_connect->setUnionid($unionid)->save();
    }

    return $open_connect->getAccount();
  }

  /**
   * Configuration assertion.
   */
  private function assertConfiguration() {
    if (empty($this->configuration['client_id'])) {
      throw new \InvalidArgumentException('Client ID is not set.');
    }
    if (empty($this->configuration['client_secret'])) {
      throw new \InvalidArgumentException('Client secret is not set.');
    }
  }

  /**
   * Fetches an authorization token by the given authorization code.
   *
   * @param string $code
   *   The authorization code.
   *
   * @return array
   *   An array of response data values.
   *
   * @throws \Drupal\open_connect\Exception\OpenConnectException
   *   Thrown when the http request fails or the response is failed.
   */
  protected function fetchToken($code) {
    try {
      // Fetch authorization token.
      $token_response = $this->doFetchToken($this->getUrl('access_token'), [
        'grant_type' => 'authorization_code',
        'code' => $code,
        'redirect_uri' => $this->getRedirectUri(),
        $this->getKey('client_id') => $this->configuration['client_id'],
        $this->getKey('client_secret') => $this->configuration['client_secret'],
      ]);
      $this->logResponse('Fetch token', $token_response);
    }
    catch (RequestException $e) {
      throw new OpenConnectException(sprintf('%s Could not fetch authorization token: %s', $this->pluginId, $e->getMessage()), $e->getCode(), $e);
    }

    if (!$this->isResponseSuccessful($token_response)) {
      throw new OpenConnectException($this->getResponseError($token_response));
    }

    return $token_response;
  }

  /**
   * Preforms a provider-specific fetching token.
   *
   * @param string $url
   *   The api url.
   * @param array $params
   *   The request parameters.
   *
   * @return array
   *   The token array
   *
   * @throws \GuzzleHttp\Exception\RequestException
   *   Thrown when the http request fails.
   */
  protected function doFetchToken($url, array $params) {
    $response = $this->httpClient->post($url, [
      'form_params' => $params,
    ]);

    // getBody() returns an instance of Psr\Http\Message\StreamInterface.
    // @see http://docs.guzzlephp.org/en/latest/psr7.html#body
    return \GuzzleHttp\json_decode($response->getBody(), TRUE);
  }

  /**
   * Fetches openid.
   *
   * Note: currently only QQ needs to fetch the openid.
   *
   * @param string $access_token
   *   The access token.
   *
   * @return string
   *   The openid.
   *
   * @throws \Drupal\open_connect\Exception\OpenConnectException
   *   Thrown when the http request fails or the response is failed.
   */
  protected function fetchOpenid($access_token) {
    try {
      $openid_response = $this->doFetchOpenid($this->getUrl('openid'), [
        'access_token' => $access_token,
      ]);
      $this->logResponse('Fetch openid', $openid_response);
    }
    catch (RequestException $e) {
      throw new OpenConnectException(sprintf('%s Could not fetch openid: %s', $this->pluginId, $e->getMessage()), $e->getCode(), $e);
    }

    // Throws when the transaction fails for any reason, see SupportsRefundsInterface.
    if (!$this->isResponseSuccessful($openid_response)) {
      throw new OpenConnectException($this->getResponseError($openid_response));
    }

    $openid_key = $this->getKey('openid');
    return $openid_response[$openid_key];
  }

  /**
   * Preforms a provider-specific fetching openid.
   *
   * @param string $url
   *   The api url.
   * @param array $params
   *   The request parameters.
   *
   * @return array
   *   An array of response data values.
   *
   * @throws \GuzzleHttp\Exception\RequestException
   *   Thrown when the http request fails.
   */
  protected function doFetchOpenid($url, array $params) {
    $response = $this->httpClient->get($url, [
      'query' => $params,
    ]);
    return \GuzzleHttp\json_decode($response->getBody(), TRUE);
  }

  /**
   * Fetches the user info with the given access token and openid.
   *
   * @param string $access_token
   *   The access token.
   * @param string $openid
   *   The openid.
   *
   * @return array
   *   The user claims.
   *
   * @throws \Drupal\open_connect\Exception\OpenConnectException
   *   Thrown when the http request fails or the response is failed.
   */
  public function fetchUserInfo($access_token, $openid) {
    $openid_key = $this->getKey('openid');
    $params = [
      'access_token' => $access_token,
      $openid_key => $openid,
    ];
    return $this->doFetchUserInfo($this->getUrl('user_info'), $params);
  }

  /**
   * Preforms a provider-specific fetching token.
   *
   * @param string $url
   *   The api url.
   * @param array $params
   *   The request parameters.
   *
   * @return array
   *   The token array
   *
   * @throws \GuzzleHttp\Exception\RequestException
   *   Thrown when the http request fails.
   */
  protected function doFetchUserInfo($url, array $params) {
    $response = $this->httpClient->get($url, [
      'query' => $params,
    ]);
    return \GuzzleHttp\json_decode($response->getBody(), TRUE);
  }

  /**
   * Creates a new user.
   *
   * @return \Drupal\user\UserInterface
   *   The newly created user object.
   */
  private function createUser() {
    $user_storage = $this->entityTypeManager->getStorage('user');

    // Get a unique username.
    $name = 'u' . date('YmdHis');
    $i = 0;
    while ($user_storage->loadByProperties(['name' => $name])) {
      $name .= '_' . ++$i;
    }

    /** @var \Drupal\user\UserInterface $user */
    $user = $user_storage->create([
      'name' => $name,
      'pass' => user_password(),
      'mail' => '',
    ]);
    // Always active the new user.
    $user->activate();
    $user->save();

    return $user;
  }

  /**
   * Gets a url for the the given api.
   *
   * @param string $api
   *   The api.
   *
   * @return string|bool
   *   The url, or FALSE if not found.
   */
  protected function getUrl($api) {
    $urls = $this->pluginDefinition['urls'];
    return isset($urls[$api]) ? $urls[$api]: FALSE;
  }

  /**
   * Gets a specific key.
   *
   * @param string $key
   *   The name of the key to return.
   *
   * @return string|bool
   *   The key, or FALSE if it does not exist.
   */
  protected function getKey($key) {
    $keys = $this->pluginDefinition['keys'];
    return isset($keys[$key]) ? $keys[$key] : FALSE;
  }

  /**
   * Gets the redirect uri.
   *
   * @return string
   */
  private function getRedirectUri() {
    // Redirect uri
    return Url::fromRoute('open_connect.authenticate', [
      'open_connect_provider' => $this->pluginId,
    ], [
      // 'query' => $redirect_uri_query,
      'absolute' => TRUE,
      'language' => $this->languageManager->getLanguage(LanguageInterface::LANGCODE_NOT_APPLICABLE),
    ])->toString();
  }

  /**
   * Logs response.
   *
   * @param string $operation
   *   The operation
   * @param array $response
   *   An array of response data.
   */
  private function logResponse($operation, array $response) {
    if (empty($response)) {
      return;
    }
    // Log response if the data represents an failure, or the plugin is not in
    // live mode.
    $successful = $this->isResponseSuccessful($response);
    if (!$successful || $this->configuration['mode'] !== 'live') {
      $level = $successful ? 'debug' : 'warning';
      $this->logger->$level('@provider @operation: <pre>@response</pre>', [
        '@provider' => $this->pluginId,
        '@operation' => $operation,
        '@response' => print_r($response, TRUE),
      ]);
    }
  }

  /**
   * Whether the response represents a successful data.
   *
   * @param array $response
   *   The response data.
   *
   * @return bool
   *   True if the existing payment is available for reuse, FALSE otherwise.
   */
  abstract protected function isResponseSuccessful(array $response);

  /**
   * Gets the error message from the given response data.
   *
   * @param array $response
   *   The response data
   *
   * @return string
   *   The error message.
   */
  abstract protected function getResponseError(array $response);

}

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

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