diff --git a/www/saml2/idp/SingleLogoutService.php b/www/saml2/idp/SingleLogoutService.php index e717ea62e51af68aa301c43234c85cd96b842c23..6b55c7cad82ac127d9ecbf7fb20e2ee0a27e865a 100644 --- a/www/saml2/idp/SingleLogoutService.php +++ b/www/saml2/idp/SingleLogoutService.php @@ -23,18 +23,16 @@ if (!$config->getValue('enable.saml20-idp', false)) SimpleSAML_Utilities::fatalError(isset($session) ? $session->getTrackID() : null, 'NOACCESS'); try { - $idpentityid = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted'); + $idpEntityId = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted'); + $idpMetadata = $metadata->getMetaDataConfig($idpEntityId, 'saml20-idp-hosted'); } catch (Exception $exception) { SimpleSAML_Utilities::fatalError($session->getTrackID(), 'METADATA', $exception); } -SimpleSAML_Logger::debug('SAML2.0 - IdP.SingleLogoutService: Got IdP entity id: ' . $idpentityid); +SimpleSAML_Logger::debug('SAML2.0 - IdP.SingleLogoutService: Got IdP entity id: ' . $idpEntityId); -$logouttype = 'traditional'; -$idpmeta = $metadata->getMetaDataCurrent('saml20-idp-hosted'); -if (array_key_exists('logouttype', $idpmeta)) $logouttype = $idpmeta['logouttype']; - +$logouttype = $idpMetadata->getString('logouttype', 'traditional'); if ($logouttype !== 'traditional') SimpleSAML_Utilities::fatalError($session->getTrackID(), 'NOACCESS', new Exception('This IdP is configured to use logout type [' . $logouttype . '], but this endpoint is only available for IdP using logout type [traditional]')); @@ -87,114 +85,84 @@ function saveLogoutInfo($id) { * SP initiated Single Logout. * */ -if (isset($_GET['SAMLRequest'])) { +if (isset($_REQUEST['SAMLRequest'])) { SimpleSAML_Logger::debug('SAML2.0 - IdP.SingleLogoutService: Got SAML reuqest'); - $binding = new SimpleSAML_Bindings_SAML20_HTTPRedirect($config, $metadata); + $binding = SAML2_Binding::getCurrentBinding(); try { - $logoutrequest = $binding->decodeLogoutRequest($_GET); + $logoutRequest = $binding->receive(); + if (!($logoutRequest instanceof SAML2_LogoutRequest)) { + throw new Exception('Received a request which wasn\'t a LogoutRequest ' . + 'on logout endpoint. Was: ' . get_class($logoutRequest)); + } - if ($binding->validateQuery($logoutrequest->getIssuer(),'IdP')) { - SimpleSAML_Logger::info('SAML2.0 - IdP.SingleLogoutService: Valid signature found for '.$logoutrequest->getRequestID()); + $spEntityId = $logoutRequest->getIssuer(); + if ($spEntityId === NULL) { + throw new Exception('Missing issuer in logout reqeust.'); } + $spMetadata = $metadata->getMetadataConfig($spEntityId, 'saml20-sp-remote'); + + sspmod_saml2_Message::validateMessage($spMetadata, $idpMetadata, $logoutRequest); + } catch(Exception $exception) { - SimpleSAML_Utilities::fatalError($session->getTrackID(), 'LOGOUTREQUEST', $exception); - - } - - // Extract some parameters from the logout request - #$requestid = $logoutrequest->getRequestID(); - $requester = $logoutrequest->getIssuer(); - #$relayState = $logoutrequest->getRelayState(); - - //$responder = $config->getValue('saml2-hosted-sp'); - $responder = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted'); - - - SimpleSAML_Logger::info('SAML2.0 - IdP.SingleLogoutService: got Logoutrequest from ' . $requester); - SimpleSAML_Logger::stats('saml20-idp-SLO spinit ' . $requester . ' ' . $responder); - - /* Check if we have a valid session. */ - if($session === NULL) { - - /* Invalid session. To prevent the user from being unable to - * log out from the service provider, we should just return a - * LogoutResponse pretending that the logout was successful to - * the SP that sent the LogoutRequest. - */ - - SimpleSAML_Logger::info('SAML2.0 - IdP.SingleLogoutService: Did not find a session here, but we are returning a LogoutResponse anyway.'); - - $spentityid = $logoutrequest->getIssuer(); - - /* Generate the response. */ - $response = new SimpleSAML_XML_SAML20_LogoutResponse($config, $metadata); - $responseText = $response->generate($idpentityid, $spentityid, $logoutrequest->getRequestID(), 'IdP'); - - /* Retrieve the relay state from the request. */ - $relayState = $logoutrequest->getRelayState(); - - /* Send the response using the HTTP-Redirect binding. */ - $binding = new SimpleSAML_Bindings_SAML20_HTTPRedirect($config, - $metadata); - $binding->sendMessage($responseText, $idpentityid, $spentityid, $relayState, - 'SingleLogoutService', 'SAMLResponse', 'IdP'); - exit; } + SimpleSAML_Logger::info('SAML2.0 - IdP.SingleLogoutService: got Logoutrequest from ' . $spEntityId); + SimpleSAML_Logger::stats('saml20-idp-SLO spinit ' . $spEntityId . ' ' . $idpEntityId); $session->doLogout(); /* Fill in the $logoutInfo associative array with information about this logout request. */ - $logoutInfo['Issuer'] = $logoutrequest->getIssuer(); - $logoutInfo['RequestID'] = $logoutrequest->getRequestID(); + $logoutInfo['Issuer'] = $spEntityId; + $logoutInfo['RequestID'] = $logoutRequest->getId(); - $relayState = $logoutrequest->getRelayState(); - if($relayState !== NULL) { - $logoutInfo['RelayState'] = $relayState; - } + $logoutInfo['RelayState'] = $logoutRequest->getRelayState(); + + SimpleSAML_Logger::debug('SAML2.0 - IDP.SingleLogoutService: Setting cached request with issuer ' . $spEntityId); + + $session->set_sp_logout_completed($spEntityId); - - SimpleSAML_Logger::debug('SAML2.0 - IDP.SingleLogoutService: Setting cached request with issuer ' . $logoutrequest->getIssuer()); - - $session->set_sp_logout_completed($logoutrequest->getIssuer() ); - /* * We receive a Logout Response to a Logout Request that we have issued earlier. */ -} elseif (isset($_GET['SAMLResponse'])) { +} elseif (isset($_REQUEST['SAMLResponse'])) { SimpleSAML_Logger::debug('SAML2.0 - IdP.SingleLogoutService: Got SAML response'); - $binding = new SimpleSAML_Bindings_SAML20_HTTPRedirect($config, $metadata); + $binding = SAML2_Binding::getCurrentBinding(); try { - $loginresponse = $binding->decodeLogoutResponse($_GET); - - SimpleSAML_Logger::debug('SAML2.0 - IdP.SingleLogoutService: SAML response parsed. Issuer is: ' . $loginresponse->getIssuer()); + $logoutResponse = $binding->receive(); + if (!($logoutResponse instanceof SAML2_LogoutResponse)) { + throw new Exception('Received a response which wasn\'t a LogoutResponse ' . + 'on logout endpoint. Was: ' . get_class($logoutResponse)); + } - if ($binding->validateQuery($loginresponse->getIssuer(),'IdP','SAMLResponse')) { - SimpleSAML_Logger::info('SAML2.0 - IDP.SingleLogoutService: Valid signature found'); + $spEntityId = $logoutResponse->getIssuer(); + if ($spEntityId === NULL) { + throw new Exception('Missing issuer in logout response.'); } + SimpleSAML_Logger::debug('SAML2.0 - IdP.SingleLogoutService: SAML response parsed. Issuer is: ' . $spEntityId); + $spMetadata = $metadata->getMetadataConfig($spEntityId, 'saml20-sp-remote'); - } catch(Exception $exception) { + sspmod_saml2_Message::validateMessage($spMetadata, $idpMetadata, $logoutResponse); + } catch(Exception $exception) { SimpleSAML_Utilities::fatalError($session->getTrackID(), 'LOGOUTRESPONSE', $exception); - } /* Fetch the $logoutInfo variable based on the InResponseTo attribute of the response. */ - fetchLogoutInfo($loginresponse->getInResponseTo()); + fetchLogoutInfo($logoutResponse->getInResponseTo()); - $session->set_sp_logout_completed($loginresponse->getIssuer()); + $session->set_sp_logout_completed($spEntityId); - SimpleSAML_Logger::info('SAML2.0 - IDP.SingleLogoutService: got LogoutResponse from ' . $loginresponse->getIssuer()); + SimpleSAML_Logger::info('SAML2.0 - IDP.SingleLogoutService: got LogoutResponse from ' . $spEntityId); } elseif(array_key_exists('LogoutID', $_GET)) { /* This is a response from bridged SLO. */ @@ -216,70 +184,74 @@ if (isset($_GET['SAMLRequest'])) { SimpleSAML_Utilities::fatalError($session->getTrackID(), 'SLOSERVICEPARAMS'); } -$lookformore = true; -$spentityid = null; -do { +/* + * Find the next SP we should log out from. We will search through the list of + * SPs until we find a valid SP with a SingleLogoutService endpoint. + */ +while (TRUE) { /* 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(); - - + $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; - + if (empty($spEntityId)) { + break; + } + try { - $spmetadata = $metadata->getMetadata($spentityid, 'saml20-sp-remote'); + $spMetadata = $metadata->getMetadataConfig($spEntityId, 'saml20-sp-remote'); } catch (Exception $e) { + /* It seems that an SP has disappeared from the metadata between login and logout. */ + SimpleSAML_Logger::info('SAML2.0 - IDP.SingleLogoutService: Missing metadata for ' . + $spEntityId . '; looking for more SPs.'); + continue; + } + + $singleLogoutService = $spMetadata->getString('SingleLogoutService', NULL); + if ($singleLogoutService === NULL) { + SimpleSAML_Logger::info('SAML2.0 - IDP.SingleLogoutService: No SingleLogoutService for ' . + $spEntityId . '; looking for more SPs.'); 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); + /* $spEntityId now contains the next SP. */ + break; +} -if ($spentityid) { +if ($spEntityId) { - SimpleSAML_Logger::info('SAML2.0 - IDP.SingleLogoutService: Logout next SP ' . $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) { - $nameId = $session->getSessionNameId('saml20-sp-remote', $spentityid); + $nameId = $session->getSessionNameId('saml20-sp-remote', $spEntityId); if($nameId === NULL) { $nameId = $session->getNameID(); } - $req = $lr->generate($idpentityid, $spentityid, $nameId, $session->getSessionIndex(), 'IdP'); - - /* Save the $logoutInfo until we return from the SP. */ - saveLogoutInfo($lr->getGeneratedID()); + /* Convert to new-style NameId format. */ + $nameId['Value'] = $nameId['value']; + unset($nameId['value']); - $httpredirect = new SimpleSAML_Bindings_SAML20_HTTPRedirect($config, $metadata); + $lr = sspmod_saml2_Message::buildLogoutRequest($idpMetadata, $spMetadata); + $lr->setSessionIndex($session->getSessionIndex()); + $lr->setNameId($nameId); - //$request, $remoteentityid, $relayState = null, $endpoint = 'SingleLogoutService', $direction = 'SAMLRequest', $mode = 'SP' - $httpredirect->sendMessage($req, $idpentityid, $spentityid, NULL, 'SingleLogoutService', 'SAMLRequest', 'IdP'); + /* Save the $logoutInfo until we return from the SP. */ + saveLogoutInfo($lr->getId()); - exit(); + $binding = new SAML2_HTTPRedirect(); + $binding->setDestination(sspmod_SAML2_Message::getDebugDestination()); + $binding->send($lr); } catch(Exception $exception) { - SimpleSAML_Utilities::fatalError($session->getTrackID(), 'GENERATELOGOUTREQUEST', $exception); - } - - } if ($config->getValue('debug', false)) @@ -323,59 +295,45 @@ try { if(!$logoutInfo) { SimpleSAML_Utilities::fatalError($session->getTrackID(), 'LOGOUTINFOLOST'); } - + SimpleSAML_Logger::debug('SAML2.0 - IdP.SingleLogoutService: Found logout info with these keys: ' . join(',', array_keys($logoutInfo))); - + /** * Clean up session object to save storage. */ - if ($config->getValue('debug', false)) + if ($config->getValue('debug', false)) SimpleSAML_Logger::info('SAML2.0 - IdP.SingleLogoutService: Session Size before cleaning: ' . $session->getSize()); - + $session->clean(); - - if ($config->getValue('debug', false)) + + if ($config->getValue('debug', false)) SimpleSAML_Logger::info('SAML2.0 - IdP.SingleLogoutService: Session Size after cleaning: ' . $session->getSize()); - - + + /* * Check if the Single Logout procedure is initated by an SP (alternatively IdP initiated SLO */ if (array_key_exists('Issuer', $logoutInfo)) { - - /** - * Create a Logot Response. - */ - $rg = new SimpleSAML_XML_SAML20_LogoutResponse($config, $metadata); - - // generate($issuer, $receiver, $inresponseto, $mode ) - $logoutResponseXML = $rg->generate($idpentityid, $logoutInfo['Issuer'], $logoutInfo['RequestID'], 'IdP'); - - // Create a HTTP-REDIRECT Binding. - $httpredirect = new SimpleSAML_Bindings_SAML20_HTTPRedirect($config, $metadata); - - // Find the relaystate if cached. - $relayState = isset($logoutInfo['RelayState']) ? $logoutInfo['RelayState'] : null; - - // Parameters: $request, $remoteentityid, $relayState = null, $endpoint = 'SingleLogoutService', $direction = 'SAMLRequest', $mode = 'SP' - $httpredirect->sendMessage($logoutResponseXML, $idpentityid, $logoutInfo['Issuer'], $relayState, 'SingleLogoutServiceResponse', 'SAMLResponse', 'IdP'); - exit; - + + $spEntityId = $logoutInfo['Issuer']; + $spMetadata = $metadata->getMetadataConfig($spEntityId, 'saml20-sp-remote'); + + $lr = sspmod_saml2_Message::buildLogoutResponse($idpMetadata, $spMetadata); + $lr->setInResponseTo($logoutInfo['RequestID']); + $lr->setRelayState($logoutInfo['RelayState']); + $binding = new SAML2_HTTPRedirect(); + $binding->setDestination(sspmod_SAML2_Message::getDebugDestination()); + $binding->send($lr); + } elseif (array_key_exists('RelayState', $logoutInfo)) { SimpleSAML_Utilities::redirect($logoutInfo['RelayState']); exit; - + } else { - echo 'You are logged out'; exit; - } } catch(Exception $exception) { - SimpleSAML_Utilities::fatalError($session->getTrackID(), 'GENERATELOGOUTRESPONSE', $exception); - } - -