diff --git a/docs/source/simplesamlphp-idp.xml b/docs/source/simplesamlphp-idp.xml index 1c123bf4ad56f1d893144c665368388a373cbe88..337411ad25361cab29aad188c8ee7f83079f1acd 100644 --- a/docs/source/simplesamlphp-idp.xml +++ b/docs/source/simplesamlphp-idp.xml @@ -529,6 +529,16 @@ openssl x509 -req -days 60 -in server2.csr -signkey server2.key -out server2.crt <literal>true</literal>.</para> </glossdef> </glossentry> + + <glossentry> + <glossterm>ForceAuthn</glossterm> + + <glossdef> + <para>Set this <literal>true</literal> to force authentication + when receiving authentication requests from this SP. The default + is <literal>false</literal>.</para> + </glossdef> + </glossentry> </glosslist> </section> </section> diff --git a/lib/SimpleSAML/Session.php b/lib/SimpleSAML/Session.php index 997f8d3a203e1e31b4d1f7a51594204b9c27ce08..8d689f0f3aaa8f9b4cd281d110b1d27410000a5d 100644 --- a/lib/SimpleSAML/Session.php +++ b/lib/SimpleSAML/Session.php @@ -335,6 +335,7 @@ class SimpleSAML_Session implements SimpleSAML_ModifiedInfo { $this->authenticated = $auth; if ($auth) { + $this->clearNeedAuthFlag(); $this->sessionstarted = time(); } else { /* Call logout handlers. */ @@ -473,6 +474,21 @@ class SimpleSAML_Session implements SimpleSAML_ModifiedInfo { $this->logout_handlers = array(); } + + /** + * This function iterates over all current authentication requests, and removes any 'NeedAuthentication' flags + * from them. + */ + private function clearNeedAuthFlag() { + foreach($this->authnrequests as &$cache) { + foreach($cache as &$request) { + if(array_key_exists('NeedAuthentication', $request)) { + $request['NeedAuthentication'] = FALSE; + } + } + } + } + } ?> \ No newline at end of file diff --git a/lib/SimpleSAML/XML/SAML20/AuthnRequest.php b/lib/SimpleSAML/XML/SAML20/AuthnRequest.php index 2b9f36a2e6da2988a449ef5e4d961ef52fa99ed2..4e4412f147f6959eb05dd11cc2a16261c1c7e6f9 100644 --- a/lib/SimpleSAML/XML/SAML20/AuthnRequest.php +++ b/lib/SimpleSAML/XML/SAML20/AuthnRequest.php @@ -107,6 +107,36 @@ class SimpleSAML_XML_SAML20_AuthnRequest { } + /** + * This function retrieves the ForceAuthn flag from this authentication request. + * + * @return The ForceAuthn flag from this authentication request. + */ + public function getForceAuthn() { + $dom = $this->getDOM(); + if (empty($dom)) { + throw new Exception("Could not get message DOM in AuthnRequest object"); + } + + $root = $dom->documentElement; + + if(!$root->hasAttribute('ForceAuthn')) { + /* ForceAuthn defaults to false. */ + return FALSE; + } + + $fa = $root->getAttribute('ForceAuthn'); + if($fa === 'true') { + return TRUE; + } elseif($fa === 'false') { + return FALSE; + } else { + throw new Exception('Invalid value of ForceAuthn attribute in SAML2 AuthnRequest.'); + } + } + + + /** * Generate a new SAML 2.0 Authentication Request * diff --git a/www/admin/metadata.php b/www/admin/metadata.php index 53579c55a21185e2f25bb5e53350cf71e27e0653..50634e27f03d28d411c210acd69657429158afb4 100644 --- a/www/admin/metadata.php +++ b/www/admin/metadata.php @@ -67,7 +67,7 @@ try { foreach ($metalist AS $entityid => $mentry) { $results[$entityid] = SimpleSAML_Utilities::checkAssocArrayRules($mentry, array('entityid', 'AssertionConsumerService'), - array('SingleLogoutService', 'NameIDFormat', 'SPNameQualifier', 'base64attributes', 'simplesaml.nameidattribute', 'attributemap', 'attributealter', 'simplesaml.attributes', 'attributes', 'name', 'description','request.signing','certificate') + array('SingleLogoutService', 'NameIDFormat', 'SPNameQualifier', 'base64attributes', 'simplesaml.nameidattribute', 'attributemap', 'attributealter', 'simplesaml.attributes', 'attributes', 'name', 'description','request.signing','certificate', 'ForceAuthn') ); } $et->data['metadata.saml20-sp-remote'] = $results; diff --git a/www/saml2/idp/SSOService.php b/www/saml2/idp/SSOService.php index c7ae11bbb1dc13a50590b6beeda930dbcc888916..d7f44fd849f26846f9879bb284b5979cb0f11e93 100644 --- a/www/saml2/idp/SSOService.php +++ b/www/saml2/idp/SSOService.php @@ -75,6 +75,41 @@ if (isset($_GET['SAMLRequest'])) { if ($relaystate = $authnrequest->getRelayState() ) $requestcache['RelayState'] = $relaystate; + + /* + * Handle the ForceAuthn option. + */ + + /* 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']; + if(!is_bool($spmetadata['ForceAuthn'])) { + throw new Exception('The ForceAuthn option in the metadata for the sp [' . $spentityid . '] is not a boolean.'); + } + + if($spmetadata['ForceAuthn']) { + /* ForceAuthn enabled in the metadata for the SP. */ + $forceAuthn = TRUE; + } + } + + if($authnrequest->getForceAuthn()) { + /* The ForceAuthn flag was set to true in the authentication request. */ + $forceAuthn = TRUE; + } + + if($forceAuthn) { + /* ForceAuthn is enabled. Mark the request as needing authentication. This flag + * will be cleared by a call to setAuthenticated(TRUE, ...) to the current session. + */ + $requestcache['NeedAuthentication'] = TRUE; + } + $session->setAuthnRequest('saml2', $requestid, $requestcache); @@ -129,6 +164,17 @@ $authority = isset($idpmetadata['authority']) ? $idpmetadata['authority'] : NULL * 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; +} else { + /* We have a valid session. */ + $needAuth = FALSE; +} + +if($needAuth) { SimpleSAML_Logger::info('SAML2.0 - IdP.SSOService: Will go to authentication module ' . $idpmetadata['auth']);