From 58e1ba0bd25dff9ba4cc4ac0de2d029f97a9265c Mon Sep 17 00:00:00 2001
From: Olav Morken <olav.morken@uninett.no>
Date: Mon, 8 Sep 2008 13:30:35 +0000
Subject: [PATCH] Metadata parser: Determine when the metadata expires, and add
 it to the generated metadata.

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@861 44740490-163a-0410-bde0-09ae8108e29a
---
 lib/SimpleSAML/Metadata/SAMLParser.php | 127 ++++++++++++++++++++++---
 1 file changed, 115 insertions(+), 12 deletions(-)

diff --git a/lib/SimpleSAML/Metadata/SAMLParser.php b/lib/SimpleSAML/Metadata/SAMLParser.php
index 60b528591..49c920dca 100644
--- a/lib/SimpleSAML/Metadata/SAMLParser.php
+++ b/lib/SimpleSAML/Metadata/SAMLParser.php
@@ -117,8 +117,11 @@ class SimpleSAML_Metadata_SAMLParser {
 	 * @param $entitiesValidator  A Validator instance for a signature element in the EntitiesDescriptor,
 	 *                            or NULL if this EntityDescriptor isn't a child of an EntitiesDescriptor
 	 *                            with a Signature element.
+	 * @param int|NULL $expireTime  The unix timestamp for when this entity should expire, or NULL if unknwon.
 	 */
-	private function __construct($entityElement, $entitiesValidator) {
+	private function __construct($entityElement, $entitiesValidator, $expireTime) {
+		assert('is_null($expireTime) || is_int($expireTime)');
+
 		$this->spDescriptors = array();
 		$this->idpDescriptors = array();
 
@@ -133,6 +136,13 @@ class SimpleSAML_Metadata_SAMLParser {
 		}
 		$this->entityId = $entityElement->getAttribute('entityID');
 
+		if ($expireTime === NULL) {
+			/* No expiry time defined by a parent element. Check if this element defines
+			 * one.
+			 */
+			$expireTime = self::getExpireTime($entityElement);
+		}
+
 
 		/* Check if the Signature element from the EntitiesDescriptor can be used to verify this
 		 * EntityDescriptor, and add it to the list of validators if it is.
@@ -155,11 +165,11 @@ class SimpleSAML_Metadata_SAMLParser {
 			}
 
 			if(SimpleSAML_Utilities::isDOMElementOfType($child, 'SPSSODescriptor', '@md') === TRUE) {
-				$this->processSPSSODescriptor($child);
+				$this->processSPSSODescriptor($child, $expireTime);
 			}
 
 			if(SimpleSAML_Utilities::isDOMElementOfType($child, 'IDPSSODescriptor', '@md') === TRUE) {
-				$this->processIDPSSODescriptor($child);
+				$this->processIDPSSODescriptor($child, $expireTime);
 			}
 
 			if(SimpleSAML_Utilities::isDOMElementOfType($child, 'Organization', '@md') === TRUE) {
@@ -229,7 +239,7 @@ class SimpleSAML_Metadata_SAMLParser {
 	public static function parseElement($entityElement) {
 		assert('$entityElement instanceof DOMElement');
 
-		return new SimpleSAML_Metadata_SAMLParser($entityElement, NULL);
+		return new SimpleSAML_Metadata_SAMLParser($entityElement, NULL, NULL);
 	}
 
 
@@ -292,11 +302,11 @@ class SimpleSAML_Metadata_SAMLParser {
 
 		assert('$element instanceof DOMElement');
 
-
 		$entitiesValidator = NULL;
 
 		if(SimpleSAML_Utilities::isDOMElementOfType($element, 'EntityDescriptor', '@md') === TRUE) {
 			$elements = array($element);
+			$expireTime = NULL;
 		} elseif(SimpleSAML_Utilities::isDOMElementOfType($element, 'EntitiesDescriptor', '@md') === TRUE) {
 
 			/* Check if there is a signature element in the EntitiesDescriptor. */
@@ -308,6 +318,8 @@ class SimpleSAML_Metadata_SAMLParser {
 				}
 			}
 
+			$expireTime = self::getExpireTime($element);
+
 			$elements = SimpleSAML_Utilities::getDOMChildren($element, 'EntityDescriptor', '@md');
 		} else {
 			throw new Exception('Unexpected root node: [' . $element->namespaceURI . ']:' .
@@ -316,7 +328,7 @@ class SimpleSAML_Metadata_SAMLParser {
 
 		$ret = array();
 		foreach($elements as $e) {
-			$entity = new SimpleSAML_Metadata_SAMLParser($e, $entitiesValidator);
+			$entity = new SimpleSAML_Metadata_SAMLParser($e, $entitiesValidator, $expireTime);
 			$ret[$entity->getEntityId()] = $entity;
 		}
 
@@ -324,6 +336,54 @@ class SimpleSAML_Metadata_SAMLParser {
 	}
 
 
+	/**
+	 * Determine how long a given element can be cached.
+	 *
+	 * This function looks for the 'cacheDuration' and 'validUntil' attributes to determine
+	 * how long a given XML-element is valid. It returns this as na unix timestamp.
+	 *
+	 * If both the 'cacheDuration' and 'validUntil' attributes are present, the shorter of them
+	 * will be returned.
+	 *
+	 * @param DOMElement $element  The element we should determine the expiry time of.
+	 * @return int  The unix timestamp for when the element should expire. Will be NULL if no
+	 *              limit is set for the element.
+	 */
+	private static function getExpireTime(DOMElement $element) {
+
+		if ($element->hasAttribute('cacheDuration')) {
+			$cacheDuration = $element->getAttribute('cacheDuration');
+			$cacheDuration = SimpleSAML_Utilities::parseDuration($cacheDuration, time());
+		} else {
+			$cacheDuration = NULL;
+		}
+
+		if ($element->hasAttribute('validUntil')) {
+			$validUntil = $element->getAttribute('validUntil');
+			$validUntil = SimpleSAML_Utilities::parseSAML2Time($validUntil);
+		} else {
+			$validUntil = NULL;
+		}
+
+		if ($cacheDuration !== NULL && $validUntil !== NULL) {
+			/* Both are given. Return the shortest. */
+
+			if($cacheDuration < $validUntil) {
+				return $cacheDuration;
+			} else {
+				return $validUntil;
+			}
+
+		} elseif ($cacheDuration !== NULL) {
+			return $cacheDuration;
+		} elseif ($validUntil !== NULL) {
+			return $validUntil;
+		} else {
+			return NULL;
+		}
+	}
+
+
 	/**
 	 * This function returns the entity id of this parsed entity.
 	 *
@@ -361,6 +421,11 @@ class SimpleSAML_Metadata_SAMLParser {
 		/* We currently only look at the first SPDescriptor which supports SAML 1.x. */
 		$spd = $spd[0];
 
+		/* Add expire time to metadata. */
+		if (array_key_exists('expire', $spd)) {
+			$ret['expire'] = $spd['expire'];
+		}
+
 		/* Find the assertion consumer service endpoint. */
 		$acs = $this->getDefaultEndpoint($spd['assertionConsumerServices'], array(self::SAML_1X_POST_BINDING));
 		if($acs === NULL) {
@@ -408,6 +473,11 @@ class SimpleSAML_Metadata_SAMLParser {
 		/* We currently only look at the first IDP descriptor which supports SAML 1.x. */
 		$idp = $idp[0];
 
+		/* Add expire time to metadata. */
+		if (array_key_exists('expire', $idp)) {
+			$ret['expire'] = $idp['expire'];
+		}
+
 		/* Find the SSO service endpoint. */
 		$sso = $this->getDefaultEndpoint($idp['singleSignOnServices'], array(self::SAML_1x_AUTHN_REQUEST));
 		if($sso === NULL) {
@@ -475,6 +545,11 @@ class SimpleSAML_Metadata_SAMLParser {
 		/* We currently only look at the first SPDescriptor which supports SAML 2.0. */
 		$spd = $spd[0];
 
+		/* Add expire time to metadata. */
+		if (array_key_exists('expire', $spd)) {
+			$ret['expire'] = $spd['expire'];
+		}
+
 		/* Find the assertion consumer service endpoint. */
 		$acs = $this->getDefaultEndpoint($spd['assertionConsumerServices'], array(self::SAML_20_POST_BINDING));
 		if($acs === NULL) {
@@ -538,6 +613,11 @@ class SimpleSAML_Metadata_SAMLParser {
 		/* We currently only look at the first IDP descriptor which supports SAML 2.0. */
 		$idp = $idp[0];
 
+		/* Add expire time to metadata. */
+		if (array_key_exists('expire', $idp)) {
+			$ret['expire'] = $idp['expire'];
+		}
+
 		/* Find the SSO service endpoint. */
 		$sso = $this->getDefaultEndpoint($idp['singleSignOnServices'], array(self::SAML_20_REDIRECT_BINDING));
 		if($sso === NULL) {
@@ -615,14 +695,31 @@ class SimpleSAML_Metadata_SAMLParser {
 	 * - 'keys': Array of associative arrays with the elements from parseKeyDescriptor:
 	 *
 	 * @param $element The element we should extract metadata from.
+	 * @param int|NULL $expireTime  The unix timestamp for when this element should expire, or
+	 *                              NULL if unknwon.
 	 * @return Associative array with metadata we have extracted from this element.
 	 */
-	private static function parseSSODescriptor($element) {
-
+	private static function parseSSODescriptor($element, $expireTime) {
 		assert('$element instanceof DOMElement');
+		assert('is_null($expireTime) || is_int($expireTime)');
+
+		if ($expireTime === NULL) {
+			/* No expiry time defined by a parent element. Check if this element defines
+			 * one.
+			 */
+			$expireTime = self::getExpireTime($element);
+		}
+
 
 		$sd = array();
 
+		if ($expireTime !== NULL) {
+			/* We have got an expire timestamp, either from this element, or one of the
+			 * parent elements.
+			 */
+			$sd['expire'] = $expireTime;
+		}
+
 		$sd['protocols'] = self::getSupportedProtocols($element);
 
 		/* Find all SingleLogoutService elements. */
@@ -658,11 +755,14 @@ class SimpleSAML_Metadata_SAMLParser {
 	 * This function extracts metadata from a SPSSODescriptor element.
 	 *
 	 * @param $element The element which should be parsed.
+	 * @param int|NULL $expireTime  The unix timestamp for when this element should expire, or
+	 *                              NULL if unknwon.
 	 */
-	private function processSPSSODescriptor($element) {
+	private function processSPSSODescriptor($element, $expireTime) {
 		assert('$element instanceof DOMElement');
+		assert('is_null($expireTime) || is_int($expireTime)');
 
-		$sp = self::parseSSODescriptor($element);
+		$sp = self::parseSSODescriptor($element, $expireTime);
 
 		/* Find all AssertionConsumerService elements. */
 		$sp['assertionConsumerServices'] = array();
@@ -680,11 +780,14 @@ class SimpleSAML_Metadata_SAMLParser {
 	 * This function extracts metadata from a IDPSSODescriptor element.
 	 *
 	 * @param $element The element which should be parsed.
+	 * @param int|NULL $expireTime  The unix timestamp for when this element should expire, or
+	 *                              NULL if unknwon.
 	 */
-	private function processIDPSSODescriptor($element) {
+	private function processIDPSSODescriptor($element, $expireTime) {
 		assert('$element instanceof DOMElement');
+		assert('is_null($expireTime) || is_int($expireTime)');
 
-		$idp = self::parseSSODescriptor($element);
+		$idp = self::parseSSODescriptor($element, $expireTime);
 
 		/* Find all SingleSignOnService elements. */
 		$idp['singleSignOnServices'] = array();
-- 
GitLab