Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merged Shibboleth logout support from 1.9 stable
  • Loading branch information
exe-cutor committed Dec 4, 2008
1 parent 636bbc8 commit 2db6ec1
Show file tree
Hide file tree
Showing 3 changed files with 270 additions and 2 deletions.
48 changes: 47 additions & 1 deletion auth/shibboleth/README.txt
Expand Up @@ -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
-------------------------------------------------------------------------------
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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>
20 changes: 19 additions & 1 deletion auth/shibboleth/auth.php
Expand Up @@ -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
Expand Down
204 changes: 204 additions & 0 deletions auth/shibboleth/logout.php
@@ -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.