Commit e782f626 authored by Florian Weber's avatar Florian Weber
Browse files

Issue #3322786: Replace single sign out (end_session handling) with openid_connect core

parent 4b64dff9
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@
  },
  "require": {
    "drupal/key": "^1.0",
    "drupal/openid_connect": "^2.0"
    "drupal/openid_connect": "^2.0",
    "lcobucci/jwt": "^4.2.1"
  }
}
+0 −3
Original line number Diff line number Diff line
@@ -18,9 +18,6 @@ openid_connect.client.plugin.windows_aad:
    userinfo_endpoint_wa:
      type: uri
      label: 'Userinfo endpoint'
    enable_single_sign_out:
      type: boolean
      label: 'Enable Single Sign Out'
    map_ad_groups_to_roles:
      type: boolean
      label: 'Map user''s AD groups to Drupal roles'
+21 −0
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@

use Drupal\Core\Language\LanguageInterface;
use Drupal\key\Entity\Key;
use Drupal\openid_connect\Entity\OpenIDConnectClientEntity;

/**
 * Implements hook_uninstall().
@@ -77,3 +78,23 @@ function openid_connect_windows_aad_update_9202() {
  $config->set('settings', $settings);
  $config->save();
}

/**
 * Convert enable_single_sign_out to openid_connect end_session endpoint.
 */
function openid_connect_windows_aad_update_9205() {
  $ids = \Drupal::entityQuery('openid_connect_client')
    ->condition('plugin', 'windows_aad')
    ->condition('settings.enable_single_sign_out', TRUE, '=')
    ->execute();

  foreach ($ids as $id) {
    /** @var OpenIDConnectClientEntity $client */
    $client = OpenIDConnectClientEntity::load($id);
    $settings = $client->get('settings');
    if (empty($settings['end_session_endpoint'])) {
      $settings['end_session_endpoint'] = 'https://login.microsoftonline.com/common/oauth2/v2.0/logout';
      $client->set('settings', $settings)->save();
    }
  }
}
+35 −44
Original line number Diff line number Diff line
<?php

declare(strict_types = 1);

/**
 * @file
 * OpenID Connect Windows AAD module file.
 */

use Drupal\Core\Form\FormState;
use Drupal\Core\Routing\TrustedRedirectResponse;
use Drupal\openid_connect\Entity\OpenIDConnectClientEntity;
use Drupal\openid_connect_windows_aad\Plugin\OpenIDConnectClient\WindowsAad;
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.client.windows_aad');
    $settings = $configuration->get('settings');
    $aad_enabled = $configuration->get('status');
    $sso_enabled = $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([
    'clients_enabled',
    'windows_aad',
  ]);
  $sso_checked = (bool) $form_state->getValue([
    '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();
  }
}
use Drupal\user\UserInterface;
use Lcobucci\JWT\Encoding\CannotDecodeContent;
use Lcobucci\JWT\Encoding\JoseEncoder;
use Lcobucci\JWT\Token\InvalidTokenStructure;
use Lcobucci\JWT\Token\Parser;
use Lcobucci\JWT\Token\UnsupportedHeaderFound;

/**
 * Implements hook_openid_connect_userinfo_save().
@@ -225,3 +193,26 @@ function openid_connect_windows_aad_openid_connect_userinfo_save(UserInterface $
  }

}

/**
 * Implements hook_openid_connect_redirect_logout_alter().
 */
function openid_connect_windows_aad_openid_connect_redirect_logout_alter(array &$response, array $context) : void {
  $client = OpenIDConnectClientEntity::load($context['client']);
  if ($client->getPlugin() instanceof WindowsAad && $response['response'] instanceof TrustedRedirectResponse) {
    $idToken = \Drupal::service('openid_connect.session')->retrieveIdToken();
    if (!$idToken) {
      return;
    }
    $parser = new Parser(new JoseEncoder());
    try {
      $token = $parser->parse($idToken);
      $loginHint = $token->claims()->get('login_hint');
      if ($loginHint) {
        $response['response']->setTrustedTargetUrl($response['response']->getTargetUrl() . '&logout_hint=' . $loginHint);
      }
    } catch (CannotDecodeContent | InvalidTokenStructure | UnsupportedHeaderFound $e) {
      // ID Token is incorrect, don't add alter the redirect url.
    }
  }
}
+0 −47
Original line number Diff line number Diff line
@@ -88,51 +88,4 @@ class WindowsAadSSOController extends ControllerBase {
    return new Response('', Response::HTTP_FORBIDDEN);
  }

  /**
   * Logs the current user out. Overrides UserController::logout().
   *
   * If Single Sign out has been enabled in OpenID Connect Windows AAD config
   * then redirect the user when they try to log out of the app to the Windows
   * single sign out endpoint. They will be logged out of their other SSO apps.
   *
   * @return \Symfony\Component\HttpFoundation\RedirectResponse
   *   A redirection to either the home page or to Azure AD Single Sign out.
   */
  public function logout() {
    $connected = FALSE;
    $configuration = $this->config('openid_connect.client.windows_aad');
    $settings = $configuration->get('settings');
    // Check that the windows_aad client is enabled and so is SSOut.
    $enabled = (($configuration->get('status')) && isset($settings['enable_single_sign_out']) && $settings['enable_single_sign_out']);

    // Check for a connected account before we log the Drupal user out.
    if ($enabled) {
      // Ensure the user has a connected account.
      $user = \Drupal::currentUser();
      $connected_accounts = $this->authmap->getAll($user->id());
      $connected = ($connected_accounts && isset($connected_accounts['windows_aad']));
    }

    user_logout();
    if ($connected) {
      // Redirect back to the home page once signed out.
      $redirect_uri = Url::fromRoute('<front>', [], ['absolute' => TRUE])
        ->toString(TRUE)
        ->getGeneratedUrl();
      $query_parameters = [
        'post_logout_redirect_uri' => $redirect_uri,
      ];
      $query = UrlHelper::buildQuery($query_parameters);

      $response = new TrustedRedirectResponse('https://login.microsoftonline.com/common/oauth2/v2.0/logout?' . $query);
      // We can't cache the response, since we need the user to get logged out
      // prior to being redirected. The kill switch will prevent the page
      // getting cached when page cache is active.
      \Drupal::service('page_cache_kill_switch')->trigger();
      return $response;
    }
    // No SSOut so do the usual thing and redirect to the front page.
    return $this->redirect('<front>');
  }

}
Loading