From e8797f85395ea2da616c7af211ac61b6546c68d2 Mon Sep 17 00:00:00 2001 From: Olav Morken <olav.morken@uninett.no> Date: Mon, 7 Mar 2011 13:25:18 +0000 Subject: [PATCH] saml:IDP: Better selection of ACS endpoint based on AuthnRequest parameters. git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@2752 44740490-163a-0410-bde0-09ae8108e29a --- modules/saml/lib/IdP/SAML2.php | 122 +++++++++++++++++++++++---------- 1 file changed, 87 insertions(+), 35 deletions(-) diff --git a/modules/saml/lib/IdP/SAML2.php b/modules/saml/lib/IdP/SAML2.php index 46026579f..da38771a6 100644 --- a/modules/saml/lib/IdP/SAML2.php +++ b/modules/saml/lib/IdP/SAML2.php @@ -116,6 +116,88 @@ class sspmod_saml_IdP_SAML2 { } + /** + * Find SP AssertionConsumerService based on parameter in AuthnRequest. + * + * @param array $supportedBindings The bindings we allow for the response. + * @param SimpleSAML_Configuration $spMetadata The metadata for the SP. + * @param string|NULL $AssertionConsumerServiceURL AssertionConsumerServiceURL from request. + * @param string|NULL $ProtocolBinding ProtocolBinding from request. + * @param int|NULL $AssertionConsumerServiceIndex AssertionConsumerServiceIndex from request. + * @return array Array with the Location and Binding we should use for the response. + */ + private static function getAssertionConsumerService(array $supportedBindings, SimpleSAML_Configuration $spMetadata, + $AssertionConsumerServiceURL, $ProtocolBinding, $AssertionConsumerServiceIndex) { + assert('is_string($AssertionConsumerServiceURL) || is_null($AssertionConsumerServiceURL)'); + assert('is_string($ProtocolBinding) || is_null($ProtocolBinding)'); + assert('is_int($AssertionConsumerServiceIndex) || is_null($AssertionConsumerServiceIndex)'); + + /* We want to pick the best matching endpoint in the case where for example + * only the ProtocolBinding is given. We therefore pick endpoints with the + * following priority: + * 1. isDefault="true" + * 2. isDefault unset + * 3. isDefault="false" + */ + $firstNotFalse = NULL; + $firstFalse = NULL; + foreach ($spMetadata->getEndpoints('AssertionConsumerService') as $ep) { + + if ($AssertionConsumerServiceURL !== NULL && $ep['Location'] !== $AssertionConsumerServiceURL) { + continue; + } + if ($ProtocolBinding !== NULL && $ep['Binding'] !== $ProtocolBinding) { + continue; + } + if ($AssertionConsumerServiceIndex !== NULL && $ep['index'] !== $AssertionConsumerServiceIndex) { + continue; + } + + if (!in_array($ep['Binding'], $supportedBindings, TRUE)) { + /* The endpoint has an unsupported binding. */ + continue; + } + + /* We have an endpoint that matches all our requirements. Check if it is the best one. */ + + if (array_key_exists('isDefault', $ep)) { + if ($ep['isDefault'] === TRUE) { + /* This is the first matching endpoint with isDefault set to TRUE. */ + return $ep; + } + /* isDefault is set to FALSE, but the endpoint is still useable. */ + if ($firstFalse === NULL) { + /* This is the first endpoint that we can use. */ + $firstFalse = $ep; + } + } else if ($firstNotFalse === NULL) { + /* This is the first endpoint without isDefault set. */ + $firstNotFalse = $ep; + } + } + + if ($firstNotFalse !== NULL) { + return $firstNotFalse; + } elseif ($firstFalse !== NULL) { + return $firstFalse; + } + + SimpleSAML_Logger::warning('Authentication request specifies invalid AssertionConsumerService:'); + if ($AssertionConsumerServiceURL !== NULL) { + SimpleSAML_Logger::warning('AssertionConsumerServiceURL: ' . var_export($AssertionConsumerServiceURL, TRUE)); + } + if ($ProtocolBinding !== NULL) { + SimpleSAML_Logger::warning('ProtocolBinding: ' . var_export($ProtocolBinding, TRUE)); + } + if ($AssertionConsumerServiceIndex !== NULL) { + SimpleSAML_Logger::warning('AssertionConsumerServiceIndex: ' . var_export($AssertionConsumerServiceIndex, TRUE)); + } + + /* We have no good endpoints. Our last resort is to just use the default endpoint. */ + return $spMetadata->getDefaultEndpoint('AssertionConsumerService', $supportedBindings); + } + + /** * Receive an authentication request. * @@ -173,6 +255,7 @@ class sspmod_saml_IdP_SAML2 { $forceAuthn = FALSE; $isPassive = FALSE; $consumerURL = NULL; + $consumerIndex = NULL; $extensions = NULL; SimpleSAML_Logger::info('SAML2.0 - IdP.SSOService: IdP initiated authentication: '. var_export($spEntityId, TRUE)); @@ -205,6 +288,7 @@ class sspmod_saml_IdP_SAML2 { $isPassive = $request->getIsPassive(); $consumerURL = $request->getAssertionConsumerServiceURL(); $protocolBinding = $request->getProtocolBinding(); + $consumerIndex = $request->getAssertionConsumerServiceIndex(); $extensions = $request->getExtensions(); $nameIdPolicy = $request->getNameIdPolicy(); @@ -216,39 +300,7 @@ class sspmod_saml_IdP_SAML2 { SimpleSAML_Logger::info('SAML2.0 - IdP.SSOService: Incomming Authentication request: '. var_export($spEntityId, TRUE)); } - if ($protocolBinding === NULL || !in_array($protocolBinding, $supportedBindings, TRUE)) { - /* - * No binding specified or unsupported binding requested - default to HTTP-POST. - * TODO: Select any supported binding based on default endpoint? - */ - $protocolBinding = SAML2_Const::BINDING_HTTP_POST; - } - - if ($consumerURL !== NULL) { - $found = FALSE; - foreach ($spMetadata->getEndpoints('AssertionConsumerService') as $ep) { - if ($ep['Binding'] !== $protocolBinding) { - continue; - } - if ($ep['Location'] !== $consumerURL) { - continue; - } - $found = TRUE; - break; - } - - if (!$found) { - SimpleSAML_Logger::warning('Authentication request from ' . var_export($spEntityId, TRUE) . - ' contains invalid AssertionConsumerService URL. Was ' . - var_export($consumerURL, TRUE) . '.'); - $consumerURL = NULL; - } - } - if ($consumerURL === NULL) { - /* Not specified or invalid. Use default. */ - $consumerURL = $spMetadata->getDefaultEndpoint('AssertionConsumerService', array($protocolBinding)); - $consumerURL = $consumerURL['Location']; - } + $acsEndpoint = self::getAssertionConsumerService($supportedBindings, $spMetadata, $consumerURL, $protocolBinding, $consumerIndex); $IDPList = array_unique(array_merge($IDPList, $spMetadata->getArrayizeString('IDPList', array()))); if ($ProxyCount == null) $ProxyCount = $spMetadata->getInteger('ProxyCount', null); @@ -282,8 +334,8 @@ class sspmod_saml_IdP_SAML2 { 'saml:RequesterID' => $RequesterID, 'ForceAuthn' => $forceAuthn, 'isPassive' => $isPassive, - 'saml:ConsumerURL' => $consumerURL, - 'saml:Binding' => $protocolBinding, + 'saml:ConsumerURL' => $acsEndpoint['Location'], + 'saml:Binding' => $acsEndpoint['Binding'], 'saml:NameIDFormat' => $nameIDFormat, 'saml:Extensions' => $extensions, ); -- GitLab