From ac90032707b7ab74c697cc62529768cdba96b193 Mon Sep 17 00:00:00 2001
From: Olav Morken <olav.morken@uninett.no>
Date: Tue, 1 Apr 2008 14:09:07 +0000
Subject: [PATCH] Add support for ForceAuthn on the IdP side.

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@481 44740490-163a-0410-bde0-09ae8108e29a
---
 docs/source/simplesamlphp-idp.xml          | 10 +++++
 lib/SimpleSAML/Session.php                 | 16 ++++++++
 lib/SimpleSAML/XML/SAML20/AuthnRequest.php | 30 ++++++++++++++
 www/admin/metadata.php                     |  2 +-
 www/saml2/idp/SSOService.php               | 46 ++++++++++++++++++++++
 5 files changed, 103 insertions(+), 1 deletion(-)

diff --git a/docs/source/simplesamlphp-idp.xml b/docs/source/simplesamlphp-idp.xml
index 1c123bf4a..337411ad2 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 997f8d3a2..8d689f0f3 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 2b9f36a2e..4e4412f14 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 53579c55a..50634e27f 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 c7ae11bbb..d7f44fd84 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']);
 
-- 
GitLab