Skip to content
Snippets Groups Projects
Commit 1ccaf68e authored by João Ventura's avatar João Ventura Committed by João Ventura
Browse files

Issue #3219004 by jcnventura: Add experimental support for group mapping

parent a4adbffa
No related branches found
Tags 8.x-1.7
No related merge requests found
......@@ -28,6 +28,13 @@ openid_connect.settings:
label: 'User claims mapping'
sequence:
type: string
role_mappings:
type: sequence
label: 'User role mapping'
sequence:
type: sequence
sequence:
type: string
openid_connect.client.*:
type: config_entity
......@@ -99,3 +106,8 @@ openid_connect.client.plugin.okta:
okta_domain:
type: string
label: 'Okta domain'
scopes:
type: sequence
label: 'Scopes'
sequence:
type: string
......@@ -4,6 +4,7 @@ namespace Drupal\openid_connect\Form;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\openid_connect\OpenIDConnect;
......@@ -18,49 +19,52 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
class OpenIDConnectSettingsForm extends ConfigFormBase {
/**
* The OpenID Connect service.
* The entity type manager.
*
* @var \Drupal\openid_connect\OpenIDConnect
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $openIDConnect;
protected $entityTypeManager;
/**
* The entity manager.
* The entity field manager.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* The OpenID Connect claims.
* The OpenID Connect service.
*
* @var \Drupal\openid_connect\OpenIDConnectClaims
* @var \Drupal\openid_connect\OpenIDConnect
*/
protected $claims;
protected $openIDConnect;
/**
* OpenID Connect client plugins.
* The OpenID Connect claims service.
*
* @var \Drupal\openid_connect\Plugin\OpenIDConnectClientInterface[]
* @var \Drupal\openid_connect\OpenIDConnectClaims
*/
protected static $clients;
protected $claims;
/**
* The constructor.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Drupal\openid_connect\OpenIDConnect $openid_connect
* The OpenID Connect service.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
* The entity field manager.
* @param \Drupal\openid_connect\OpenIDConnect $openid_connect
* The OpenID Connect service.
* @param \Drupal\openid_connect\OpenIDConnectClaims $claims
* The claims.
*/
public function __construct(ConfigFactoryInterface $config_factory, OpenIDConnect $openid_connect, EntityFieldManagerInterface $entity_field_manager, OpenIDConnectClaims $claims) {
public function __construct(ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, OpenIDConnect $openid_connect, OpenIDConnectClaims $claims) {
parent::__construct($config_factory);
$this->openIDConnect = $openid_connect;
$this->entityTypeManager = $entity_type_manager;
$this->entityFieldManager = $entity_field_manager;
$this->openIDConnect = $openid_connect;
$this->claims = $claims;
}
......@@ -70,8 +74,9 @@ class OpenIDConnectSettingsForm extends ConfigFormBase {
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('openid_connect.openid_connect'),
$container->get('entity_type.manager'),
$container->get('entity_field.manager'),
$container->get('openid_connect.openid_connect'),
$container->get('openid_connect.claims')
);
}
......@@ -184,7 +189,28 @@ class OpenIDConnectSettingsForm extends ConfigFormBase {
'#options' => (array) $claims,
'#empty_value' => '',
'#empty_option' => $this->t('- No mapping -'),
'#default_value' => isset($mappings[$property_name]) ? $mappings[$property_name] : $default_value,
'#default_value' => $mappings[$property_name] ?? $default_value,
];
}
/** @var \Drupal\user\Entity\Role[] $roles */
$roles = $this->entityTypeManager->getStorage('user_role')->loadMultiple();
unset($roles['anonymous']);
unset($roles['authenticated']);
$role_mappings = $settings->get('role_mappings');
$form['role_mappings'] = [
'#title' => 'EXPERIMENTAL - ' . $this->t('User role mapping'),
'#type' => 'fieldset',
'#description' => 'For each Drupal role, provide the sets of equivalent external groups. A user belonging to one of the provided groups will be assigned the configured Drupal role. Use client_id.group to limit a group to a specific client.',
'#tree' => TRUE,
];
foreach ($roles as $role_id => $role) {
$form['role_mappings'][$role_id] = [
'#title' => $role->label(),
'#type' => 'textfield',
'#default_value' => implode(' ', $role_mappings[$role_id] ?? []),
];
}
......@@ -197,6 +223,11 @@ class OpenIDConnectSettingsForm extends ConfigFormBase {
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
$role_mappings = [];
foreach ($form_state->getValue('role_mappings') as $role => $mapping) {
$role_mappings[$role] = array_values(array_filter(explode(' ', $mapping)));
}
$this->config('openid_connect.settings')
->set('always_save_userinfo', $form_state->getValue('always_save_userinfo'))
->set('connect_existing_users', $form_state->getValue('connect_existing_users'))
......@@ -205,6 +236,7 @@ class OpenIDConnectSettingsForm extends ConfigFormBase {
->set('redirect_login', $form_state->getValue('redirect_login'))
->set('redirect_logout', $form_state->getValue('redirect_logout'))
->set('userinfo_mappings', array_filter($form_state->getValue('userinfo_mappings')))
->set('role_mappings', $role_mappings)
->save();
}
......
......@@ -680,6 +680,15 @@ class OpenIDConnect {
}
}
}
// Map groups to Drupal roles.
$role_mappings = $this->configFactory->get('openid_connect.settings')
->get('role_mappings');
foreach ($role_mappings as $role => $mappings) {
if (!empty(array_intersect($mappings, $userinfo['groups']))) {
$account->addRole($role);
}
}
}
// Save the display name additionally in the user account 'data', for
......
......@@ -114,20 +114,14 @@ class OpenIDConnectClaims implements ContainerInjectionInterface {
* @see http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims
*/
public function getScopes(OpenIDConnectClientInterface $client = NULL): string {
$claims = $this->configFactory
->getEditable('openid_connect.settings')
->get('userinfo_mappings');
// If a client was provided, get the scopes from it.
$scopes = !empty($client) ? $client->getClientScopes() : $this->defaultScopes;
$claims_info = $this->getClaims();
$claims = $this->configFactory->getEditable('openid_connect.settings')->get('userinfo_mappings');
foreach ($claims as $claim) {
if (isset($claims_info[$claim]) &&
!isset($scopes[$claims_info[$claim]['scope']]) &&
$claim != 'email') {
$scopes[$claims_info[$claim]['scope']] = $claims_info[$claim]['scope'];
if (isset($claims_info[$claim]) && !in_array($claims_info[$claim]['scope'], $scopes)) {
$scopes[] = $claims_info[$claim]['scope'];
}
}
return implode(' ', $scopes);
......
......@@ -23,6 +23,7 @@ class OpenIDConnectOktaClient extends OpenIDConnectClientBase {
public function defaultConfiguration(): array {
return [
'okta_domain' => '',
'scopes' => ['openid', 'email'],
] + parent::defaultConfiguration();
}
......@@ -38,6 +39,13 @@ class OpenIDConnectOktaClient extends OpenIDConnectClientBase {
'#default_value' => $this->configuration['okta_domain'],
];
$form['scopes'] = [
'#title' => $this->t('Scopes'),
'#type' => 'textfield',
'#description' => $this->t('Custom scopes, separated by spaces, for example: openid email'),
'#default_value' => implode(' ', $this->configuration['scopes']),
];
return $form;
}
......@@ -55,4 +63,23 @@ class OpenIDConnectOktaClient extends OpenIDConnectClientBase {
];
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$configuration = $form_state->getValues();
if (!empty($configuration['scopes'])) {
$this->setConfiguration(['scopes' => explode(' ', $configuration['scopes'])]);
}
parent::submitConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function getClientScopes(): ?array {
return $this->configuration['scopes'];
}
}
......@@ -274,6 +274,7 @@ abstract class OpenIDConnectClientBase extends PluginBase implements OpenIDConne
$this->requestStack->getCurrentRequest()->query->remove('destination');
$authorization_endpoint = Url::fromUri($endpoints['authorization'], $url_options)->toString(TRUE);
$this->loggerFactory->get('openid_connect_' . $this->pluginId)->debug('Send authorize request to @url', ['@url' => $authorization_endpoint->getGeneratedUrl()]);
$response = new TrustedRedirectResponse($authorization_endpoint->getGeneratedUrl());
// We can't cache the response, since this will prevent the state to be
// added to the session. The kill switch will prevent the page getting
......@@ -391,6 +392,10 @@ abstract class OpenIDConnectClientBase extends PluginBase implements OpenIDConne
try {
$response = $this->httpClient->get($endpoints['userinfo'], $request_options);
$userinfo = Json::decode((string) $response->getBody());
$this->loggerFactory->get('openid_connect_' . $this->pluginId)->debug('Response from userinfo endpoint: @userinfo',
['@userinfo' => print_r($userinfo, TRUE)]);
return (is_array($userinfo)) ? $userinfo : NULL;
}
catch (\Exception $e) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment