diff --git a/auth/shibboleth/README.txt b/auth/shibboleth/README.txt index d630413cbdac0..03b1d2bb9a0ca 100644 --- a/auth/shibboleth/README.txt +++ b/auth/shibboleth/README.txt @@ -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 element. + +-- + + + + +-- + +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 or Markus Hagman diff --git a/auth/shibboleth/auth.php b/auth/shibboleth/auth.php index 8ed938150fe08..6fe37a5490954 100644 --- a/auth/shibboleth/auth.php +++ b/auth/shibboleth/auth.php @@ -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 diff --git a/auth/shibboleth/logout.php b/auth/shibboleth/logout.php new file mode 100644 index 0000000000000..3022ac73284ba --- /dev/null +++ b/auth/shibboleth/logout.php @@ -0,0 +1,204 @@ +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; + 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 ); +} + + +?>