From 5a640391a867b9bf2664eeb5e332be5ada76bc48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20P=C3=A9rez=20Crespo?= <jaime.perez@uninett.no> Date: Wed, 17 Jul 2013 12:04:51 +0000 Subject: [PATCH] Full support for HTTP-POST binding in WebSSO profile. Two new directives in hosted metadata (SingleSignOnServiceBinding and SingleLogoutServiceBinding) to control the bindings published as supported in the metadata. Bugfix in the logout handler (SOAP binding should be reused when responding a request). git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@3257 44740490-163a-0410-bde0-09ae8108e29a --- docs/simplesamlphp-reference-idp-hosted.txt | 62 +++++++++++++------ .../Metadata/MetaDataStorageHandler.php | 7 ++- modules/saml/docs/sp.txt | 8 +++ modules/saml/lib/Auth/Source/SP.php | 24 ++++++- modules/saml/lib/IdP/SAML2.php | 12 +++- modules/saml/lib/Message.php | 23 ------- modules/saml/www/sp/metadata.php | 31 +++++----- modules/saml/www/sp/saml2-logout.php | 15 +++++ www/saml2/idp/metadata.php | 38 ++++++++++-- www/saml2/sp/SingleLogoutService.php | 19 ++++-- www/saml2/sp/initSLO.php | 7 +-- www/saml2/sp/initSSO.php | 11 +++- www/saml2/sp/metadata.php | 19 +++++- 13 files changed, 197 insertions(+), 79 deletions(-) diff --git a/docs/simplesamlphp-reference-idp-hosted.txt b/docs/simplesamlphp-reference-idp-hosted.txt index b2d14bab0..6ee064059 100644 --- a/docs/simplesamlphp-reference-idp-hosted.txt +++ b/docs/simplesamlphp-reference-idp-hosted.txt @@ -181,26 +181,6 @@ The following SAML 2.0 options are available: : Note that this option can be set for each SP in the [SP-remote metadata](./simplesamlphp-reference-sp-remote). -`SingleSignOnService` -: Override the default URL for the SingleSignOnService for this - IdP. This is an absolute URL. The default value is - `<simpleSAMLphp-root>/saml2/idp/SSOService.php` - -: Note that this only changes the values in the generated - metadata and in the messages sent to others. You must also - configure your webserver to deliver this URL to the correct PHP - page. - -`SingleLogoutService` -: Override the default URL for the SingleLogoutService for this - IdP. This is an absolute URL. The default value is - `<simpleSAMLphp-root>/saml2/idp/SingleLogoutService.php` - -: Note that this only changes the values in the generated - metadata and in the messages sent to others. You must also - configure your webserver to deliver this URL to the correct PHP - page. - `saml20.sendartifact` : Set to `TRUE` to enable the IdP to send responses with the HTTP-Artifact binding. Defaults to `FALSE`. @@ -234,6 +214,48 @@ The following SAML 2.0 options are available: any value in the SP-remote metadata overrides the one configured in the IdP metadata. +`SingleSignOnService` +: Override the default URL for the SingleSignOnService for this + IdP. This is an absolute URL. The default value is + `<simpleSAMLphp-root>/saml2/idp/SSOService.php` + +: Note that this only changes the values in the generated + metadata and in the messages sent to others. You must also + configure your webserver to deliver this URL to the correct PHP + page. + +`SingleSignOnServiceBinding` +: List of SingleSignOnService bindings that the IdP will claim support for. +: Possible values: + + * `urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect` + * `urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST` + +: Defaults to HTTP-Redirect binding. Please note that the order + specified will be kept in the metadata, making the first binding + the default one. + +`SingleLogoutService` +: Override the default URL for the SingleLogoutService for this + IdP. This is an absolute URL. The default value is + `<simpleSAMLphp-root>/saml2/idp/SingleLogoutService.php` + +: Note that this only changes the values in the generated + metadata and in the messages sent to others. You must also + configure your webserver to deliver this URL to the correct PHP + page. + +`SingleLogoutServiceBinding` +: List of SingleLogoutService bindings the IdP will claim support for. +: Possible values: + + * `urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect` + * `urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST` + +: Defaults to HTTP-Redirect binding. Please note that the order + specified will be kept in the metadata, making the first binding + the default one. + `validate.authnrequest` : Whether we require signatures on authentication requests sent to this IdP. diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php b/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php index d91b913ba..4d07d3c88 100644 --- a/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php +++ b/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php @@ -107,8 +107,14 @@ class SimpleSAML_Metadata_MetaDataStorageHandler { case 'SingleSignOnService' : return $baseurl . 'saml2/idp/SSOService.php'; + case 'SingleSignOnServiceBinding' : + return SAML2_Const::BINDING_HTTP_REDIRECT; + case 'SingleLogoutService' : return $baseurl . 'saml2/idp/SingleLogoutService.php'; + + case 'SingleLogoutServiceBinding' : + return SAML2_Const::BINDING_HTTP_REDIRECT; } } elseif($set == 'shib13-sp-hosted') { switch ($property) { @@ -341,4 +347,3 @@ class SimpleSAML_Metadata_MetaDataStorageHandler { } -?> \ No newline at end of file diff --git a/modules/saml/docs/sp.txt b/modules/saml/docs/sp.txt index 84f8ca43f..e77b32d4c 100644 --- a/modules/saml/docs/sp.txt +++ b/modules/saml/docs/sp.txt @@ -388,6 +388,14 @@ Options : *Note*: SAML 2 specific. +`SingleLogoutServiceBinding` +: List of SingleLogoutService bindings the IdP will claim support for. +: Possible values: + + * `urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect` + * `urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST` + * `urn:oasis:names:tc:SAML:2.0:bindings:SOAP` + `redirect.sign` : Whether authentication requests, logout requests and logout responses sent from this SP should be signed. The default is `FALSE`. diff --git a/modules/saml/lib/Auth/Source/SP.php b/modules/saml/lib/Auth/Source/SP.php index 32d4bcc5e..1154bdc8e 100644 --- a/modules/saml/lib/Auth/Source/SP.php +++ b/modules/saml/lib/Auth/Source/SP.php @@ -259,7 +259,22 @@ class sspmod_saml_Auth_Source_SP extends SimpleSAML_Auth_Source { $ar->setId($id); SimpleSAML_Logger::debug('Sending SAML 2 AuthnRequest to ' . var_export($idpMetadata->getString('entityid'), TRUE)); - $b = new SAML2_HTTPRedirect(); + + /* Select appropriate SSO endpoint */ + if ($ar->getProtocolBinding() === SAML2_Const::BINDING_HOK_SSO) { + $dst = $idpMetadata->getDefaultEndpoint('SingleSignOnService', array( + SAML2_Const::BINDING_HOK_SSO) + ); + } else { + $dst = $idpMetadata->getDefaultEndpoint('SingleSignOnService', array( + SAML2_Const::BINDING_HTTP_REDIRECT, + SAML2_Const::BINDING_HTTP_POST) + ); + } + $ar->setDestination($dst['Location']); + + $b = SAML2_Binding::getBinding($dst['Binding']); + $this->sendSAML2AuthnRequest($state, $b, $ar); assert('FALSE'); @@ -392,7 +407,9 @@ class sspmod_saml_Auth_Source_SP extends SimpleSAML_Auth_Source { $idpMetadata = $this->getIdPMetadata($idp); - $endpoint = $idpMetadata->getDefaultEndpoint('SingleLogoutService', array(SAML2_Const::BINDING_HTTP_REDIRECT), FALSE); + $endpoint = $idpMetadata->getDefaultEndpoint('SingleLogoutService', array( + SAML2_Const::BINDING_HTTP_REDIRECT, + SAML2_Const::BINDING_HTTP_POST), FALSE); if ($endpoint === FALSE) { SimpleSAML_Logger::info('No logout endpoint for IdP ' . var_export($idp, TRUE) . '.'); return; @@ -402,6 +419,7 @@ class sspmod_saml_Auth_Source_SP extends SimpleSAML_Auth_Source { $lr->setNameId($nameId); $lr->setSessionIndex($sessionIndex); $lr->setRelayState($id); + $lr->setDestination($endpoint['Location']); $encryptNameId = $idpMetadata->getBoolean('nameid.encryption', NULL); if ($encryptNameId === NULL) { @@ -411,7 +429,7 @@ class sspmod_saml_Auth_Source_SP extends SimpleSAML_Auth_Source { $lr->encryptNameId(sspmod_saml_Message::getEncryptionKey($idpMetadata)); } - $b = new SAML2_HTTPRedirect(); + $b = SAML2_Binding::getBinding($endpoint['Binding']); $b->send($lr); assert('FALSE'); diff --git a/modules/saml/lib/IdP/SAML2.php b/modules/saml/lib/IdP/SAML2.php index 94613b5f9..8b8897eb4 100644 --- a/modules/saml/lib/IdP/SAML2.php +++ b/modules/saml/lib/IdP/SAML2.php @@ -419,8 +419,18 @@ class sspmod_saml_IdP_SAML2 { 'idpEntityID' => $idpMetadata->getString('entityid'), 'partial' => $partial )); + $dst = $spMetadata->getDefaultEndpoint('SingleLogoutService', array( + SAML2_Const::BINDING_HTTP_REDIRECT, + SAML2_Const::BINDING_HTTP_POST) + ); + $binding = SAML2_Binding::getBinding($dst['Binding']); + if (isset($dst['ResponseLocation'])) { + $dst = $dst['ResponseLocation']; + } else { + $dst = $dst['Location']; + } + $lr->setDestination($dst); - $binding = new SAML2_HTTPRedirect(); $binding->send($lr); } diff --git a/modules/saml/lib/Message.php b/modules/saml/lib/Message.php index 5b6c91a3c..68c2edefc 100644 --- a/modules/saml/lib/Message.php +++ b/modules/saml/lib/Message.php @@ -413,16 +413,7 @@ class sspmod_saml_Message { /* Shoaib - setting the appropriate binding based on parameter in sp-metadata defaults to HTTP_POST */ $ar->setProtocolBinding($protbind); - /* Select appropriate SSO endpoint */ - if ($protbind === SAML2_Const::BINDING_HOK_SSO) { - $dst = $idpMetadata->getDefaultEndpoint('SingleSignOnService', array(SAML2_Const::BINDING_HOK_SSO)); - } else { - $dst = $idpMetadata->getDefaultEndpoint('SingleSignOnService', array(SAML2_Const::BINDING_HTTP_REDIRECT)); - } - $dst = $dst['Location']; - $ar->setIssuer($spMetadata->getString('entityid')); - $ar->setDestination($dst); if ($spMetadata->hasValue('AuthnContextClassRef')) { $accr = $spMetadata->getArrayizeString('AuthnContextClassRef'); @@ -443,13 +434,8 @@ class sspmod_saml_Message { */ public static function buildLogoutRequest(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata) { - $dst = $dstMetadata->getDefaultEndpoint('SingleLogoutService', array(SAML2_Const::BINDING_HTTP_REDIRECT)); - $dst = $dst['Location']; - $lr = new SAML2_LogoutRequest(); - $lr->setIssuer($srcMetadata->getString('entityid')); - $lr->setDestination($dst); self::addRedirectSign($srcMetadata, $dstMetadata, $lr); @@ -465,17 +451,8 @@ class sspmod_saml_Message { */ public static function buildLogoutResponse(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata) { - $dst = $dstMetadata->getDefaultEndpoint('SingleLogoutService', array(SAML2_Const::BINDING_HTTP_REDIRECT)); - if (isset($dst['ResponseLocation'])) { - $dst = $dst['ResponseLocation']; - } else { - $dst = $dst['Location']; - } - $lr = new SAML2_LogoutResponse(); - $lr->setIssuer($srcMetadata->getString('entityid')); - $lr->setDestination($dst); self::addRedirectSign($srcMetadata, $dstMetadata, $lr); diff --git a/modules/saml/www/sp/metadata.php b/modules/saml/www/sp/metadata.php index 6e2894271..6a9a153dc 100644 --- a/modules/saml/www/sp/metadata.php +++ b/modules/saml/www/sp/metadata.php @@ -17,23 +17,26 @@ if (!($source instanceof sspmod_saml_Auth_Source_SP)) { $entityId = $source->getEntityId(); $spconfig = $source->getMetadata(); +$store = SimpleSAML_Store::getInstance(); + +$metaArray20 = array(); -$metaArray20 = array( - 'SingleLogoutService' => SimpleSAML_Module::getModuleURL('saml/sp/saml2-logout.php/' . $sourceId), +$slosvcdefault = array( + SAML2_Const::BINDING_HTTP_REDIRECT, + SAML2_Const::BINDING_SOAP, ); -$store = SimpleSAML_Store::getInstance(); -if ($store instanceof SimpleSAML_Store_SQL) { - /* We can properly support SOAP logout. */ - $metaArray20['SingleLogoutService'] = array( - array( - 'Binding' => SAML2_Const::BINDING_HTTP_REDIRECT, - 'Location' => SimpleSAML_Module::getModuleURL('saml/sp/saml2-logout.php/' . $sourceId), - ), - array( - 'Binding' => SAML2_Const::BINDING_SOAP, - 'Location' => SimpleSAML_Module::getModuleURL('saml/sp/saml2-logout.php/' . $sourceId), - ), +$slob = $spconfig->getArray('SingleLogoutServiceBinding', $slosvcdefault); +$slol = SimpleSAML_Module::getModuleURL('saml/sp/saml2-logout.php/' . $sourceId); + +foreach ($slob as $binding) { + if ($binding == SAML2_Const::BINDING_SOAP && !($store instanceof SimpleSAML_Store_SQL)) { + /* We cannot properly support SOAP logout. */ + continue; + } + $metaArray20['SingleLogoutService'][] = array( + 'Binding' => $binding, + 'Location' => $slol, ); } diff --git a/modules/saml/www/sp/saml2-logout.php b/modules/saml/www/sp/saml2-logout.php index caf09b046..ca168fdc0 100644 --- a/modules/saml/www/sp/saml2-logout.php +++ b/modules/saml/www/sp/saml2-logout.php @@ -108,6 +108,21 @@ if ($message instanceof SAML2_LogoutResponse) { SimpleSAML_Logger::warning('Logged out of ' . $numLoggedOut . ' of ' . count($sessionIndexes) . ' sessions.'); } + $dst = $idpMetadata->getDefaultEndpoint('SingleLogoutService', array( + SAML2_Const::BINDING_HTTP_REDIRECT, + SAML2_Const::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); + } + $binding->send($lr); } else { throw new SimpleSAML_Error_BadRequest('Unknown message received on logout endpoint: ' . get_class($message)); diff --git a/www/saml2/idp/metadata.php b/www/saml2/idp/metadata.php index e0b8e0db5..a7d278553 100644 --- a/www/saml2/idp/metadata.php +++ b/www/saml2/idp/metadata.php @@ -60,12 +60,41 @@ try { $metaArray = array( 'metadata-set' => 'saml20-idp-remote', 'entityid' => $idpentityid, - 'SingleSignOnService' => array(0 => array( - 'Binding' => SAML2_Const::BINDING_HTTP_REDIRECT, - 'Location' => $metadata->getGenerated('SingleSignOnService', 'saml20-idp-hosted'))), - 'SingleLogoutService' => $metadata->getGenerated('SingleLogoutService', 'saml20-idp-hosted'), ); + $ssob = $metadata->getGenerated('SingleSignOnServiceBinding', 'saml20-idp-hosted'); + $slob = $metadata->getGenerated('SingleLogoutServiceBinding', 'saml20-idp-hosted'); + $ssol = $metadata->getGenerated('SingleSignOnService', 'saml20-idp-hosted'); + $slol = $metadata->getGenerated('SingleLogoutService', 'saml20-idp-hosted'); + + if (is_array($ssob)) { + foreach ($ssob as $binding) { + $metaArray['SingleSignOnService'][] = array( + 'Binding' => $binding, + 'Location' => $ssol, + ); + } + } else { + $metaArray['SingleSignOnService'][] = array( + 'Binding' => $ssob, + 'Location' => $ssol, + ); + } + + if (is_array($slob)) { + foreach ($slob as $binding) { + $metaArray['SingleLogoutService'][] = array( + 'Binding' => $binding, + 'Location' => $slol, + ); + } + } else { + $metaArray['SingleLogoutService'][] = array( + 'Binding' => $slob, + 'Location' => $slol, + ); + } + if (count($keys) === 1) { $metaArray['certData'] = $keys[0]['X509Certificate']; } else { @@ -164,4 +193,3 @@ try { } -?> \ No newline at end of file diff --git a/www/saml2/sp/SingleLogoutService.php b/www/saml2/sp/SingleLogoutService.php index 559a088c0..bf1a8dbbf 100644 --- a/www/saml2/sp/SingleLogoutService.php +++ b/www/saml2/sp/SingleLogoutService.php @@ -52,8 +52,22 @@ if ($message instanceof SAML2_LogoutRequest) { SimpleSAML_Logger::info('SAML2.0 - SP.SingleLogoutService: SP me (' . $spEntityId . ') is sending logout response to IdP (' . $idpEntityId . ')'); + $dst = $idpMetadata->getDefaultEndpoint('SingleLogoutService', array( + SAML2_Const::BINDING_HTTP_REDIRECT, + SAML2_Const::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); + } + /* Send response. */ - $binding = new SAML2_HTTPRedirect(); $binding->send($lr); } catch (Exception $exception) { throw new SimpleSAML_Error_Error('LOGOUTREQUEST', $exception); @@ -80,6 +94,3 @@ if ($message instanceof SAML2_LogoutRequest) { throw new SimpleSAML_Error_Error('SLOSERVICEPARAMS'); } - - -?> \ No newline at end of file diff --git a/www/saml2/sp/initSLO.php b/www/saml2/sp/initSLO.php index 7d4bb8f86..84855d00e 100644 --- a/www/saml2/sp/initSLO.php +++ b/www/saml2/sp/initSLO.php @@ -28,12 +28,13 @@ try { SimpleSAML_Utilities::redirect($returnTo); } $idpMetadata = $metadata->getMetaDataConfig($idpEntityId, 'saml20-idp-remote'); - $SLOendpoint = $idpMetadata->getDefaultEndpoint('SingleLogoutService', array(SAML2_Const::BINDING_HTTP_REDIRECT), NULL); + $SLOendpoint = $idpMetadata->getDefaultEndpoint('SingleLogoutService', array(SAML2_Const::BINDING_HTTP_REDIRECT, SAML2_Const::BINDING_HTTP_POST), NULL); if ($SLOendpoint === NULL) { $session->doLogout('saml2'); SimpleSAML_Logger::info('SAML2.0 - SP.initSLO: No supported SingleLogoutService endpoint in IdP.'); SimpleSAML_Utilities::redirect($returnTo); } + $lr->setDestination($SLOendpoint['Location']); $spEntityId = isset($_GET['spentityid']) ? $_GET['spentityid'] : $metadata->getMetaDataCurrentEntityID(); $spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-hosted'); @@ -51,7 +52,7 @@ try { SimpleSAML_Logger::info('SAML2.0 - SP.initSLO: SP (' . $spEntityId . ') is sending logout request to IdP (' . $idpEntityId . ')'); - $b = new SAML2_HTTPRedirect(); + $b = SAML2_Binding::getBinding($SLOendpoint['Binding']); $b->send($lr); @@ -59,5 +60,3 @@ try { throw new SimpleSAML_Error_Error('CREATEREQUEST', $exception); } - -?> \ No newline at end of file diff --git a/www/saml2/sp/initSSO.php b/www/saml2/sp/initSSO.php index b2abf60b8..491a18d17 100644 --- a/www/saml2/sp/initSSO.php +++ b/www/saml2/sp/initSSO.php @@ -164,11 +164,18 @@ try { } $session->setData('SAML2:SP:SSO:Info', $ar->getId(), $info); - $b = new SAML2_HTTPRedirect(); + /* Select appropriate SSO endpoint */ + if ($ar->getProtocolBinding() === SAML2_Const::BINDING_HOK_SSO) { + $dst = $idpMetadata->getDefaultEndpoint('SingleSignOnService', array(SAML2_Const::BINDING_HOK_SSO)); + } else { + $dst = $idpMetadata->getDefaultEndpoint('SingleSignOnService', array(SAML2_Const::BINDING_HTTP_REDIRECT, SAML2_Const::BINDING_HTTP_POST)); + } + $ar->setDestination($dst['Location']); + + $b = SAML2_Binding::getBinding($dst['Binding']); $b->send($ar); } catch(Exception $exception) { throw new SimpleSAML_Error_Error('CREATEREQUEST', $exception); } -?> \ No newline at end of file diff --git a/www/saml2/sp/metadata.php b/www/saml2/sp/metadata.php index 03331212c..947194685 100644 --- a/www/saml2/sp/metadata.php +++ b/www/saml2/sp/metadata.php @@ -25,9 +25,25 @@ try { 'metadata-set' => 'saml20-sp-remote', 'entityid' => $spentityid, 'AssertionConsumerService' => $metadata->getGenerated('AssertionConsumerService', 'saml20-sp-hosted'), - 'SingleLogoutService' => $metadata->getGenerated('SingleLogoutService', 'saml20-sp-hosted'), ); + $slob = $metadata->getGenerated('SingleLogoutServiceBinding', 'saml20-sp-hosted'); + $slol = $metadata->getGenerated('SingleLogoutService', 'saml20-sp-hosted'); + + if (is_array($slob)) { + foreach ($slob as $binding) { + $metaArray['SingleLogoutService'][] = array( + 'Binding' => $binding, + 'Location' => $slol, + ); + } + } else { + $metaArray['SingleLogoutService'][] = array( + 'Binding' => $slob, + 'Location' => $slol, + ); + } + $metaArray['NameIDFormat'] = $spmeta->getString('NameIDFormat', 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'); if ($spmeta->hasValue('OrganizationName')) { @@ -97,4 +113,3 @@ try { } -?> \ No newline at end of file -- GitLab