<?php

/*
 * This file is part of simpleSAMLphp. See the file COPYING in the
 * root of the distribution for licence information.
 *
 * This file defines a base class for metadata handling.
 * Instantiation of session handler objects should be done through
 * the class method getMetadataHandler().
 */

require_once('SimpleSAML/Configuration.php');
require_once('SimpleSAML/Utilities.php');
require_once('SimpleSAML/XML/Parser.php');
require_once('SimpleSAML/Metadata/MetaDataStorageHandler.php');

/**
 * Configuration of SimpleSAMLphp
 */
class SimpleSAML_Metadata_MetaDataStorageHandlerSAML2Meta extends SimpleSAML_Metadata_MetaDataStorageHandler {


	private static $cachedfiles;

	/* This constructor is included in case it is needed in the the
	 * future. Including it now allows us to write parent::__construct() in
	 * the subclasses of this class.
	 */
	protected function __construct() {
		if (!isset($this->cachedfiles)) $this->cachedfiles = array();
	}


	public function load($set) {
		$metadata = null;
		if (!in_array($set, array(
			'saml20-sp-hosted', 'saml20-sp-remote','saml20-idp-hosted', 'saml20-idp-remote',
			'shib13-sp-hosted', 'shib13-sp-remote', 'shib13-idp-hosted', 'shib13-idp-remote' ))) {
				throw new Exception('Trying to load illegal set of Meta data [' . $set . ']');
		}
		
		$settofile = array(
			'saml20-sp-hosted' => 'saml20-hosted',
			'saml20-idp-hosted' => 'saml20-hosted',
			'saml20-sp-remote' => 'saml20-remote',
			'saml20-idp-remote' => 'saml20-remote',
			'shib13-sp-hosted' => 'shib13-hosted',
			'shib13-idp-hosted' => 'shib13-hosted',
			'shib13-sp-remote' => 'shib13-remote',
			'shib13-idp-remote' => 'shib13-remote'
		);
		
		/* Get the configuration. */
		$config = SimpleSAML_Configuration::getInstance();
		assert($config instanceof SimpleSAML_Configuration);

		
		$metadatasetfile = $config->getBaseDir() . '' . 
			$config->getValue('metadatadir') . 'xml/' . $settofile[$set] . '.xml';
		
		if (array_key_exists($metadatasetfile, $this->cachedfiles)) {
			$metadataxml = self::$cachedfiles[$metadatasetfile];
		} else {
			if (!file_exists($metadatasetfile)) throw new Exception('Could not find SAML 2.0 Metadata file :'. $metadatasetfile);
		
			// for now testing with the shib aai metadata...
			//$metadataxml = file_get_contents("http://www.switch.ch/aai/federation/SWITCHaai/metadata.switchaai_signed.xml");
			$metadataxml = file_get_contents($metadatasetfile);
			self::$cachedfiles[$metadatasetfile] = $metadataxml;
		}
		
		$metadata = null;
		switch ($set) {
			case 'saml20-idp-remote' : $metadata = $this->getmetadata_saml20idpremote($metadataxml); break;
			case 'saml20-idp-hosted' : $metadata = $this->getmetadata_saml20idphosted($metadataxml); break;
			case 'saml20-sp-remote' : $metadata = $this->getmetadata_saml20spremote($metadataxml); break; 
			case 'saml20-sp-hosted' : $metadata = $this->getmetadata_saml20sphosted($metadataxml); break; 
			case 'shib13-idp-remote' : $metadata = $this->getmetadata_shib13idpremote($metadataxml); break;
			case 'shib13-idp-hosted' : throw new Exception('Meta data parsing for Shib 1.3 IdP Hosted not yet implemented.');
			case 'shib13-sp-remote' : throw new Exception('Meta data parsing for Shib 1.3 SP Remote not yet implemented.');
			case 'shib13-sp-hosted' : throw new Exception('Meta data parsing for Shib 1.3 SP Hosted not yet implemented.');
		}
		
		if (!is_array($metadata)) {
			throw new Exception('Could not load metadata set [' . $set . '] from file: ' . $metadatasetfile);
		}
		/*
		echo '<pre>';
		print_r($metadata);
		echo '</pre>';
		exit();
		*/
		foreach ($metadata AS $key => $entry) { 
			$this->metadata[$set][$key] = $entry;
			$this->metadata[$set][$key]['entityid'] = $key;
			
			if (isset($entry['host'])) {
				$this->hostmap[$set][$entry['host']] = $key;
			}
		}

	}
	
	
	private function getmetadata_saml20idpremote($metadataxml) {
		// Create a parser for the metadata document.
		$metadata_parser = new SimpleSAML_XML_Parser($metadataxml);
		
		// Get all entries in the metadata.
		$idpentities = $metadata_parser->simplexml->xpath('/saml2meta:EntitiesDescriptor/saml2meta:EntityDescriptor[./saml2meta:IDPSSODescriptor]');
		if (!$idpentities) throw new Exception('Could not find any entity descriptors in the meta data file: ' . $metadatasetfile);
		
		// Array to hold the resulting metadata, to return at the end of this function.
		$metadata = array();
		
		// Traverse all entries.
		foreach ($idpentities as $idpentity) {
			try {
				$entityid = (string) $idpentity['entityID'];
				if (!$entityid) throw new Exception('Could not find entityID in element');
				
				$metadata[$entityid] = array('entityid' => $entityid);				
				$metadata_entry = SimpleSAML_XML_Parser::fromSimpleXMLElement($idpentity);
				
				$metadata[$entityid]['SingleSignOnService'] = $metadata_entry->getValue("/saml2meta:EntityDescriptor/saml2meta:IDPSSODescriptor/saml2meta:SingleSignOnService[@Binding='urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect']/@Location", true);

				$metadata[$entityid]['SingleLogoutService'] = $metadata_entry->getValue("/saml2meta:EntityDescriptor/saml2meta:IDPSSODescriptor/saml2meta:SingleLogoutService[@Binding='urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect']/@Location", true);
				
				$metadata[$entityid]['certFingerprint'] = SimpleSAML_Utilities::cert_fingerprint($metadata_entry->getValue("/saml2meta:EntityDescriptor/saml2meta:IDPSSODescriptor/saml2meta:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate", true));
				
				$seek_base64 = $metadata_entry->getValue("/saml2meta:EntityDescriptor/saml2meta:IDPSSODescriptor/saml2meta:Extensions/saml2:Attribute[@Name='urn:mace:feide.no:simplesamlphp:base64attributes']/saml2:AttributeValue");
				$metadata[$entityid]['base64attributes'] = (isset($seek_base64) ? ($seek_base64 === 'true') : false);
				
				
				$metadata[$entityid]['name'] = $metadata_entry->getValueAlternatives(
					array("/saml2meta:EntityDescriptor/saml2meta:IDPSSODescriptor/saml2meta:Extensions/saml2:Attribute[@Name='urn:mace:feide.no:simplesamlphp:name']/saml2:AttributeValue",
					"/saml2meta:EntityDescriptor/saml2meta:IDPSSODescriptor/saml2meta:Organization/saml2meta:OrganizationDisplayName"
					));
					
				$metadata[$entityid]['description'] = $metadata_entry->getValue("/saml2meta:EntityDescriptor/saml2meta:IDPSSODescriptor/saml2meta:Extensions/saml2:Attribute[@Name='urn:mace:feide.no:simplesamlphp:description']/saml2:AttributeValue");
												

			} catch (Exception $e) {
				echo 'Error reading one metadata entry: ' . $e;
			}

		}
		return $metadata;
	}


