runff 1.0 commit

This commit is contained in:
rock64
2019-04-29 16:09:00 +02:00
commit f1567f989b
1268 changed files with 98652 additions and 0 deletions

View File

@@ -0,0 +1,361 @@
<?php
/**
* This file defines a class for metadata handling.
*
* @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
* @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;
}
}

View File

@@ -0,0 +1,144 @@
<?php
/**
* This file defines a flat file metadata source.
* Instantiation of session handler objects should be done through
* the class method getMetadataHandler().
*
* @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
* @package SimpleSAMLphp
*/
class SimpleSAML_Metadata_MetaDataStorageHandlerFlatFile extends SimpleSAML_Metadata_MetaDataStorageSource
{
/**
* This is the directory we will load metadata files from. The path will always end
* with a '/'.
*
* @var string
*/
private $directory;
/**
* This is an associative array which stores the different metadata sets we have loaded.
*
* @var array
*/
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 array $config An associative array with the configuration for this handler.
*/
protected function __construct($config)
{
assert(is_array($config));
// get the configuration
$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->getString('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 string $set The set of metadata we are loading.
*
* @return array 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.
*/
private function load($set)
{
$metadatasetfile = $this->directory.$set.'.php';
if (!file_exists($metadatasetfile)) {
return null;
}
$metadata = array();
include($metadatasetfile);
if (!is_array($metadata)) {
throw new Exception('Could not load metadata set ['.$set.'] from file: '.$metadatasetfile);
}
return $metadata;
}
/**
* This function retrieves the given set of metadata. It will return an empty array if it is
* unable to locate it.
*
* @param string $set The set of metadata we are retrieving.
*
* @return array An associative array with the metadata. Each element in the array is an entity, and the
* key is the entity id.
*/
public function getMetadataSet($set)
{
if (array_key_exists($set, $this->cachedMetadata)) {
return $this->cachedMetadata[$set];
}
$metadataSet = $this->load($set);
if ($metadataSet === null) {
$metadataSet = array();
}
// 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;
}
}
$this->cachedMetadata[$set] = $metadataSet;
return $metadataSet;
}
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.']');
}
}
}

View File

@@ -0,0 +1,306 @@
<?php
/**
* Class for handling metadata files stored in a database.
*
* This class has been based off a previous version written by
* mooknarf@gmail.com and patched to work with the latest version
* of SimpleSAMLphp
*
* @package SimpleSAMLphp
*/
class SimpleSAML_Metadata_MetaDataStorageHandlerPdo extends SimpleSAML_Metadata_MetaDataStorageSource
{
/**
* The PDO object
*/
private $db;
/**
* Prefix to apply to the metadata table
*/
private $tablePrefix;
/**
* This is an associative array which stores the different metadata sets we have loaded.
*/
private $cachedMetadata = array();
/**
* All the metadata sets supported by this MetaDataStorageHandler
*/
public $supportedSets = array(
'adfs-idp-hosted',
'adfs-sp-remote',
'saml20-idp-hosted',
'saml20-idp-remote',
'saml20-sp-remote',
'shib13-idp-hosted',
'shib13-idp-remote',
'shib13-sp-hosted',
'shib13-sp-remote',
'wsfed-idp-remote',
'wsfed-sp-hosted'
);
/**
* This constructor initializes the PDO metadata storage handler with the specified
* configuration. The configuration is an associative array with the following
* possible elements (set in config.php):
* - 'usePersistentConnection': TRUE/FALSE if database connection should be persistent.
* - 'dsn': The database connection string.
* - 'username': Database user name
* - 'password': Password for the database user.
*
* @param array $config An associative array with the configuration for this handler.
*/
public function __construct($config)
{
assert(is_array($config));
$this->db = SimpleSAML\Database::getInstance();
}
/**
* This function loads the given set of metadata from a file to a configured database.
* This function returns NULL if it is unable to locate the given set in the metadata directory.
*
* @param string $set The set of metadata we are loading.
*
* @return array $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 SimpleSAML_Error_Exception If the metadata can be retrieved from the database, but cannot be decoded.
*/
private function load($set)
{
assert(is_string($set));
$tableName = $this->getTableName($set);
if (!in_array($set, $this->supportedSets, true)) {
return null;
}
$stmt = $this->db->read("SELECT entity_id, entity_data FROM $tableName");
if ($stmt->execute()) {
$metadata = array();
while ($d = $stmt->fetch()) {
$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'];
}
$metadata[$d['entity_id']] = $data;
}
return $metadata;
} else {
throw new Exception('PDO metadata handler: Database error: '.var_export($this->db->getLastError(), true));
}
}
/**
* Retrieve a list of all available metadata for a given set.
*
* @param string $set The set we are looking for metadata in.
*
* @return array $metadata An associative array with all the metadata for the given set.
*/
public function getMetadataSet($set)
{
assert(is_string($set));
if (array_key_exists($set, $this->cachedMetadata)) {
return $this->cachedMetadata[$set];
}
$metadataSet = $this->load($set);
if ($metadataSet === null) {
$metadataSet = array();
}
foreach ($metadataSet as $entityId => &$entry) {
if (preg_match('/__DYNAMIC(:[0-9]+)?__/', $entityId)) {
$entry['entityid'] = $this->generateDynamicHostedEntityID($set);
} else {
$entry['entityid'] = $entityId;
}
}
$this->cachedMetadata[$set] = $metadataSet;
return $metadataSet;
}
/**
* Retrieve a metadata entry.
*
* @param string $entityId The entityId we are looking up.
* @param string $set The set we are looking for metadata in.
*
* @return array 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));
assert(is_string($set));
$tableName = $this->getTableName($set);
if (!in_array($set, $this->supportedSets, true)) {
return null;
}
$stmt = $this->db->read("SELECT entity_id, entity_data FROM $tableName WHERE entity_id=:entityId", array('entityId' => $entityId));
if ($stmt->execute()) {
$rowCount = 0;
$data = null;
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 {
throw new Exception('PDO metadata handler: Database error: '.var_export($this->db->getLastError(), true));
}
}
private function generateDynamicHostedEntityID($set)
{
assert(is_string($set));
// 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.']');
}
}
/**
* Add metadata to the configured database
*
* @param string $index Entity ID
* @param string $set The set to add the metadata to
* @param array $entityData Metadata
*
* @return bool True/False if entry was successfully added
*/
public function addEntry($index, $set, $entityData)
{
assert(is_string($index));
assert(is_string($set));
assert(is_array($entityData));
if (!in_array($set, $this->supportedSets, true)) {
return false;
}
$tableName = $this->getTableName($set);
$metadata = $this->db->read(
"SELECT entity_id, entity_data FROM $tableName WHERE entity_id = :entity_id",
array(
'entity_id' => $index,
)
);
$retrivedEntityIDs = $metadata->fetch();
$params = array(
'entity_id' => $index,
'entity_data' => json_encode($entityData),
);
if ($retrivedEntityIDs !== false && count($retrivedEntityIDs) > 0) {
$rows = $this->db->write(
"UPDATE $tableName SET entity_data = :entity_data WHERE entity_id = :entity_id",
$params
);
} else {
$rows = $this->db->write(
"INSERT INTO $tableName (entity_id, entity_data) VALUES (:entity_id, :entity_data)",
$params
);
}
return $rows === 1;
}
/**
* Replace the -'s to an _ in table names for Metadata sets
* since SQL does not allow a - in a table name.
*
* @param string $table Table
*
* @return string Replaced table name
*/
private function getTableName($table)
{
assert(is_string($table));
return $this->db->applyPrefix(str_replace("-", "_", $this->tablePrefix.$table));
}
/**
* Initialize the configured database
*
* @return int|false The number of SQL statements successfully executed, false if some error occurred.
*/
public function initDatabase()
{
$stmt = 0;
$fine = true;
foreach ($this->supportedSets as $set) {
$tableName = $this->getTableName($set);
$rows = $this->db->write(
"CREATE TABLE IF NOT EXISTS $tableName (entity_id VARCHAR(255) PRIMARY KEY NOT NULL, entity_data ".
"TEXT NOT NULL)"
);
if ($rows === 0) {
$fine = false;
} else {
$stmt += $rows;
}
}
if (!$fine) {
return false;
}
return $stmt;
}
}

View File

