diff --git a/config/config-template.php b/config/config-template.php index baaf2b4eb9fb9102c4a62398efe795145af143ea..3da780acd732ffd8fdec8a156855dbecb85e577a 100644 --- a/config/config-template.php +++ b/config/config-template.php @@ -140,14 +140,42 @@ $config = array ( /* - * Meta data handler. + * This option configures the metadata sources. The metadata sources is given as an array with + * different metadata sources. When searching for metadata, simpleSAMPphp will search through + * the array from start to end. * - * Options: [flatfile,saml2xmlmeta] + * Each element in the array is an associative array which configures the metadata source. + * The type of the metadata source is given by the 'type' element. For each type we have + * different configuration options. * + * Flat file metadata handler: + * - 'type': This is always 'flatfile'. + * - 'directory': The directory we will load the metadata files from. The default value for + * this option is the value of the 'metadatadir' configuration option, or + * 'metadata/' if that option is unset. + * + * + * Examples: + * + * This example defines two flatfile sources. One is the default metadata directory, the other + * is a metadata directory with autogenerated metadata files. + * + * 'metadata.sources' => array( + * array('type' => 'flatfile'), + * array('type' => 'flatfile', 'directory' => 'metadata-generated'), + * ), + * + * + * Default: + * 'metadata.sources' => array( + * array('type' => 'flatfile') + * ), */ - 'metadata.handler' => 'flatfile', + 'metadata.sources' => array( + array('type' => 'flatfile'), + ), + - /* * LDAP configuration. This is only relevant if you use the LDAP authentication plugin. */ diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php b/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php index 001aecd91e2c55e4773503d827d9be786ef2f26d..03074e5c1193ca5074078783b6b2550ecfe23b7e 100644 --- a/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php +++ b/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php @@ -1,107 +1,95 @@ <?php require_once('SimpleSAML/Configuration.php'); -require_once('SimpleSAML/Utilities.php'); +require_once('SimpleSAML/Metadata/MetaDataStorageSource.php'); /** - * This file defines a base class for metadata handling. - * Instantiation of session handler objects should be done through - * the class method getMetadataHandler(). + * This file defines a class for metadata handling. * * @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no> * @package simpleSAMLphp * @version $Id$ */ -abstract class SimpleSAML_Metadata_MetaDataStorageHandler { +class SimpleSAML_Metadata_MetaDataStorageHandler { - protected $metadata = null; - protected $hostmap = null; - - - /* This static variable contains a reference to the current + /** + * This static variable contains a reference to the current * instance of the metadata handler. This variable will be NULL if * we haven't instantiated a metadata handler yet. */ private static $metadataHandler = NULL; + /** + * This is a list of all the metadata sources we have in our metadata + * chain. When we need metadata, we will look through this chain from start to end. + */ + private $sources; - /* This function retrieves the current instance of the metadata handler. + + /** + * This function retrieves the current instance of the metadata handler. * The metadata handler will be instantiated if this is the first call * to this fuunction. * - * Returns: - * The current metadata handler. + * @return The current metadata handler instance. */ public static function getMetadataHandler() { if(self::$metadataHandler === NULL) { - self::createMetadataHandler(); + self::$metadataHandler = new SimpleSAML_Metadata_MetaDataStorageHandler(); } return self::$metadataHandler; } - - /* 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. + /** + * This constructor initializes this metadata storage handler. It will load and + * parse the configuration, and initialize the metadata source list. */ protected function __construct() { - - } - - /* This function creates an instance of the metadata handler which is - * selected in the 'metadata.handler' configuration directive. If no - * metadata handler is selected, then we will fall back to the default - * PHP metadata handler. - */ - public static function createMetadataHandler() { - - /* Get the configuration. */ $config = SimpleSAML_Configuration::getInstance(); - assert($config instanceof SimpleSAML_Configuration); - /* Get the metadata handler option from the configuration. */ - $handler = $config->getValue('metadata.handler'); + $sourcesConfig = $config->getValue('metadata.sources', NULL); - /* If 'session.handler' is NULL or unset, then we want - * to fall back to the default PHP session handler. - */ - if(is_null($handler)) { - $handler = 'flatfile'; + /* For backwards compatibility, and to provide a default configuration. */ + if($sourcesConfig === NULL) { + $type = $config->getValue('metadata.handler', 'flatfile'); + $sourcesConfig = array(array('type' => $type)); } - - /* The session handler must be a string. */ - if(!is_string($handler)) { - throw new Exception('Invalid setting for the [metadata.handler] configuration option. This option should be set to a valid string.'); + if(!is_array($sourcesConfig)) { + throw new Exception( + 'Invalid configuration of the \'metadata.sources\' configuration option.' . + ' This option should be an array.' + ); } - $handler = strtolower($handler); - - if($handler === 'flatfile') { + $this->sources = array(); - require_once('SimpleSAML/Metadata/MetaDataStorageHandlerFlatfile.php'); - $sh = new SimpleSAML_Metadata_MetaDataStorageHandlerFlatfile(); - - } elseif ($handler === 'saml2xmlmeta') { - - require_once('SimpleSAML/Metadata/MetaDataStorageHandlerSAML2Meta.php'); - $sh = new SimpleSAML_Metadata_MetaDataStorageHandlerSAML2Meta(); + foreach($sourcesConfig as $elementConfig) { + if(!is_array($elementConfig)) { + throw new Exception( + 'Invalid configuration of the \'metadata.sources\' configuration option.' . + ' Every element in the array should be an associative array.' + ); + } - - } else { - throw new Exception('Invalid value for the [metadata.handler] configuration option. Unknown handler: ' . $handler); + $src = SimpleSAML_Metadata_MetaDataStorageSource::getSource($elementConfig); + $this->sources[] = $src; } - - /* Set the session handler. */ - self::$metadataHandler = $sh; } - - + + + /** + * This function is used to generate some metadata elements automatically. + * + * @param $property The metadata property which should be autogenerated. + * @param $set The set we the property comes from. + * @return The autogenerated metadata property. + */ public function getGenerated($property, $set = 'saml20-sp-hosted') { /* Get the configuration. */ @@ -146,49 +134,116 @@ abstract class SimpleSAML_Metadata_MetaDataStorageHandler { throw new Exception('Could not generate metadata property ' . $property . ' for set ' . $set . '.'); } - + + + /** + * This function lists all known metadata in the given set. It is returned as an associative array + * where the key is the entity id. + * + * @param $set The set we want to list metadata from. + * @return An associative array with the metadata from from the given set. + */ public function getList($set = 'saml20-idp-remote') { - if (!isset($this->metadata[$set])) { - $this->load($set); + + assert('is_string($set)'); + + $result = array(); + + foreach($this->sources as $source) { + $srcList = $source->getMetadataSet($set); + + /* $result is the last argument to array_merge because we want the content already + * in $result to have precedence. + */ + $result = array_merge($srcList, $result); } - return $this->metadata[$set]; + + return $result; } - + + + /** + * This function retrieves metadata for the current entity based on the hostname/path the request + * was directed to. It will throw an exception if it is unable to locate the metadata. + * + * @param $set The set we want metadata from. + * @return An associative array with the metadata. + */ public function getMetaDataCurrent($set = 'saml20-sp-hosted') { - return $this->getMetaData($this->getMetaDataCurrentEntityID($set), $set); + return $this->getMetaData(NULL, $set); } - + + + /** + * This function locates the current entity id based on the hostname/path combination the user accessed. + * It will throw an exception if it is unable to locate the entity id. + * + * @param $set The set we look for the entity id in. + * @return The entity id which is associated with the current hostname/path combination. + */ public function getMetaDataCurrentEntityID($set = 'saml20-sp-hosted') { + + assert('is_string($set)'); + + /* First we look for the hostname/path combination. */ + $currenthostwithpath = SimpleSAML_Utilities::getSelfHostWithPath(); // sp.example.org/university + + foreach($this->sources as $source) { + $entityId = $source->getEntityIdFromHostPath($currenthostwithpath, $set); + if($entityId !== NULL) { + return $entityId; + } + } + - if (!isset($this->metadata[$set])) { - $this->load($set); + /* Then we look for the hostname. */ + $currenthost = SimpleSAML_Utilities::getSelfHost(); // sp.example.org + if(strpos($currenthost, ":") !== FALSE) { + $currenthostdecomposed = explode(":", $currenthost); + $currenthost = $currenthostdecomposed[0]; } - $currenthost = SimpleSAML_Utilities::getSelfHost(); // sp.example.org - $currenthostwithpath = SimpleSAML_Utilities::getSelfHostWithPath(); // sp.example.org/university - - if(strstr($currenthost, ":")) { - $currenthostdecomposed = explode(":", $currenthost); - $currenthost = $currenthostdecomposed[0]; + + foreach($this->sources as $source) { + $entityId = $source->getEntityIdFromHostPath($currenthost, $set); + if($entityId !== NULL) { + return $entityId; + } } - - if (!isset($this->hostmap[$set])) { - throw new Exception('No default entities defined for metadata set [' . $set . '] (host:' . $currenthost. ')'); + + + /* We were unable to find the hostname/path in any metadata source. */ + throw new Exception('Could not find any default metadata entities in set [' . $set . '] for host [' . $currenthost . ' : ' . $currenthostwithpath . ']'); + } + + + /** + * This function looks up the metadata for the given entity id in the given set. It will throw an + * exception if it is unable to locate the metadata. + * + * @param $entityId The entity id we are looking up. This parameter may be NULL, in which case we look up + * the current entity id based on the current hostname/path. + * @param $set The set of metadata we are looking up the entity id in. + */ + public function getMetaData($entityId, $set = 'saml20-sp-hosted') { + + assert('is_string($set)'); + + if($entityId === NULL) { + $entityId = $this->getMetaDataCurrentEntityID($set); } - if (!isset($currenthost)) { - throw new Exception('Could not get HTTP_HOST, in order to resolve default entity ID'); + + assert('is_string($entityId)'); + + foreach($this->sources as $source) { + $metadata = $source->getMetaData($entityId, $set); + if($metadata !== NULL) { + return $metadata; + } } - - - if (isset($this->hostmap[$set][$currenthostwithpath])) return $this->hostmap[$set][$currenthostwithpath]; - if (isset($this->hostmap[$set][$currenthost])) return $this->hostmap[$set][$currenthost]; - - throw new Exception('Could not find any default metadata entities in set [' . $set . '] for host [' . $currenthost . ' : ' . $currenthostwithpath . ']'); + + throw new Exception('Unable to locate metadata for \'' . $entityId . '\' in set \'' . $set . '\'.'); } - abstract public function load($set); - abstract public function getMetaData($entityid = null, $set = 'saml20-sp-hosted'); - - } ?> \ No newline at end of file diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatfile.php b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatfile.php index 828ecc7483e14517e3a0096f5e82c40530bbe3bc..e6e670a5034a6f7d9874917ab88dd369c188b925 100644 --- a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatfile.php +++ b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatfile.php @@ -1,11 +1,10 @@ <?php require_once('SimpleSAML/Configuration.php'); -require_once('SimpleSAML/Utilities.php'); -require_once('SimpleSAML/Metadata/MetaDataStorageHandler.php'); +require_once('SimpleSAML/Metadata/MetaDataStorageSource.php'); /** - * This file defines a flat file metadata handler. + * This file defines a flat file metadata source. * Instantiation of session handler objects should be done through * the class method getMetadataHandler(). * @@ -13,77 +12,119 @@ require_once('SimpleSAML/Metadata/MetaDataStorageHandler.php'); * @package simpleSAMLphp * @version $Id$ */ -class SimpleSAML_Metadata_MetaDataStorageHandlerFlatFile extends SimpleSAML_Metadata_MetaDataStorageHandler { +class SimpleSAML_Metadata_MetaDataStorageHandlerFlatFile extends SimpleSAML_Metadata_MetaDataStorageSource { + /** + * This is the valid metadata sets we know about. + */ + private static $validSets = 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', + 'openid-provider' + ); + /** + * This is the directory we will load metadata files from. The path will always end + * with a '/'. + */ + private $directory; - /* 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. + /** + * This is an associative array which stores the different metadata sets we have loaded. */ - protected function __construct() { - } + private $cachedMetadata = array(); + /** + * This constructor initializes the flatfile metadata storage handler with the + * specified configuration. The configuration is an associative array with the following + * possible elements: + * - 'directory': The directory we should load metadata from. The default directory is + * set in the 'metadatadir' configuration option in 'config.php'. + * + * @param $config An associtive array with the configuration for this handler. + */ + protected function __construct($config) { + assert('is_array($config)'); - 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', - 'openid-provider'))) { - throw new Exception('Trying to load illegal set of Meta data [' . $set . ']'); - } - /* Get the configuration. */ - $config = SimpleSAML_Configuration::getInstance(); - assert($config instanceof SimpleSAML_Configuration); - - $metadatasetfile = $config->getPathValue('metadatadir') . $set . '.php'; - + $globalConfig = SimpleSAML_Configuration::getInstance(); + + + /* Find the path to the directory we should search for metadata in. */ + if(array_key_exists('directory', $config)) { + $this->directory = $config['directory']; + } else { + $this->directory = $globalConfig->getValue('metadatadir', 'metadata/'); + } + + /* Resolve this directory relative to the simpleSAMLphp directory (unless it is + * an absolute path). + */ + $this->directory = $globalConfig->resolvePath($this->directory) . '/'; + } + + + /** + * This function loads the given set of metadata from a file our metadata directory. + * This function returns NULL if it is unable to locate the given set in the metadata directory. + * + * @param $set The set of metadata we are loading. + * @return Associative array with the metadata, or NULL if we are unable to load metadata from the given file. + */ + private function load($set) { + + $metadatasetfile = $this->directory . $set . '.php'; + if (!file_exists($metadatasetfile)) { - throw new Exception('Could not open file: ' . $metadatasetfile); + return NULL; } + + $metadata = array(); + include($metadatasetfile); - + if (!is_array($metadata)) { throw new Exception('Could not load metadata set [' . $set . '] from file: ' . $metadatasetfile); } - 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; - } - - } + return $metadata; } - - public function getMetaData($entityid = null, $set = 'saml20-sp-hosted') { - if (!isset($entityid)) { - return $this->getMetaDataCurrent($set); + + /** + * This function retrieves the given set of metadata. It will return an empty array if it is + * unable to locate it. + * + * @param $set The set of metadata we are retrieving. + * @return Asssociative array with the metadata. Each element in the array is an entity, and the + * key is the entity id. + */ + public function getMetadataSet($set) { + assert('in_array($set, self::$validSets)'); + + if(array_key_exists($set, $this->cachedMetadata)) { + return $this->cachedMetadata[$set]; } - - //echo 'find metadata for entityid [' . $entityid . '] in metadata set [' . $set . ']'; - - if (!isset($this->metadata[$set])) { - $this->load($set); + + $metadataSet = $this->load($set); + if($metadataSet === NULL) { + $metadataSet = array(); } - if (!isset($this->metadata[$set][$entityid]) ) { - throw new Exception('Could not find metadata for entityid [' . $entityid . '] in metadata set [' . $set . ']'); + + /* Add the entity id of an entry to each entry in the metadata. */ + foreach ($metadataSet AS $entityId => &$entry) { + $entry['entityid'] = $entityId; } - return $this->metadata[$set][$entityid]; + + $this->cachedMetadata[$set] = $metadataSet; + + return $metadataSet; } - - - } ?> \ No newline at end of file diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageSource.php b/lib/SimpleSAML/Metadata/MetaDataStorageSource.php new file mode 100644 index 0000000000000000000000000000000000000000..3eaa5c587759bc701ea83b3caf9eafc85cbb1b96 --- /dev/null +++ b/lib/SimpleSAML/Metadata/MetaDataStorageSource.php @@ -0,0 +1,120 @@ +<?php + +require_once('SimpleSAML/Metadata/MetaDataStorageHandlerFlatfile.php'); + +/** + * This abstract class defines an interface for metadata storage sources. + * + * It also contains the overview of the different metadata storage sources. + * A metadata storage source can be loaded by passing the configuration of it + * to the getSource static function. + * + * @author Olav Morken, UNINETT AS. + * @package simpleSAMLphp + * @version $Id$ + */ +abstract class SimpleSAML_Metadata_MetaDataStorageSource { + + + /** + * This function creates a metadata source based on the given configuration. + * The type of source is based on the 'type' parameter in the configuration. + * The default type is 'flatfile'. + * + * @param $sourceConfig Associative array with the configuration for this metadata source. + * @return An instance of a metadata source with the given configuration. + */ + public static function getSource($sourceConfig) { + + assert(is_array($sourceConfig)); + + if(array_key_exists('type', $sourceConfig)) { + $type = $sourceConfig['type']; + } else { + $type = 'flatfile'; + } + + switch($type) { + case 'flatfile': + return new SimpleSAML_Metadata_MetaDataStorageHandlerFlatFile($sourceConfig); + default: + throw new Exception('Invalid metadata source type: "' . $type . '".'); + } + } + + + /** + * This function attempts to generate an associative array with metadata for all entities in the + * given set. The key of the array is the entity id. + * + * A subclass should override this function if it is able to easily generate this list. + * + * @param $set The set we want to list metadata for. + * @return An associative array with all entities in the given set, or an empty array if we are + * unable to generate this list. + */ + public function getMetadataSet($set) { + return array(); + } + + + /** + * This function resolves an host/path combination to an entity id. + * + * This class implements this function using the getMetadataSet-function. A subclass should + * override this function if it doesn't implement the getMetadataSet function, or if the + * implementation of getMetadataSet is slow. + * + * @param $hostPath The host/path combination we are looking up. + * @param $set Which set of metadata we are looking it up in. + * @return An entity id which matches the given host/path combination, or NULL if + * we are unable to locate one which matches. + */ + public function getEntityIdFromHostPath($hostPath, $set) { + + $metadataSet = $this->getMetadataSet($set); + + foreach($metadataSet AS $entityId => $entry) { + + if(!array_key_exists('host', $entry)) { + continue; + } + + if($hostPath === $entry['host']) { + return $entityId; + } + } + + /* No entries matched - we should return NULL. */ + return NULL; + } + + + /** + * This function retrieves metadata for the given entity id in the given set of metadata. + * It will return NULL if it is unable to locate the metadata. + * + * This class implements this function using the getMetadataSet-function. A subclass should + * override this function if it doesn't implement the getMetadataSet function, or if the + * implementation of getMetadataSet is slow. + * + * @param $entityId The entity id we are looking up. + * @param $set The set we are looking for metadata in. + * @return An associative array with metadata for the given entity, or NULL if we are unable to + * locate the entity. + */ + public function getMetaData($entityId, $set) { + + assert('is_string($entityId)'); + + $metadataSet = $this->getMetadataSet($set); + + if(!array_key_exists($entityId, $metadataSet)) { + return NULL; + } + + return $metadataSet[$entityId]; + } + +} +?> \ No newline at end of file