diff --git a/lib/SAML2/AuthnRequest.php b/lib/SAML2/AuthnRequest.php index 897b08e028ea2cdf96c9bd4e920ec259195bf93b..475afb87781a8fad36777f8ae93b18fe5a3c8fa9 100644 --- a/lib/SAML2/AuthnRequest.php +++ b/lib/SAML2/AuthnRequest.php @@ -30,6 +30,12 @@ class SAML2_AuthnRequest extends SAML2_Request { */ private $isPassive; + /** + * The list of providerIDs in this request's scoping element + * + * @var array + */ + private $IDPList = array(); /** * The URL of the asertion consumer service where the response should be delivered. @@ -75,6 +81,7 @@ class SAML2_AuthnRequest extends SAML2_Request { } $nameIdPolicy = SAML2_Utils::xpQuery($xml, './saml_protocol:NameIDPolicy'); + if (!empty($nameIdPolicy)) { $nameIdPolicy = $nameIdPolicy[0]; if ($nameIdPolicy->hasAttribute('Format')) { @@ -87,6 +94,15 @@ class SAML2_AuthnRequest extends SAML2_Request { $this->nameIdPolicy['AllowCreate'] = SAML2_Utils::parseBoolean($nameIdPolicy, 'AllowCreate', FALSE); } } + + $idpEntries = SAML2_Utils::xpQuery($xml, './saml_protocol: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"); + } + $this->IDPList[] = $idpEntry->getAttribute('ProviderID'); + } } @@ -161,6 +177,31 @@ class SAML2_AuthnRequest extends SAML2_Request { } + /** + * This function sets the scoping for the request + * See Core 3.4.1.2 for the definition of scoping + * Currently we only support an IDPList of idpEntries + * and only the required ProviderID in an IDPEntry + * $providerIDs is an array of Entity Identifiers + * + */ + public function setIDPList($IDPList) { + assert('is_array($IDPList)'); + $this->IDPList = $IDPList; + } + + + /** + * This function retrieves the list of providerIDs from this authentication request. + * Currently we only support a list of ipd ientity id's. + * @return The list of idpidentityids from the request + */ + + public function getIDPList() { + return $this->IDPList; + } + + /** * Retrieve the value of the AssertionConsumerServiceURL attribute. * @@ -230,6 +271,18 @@ class SAML2_AuthnRequest extends SAML2_Request { $root->setAttribute('ProtocolBinding', $this->protocolBinding); } + if (count($this->IDPList) > 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); + } + $scoping->appendChild($idplist); + $root->appendChild($scoping); + } + if (!empty($this->nameIdPolicy)) { $nameIdPolicy = $this->document->createElementNS(SAML2_Const::NS_SAMLP, 'NameIDPolicy'); if (array_key_exists('Format', $this->nameIdPolicy)) { diff --git a/lib/SimpleSAML/XHTML/IdPDisco.php b/lib/SimpleSAML/XHTML/IdPDisco.php index d694111e336a4c1517331b9e9df4a55354286001..ac873df19949ba7b2e878caa8ee8ec2d1d420289 100644 --- a/lib/SimpleSAML/XHTML/IdPDisco.php +++ b/lib/SimpleSAML/XHTML/IdPDisco.php @@ -70,7 +70,13 @@ class SimpleSAML_XHTML_IdPDisco { */ protected $returnIdParam; - + /** + * The list of scoped idp's. The intersection between the metadata idpList + * and scopedIDPList (given as a $_GET IDPList[] parameter) is presented to + * the user. If the intersection is empty the metadata idpList is used. + */ + protected $scopedIDPList = array(); + /** * The URL the user should be redirected to after choosing an IdP. */ @@ -133,6 +139,10 @@ class SimpleSAML_XHTML_IdPDisco { $this->setIdPentityID = NULL; } + if (array_key_exists('IDPList', $_GET)) { + $this->scopedIDPList = $_GET['IDPList']; + } + } @@ -418,7 +428,15 @@ class SimpleSAML_XHTML_IdPDisco { return $idpList; } - + /** + * Return the list of scoped idp + * + * @return array Array of idp entities + */ + protected function getScopedIDPList() { + return $this->scopedIDPList; + } + /** * Handles a request to this discovery service. * @@ -460,6 +478,11 @@ class SimpleSAML_XHTML_IdPDisco { $idpList = $this->getIdPList(); $preferredIdP = $this->getRecommendedIdP(); + $idpintersection = array_intersect(array_keys($idpList), $this->getScopedIDPlist()); + if (sizeof($idpintersection) > 0) { + $idpList = array_intersect_key($idpList, array_fill_keys($idpintersection, NULL)); + } + /* * Make use of an XHTML template to present the select IdP choice to the user. * Currently the supported options is either a drop down menu or a list view. diff --git a/www/saml2/idp/SSOService.php b/www/saml2/idp/SSOService.php index d9d78884cfe74d316737484bc9f997e4c308c77d..4fac005ddf749bcb0bc9011eded9560304096538 100644 --- a/www/saml2/idp/SSOService.php +++ b/www/saml2/idp/SSOService.php @@ -94,7 +94,9 @@ function handleError(Exception $exception) { /* * Initiate some variables */ -$isPassive = FALSE; +$isPassive = $forceAuthn = FALSE; + +$IDPList = array(); /* * If the SAMLRequest query parameter is set, we got an incoming Authentication Request @@ -136,6 +138,17 @@ if (isset($_REQUEST['SAMLRequest'])) { ); + $spentityid = $requestcache['Issuer']; + $spmetadata = $metadata->getMetaData($spentityid, 'saml20-sp-remote'); + + $IDPList = $authnrequest->getIDPList(); + + if(array_key_exists('IDPList', $spmetadata)) { + $IDPList = array_unique(array_merge($IDPList, $spmetadata['IDPList'])); + } + + $requestcache['IDPList'] = $IDPList; + /* * Handle the ForceAuthn option. */ @@ -143,8 +156,6 @@ if (isset($_REQUEST['SAMLRequest'])) { /* The default value is FALSE. */ $forceAuthn = FALSE; - $spentityid = $requestcache['Issuer']; - $spmetadata = $metadata->getMetaData($spentityid, 'saml20-sp-remote'); if(array_key_exists('ForceAuthn', $spmetadata)) { /* The ForceAuthn flag is set in the metadata for this SP. */ $forceAuthn = $spmetadata['ForceAuthn']; @@ -177,8 +188,9 @@ if (isset($_REQUEST['SAMLRequest'])) { */ $requestcache['NeedAuthentication'] = TRUE; } + $requestcache['IsPassive'] = $isPassive; + $requestcache['ForceAuthn'] = $forceAuthn; - SimpleSAML_Logger::info('SAML2.0 - IdP.SSOService: Incomming Authentication request: '.$issuer.' id '.$requestid); } catch(Exception $exception) { @@ -287,12 +299,16 @@ if(SimpleSAML_Auth_Source::getById($idpmetadata['auth']) !== NULL) { * endpoint - then the session is authenticated and set, and the user is redirected back with a RequestID * parameter so we can retrieve the cached information from the request. */ + if (!isset($session) || !$session->isValid($authority) ) { /* We don't have a valid session. */ $needAuth = TRUE; } elseif (array_key_exists('NeedAuthentication', $requestcache) && $requestcache['NeedAuthentication']) { /* We have a valid session, but ForceAuthn is on. */ $needAuth = TRUE; +} elseif ((sizeof($IDPList) > 0 && $session->getidp() !== null && !in_array($session->getidp(), $IDPList))) { + /* we do have a valid session but not with one of the scoped idps. */ + $needAuth = TRUE; } else { /* We have a valid session. */ $needAuth = FALSE; @@ -400,7 +416,14 @@ if($needAuth && !$isPassive) { $attributes = $authProcState['Attributes']; - + $host = SimpleSAML_Utilities::getSelfHost();; + preg_match("/^(\w+)/", $host, $d); + if ($d[1] && $d[1] != 'wayf') { + setcookie($d[1], "ok", 0, "/", ".wayf.ruc.dk"); + setcookie('current', $d[1], 0, "/", ".wayf.ruc.dk"); + } + + /* * Save the time we authenticated to this SP. This can be used later to detect an diff --git a/www/saml2/sp/initSSO.php b/www/saml2/sp/initSSO.php index 1dad4aa0ecfe34141088bdf07a081f36201cc08a..b9d1b30096e9c38b0fee3d99927528552032195d 100644 --- a/www/saml2/sp/initSSO.php +++ b/www/saml2/sp/initSSO.php @@ -29,16 +29,48 @@ try { $idpentityid = isset($_GET['idpentityid']) ? $_GET['idpentityid'] : $config->getString('default-saml20-idp', NULL) ; $spentityid = isset($_GET['spentityid']) ? $_GET['spentityid'] : $metadata->getMetaDataCurrentEntityID(); - if($idpentityid === NULL) { - /* We are going to need the SP metadata to determine which IdP discovery service we should use. */ - $spmetadata = $metadata->getMetaDataCurrent('saml20-sp-hosted'); + $isPassive = isset($_GET['IsPassive']) && ($_GET['IsPassive'] === 'true' || $_GET['IsPassive'] === '1'); + $forceAuthn = isset($_GET['ForceAuthn']) && ($_GET['ForceAuthn'] === 'true' || $_GET['ForceAuthn'] === '1'); + + /* We are going to need the SP metadata to determine which IdP discovery service we should use. + And for checking for scoping parameters. */ + $spmetadata = $metadata->getMetaDataCurrent('saml20-sp-hosted'); + + $IDPList = array(); + + /* Configured idp overrides one given by Scope */ + if($idpentityid === NULL && array_key_exists('idpentityid', $spmetadata)) { + $idpentityid = $spmetadata['idpentityid']; } + /* AuthId is set if we are on the sp side on a proxy/bridge */ + $authid = isset($_GET['AuthId']) ? $_GET['AuthId'] : FALSE; + if ($authid) { + $authrequestcache = $session->getAuthnRequest('saml2', $authid); + $isPassive = $isPassive || $authrequestcache['IsPassive']; + $forceAuthn = $forceAuthn || $authrequestcache['ForceAuthn']; + + /* keep the IDPList, it MUST be sent it to the next idp, + we are only allowed to add idps */ + if (isset($authrequestcache['IDPList']) && is_array($authrequestcache['IDPList'])) { + $IDPList = $authrequestcache['IDPList']; + } + if ($idpentityid === NULL) { + /* only consider ProviderIDs we know ... */ + + $reachableIDPs = array_intersect($IDPList, array_keys($metadata->getList())); + + if (sizeof($reachableIDPs) === 1) { + $idpentityid = array_shift($reachableIDPs); + } + } + } + + } catch (Exception $exception) { SimpleSAML_Utilities::fatalError($session->getTrackID(), 'METADATA', $exception); } - /* * If no IdP can be resolved, send the user to the SAML 2.0 Discovery Service */ @@ -49,6 +81,7 @@ if ($idpentityid === NULL) { /* Which IdP discovery service should we use? Can be set in SP metadata or in global configuration. * Falling back to builtin discovery service. */ + if(array_key_exists('idpdisco.url', $spmetadata)) { $discourl = $spmetadata['idpdisco.url']; } elseif($config->getString('idpdisco.url.saml20', NULL) !== NULL) { @@ -74,12 +107,18 @@ if ($idpentityid === NULL) { ); } - - SimpleSAML_Utilities::redirect($discourl, array( + $discoparameters = array( 'entityID' => $spentityid, 'return' => SimpleSAML_Utilities::selfURL(), - 'returnIDParam' => 'idpentityid') - ); + 'returnIDParam' => 'idpentityid'); + + $discoparameters['isPassive'] = $isPassive; + + if (sizeof($reachableIDPs) > 0) { + $discoparameters['IDPList'] = $reachableIDPs; + } + + SimpleSAML_Utilities::redirect($discourl, $discoparameters); } @@ -98,9 +137,19 @@ try { $ar->setProtocolBinding(SAML2_Const::BINDING_HTTP_POST); $ar->setRelayState($_REQUEST['RelayState']); - if (isset($_GET['IsPassive'])) { - $ar->setIsPassive((bool)$_GET['IsPassive']); + $ar->setIsPassive($isPassive); + $ar->setForceAuthn($forceAuthn); + + if(array_key_exists('IDPList', $spmetadata)) { + $IDPList = array_unique(array_merge($IDPList, $spmetadata['IDPList'])); } + + if (isset($_GET['IDPList']) && !empty($_GET['IDPList'])) { + $providers = $_GET['IDPList']; + if (!is_array($providers)) $providers = array($providers); + $IDPList = array_merge($IDPList, $providers); + }; + $ar->setIDPList($IDPList); /* Save request information. */ $info = array();