@@ -0,0 +1,286 @@
<?php
/**
* Class for handling metadata files in serialized format.
*
* @package SimpleSAMLphp
*/
class SimpleSAML_Metadata_MetaDataStorageHandlerSerialize extends SimpleSAML_Metadata_MetaDataStorageSource
{
/**
* The file extension we use for our metadata files.
*
* @var string
*/
const EXTENSION = '.serialized';
/**
* The base directory where metadata is stored.
*
* @var string
*/
private $directory;
/**
* Constructor for this metadata handler.
*
* Parses configuration.
*
* @param array $config The configuration for this metadata handler.
*/
public function __construct($config)
{
assert(is_array($config));
$globalConfig = SimpleSAML_Configuration::getInstance();
$cfgHelp = SimpleSAML_Configuration::loadFromArray($config, 'serialize metadata source');
$this->directory = $cfgHelp->getString('directory');
/* Resolve this directory relative to the SimpleSAMLphp directory (unless it is
* an absolute path).
*/
$this->directory = $globalConfig->resolvePath($this->directory);
}
/**
* Helper function for retrieving the path of a metadata file.
*
* @param string $entityId The entity ID.
* @param string $set The metadata set.
*
* @return string The path to the metadata file.
*/
private function getMetadataPath($entityId, $set)
{
assert(is_string($entityId));
assert(is_string($set));
return $this->directory.'/'.rawurlencode($set).'/'.rawurlencode($entityId).self::EXTENSION;
}
/**
* Retrieve a list of all available metadata sets.
*
* @return array An array with the available sets.
*/
public function getMetadataSets()
{
$ret = array();
$dh = @opendir($this->directory);
if ($dh === false) {
SimpleSAML\Logger::warning(
'Serialize metadata handler: Unable to open directory: '.var_export($this->directory, true)
);
return $ret;
}
while (($entry = readdir($dh)) !== false) {
if ($entry[0] === '.') {
// skip '..', '.' and hidden files
continue;
}
$path = $this->directory.'/'.$entry;
if (!is_dir($path)) {
SimpleSAML\Logger::warning(
'Serialize metadata handler: Metadata directory contained a file where only directories should '.
'exist: '.var_export($path, true)
);
continue;
}
$ret[] = rawurldecode($entry);
}
closedir($dh);
return $ret;
}
/**
* Retrieve a list of all available metadata for a given set.
*
* @param string $set The set we are looking for metadata in.
*
* @return array An associative array with all the metadata for the given set.
*/
public function getMetadataSet($set)
{
assert(is_string($set));
$ret = array();
$dir = $this->directory.'/'.rawurlencode($set);
if (!is_dir($dir)) {
// probably some code asked for a metadata set which wasn't available
return $ret;
}
$dh = @opendir($dir);
if ($dh === false) {
SimpleSAML\Logger::warning('Serialize metadata handler: Unable to open directory: '.var_export($dir, true));
return $ret;
}
$extLen = strlen(self::EXTENSION);
while (($file = readdir($dh)) !== false) {
if (strlen($file) <= $extLen) {
continue;
}
if (substr($file, -$extLen) !== self::EXTENSION) {
continue;
}
$entityId = substr($file, 0, -$extLen);
$entityId = rawurldecode($entityId);
$md = $this->getMetaData($entityId, $set);
if ($md !== null) {
$ret[$entityId] = $md;
}
}
closedir($dh);
return $ret;
}
/**
* Retrieve a metadata entry.
*
* @param string $entityId The entityId we are looking up.
* @param string $set The set we are looking for metadata in.
*
* @return array 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));
assert(is_string($set));
$filePath = $this->getMetadataPath($entityId, $set);
if (!file_exists($filePath)) {
return null;
}
$data = @file_get_contents($filePath);
if ($data === false) {
$error = error_get_last();
SimpleSAML\Logger::warning(
'Error reading file '.$filePath.': '.$error['message']
);
return null;
}
$data = @unserialize($data);
if ($data === false) {
SimpleSAML\Logger::warning('Error unserializing file: '.$filePath);
return null;
}
if (!array_key_exists('entityid', $data)) {
$data['entityid'] = $entityId;
}
return $data;
}
/**
* Save a metadata entry.
*
* @param string $entityId The entityId of the metadata entry.
* @param string $set The metadata set this metadata entry belongs to.
* @param array $metadata The metadata.
*
* @return boolean True if successfully saved, false otherwise.
*/
public function saveMetadata($entityId, $set, $metadata)
{
assert(is_string($entityId));
assert(is_string($set));
assert(is_array($metadata));
$filePath = $this->getMetadataPath($entityId, $set);
$newPath = $filePath.'.new';
$dir = dirname($filePath);
if (!is_dir($dir)) {
SimpleSAML\Logger::info('Creating directory: '.$dir);
$res = @mkdir($dir, 0777, true);
if ($res === false) {
$error = error_get_last();
SimpleSAML\Logger::error('Failed to create directory '.$dir.': '.$error['message']);
return false;
}
}
$data = serialize($metadata);
SimpleSAML\Logger::debug('Writing: '.$newPath);
$res = file_put_contents($newPath, $data);
if ($res === false) {
$error = error_get_last();
SimpleSAML\Logger::error('Error saving file '.$newPath.': '.$error['message']);
return false;
}
$res = rename($newPath, $filePath);
if ($res === false) {
$error = error_get_last();
SimpleSAML\Logger::error('Error renaming '.$newPath.' to '.$filePath.': '.$error['message']);
return false;
}
return true;
}
/**
* Delete a metadata entry.
*
* @param string $entityId The entityId of the metadata entry.
* @param string $set The metadata set this metadata entry belongs to.
*/
public function deleteMetadata($entityId, $set)
{
assert(is_string($entityId));
assert(is_string($set));
$filePath = $this->getMetadataPath($entityId, $set);
if (!file_exists($filePath)) {
SimpleSAML\Logger::warning(
'Attempted to erase nonexistent metadata entry '.
var_export($entityId, true).' in set '.var_export($set, true).'.'
);
return;
}
$res = unlink($filePath);
if ($res === false) {
$error = error_get_last();
SimpleSAML\Logger::error(
'Failed to delete file '.$filePath.
': '.$error['message']
);
}
}
}

View File

@@ -0,0 +1,116 @@
<?php
/**
* This class implements a metadata source which loads metadata from XML files.
* The XML files should be in the SAML 2.0 metadata format.
*
* @author Olav Morken, UNINETT AS.
* @package SimpleSAMLphp
*/
class SimpleSAML_Metadata_MetaDataStorageHandlerXML extends SimpleSAML_Metadata_MetaDataStorageSource
{
/**
* This variable contains an associative array with the parsed metadata.
*
* @var array
*/
private $metadata;
/**
* This function initializes the XML metadata source. The configuration must contain one of
* the following options:
* - 'file': Path to a file with the metadata. This path is relative to the SimpleSAMLphp
* base directory.
* - 'url': URL we should download the metadata from. This is only meant for testing.
*
* @param array $config The configuration for this instance of the XML metadata source.
*
* @throws Exception If neither the 'file' or 'url' options are defined in the configuration.
*/
protected function __construct($config)
{
$src = $srcXml = null;
if (array_key_exists('file', $config)) {
// get the configuration
$globalConfig = SimpleSAML_Configuration::getInstance();
$src = $globalConfig->resolvePath($config['file']);
} elseif (array_key_exists('url', $config)) {
$src = $config['url'];
} elseif (array_key_exists('xml', $config)) {
$srcXml = $config['xml'];
} else {
throw new Exception("Missing one of 'file', 'url' and 'xml' in XML metadata source configuration.");
}
$SP1x = array();
$IdP1x = array();
$SP20 = array();
$IdP20 = array();
$AAD = array();
if(isset($src)) {
$entities = SimpleSAML_Metadata_SAMLParser::parseDescriptorsFile($src);
} elseif(isset($srcXml)) {
$entities = SimpleSAML_Metadata_SAMLParser::parseDescriptorsString($srcXml);
} else {
throw new Exception("Neither source file path/URI nor string data provided");
}
foreach ($entities as $entityId => $entity) {
$md = $entity->getMetadata1xSP();
if ($md !== null) {
$SP1x[$entityId] = $md;
}
$md = $entity->getMetadata1xIdP();
if ($md !== null) {
$IdP1x[$entityId] = $md;
}
$md = $entity->getMetadata20SP();
if ($md !== null) {
$SP20[$entityId] = $md;
}
$md = $entity->getMetadata20IdP();
if ($md !== null) {
$IdP20[$entityId] = $md;
}
$md = $entity->getAttributeAuthorities();
if (count($md) > 0) {
$AAD[$entityId] = $md[0];
}
}
$this->metadata = array(
'shib13-sp-remote' => $SP1x,
'shib13-idp-remote' => $IdP1x,
'saml20-sp-remote' => $SP20,
'saml20-idp-remote' => $IdP20,
'attributeauthority-remote' => $AAD,
);
}
/**
* This function returns an associative array with metadata for all entities in the given set. The
* key of the array is the entity id.
*
* @param string $set The set we want to list metadata for.
*
* @return array An associative array with all entities in the given set.
*/
public function getMetadataSet($set)
{
if (array_key_exists($set, $this->metadata)) {
return $this->metadata[$set];
}
// we don't have this metadata set
return array();
}
}

