1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
|
<?php
/**
* Logout endpoint handler for SAML SP authentication client.
*
* This endpoint handles both logout requests and logout responses.
*/
if (!array_key_exists('PATH_INFO', $_SERVER)) {
throw new SimpleSAML_Error_BadRequest('Missing authentication source ID in logout URL');
}
$sourceId = substr($_SERVER['PATH_INFO'], 1);
$source = SimpleSAML_Auth_Source::getById($sourceId);
if ($source === null) {
throw new Exception('Could not find authentication source with id ' . $sourceId);
}
if (!($source instanceof sspmod_saml_Auth_Source_SP)) {
throw new SimpleSAML_Error_Exception('Source type changed?');
}
try {
$binding = \SAML2\Binding::getCurrentBinding();
} catch (Exception $e) { // TODO: look for a specific exception
// This is dirty. Instead of checking the message of the exception, \SAML2\Binding::getCurrentBinding() should throw
// an specific exception when the binding is unknown, and we should capture that here
if ($e->getMessage() === 'Unable to find the current binding.') {
throw new SimpleSAML_Error_Error('SLOSERVICEPARAMS', $e, 400);
} else {
throw $e; // do not ignore other exceptions!
}
}
$message = $binding->receive();
$idpEntityId = $message->getIssuer();
if ($idpEntityId === null) {
// Without an issuer we have no way to respond to the message.
throw new SimpleSAML_Error_BadRequest('Received message on logout endpoint without issuer.');
}
$spEntityId = $source->getEntityId();
$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
$idpMetadata = $source->getIdPMetadata($idpEntityId);
$spMetadata = $source->getMetadata();
sspmod_saml_Message::validateMessage($idpMetadata, $spMetadata, $message);
$destination = $message->getDestination();
if ($destination !== null && $destination !== \SimpleSAML\Utils\HTTP::getSelfURLNoQuery()) {
throw new SimpleSAML_Error_Exception('Destination in logout message is wrong.');
}
if ($message instanceof \SAML2\LogoutResponse) {
$relayState = $message->getRelayState();
if ($relayState === null) {
// Somehow, our RelayState has been lost.
throw new SimpleSAML_Error_BadRequest('Missing RelayState in logout response.');
}
if (!$message->isSuccess()) {
SimpleSAML\Logger::warning('Unsuccessful logout. Status was: ' . sspmod_saml_Message::getResponseError($message));
}
$state = SimpleSAML_Auth_State::loadState($relayState, 'saml:slosent');
$state['saml:sp:LogoutStatus'] = $message->getStatus();
SimpleSAML_Auth_Source::completeLogout($state);
} elseif ($message instanceof \SAML2\LogoutRequest) {
SimpleSAML\Logger::debug('module/saml2/sp/logout: Request from ' . $idpEntityId);
SimpleSAML\Logger::stats('saml20-idp-SLO idpinit ' . $spEntityId . ' ' . $idpEntityId);
if ($message->isNameIdEncrypted()) {
try {
$keys = sspmod_saml_Message::getDecryptionKeys($idpMetadata, $spMetadata);
} catch (Exception $e) {
throw new SimpleSAML_Error_Exception('Error decrypting NameID: ' . $e->getMessage());
}
$blacklist = sspmod_saml_Message::getBlacklistedAlgorithms($idpMetadata, $spMetadata);
$lastException = null;
foreach ($keys as $i => $key) {
try {
$message->decryptNameId($key, $blacklist);
SimpleSAML\Logger::debug('Decryption with key #' . $i . ' succeeded.');
$lastException = null;
break;
} catch (Exception $e) {
SimpleSAML\Logger::debug('Decryption with key #' . $i . ' failed with exception: ' . $e->getMessage());
$lastException = $e;
}
}
if ($lastException !== null) {
throw $lastException;
}
}
$nameId = $message->getNameId();
$sessionIndexes = $message->getSessionIndexes();
$numLoggedOut = sspmod_saml_SP_LogoutStore::logoutSessions($sourceId, $nameId, $sessionIndexes);
if ($numLoggedOut === false) {
/* This type of logout was unsupported. Use the old method. */
$source->handleLogout($idpEntityId);
$numLoggedOut = count($sessionIndexes);
}
/* Create and send response. */
$lr = sspmod_saml_Message::buildLogoutResponse($spMetadata, $idpMetadata);
$lr->setRelayState($message->getRelayState());
$lr->setInResponseTo($message->getId());
if ($numLoggedOut < count($sessionIndexes)) {
SimpleSAML\Logger::warning('Logged out of ' . $numLoggedOut . ' of ' . count($sessionIndexes) . ' sessions.');
}
$dst = $idpMetadata->getEndpointPrioritizedByBinding('SingleLogoutService', array(
\SAML2\Constants::BINDING_HTTP_REDIRECT,
\SAML2\Constants::BINDING_HTTP_POST)
);
if (!$binding instanceof \SAML2\SOAP) {
$binding = \SAML2\Binding::getBinding($dst['Binding']);
if (isset($dst['ResponseLocation'])) {
$dst = $dst['ResponseLocation'];
} else {
$dst = $dst['Location'];
}
$binding->setDestination($dst);
}
$lr->setDestination($dst);
$binding->send($lr);
} else {
throw new SimpleSAML_Error_BadRequest('Unknown message received on logout endpoint: ' . get_class($message));
}
|