	private function getmetadata_saml20sphosted($metadataxml) {
		// Create a parser for the metadata document.
		$metadata_parser = new SimpleSAML_XML_Parser($metadataxml);
		
		// Get all entries in the metadata.
		$idpentities = $metadata_parser->simplexml->xpath('/saml2meta:EntitiesDescriptor/saml2meta:EntityDescriptor[./saml2meta:SPSSODescriptor]');
		if (!$idpentities) throw new Exception('Could not find any entity descriptors in the meta data file: ' . $metadatasetfile);
		
		// Array to hold the resulting metadata, to return at the end of this function.
		$metadata = array();
		
		// Traverse all entries.
		foreach ($idpentities as $idpentity) {
			try {
				$entityid = (string) $idpentity['entityID'];
				if (!$entityid) throw new Exception('Could not find entityID in element');
				
				$metadata[$entityid] = array('entityid' => $entityid);				
				$metadata_entry = SimpleSAML_XML_Parser::fromSimpleXMLElement($idpentity);

				$metadata[$entityid]['NameIDFormat'] = $metadata_entry->getValue("/saml2meta:EntityDescriptor/saml2meta:SPSSODescriptor/saml2meta:NameIDFormat", true);
					
				$metadata[$entityid]['host'] = $metadata_entry->getValue("/saml2meta:EntityDescriptor/saml2meta:SPSSODescriptor/saml2meta:Extensions/saml2:Attribute[@Name='urn:mace:feide.no:simplesamlphp:host']/saml2:AttributeValue");
												

				$seek_forceauth = $metadata_entry->getValue("/saml2meta:EntityDescriptor/saml2meta:SPSSODescriptor/saml2meta:Extensions/saml2:Attribute[@Name='urn:mace:feide.no:simplesamlphp:ForceAuthn']/saml2:AttributeValue");
				$metadata[$entityid]['ForceAuthn'] = (isset($seek_forceauth) ? ($seek_forceauth === 'true') : false);
				
			} catch (Exception $e) {
				echo 'Error reading one metadata entry: ' . $e;
			}

		}
		return $metadata;
	}
	