View File

@@ -0,0 +1,275 @@
<?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.
* @author Andreas Aakre Solberg, UNINETT AS.
* @package SimpleSAMLphp
*/
abstract class SimpleSAML_Metadata_MetaDataStorageSource
{
/**
* Parse array with metadata sources.
*
* This function accepts an array with metadata sources, and returns an array with
* each metadata source as an object.
*
* @param array $sourcesConfig Array with metadata source configuration.
*
* @return array Parsed metadata configuration.
*
* @throws Exception If something is wrong in the configuration.
*/
public static function parseSources($sourcesConfig)
{
assert(is_array($sourcesConfig));
$sources = array();
foreach ($sourcesConfig as $sourceConfig) {
if (!is_array($sourceConfig)) {
throw new Exception("Found an element in metadata source configuration which wasn't an array.");
}
$sources[] = self::getSource($sourceConfig);
}
return $sources;
}
/**
* 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 array $sourceConfig Associative array with the configuration for this metadata source.
*
* @return mixed An instance of a metadata source with the given configuration.
*
* @throws Exception If the metadata source type is invalid.
*/
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);
case 'xml':
return new SimpleSAML_Metadata_MetaDataStorageHandlerXML($sourceConfig);
case 'serialize':
return new SimpleSAML_Metadata_MetaDataStorageHandlerSerialize($sourceConfig);
case 'mdx':
case 'mdq':
return new \SimpleSAML\Metadata\Sources\MDQ($sourceConfig);
case 'pdo':
return new SimpleSAML_Metadata_MetaDataStorageHandlerPdo($sourceConfig);
default:
// metadata store from module
try {
$className = SimpleSAML\Module::resolveClass(
$type,
'MetadataStore',
'SimpleSAML_Metadata_MetaDataStorageSource'
);
} catch (Exception $e) {
throw new SimpleSAML\Error\CriticalConfigurationError(
"Invalid 'type' for metadata source. Cannot find store '$type'.",
null
);
}
return new $className($sourceConfig);
}
}
/**
* 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 string $set The set we want to list metadata for.
*
* @return array 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 string $hostPath The host/path combination we are looking up.
* @param string $set Which set of metadata we are looking it up in.
* @param string $type Do you want to return the metaindex or the entityID. [entityid|metaindex]
*
* @return string|null 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, $type = 'entityid')
{
$metadataSet = $this->getMetadataSet($set);
if ($metadataSet === null) {
// this metadata source does not have this metadata set
return null;
}
foreach ($metadataSet as $index => $entry) {
if (!array_key_exists('host', $entry)) {
continue;
}
if ($hostPath === $entry['host']) {
if ($type === 'entityid') {
return $entry['entityid'];
} else {
return $index;
}
}
}
// no entries matched, we should return null
return null;
}
/**
* This function will go through all the metadata, and check the DiscoHints->IPHint
* parameter, which defines a network space (ip range) for each remote entry.
* This function returns the entityID for any of the entities that have an
* IP range which the IP falls within.
*
* @param string $set Which set of metadata we are looking it up in.
* @param string $ip IP address
* @param string $type Do you want to return the metaindex or the entityID. [entityid|metaindex]
*
* @return string The entity id of a entity which have a CIDR hint where the provided
* IP address match.
*/
public function getPreferredEntityIdFromCIDRhint($set, $ip, $type = 'entityid')
{
$metadataSet = $this->getMetadataSet($set);
foreach ($metadataSet as $index => $entry) {
$cidrHints = array();
// support hint.cidr for idp discovery
if (array_key_exists('hint.cidr', $entry) && is_array($entry['hint.cidr'])) {
$cidrHints = $entry['hint.cidr'];
}
// support discohints in idp metadata for idp discovery
if (array_key_exists('DiscoHints', $entry)
&& array_key_exists('IPHint', $entry['DiscoHints'])
&& is_array($entry['DiscoHints']['IPHint'])) {
// merge with hints derived from discohints, but prioritize hint.cidr in case it is used
$cidrHints = array_merge($entry['DiscoHints']['IPHint'], $cidrHints);
}
if (empty($cidrHints)) {
continue;
}
foreach ($cidrHints as $hint_entry) {
if (SimpleSAML\Utils\Net::ipCIDRcheck($hint_entry, $ip)) {
if ($type === 'entityid') {
return $entry['entityid'];
} else {
return $index;
}
}
}
}
// no entries matched, we should return null
return null;
}
/*
*
*/
private function lookupIndexFromEntityId($entityId, $set)
{
assert(is_string($entityId));
assert(isset($set));
$metadataSet = $this->getMetadataSet($set);
// check for hostname
$currenthost = \SimpleSAML\Utils\HTTP::getSelfHost(); // sp.example.org
foreach ($metadataSet as $index => $entry) {
if ($index === $entityId) {
return $index;
}
if ($entry['entityid'] === $entityId) {
if ($entry['host'] === '__DEFAULT__' || $entry['host'] === $currenthost) {
return $index;
}
}
}
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 string $index The entityId or metaindex we are looking up.
* @param string $set The set we are looking for metadata in.
*
* @return array An associative array with metadata for the given entity, or NULL if we are unable to
* locate the entity.
*/
public function getMetaData($index, $set)
{
assert(is_string($index));
assert(isset($set));
$metadataSet = $this->getMetadataSet($set);
if (array_key_exists($index, $metadataSet)) {
return $metadataSet[$index];
}
$indexlookup = $this->lookupIndexFromEntityId($index, $set);
if (isset($indexlookup) && array_key_exists($indexlookup, $metadataSet)) {
return $metadataSet[$indexlookup];
}
return null;
}
}

View File

