diff --git a/.extlib/simplesamlphp/lib/SimpleSAML/Session.php b/.extlib/simplesamlphp/lib/SimpleSAML/Session.php index 0c9c6c508..6167517a3 100644 --- a/.extlib/simplesamlphp/lib/SimpleSAML/Session.php +++ b/.extlib/simplesamlphp/lib/SimpleSAML/Session.php @@ -738,6 +738,7 @@ private function callLogoutHandlers(string $authority): void if (empty($this->authData[$authority]['LogoutHandlers'])) { return; } + foreach ($this->authData[$authority]['LogoutHandlers'] as $handler) { // verify that the logout handler is a valid function if (!is_callable($handler)) { @@ -746,10 +747,9 @@ private function callLogoutHandlers(string $authority): void throw new \Exception( 'Logout handler is not a valid function: ' . $classname . '::' . - $functionname + $functionname ); } - // call the logout handler call_user_func($handler); } diff --git a/.extlib/simplesamlphp/modules/saml/www/sp/saml2-logout.php b/.extlib/simplesamlphp/modules/saml/www/sp/saml2-logout.php index 9b756d8d6..25ad8a7ef 100644 --- a/.extlib/simplesamlphp/modules/saml/www/sp/saml2-logout.php +++ b/.extlib/simplesamlphp/modules/saml/www/sp/saml2-logout.php @@ -130,6 +130,7 @@ $dst = $idpMetadata->getEndpointPrioritizedByBinding( 'SingleLogoutService', [ + \SAML2\Constants::BINDING_SOAP, \SAML2\Constants::BINDING_HTTP_REDIRECT, \SAML2\Constants::BINDING_HTTP_POST ] @@ -143,8 +144,9 @@ $dst = $dst['Location']; } $binding->setDestination($dst); + } else { + $lr->setDestination($dst['Location']); } - $lr->setDestination($dst); $binding->send($lr); } else { diff --git a/classes/api.php b/classes/api.php index ab522f80a..2de001dfb 100644 --- a/classes/api.php +++ b/classes/api.php @@ -39,6 +39,18 @@ public static function logout_from_idp_front_channel(): void { require_logout(); } + public static function logout_from_idp_back_channel(): void + { + global $DB, $sp_sessionId; + + if (isset($sp_sessionId)) { + $DB->delete_records('auth_saml2_kvstore', array('k' => $sp_sessionId)); + $session = \SimpleSAML\Session::getSession($sp_sessionId); + \core\session\manager::kill_session($session->moodle_session_id); + } + + } + /** * SP logout callback. Called in case of normal Moodle logout. * {@see auth::logoutpage_hook} diff --git a/classes/auth.php b/classes/auth.php index 9bc49196e..32c822d5b 100644 --- a/classes/auth.php +++ b/classes/auth.php @@ -726,6 +726,12 @@ public function saml_login_complete($attributes) { $USER->site = $CFG->wwwroot; set_moodle_cookie($USER->username); + $moodle_session_id = session_id(); + $saml_session = \SimpleSAML\Session::getSessionFromRequest(); + $saml_session->moodle_session_id = $moodle_session_id; + $saml_session_handler = \SimpleSAML\SessionHandler::getSessionHandler(); + $saml_session_handler->saveSession($saml_session); + $wantsurl = core_login_get_return_url(); // If we are not on the page we want, then redirect to it (unless this is CLI). if ( qualified_me() !== false && qualified_me() !== $wantsurl ) { diff --git a/sp/saml2-logout.php b/sp/saml2-logout.php index 6777e4162..40a90f29e 100644 --- a/sp/saml2-logout.php +++ b/sp/saml2-logout.php @@ -54,6 +54,73 @@ // user out in Moodle. if (!is_null($session->getAuthState($saml2auth->spname))) { $session->registerLogoutHandler($saml2auth->spname, '\auth_saml2\api', 'logout_from_idp_front_channel'); + } else { + // check if binding message exists and is logout request + 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(); + if ($message instanceof \SAML2\LogoutRequest) { + $nameId = $message->getNameId(); + $sessionIndexes = $message->getSessionIndexes(); + + // Getting session from $nameId and $sessionIndexes + $authId = $saml2auth->spname; + + assert(is_string($authId)); + + $store = \SimpleSAML\Store::getInstance(); + if ($store === false) { + // We don't have a datastore + // TODO throw error + } + + // serialize and anonymize the NameID + $strNameId = serialize($nameId); + $strNameId = sha1($strNameId); + + // Normalize SessionIndexes + foreach ($sessionIndexes as &$sessionIndex) { + assert(is_string($sessionIndex)); + if (strlen($sessionIndex) > 50) { + $sessionIndex = sha1($sessionIndex); + } + } + + // Remove reference + unset($sessionIndex); + + if ($store instanceof \SimpleSAML\Store\SQL) { + // TODO : ssp_sessions stored in db option + //$sessions = self::getSessionsSQL($store, $authId, $strNameId); + } else { + if (empty($sessionIndexes)) { + // We cannot fetch all sessions without a SQL store + return false; + } + + foreach ($sessionIndexes as $sessionIndex) { + $sessionId = $store->get('saml.LogoutStore', $strNameId . ':' . $sessionIndex); + if ($sessionId === null) { + continue; + } + assert(is_string($sessionId)); + $session = \SimpleSAML\Session::getSession($sessionId); + $session->registerLogoutHandler($authId, '\auth_saml2\api', 'logout_from_idp_back_channel'); + $sp_sessionId = $sessionId; + continue; // only registering first session... + } + } + } } require('../.extlib/simplesamlphp/modules/saml/www/sp/saml2-logout.php');