	private function getmetadata_saml20idphosted($metadataxml) {
		// Create a parser for the metadata document.
		$metadata_parser = new SimpleSAML_XML_Parser($metadataxml);
		
		// Get all entries in the metadata.
		$idpentities = $metadata_parser->simplexml->xpath('/saml2meta:EntitiesDescriptor/saml2meta:EntityDescriptor[./saml2meta:IDPSSODescriptor]');
		if (!$idpentities) throw new Exception('Could not find any entity descriptors in the meta data file.');
		
		// Array to hold the resulting metadata, to return at the end of this function.
		$metadata = array();
		
		
		/*
			required	array('entityid', 'host', 'privatekey', 'certificate', 'auth'),
			optional	array('base64attributes', 'requireconsent')
		*/
		
		// Traverse all entries.
		foreach ($idpentities as $idpentity) {
			try {
				$entityid = (string) $idpentity['entityID'];
				if (!$entityid) throw new Exception('Could not find entityID in element');
				
				$metadata[$entityid] = array('entityid' => $entityid);				
				$metadata_entry = SimpleSAML_XML_Parser::fromSimpleXMLElement($idpentity);

				$metadata[$entityid]['host'] = $metadata_entry->getValue("/saml2meta:EntityDescriptor/saml2meta:IDPSSODescriptor/saml2meta:Extensions/saml2:Attribute[@Name='urn:mace:feide.no:simplesamlphp:host']/saml2:AttributeValue");

				$metadata[$entityid]['privatekey'] = $metadata_entry->getValue("/saml2meta:EntityDescriptor/saml2meta:IDPSSODescriptor/saml2meta:Extensions/saml2:Attribute[@Name='urn:mace:feide.no:simplesamlphp:privatekey']/saml2:AttributeValue", true);

				$metadata[$entityid]['certificate'] = $metadata_entry->getValue("/saml2meta:EntityDescriptor/saml2meta:IDPSSODescriptor/saml2meta:Extensions/saml2:Attribute[@Name='urn:mace:feide.no:simplesamlphp:certificate']/saml2:AttributeValue", true);

				$metadata[$entityid]['auth'] = $metadata_entry->getValue("/saml2meta:EntityDescriptor/saml2meta:IDPSSODescriptor/saml2meta:Extensions/saml2:Attribute[@Name='urn:mace:feide.no:simplesamlphp:auth']/saml2:AttributeValue", true);
				
				$seek_requireconsent = $metadata_entry->getValue("/saml2meta:EntityDescriptor/saml2meta:IDPSSODescriptor/saml2meta:Extensions/saml2:Attribute[@Name='urn:mace:feide.no:simplesamlphp:requireconsent']/saml2:AttributeValue");
				$metadata[$entityid]['requireconsent'] = (isset($seek_requireconsent) ? ($seek_requireconsent === 'true') : false);
				
			} catch (Exception $e) {
				// TODO: do syslog, not echo.
				echo 'Error reading one metadata entry: ' . $e;
			}

		}
		return $metadata;
	}
	
	
	private function getmetadata_saml20spremote($metadataxml) {
		// Create a parser for the metadata document.
		$metadata_parser = new SimpleSAML_XML_Parser($metadataxml);
		
		// Get all entries in the metadata.
		$idpentities = $metadata_parser->simplexml->xpath('/saml2meta:EntitiesDescriptor/saml2meta:EntityDescriptor[./saml2meta:SPSSODescriptor]');
		if (!$idpentities) throw new Exception('Could not find any entity descriptors in the meta data file: ' . $metadatasetfile);
		
		// Array to hold the resulting metadata, to return at the end of this function.
		$metadata = array();
		
		// Traverse all entries.
		foreach ($idpentities as $idpentity) {
			try {
				$entityid = (string) $idpentity['entityID'];
				if (!$entityid) throw new Exception('Could not find entityID in element');
				
				
				/*
					array('entityid', 'spNameQualifier', 'AssertionConsumerService', 'SingleLogoutService', 'NameIDFormat'),
					array('base64attributes', 'attributemap', 'simplesaml.attributes', 'attributes')
				*/
				
				$metadata[$entityid] = array('entityid' => $entityid);				
				$metadata_entry = SimpleSAML_XML_Parser::fromSimpleXMLElement($idpentity);

				$metadata[$entityid]['spNameQualifier'] = $metadata_entry->getValue("/saml2meta:EntityDescriptor/saml2meta:SPSSODescriptor/saml2meta:Extensions/saml2:Attribute[@Name='urn:mace:feide.no:simplesamlphp:spnamequalifier']/saml2:AttributeValue");
				
				$metadata[$entityid]['NameIDFormat'] = $metadata_entry->getValue("/saml2meta:EntityDescriptor/saml2meta:SPSSODescriptor/saml2meta:NameIDFormat", true);
												

				$seek_base64 = $metadata_entry->getValue("/saml2meta:EntityDescriptor/saml2meta:SPSSODescriptor/saml2meta:Extensions/saml2:Attribute[@Name='urn:mace:feide.no:simplesamlphp:base64attributes']/saml2:AttributeValue");
				$metadata[$entityid]['base64attributes'] = (isset($seek_base64) ? ($seek_base64 === 'true') : false);
				
				
				$metadata[$entityid]['name'] = $metadata_entry->getValueAlternatives(
					array("/saml2meta:EntityDescriptor/saml2meta:SPSSODescriptor/saml2meta:Extensions/saml2:Attribute[@Name='urn:mace:feide.no:simplesamlphp:name']/saml2:AttributeValue",
					"/saml2meta:EntityDescriptor/saml2meta:SPSSODescriptor/saml2meta:Organization/saml2meta:OrganizationDisplayName"
					));
					
				$metadata[$entityid]['description'] = $metadata_entry->getValue("/saml2meta:EntityDescriptor/saml2meta:SPSSODescriptor/saml2meta:Extensions/saml2:Attribute[@Name='urn:mace:feide.no:simplesamlphp:description']/saml2:AttributeValue");

				$metadata[$entityid]['simplesaml.attributes'] = $metadata_entry->getValue("/saml2meta:EntityDescriptor/saml2meta:SPSSODescriptor/saml2meta:Extensions/saml2:Attribute[@Name='urn:mace:feide.no:simplesamlphp:simplesaml.attributes']/saml2:AttributeValue");
				
				
				$seek_attributes = $metadata_entry->getValue("/saml2meta:EntityDescriptor/saml2meta:SPSSODescriptor/saml2meta:Extensions/saml2:Attribute[@Name='urn:mace:feide.no:simplesamlphp:attributes']/saml2:AttributeValue");
				if (isset($seek_attributes)) $metadata[$entityid]['attributes'] = explode(',', $seek_attributes);
				
				$metadata[$entityid]['attributemap'] = $metadata_entry->getValue("/saml2meta:EntityDescriptor/saml2meta:SPSSODescriptor/saml2meta:Extensions/saml2:Attribute[@Name='urn:mace:feide.no:simplesamlphp:attributemap']/saml2:AttributeValue");
				
				$metadata[$entityid]['AssertionConsumerService'] = $metadata_entry->getValue("/saml2meta:EntityDescriptor/saml2meta:SPSSODescriptor/saml2meta:AssertionConsumerService/@Location", true);
				
				$metadata[$entityid]['SingleLogoutService'] = $metadata_entry->getValue("/saml2meta:EntityDescriptor/saml2meta:SPSSODescriptor/saml2meta:SingleLogoutService/@Location", true);
				
				
				
			} catch (Exception $e) {
				echo 'Error reading one metadata entry: ' . $e;
			}

		}
		return $metadata;
	}
	
	
	