@@ -0,0 +1,777 @@
<?php
/**
* Class for generating SAML 2.0 metadata from SimpleSAMLphp metadata arrays.
*
* This class builds SAML 2.0 metadata for an entity by examining the metadata for the entity.
*
* @package SimpleSAMLphp
*/
class SimpleSAML_Metadata_SAMLBuilder
{
/**
* The EntityDescriptor we are building.
*
* @var \SAML2\XML\md\EntityDescriptor
*/
private $entityDescriptor;
/**
* The maximum time in seconds the metadata should be cached.
*
* @var int|null
*/
private $maxCache = null;
/**
* The maximum time in seconds since the current time that this metadata should be considered valid.
*
* @var int|null
*/
private $maxDuration = null;
/**
* Initialize the SAML builder.
*
* @param string $entityId The entity id of the entity.
* @param double|null $maxCache The maximum time in seconds the metadata should be cached. Defaults to null
* @param double|null $maxDuration The maximum time in seconds this metadata should be considered valid. Defaults
* to null.
*/
public function __construct($entityId, $maxCache = null, $maxDuration = null)
{
assert(is_string($entityId));
$this->maxCache = $maxCache;
$this->maxDuration = $maxDuration;
$this->entityDescriptor = new \SAML2\XML\md\EntityDescriptor();
$this->entityDescriptor->entityID = $entityId;
}
private function setExpiration($metadata)
{
if (array_key_exists('expire', $metadata)) {
if ($metadata['expire'] - time() < $this->maxDuration) {
$this->maxDuration = $metadata['expire'] - time();
}
}
if ($this->maxCache !== null) {
$this->entityDescriptor->cacheDuration = 'PT'.$this->maxCache.'S';
}
if ($this->maxDuration !== null) {
$this->entityDescriptor->validUntil = time() + $this->maxDuration;
}
}
/**
* Retrieve the EntityDescriptor element which is generated for this entity.
*
* @return DOMElement The EntityDescriptor element of this entity.
*/
public function getEntityDescriptor()
{
$xml = $this->entityDescriptor->toXML();
$xml->ownerDocument->appendChild($xml);
return $xml;
}
/**
* Retrieve the EntityDescriptor as text.
*
* This function serializes this EntityDescriptor, and returns it as text.
*
* @param bool $formatted Whether the returned EntityDescriptor should be formatted first.
*
* @return string The serialized EntityDescriptor.
*/
public function getEntityDescriptorText($formatted = true)
{
assert(is_bool($formatted));
$xml = $this->getEntityDescriptor();
if ($formatted) {
SimpleSAML\Utils\XML::formatDOMElement($xml);
}
return $xml->ownerDocument->saveXML();
}
/**
* Add a SecurityTokenServiceType for ADFS metadata.
*
* @param array $metadata The metadata with the information about the SecurityTokenServiceType.
*/
public function addSecurityTokenServiceType($metadata)
{
assert(is_array($metadata));
assert(isset($metadata['entityid']));
assert(isset($metadata['metadata-set']));
$metadata = SimpleSAML_Configuration::loadFromArray($metadata, $metadata['entityid']);
$defaultEndpoint = $metadata->getDefaultEndpoint('SingleSignOnService');
$e = new sspmod_adfs_SAML2_XML_fed_SecurityTokenServiceType();
$e->Location = $defaultEndpoint['Location'];
$this->addCertificate($e, $metadata);
$this->entityDescriptor->RoleDescriptor[] = $e;
}
/**
* Add extensions to the metadata.
*
* @param SimpleSAML_Configuration $metadata The metadata to get extensions from.
* @param \SAML2\XML\md\RoleDescriptor $e Reference to the element where the Extensions element should be included.
*/
private function addExtensions(SimpleSAML_Configuration $metadata, \SAML2\XML\md\RoleDescriptor $e)
{
if ($metadata->hasValue('tags')) {
$a = new \SAML2\XML\saml\Attribute();
$a->Name = 'tags';
foreach ($metadata->getArray('tags') as $tag) {
$a->AttributeValue[] = new \SAML2\XML\saml\AttributeValue($tag);
}
$e->Extensions[] = $a;
}
if ($metadata->hasValue('hint.cidr')) {
$a = new \SAML2\XML\saml\Attribute();
$a->Name = 'hint.cidr';
foreach ($metadata->getArray('hint.cidr') as $hint) {
$a->AttributeValue[] = new \SAML2\XML\saml\AttributeValue($hint);
}
$e->Extensions[] = $a;
}
if ($metadata->hasValue('scope')) {
foreach ($metadata->getArray('scope') as $scopetext) {
$s = new \SAML2\XML\shibmd\Scope();
$s->scope = $scopetext;
// Check whether $ ^ ( ) * | \ are in a scope -> assume regex.
if (1 === preg_match('/[\$\^\)\(\*\|\\\\]/', $scopetext)) {
$s->regexp = true;
} else {
$s->regexp = false;
}
$e->Extensions[] = $s;
}
}
if ($metadata->hasValue('EntityAttributes')) {
$ea = new \SAML2\XML\mdattr\EntityAttributes();
foreach ($metadata->getArray('EntityAttributes') as $attributeName => $attributeValues) {
$a = new \SAML2\XML\saml\Attribute();
$a->Name = $attributeName;
$a->NameFormat = 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri';
// Attribute names that is not URI is prefixed as this: '{nameformat}name'
if (preg_match('/^\{(.*?)\}(.*)$/', $attributeName, $matches)) {
$a->Name = $matches[2];
$nameFormat = $matches[1];
if ($nameFormat !== \SAML2\Constants::NAMEFORMAT_UNSPECIFIED) {
$a->NameFormat = $nameFormat;
}
}
foreach ($attributeValues as $attributeValue) {
$a->AttributeValue[] = new \SAML2\XML\saml\AttributeValue($attributeValue);
}
$ea->children[] = $a;
}
$this->entityDescriptor->Extensions[] = $ea;
}
if ($metadata->hasValue('RegistrationInfo')) {
$ri = new \SAML2\XML\mdrpi\RegistrationInfo();
foreach ($metadata->getArray('RegistrationInfo') as $riName => $riValues) {
switch ($riName) {
case 'authority':
$ri->registrationAuthority = $riValues;
break;
case 'instant':
$ri->registrationInstant = \SAML2\Utils::xsDateTimeToTimestamp($riValues);
break;
case 'policies':
$ri->RegistrationPolicy = $riValues;
break;
}
}
$this->entityDescriptor->Extensions[] = $ri;
}
if ($metadata->hasValue('UIInfo')) {
$ui = new \SAML2\XML\mdui\UIInfo();
foreach ($metadata->getArray('UIInfo') as $uiName => $uiValues) {
switch ($uiName) {
case 'DisplayName':
$ui->DisplayName = $uiValues;
break;
case 'Description':
$ui->Description = $uiValues;
break;
case 'InformationURL':
$ui->InformationURL = $uiValues;
break;
case 'PrivacyStatementURL':
$ui->PrivacyStatementURL = $uiValues;
break;
case 'Keywords':
foreach ($uiValues as $lang => $keywords) {
$uiItem = new \SAML2\XML\mdui\Keywords();
$uiItem->lang = $lang;
$uiItem->Keywords = $keywords;
$ui->Keywords[] = $uiItem;
}
break;
case 'Logo':
foreach ($uiValues as $logo) {
$uiItem = new \SAML2\XML\mdui\Logo();
$uiItem->url = $logo['url'];
$uiItem->width = $logo['width'];
$uiItem->height = $logo['height'];
if (isset($logo['lang'])) {
$uiItem->lang = $logo['lang'];
}
$ui->Logo[] = $uiItem;
}
break;
}
}
$e->Extensions[] = $ui;
}
if ($metadata->hasValue('DiscoHints')) {
$dh = new \SAML2\XML\mdui\DiscoHints();
foreach ($metadata->getArray('DiscoHints') as $dhName => $dhValues) {
switch ($dhName) {
case 'IPHint':
$dh->IPHint = $dhValues;
break;
case 'DomainHint':
$dh->DomainHint = $dhValues;
break;
case 'GeolocationHint':
$dh->GeolocationHint = $dhValues;
break;
}
}
$e->Extensions[] = $dh;
}
}
/**
* Add an Organization element based on data passed as parameters
*
* @param array $orgName An array with the localized OrganizationName.
* @param array $orgDisplayName An array with the localized OrganizationDisplayName.
* @param array $orgURL An array with the localized OrganizationURL.
*/
public function addOrganization(array $orgName, array $orgDisplayName, array $orgURL)
{
$org = new \SAML2\XML\md\Organization();
$org->OrganizationName = $orgName;
$org->OrganizationDisplayName = $orgDisplayName;
$org->OrganizationURL = $orgURL;
$this->entityDescriptor->Organization = $org;
}
/**
* Add an Organization element based on metadata array.
*
* @param array $metadata The metadata we should extract the organization information from.
*/
public function addOrganizationInfo(array $metadata)
{
if (empty($metadata['OrganizationName']) ||
empty($metadata['OrganizationDisplayName']) ||
empty($metadata['OrganizationURL'])
) {
// empty or incomplete organization information
return;
}
$orgName = SimpleSAML\Utils\Arrays::arrayize($metadata['OrganizationName'], 'en');
$orgDisplayName = SimpleSAML\Utils\Arrays::arrayize($metadata['OrganizationDisplayName'], 'en');
$orgURL = SimpleSAML\Utils\Arrays::arrayize($metadata['OrganizationURL'], 'en');
$this->addOrganization($orgName, $orgDisplayName, $orgURL);
}
/**
* Add a list of endpoints to metadata.
*
* @param array $endpoints The endpoints.
* @param bool $indexed Whether the endpoints should be indexed.
*
* @return array An array of endpoint objects, either \SAML2\XML\md\EndpointType or \SAML2\XML\md\IndexedEndpointType.
*/
private static function createEndpoints(array $endpoints, $indexed)
{
assert(is_bool($indexed));
$ret = array();
foreach ($endpoints as &$ep) {
if ($indexed) {
$t = new \SAML2\XML\md\IndexedEndpointType();
} else {
$t = new \SAML2\XML\md\EndpointType();
}
$t->Binding = $ep['Binding'];
$t->Location = $ep['Location'];
if (isset($ep['ResponseLocation'])) {
$t->ResponseLocation = $ep['ResponseLocation'];
}
if (isset($ep['hoksso:ProtocolBinding'])) {
$t->setAttributeNS(
\SAML2\Constants::NS_HOK,
'hoksso:ProtocolBinding',
\SAML2\Constants::BINDING_HTTP_REDIRECT
);
}
if ($indexed) {
if (!isset($ep['index'])) {
// Find the maximum index
$maxIndex = -1;
foreach ($endpoints as $ep) {
if (!isset($ep['index'])) {
continue;
}
if ($ep['index'] > $maxIndex) {
$maxIndex = $ep['index'];
}
}
$ep['index'] = $maxIndex + 1;
}
$t->index = $ep['index'];
}
$ret[] = $t;
}
return $ret;
}
/**
* Add an AttributeConsumingService element to the metadata.
*
* @param \SAML2\XML\md\SPSSODescriptor $spDesc The SPSSODescriptor element.
* @param SimpleSAML_Configuration $metadata The metadata.
*/
private function addAttributeConsumingService(
\SAML2\XML\md\SPSSODescriptor $spDesc,
SimpleSAML_Configuration $metadata
) {
$attributes = $metadata->getArray('attributes', array());
$name = $metadata->getLocalizedString('name', null);
if ($name === null || count($attributes) == 0) {
// we cannot add an AttributeConsumingService without name and attributes
return;
}
$attributesrequired = $metadata->getArray('attributes.required', array());
/*
* Add an AttributeConsumingService element with information as name and description and list
* of requested attributes
*/
$attributeconsumer = new \SAML2\XML\md\AttributeConsumingService();
$attributeconsumer->index = $metadata->getInteger('attributes.index', 0);
if ($metadata->hasValue('attributes.isDefault')) {
$attributeconsumer->isDefault = $metadata->getBoolean('attributes.isDefault', false);
}
$attributeconsumer->ServiceName = $name;
$attributeconsumer->ServiceDescription = $metadata->getLocalizedString('description', array());
$nameFormat = $metadata->getString('attributes.NameFormat', \SAML2\Constants::NAMEFORMAT_UNSPECIFIED);
foreach ($attributes as $friendlyName => $attribute) {
$t = new \SAML2\XML\md\RequestedAttribute();
$t->Name = $attribute;
if (!is_int($friendlyName)) {
$t->FriendlyName = $friendlyName;
}
if ($nameFormat !== \SAML2\Constants::NAMEFORMAT_UNSPECIFIED) {
$t->NameFormat = $nameFormat;
}
if (in_array($attribute, $attributesrequired, true)) {
$t->isRequired = true;
}
$attributeconsumer->RequestedAttribute[] = $t;
}
$spDesc->AttributeConsumingService[] = $attributeconsumer;
}
/**
* Add a specific type of metadata to an entity.
*
* @param string $set The metadata set this metadata comes from.
* @param array $metadata The metadata.
*/
public function addMetadata($set, $metadata)
{
assert(is_string($set));
assert(is_array($metadata));
$this->setExpiration($metadata);
switch ($set) {
case 'saml20-sp-remote':
$this->addMetadataSP20($metadata);
break;
case 'saml20-idp-remote':
$this->addMetadataIdP20($metadata);
break;
case 'shib13-sp-remote':
$this->addMetadataSP11($metadata);
break;
case 'shib13-idp-remote':
$this->addMetadataIdP11($metadata);
break;
case 'attributeauthority-remote':
$this->addAttributeAuthority($metadata);
break;
default:
SimpleSAML\Logger::warning('Unable to generate metadata for unknown type \''.$set.'\'.');
}
}
/**
* Add SAML 2.0 SP metadata.
*
* @param array $metadata The metadata.
* @param array $protocols The protocols supported. Defaults to \SAML2\Constants::NS_SAMLP.
*/
public function addMetadataSP20($metadata, $protocols = array(\SAML2\Constants::NS_SAMLP))
{
assert(is_array($metadata));
assert(is_array($protocols));
assert(isset($metadata['entityid']));
assert(isset($metadata['metadata-set']));
$metadata = SimpleSAML_Configuration::loadFromArray($metadata, $metadata['entityid']);
$e = new \SAML2\XML\md\SPSSODescriptor();
$e->protocolSupportEnumeration = $protocols;
if ($metadata->hasValue('saml20.sign.assertion')) {
$e->WantAssertionsSigned = $metadata->getBoolean('saml20.sign.assertion');
}
if ($metadata->hasValue('redirect.validate')) {
$e->AuthnRequestsSigned = $metadata->getBoolean('redirect.validate');
} elseif ($metadata->hasValue('validate.authnrequest')) {
$e->AuthnRequestsSigned = $metadata->getBoolean('validate.authnrequest');
}
$this->addExtensions($metadata, $e);
$this->addCertificate($e, $metadata);
$e->SingleLogoutService = self::createEndpoints($metadata->getEndpoints('SingleLogoutService'), false);
$e->NameIDFormat = $metadata->getArrayizeString('NameIDFormat', array());
$endpoints = $metadata->getEndpoints('AssertionConsumerService');
foreach ($metadata->getArrayizeString('AssertionConsumerService.artifact', array()) as $acs) {
$endpoints[] = array(
'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact',
'Location' => $acs,
);
}
$e->AssertionConsumerService = self::createEndpoints($endpoints, true);
$this->addAttributeConsumingService($e, $metadata);
$this->entityDescriptor->RoleDescriptor[] = $e;
foreach ($metadata->getArray('contacts', array()) as $contact) {
if (array_key_exists('contactType', $contact) && array_key_exists('emailAddress', $contact)) {
$this->addContact($contact['contactType'], \SimpleSAML\Utils\Config\Metadata::getContact($contact));
}
}
}
/**
* Add metadata of a SAML 2.0 identity provider.
*
* @param array $metadata The metadata.
*/
public function addMetadataIdP20($metadata)
{
assert(is_array($metadata));
assert(isset($metadata['entityid']));
assert(isset($metadata['metadata-set']));
$metadata = SimpleSAML_Configuration::loadFromArray($metadata, $metadata['entityid']);
$e = new \SAML2\XML\md\IDPSSODescriptor();
$e->protocolSupportEnumeration[] = 'urn:oasis:names:tc:SAML:2.0:protocol';
if ($metadata->hasValue('sign.authnrequest')) {
$e->WantAuthnRequestsSigned = $metadata->getBoolean('sign.authnrequest');
} elseif ($metadata->hasValue('redirect.sign')) {
$e->WantAuthnRequestsSigned = $metadata->getBoolean('redirect.sign');
}
$this->addExtensions($metadata, $e);
$this->addCertificate($e, $metadata);
if ($metadata->hasValue('ArtifactResolutionService')) {
$e->ArtifactResolutionService = self::createEndpoints(
$metadata->getEndpoints('ArtifactResolutionService'),
true
);
}
$e->SingleLogoutService = self::createEndpoints($metadata->getEndpoints('SingleLogoutService'), false);
$e->NameIDFormat = $metadata->getArrayizeString('NameIDFormat', array());
$e->SingleSignOnService = self::createEndpoints($metadata->getEndpoints('SingleSignOnService'), false);
$this->entityDescriptor->RoleDescriptor[] = $e;
foreach ($metadata->getArray('contacts', array()) as $contact) {
if (array_key_exists('contactType', $contact) && array_key_exists('emailAddress', $contact)) {
$this->addContact($contact['contactType'], \SimpleSAML\Utils\Config\Metadata::getContact($contact));
}
}
}
/**
* Add metadata of a SAML 1.1 service provider.
*
* @param array $metadata The metadata.
*/
public function addMetadataSP11($metadata)
{
assert(is_array($metadata));
assert(isset($metadata['entityid']));
assert(isset($metadata['metadata-set']));
$metadata = SimpleSAML_Configuration::loadFromArray($metadata, $metadata['entityid']);
$e = new \SAML2\XML\md\SPSSODescriptor();
$e->protocolSupportEnumeration[] = 'urn:oasis:names:tc:SAML:1.1:protocol';
$this->addCertificate($e, $metadata);
$e->NameIDFormat = $metadata->getArrayizeString('NameIDFormat', array());
$endpoints = $metadata->getEndpoints('AssertionConsumerService');
foreach ($metadata->getArrayizeString('AssertionConsumerService.artifact', array()) as $acs) {
$endpoints[] = array(
'Binding' => 'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01',
'Location' => $acs,
);
}
$e->AssertionConsumerService = self::createEndpoints($endpoints, true);
$this->addAttributeConsumingService($e, $metadata);
$this->entityDescriptor->RoleDescriptor[] = $e;
}
/**
* Add metadata of a SAML 1.1 identity provider.
*
* @param array $metadata The metadata.
*/
public function addMetadataIdP11($metadata)
{
assert(is_array($metadata));
assert(isset($metadata['entityid']));
assert(isset($metadata['metadata-set']));
$metadata = SimpleSAML_Configuration::loadFromArray($metadata, $metadata['entityid']);
$e = new \SAML2\XML\md\IDPSSODescriptor();
$e->protocolSupportEnumeration[] = 'urn:oasis:names:tc:SAML:1.1:protocol';
$e->protocolSupportEnumeration[] = 'urn:mace:shibboleth:1.0';
$this->addCertificate($e, $metadata);
$e->NameIDFormat = $metadata->getArrayizeString('NameIDFormat', array());
$e->SingleSignOnService = self::createEndpoints($metadata->getEndpoints('SingleSignOnService'), false);
$this->entityDescriptor->RoleDescriptor[] = $e;
}
/**
* Add metadata of a SAML attribute authority.
*
* @param array $metadata The AttributeAuthorityDescriptor, in the format returned by
* SimpleSAML_Metadata_SAMLParser.
*/
public function addAttributeAuthority(array $metadata)
{
assert(is_array($metadata));
assert(isset($metadata['entityid']));
assert(isset($metadata['metadata-set']));
$metadata = SimpleSAML_Configuration::loadFromArray($metadata, $metadata['entityid']);
$e = new \SAML2\XML\md\AttributeAuthorityDescriptor();
$e->protocolSupportEnumeration = $metadata->getArray('protocols', array(\SAML2\Constants::NS_SAMLP));
$this->addExtensions($metadata, $e);
$this->addCertificate($e, $metadata);
$e->AttributeService = self::createEndpoints($metadata->getEndpoints('AttributeService'), false);
$e->AssertionIDRequestService = self::createEndpoints(
$metadata->getEndpoints('AssertionIDRequestService'),
false
);
$e->NameIDFormat = $metadata->getArrayizeString('NameIDFormat', array());
$this->entityDescriptor->RoleDescriptor[] = $e;
}
/**
* Add contact information.
*
* Accepts a contact type, and a contact array that must be previously sanitized.
*
* WARNING: This function will change its signature and no longer parse a 'name' element.
*
* @param string $type The type of contact. Deprecated.
* @param array $details The details about the contact.
*
* @todo Change the signature to remove $type.
* @todo Remove the capability to pass a name and parse it inside the method.
*/
public function addContact($type, $details)
{
assert(is_string($type));
assert(is_array($details));
assert(in_array($type, array('technical', 'support', 'administrative', 'billing', 'other'), true));
// TODO: remove this check as soon as getContact() is called always before calling this function
$details = \SimpleSAML\Utils\Config\Metadata::getContact($details);
$e = new \SAML2\XML\md\ContactPerson();
$e->contactType = $type;
if (!empty($details['attributes'])) {
$e->ContactPersonAttributes = $details['attributes'];
}
if (isset($details['company'])) {
$e->Company = $details['company'];
}
if (isset($details['givenName'])) {
$e->GivenName = $details['givenName'];
}
if (isset($details['surName'])) {
$e->SurName = $details['surName'];
}
if (isset($details['emailAddress'])) {
$eas = $details['emailAddress'];
if (!is_array($eas)) {
$eas = array($eas);
}
foreach ($eas as $ea) {
$e->EmailAddress[] = $ea;
}
}
if (isset($details['telephoneNumber'])) {
$tlfNrs = $details['telephoneNumber'];
if (!is_array($tlfNrs)) {
$tlfNrs = array($tlfNrs);
}
foreach ($tlfNrs as $tlfNr) {
$e->TelephoneNumber[] = $tlfNr;
}
}
$this->entityDescriptor->ContactPerson[] = $e;
}
/**
* Add a KeyDescriptor with an X509 certificate.
*
* @param \SAML2\XML\md\RoleDescriptor $rd The RoleDescriptor the certificate should be added to.
* @param string $use The value of the 'use' attribute.
* @param string $x509data The certificate data.
*/
private function addX509KeyDescriptor(\SAML2\XML\md\RoleDescriptor $rd, $use, $x509data)
{
assert(in_array($use, array('encryption', 'signing'), true));
assert(is_string($x509data));
$keyDescriptor = \SAML2\Utils::createKeyDescriptor($x509data);
$keyDescriptor->use = $use;
$rd->KeyDescriptor[] = $keyDescriptor;
}
/**
* Add a certificate.
*
* Helper function for adding a certificate to the metadata.
*
* @param \SAML2\XML\md\RoleDescriptor $rd The RoleDescriptor the certificate should be added to.
* @param SimpleSAML_Configuration $metadata The metadata of the entity.
*/
private function addCertificate(\SAML2\XML\md\RoleDescriptor $rd, SimpleSAML_Configuration $metadata)
{
$keys = $metadata->getPublicKeys();
foreach ($keys as $key) {
if ($key['type'] !== 'X509Certificate') {
continue;
}
if (!isset($key['signing']) || $key['signing'] === true) {
$this->addX509KeyDescriptor($rd, 'signing', $key['X509Certificate']);
}
if (!isset($key['encryption']) || $key['encryption'] === true) {
$this->addX509KeyDescriptor($rd, 'encryption', $key['X509Certificate']);
}
}
if ($metadata->hasValue('https.certData')) {
$this->addX509KeyDescriptor($rd, 'signing', $metadata->getString('https.certData'));
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,283 @@
<?php
/**
* This class implements a helper function for signing of metadata.
*
* @author Olav Morken, UNINETT AS.
* @package SimpleSAMLphp
*/
class SimpleSAML_Metadata_Signer
{
/**
* This functions finds what key & certificate files should be used to sign the metadata
* for the given entity.
*
* @param SimpleSAML_Configuration $config Our SimpleSAML_Configuration instance.
* @param array $entityMetadata The metadata of the entity.
* @param string $type A string which describes the type entity this is, e.g. 'SAML 2 IdP' or
* 'Shib 1.3 SP'.
*
* @return array An associative array with the keys 'privatekey', 'certificate', and optionally 'privatekey_pass'.
* @throws Exception If the key and certificate used to sign is unknown.
*/
private static function findKeyCert($config, $entityMetadata, $type)
{
// first we look for metadata.privatekey and metadata.certificate in the metadata
if (array_key_exists('metadata.sign.privatekey', $entityMetadata)
|| array_key_exists('metadata.sign.certificate', $entityMetadata)
) {
if (!array_key_exists('metadata.sign.privatekey', $entityMetadata)
|| !array_key_exists('metadata.sign.certificate', $entityMetadata)
) {
throw new Exception(
'Missing either the "metadata.sign.privatekey" or the'.
' "metadata.sign.certificate" configuration option in the metadata for'.
' the '.$type.' "'.$entityMetadata['entityid'].'". If one of'.
' these options is specified, then the other must also be specified.'
);
}
$ret = array(
'privatekey' => $entityMetadata['metadata.sign.privatekey'],
'certificate' => $entityMetadata['metadata.sign.certificate']
);
if (array_key_exists('metadata.sign.privatekey_pass', $entityMetadata)) {
$ret['privatekey_pass'] = $entityMetadata['metadata.sign.privatekey_pass'];
}
return $ret;
}
// then we look for default values in the global configuration
$privatekey = $config->getString('metadata.sign.privatekey', null);
$certificate = $config->getString('metadata.sign.certificate', null);
if ($privatekey !== null || $certificate !== null) {
if ($privatekey === null || $certificate === null) {
throw new Exception(
'Missing either the "metadata.sign.privatekey" or the'.
' "metadata.sign.certificate" configuration option in the global'.
' configuration. If one of these options is specified, then the other'.
' must also be specified.'
);
}
$ret = array('privatekey' => $privatekey, 'certificate' => $certificate);
$privatekey_pass = $config->getString('metadata.sign.privatekey_pass', null);
if ($privatekey_pass !== null) {
$ret['privatekey_pass'] = $privatekey_pass;
}
return $ret;
}
// as a last resort we attempt to use the privatekey and certificate option from the metadata
if (array_key_exists('privatekey', $entityMetadata)
|| array_key_exists('certificate', $entityMetadata)
) {
if (!array_key_exists('privatekey', $entityMetadata)
|| !array_key_exists('certificate', $entityMetadata)
) {
throw new Exception(
'Both the "privatekey" and the "certificate" option must'.
' be set in the metadata for the '.$type.' "'.
$entityMetadata['entityid'].'" before it is possible to sign metadata'.
' from this entity.'
);
}
$ret = array(
'privatekey' => $entityMetadata['privatekey'],
'certificate' => $entityMetadata['certificate']
);
if (array_key_exists('privatekey_pass', $entityMetadata)) {
$ret['privatekey_pass'] = $entityMetadata['privatekey_pass'];
}
return $ret;
}
throw new Exception(
'Could not find what key & certificate should be used to sign the metadata'.
' for the '.$type.' "'.$entityMetadata['entityid'].'".'
);
}
/**
* Determine whether metadata signing is enabled for the given metadata.
*
* @param SimpleSAML_Configuration $config Our SimpleSAML_Configuration instance.
* @param array $entityMetadata The metadata of the entity.
* @param string $type A string which describes the type entity this is, e.g. 'SAML 2 IdP' or
* 'Shib 1.3 SP'.
*
* @return boolean True if metadata signing is enabled, false otherwise.
* @throws Exception If the value of the 'metadata.sign.enable' option is not a boolean.
*/
private static function isMetadataSigningEnabled($config, $entityMetadata, $type)
{
// first check the metadata for the entity
if (array_key_exists('metadata.sign.enable', $entityMetadata)) {
if (!is_bool($entityMetadata['metadata.sign.enable'])) {
throw new Exception(
'Invalid value for the "metadata.sign.enable" configuration option for'.
' the '.$type.' "'.$entityMetadata['entityid'].'". This option'.
' should be a boolean.'
);
}
return $entityMetadata['metadata.sign.enable'];
}
$enabled = $config->getBoolean('metadata.sign.enable', false);
return $enabled;
}
/**
* Determine the signature and digest algorithms to use when signing metadata.
*
* This method will look for the 'metadata.sign.algorithm' key in the $entityMetadata array, or look for such
* a configuration option in the $config object.
*
* @param SimpleSAML_Configuration $config The global configuration.
* @param array $entityMetadata An array containing the metadata related to this entity.
* @param string $type A string describing the type of entity. E.g. 'SAML 2 IdP' or 'Shib 1.3 SP'.
*
* @return array An array with two keys, 'algorithm' and 'digest', corresponding to the signature and digest
* algorithms to use, respectively.
*
* @throws \SimpleSAML\Error\CriticalConfigurationError
*/
private static function getMetadataSigningAlgorithm($config, $entityMetadata, $type)
{
// configure the algorithm to use
if (array_key_exists('metadata.sign.algorithm', $entityMetadata)) {
if (!is_string($entityMetadata['metadata.sign.algorithm'])) {
throw new \SimpleSAML\Error\CriticalConfigurationError(
"Invalid value for the 'metadata.sign.algorithm' configuration option for the ".$type.
"'".$entityMetadata['entityid']."'. This option has restricted values"
);
}
$alg = $entityMetadata['metadata.sign.algorithm'];
} else {
$alg = $config->getString('metadata.sign.algorithm', XMLSecurityKey::RSA_SHA256);
}
$supported_algs = array(
XMLSecurityKey::RSA_SHA1,
XMLSecurityKey::RSA_SHA256,
XMLSecurityKey::RSA_SHA384,
XMLSecurityKey::RSA_SHA512,
);
if (!in_array($alg, $supported_algs, true)) {
throw new \SimpleSAML\Error\CriticalConfigurationError("Unknown signature algorithm '$alg'");
}
switch ($alg) {
case XMLSecurityKey::RSA_SHA256:
$digest = XMLSecurityDSig::SHA256;
break;
case XMLSecurityKey::RSA_SHA384:
$digest = XMLSecurityDSig::SHA384;
break;
case XMLSecurityKey::RSA_SHA512:
$digest = XMLSecurityDSig::SHA512;
break;
default:
$digest = XMLSecurityDSig::SHA1;
}
return array(
'algorithm' => $alg,
'digest' => $digest,
);
}
/**
* Signs the given metadata if metadata signing is enabled.
*
* @param string $metadataString A string with the metadata.
* @param array $entityMetadata The metadata of the entity.
* @param string $type A string which describes the type entity this is, e.g. 'SAML 2 IdP' or 'Shib 1.3 SP'.
*
* @return string The $metadataString with the signature embedded.
* @throws Exception If the certificate or private key cannot be loaded, or the metadata doesn't parse properly.
*/
public static function sign($metadataString, $entityMetadata, $type)
{
$config = SimpleSAML_Configuration::getInstance();
// check if metadata signing is enabled
if (!self::isMetadataSigningEnabled($config, $entityMetadata, $type)) {
return $metadataString;
}
// find the key & certificate which should be used to sign the metadata
$keyCertFiles = self::findKeyCert($config, $entityMetadata, $type);
$keyFile = \SimpleSAML\Utils\Config::getCertPath($keyCertFiles['privatekey']);
if (!file_exists($keyFile)) {
throw new Exception('Could not find private key file ['.$keyFile.'], which is needed to sign the metadata');
}
$keyData = file_get_contents($keyFile);
$certFile = \SimpleSAML\Utils\Config::getCertPath($keyCertFiles['certificate']);
if (!file_exists($certFile)) {
throw new Exception(
'Could not find certificate file ['.$certFile.'], which is needed to sign the metadata'
);
}
$certData = file_get_contents($certFile);
// convert the metadata to a DOM tree
try {
$xml = \SAML2\DOMDocumentFactory::fromString($metadataString);
} catch (Exception $e) {
throw new Exception('Error parsing self-generated metadata.');
}
$signature_cf = self::getMetadataSigningAlgorithm($config, $entityMetadata, $type);
// load the private key
$objKey = new XMLSecurityKey($signature_cf['algorithm'], array('type' => 'private'));
if (array_key_exists('privatekey_pass', $keyCertFiles)) {
$objKey->passphrase = $keyCertFiles['privatekey_pass'];
}
$objKey->loadKey($keyData, false);
// get the EntityDescriptor node we should sign
$rootNode = $xml->firstChild;
// sign the metadata with our private key
$objXMLSecDSig = new XMLSecurityDSig();
$objXMLSecDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N);
$objXMLSecDSig->addReferenceList(
array($rootNode),
$signature_cf['digest'],
array('http://www.w3.org/2000/09/xmldsig#enveloped-signature', XMLSecurityDSig::EXC_C14N),
array('id_name' => 'ID')
);
$objXMLSecDSig->sign($objKey);
// add the certificate to the signature
$objXMLSecDSig->add509Cert($certData, true);
// add the signature to the metadata
$objXMLSecDSig->insertSignature($rootNode, $rootNode->firstChild);
// return the DOM tree as a string
return $xml->saveXML();
}
}

View File

@@ -0,0 +1,337 @@
<?php
namespace SimpleSAML\Metadata\Sources;
use SimpleSAML\Logger;
use SimpleSAML\Utils\HTTP;
/**
* This class implements SAML Metadata Query Protocol
*
* @author Andreas Åkre Solberg, UNINETT AS.
* @author Olav Morken, UNINETT AS.
* @author Tamas Frank, NIIFI
* @package SimpleSAMLphp
*/
class MDQ extends \SimpleSAML_Metadata_MetaDataStorageSource
{
/**
* The URL of MDQ server (url:port)
*
* @var string
*/
private $server;
/**
* The fingerprint of the certificate used to sign the metadata. You don't need this option if you don't want to
* validate the signature on the metadata.
*
* @var string|null
*/
private $validateFingerprint;
/**
* The cache directory, or null if no cache directory is configured.
*
* @var string|null
*/
private $cacheDir;
/**
* The maximum cache length, in seconds.
*
* @var integer
*/
private $cacheLength;
/**
* This function initializes the dynamic XML metadata source.
*
* Options:
* - 'server': URL of the MDQ server (url:port). Mandatory.
* - 'validateFingerprint': The fingerprint of the certificate used to sign the metadata.
* You don't need this option if you don't want to validate the signature on the metadata.
* Optional.
* - 'cachedir': Directory where metadata can be cached. Optional.
* - 'cachelength': Maximum time metadata cah be cached, in seconds. Default to 24
* hours (86400 seconds).
*
* @param array $config The configuration for this instance of the XML metadata source.
*
* @throws \Exception If no server option can be found in the configuration.
*/
protected function __construct($config)
{
assert(is_array($config));
if (!array_key_exists('server', $config)) {
throw new \Exception(__CLASS__.": the 'server' configuration option is not set.");
} else {
$this->server = $config['server'];
}
if (array_key_exists('validateFingerprint', $config)) {
$this->validateFingerprint = $config['validateFingerprint'];
} else {
$this->validateFingerprint = null;
}
if (array_key_exists('cachedir', $config)) {
$globalConfig = \SimpleSAML_Configuration::getInstance();
$this->cacheDir = $globalConfig->resolvePath($config['cachedir']);
} else {
$this->cacheDir = null;
}
if (array_key_exists('cachelength', $config)) {
$this->cacheLength = $config['cachelength'];
} else {
$this->cacheLength = 86400;
}
}
/**
* This function is not implemented.
*
* @param string $set The set we want to list metadata for.
*
* @return array An empty array.
*/
public function getMetadataSet($set)
{
// we don't have this metadata set
return array();
}
/**
* Find the cache file name for an entity,
*
* @param string $set The metadata set this entity belongs to.
* @param string $entityId The entity id of this entity.
*
* @return string The full path to the cache file.
*/
private function getCacheFilename($set, $entityId)
{
assert(is_string($set));
assert(is_string($entityId));
$cachekey = sha1($entityId);
return $this->cacheDir.'/'.$set.'-'.$cachekey.'.cached.xml';
}
/**
* Load a entity from the cache.
*
* @param string $set The metadata set this entity belongs to.
* @param string $entityId The entity id of this entity.
*
* @return array|NULL The associative array with the metadata for this entity, or NULL
* if the entity could not be found.
* @throws \Exception If an error occurs while loading metadata from cache.
*/
private function getFromCache($set, $entityId)
{
assert(is_string($set));
assert(is_string($entityId));
if (empty($this->cacheDir)) {
return null;
}
$cachefilename = $this->getCacheFilename($set, $entityId);
if (!file_exists($cachefilename)) {
return null;
}
if (!is_readable($cachefilename)) {
throw new \Exception(__CLASS__.': could not read cache file for entity ['.$cachefilename.']');
}
Logger::debug(__CLASS__.': reading cache ['.$entityId.'] => ['.$cachefilename.']');
/* Ensure that this metadata isn't older that the cachelength option allows. This
* must be verified based on the file, since this option may be changed after the
* file is written.
*/
$stat = stat($cachefilename);
if ($stat['mtime'] + $this->cacheLength <= time()) {
Logger::debug(__CLASS__.': cache file older that the cachelength option allows.');
return null;
}
$rawData = file_get_contents($cachefilename);
if (empty($rawData)) {
$error = error_get_last();
throw new \Exception(
__CLASS__.': error reading metadata from cache file "'.$cachefilename.'": '.$error['message']
);
}
$data = unserialize($rawData);
if ($data === false) {
throw new \Exception(__CLASS__.': error unserializing cached data from file "'.$cachefilename.'".');
}
if (!is_array($data)) {
throw new \Exception(__CLASS__.': Cached metadata from "'.$cachefilename.'" wasn\'t an array.');
}
return $data;
}
/**
* Save a entity to the cache.
*
* @param string $set The metadata set this entity belongs to.
* @param string $entityId The entity id of this entity.
* @param array $data The associative array with the metadata for this entity.
*
* @throws \Exception If metadata cannot be written to cache.
*/
private function writeToCache($set, $entityId, $data)
{
assert(is_string($set));
assert(is_string($entityId));
assert(is_array($data));
if (empty($this->cacheDir)) {
return;
}
$cachefilename = $this->getCacheFilename($set, $entityId);
if (!is_writable(dirname($cachefilename))) {
throw new \Exception(__CLASS__.': could not write cache file for entity ['.$cachefilename.']');
}
Logger::debug(__CLASS__.': Writing cache ['.$entityId.'] => ['.$cachefilename.']');
file_put_contents($cachefilename, serialize($data));
}
/**
* Retrieve metadata for the correct set from a SAML2Parser.
*
* @param \SimpleSAML_Metadata_SAMLParser $entity A SAML2Parser representing an entity.
* @param string $set The metadata set we are looking for.
*
* @return array|NULL The associative array with the metadata, or NULL if no metadata for
* the given set was found.
*/
private static function getParsedSet(\SimpleSAML_Metadata_SAMLParser $entity, $set)
{
assert(is_string($set));
switch ($set) {
case 'saml20-idp-remote':
return $entity->getMetadata20IdP();
case 'saml20-sp-remote':
return $entity->getMetadata20SP();
case 'shib13-idp-remote':
return $entity->getMetadata1xIdP();
case 'shib13-sp-remote':
return $entity->getMetadata1xSP();
case 'attributeauthority-remote':
$ret = $entity->getAttributeAuthorities();
return $ret[0];
default:
Logger::warning(__CLASS__.': unknown metadata set: \''.$set.'\'.');
}
return null;
}
/**
* Overriding this function from the superclass SimpleSAML_Metadata_MetaDataStorageSource.
*
* 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 An associative array with metadata for the given entity, or NULL if we are unable to
* locate the entity.
* @throws \Exception If an error occurs while validating the signature or the metadata is in an
* incorrect set.
*/
public function getMetaData($index, $set)
{
assert(is_string($index));
assert(is_string($set));
Logger::info(__CLASS__.': loading metadata entity ['.$index.'] from ['.$set.']');
// read from cache if possible
try {
$data = $this->getFromCache($set, $index);
} catch (\Exception $e) {
Logger::error($e->getMessage());
// proceed with fetching metadata even if the cache is broken
$data = null;
}
if ($data !== null && array_key_exists('expires', $data) && $data['expires'] < time()) {
// metadata has expired
$data = null;
}
if (isset($data)) {
// metadata found in cache and not expired
Logger::debug(__CLASS__.': using cached metadata for: '.$index.'.');
return $data;
}
// look at Metadata Query Protocol: https://github.com/iay/md-query/blob/master/draft-young-md-query.txt
$mdq_url = $this->server.'/entities/'.urlencode($index);
Logger::debug(__CLASS__.': downloading metadata for "'.$index.'" from ['.$mdq_url.']');
try {
$xmldata = HTTP::fetch($mdq_url);
} catch (\Exception $e) {
// Avoid propagating the exception, make sure we can handle the error later
$xmldata = false;
}
if (empty($xmldata)) {
$error = error_get_last();
Logger::info('Unable to fetch metadata for "'.$index.'" from '.$mdq_url.': '.
(is_array($error) ? $error['message'] : 'no error available'));
return null;
}
/** @var string $xmldata */
$entity = \SimpleSAML_Metadata_SAMLParser::parseString($xmldata);
Logger::debug(__CLASS__.': completed parsing of ['.$mdq_url.']');
if ($this->validateFingerprint !== null) {
if (!$entity->validateFingerprint($this->validateFingerprint)) {
throw new \Exception(__CLASS__.': error, could not verify signature for entity: '.$index.'".');
}
}
$data = self::getParsedSet($entity, $set);
if ($data === null) {
throw new \Exception(__CLASS__.': no metadata for set "'.$set.'" available from "'.$index.'".');
}
try {
$this->writeToCache($set, $index, $data);
} catch (\Exception $e) {
// Proceed without writing to cache
Logger::error('Error writing MDQ result to cache: '.$e->getMessage());
}
return $data;
}
}