* @package SimpleSAMLphp */ class SimpleSAML_Metadata_MetaDataStorageHandler { /** * 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. * * @var SimpleSAML_Metadata_MetaDataStorageHandler */ 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. * * @var SimpleSAML_Metadata_MetaDataStorageSource[] */ private $sources; /** * This function retrieves the current instance of the metadata handler. * The metadata handler will be instantiated if this is the first call * to this function. * * @return SimpleSAML_Metadata_MetaDataStorageHandler The current metadata handler instance. */ public static function getMetadataHandler() { if (self::$metadataHandler === null) { self::$metadataHandler = new SimpleSAML_Metadata_MetaDataStorageHandler(); } return self::$metadataHandler; } /** * This constructor initializes this metadata storage handler. It will load and * parse the configuration, and initialize the metadata source list. */ protected function __construct() { $config = SimpleSAML_Configuration::getInstance(); $sourcesConfig = $config->getArray('metadata.sources', null); // for backwards compatibility, and to provide a default configuration if ($sourcesConfig === null) { $type = $config->getString('metadata.handler', 'flatfile'); $sourcesConfig = array(array('type' => $type)); } try { $this->sources = SimpleSAML_Metadata_MetaDataStorageSource::parseSources($sourcesConfig); } catch (Exception $e) { throw new Exception( "Invalid configuration of the 'metadata.sources' configuration option: ".$e->getMessage() ); } } /** * This function is used to generate some metadata elements automatically. * * @param string $property The metadata property which should be auto-generated. * @param string $set The set we the property comes from. * * @return string The auto-generated metadata property. * @throws Exception If the metadata cannot be generated automatically. */ public function getGenerated($property, $set) { // first we check if the user has overridden this property in the metadata try { $metadataSet = $this->getMetaDataCurrent($set); if (array_key_exists($property, $metadataSet)) { return $metadataSet[$property]; } } catch (Exception $e) { // probably metadata wasn't found. In any case we continue by generating the metadata } // get the configuration $config = SimpleSAML_Configuration::getInstance(); assert($config instanceof SimpleSAML_Configuration); $baseurl = \SimpleSAML\Utils\HTTP::getSelfURLHost().$config->getBasePath(); if ($set == 'saml20-sp-hosted') { if ($property === 'SingleLogoutServiceBinding') { return \SAML2\Constants::BINDING_HTTP_REDIRECT; } } elseif ($set == 'saml20-idp-hosted') { switch ($property) { case 'SingleSignOnService': return $baseurl.'saml2/idp/SSOService.php'; case 'SingleSignOnServiceBinding': return \SAML2\Constants::BINDING_HTTP_REDIRECT; case 'SingleLogoutService': return $baseurl.'saml2/idp/SingleLogoutService.php'; case 'SingleLogoutServiceBinding': return \SAML2\Constants::BINDING_HTTP_REDIRECT; } } elseif ($set == 'shib13-idp-hosted') { if ($property === 'SingleSignOnService') { return $baseurl.'shib13/idp/SSOService.php'; } } 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 string $set The set we want to list metadata from. * * @return array An associative array with the metadata from from the given set. */ public function getList($set = 'saml20-idp-remote') { assert('is_string($set)'); $result = array(); foreach ($this->sources as $source) { $srcList = $source->getMetadataSet($set); foreach ($srcList as $key => $le) { if (array_key_exists('expire', $le)) { if ($le['expire'] < time()) { unset($srcList[$key]); SimpleSAML\Logger::warning( "Dropping metadata entity ".var_export($key, true).", expired ". SimpleSAML\Utils\Time::generateTimestamp($le['expire'])."." ); } } } /* $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 $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 string $set The set we want metadata from. * * @return array An associative array with the metadata. */ public function getMetaDataCurrent($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 string $set The set we look for the entity id in. * @param string $type Do you want to return the metaindex or the entityID. [entityid|metaindex] * * @return string The entity id which is associated with the current hostname/path combination. * @throws Exception If no default metadata can be found in the set for the current host. */ public function getMetaDataCurrentEntityID($set, $type = 'entityid') { assert('is_string($set)'); // first we look for the hostname/path combination $currenthostwithpath = \SimpleSAML\Utils\HTTP::getSelfHostWithPath(); // sp.example.org/university foreach ($this->sources as $source) { $index = $source->getEntityIdFromHostPath($currenthostwithpath, $set, $type); if ($index !== null) { return $index; } } // then we look for the hostname $currenthost = \SimpleSAML\Utils\HTTP::getSelfHost(); // sp.example.org foreach ($this->sources as $source) { $index = $source->getEntityIdFromHostPath($currenthost, $set, $type); if ($index !== null) { return $index; } } // then we look for the DEFAULT entry foreach ($this->sources as $source) { $entityId = $source->getEntityIdFromHostPath('__DEFAULT__', $set, $type); if ($entityId !== null) { return $entityId; } } // 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 method will call getPreferredEntityIdFromCIDRhint() on all of the * sources. * * @param string $set Which set of metadata we are looking it up in. * @param string $ip IP address * * @return string The entity id of a entity which have a CIDR hint where the provided * IP address match. */ public function getPreferredEntityIdFromCIDRhint($set, $ip) { foreach ($this->sources as $source) { $entityId = $source->getPreferredEntityIdFromCIDRhint($set, $ip); if ($entityId !== null) { return $entityId; } } return null; } /** * 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 string $index 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 string $set The set of metadata we are looking up the entity id in. * * @return array The metadata array describing the specified entity. * @throws Exception If metadata for the specified entity is expired. * @throws SimpleSAML_Error_MetadataNotFound If no metadata for the entity specified can be found. */ public function getMetaData($index, $set) { assert('is_string($set)'); if ($index === null) { $index = $this->getMetaDataCurrentEntityID($set, 'metaindex'); } assert('is_string($index)'); foreach ($this->sources as $source) { $metadata = $source->getMetaData($index, $set); if ($metadata !== null) { if (array_key_exists('expire', $metadata)) { if ($metadata['expire'] < time()) { throw new Exception( 'Metadata for the entity ['.$index.'] expired '. (time() - $metadata['expire']).' seconds ago.' ); } } $metadata['metadata-index'] = $index; $metadata['metadata-set'] = $set; assert('array_key_exists("entityid", $metadata)'); return $metadata; } } throw new SimpleSAML_Error_MetadataNotFound($index); } /** * Retrieve the metadata as a configuration object. * * This function will throw an exception if it is unable to locate the metadata. * * @param string $entityId The entity ID we are looking up. * @param string $set The metadata set we are searching. * * @return SimpleSAML_Configuration The configuration object representing the metadata. * @throws SimpleSAML_Error_MetadataNotFound If no metadata for the entity specified can be found. */ public function getMetaDataConfig($entityId, $set) { assert('is_string($entityId)'); assert('is_string($set)'); $metadata = $this->getMetaData($entityId, $set); return SimpleSAML_Configuration::loadFromArray($metadata, $set.'/'.var_export($entityId, true)); } /** * Search for an entity's metadata, given the SHA1 digest of its entity ID. * * @param string $sha1 The SHA1 digest of the entity ID. * @param string $set The metadata set we are searching. * * @return null|SimpleSAML_Configuration The metadata corresponding to the entity, or null if the entity cannot be * found. */ public function getMetaDataConfigForSha1($sha1, $set) { assert('is_string($sha1)'); 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); } foreach ($result as $remote_provider) { if (sha1($remote_provider['entityid']) == $sha1) { $remote_provider['metadata-set'] = $set; return SimpleSAML_Configuration::loadFromArray( $remote_provider, $set.'/'.var_export($remote_provider['entityid'], true) ); } } return null; } }