From b97b910e5ea80a151d42202a3545d62a3eb962e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20P=C3=A9rez=20Crespo?= <jaime.perez@uninett.no> Date: Tue, 13 Aug 2013 10:28:38 +0000 Subject: [PATCH] Complete bugfix for issue #561. HTTP-Post supported for SLO not initiated by the SP. Working for both traditional logout and iframe version. git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@3262 44740490-163a-0410-bde0-09ae8108e29a --- lib/SimpleSAML/IdP/LogoutTraditional.php | 3 +- modules/core/templates/logout-iframe.php | 12 ++- modules/core/www/idp/logout-iframe.php | 3 +- modules/core/www/idp/performlogout.php | 59 +++++++++++++ modules/saml/lib/IdP/SAML2.php | 105 +++++++++++++++++------ templates/includes/header-embed.php | 2 +- www/resources/default.css | 6 ++ 7 files changed, 157 insertions(+), 33 deletions(-) create mode 100644 modules/core/www/idp/performlogout.php diff --git a/lib/SimpleSAML/IdP/LogoutTraditional.php b/lib/SimpleSAML/IdP/LogoutTraditional.php index 5a4846608..202db7b33 100644 --- a/lib/SimpleSAML/IdP/LogoutTraditional.php +++ b/lib/SimpleSAML/IdP/LogoutTraditional.php @@ -29,8 +29,7 @@ class SimpleSAML_IdP_LogoutTraditional extends SimpleSAML_IdP_LogoutHandler { try { $idp = SimpleSAML_IdP::getByState($association); - $url = call_user_func(array($association['Handler'], 'getLogoutURL'), $idp, $association, $relayState); - SimpleSAML_Utilities::redirect($url); + call_user_func(array($association['Handler'], 'sendLogoutRequest'), $idp, $association, $relayState); } catch (Exception $e) { SimpleSAML_Logger::warning('Unable to initialize logout to ' . var_export($id, TRUE) . '.'); $this->idp->terminateAssociation($id); diff --git a/modules/core/templates/logout-iframe.php b/modules/core/templates/logout-iframe.php index fb31cdc04..d77213440 100644 --- a/modules/core/templates/logout-iframe.php +++ b/modules/core/templates/logout-iframe.php @@ -74,7 +74,10 @@ if ($type === 'embed') { } else { $this->includeAtTemplateBase('includes/header.php'); } - +?> +<div id="wrap"> + <div id="content"> +<?php if ($from !== NULL) { echo('<div><img style="float: left; margin-right: 12px" src="/' . $this->data['baseurlpath'] . 'resources/icons/checkmark.48x48.png" alt="Successful logout" />'); @@ -167,7 +170,6 @@ echo('<form method="post" action="logout-iframe-done.php" id="failed-form" targe echo('<input type="hidden" name="id" value="' . $id . '" />'); echo('<input type="submit" name="continue" value="' . $this->t('{logout:return}'). '" />'); echo('</form>'); - echo('</div>'); if ($nProgress == 0 && $nFailed == 0) { @@ -203,10 +205,14 @@ if ($type === 'js') { <?php } ?> - + </div> + <!-- #content --> <?php if ($type === 'embed') { $this->includeAtTemplateBase('includes/footer-embed.php'); } else { $this->includeAtTemplateBase('includes/footer.php'); } +?> +</div> +<!-- #wrap --> diff --git a/modules/core/www/idp/logout-iframe.php b/modules/core/www/idp/logout-iframe.php index 1b751e931..9d77bebe8 100644 --- a/modules/core/www/idp/logout-iframe.php +++ b/modules/core/www/idp/logout-iframe.php @@ -83,7 +83,8 @@ if ($type === 'js' || $type === 'nojs') { try { $assocIdP = SimpleSAML_IdP::getByState($sp); - $url = call_user_func(array($sp['Handler'], 'getLogoutURL'), $assocIdP, $sp, NULL); + $params = array('id' => $id, 'sp' => $sp['id'], 'type' => $type); + $url = SimpleSAML_Module::getModuleURL('core/idp/performlogout.php', $params); $sp['core:Logout-IFrame:URL'] = $url; } catch (Exception $e) { $sp['core:Logout-IFrame:State'] = 'failed'; diff --git a/modules/core/www/idp/performlogout.php b/modules/core/www/idp/performlogout.php new file mode 100644 index 000000000..6c17a1c9f --- /dev/null +++ b/modules/core/www/idp/performlogout.php @@ -0,0 +1,59 @@ +<?php + +if (!isset($_REQUEST['id'])) { + throw new SimpleSAML_Error_BadRequest('Missing id-parameter.'); +} +$id = (string)$_REQUEST['id']; +if (!isset($_REQUEST['sp'])) { + throw new SimpleSAML_Error_BadRequest('Missing sp-parameter.'); +} +$sp = urldecode($_REQUEST['sp']); +$type = @(string)$_REQUEST['type']; + +$state = SimpleSAML_Auth_State::loadState($id, 'core:Logout-IFrame'); +$idp = SimpleSAML_IdP::getByState($state); + +$associations = $state['core:Logout-IFrame:Associations']; +if (!isset($associations[$sp])) { + exit; +} + +$association = $associations[$sp]; + +$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler(); +$idpMetadata = $idp->getConfig(); +$spMetadata = $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote'); + +$lr = sspmod_saml_Message::buildLogoutRequest($idpMetadata, $spMetadata); +$lr->setSessionIndex($association['saml:SessionIndex']); +$lr->setNameId($association['saml:NameID']); + +$assertionLifetime = $spMetadata->getInteger('assertion.lifetime', NULL); +if ($assertionLifetime === NULL) { + $assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300); +} +$lr->setNotOnOrAfter(time() + $assertionLifetime); + +$encryptNameId = $spMetadata->getBoolean('nameid.encryption', NULL); +if ($encryptNameId === NULL) { + $encryptNameId = $idpMetadata->getBoolean('nameid.encryption', FALSE); +} +if ($encryptNameId) { + $lr->encryptNameId(sspmod_saml_Message::getEncryptionKey($spMetadata)); +} + +SimpleSAML_Stats::log('saml:idp:LogoutRequest:sent', array( + 'spEntityID' => $association['saml:entityID'], + 'idpEntityID' => $idpMetadata->getString('entityid'), +)); + +$bindings = array(SAML2_Const::BINDING_HTTP_REDIRECT); +if ($type === 'js') { + array_push($bindings, SAML2_Const::BINDING_HTTP_POST); +} + +$dst = $spMetadata->getDefaultEndpoint('SingleLogoutService', $bindings); +$binding = SAML2_Binding::getBinding($dst['Binding']); +$lr->setDestination($dst['Location']); + +$binding->send($lr); diff --git a/modules/saml/lib/IdP/SAML2.php b/modules/saml/lib/IdP/SAML2.php index 8b8897eb4..bc5613fd9 100644 --- a/modules/saml/lib/IdP/SAML2.php +++ b/modules/saml/lib/IdP/SAML2.php @@ -382,9 +382,43 @@ class sspmod_saml_IdP_SAML2 { } + /** + * Send a logout request to a given association. + * + * @param SimpleSAML_IdP $idp The IdP we are sending a logout request from. + * @param array $association The association that should be terminated. + * @param string|NULL $relayState An id that should be carried across the logout. + */ + public static function sendLogoutRequest(SimpleSAML_IdP $idp, array $association, $relayState) { + assert('is_string($relayState) || is_null($relayState)'); + + SimpleSAML_Logger::info('Sending SAML 2.0 LogoutRequest to: '. var_export($association['saml:entityID'], TRUE)); + + $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler(); + $idpMetadata = $idp->getConfig(); + $spMetadata = $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote'); + + SimpleSAML_Stats::log('saml:idp:LogoutRequest:sent', array( + 'spEntityID' => $association['saml:entityID'], + 'idpEntityID' => $idpMetadata->getString('entityid'), + )); + + $dst = $spMetadata->getDefaultEndpoint('SingleLogoutService', array( + SAML2_Const::BINDING_HTTP_REDIRECT, + SAML2_Const::BINDING_HTTP_POST) + ); + $binding = SAML2_Binding::getBinding($dst['Binding']); + $lr = self::buildLogoutRequest($idpMetadata, $spMetadata, $association, $relayState); + $lr->setDestination($dst['Location']); + + $binding->send($lr); + } + + /** * Send a logout response. * + * @param SimpleSAML_IdP $idp The IdP we are sending a logout request from. * @param array &$state The logout state array. */ public static function sendLogoutResponse(SimpleSAML_IdP $idp, array $state) { @@ -512,7 +546,8 @@ class sspmod_saml_IdP_SAML2 { /** - * Retrieve a logout URL for a given logout association. + * Retrieve a logout URL for a given logout association. Only for logout endpoints that support + * HTTP-Redirect binding. * * @param SimpleSAML_IdP $idp The IdP we are sending a logout request from. * @param array $association The association that should be terminated. @@ -527,29 +562,11 @@ class sspmod_saml_IdP_SAML2 { $idpMetadata = $idp->getConfig(); $spMetadata = $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote'); - $lr = sspmod_saml_Message::buildLogoutRequest($idpMetadata, $spMetadata); - $lr->setRelayState($relayState); - $lr->setSessionIndex($association['saml:SessionIndex']); - $lr->setNameId($association['saml:NameID']); - - $assertionLifetime = $spMetadata->getInteger('assertion.lifetime', NULL); - if ($assertionLifetime === NULL) { - $assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300); - } - $lr->setNotOnOrAfter(time() + $assertionLifetime); - - $encryptNameId = $spMetadata->getBoolean('nameid.encryption', NULL); - if ($encryptNameId === NULL) { - $encryptNameId = $idpMetadata->getBoolean('nameid.encryption', FALSE); - } - if ($encryptNameId) { - $lr->encryptNameId(sspmod_saml_Message::getEncryptionKey($spMetadata)); - } - - SimpleSAML_Stats::log('saml:idp:LogoutRequest:sent', array( - 'spEntityID' => $association['saml:entityID'], - 'idpEntityID' => $idpMetadata->getString('entityid'), - )); + // It doesn't make sense to use HTTP-Post when asking for a logout URL, therefore we allow only + // HTTP-Redirect. + $dst = $spMetadata->getDefaultEndpoint('SingleLogoutService', array(SAML2_Const::BINDING_HTTP_REDIRECT)); + $lr = self::buildLogoutRequest($idpMetadata, $spMetadata, $association, $relayState); + $lr->setDestination($dst['Location']); $binding = new SAML2_HTTPRedirect(); return $binding->getRedirectURL($lr); @@ -693,7 +710,8 @@ class sspmod_saml_IdP_SAML2 { * @param SimpleSAML_Configuration $spMetadata The metadata of the SP. * @return string The NameFormat. */ - private static function getAttributeNameFormat(SimpleSAML_Configuration $idpMetadata, SimpleSAML_Configuration $spMetadata) { + private static function getAttributeNameFormat(SimpleSAML_Configuration $idpMetadata, + SimpleSAML_Configuration $spMetadata) { /* Try SP metadata first. */ $attributeNameFormat = $spMetadata->getString('attributes.NameFormat', NULL); @@ -940,6 +958,40 @@ class sspmod_saml_IdP_SAML2 { } + /** + * Build a logout request based on information in the metadata. + * + * @param SimpleSAML_Configuration idpMetadata The metadata of the IdP. + * @param SimpleSAML_Configuration spMetadata The metadata of the SP. + * @param array $association The SP association. + * @param string|NULL $relayState An id that should be carried across the logout. + */ + private static function buildLogoutRequest(SimpleSAML_Configuration $idpMetadata, + SimpleSAML_Configuration $spMetadata, array $association, $relayState) { + + $lr = sspmod_saml_Message::buildLogoutRequest($idpMetadata, $spMetadata); + $lr->setRelayState($relayState); + $lr->setSessionIndex($association['saml:SessionIndex']); + $lr->setNameId($association['saml:NameID']); + + $assertionLifetime = $spMetadata->getInteger('assertion.lifetime', NULL); + if ($assertionLifetime === NULL) { + $assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300); + } + $lr->setNotOnOrAfter(time() + $assertionLifetime); + + $encryptNameId = $spMetadata->getBoolean('nameid.encryption', NULL); + if ($encryptNameId === NULL) { + $encryptNameId = $idpMetadata->getBoolean('nameid.encryption', FALSE); + } + if ($encryptNameId) { + $lr->encryptNameId(sspmod_saml_Message::getEncryptionKey($spMetadata)); + } + + return $lr; + } + + /** * Build a authentication response based on information in the metadata. * @@ -947,7 +999,8 @@ class sspmod_saml_IdP_SAML2 { * @param SimpleSAML_Configuration $spMetadata The metadata of the SP. * @param string $consumerURL The Destination URL of the response. */ - private static function buildResponse(SimpleSAML_Configuration $idpMetadata, SimpleSAML_Configuration $spMetadata, $consumerURL) { + private static function buildResponse(SimpleSAML_Configuration $idpMetadata, + SimpleSAML_Configuration $spMetadata, $consumerURL) { $signResponse = $spMetadata->getBoolean('saml20.sign.response', NULL); if ($signResponse === NULL) { diff --git a/templates/includes/header-embed.php b/templates/includes/header-embed.php index dce6cba61..7666371cd 100644 --- a/templates/includes/header-embed.php +++ b/templates/includes/header-embed.php @@ -19,5 +19,5 @@ if(array_key_exists('head', $this->data)) { } ?> </head> -<body> +<body class="body-embed"> diff --git a/www/resources/default.css b/www/resources/default.css index 2d2584112..dbce4317c 100644 --- a/www/resources/default.css +++ b/www/resources/default.css @@ -16,6 +16,12 @@ body { font: 83%/1.5 arial,tahoma,verdana,sans-serif; } +.body-embed { + padding: 0; + background: #ffffff; + font: 83%/1.5 arial,tahoma,verdana,sans-serif; +} + img { border: none; display: block; -- GitLab