You are here

openid_connect_windows_aad.module in OpenID Connect Microsoft Azure Active Directory client 8

OpenID Connect Windows AAD module file.

File

openid_connect_windows_aad.module
View source
<?php

/**
 * @file
 * OpenID Connect Windows AAD module file.
 */
use Drupal\user\Entity\Role;
use Drupal\user\UserInterface;
use Drupal\user\RoleInterface;
use Drupal\Core\Form\FormState;

/**
 * Implements hook_admin_settings_alter().
 */
function openid_connect_windows_aad_form_openid_connect_admin_settings_alter(&$form, FormState $form_state, $form_id) {
  array_unshift($form['#submit'], '_openid_connect_windows_aad_form_submit_refresh_routes');
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function _openid_connect_windows_aad_form_submit_refresh_routes(&$form, FormState $form_state) {
  $assume_disabled = FALSE;
  try {
    $configuration = \Drupal::config('openid_connect.settings.windows_aad');
    $settings = $configuration
      ->get('settings');
    $aad_enabled = (bool) $configuration
      ->get('enabled');
    $sso_enabled = isset($settings['enable_single_sign_out']) && $settings['enable_single_sign_out'];
  } catch (Exception $exception) {

    // Not likely to happen but assume windows_aad is not enabled if it does.
    $assume_disabled = TRUE;
  }

  // Get clients' enabled status.
  $aad_checked = (bool) $form_state
    ->getValue(array(
    'clients_enabled',
    'windows_aad',
  ));
  $sso_checked = (bool) $form_state
    ->getValue(array(
    'clients',
    'windows_aad',
    'settings',
    'enable_single_sign_out',
  ));

  // Rebuild routes since we may override the user.logout route for single
  // sign off.
  if (!$assume_disabled && ($aad_enabled !== $aad_checked || $sso_enabled !== $sso_checked) || $assume_disabled && (isset($aad_enabled) && $aad_enabled !== $aad_checked || $aad_checked)) {
    \Drupal::logger('openid_connect_windows_aad')
      ->debug('rebuild routes');
    \Drupal::service('router.builder')
      ->setRebuildNeeded();
  }
}

/**
 * Implements hook_openid_connect_userinfo_save().
 */
function openid_connect_windows_aad_openid_connect_userinfo_save(UserInterface $account, array $context) {
  if ($context['plugin_id'] !== 'windows_aad' || !isset($context['user_data']['groups']) && !isset($context['userinfo']['groups']['value'])) {
    return;
  }
  try {

    // Ensure windows_aad auth is enabled and configured for group mapping.
    $configuration = \Drupal::config('openid_connect.settings.windows_aad');
    $settings = $configuration
      ->get('settings');
    $aad_enabled = (bool) $configuration
      ->get('enabled');
    if (!$aad_enabled || !$settings['map_ad_groups_to_roles']) {
      return;
    }
  } catch (Exception $exception) {

    // Not likely to happen but assume windows_aad is not enabled if it does.
    return;
  }
  $mapped_roles = $groups = [];
  $user_roles = $account
    ->getRoles(true);
  $all_roles = Role::loadMultiple();
  unset($all_roles[RoleInterface::ANONYMOUS_ID]);
  unset($all_roles[RoleInterface::AUTHENTICATED_ID]);

  // Retreive a list of previously mapped roles.
  $UserData = \Drupal::service('user.data');
  $previous_mapped_roles = $UserData
    ->get('openid_connect_windows_aad', $account
    ->id(), 'mapped_roles');
  if (!isset($previous_mapped_roles)) {
    $previous_mapped_roles = array();
  }

  // Groups found in the user_data returned from the auth request.
  if (isset($context['user_data']['groups'])) {
    foreach ($context['user_data']['groups'] as $gid) {
      $groups[$gid] = $gid;
    }
  }
  $group_id_key = 'id';

  // Depending on the API the group data has different keys.
  if ($settings['userinfo_graph_api_wa'] == 1) {
    $group_id_key = 'objectId';
  }

  // Groups found in the userinfo returned from the user info request.
  if (isset($context['userinfo']['groups']['value'])) {
    foreach ($context['userinfo']['groups']['value'] as $group) {
      $groups[$group['displayName']] = $group['displayName'];
      if (!isset($groups[$group[$group_id_key]])) {
        $groups[$group[$group_id_key]] = $group[$group_id_key];
      }
    }
  }
  switch ($settings['group_mapping']['method']) {

    // Manual mapping.
    case 1:
      if (!empty($settings['group_mapping']['mappings'])) {
        $role_group_map = [];

        // The mappings is a text area with each line containing the following
        // format: <role id or label>|<AD Group ID or Label>;<AD Group ID or Label>; ...
        // Thus we need to split the text out into its components and determine
        // if there are any roles that should map to this user.
        $list = explode("\n", $settings['group_mapping']['mappings']);
        $list = array_map('trim', $list);
        $list = array_filter($list, 'strlen');
        foreach ($list as $position => $text) {
          $matches = [];
          if (preg_match('/(.*)\\|(.*)/', $text, $matches)) {

            // Trim key and value to avoid unwanted spaces issues.
            $key = trim($matches[1]);
            if (!$key) {
              continue;
            }

            // Ensure we are dealing with the Role's id and not label.
            if (!isset($all_roles[$key])) {
              foreach ($all_roles as $role) {
                if ($key === $role
                  ->label()) {
                  $key = $role
                    ->id();
                }
              }
            }
            if (!isset($all_roles[$key])) {
              continue;
            }

            // Ensure we end up with a non-empty array of AD groups.
            $value = trim($matches[2]);
            $values = explode(';', $value);
            $values = array_map('trim', $values);
            if (!$values) {
              continue;
            }

            // If any of the mappings match the user's groups then the user
            // should be granted the mapped role.
            if (array_intersect($values, $groups)) {
              $mapped_roles[$key] = $key;
            }
          }
        }
      }
      break;

    // Automatic mapping.
    default:
      foreach ($all_roles as $role) {

        // Only Role labels can be matched to the list of the user's groups.
        if (in_array($role
          ->label(), $groups)) {
          $key = $role
            ->id();
          $mapped_roles[$key] = $key;
        }
      }
      break;
  }
  if ($mapped_roles) {
    $mapped_roles = array_keys($mapped_roles);
  }

  // Determine which mapped roles the user does not already have
  $add = array_diff($mapped_roles, $user_roles);
  if ($settings['group_mapping']['strict']) {

    // Strict mode so remove any role the user has that is unmapped.
    $remove = array_diff($user_roles, $mapped_roles);
  }
  else {

    // Not so strict, only remove roles previously mapped, but nolonger are.
    $remove = array_diff($previous_mapped_roles, $mapped_roles);
  }

  // Remove Drupal roles the user is no longer permitted to have.
  foreach ($remove as $rid) {
    if (isset($all_roles[$rid])) {
      $role = $all_roles[$rid];
      $account
        ->removeRole($rid);
      $vars = [
        '@role' => $role
          ->label(),
        '@user' => $account
          ->id(),
      ];
      \Drupal::logger('openid_connect_windows_aad')
        ->notice('Removed role @role from user @user', $vars);
    }
  }

  // Add Drupal roles that the user should be granted.
  foreach ($add as $rid) {
    $account
      ->addRole($rid);
    $role = $all_roles[$rid];
    $vars = [
      '@role' => $role
        ->label(),
      '@user' => $account
        ->id(),
    ];
    \Drupal::logger('openid_connect_windows_aad')
      ->notice('Added role @role to user @user', $vars);
  }

  // Save which roles were mapped so we can properly handle removing mapped
  // roles that change the next time.
  $UserData
    ->set('openid_connect_windows_aad', $account
    ->id(), 'mapped_roles', $mapped_roles);
}