View source
<?php
namespace Drupal\openid_connect\Plugin;
use Drupal\Component\Plugin\PluginBase;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\GeneratedUrl;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\PageCache\ResponsePolicy\KillSwitch;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Routing\TrustedRedirectResponse;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\openid_connect\OpenIDConnectStateTokenInterface;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\RequestException;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Drupal\Component\Datetime\TimeInterface;
abstract class OpenIDConnectClientBase extends PluginBase implements OpenIDConnectClientInterface, ContainerFactoryPluginInterface {
use StringTranslationTrait;
protected $requestStack;
protected $httpClient;
protected $clientScopes = [
'openid',
'email',
];
protected $loggerFactory;
protected $dateTime;
protected $pageCacheKillSwitch;
protected $languageManager;
protected $stateToken;
public function __construct(array $configuration, $plugin_id, $plugin_definition, RequestStack $request_stack, ClientInterface $http_client, LoggerChannelFactoryInterface $logger_factory, TimeInterface $datetime_time = NULL, KillSwitch $page_cache_kill_switch = NULL, LanguageManagerInterface $language_manager = NULL, OpenIDConnectStateTokenInterface $state_token = NULL) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->requestStack = $request_stack;
$this->httpClient = $http_client;
$this->loggerFactory = $logger_factory;
$this->dateTime = $datetime_time ?: \Drupal::time();
$this->pageCacheKillSwitch = $page_cache_kill_switch ?: \Drupal::service('page_cache_kill_switch');
$this->languageManager = $language_manager ?: \Drupal::languageManager();
$this->stateToken = $state_token ?: \Drupal::service('openid_connect.state_token');
$this
->setConfiguration($configuration);
}
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container
->get('request_stack'), $container
->get('http_client'), $container
->get('logger.factory'), $container
->get('datetime.time'), $container
->get('page_cache_kill_switch'), $container
->get('language_manager'), $container
->get('openid_connect.state_token'));
}
public function getConfiguration() {
return $this->configuration;
}
public function setConfiguration(array $configuration) {
$this->configuration = NestedArray::mergeDeep($this
->defaultConfiguration(), $configuration);
}
public function defaultConfiguration() {
return [
'client_id' => '',
'client_secret' => '',
];
}
public function calculateDependencies() {
return [];
}
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['redirect_url'] = [
'#title' => $this
->t('Redirect URL'),
'#type' => 'item',
'#markup' => $this
->getRedirectUrl()
->toString(),
];
$form['client_id'] = [
'#title' => $this
->t('Client ID'),
'#type' => 'textfield',
'#default_value' => $this->configuration['client_id'],
];
$form['client_secret'] = [
'#title' => $this
->t('Client secret'),
'#type' => 'textarea',
'#default_value' => $this->configuration['client_secret'],
];
return $form;
}
public function getClientScopes() {
return $this->clientScopes;
}
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
$provider = [
'@provider' => $this
->getPluginDefinition()['label'],
];
$configuration = $form_state
->getValues();
if (empty($configuration['client_id'])) {
$form_state
->setErrorByName('client_id', $this
->t('The client ID is missing for @provider.', $provider));
}
if (empty($configuration['client_secret'])) {
$form_state
->setErrorByName('client_secret', $this
->t('The client secret is missing for @provider.', $provider));
}
}
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
}
public function authorize($scope = 'openid email') {
$redirect_uri = $this
->getRedirectUrl()
->toString(TRUE);
$url_options = $this
->getUrlOptions($scope, $redirect_uri);
$endpoints = $this
->getEndpoints();
$this->requestStack
->getCurrentRequest()->query
->remove('destination');
$authorization_endpoint = Url::fromUri($endpoints['authorization'], $url_options)
->toString(TRUE);
$response = new TrustedRedirectResponse($authorization_endpoint
->getGeneratedUrl());
$this->pageCacheKillSwitch
->trigger();
return $response;
}
protected function getUrlOptions($scope, GeneratedUrl $redirect_uri) {
return [
'query' => [
'client_id' => $this->configuration['client_id'],
'response_type' => 'code',
'scope' => $scope,
'redirect_uri' => $redirect_uri
->getGeneratedUrl(),
'state' => $this->stateToken
->create(),
],
];
}
protected function getRequestOptions($authorization_code, $redirect_uri) {
return [
'form_params' => [
'code' => $authorization_code,
'client_id' => $this->configuration['client_id'],
'client_secret' => $this->configuration['client_secret'],
'redirect_uri' => $redirect_uri,
'grant_type' => 'authorization_code',
],
'headers' => [
'Accept' => 'application/json',
],
];
}
public function retrieveTokens($authorization_code) {
$redirect_uri = $this
->getRedirectUrl()
->toString();
$endpoints = $this
->getEndpoints();
$request_options = $this
->getRequestOptions($authorization_code, $redirect_uri);
$client = $this->httpClient;
try {
$response = $client
->post($endpoints['token'], $request_options);
$response_data = json_decode((string) $response
->getBody(), TRUE);
$tokens = [
'id_token' => isset($response_data['id_token']) ? $response_data['id_token'] : NULL,
'access_token' => isset($response_data['access_token']) ? $response_data['access_token'] : NULL,
];
if (array_key_exists('expires_in', $response_data)) {
$tokens['expire'] = $this->dateTime
->getRequestTime() + $response_data['expires_in'];
}
if (array_key_exists('refresh_token', $response_data)) {
$tokens['refresh_token'] = $response_data['refresh_token'];
}
return $tokens;
} catch (\Exception $e) {
$variables = [
'@message' => 'Could not retrieve tokens',
'@error_message' => $e
->getMessage(),
];
if ($e instanceof RequestException && $e
->hasResponse()) {
$response_body = $e
->getResponse()
->getBody()
->getContents();
$variables['@error_message'] .= ' Response: ' . $response_body;
}
$this->loggerFactory
->get('openid_connect_' . $this->pluginId)
->error('@message. Details: @error_message', $variables);
return FALSE;
}
}
public function decodeIdToken($id_token) {
list(, $claims64, ) = explode('.', $id_token);
$claims64 = str_replace([
'-',
'_',
], [
'+',
'/',
], $claims64);
$claims64 = base64_decode($claims64);
return json_decode($claims64, TRUE);
}
public function retrieveUserInfo($access_token) {
$request_options = [
'headers' => [
'Authorization' => 'Bearer ' . $access_token,
'Accept' => 'application/json',
],
];
$endpoints = $this
->getEndpoints();
$client = $this->httpClient;
try {
$response = $client
->get($endpoints['userinfo'], $request_options);
$response_data = (string) $response
->getBody();
return json_decode($response_data, TRUE);
} catch (\Exception $e) {
$variables = [
'@message' => 'Could not retrieve user profile information',
'@error_message' => $e
->getMessage(),
];
if ($e instanceof RequestException && $e
->hasResponse()) {
$response_body = $e
->getResponse()
->getBody()
->getContents();
$variables['@error_message'] .= ' Response: ' . $response_body;
}
$this->loggerFactory
->get('openid_connect_' . $this->pluginId)
->error('@message. Details: @error_message', $variables);
return FALSE;
}
}
protected function getRedirectUrl(array $route_parameters = [], array $options = []) {
$language_none = $this->languageManager
->getLanguage(LanguageInterface::LANGCODE_NOT_APPLICABLE);
$route_parameters += [
'client_name' => $this->pluginId,
];
$options += [
'absolute' => TRUE,
'language' => $language_none,
];
return Url::fromRoute('openid_connect.redirect_controller_redirect', $route_parameters, $options);
}
}