	private function getmetadata_shib13idpremote($metadataxml) {
		// Create a parser for the metadata document.
		$metadata_parser = new SimpleSAML_XML_Parser($metadataxml);
		
		// Get all entries in the metadata.
		$idpentities = $metadata_parser->simplexml->xpath('/saml2meta:EntitiesDescriptor/saml2meta:EntityDescriptor[./saml2meta:IDPSSODescriptor]');
		if (!$idpentities) throw new Exception('Could not find any entity descriptors in the meta data file: ' . $metadatasetfile);
		
		// Array to hold the resulting metadata, to return at the end of this function.
		$metadata = array();
		
		// Traverse all entries.
		foreach ($idpentities as $idpentity) {
			try {
				$entityid = (string) $idpentity['entityID'];
				if (!$entityid) throw new Exception('Could not find entityID in element');
				
				$metadata[$entityid] = array('entityid' => $entityid);				
				$metadata_entry = SimpleSAML_XML_Parser::fromSimpleXMLElement($idpentity);
				
				$metadata[$entityid]['SingleSignOnService'] = $metadata_entry->getValue("/saml2meta:EntityDescriptor/saml2meta:IDPSSODescriptor/saml2meta:SingleSignOnService[@Binding='urn:mace:shibboleth:1.0:profiles:AuthnRequest']/@Location", true);

			} catch (Exception $e) {
				echo 'Error reading one metadata entry: ' . $e;
			}

		}
		return $metadata;
	}
	
	public function getMetaData($entityid = null, $set = 'saml20-sp-hosted') {
		if (!isset($entityid)) {
			return $this->getMetaDataCurrent($set);
		}
		
		//echo 'find metadata for entityid [' . $entityid . '] in metadata set [' . $set . ']';
		
		if (!isset($this->metadata[$set])) {
			$this->load($set);
		}
		if (!isset($this->metadata[$set][$entityid]) ) {
			throw new Exception('Could not find metadata for entityid [' . $entityid . '] in metadata set [' . $set . ']');
		}
		return $this->metadata[$set][$entityid];
	}
	


	
	
}

?>