diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php index 4664edcab3c1297089865ec2d80b8d605a269fba..de0fb88b0efc2f0d017b70a95be5a06179f819ef 100644 --- a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php +++ b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php @@ -68,7 +68,7 @@ class MetaDataStorageHandlerFlatFile extends MetaDataStorageSource * * @return array|null An associative array with the metadata, or null if we are unable to load metadata from the given * file. - * @throws Exception If the metadata set cannot be loaded. + * @throws \Exception If the metadata set cannot be loaded. */ private function load($set) { @@ -113,38 +113,10 @@ class MetaDataStorageHandlerFlatFile extends MetaDataStorageSource // add the entity id of an entry to each entry in the metadata foreach ($metadataSet as $entityId => &$entry) { - if (preg_match('/__DYNAMIC(:[0-9]+)?__/', $entityId)) { - $entry['entityid'] = $this->generateDynamicHostedEntityID($set); - } else { - $entry['entityid'] = $entityId; - } + $entry = $this->updateEntityID($set, $entityId, $entry); } $this->cachedMetadata[$set] = $metadataSet; return $metadataSet; } - - - /** - * @param string $set - * @throws \Exception - * @return string - */ - private function generateDynamicHostedEntityID($set) - { - // get the configuration - $baseurl = \SimpleSAML\Utils\HTTP::getBaseURL(); - - if ($set === 'saml20-idp-hosted') { - return $baseurl.'saml2/idp/metadata.php'; - } elseif ($set === 'shib13-idp-hosted') { - return $baseurl.'shib13/idp/metadata.php'; - } elseif ($set === 'wsfed-sp-hosted') { - return 'urn:federation:'.\SimpleSAML\Utils\HTTP::getSelfHost(); - } elseif ($set === 'adfs-idp-hosted') { - return 'urn:federation:'.\SimpleSAML\Utils\HTTP::getSelfHost().':idp'; - } else { - throw new \Exception('Can not generate dynamic EntityID for metadata of this type: ['.$set.']'); - } - } } diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerPdo.php b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerPdo.php index f33f8757089d104189474dd55130e674f1457912..4c26cec764fb36ee4b6ccf12f52b8fd961b2483e 100644 --- a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerPdo.php +++ b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerPdo.php @@ -75,7 +75,7 @@ class MetaDataStorageHandlerPdo extends MetaDataStorageSource * @return array|null $metadata Associative array with the metadata, or NULL if we are unable to load * metadata from the given file. * - * @throws Exception If a database error occurs. + * @throws \Exception If a database error occurs. * @throws \SimpleSAML\Error\Exception If the metadata can be retrieved from the database, but cannot be decoded. */ private function load($set) @@ -129,14 +129,10 @@ class MetaDataStorageHandlerPdo extends MetaDataStorageSource if ($metadataSet === null) { $metadataSet = []; } - /** @var array $metadataSet */ + /** @var array $metadataSet */ foreach ($metadataSet as $entityId => &$entry) { - if (preg_match('/__DYNAMIC(:[0-9]+)?__/', $entityId)) { - $entry['entityid'] = $this->generateDynamicHostedEntityID($set); - } else { - $entry['entityid'] = $entityId; - } + $entry = $this->updateEntityID($set, $entityId, $entry); } $this->cachedMetadata[$set] = $metadataSet; @@ -157,68 +153,61 @@ class MetaDataStorageHandlerPdo extends MetaDataStorageSource assert(is_string($entityId)); assert(is_string($set)); - $tableName = $this->getTableName($set); - + // validate the metadata set is valid if (!in_array($set, $this->supportedSets, true)) { return null; } - $stmt = $this->db->read( - "SELECT entity_id, entity_data FROM $tableName WHERE entity_id=:entityId", - ['entityId' => $entityId] - ); - if ($stmt->execute()) { - $rowCount = 0; - $data = null; + // support caching + if (isset($this->cachedMetadata[$entityId][$set])) { + return $this->cachedMetadata[$entityId][$set]; + } - while ($d = $stmt->fetch()) { - if (++$rowCount > 1) { - \SimpleSAML\Logger::warning("Duplicate match for $entityId in set $set"); - break; - } - $data = json_decode($d['entity_data'], true); - if ($data === null) { - throw new \SimpleSAML\Error\Exception("Cannot decode metadata for entity '${d['entity_id']}'"); - } - if (!array_key_exists('entityid', $data)) { - $data['entityid'] = $d['entity_id']; - } - } - return $data; - } else { + $tableName = $this->getTableName($set); + + // according to the docs, it looks like *-idp-hosted metadata are the types + // that allow the __DYNAMIC:*__ entity id. with the current table design + // we need to lookup the specific metadata entry but also we need to lookup + // any dynamic entries to see if the dynamic hosted entity id matches + if (substr($set, -10) == 'idp-hosted') { + $stmt = $this->db->read( + "SELECT entity_id, entity_data FROM {$tableName} WHERE (entity_id LIKE :dynamicId OR entity_id = :entityId)", + ['dynamicId' => '__DYNAMIC%', 'entityId' => $entityId] + ); + } + // other metadata types should be able to match on entity id + else { + $stmt = $this->db->read( + "SELECT entity_id, entity_data FROM {$tableName} WHERE entity_id = :entityId", + ['entityId' => $entityId] + ); + } + + // throw pdo exception upon execution failure + if (!$stmt->execute()) { throw new \Exception('PDO metadata handler: Database error: '.var_export($this->db->getLastError(), true)); } - } - /** - * @param string $set - * @throws \Exception - * @return string - */ - private function generateDynamicHostedEntityID($set) - { - assert(is_string($set)); + // load the metadata into an array + $metadataSet = []; + while ($d = $stmt->fetch()) { + $data = json_decode($d['entity_data'], true); + if (json_last_error() != JSON_ERROR_NONE) { + throw new \SimpleSAML\Error\Exception("Cannot decode metadata for entity '${d['entity_id']}'"); + } - // get the configuration - $baseurl = \SimpleSAML\Utils\HTTP::getBaseURL(); - - if ($set === 'saml20-idp-hosted') { - return $baseurl.'saml2/idp/metadata.php'; - } elseif ($set === 'saml20-sp-hosted') { - return $baseurl.'saml2/sp/metadata.php'; - } elseif ($set === 'shib13-idp-hosted') { - return $baseurl.'shib13/idp/metadata.php'; - } elseif ($set === 'shib13-sp-hosted') { - return $baseurl.'shib13/sp/metadata.php'; - } elseif ($set === 'wsfed-sp-hosted') { - return 'urn:federation:'.\SimpleSAML\Utils\HTTP::getSelfHost(); - } elseif ($set === 'adfs-idp-hosted') { - return 'urn:federation:'.\SimpleSAML\Utils\HTTP::getSelfHost().':idp'; - } else { - throw new \Exception('Can not generate dynamic EntityID for metadata of this type: ['.$set.']'); + // update the entity id to either the key (if not dynamic or generate the dynamic hosted url) + $metadataSet[$d['entity_id']] = $this->updateEntityID($set, $entityId, $data); + } + + $indexLookup = $this->lookupIndexFromEntityId($entityId, $metadataSet); + if (isset($indexLookup) && array_key_exists($indexLookup, $metadataSet)) { + $this->cachedMetadata[$indexLookup][$set] = $metadataSet[$indexLookup]; + return $this->cachedMetadata[$indexLookup][$set]; } - } + return null; + } /** * Add metadata to the configured database diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageSource.php b/lib/SimpleSAML/Metadata/MetaDataStorageSource.php index ceb10ade551584d873be311773fb0950096b8b07..e6cdb5d15942f769897293e5cb66255e89157e21 100644 --- a/lib/SimpleSAML/Metadata/MetaDataStorageSource.php +++ b/lib/SimpleSAML/Metadata/MetaDataStorageSource.php @@ -213,26 +213,59 @@ abstract class MetaDataStorageSource /** - * @param string $entityId - * @param string $set - * @return mixed|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 string $index The entityId or metaindex we are looking up. + * @param string $set The set we are looking for metadata in. + * + * @return array|null An associative array with metadata for the given entity, or NULL if we are unable to + * locate the entity. */ - private function lookupIndexFromEntityId($entityId, $set) + public function getMetaData($index, $set) { - assert(is_string($entityId)); + + assert(is_string($index)); assert(isset($set)); $metadataSet = $this->getMetadataSet($set); + $indexLookup = $this->lookupIndexFromEntityId($index, $metadataSet); + if (isset($indexLookup) && array_key_exists($indexLookup, $metadataSet)) { + return $metadataSet[$indexLookup]; + } + + return null; + } + + /** + * This method returns the full metadata set for a given entity id or null if the entity id cannot be found + * in the given metadata set. + * + * @param string $entityId + * @param array $metadataSet the already loaded metadata set + * @return mixed|null + */ + protected function lookupIndexFromEntityId($entityId, array $metadataSet) + { + assert(is_string($entityId)); + assert(is_array($metadataSet)); + // check for hostname - $currenthost = \SimpleSAML\Utils\HTTP::getSelfHost(); // sp.example.org + $currentHost = \SimpleSAML\Utils\HTTP::getSelfHost(); // sp.example.org foreach ($metadataSet as $index => $entry) { + // explicit index match if ($index === $entityId) { return $index; } + if ($entry['entityid'] === $entityId) { - if ($entry['host'] === '__DEFAULT__' || $entry['host'] === $currenthost) { + if ($entry['host'] === '__DEFAULT__' || $entry['host'] === $currentHost) { return $index; } } @@ -241,38 +274,70 @@ abstract class MetaDataStorageSource return null; } + /** + * @param string $set + * @throws \Exception + * @return string + */ + private function getDynamicHostedUrl($set) + { + assert(is_string($set)); + + // get the configuration + $baseUrl = \SimpleSAML\Utils\HTTP::getBaseURL(); + + if ($set === 'saml20-idp-hosted') { + return $baseUrl.'saml2/idp/metadata.php'; + } + else if ($set === 'saml20-sp-hosted') { + return $baseUrl.'saml2/sp/metadata.php'; + } + else if ($set === 'shib13-idp-hosted') { + return $baseUrl.'shib13/idp/metadata.php'; + } + else if ($set === 'shib13-sp-hosted') { + return $baseUrl.'shib13/sp/metadata.php'; + } + else if ($set === 'wsfed-sp-hosted') { + return 'urn:federation:'.\SimpleSAML\Utils\HTTP::getSelfHost(); + } + else if ($set === 'adfs-idp-hosted') { + return 'urn:federation:'.\SimpleSAML\Utils\HTTP::getSelfHost().':idp'; + } + else { + throw new \Exception('Can not generate dynamic EntityID for metadata of this type: ['.$set.']'); + } + } /** - * 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. + * Updates the metadata entry's entity id and returns the modified array. If the entity id is __DYNAMIC:*__ a + * the current url is assigned. If it is explicit the entityid array key is updated to the entityId that was + * provided. * - * 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 string $index The entityId or metaindex we are looking up. - * @param string $set The set we are looking for metadata in. + * @param string $metadataSet a metadata set (saml20-idp-hosted, saml20-sp-remote, etc) + * @param string $entityId the entity id we are modifying + * @param array $metadataEntry the fully populated metadata entry + * @return array modified metadata to include the valid entityid * - * @return array|null An associative array with metadata for the given entity, or NULL if we are unable to - * locate the entity. + * @throws \Exception */ - public function getMetaData($index, $set) + protected function updateEntityID($metadataSet, $entityId, array $metadataEntry) { + assert(is_string($metadataSet)); + assert(is_string($entityId)); + assert(is_array($metadataEntry)); - assert(is_string($index)); - assert(isset($set)); - - $metadataSet = $this->getMetadataSet($set); + $modifiedMetadataEntry = $metadataEntry; - if (array_key_exists($index, $metadataSet)) { - return $metadataSet[$index]; + // generate a dynamic hosted url + if (preg_match('/__DYNAMIC(:[0-9]+)?__/', $entityId)) { + $modifiedMetadataEntry['entityid'] = $this->getDynamicHostedUrl($metadataSet); } - - $indexlookup = $this->lookupIndexFromEntityId($index, $set); - if (isset($indexlookup) && array_key_exists($indexlookup, $metadataSet)) { - return $metadataSet[$indexlookup]; + // set the entityid metadata array key to the provided entity id + else { + $modifiedMetadataEntry['entityid'] = $entityId; } - return null; + return $modifiedMetadataEntry; } -} +} \ No newline at end of file