diff --git a/docs/simplesamlphp-sp-api.txt b/docs/simplesamlphp-sp-api.txt index fef97669caf7f8489c06bc2b2bee3ae49fb8a631..90969e1a951c23c69a2335f5a5af371330ab2612 100644 --- a/docs/simplesamlphp-sp-api.txt +++ b/docs/simplesamlphp-sp-api.txt @@ -105,7 +105,7 @@ The [`saml:SP`](./saml:sp) authentication source also defines some parameters. # Send a passive authentication request. $auth->login(array( - 'saml:IsPassive' => TRUE, + 'isPassive' => TRUE, 'ErrorURL' => 'https://.../error_handler.php', )); diff --git a/lib/SAML2/Assertion.php b/lib/SAML2/Assertion.php index de3ca91f77db7542a03680fea12b820069d037e9..a12b2a90492acd8c7f4922d64d06e3cf50ccb86f 100644 --- a/lib/SAML2/Assertion.php +++ b/lib/SAML2/Assertion.php @@ -131,6 +131,13 @@ class SAML2_Assertion implements SAML2_SignedElement { */ private $authnContext; + /** + * The list of AuthenticatingAuthorities for this assertion. + * + * @var array + */ + private $AuthenticatingAuthority; + /** * The attributes, as an associative array. @@ -192,6 +199,7 @@ class SAML2_Assertion implements SAML2_SignedElement { $this->attributes = array(); $this->nameFormat = SAML2_Const::NAMEFORMAT_UNSPECIFIED; $this->certificates = array(); + $this->AuthenticatingAuthority = array(); if ($xml === NULL) { return; @@ -416,6 +424,8 @@ class SAML2_Assertion implements SAML2_SignedElement { } else { $this->authnContext = trim($accr[0]->textContent); } + + $this->AuthenticatingAuthority = SAML2_Utils::extractStrings($ac, './saml_assertion:AuthenticatingAuthority'); } @@ -874,6 +884,29 @@ class SAML2_Assertion implements SAML2_SignedElement { } + /** + * Retrieve the AuthenticatingAuthority. + * + * + * @return array + */ + public function getAuthenticatingAuthority() { + + return $this->AuthenticatingAuthority; + } + + + /** + * Set the AuthenticatingAuthority + * + * + * @param array. + */ + public function setAuthenticatingAuthority($AuthenticatingAuthority) { + $this->AuthenticatingAuthority = $AuthenticatingAuthority; + } + + /** * Retrieve all attributes. * @@ -1105,6 +1138,7 @@ class SAML2_Assertion implements SAML2_SignedElement { $as->appendChild($ac); SAML2_Utils::addString($ac, SAML2_Const::NS_SAML, 'saml:AuthnContextClassRef', $this->authnContext); + SAML2_Utils::addStrings($ac, SAML2_Const::NS_SAML, 'saml:AuthenticatingAuthority', false, $this->AuthenticatingAuthority); } diff --git a/lib/SAML2/AuthnRequest.php b/lib/SAML2/AuthnRequest.php index 10c7defe360ccc888f5a0e792c07a635676b03f0..dc1c26dc9b131abb8c463fc8263f752a6023c93b 100644 --- a/lib/SAML2/AuthnRequest.php +++ b/lib/SAML2/AuthnRequest.php @@ -37,6 +37,21 @@ class SAML2_AuthnRequest extends SAML2_Request { */ private $IDPList = array(); + /** + * The ProxyCount in this request's scoping element + * + * @var int + */ + private $ProxyCount = null; + + /** + * The RequesterID list in this request's scoping element + * + * @var array + */ + + private $RequesterID = array(); + /** * The URL of the asertion consumer service where the response should be delivered. * @@ -128,13 +143,27 @@ class SAML2_AuthnRequest extends SAML2_Request { $this->requestedAuthnContext = $rac; } - $idpEntries = SAML2_Utils::xpQuery($xml, './saml_protocol:Scoping/saml_protocol:IDPList/saml_protocol:IDPEntry'); + $scoping = SAML2_Utils::xpQuery($xml, './saml_protocol:Scoping'); + if (!empty($scoping)) { + $scoping =$scoping[0]; + + if ($scoping->hasAttribute('ProxyCount')) { + $this->ProxyCount = (int)$scoping->getAttribute('ProxyCount'); + } + $idpEntries = SAML2_Utils::xpQuery($scoping, './saml_protocol:IDPList/saml_protocol:IDPEntry'); - foreach($idpEntries as $idpEntry) { - if (!$idpEntry->hasAttribute('ProviderID')) { - throw new Exception("Could not get ProviderID from Scoping/IDPEntry element in AuthnRequest object"); + foreach($idpEntries as $idpEntry) { + if (!$idpEntry->hasAttribute('ProviderID')) { + throw new Exception("Could not get ProviderID from Scoping/IDPEntry element in AuthnRequest object"); + } + $this->IDPList[] = $idpEntry->getAttribute('ProviderID'); + } + + $requesterIDs = SAML2_Utils::xpQuery($scoping, './saml_protocol:RequesterID'); + foreach ($requesterIDs as $requesterID) { + $this->RequesterID[] = trim($requesterID->textContent); } - $this->IDPList[] = $idpEntry->getAttribute('ProviderID'); + } } @@ -234,6 +263,22 @@ class SAML2_AuthnRequest extends SAML2_Request { return $this->IDPList; } + public function setProxyCount($ProxyCount) { + assert('is_int($ProxyCount)'); + $this->ProxyCount = $ProxyCount; + } + + public function getProxyCount() { + return $this->ProxyCount; + } + + public function setRequesterID(array $RequesterID) { + $this->RequesterID = $RequesterID; + } + + public function getRequesterID() { + return $this->RequesterID; + } /** * Retrieve the value of the AssertionConsumerServiceURL attribute. @@ -352,16 +397,25 @@ class SAML2_AuthnRequest extends SAML2_Request { } } - if (count($this->IDPList) > 0) { + + if ($this->ProxyCount !== null || count($this->IDPList) > 0 || count($this->RequesterID) > 0) { $scoping = $this->document->createElementNS(SAML2_Const::NS_SAMLP, 'Scoping'); - $idplist = $this->document->createElementNS(SAML2_Const::NS_SAMLP, 'IDPList'); - foreach ($this->IDPList as $provider) { - $idpEntry = $this->document->createElementNS(SAML2_Const::NS_SAMLP, 'IDPEntry'); - $idpEntry->setAttribute('ProviderID', $provider); - $idplist->appendChild($idpEntry); + if ($this->ProxyCount !== null) { + $scoping->setAttribute('ProxyCount', $this->ProxyCount); + } + if (count($this->IDPList) > 0) { + $idplist = $this->document->createElementNS(SAML2_Const::NS_SAMLP, 'IDPList'); + foreach ($this->IDPList as $provider) { + $idpEntry = $this->document->createElementNS(SAML2_Const::NS_SAMLP, 'IDPEntry'); + $idpEntry->setAttribute('ProviderID', $provider); + $idplist->appendChild($idpEntry); + } + $scoping->appendChild($idplist); + $root->appendChild($scoping); + } + if (count($this->RequesterID) > 0) { + SAML2_Utils::addStrings($scoping, SAML2_Const::NS_SAMLP, 'RequesterID', FALSE, $this->RequesterID); } - $scoping->appendChild($idplist); - $root->appendChild($scoping); } return $root; diff --git a/lib/SAML2/Const.php b/lib/SAML2/Const.php index aa90068719a0862d34f432bc0456977470b6cde5..d44a28e0383488b811329770812c425186ba01dc 100644 --- a/lib/SAML2/Const.php +++ b/lib/SAML2/Const.php @@ -135,6 +135,12 @@ class SAML2_Const { */ const STATUS_PARTIAL_LOGOUT = 'urn:oasis:names:tc:SAML:2.0:status:PartialLogout'; + /** + * Second-level status code for ProxyCountExceeded. + */ + const STATUS_PROXY_COUNT_EXCEEDED = 'urn:oasis:names:tc:SAML:2.0:status:ProxyCountExceeded'; + + } ?> \ No newline at end of file diff --git a/lib/SimpleSAML/IdP.php b/lib/SimpleSAML/IdP.php index e324e09af0b36d2403ac8e2faa2281703df5bc6f..18f78b75c0d4e37f23e418f9ac54df55e29ffa67 100644 --- a/lib/SimpleSAML/IdP.php +++ b/lib/SimpleSAML/IdP.php @@ -393,6 +393,8 @@ class SimpleSAML_IdP { if (isset($state['ForceAuthn']) && (bool)$state['ForceAuthn']) { /* Force authentication is in effect. */ $needAuth = TRUE; + } elseif (isset($state['saml:IDPList']) && sizeof($state['saml:IDPList']) > 0) { + $needAuth = TRUE; } else { $needAuth = !$this->isAuthenticated(); } diff --git a/modules/saml/docs/sp.txt b/modules/saml/docs/sp.txt index 3164bc6aca13145b5019cd22c37427ffa486d5f7..2d44c0fcc18a4147458b391aaa8782c6f2afe7a0 100644 --- a/modules/saml/docs/sp.txt +++ b/modules/saml/docs/sp.txt @@ -22,7 +22,7 @@ All these parameters override the equivalent option from the configuration. : *Note*: SAML 2 specific. -`saml:ForceAuthn` +`ForceAuthnn` : Force authentication allows you to force re-authentication of users even if the user has a SSO session at the IdP. : *Note*: SAML 2 specific. @@ -30,7 +30,7 @@ All these parameters override the equivalent option from the configuration. `saml:idp` : The entity ID of the IdP we should send an authentication request to. -`saml:IsPassive` +`isPassive` : Send a passive authentication request. : *Note*: SAML 2 specific. diff --git a/modules/saml/lib/Auth/Source/SP.php b/modules/saml/lib/Auth/Source/SP.php index d27a3cd4c26d47914b9f4b444253ffc8343ace99..b8e708738063c66a17409eddb94c3229ce568acd 100644 --- a/modules/saml/lib/Auth/Source/SP.php +++ b/modules/saml/lib/Auth/Source/SP.php @@ -175,6 +175,10 @@ class sspmod_saml_Auth_Source_SP extends SimpleSAML_Auth_Source { * @param array $state The state array for the current authentication. */ private function startSSO2(SimpleSAML_Configuration $idpMetadata, array $state) { + + if (isset($state['saml:ProxyCount']) && $state['saml:ProxyCount'] < 0) { + SimpleSAML_Auth_State::throwException($state, new SimpleSAML_Error_ProxyCountExceeded("ProxyCountExceeded")); + } $ar = sspmod_saml2_Message::buildAuthnRequest($this->metadata, $idpMetadata); @@ -190,12 +194,12 @@ class sspmod_saml_Auth_Source_SP extends SimpleSAML_Auth_Source { $ar->setRequestedAuthnContext(array('AuthnContextClassRef' => $accr)); } - if (isset($state['saml:ForceAuthn'])) { - $ar->setForceAuthn((bool)$state['saml:ForceAuthn']); + if (isset($state['ForceAuthnn'])) { + $ar->setForceAuthn((bool)$state['ForceAuthn']); } - if (isset($state['saml:IsPassive'])) { - $ar->setIsPassive((bool)$state['saml:IsPassive']); + if (isset($state['isPassive'])) { + $ar->setIsPassive((bool)$state['isPassive']); } if (isset($state['saml:NameIDPolicy'])) { @@ -205,12 +209,37 @@ class sspmod_saml_Auth_Source_SP extends SimpleSAML_Auth_Source { )); } - + if (isset($state['saml:IDPList'])) { + $IDPList = $state['saml:IDPList']; + } + + $ar->setIDPList(array_unique(array_merge($this->metadata->getArray('IDPList', array()), + $idpMetadata->getArray('IDPList', array()), + (array) $IDPList))); + + if (isset($state['saml:ProxyCount']) && $state['saml:ProxyCount'] !== null) { + $ar->setProxyCount($state['saml:ProxyCount']); + } elseif ($idpMetadata->getInteger('ProxyCount', null) !== null) { + $ar->setProxyCount($idpMetadata->getInteger('ProxyCount', null)); + } elseif ($this->metadata->getInteger('ProxyCount', null) !== null) { + $ar->setProxyCount($this->metadata->getInteger('ProxyCount', null)); + } + + $requesterID = array(); + if (isset($state['saml:RequesterID'])) { + $requesterID = $state['saml:RequesterID']; + } + + if (isset($state['core:SP'])) { + $requesterID[] = $state['core:SP']; + } + + $ar->setRequesterID($requesterID); + $id = SimpleSAML_Auth_State::saveState($state, 'saml:sp:sso', TRUE); $ar->setId($id); SimpleSAML_Logger::debug('Sending SAML 2 AuthnRequest to ' . var_export($idpMetadata->getString('entityid'), TRUE)); - $b = new SAML2_HTTPRedirect(); $b->setDestination(sspmod_SAML2_Message::getDebugDestination()); $b->send($ar); @@ -291,6 +320,10 @@ class sspmod_saml_Auth_Source_SP extends SimpleSAML_Auth_Source { $idp = (string)$state['saml:idp']; } + if ($idp === NULL && isset($state['saml:IDPList']) && sizeof($state['saml:IDPList']) == 1) { + $idp = $state['saml:IDPList'][0]; + } + if ($idp === NULL) { $this->startDisco($state); assert('FALSE'); @@ -374,7 +407,7 @@ class sspmod_saml_Auth_Source_SP extends SimpleSAML_Auth_Source { assert('is_string($idp)'); assert('array_key_exists("LogoutState", $state)'); assert('array_key_exists("saml:logout:Type", $state["LogoutState"])'); - + $idpMetadata = $this->getIdpMetadata($idp); $spMetadataArray = $this->metadata->toArray(); diff --git a/modules/saml/lib/IdP/SAML2.php b/modules/saml/lib/IdP/SAML2.php index e9c365450a124b9b8089d97881b4a1af85f76646..e1bafa37e9303baf3dfa0e3b5f9dc3d9fb3c304b 100644 --- a/modules/saml/lib/IdP/SAML2.php +++ b/modules/saml/lib/IdP/SAML2.php @@ -48,6 +48,10 @@ class sspmod_saml_IdP_SAML2 { $assertion = sspmod_saml2_Message::buildAssertion($idpMetadata, $spMetadata, $attributes, $consumerURL); $assertion->setInResponseTo($requestId); + + if (isset($state['saml:AuthenticatingAuthority'])) { + $assertion->setAuthenticatingAuthority($state['saml:AuthenticatingAuthority']); + } /* Create the session association (for logout). */ $association = array( @@ -242,6 +246,9 @@ class sspmod_saml_IdP_SAML2 { $requestId = $request->getId(); $IDPList = $request->getIDPList(); + $ProxyCount = $request->getProxyCount(); + if ($ProxyCount !== null) $ProxyCount--; + $RequesterID = $request->getRequesterID(); $forceAuthn = $request->getForceAuthn(); $isPassive = $request->getIsPassive(); $consumerURL = $request->getAssertionConsumerServiceURL(); @@ -285,6 +292,7 @@ class sspmod_saml_IdP_SAML2 { } $IDPList = array_unique(array_merge($IDPList, $spMetadata->getArrayizeString('IDPList', array()))); + if ($ProxyCount == null) $ProxyCount = $spMetadata->getInteger('ProxyCount', null); if (!$forceAuthn) { $forceAuthn = $spMetadata->getBoolean('ForceAuthn', FALSE); @@ -311,7 +319,9 @@ class sspmod_saml_IdP_SAML2 { 'saml:RelayState' => $relayState, 'saml:RequestId' => $requestId, 'saml:IDPList' => $IDPList, - 'ForceAuthn' => $forceAuthn, + 'saml:ProxyCount' => $ProxyCount, + 'saml:RequesterID' => $RequesterID, + 'ForceAuthnn' => $forceAuthn, 'isPassive' => $isPassive, 'saml:ConsumerURL' => $consumerURL, 'saml:Binding' => $protocolBinding, diff --git a/modules/saml/www/sp/saml2-acs.php b/modules/saml/www/sp/saml2-acs.php index 0de7f2fb3571b9fede87643c3d5c395d0c1230d8..d9ef345c1d69f1217a2935ab0a10529dc6f7a58b 100644 --- a/modules/saml/www/sp/saml2-acs.php +++ b/modules/saml/www/sp/saml2-acs.php @@ -32,7 +32,6 @@ if (!empty($stateId)) { ); } - $idp = $response->getIssuer(); if ($idp === NULL) { throw new Exception('Missing <saml:Issuer> in message delivered to AssertionConsumerService.'); @@ -63,6 +62,9 @@ $logoutState = array( 'saml:logout:SessionIndex' => $sessionIndex, ); $state['LogoutState'] = $logoutState; +$state['saml:AuthenticatingAuthority'] = $assertion->getAuthenticatingAuthority(); +$state['saml:AuthenticatingAuthority'][] = $idp; +$state['PersistentAuthData'][] = 'saml:AuthenticatingAuthority'; $source->handleResponse($state, $idp, $assertion->getAttributes()); assert('FALSE'); diff --git a/modules/saml2/lib/Error.php b/modules/saml2/lib/Error.php index cdb017916249eaff7d590a1856deee54a627592d..1c9e11ccc05b8e7d57c76a4420cb350d94289c63 100644 --- a/modules/saml2/lib/Error.php +++ b/modules/saml2/lib/Error.php @@ -112,6 +112,13 @@ class sspmod_saml2_Error extends SimpleSAML_Error_Exception { $exception->getMessage(), $exception ); + } elseif ($exception instanceof SimpleSAML_Error_ProxyCountExceeded) { + $e = new self( + SAML2_Const::STATUS_RESPONDER, + SAML2_Const::STATUS_PROXY_COUNT_EXCEEDED, + $exception->getMessage(), + $exception + ); } else { $e = new self(