Skip to content
Snippets Groups Projects
Commit f2cac4d5 authored by Bryce Lowe's avatar Bryce Lowe Committed by Tim van Dijen
Browse files

Fixes #926: PDO Metadata Source Doesn't Load __DYNAMIC:1__ Metadata Sets (#1111)

parent 82a176dc
Branches
Tags
No related merge requests found
......@@ -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.']');
}
}
}
......@@ -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
......
......@@ -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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment