Permalink
Browse files

Merged Shibboleth logout support from 1.9 stable

  • Loading branch information...
1 parent 636bbc8 commit 2db6ec19465e699373d883a5eac517e4c0e994aa exe-cutor committed Dec 4, 2008
Showing with 270 additions and 2 deletions.
  1. +47 −1 auth/shibboleth/README.txt
  2. +19 −1 auth/shibboleth/auth.php
  3. +204 −0 auth/shibboleth/logout.php
View
@@ -20,6 +20,7 @@ Changes:
- 10. 2007: Removed the requirement for email address, surname and given name
attributes on request of Markus Hagman
- 11. 2007: Integrated WAYF Service in Moodle
+- 12. 2008: Single Logout support added
Moodle Configuration with Dual login
-------------------------------------------------------------------------------
@@ -87,7 +88,7 @@ Moodle Configuration with Dual login
moodle/auth/shibboleth/ is protected but *not* the other
scripts and especially not the login.php script.
-5. Save the changes for the 'Shibboleth settings'. T
+5. Save the changes for the 'Shibboleth settings'.
Important Note: If you went for 4.b (integrated WAYF service), saving the
settings will overwrite the Moodle Alternate Login URL
@@ -199,6 +200,51 @@ Example file:
?>
--
+
+How to add logout support
+--------------------------------------------------------------------------------
+
+In order make Moodle support Shibboleth logout, one has to make the Shibboleth
+Service Provider (SP) aware of the Moodle logout capability. Only then the SP
+can trigger Moodle's front or back channel logout handler.
+
+To make the SP aware of the Moodle logout, you have to add the following to the
+Shibboleth main configuration file shibboleth2.xml (usually in /etc/shibboleth/)
+just before the <MetadataProvider> element.
+
+--
+<Notify
+ Channel="back"
+ Location="https://#YOUR_MOODLE_HOSTNAME#/moodle/auth/shibboleth/logout.php" />
+
+<Notify
+ Channel="front"
+ Location="https://#YOUR_MOODLE_HOSTNAME#/moodle/auth/shibboleth/logout.php" />
+
+--
+
+The restart the Shibboleth daemon and check the log file for errors. If there
+were no errors, you cat test the logout feature by accessing Moodle,
+authenticating via Shibboleth and the access the URL:
+#YOUR_MOODLE_HOSTNAME#/Shibboleth.sso/Logout (assuming you have a standard
+Shibboleth installation). If everything worked well, you should see a Shibboleth
+page saying that you were successfully logged out and if you go back to Moodle
+you also should be logged out from Moodle.
+
+
+Limitations:
+Single Logout is only supported with SAML2 and so far only with the Shibboleth
+Service Provider 2.x.
+As of December 2008, the Shibboleth Identity Provider 2.1.1 does not yet support
+Single Logout (SLO). Therefore, the logout feature doesn't make that much
+sense yet. One of the reasons why SLO isn't supported yet is because there aren't
+ many applications yet that were adapted to support front and back channel
+logout. Hopefully, the Moodle logout helps to motivate the developers to
+implement SLO :)
+
+Also see https://spaces.internet2.edu/display/SHIB2/SLOIssues for some
+background information.
+
--------------------------------------------------------------------------------
In case of problems and questions with Shibboleth authentication, contact
Lukas Haemmerle <lukas.haemmerle@switch.ch> or Markus Hagman <hagman@hytti.uku.fi>
View
@@ -51,9 +51,27 @@ function auth_plugin_shibboleth() {
* @return bool Authentication success or failure.
*/
function user_login($username, $password) {
-
+ global $SESSION;
+
// If we are in the shibboleth directory then we trust the server var
if (!empty($_SERVER[$this->config->user_attribute])) {
+ // Associate Shibboleth session with user for SLO preparation
+ $sessionkey = '';
+ if (isset($_SERVER['Shib-Session-ID'])){
+ // This is only available for Shibboleth 2.x SPs
+ $sessionkey = $_SERVER['Shib-Session-ID'];
+ } else {
+ // Try to find out using the user's cookie
+ foreach ($_COOKIE as $name => $value){
+ if (eregi('_shibsession_', $name)){
+ $sessionkey = $value;
+ }
+ }
+ }
+
+ // Set shibboleth session ID for logout
+ $SESSION->shibboleth_session_id = $sessionkey;
+
return (strtolower($_SERVER[$this->config->user_attribute]) == strtolower($username));
} else {
// If we are not, the user has used the manual login and the login name is
View
@@ -0,0 +1,204 @@
+<?php // $Id$
+
+// Implements logout for Shibboleth authenticated users according to:
+// - https://spaces.internet2.edu/display/SHIB2/NativeSPLogoutInitiator
+// - https://spaces.internet2.edu/display/SHIB2/NativeSPNotify
+
+require_once("../../config.php");
+
+require_once($CFG->dirroot."/auth/shibboleth/auth.php");
+
+
+// Front channel logout
+if (
+ isset($_GET['return'])
+ && isset($_GET['action'])
+ && $_GET['action'] == 'logout'
+ ){
+
+ // Logout out user from application
+ // E.g. destroy application session/cookie etc
+ require_logout();
+
+ // Finally, send user to the return URL
+ redirect($_GET['return']);
+}
+
+// Back channel logout
+elseif (!empty($HTTP_RAW_POST_DATA)) {
+
+ // Requires PHP 5
+
+ // Set SOAP header
+ $server = new SoapServer('https://'.$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'].'/LogoutNotification.wsdl');
+ $server->addFunction("LogoutNotification");
+ $server->handle();
+}
+
+// Return WSDL
+else {
+
+ header('Content-Type: text/xml');
+
+ echo <<<WSDL
+<?xml version ="1.0" encoding ="UTF-8" ?>
+<definitions name="LogoutNotification"
+ targetNamespace="urn:mace:shibboleth:2.0:sp:notify"
+ xmlns:notify="urn:mace:shibboleth:2.0:sp:notify"
+ xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
+ xmlns="http://schemas.xmlsoap.org/wsdl/">
+
+<!--
+This page either has to be called with the GET arguments 'action' and 'return' via
+a redirect from the Shibboleth Service Provider logout handler (front-channel
+logout) or via a SOAP request by a Shibboleth Service Provider (back-channel
+logout).
+Because neither of these two variants seems to be the case, the WSDL file for
+the web service is returned.
+
+For more information see:
+- https://spaces.internet2.edu/display/SHIB2/NativeSPLogoutInitiator
+- https://spaces.internet2.edu/display/SHIB2/NativeSPNotify
+-->
+
+ <types>
+ <schema targetNamespace="urn:mace:shibboleth:2.0:sp:notify"
+ xmlns="http://www.w3.org/2000/10/XMLSchema"
+ xmlns:notify="urn:mace:shibboleth:2.0:sp:notify">
+
+ <simpleType name="string">
+ <restriction base="string">
+ <minLength value="1"/>
+ </restriction>
+ </simpleType>
+
+ <element name="OK" type="notify:OKType"/>
+ <complexType name="OKType">
+ <sequence/>
+ </complexType>
+
+ </schema>
+ </types>
+
+ <message name="getLogoutNotificationRequest">
+ <part name="SessionID" type="notify:string" />
+ </message>
+
+ <message name="getLogoutNotificationResponse" >
+ <part name="OK"/>
+ </message>
+
+ <portType name="LogoutNotificationPortType">
+ <operation name="LogoutNotification">
+ <input message="getLogoutNotificationRequest"/>
+ <output message="getLogoutNotificationResponse"/>
+ </operation>
+ </portType>
+
+ <binding name="LogoutNotificationBinding" type="notify:LogoutNotificationPortType">
+ <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
+ <operation name="LogoutNotification">
+ <soap:operation soapAction="urn:xmethods-logout-notification#LogoutNotification"/>
+ </operation>
+ </binding>
+
+ <service name="LogoutNotificationService">
+ <port name="LogoutNotificationPort" binding="notify:LogoutNotificationBinding">
+ <soap:address location="https://{$_SERVER['HTTP_HOST']}{$_SERVER['PHP_SELF']}"/>
+ </port>
+ </service>
+</definitions>
+WSDL;
+ exit;
+
+}
+
+/******************************************************************************/
+
+function LogoutNotification($SessionID){
+
+ global $CFG, $SESSION;
+
+ // Delete session of user using $SessionID
+ if(empty($CFG->dbsessions)) {
+
+ // File session
+ $dir = $CFG->dataroot .'/sessions';
+ if (is_dir($dir)) {
+ if ($dh = opendir($dir)) {
+ while (($file = readdir($dh)) !== false) {
+ //echo $dir.'/'.$file."\n";exit;
+ if (is_file($dir.'/'.$file)){
+ $session_key = ereg_replace('sess_', '', $file);
+
+ $data = file($dir.'/'.$file);
+ if (isset($data[0])){
+ $user_session = unserializesession($data[0]);
+
+ if (isset($user_session['SESSION']) && isset($user_session['SESSION']->shibboleth_session_id)){
+ //echo '2. Shibboleth Session (from filesystem session) of '.$user_session['USER']->username.':' .$user_session['SESSION']->shibboleth_session_id."\n";
+ // If there is a match, delete file
+ if ($user_session['SESSION']->shibboleth_session_id == $SessionID){
+ // Delete this file
+ if (!unlink($dir.'/'.$file)){
+ return new SoapFault('LogoutError', 'Could not delete Moodle session file.');
+ }
+ }
+ }
+ //print_r($user_session);
+ }
+
+ //echo "Moodle session: $session_key \n";
+ //echo "filename: $file \n";
+ }
+ }
+ closedir($dh);
+ }
+ }
+ } else {
+ // DB Session
+ if (!empty($CFG->sessiontimeout)) {
+ $ADODB_SESS_LIFE = $CFG->sessiontimeout;
+ }
+
+ if ($user_session_data = get_records_sql('SELECT sesskey, sessdata FROM '. $CFG->prefix .'sessions2 WHERE expiry > NOW()')) {
+ foreach ($user_session_data as $session_data) {
+
+ //print_r($session_data);
+ $user_session = adodb_unserialize( urldecode($session_data->sessdata) );
+
+ if (isset($user_session['SESSION']) && isset($user_session['SESSION']->shibboleth_session_id)){
+ //echo '3. Shibboleth Session (from ADODB session) of '.$user_session['USER']->username.':' .$user_session['SESSION']->shibboleth_session_id."\n";
+
+ // If there is a match, delete file
+ if ($user_session['SESSION']->shibboleth_session_id == $SessionID){
+ // Delete this session entry
+ if (ADODB_Session::destroy($session_data->sesskey) !== true){
+ return new SoapFault('LogoutError', 'Could not delete Moodle session entry in database.');
+ }
+ }
+ }
+
+ //print_r($user_session);
+ }
+ }
+ }
+
+ // If now SoapFault was thrown the function will return OK as the SP assumes
+
+}
+
+/*****************************************************************************/
+
+// Same function as in adodb, but cannot be used for file session for some reason...
+function unserializesession( $serialized_string ){
+ $variables = array( );
+ $a = preg_split( "/(\w+)\|/", $serialized_string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE );
+ for( $i = 0; $i < count( $a ); $i = $i+2 ) {
+ $variables[$a[$i]] = unserialize( $a[$i+1] );
+ }
+ return( $variables );
+}
+
+
+?>

0 comments on commit 2db6ec1

Please sign in to comment.