class WindowsAad in OpenID Connect Microsoft Azure Active Directory client 8
Same name and namespace in other branches
- 2.0.x src/Plugin/OpenIDConnectClient/WindowsAad.php \Drupal\openid_connect_windows_aad\Plugin\OpenIDConnectClient\WindowsAad
Generic OpenID Connect client.
Used primarily to login to Drupal sites powered by oauth2_server or PHP sites powered by oauth2-server-php.
Plugin annotation
@OpenIDConnectClient(
id = "windows_aad",
label = @Translation("Windows Azure AD")
)
Hierarchy
- class \Drupal\Component\Plugin\PluginBase implements DerivativeInspectionInterface, PluginInspectionInterface
- class \Drupal\openid_connect\Plugin\OpenIDConnectClientBase implements ContainerFactoryPluginInterface, OpenIDConnectClientInterface uses StringTranslationTrait
- class \Drupal\openid_connect_windows_aad\Plugin\OpenIDConnectClient\WindowsAad
- class \Drupal\openid_connect\Plugin\OpenIDConnectClientBase implements ContainerFactoryPluginInterface, OpenIDConnectClientInterface uses StringTranslationTrait
Expanded class hierarchy of WindowsAad
File
- src/
Plugin/ OpenIDConnectClient/ WindowsAad.php, line 22
Namespace
Drupal\openid_connect_windows_aad\Plugin\OpenIDConnectClientView source
class WindowsAad extends OpenIDConnectClientBase {
/**
* Overrides OpenIDConnectClientBase::settingsForm().
*
* @param array $form
* Windows AAD form array containing form elements.
* @param Drupal\Core\Form\FormStateInterface $form_state
* Submitted form values.
*
* @return array
* Renderable form array with form elements.
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['enable_single_sign_out'] = [
'#title' => $this
->t('Enable Single Sign Out'),
'#type' => 'checkbox',
'#default_value' => !empty($this->configuration['enable_single_sign_out']) ? $this->configuration['enable_single_sign_out'] : false,
'#description' => $this
->t('Checking this option will enable Single Sign Out to occur so long as the logout url has been set to (http(s)://yoursite.com/openid-connect/windows_aad/signout) in your Azure AD registered app settings. If a user logs out of the Drupal app then they will be logged out of their SSO session elsewhere as well. Conversely if a user signs out of their SSO account elsewhere, such as Office 365, they will also be logged out of this app.'),
];
$form['authorization_endpoint_wa'] = [
'#title' => $this
->t('Authorization endpoint'),
'#type' => 'textfield',
'#default_value' => $this->configuration['authorization_endpoint_wa'],
];
$form['token_endpoint_wa'] = [
'#title' => $this
->t('Token endpoint'),
'#type' => 'textfield',
'#default_value' => $this->configuration['token_endpoint_wa'],
];
$form['map_ad_groups_to_roles'] = [
'#title' => $this
->t('Map user\'s AD groups to Drupal roles'),
'#type' => 'checkbox',
'#default_value' => !empty($this->configuration['map_ad_groups_to_roles']) ? $this->configuration['map_ad_groups_to_roles'] : '',
'#description' => $this
->t('Enable this to configure Drupal user role assignment based on AD group membership.'),
];
// AD group mapping configuration field set.
$form['group_mapping'] = [
'#type' => 'fieldset',
'#title' => $this
->t('AD group mapping options'),
'#states' => [
'invisible' => [
':input[name="clients[windows_aad][settings][map_ad_groups_to_roles]"]' => [
'checked' => FALSE,
],
],
],
];
$form['group_mapping']['method'] = [
'#type' => 'radios',
'#title' => $this
->t('Method for mapping AD groups to roles'),
'#options' => [
0 => $this
->t('Automatic (AD group names or ids identically match Drupal role names)'),
1 => $this
->t('Manual (Specify which AD groups map to which Drupal roles)'),
],
'#default_value' => !empty($this->configuration['group_mapping']['method']) ? $this->configuration['group_mapping']['method'] : 0,
'#description' => $this
->t('Note: For name mapping to function the Azure AD Graph or Windows Graph APIs must be selected as a User endpoint. Otherwise only mapping based on Group Object IDs can be used.'),
];
$form['group_mapping']['mappings'] = [
'#title' => $this
->t('Manual mappings'),
'#type' => 'textarea',
'#default_value' => isset($this->configuration['group_mapping']) && isset($this->configuration['group_mapping']['mappings']) ? $this->configuration['group_mapping']['mappings'] : '',
'#description' => $this
->t('Add one role|group(s) mapping per line. Role and Group should be separated by "|". Multiple groups can be mapped to a single role on the same line using ";" to separate the groups. Ideally you should use the group id since it is immutable, but the title (displayName) may also be used.'),
'#states' => [
'invisible' => [
':input[name="clients[windows_aad][settings][group_mapping][method]"]' => [
'value' => 0,
],
],
],
];
$form['group_mapping']['strict'] = [
'#type' => 'checkbox',
'#title' => $this
->t('Only allow users to have roles that map to an AD group they are a member of.'),
'#default_value' => !empty($this->configuration['group_mapping']['strict']) ? $this->configuration['group_mapping']['strict'] : '',
'#description' => $this
->t('Removes roles from a Drupal user account that do not map to AD groups the user is a member of. Also, with this enabled you can not grant unmapped roles to a user through the usual Drupal user/role interface such as editing a user account. Note: Only affects users with connected AD accounts.'),
];
$form['userinfo_graph_api_wa'] = [
'#title' => $this
->t('User info endpoint configuration'),
'#type' => 'radios',
'#default_value' => !empty($this->configuration['userinfo_graph_api_wa']) ? $this->configuration['userinfo_graph_api_wa'] : 0,
'#options' => [
0 => $this
->t('Alternate or no user endpoint'),
1 => $this
->t('Azure AD Graph API (v1.6)'),
2 => $this
->t('Windows Graph API (v1.0)'),
],
'#description' => $this
->t('Most user/group info can be returned in the access token response through proper claims/permissions configuration for your app registration within Azure AD. If this is the case for your setup then you can choose "Alternate or no user endpoint" and leave blank the dependent "Alternate userinfo endpoint" text box. Otherwise you can choose to use the Azure AD graph API or the Windows Graph API (recommended) to retrieve user and/or graph info.'),
];
$form['userinfo_endpoint_wa'] = [
'#title' => $this
->t('Alternate UserInfo endpoint'),
'#type' => 'textfield',
'#default_value' => $this->configuration['userinfo_endpoint_wa'],
'#states' => [
'visible' => [
':input[name="clients[windows_aad][settings][userinfo_graph_api_wa]"]' => [
'value' => 0,
],
],
],
];
$form['userinfo_graph_api_use_other_mails'] = [
'#title' => $this
->t('Use Graph API otherMails property for email address'),
'#type' => 'checkbox',
'#default_value' => !empty($this->configuration['userinfo_graph_api_use_other_mails']) ? $this->configuration['userinfo_graph_api_use_other_mails'] : '',
'#description' => $this
->t('Find the first occurrence of an email address in the Graph otherMails property and use this as email address.'),
'#states' => [
'visible' => [
':input[name="clients[windows_aad][settings][userinfo_graph_api_wa]"]' => [
'value' => 1,
],
],
],
];
$form['userinfo_update_email'] = [
'#title' => $this
->t('Update email address in user profile'),
'#type' => 'checkbox',
'#default_value' => !empty($this->configuration['userinfo_update_email']) ? $this->configuration['userinfo_update_email'] : '',
'#description' => $this
->t('If email address has been changed for existing user, save the new value to the user profile.'),
];
$form['hide_email_address_warning'] = [
'#title' => $this
->t('Hide missing email address warning'),
'#type' => 'checkbox',
'#default_value' => !empty($this->configuration['hide_email_address_warning']) ? $this->configuration['hide_email_address_warning'] : '',
'#description' => $this
->t('By default, when email address is not found, a message will appear on the screen. This option hides that message (as it might be confusing for end users).'),
];
return $form;
}
/**
* Overrides OpenIDConnectClientBase::getEndpoints().
*
* @return array
* Endpoint details with authorization endpoints, user access token and
* userinfo object.
*/
public function getEndpoints() {
return [
'authorization' => $this->configuration['authorization_endpoint_wa'],
'token' => $this->configuration['token_endpoint_wa'],
'userinfo' => $this->configuration['userinfo_endpoint_wa'],
];
}
/**
* Implements OpenIDConnectClientInterface::retrieveIDToken().
*
* @param string $authorization_code
* A authorization code string.
*
* @return array|bool
* A result array or false.
*/
public function retrieveTokens($authorization_code) {
// Exchange `code` for access token and ID token.
$language_none = \Drupal::languageManager()
->getLanguage(LanguageInterface::LANGCODE_NOT_APPLICABLE);
$redirect_uri = Url::fromRoute('openid_connect.redirect_controller_redirect', [
'client_name' => $this->pluginId,
], [
'absolute' => TRUE,
'language' => $language_none,
])
->toString();
$endpoints = $this
->getEndpoints();
$request_options = [
'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',
],
];
// Add a Graph API as resource if an option is selected.
switch ($this->configuration['userinfo_graph_api_wa']) {
case 1:
$request_options['form_params']['resource'] = 'https://graph.windows.net';
break;
case 2:
$request_options['form_params']['resource'] = 'https://graph.microsoft.com';
break;
}
/* @var \GuzzleHttp\ClientInterface $client */
$client = $this->httpClient;
try {
$response = $client
->post($endpoints['token'], $request_options);
$response_data = json_decode((string) $response
->getBody(), TRUE);
// Expected result.
$tokens = [
'id_token' => $response_data['id_token'],
'access_token' => $response_data['access_token'],
'refresh_token' => isset($response_data['refresh_token']) ? $response_data['refresh_token'] : FALSE,
];
if (array_key_exists('expires_in', $response_data)) {
$tokens['expire'] = \Drupal::time()
->getRequestTime() + $response_data['expires_in'];
}
return $tokens;
} catch (RequestException $e) {
$variables = [
'@message' => 'Could not retrieve tokens',
'@error_message' => $e
->getMessage(),
];
$this->loggerFactory
->get('openid_connect_windows_aad')
->error('@message. Details: @error_message', $variables);
return FALSE;
}
}
/**
* Implements OpenIDConnectClientInterface::retrieveUserInfo().
*
* @param string $access_token
* An access token string.
*
* @return array|bool
* A result array or false.
*/
public function retrieveUserInfo($access_token) {
// Determine if we use Graph API or default O365 Userinfo as this will
// affect the data we collect and use in the Userinfo array.
switch ($this->configuration['userinfo_graph_api_wa']) {
case 1:
$userinfo = $this
->buildUserinfo($access_token, 'https://graph.windows.net/me?api-version=1.6', 'userPrincipalName', 'displayName');
break;
case 2:
$userinfo = $this
->buildUserinfo($access_token, 'https://graph.microsoft.com/v1.0/me', 'userPrincipalName', 'displayName');
break;
default:
$endpoints = $this
->getEndpoints();
if ($endpoints['userinfo']) {
$userinfo = $this
->buildUserinfo($access_token, $endpoints['userinfo'], 'upn', 'name');
}
else {
$userinfo = array();
}
break;
}
// If AD group to Drupal role mapping has been enabled then attach group
// data from a graph API if configured to do so.
if (!empty($this->configuration['map_ad_groups_to_roles'])) {
$userinfo['groups'] = $this
->retrieveGroupInfo($access_token);
}
// Check to see if we have changed email data, O365_connect doesn't
// give us the possibility to add a mapping for it, so we do the change
// now, first checking if this is wanted by checking the setting for it.
if ($userinfo && $this->configuration['userinfo_update_email'] == 1) {
/** @var \Drupal\user\UserInterface $user */
$user = user_load_by_name($userinfo['name']);
if ($user && $user
->getEmail() != $userinfo['email']) {
$user
->setEmail($userinfo['email']);
$user
->save();
}
}
return $userinfo;
}
/**
* Helper function to do the call to the endpoint and build userinfo array.
*
* @param string $access_token
* The access token.
* @param string $url
* The endpoint we want to send the request to.
* @param string $upn
* The name of the property that holds the Azure username.
* @param string $name
* The name of the property we want to map to Drupal username.
*
* @return array
* The userinfo array. Empty array if unsuccessful.
*/
private function buildUserinfo($access_token, $url, $upn, $name) {
$profile_data = [];
// Perform the request.
$options = [
'method' => 'GET',
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $access_token,
],
];
$client = $this->httpClient;
try {
$response = $client
->get($url, $options);
$response_data = (string) $response
->getBody();
// Profile Information.
$profile_data = json_decode($response_data, TRUE);
$profile_data['name'] = $profile_data[$name];
// Azure provides 'mail' for userinfo vs email.
if (!isset($profile_data['mail'])) {
// See if we have the Graph otherMails property and use it if available,
// if not, add the principal name as email instead, so Drupal still will
// create the user anyway.
if ($this->configuration['userinfo_graph_api_use_other_mails'] == 1) {
if (!empty($profile_data['otherMails'])) {
// Use first occurrence of otherMails attribute.
$profile_data['email'] = current($profile_data['otherMails']);
}
}
else {
// Show message to user.
if ($this->configuration['hide_email_address_warning'] != 1) {
\Drupal::messenger()
->addWarning(t('Email address not found in UserInfo. Used username instead, please check this in your profile.'));
}
// Write watchdog warning.
$variables = [
'@user' => $profile_data[$upn],
];
$this->loggerFactory
->get('openid_connect_windows_aad')
->warning('Email address of user @user not found in UserInfo. Used username instead, please check.', $variables);
$profile_data['email'] = $profile_data[$upn];
}
}
else {
// OpenID Connect module expects the 'email' token for userinfo.
$profile_data['email'] = $profile_data['mail'];
}
} catch (RequestException $e) {
$variables = [
'@error_message' => $e
->getMessage(),
];
$this->loggerFactory
->get('openid_connect_windows_aad')
->error('Could not retrieve user profile information. Details: @error_message', $variables);
}
return $profile_data;
}
/**
* Calls a graph api to retrieve teh user's group membership information.
*
* @param string $access_token
* An access token string.
*
* @return array
* An array of group informaion.
*/
protected function retrieveGroupInfo($access_token) {
// By default or if an error occurs return empty group information.
$group_data = [];
switch ($this->configuration['userinfo_graph_api_wa']) {
case 1:
$uri = 'https://graph.windows.net/me/memberOf?api-version=1.6';
break;
case 2:
$uri = 'https://graph.microsoft.com/v1.0/me/memberOf';
break;
default:
$uri = false;
break;
}
if ($uri) {
// Perform the request.
$options = [
'method' => 'GET',
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $access_token,
],
];
$client = $this->httpClient;
try {
$response = $client
->get($uri, $options);
$response_data = (string) $response
->getBody();
// Group Information.
$group_data = json_decode($response_data, TRUE);
} catch (RequestException $e) {
$variables = [
'@api' => $uri,
'@error_message' => $e
->getMessage(),
];
$this->loggerFactory
->get('openid_connect_windows_aad')
->error('Failed to retrieve AD group information from graph api (@api). Details: @error_message', $variables);
}
}
// Return group information or an empty array.
return $group_data;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
OpenIDConnectClientBase:: |
protected | property | The minimum set of scopes for this client. | |
OpenIDConnectClientBase:: |
protected | property | The datetime.time service. | |
OpenIDConnectClientBase:: |
protected | property | The HTTP client to fetch the feed data with. | |
OpenIDConnectClientBase:: |
protected | property | The language manager. | |
OpenIDConnectClientBase:: |
protected | property | The logger factory used for logging. | |
OpenIDConnectClientBase:: |
protected | property | Page cache kill switch. | |
OpenIDConnectClientBase:: |
protected | property | The request stack used to access request globals. | |
OpenIDConnectClientBase:: |
protected | property | The OpenID state token service. | |
OpenIDConnectClientBase:: |
public | function |
Redirects the user to the authorization endpoint. Overrides OpenIDConnectClientInterface:: |
3 |
OpenIDConnectClientBase:: |
public | function |
Calculates dependencies for the configured plugin. Overrides DependentPluginInterface:: |
|
OpenIDConnectClientBase:: |
public static | function |
Creates an instance of the plugin. Overrides ContainerFactoryPluginInterface:: |
|
OpenIDConnectClientBase:: |
public | function |
Decodes ID token to access user data. Overrides OpenIDConnectClientInterface:: |
3 |
OpenIDConnectClientBase:: |
public | function |
Gets default configuration for this plugin. Overrides ConfigurableInterface:: |
3 |
OpenIDConnectClientBase:: |
public | function |
Gets an array of of scopes. Overrides OpenIDConnectClientInterface:: |
|
OpenIDConnectClientBase:: |
public | function |
Gets this plugin's configuration. Overrides ConfigurableInterface:: |
|
OpenIDConnectClientBase:: |
protected | function | Returns the redirect URL. | |
OpenIDConnectClientBase:: |
protected | function | Helper function for request options. | |
OpenIDConnectClientBase:: |
protected | function | Helper function for URL options. | |
OpenIDConnectClientBase:: |
public | function |
Sets the configuration for this plugin instance. Overrides ConfigurableInterface:: |
|
OpenIDConnectClientBase:: |
public | function |
Form submission handler. Overrides PluginFormInterface:: |
|
OpenIDConnectClientBase:: |
public | function |
Form validation handler. Overrides PluginFormInterface:: |
|
OpenIDConnectClientBase:: |
public | function |
The constructor. Overrides PluginBase:: |
|
PluginBase:: |
protected | property | Configuration information passed into the plugin. | 1 |
PluginBase:: |
protected | property | The plugin implementation definition. | 1 |
PluginBase:: |
protected | property | The plugin_id. | |
PluginBase:: |
constant | A string which is used to separate base plugin IDs from the derivative ID. | ||
PluginBase:: |
public | function |
Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the definition of the plugin implementation. Overrides PluginInspectionInterface:: |
3 |
PluginBase:: |
public | function |
Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface:: |
|
PluginBase:: |
public | function | Determines if the plugin is configurable. | |
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. | |
WindowsAad:: |
public | function |
Overrides OpenIDConnectClientBase::settingsForm(). Overrides OpenIDConnectClientBase:: |
|
WindowsAad:: |
private | function | Helper function to do the call to the endpoint and build userinfo array. | |
WindowsAad:: |
public | function |
Overrides OpenIDConnectClientBase::getEndpoints(). Overrides OpenIDConnectClientInterface:: |
|
WindowsAad:: |
protected | function | Calls a graph api to retrieve teh user's group membership information. | |
WindowsAad:: |
public | function |
Implements OpenIDConnectClientInterface::retrieveIDToken(). Overrides OpenIDConnectClientBase:: |
|
WindowsAad:: |
public | function |
Implements OpenIDConnectClientInterface::retrieveUserInfo(). Overrides OpenIDConnectClientBase:: |