]> git.mjollnir.org Git - moodle.git/commitdiff
Merged Shibboleth logout support from 1.9 stable
authorexe-cutor <exe-cutor>
Thu, 4 Dec 2008 16:28:42 +0000 (16:28 +0000)
committerexe-cutor <exe-cutor>
Thu, 4 Dec 2008 16:28:42 +0000 (16:28 +0000)
auth/shibboleth/README.txt
auth/shibboleth/auth.php
auth/shibboleth/logout.php [new file with mode: 0644]

index d630413cbdac0b1c938035160dd687fef24edc74..03b1d2bb9a0cad5668c34d7114a11608a49ebc2b 100644 (file)
@@ -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>
index 8ed938150fe083579cc828efb9cef70b215f91f4..6fe37a5490954fda0698ac0238521d3b5b2625b7 100644 (file)
@@ -51,9 +51,27 @@ class auth_plugin_shibboleth extends auth_plugin_base {
      * @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 (file)
index 0000000..3022ac7
--- /dev/null
@@ -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 );
+}
+
+
+?>