oidc-1.0.0-alpha2/src/Plugin/OpenidConnectRealm/GenericOpenidConnectRealm.php
src/Plugin/OpenidConnectRealm/GenericOpenidConnectRealm.php
<?php
namespace Drupal\oidc\Plugin\OpenidConnectRealm;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\oidc\DisplayNameFormatOptionsTrait;
use Drupal\oidc\JsonHttp\JsonHttpClientInterface;
use Drupal\oidc\JsonHttp\JsonHttpGetRequest;
use Drupal\oidc\JsonHttp\JsonHttpPostRequestInterface;
use Drupal\oidc\OpenidConnectRealm\OpenidConnectRealmBase;
use Drupal\oidc\OpenidConnectRealm\OpenidConnectRealmConfigurableInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides the generic OpenID Connect realm plugin.
*
* @OpenidConnectRealm(
* id = "generic",
* name = @Translation("Generic"),
* deriver = "Drupal\oidc\Plugin\Derivative\GenericOpenidConnectRealmDeriver"
* )
*/
class GenericOpenidConnectRealm extends OpenidConnectRealmBase implements OpenidConnectRealmConfigurableInterface {
use DisplayNameFormatOptionsTrait;
/**
* The configuration storage.
*
* @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
*/
protected $configStorage;
/**
* The OpenID Connect configuration.
*
* @var array
*/
protected $openidConnectConfig;
/**
* The user role storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $userRoleStorage;
/**
* Class constructor.
*
* @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\KeyValueStore\KeyValueFactoryInterface $key_value_factory
* The key-value storage factory.
* @param \Drupal\oidc\JsonHttp\JsonHttpClientInterface $json_http_client
* The JSON HTTP client.
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time service.
* @param \Psr\Log\LoggerInterface $logger
* The logger.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, KeyValueFactoryInterface $key_value_factory, JsonHttpClientInterface $json_http_client, TimeInterface $time, LoggerInterface $logger, EntityTypeManagerInterface $entity_type_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $key_value_factory, $json_http_client, $time, $logger);
$this->setConfiguration($configuration);
$this->configStorage = $key_value_factory->get('oidc.config');
$this->userRoleStorage = $entity_type_manager->getStorage('user_role');
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('keyvalue'),
$container->get('oidc.json_http_client'),
$container->get('datetime.time'),
$container->get('logger.channel.oidc'),
$container->get('entity_type.manager')
);
}
/**
* {@inheritdoc}
*/
public function getConfiguration() {
return $this->configuration;
}
/**
* {@inheritdoc}
*/
public function setConfiguration(array $configuration) {
$this->configuration = array_merge(
$this->defaultConfiguration(),
$configuration
);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'name' => NULL,
'config_url' => NULL,
'client_id' => NULL,
'client_secret' => NULL,
'scopes' => ['profile'],
'request_userinfo' => FALSE,
'id_claim' => 'sub',
'username_claim' => 'preferred_username',
'email_claim' => 'email',
'given_name_claim' => 'given_name',
'family_name_claim' => 'family_name',
'auth_only' => FALSE,
'display_name_format' => '[user:account-name]',
'default_rid' => NULL,
];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['name'] = [
'#type' => 'textfield',
'#title' => $this->t('Name'),
'#default_value' => $this->configuration['name'],
'#required' => TRUE,
];
$form['general'] = [
'#type' => 'details',
'#title' => $this->t('General'),
'#open' => FALSE,
'#parents' => $form['#parents'],
];
$form['general']['config_url'] = [
'#type' => 'url',
'#title' => $this->t('Configuration URL'),
'#description' => $this->t('URL to the .well-known OpenID Connect configuration.'),
'#default_value' => $this->configuration['config_url'],
'#required' => TRUE,
];
$form['general']['client_id'] = [
'#type' => 'textfield',
'#title' => $this->t('Client ID'),
'#default_value' => $this->configuration['client_id'],
'#required' => TRUE,
];
$form['general']['client_secret'] = [
'#type' => 'textfield',
'#title' => $this->t('Client secret'),
'#default_value' => $this->configuration['client_secret'],
'#required' => TRUE,
];
$form['general']['scopes'] = [
'#type' => 'textfield',
'#title' => $this->t('Scopes'),
'#description' => $this->t('Separate the scopes with a space. The %scope scope will be added automatically.', [
'%scope' => 'openid',
]),
'#default_value' => implode(' ', $this->configuration['scopes']),
'#required' => TRUE,
];
$form['claims'] = [
'#type' => 'details',
'#title' => $this->t('Claims'),
'#open' => FALSE,
'#parents' => $form['#parents'],
];
$form['claims']['request_userinfo'] = [
'#type' => 'checkbox',
'#title' => $this->t('User info via endpoint'),
'#description' => $this->t('Request the user info using the dedicated endpoint.'),
'#default_value' => (int) $this->configuration['request_userinfo'],
];
$form['claims']['id_claim'] = [
'#type' => 'textfield',
'#title' => $this->t('ID claim'),
'#description' => $this->t('Name of the ID claim.'),
'#default_value' => $this->configuration['id_claim'],
'#required' => TRUE,
];
$form['claims']['username_claim'] = [
'#type' => 'textfield',
'#title' => $this->t('Username claim'),
'#description' => $this->t('Name of the username claim.'),
'#default_value' => $this->configuration['username_claim'],
'#required' => TRUE,
];
$form['claims']['email_claim'] = [
'#type' => 'textfield',
'#title' => $this->t('Email claim'),
'#description' => $this->t('Name of the email claim.'),
'#default_value' => $this->configuration['email_claim'],
'#required' => TRUE,
];
$form['claims']['given_name_claim'] = [
'#type' => 'textfield',
'#title' => $this->t('Given name claim'),
'#description' => $this->t('Name of the given name claim.'),
'#default_value' => $this->configuration['given_name_claim'],
'#required' => TRUE,
];
$form['claims']['family_name_claim'] = [
'#type' => 'textfield',
'#title' => $this->t('Family name claim'),
'#description' => $this->t('Name of the family name claim.'),
'#default_value' => $this->configuration['family_name_claim'],
'#required' => TRUE,
];
$form['advanced'] = [
'#type' => 'details',
'#title' => $this->t('Advanced'),
'#open' => FALSE,
'#parents' => $form['#parents'],
];
$form['advanced']['auth_only'] = [
'#type' => 'checkbox',
'#title' => $this->t('Use only for authentication'),
'#description' => $this->t('Check to use the realm for authentication and save the personal information from the claims, but afterward forget the OpenID Connect session. Drupal will not store any tokens nor end the OpenID Connect session when the user logs out. It will also not be possible to trigger a logout via the OpenID Connect Identity Provider.'),
'#default_value' => $this->configuration['auth_only'],
];
$form['advanced']['display_name_format'] = [
'#type' => 'select',
'#title' => $this->t('Display name format'),
'#options' => $this->buildDisplayNameFormatOptions([
'[user:account-name]',
'[user:given-name]',
'[user:given-name] [user:family-name]',
'[user:given-name] [user:family-name-abbr]',
'[user:mail]',
]),
'#default_value' => $this->configuration['display_name_format'],
'#required' => TRUE,
];
$roles = $this->userRoleStorage->loadMultiple();
if (count($roles) > 2) {
// Remove the locked roles.
unset($roles[AccountInterface::ANONYMOUS_ROLE], $roles[AccountInterface::AUTHENTICATED_ROLE]);
// Turn into an [ID => label] array.
array_walk($roles, static function (&$role) {
$role = $role->label();
});
$form['advanced']['default_rid'] = [
'#type' => 'select',
'#title' => $this->t('Default role'),
'#description' => $this->t('Assign this role to all new users.'),
'#options' => $roles,
'#empty_option' => '- ' . $this->t('None') . ' -',
'#default_value' => $this->configuration['default_rid'],
'#required' => FALSE,
];
}
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$config_url = $this->configuration['config_url'];
if ($scopes = $form_state->getValue('scopes')) {
$scopes = preg_split('/\s+/', str_replace(',', ' ', $scopes));
$scopes = array_diff($scopes, ['openid']);
$scopes = array_unique($scopes);
}
$this->configuration['name'] = $form_state->getValue('name');
$this->configuration['config_url'] = $form_state->getValue('config_url');
$this->configuration['client_id'] = $form_state->getValue('client_id');
$this->configuration['client_secret'] = $form_state->getValue('client_secret');
$this->configuration['scopes'] = $scopes ?: [];
$this->configuration['request_userinfo'] = (bool) $form_state->getValue('request_userinfo');
$this->configuration['id_claim'] = $form_state->getValue('id_claim');
$this->configuration['username_claim'] = $form_state->getValue('username_claim');
$this->configuration['email_claim'] = $form_state->getValue('email_claim');
$this->configuration['given_name_claim'] = $form_state->getValue('given_name_claim');
$this->configuration['family_name_claim'] = $form_state->getValue('family_name_claim');
$this->configuration['auth_only'] = (bool) $form_state->getValue('auth_only');
$this->configuration['display_name_format'] = $form_state->getValue('display_name_format');
$this->configuration['default_rid'] = $form_state->getValue('default_rid') ?: NULL;
if ($config_url && $this->configuration['config_url'] !== $config_url) {
$this->clearStorage();
}
}
/**
* {@inheritdoc}
*/
public function getDisplayNameFormat() {
return $this->configuration['display_name_format'];
}
/**
* Returns TRUE if the realm is only to be used for authentication.
*
* @return bool
* TRUE if the realm should only be used for authentication.
*/
public function isOnlyForAuthentication() {
return $this->configuration['auth_only'];
}
/**
* Get the default role ID.
*
* @return string|null
* ID of the default role or NULL if not set.
*/
public function getDefaultRoleId() {
return $this->configuration['default_rid'] ?? NULL;
}
/**
* Clear all stored data.
*/
public function clearStorage() {
$this->clearJwks();
$this->configStorage->delete($this->getPluginId());
}
/**
* {@inheritdoc}
*/
protected function getClientId() {
return $this->configuration['client_id'];
}
/**
* {@inheritdoc}
*/
protected function getClientSecret() {
return $this->configuration['client_secret'];
}
/**
* {@inheritdoc}
*/
protected function getScopes() {
return $this->configuration['scopes'];
}
/**
* {@inheritdoc}
*/
protected function getIssuer() {
return $this->getOpenidConnectConfig('issuer');
}
/**
* {@inheritdoc}
*/
protected function getAuthorizationEndpoint() {
return $this->getOpenidConnectConfig('authorization_endpoint');
}
/**
* {@inheritdoc}
*/
protected function getTokenEndpoint() {
return $this->getOpenidConnectConfig('token_endpoint');
}
/**
* {@inheritdoc}
*/
protected function getUserinfoEndpoint() {
if ($this->configuration['request_userinfo']) {
return $this->getOpenidConnectConfig('userinfo_endpoint');
}
return NULL;
}
/**
* {@inheritdoc}
*/
protected function getEndSessionEndpoint() {
return $this->getOpenidConnectConfig('end_session_endpoint');
}
/**
* {@inheritdoc}
*/
protected function getJwksUrl() {
return $this->getOpenidConnectConfig('jwks_uri');
}
/**
* {@inheritdoc}
*/
protected function getJsonWebTokens(JsonHttpPostRequestInterface $json_http_post_request) {
$tokens = parent::getJsonWebTokens($json_http_post_request)
->setIdClaim($this->configuration['id_claim'])
->setUsernameClaim($this->configuration['username_claim'])
->setEmailClaim($this->configuration['email_claim'])
->setGivenNameClaim($this->configuration['given_name_claim'])
->setFamilyNameClaim($this->configuration['family_name_claim']);
if ($this->configuration['request_userinfo'] && !$this->getUserinfoEndpoint()) {
$this->logger->debug('Cannot request user info for %name realm, endpoint unkown.', [
'%name' => $this->configuration['name'],
]);
}
return $tokens;
}
/**
* Get an entry from the OpenID Connect configuration.
*
* @param string|null $key
* The key of the configuration entry to retrieve,
* leave NULL to get the whole configuration.
*
* @return mixed
* The OpenID Connect configuration.
*
* @throws \RuntimeException
*/
protected function getOpenidConnectConfig($key = NULL) {
if ($this->openidConnectConfig === NULL) {
$url = $this->configuration['config_url'];
$config = $this->configStorage->get($this->getPluginId());
if (!isset($config['_url']) || $config['_url'] !== $url) {
$request = new JsonHttpGetRequest($url);
$config = $this->jsonHttpClient->get($request);
$config['_url'] = $url;
$this->configStorage->set($this->getPluginId(), $config);
}
$this->openidConnectConfig = $config;
}
if ($key !== NULL) {
return $this->openidConnectConfig[$key] ?? NULL;
}
return $this->openidConnectConfig;
}
}
