From 140bb89f22e4f33818421c0fada1f7b03a394fe8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andreas=20=C3=85kre=20Solberg?= <andreas.solberg@uninett.no>
Date: Wed, 5 Mar 2008 14:25:15 +0000
Subject: [PATCH] Improved SLO error handling, and implemented IdP initiated
SLO (to be used with google apps for education)
git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@357 44740490-163a-0410-bde0-09ae8108e29a
---
dictionaries/errors.php | 7 +
.../Bindings/SAML20/HTTPRedirect.php | 2 +-
www/saml2/idp/SingleLogoutService.php | 136 +++++++++-------
www/saml2/idp/initSLO.php | 153 ++++++++++++++++++
4 files changed, 242 insertions(+), 56 deletions(-)
create mode 100644 www/saml2/idp/initSLO.php
diff --git a/dictionaries/errors.php b/dictionaries/errors.php
index 49a654a64..e94c48217 100644
--- a/dictionaries/errors.php
+++ b/dictionaries/errors.php
@@ -13,6 +13,10 @@ $lang = array(
'title_GENERATEAUTHNRESPONSE' => 'Could not create authentication response',
'descr_GENERATEAUTHNRESPONSE' => 'When this identity provider tried to create an authentication response, an error occured.',
+
+ 'title_GENERATELOGOUTRESPONSE' => 'Could not create logout response',
+ 'descr_GENERATELOGOUTRESPONSE' => 'When this SAML entity tried to create an logout response, an error occured.',
+
'title_LDAPERROR' => 'LDAP Error',
'descr_LDAPERROR' => 'LDAP is the user database, and when you try to login, we need to contact an LDAP database. When we tried it this time an error occured.',
@@ -20,6 +24,9 @@ $lang = array(
'title_LOGOUTREQUEST' => 'Error processing Logout Request',
'descr_LOGOUTREQUEST' => 'An error occured when trying to process the Logout Request.',
+ 'title_GENERATELOGOUTREQUEST' => 'Could not create logout request',
+ 'descr_GENERATELOGOUTREQUEST' => 'When this SAML entity tried to create an logout request, an error occured.',
+
'title_LOGOUTRESPONSE' => 'Error processing Logout Response',
'descr_LOGOUTRESPONSE' => 'An error occured when trying to process the Logout Response.',
diff --git a/lib/SimpleSAML/Bindings/SAML20/HTTPRedirect.php b/lib/SimpleSAML/Bindings/SAML20/HTTPRedirect.php
index a57bd2270..c7d26d786 100644
--- a/lib/SimpleSAML/Bindings/SAML20/HTTPRedirect.php
+++ b/lib/SimpleSAML/Bindings/SAML20/HTTPRedirect.php
@@ -67,7 +67,7 @@ class SimpleSAML_Bindings_SAML20_HTTPRedirect {
if ($mode == 'IdP') {
$metadataset = 'saml20-sp-remote';
}
-
+ SimpleSAML_Logger::debug('Library - HTTPRedirect validateQuery(): Looking up metadata issuer:' . $issuer . ' in set '. $metadataset);
$md = $this->metadata->getMetaData($issuer, $metadataset);
// check wether to validate or not
diff --git a/www/saml2/idp/SingleLogoutService.php b/www/saml2/idp/SingleLogoutService.php
index cbe7f4644..ddf7a3d1b 100644
--- a/www/saml2/idp/SingleLogoutService.php
+++ b/www/saml2/idp/SingleLogoutService.php
@@ -39,6 +39,7 @@ try {
SimpleSAML_Utilities::fatalError($session->getTrackID(), 'METADATA', $exception);
}
+SimpleSAML_Logger::debug('SAML2.0 - IdP.SingleLogoutService: Got IdP entity id: ' . $idpentityid);
/**
* If we get an incomming LogoutRequest then we initiate the logout process.
@@ -48,6 +49,8 @@ try {
*/
if (isset($_GET['SAMLRequest'])) {
+ SimpleSAML_Logger::debug('SAML2.0 - IdP.SingleLogoutService: Got SAML reuqest');
+
$binding = new SimpleSAML_Bindings_SAML20_HTTPRedirect($config, $metadata);
try {
@@ -58,16 +61,9 @@ if (isset($_GET['SAMLRequest'])) {
}
} catch(Exception $exception) {
-
- $et = new SimpleSAML_XHTML_Template($config, 'error.php');
-
- $et->data['header'] = 'Error in received logout request';
- $et->data['message'] = 'An error occured when trying to read logout request.';
- $et->data['e'] = $exception;
-
- $et->show();
- exit(0);
-
+
+ SimpleSAML_Utilities::fatalError($session->getTrackID(), 'LOGOUTREQUEST', $exception);
+
}
// Extract some parameters from the logout request
@@ -125,6 +121,8 @@ if (isset($_GET['SAMLRequest'])) {
$requestcache['RelayState'] = $relaystate;
$session->setLogoutRequest($requestcache);
+ SimpleSAML_Logger::debug('SAML2.0 - IDP.SingleLogoutService: Setting cached request with issuer ' . $logoutrequest->getIssuer());
+
$session->set_sp_logout_completed($logoutrequest->getIssuer() );
@@ -133,26 +131,23 @@ if (isset($_GET['SAMLRequest'])) {
*/
} elseif (isset($_GET['SAMLResponse'])) {
+ SimpleSAML_Logger::debug('SAML2.0 - IdP.SingleLogoutService: Got SAML response');
+
$binding = new SimpleSAML_Bindings_SAML20_HTTPRedirect($config, $metadata);
try {
$loginresponse = $binding->decodeLogoutResponse($_GET);
+
+ SimpleSAML_Logger::debug('SAML2.0 - IdP.SingleLogoutService: SAML response parsed. Issuer is: ' . $loginresponse->getIssuer());
- if ($binding->validateQuery($loginresponse->getIssuer(),'SP','SAMLResponse')) {
+ if ($binding->validateQuery($loginresponse->getIssuer(),'IdP','SAMLResponse')) {
SimpleSAML_Logger::info('SAML2.0 - IDP.SingleLogoutService: Valid signature found');
}
} catch(Exception $exception) {
- $et = new SimpleSAML_XHTML_Template($config, 'error.php');
-
- $et->data['header'] = 'Error in received logout response';
- $et->data['message'] = 'An error occured when trying to read logout response.';
- $et->data['e'] = $exception;
-
- $et->show();
- exit(0);
+ SimpleSAML_Utilities::fatalError($session->getTrackID(), 'LOGOUTRESPONSE', $exception);
}
@@ -163,6 +158,7 @@ if (isset($_GET['SAMLRequest'])) {
SimpleSAML_Logger::info('SAML2.0 - IDP.SingleLogoutService: got LogoutResponse from ' . $loginresponse->getIssuer());
} else {
+ SimpleSAML_Logger::debug('SAML2.0 - IdP.SingleLogoutService: No request or response...');
/**
* This error message was removed 2008-02-27, because it interrupts with bridged SLO.
*
@@ -170,15 +166,38 @@ if (isset($_GET['SAMLRequest'])) {
*/
}
+$lookformore = true;
+$spentityid = null;
+do {
+ /* Dump the current sessions (for debugging). */
+ $session->dump_sp_sessions();
-/* Dump the current sessions (for debugging). */
-$session->dump_sp_sessions();
+ /*
+ * We proceed to send logout requests to all remaining SPs.
+ */
+ $spentityid = $session->get_next_sp_logout();
+
+
+ // If there are no more SPs left, then we will not look for more SPs.
+ if (empty($spentityid)) $lookformore = false;
+
+ try {
+ $spmetadata = $metadata->getMetadata($spentityid, 'saml20-sp-remote');
+ } catch (Exception $e) {
+ continue;
+ }
+
+ // If the SP we found have an SingleLogout endpoint then we will use it, and
+ // hence we do not need to look for more yet.
+ if (array_key_exists('SingleLogoutService', $spmetadata) &&
+ !empty($spmetadata['SingleLogoutService']) ) $lookformore = false;
+
+ if ($lookformore)
+ SimpleSAML_Logger::info('SAML2.0 - IDP.SingleLogoutService: Will not logout from ' . $spentityid . ' looking for more SPs');
+
+} while ($lookformore);
-/*
- * We proceed to send logout requests to all remaining SPs.
- */
-$spentityid = $session->get_next_sp_logout();
if ($spentityid) {
SimpleSAML_Logger::info('SAML2.0 - IDP.SingleLogoutService: Logout next SP ' . $spentityid);
@@ -203,14 +222,8 @@ if ($spentityid) {
} catch(Exception $exception) {
- $et = new SimpleSAML_XHTML_Template($config, 'error.php');
-
- $et->data['header'] = 'Error sending logout request to service';
- $et->data['message'] = 'Some error occured when trying to issue the logout response, and send it to the SP.';
- $et->data['e'] = $exception;
+ SimpleSAML_Utilities::fatalError($session->getTrackID(), 'GENERATELOGOUTREQUEST', $exception);
- $et->show();
- exit(0);
}
@@ -252,6 +265,7 @@ try {
throw new Exception('Could not get reference to the logout request.');
}
+ SimpleSAML_Logger::debug('SAML2.0 - IdP.SingleLogoutService: Found request cache with these keys: ' . join(',', array_keys($requestcache)));
/**
* Clean up session object to save storage.
@@ -265,32 +279,44 @@ try {
SimpleSAML_Logger::info('SAML2.0 - IdP.SingleLogoutService: Session Size after cleaning: ' . $session->getSize());
- /**
- * Create a Logot Response.
+ /*
+ * Check if the Single Logout procedure is initated by an SP (alternatively IdP initiated SLO
*/
- $rg = new SimpleSAML_XML_SAML20_LogoutResponse($config, $metadata);
-
- // generate($issuer, $receiver, $inresponseto, $mode )
- $logoutResponseXML = $rg->generate($idpentityid, $requestcache['Issuer'], $requestcache['RequestID'], 'IdP');
-
- // Create a HTTP-REDIRECT Binding.
- $httpredirect = new SimpleSAML_Bindings_SAML20_HTTPRedirect($config, $metadata);
-
- // Find the relaystate if cached.
- $relayState = isset($requestcache['RelayState']) ? $requestcache['RelayState'] : null;
+ if (array_key_exists('Issuer', $requestcache)) {
+
+ /**
+ * Create a Logot Response.
+ */
+ $rg = new SimpleSAML_XML_SAML20_LogoutResponse($config, $metadata);
+
+ // generate($issuer, $receiver, $inresponseto, $mode )
+ $logoutResponseXML = $rg->generate($idpentityid, $requestcache['Issuer'], $requestcache['RequestID'], 'IdP');
+
+ // Create a HTTP-REDIRECT Binding.
+ $httpredirect = new SimpleSAML_Bindings_SAML20_HTTPRedirect($config, $metadata);
+
+ // Find the relaystate if cached.
+ $relayState = isset($requestcache['RelayState']) ? $requestcache['RelayState'] : null;
+
+ // Parameters: $request, $remoteentityid, $relayState = null, $endpoint = 'SingleLogoutService', $direction = 'SAMLRequest', $mode = 'SP'
+ $httpredirect->sendMessage($logoutResponseXML, $idpentityid, $requestcache['Issuer'], $relayState, 'SingleLogoutService', 'SAMLResponse', 'IdP');
+ exit;
+
+ } elseif (array_key_exists('RelayState', $requestcache)) {
- // Parameters: $request, $remoteentityid, $relayState = null, $endpoint = 'SingleLogoutService', $direction = 'SAMLRequest', $mode = 'SP'
- $httpredirect->sendMessage($logoutResponseXML, $idpentityid, $requestcache['Issuer'], $relayState, 'SingleLogoutService', 'SAMLResponse', 'IdP');
+ SimpleSAML_Utilities::redirect($requestcache['RelayState']);
+ exit;
+
+ } else {
+
+ echo 'You are logged out'; exit;
+
+ }
} catch(Exception $exception) {
-
- $et = new SimpleSAML_XHTML_Template($config, 'error.php');
-
- $et->data['header'] = 'Error sending response to service';
- $et->data['message'] = 'Some error occured when trying to issue the logout response, and send it to the SP.';
- $et->data['e'] = $exception;
-
- $et->show();
+
+ SimpleSAML_Utilities::fatalError($session->getTrackID(), 'GENERATELOGOUTRESPONSE', $exception);
+
}
-?>
\ No newline at end of file
+
diff --git a/www/saml2/idp/initSLO.php b/www/saml2/idp/initSLO.php
new file mode 100644
index 000000000..e3eec8d4b
--- /dev/null
+++ b/www/saml2/idp/initSLO.php
@@ -0,0 +1,153 @@
+<?php
+
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . '../../../www/_include.php');
+
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Utilities.php');
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Session.php');
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Logger.php');
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Metadata/MetaDataStorageHandler.php');
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/XML/SAML20/LogoutRequest.php');
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/XML/SAML20/LogoutResponse.php');
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Bindings/SAML20/HTTPRedirect.php');
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/XHTML/Template.php');
+
+
+$config = SimpleSAML_Configuration::getInstance();
+$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
+$session = SimpleSAML_Session::getInstance();
+
+
+SimpleSAML_Logger::info('SAML2.0 - IdP.initSLO: Accessing SAML 2.0 IdP endpoint init Single Logout');
+
+if (!$config->getValue('enable.saml20-idp', false))
+ SimpleSAML_Utilities::fatalError(isset($session) ? $session->getTrackID() : null, 'NOACCESS');
+
+try {
+ $idpentityid = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted');
+} catch (Exception $exception) {
+ SimpleSAML_Utilities::fatalError($session->getTrackID(), 'METADATA', $exception);
+}
+
+
+/**
+ * If we get an incomming LogoutRequest then we initiate the logout process.
+ * in this case an SAML 2.0 SP is sending an request, which also is referred to as
+ * SP initiated Single Logout.
+ *
+ */
+if (isset($_GET['RelayState'])) {
+
+ $relaystate = $_GET['RelayState'];
+
+ /**
+ * No session exists. Just go to the RelayState.
+ */
+ if($session === NULL) {
+ SimpleSAML_Logger::info('SAML2.0 - IdP.initSLO: Did not find a session here, so we redirect to the RelayState');
+ SimpleSAML_Utilities::redirect($relaystate);
+ exit;
+ }
+
+ // Set local IdP session to invalid.
+ $session->setAuthenticated(false, $session->getAuthority() );
+
+
+ /*
+ * Create an assoc array of the request to store in the session cache.
+ */
+ $requestcache = array(
+ 'RelayState' => $relaystate
+ );
+
+ $session->setLogoutRequest($requestcache);
+ SimpleSAML_Logger::debug('SAML2.0 - IDP.initSSO: Setting cached request with relay state ' . $relaystate);
+
+ //$session->set_sp_logout_completed($logoutrequest->getIssuer() );
+
+
+}
+
+$lookformore = true;
+$spentityid = null;
+do {
+ /* Dump the current sessions (for debugging). */
+ $session->dump_sp_sessions();
+
+ /*
+ * We proceed to send logout requests to all remaining SPs.
+ */
+ $spentityid = $session->get_next_sp_logout();
+
+
+ // If there are no more SPs left, then we will not look for more SPs.
+ if (empty($spentityid)) $lookformore = false;
+
+ try {
+ $spmetadata = $metadata->getMetadata($spentityid, 'saml20-sp-remote');
+ } catch (Exception $e) {
+ continue;
+ }
+
+ // If the SP we found have an SingleLogout endpoint then we will use it, and
+ // hence we do not need to look for more yet.
+ if (array_key_exists('SingleLogoutService', $spmetadata) &&
+ !empty($spmetadata['SingleLogoutService']) ) $lookformore = false;
+
+ if ($lookformore)
+ SimpleSAML_Logger::info('SAML2.0 - IDP.SingleLogoutService: Will not logout from ' . $spentityid . ' looking for more SPs');
+
+} while ($lookformore);
+
+
+
+/*
+ * We proceed to send logout requests to the first remaining SP.
+ */
+$spentityid = $session->get_next_sp_logout();
+if ($spentityid) {
+
+ SimpleSAML_Logger::info('SAML2.0 - IDP.SingleLogoutService: Logout next SP ' . $spentityid);
+
+ try {
+ $lr = new SimpleSAML_XML_SAML20_LogoutRequest($config, $metadata);
+
+ // ($issuer, $receiver, $nameid, $nameidformat, $sessionindex, $mode) {
+ $req = $lr->generate($idpentityid, $spentityid, $session->getNameID(), $session->getSessionIndex(), 'IdP');
+
+ $httpredirect = new SimpleSAML_Bindings_SAML20_HTTPRedirect($config, $metadata);
+
+ $relayState = SimpleSAML_Utilities::selfURL();
+ if (isset($_GET['RelayState'])) {
+ $relayState = $_GET['RelayState'];
+ }
+
+ //$request, $remoteentityid, $relayState = null, $endpoint = 'SingleLogoutService', $direction = 'SAMLRequest', $mode = 'SP'
+ $httpredirect->sendMessage($req, $idpentityid, $spentityid, $relayState, 'SingleLogoutService', 'SAMLRequest', 'IdP');
+
+ exit();
+
+ } catch(Exception $exception) {
+
+ SimpleSAML_Utilities::fatalError($session->getTrackID(), 'GENERATELOGOUTREQUEST', $exception);
+
+ }
+
+}
+
+if ($config->getValue('debug', false))
+ SimpleSAML_Logger::info('SAML2.0 - IdP.SingleLogoutService: LogoutService: All SPs done ');
+
+
+if (isset($_GET['RelayState'])) {
+
+ $relayState = $_GET['RelayState'];
+ SimpleSAML_Utilities::redirect($relayState);
+
+} else {
+
+ SimpleSAML_Utilities::fatalError($session->getTrackID(), 'NORELAYSTATE');
+
+}
+
+
+
--
GitLab