Skip to content
Snippets Groups Projects
Commit 864b9a18 authored by Jesse Payne's avatar Jesse Payne Committed by Tom van Vliet
Browse files

Issue #3012336 by acrazyanimal: Add option for Single Sign out

parent 37762395
Branches
Tags
No related merge requests found
From 59b62f6ec81ea9d5be1867482c14d080c82a0276 Mon Sep 17 00:00:00 2001
From: Jesse Payne <jesseboyntonpayne+gitlab@gmail.com>
Date: Tue, 21 Jan 2020 16:47:21 -0500
Subject: [PATCH] Issue #3012336 by acrazyanimal: Add option for Single Sign
out
---
openid_connect_windows_aad.routing.yml | 8 ++
openid_connect_windows_aad.services.yml | 5 +
src/Controller/WindowsAadSSOController.php | 133 ++++++++++++++++++++++++++
src/Plugin/OpenIDConnectClient/WindowsAad.php | 6 ++
src/Routing/WindowsAadSSORouteSubscriber.php | 41 ++++++++
5 files changed, 193 insertions(+)
create mode 100644 openid_connect_windows_aad.routing.yml
create mode 100644 openid_connect_windows_aad.services.yml
create mode 100644 src/Controller/WindowsAadSSOController.php
create mode 100644 src/Routing/WindowsAadSSORouteSubscriber.php
diff --git a/openid_connect_windows_aad.routing.yml b/openid_connect_windows_aad.routing.yml
new file mode 100644
index 0000000..2d57b68
--- /dev/null
+++ b/openid_connect_windows_aad.routing.yml
@@ -0,0 +1,8 @@
+openid_connect_windows_aad.sso:
+ path: '/openid-connect/windows_aad/signout'
+ defaults:
+ _controller: '\Drupal\openid_connect_windows_aad\Controller\WindowsAadSSOController::signout'
+ requirements:
+ _access: 'TRUE'
+ options:
+ _maintenance_access: TRUE
diff --git a/openid_connect_windows_aad.services.yml b/openid_connect_windows_aad.services.yml
new file mode 100644
index 0000000..819c9b6
--- /dev/null
+++ b/openid_connect_windows_aad.services.yml
@@ -0,0 +1,5 @@
+services:
+ openid_connect_windows_aad.route_subscriber:
+ class: \Drupal\openid_connect_windows_aad\Routing\WindowsAadSSORouteSubscriber
+ tags:
+ - { name: event_subscriber }
diff --git a/src/Controller/WindowsAadSSOController.php b/src/Controller/WindowsAadSSOController.php
new file mode 100644
index 0000000..bd52272
--- /dev/null
+++ b/src/Controller/WindowsAadSSOController.php
@@ -0,0 +1,133 @@
+<?php
+
+namespace Drupal\openid_connect_windows_aad\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Psr\Log\LoggerInterface;
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Url;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Routing\TrustedRedirectResponse;
+use Symfony\Component\HttpFoundation\Response;
+use Drupal\openid_connect\OpenIDConnectAuthmap;
+
+/**
+ * Controller routines for Azure AD single sign out user routes.
+ */
+class WindowsAadSSOController extends ControllerBase {
+
+ /**
+ * A logger instance.
+ *
+ * @var \Psr\Log\LoggerInterface
+ */
+ protected $logger;
+
+ /*
+ * @param \Drupal\openid_connect\OpenIDConnectAuthmap $authmap
+ * The authmap storage.
+ */
+ protected $authmap;
+
+ /**
+ * Constructs a WindowsAadSSOController object.
+ *
+ * @param \Psr\Log\LoggerInterface $logger
+ * A logger instance.
+ */
+ public function __construct(LoggerInterface $logger, OpenIDConnectAuthmap $authmap) {
+ $this->logger = $logger;
+ $this->authmap = $authmap;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('logger.factory')->get('openid_connect_windows_aad'),
+ $container->get('openid_connect.authmap')
+ );
+ }
+
+ /**
+ * Single Sign Out callback to log the current user out.
+ *
+ * Called by Windows Azure AD when a user logs out of their SSO session from
+ * another application such as Office 365.
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ * Either a 200 or 403 response without any content.
+ */
+ public function signout() {
+ $configuration = $this->config('openid_connect.settings.windows_aad');
+ $settings = $configuration->get('settings');
+ $enabled = $configuration->get('enabled');
+ // Check that the windows_aad client is enabled and so is SSOut.
+ if ($enabled && isset($settings['enable_single_sign_out']) && $settings['enable_single_sign_out']) {
+ // Ensure the user has a connected account.
+ $user = \Drupal::currentUser();
+ $connected_accounts = $this->authmap->getConnectedAccounts($user);
+ $connected = ($connected_accounts && isset($connected_accounts['windows_aad']));
+ $logged_in = $user->isAuthenticated();
+ // Only log the user out if they are logged in and have a connected
+ // account. Return a 200 OK in any case since all is good.
+ if ($logged_in && $connected) {
+ user_logout();
+ }
+ return new Response('', Response::HTTP_OK);
+ }
+ // Likely a misconfiguration since SSOut attempts should not be made to the
+ // logout uri unless it has been configured in Azure AD; if you had
+ // configured it in Azure AD then you should have also enabled SSOut in the
+ // OpenID Connect settings. Also, a possible malicious CSRF attempt. Log a
+ // warning either way.
+ $this->logger->warning('Windows AAD Single Sign Out attempt, but SSOut has not been enabled in the OpenID Connect Windows AAD configuration.');
+ 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.settings.windows_aad');
+ $settings = $configuration->get('settings');
+ // Check that the windows_aad client is enabled and so is SSOut.
+ $enabled = (($configuration->get('enabled')) && 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->getConnectedAccounts($user);
+ $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>');
+ }
+}
diff --git a/src/Plugin/OpenIDConnectClient/WindowsAad.php b/src/Plugin/OpenIDConnectClient/WindowsAad.php
index e85443f..eb6366e 100644
--- a/src/Plugin/OpenIDConnectClient/WindowsAad.php
+++ b/src/Plugin/OpenIDConnectClient/WindowsAad.php
@@ -35,6 +35,12 @@ class WindowsAad extends OpenIDConnectClientBase {
*/
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',
diff --git a/src/Routing/WindowsAadSSORouteSubscriber.php b/src/Routing/WindowsAadSSORouteSubscriber.php
new file mode 100644
index 0000000..ba2ea58
--- /dev/null
+++ b/src/Routing/WindowsAadSSORouteSubscriber.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Drupal\openid_connect_windows_aad\Routing;
+
+use Drupal\Core\Routing\RouteSubscriberBase;
+use Symfony\Component\Routing\RouteCollection;
+use Drupal\Core\Utility\Error;
+
+/**
+ * Listens to the dynamic route events.
+ */
+class WindowsAadSSORouteSubscriber extends RouteSubscriberBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function alterRoutes(RouteCollection $collection) {
+ if ($route = $collection->get('user.logout')) {
+ try {
+ $configuration = \Drupal::config('openid_connect.settings.windows_aad');
+ $settings = $configuration->get('settings');
+ $enabled = $configuration->get('enabled');
+ }
+ catch (Exception $exception) {
+ // Not important to differentiate between Exceptions here, we just need
+ // make it know that something is wrong and we won't enable SSOut.
+ $configuration = FALSE;
+ // TODO: When watchdog_exception() is deprecated and ExceptionLogger is
+ // available, update this to use ExceptionLogger.
+ // @see https://www.drupal.org/project/drupal/issues/2932518
+ $variables = Error::decodeException($exception);
+ \Drupal::logger('openid_connect_windows_aad')->error('Failed to check OpenID Connect Windows AAD configuration so Single Sign Off will remain disabled. %type: @message in %function (line %line of %file).', $variables);
+ }
+ // Override the controller for the user.logout route in order to redirect
+ // to the Windows Azure AD Single Sign out endpoint if SSOut is enabled.
+ if ($configuration && $enabled && isset($settings['enable_single_sign_out']) && $settings['enable_single_sign_out']) {
+ $route->setDefault('_controller', '\Drupal\openid_connect_windows_aad\Controller\WindowsAadSSOController::logout');
+ }
+ }
+ }
+}
--
2.7.4
......@@ -8,6 +8,42 @@
use Drupal\user\Entity\Role;
use Drupal\user\UserInterface;
use Drupal\user\RoleInterface;
use Drupal\Form\FormStateInterface;
/**
* Implements hook_admin_settings_alter().
*/
function openid_connect_windows_aad_form_openid_connect_admin_settings_alter(&$form, FormStateInterface $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, FormStateInterface $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().
......
openid_connect_windows_aad.sso:
path: '/openid-connect/windows_aad/signout'
defaults:
_controller: '\Drupal\openid_connect_windows_aad\Controller\WindowsAadSSOController::signout'
requirements:
_access: 'TRUE'
options:
_maintenance_access: TRUE
services:
openid_connect_windows_aad.route_subscriber:
class: \Drupal\openid_connect_windows_aad\Routing\WindowsAadSSORouteSubscriber
tags:
- { name: event_subscriber }
<?php
namespace Drupal\openid_connect_windows_aad\Controller;
use Drupal\Core\Controller\ControllerBase;
use Psr\Log\LoggerInterface;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Routing\TrustedRedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Drupal\openid_connect\OpenIDConnectAuthmap;
/**
* Controller routines for Azure AD single sign out user routes.
*/
class WindowsAadSSOController extends ControllerBase {
/**
* A logger instance.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/*
* @param \Drupal\openid_connect\OpenIDConnectAuthmap $authmap
* The authmap storage.
*/
protected $authmap;
/**
* Constructs a WindowsAadSSOController object.
*
* @param \Psr\Log\LoggerInterface $logger
* A logger instance.
*/
public function __construct(LoggerInterface $logger, OpenIDConnectAuthmap $authmap) {
$this->logger = $logger;
$this->authmap = $authmap;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('logger.factory')->get('openid_connect_windows_aad'),
$container->get('openid_connect.authmap')
);
}
/**
* Single Sign Out callback to log the current user out.
*
* Called by Windows Azure AD when a user logs out of their SSO session from
* another application such as Office 365.
*
* @return \Symfony\Component\HttpFoundation\Response
* Either a 200 or 403 response without any content.
*/
public function signout() {
$configuration = $this->config('openid_connect.settings.windows_aad');
$settings = $configuration->get('settings');
$enabled = $configuration->get('enabled');
// Check that the windows_aad client is enabled and so is SSOut.
if ($enabled && isset($settings['enable_single_sign_out']) && $settings['enable_single_sign_out']) {
// Ensure the user has a connected account.
$user = \Drupal::currentUser();
$connected_accounts = $this->authmap->getConnectedAccounts($user);
$connected = ($connected_accounts && isset($connected_accounts['windows_aad']));
$logged_in = $user->isAuthenticated();
// Only log the user out if they are logged in and have a connected
// account. Return a 200 OK in any case since all is good.
if ($logged_in && $connected) {
user_logout();
}
return new Response('', Response::HTTP_OK);
}
// Likely a misconfiguration since SSOut attempts should not be made to the
// logout uri unless it has been configured in Azure AD; if you had
// configured it in Azure AD then you should have also enabled SSOut in the
// OpenID Connect settings. Also, a possible malicious CSRF attempt. Log a
// warning either way.
$this->logger->warning('Windows AAD Single Sign Out attempt, but SSOut has not been enabled in the OpenID Connect Windows AAD configuration.');
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.settings.windows_aad');
$settings = $configuration->get('settings');
// Check that the windows_aad client is enabled and so is SSOut.
$enabled = (($configuration->get('enabled')) && 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->getConnectedAccounts($user);
$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>');
}
}
......@@ -34,6 +34,12 @@ class WindowsAad extends OpenIDConnectClientBase {
*/
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',
......
<?php
namespace Drupal\openid_connect_windows_aad\Routing;
use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;
use Drupal\Core\Utility\Error;
/**
* Listens to the dynamic route events.
*/
class WindowsAadSSORouteSubscriber extends RouteSubscriberBase {
/**
* {@inheritdoc}
*/
protected function alterRoutes(RouteCollection $collection) {
if ($route = $collection->get('user.logout')) {
try {
$configuration = \Drupal::config('openid_connect.settings.windows_aad');
$settings = $configuration->get('settings');
$enabled = $configuration->get('enabled');
}
catch (Exception $exception) {
// Not important to differentiate between Exceptions here, we just need
// make it know that something is wrong and we won't enable SSOut.
$configuration = FALSE;
// TODO: When watchdog_exception() is deprecated and ExceptionLogger is
// available, update this to use ExceptionLogger.
// @see https://www.drupal.org/project/drupal/issues/2932518
$variables = Error::decodeException($exception);
\Drupal::logger('openid_connect_windows_aad')->error('Failed to check OpenID Connect Windows AAD configuration so Single Sign Off will remain disabled. %type: @message in %function (line %line of %file).', $variables);
}
// Override the controller for the user.logout route in order to redirect
// to the Windows Azure AD Single Sign out endpoint if SSOut is enabled.
if ($configuration && $enabled && isset($settings['enable_single_sign_out']) && $settings['enable_single_sign_out']) {
$route->setDefault('_controller', '\Drupal\openid_connect_windows_aad\Controller\WindowsAadSSOController::logout');
}
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment