Skip to content
Snippets Groups Projects
Commit 77dec968 authored by Andreas Åkre Solberg's avatar Andreas Åkre Solberg
Browse files

Improvements to the aggregator module. Added documentation, and re-written more OO-oriented.

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@1894 44740490-163a-0410-bde0-09ae8108e29a
parent b6a25762
No related branches found
No related tags found
Loading
......@@ -6,19 +6,28 @@ $config = array(
/* List of aggregators. */
'aggregators' => array(
'example' => array(
array('type' => 'flatfile'), /* Metadata from metadata-directory. */
array('type' => 'xml', 'url' => 'https://idp.example.org/Metadata'),
array('type' => 'xml', 'file' => 'static-metadata.xml'),
'source' => array(
array('type' => 'flatfile'), /* Metadata from metadata-directory. */
array('type' => 'xml', 'url' => 'https://idp.example.org/Metadata'),
array('type' => 'xml', 'file' => 'static-metadata.xml'),
),
),
'example2' => array(
'source' => array(
array('type' => 'xml', 'url' => 'https://idp.example.org/Metadata2'),
),
'set' => 'saml2',
'sign.privatekey' => 'server2.key',
'sign.certificate' => 'server2.crt',
)
),
'maxCache' => 60*60*24, // 24 hour cache time
'maxDuration' => 60*60*24*5, // Maximum 5 days duration on ValidUntil.
// If base64 encoded for entity is already cached in the entity, should we
// reconstruct the XML or re-use.
'reconstruct' => TRUE,
'reconstruct' => FALSE,
/* Whether metadata should be signed. */
'sign.enable' => FALSE,
......@@ -34,4 +43,3 @@ $config = array(
);
?>
\ No newline at end of file
aggregator Module
=================
<!--
This file is written in Markdown syntax.
For more information about how to use the Markdown syntax, read here:
http://daringfireball.net/projects/markdown/syntax
-->
* Version: `$Id$`
* Author: Andreas Åkre Solberg <andreas.solberg@uninett.no>, UNINETT AS
* Package: simpleSAMLphp
This module, aggregates a set of metadata of SAML entities to SAML 2.0 documents with an `EntitiesDescriptor` with multiple entities inside.
Multiple aggregates can be configured.
The configuration file: module_aggregate.php
--------------------------------------------
The configuration file includes an option `aggregators`, which includes a indexed list of different aggregator configurations that all can be accessed independently. The structure is as follows:
'aggregators' => array(
'aggr1' => array(
'sources' => [...]
[...local params...]
),
'aggr2' => ...
)
[...global params...]
All of the global parameters can be overriden for each aggregator. Here is a list of the available (global) paramters:
`set`
: By default all SAML types are available, including: `array('saml20-idp-remote', 'saml20-sp-remote', 'shib13-idp-remote', 'shib13-sp-remote')`. This list can be reduced by specifying one of the following values:
* `saml20-idp-remote`
* `saml20-sp-remote`
* `shib13-idp-remote`
* `shib13-sp-remote`
* `saml2`
* `shib13`
`foo`
: sldkfjdslkjf
`reconstruct`
: Whether simpleSAMLphp should regenerate the metadata XML (TRUE) or pass-through the input metadata XML (FALSE).
`maxDuration`
: Max validity of metadata (duration) in seconds.
`sign.enable`
: Enable signing of metadata document
`sign.privatekey`
: Private key to use when signing
`sign.privatekey_pass`
: Optionally a passphrase to the private key
`sign.certificate`
: Certificate to embed, corresponding to the private key.
Accessing the aggregate
-----------------------
On the SimpleSAMLphp frontpage on the federation tab, there is a link to the aggregator named *Metadata aggregator*.
When accessing the aggregator endpoint without specifying an aggregate ID, a list of available aggregators will be presented, with different options for mime-type presenting the result.
The endpoint supports the following query parameter:
`id`
: The ID of the aggregator (From configuration file)
`set`
: Subset the available types of SAML entities. Similar to the `set` parameter described over in the configuration file description.
`exclude`
: Specify a `tag` that will be excluded from the metadata set. Useful for leaving out your own federation metadata.
`mimetype`
: Select Mime-Type that will be used. Default is `application/samlmetadata+xml`.
<?php
/**
* Aggregates metadata for multiple sources into one signed file
*
* @author Andreas Åkre Solberg <andreas.solberg@uninett.no>
* @package simpleSAMLphp
* @version $Id$
*/
class sspmod_aggregator_Aggregator {
// Configuration for the whole aggregator module
private $gConfig;
// Configuration for the specific aggregate
private $aConfig;
private $sets;
private $excludeTags = array();
private $id;
/**
* Constructor for the Aggregator.
*
*/
public function __construct($gConfig, $aConfig, $id) {
$this->gConfig = $gConfig;
$this->aConfig = $aConfig;
$this->id = $id;
$this->sets = array('saml20-idp-remote', 'saml20-sp-remote', 'shib13-idp-remote', 'shib13-sp-remote');
if ($this->aConfig->hasValue('set')) {
$this->limitSets($this->aConfig->getString('set'));
}
}
public function limitSets($set) {
if (is_array($set)) {
$this->sets = array_intersect($this->sets, $set);
return;
}
switch($set) {
case 'saml2' :
$this->sets = array_intersect($this->sets, array('saml20-idp-remote', 'saml20-sp-remote')); break;
case 'shib13' :
$this->sets = array_intersect($this->sets, array('shib13-idp-remote', 'shib13-sp-remote')); break;
case 'idp' :
$this->sets = array_intersect($this->sets, array('saml20-idp-remote', 'shib13-idp-remote')); break;
case 'sp' :
$this->sets = array_intersect($this->sets, array('saml20-sp-remote', 'shib13-sp-remote')); break;
default:
$this->sets = array_intersect($this->sets, array($set));
}
}
/**
* Add tag to excelude when collecting source metadata.
*
* $exclude May be string or array identifying a tag to exclude.
*/
public function exclude($exclude) {
$this->excludeTags = array_merge($this->excludeTags, SimpleSAML_Utilities::arrayize($exclude));
}
/**
* Returns a list of entities with metadata
*/
public function getSources() {
$sourcesDef = $this->aConfig->getArray('sources');
try {
$sources = SimpleSAML_Metadata_MetaDataStorageSource::parseSources($sourcesDef);
} catch (Exception $e) {
throw new Exception('Invalid aggregator source configuration for aggregator ' .
var_export($id, TRUE) . ': ' . $e->getMessage());
}
#echo $exclude; exit;
/* Find list of all available entities. */
$entities = array();
#echo '<pre>'; print_r($this->sets); exit;
foreach ($sources as $source) {
foreach ($this->sets as $set) {
foreach ($source->getMetadataSet($set) as $entityId => $metadata) {
if (isset($metadata['tags']) &&
count(array_intersect($this->excludeTags, $metadata['tags'])) > 0) {
SimpleSAML_Logger::debug('Excluding entity ID [' . $entityId . '] becuase it is tagged with one of [' .
var_export($this->excludeTags, TRUE) . ']');
continue;
} else {
#echo('<pre>'); print_r($metadata); exit;
}
if (!array_key_exists($entityId, $entities))
$entities[$entityId] = array();
if (array_key_exists($set, $entities[$entityId])) {
/* Entity already has metadata for the given set. */
continue;
}
$entities[$entityId][$set] = $metadata;
}
}
}
return $entities;
}
public function getMaxDuration() {
if ($this->aConfig->hasValue('maxDuration'))
return $this->aConfig->getInteger('maxDuration');
if ($this->gConfig->hasValue('maxDuration'))
return $this->gConfig->getInteger('maxDuration');
return NULL;
}
public function getReconstruct() {
if ($this->aConfig->hasValue('reconstruct'))
return $this->aConfig->getBoolean('reconstruct');
if ($this->gConfig->hasValue('reconstruct'))
return $this->gConfig->getBoolean('reconstruct');
return FALSE;
}
public function shouldSign() {
if ($this->aConfig->hasValue('sign.enable'))
return $this->aConfig->getBoolean('sign.enable');
if ($this->gConfig->hasValue('sign.enable'))
return $this->gConfig->getBoolean('sign.enable');
return FALSE;
}
public function getSigningInfo() {
if ($this->aConfig->hasValue('sign.privatekey')) {
return array(
'privatekey' => $this->aConfig->getString('sign.privatekey'),
'privatekey_pass' => $this->aConfig->getString('sign.privatekey_pass', NULL),
'certificate' => $this->aConfig->getString('sign.certificate'),
'id' => 'ID'
);
}
return array(
'privatekey' => $this->gConfig->getString('sign.privatekey'),
'privatekey_pass' => $this->gConfig->getString('sign.privatekey_pass', NULL),
'certificate' => $this->gConfig->getString('sign.certificate'),
'id' => 'ID'
);
}
public function getMetadataDocument() {
// Get metadata entries
$entities = $this->getSources();
// Generate XML Document
$xml = new DOMDocument();
$entitiesDescriptor = $xml->createElementNS('urn:oasis:names:tc:SAML:2.0:metadata', 'EntitiesDescriptor');
$entitiesDescriptor->setAttribute('Name', $this->id);
$xml->appendChild($entitiesDescriptor);
$maxDuration = $this->getMaxDuration();
$reconstruct = $this->getReconstruct();
/* Build EntityDescriptor elements for them. */
foreach ($entities as $entity => $sets) {
$entityDescriptor = NULL;
foreach ($sets as $set => $metadata) {
if (!array_key_exists('entityDescriptor', $metadata)) {
/* One of the sets doesn't contain an EntityDescriptor element. */
$entityDescriptor = FALSE;
break;
}
if ($entityDescriptor == NULL) {
/* First EntityDescriptor elements. */
$entityDescriptor = $metadata['entityDescriptor'];
continue;
}
assert('is_string($entityDescriptor)');
if ($entityDescriptor !== $metadata['entityDescriptor']) {
/* Entity contains multiple different EntityDescriptor elements. */
$entityDescriptor = FALSE;
break;
}
}
if (is_string($entityDescriptor) && !$reconstruct) {
/* All metadata sets for the entity contain the same entity descriptor. Use that one. */
$tmp = new DOMDocument();
$tmp->loadXML(base64_decode($entityDescriptor));
$entityDescriptor = $tmp->documentElement;
} else {
$tmp = new SimpleSAML_Metadata_SAMLBuilder($entity, $maxDuration, $maxDuration);
$orgmeta = NULL;
foreach ($sets as $set => $metadata) {
$tmp->addMetadata($set, $metadata);
$orgmeta = $metadata;
}
$tmp->addOrganizationInfo($orgmeta);
$entityDescriptor = $tmp->getEntityDescriptor();
}
$entitiesDescriptor->appendChild($xml->importNode($entityDescriptor, TRUE));
}
/* Sign the metadata if enabled. */
if ($this->shouldSign()) {
$signer = new SimpleSAML_XML_Signer($this->getSigningInfo());
$signer->sign($entitiesDescriptor, $entitiesDescriptor, $entitiesDescriptor->firstChild);
}
return $xml;
}
}
<?php
$globalConfig = SimpleSAML_Configuration::getInstance();
$aggregatorConfig = SimpleSAML_Configuration::getConfig('aggregator.php');
$config = SimpleSAML_Configuration::getInstance();
$gConfig = SimpleSAML_Configuration::getConfig('module_aggregator.php');
$reconstruct = $aggregatorConfig->getBoolean('reconstruct', FALSE);
$aggregators = $aggregatorConfig->getArray('aggregators');
$metadataSets = array('saml20-idp-remote', 'saml20-sp-remote', 'shib13-idp-remote', 'shib13-sp-remote');
if ($aggregatorConfig->hasValue('default-set')) {
$metadataSets = $aggregatorConfig->getArray('default-set');
}
if (isset($_REQUEST['set'])) {
switch($_REQUEST['set']) {
case 'saml2' :
$metadataSets = array('saml20-idp-remote', 'saml20-sp-remote'); break;
case 'shib13' :
$metadataSets = array('shib13-idp-remote', 'shib13-sp-remote'); break;
case 'idp' :
$metadataSets = array('saml20-idp-remote', 'shib13-idp-remote'); break;
case 'sp' :
$metadataSets = array('saml20-sp-remote', 'shib13-sp-remote'); break;
default:
$metadataSets = array($_REQUEST['set']);
}
}
// print_r($metadataSets); exit;
// Get list of aggregators
$aggregators = $gConfig->getConfigItem('aggregators');
// If aggregator ID is not provided, show the list of available aggregates
if (!array_key_exists('id', $_GET)) {
$t = new SimpleSAML_XHTML_Template($globalConfig, 'aggregator:list.php');
$t->data['sources'] = array_keys($aggregators);
$t = new SimpleSAML_XHTML_Template($config, 'aggregator:list.php');
$t->data['sources'] = $aggregators->getOptions();
$t->show();
exit;
}
$id = $_GET['id'];
if (!array_key_exists($id, $aggregators)) {
if (!in_array($id, $aggregators->getOptions()))
throw new SimpleSAML_Error_NotFound('No aggregator with id ' . var_export($id, TRUE) . ' found.');
}
$aConfig = $aggregators->getConfigItem($id);
/* Parse metadata sources. */
$sources = $aggregators[$id];
if (!is_array($sources)) {
throw new Exception('Invalid aggregator source configuration for aggregator ' .
var_export($id, TRUE) . ': Aggregator wasn\'t an array.');
};
try {
$sources = SimpleSAML_Metadata_MetaDataStorageSource::parseSources($sources);
} catch (Exception $e) {
throw new Exception('Invalid aggregator source configuration for aggregator ' .
var_export($id, TRUE) . ': ' . $e->getMessage());
}
$aggregator = new sspmod_aggregator_Aggregator($gConfig, $aConfig, $id);
$exclude = NULL;
if (array_key_exists('exclude', $_REQUEST)) $exclude = $_REQUEST['exclude'];
#echo $exclude; exit;
/* Find list of all available entities. */
$entities = array();
foreach ($sources as $source) {
foreach ($metadataSets as $set) {
foreach ($source->getMetadataSet($set) as $entityId => $metadata) {
if (isset($exclude) &&
array_key_exists('tags', $metadata) &&
in_array($exclude, $metadata['tags'])) {
SimpleSAML_Logger::debug('Excluding entity ID [' . $entityId . '] becuase it is tagged with [' . $exclude . ']');
continue;
} else {
#echo('<pre>'); print_r($metadata); exit;
}
if (!array_key_exists($entityId, $entities))
$entities[$entityId] = array();
if (array_key_exists($set, $entities[$entityId])) {
/* Entity already has metadata for the given set. */
continue;
}
$entities[$entityId][$set] = $metadata;
}
}
}
if (isset($_REQUEST['set']))
$aggregator->limitSets($_REQUEST['set']);
$xml = new DOMDocument();
$entitiesDescriptor = $xml->createElementNS('urn:oasis:names:tc:SAML:2.0:metadata', 'EntitiesDescriptor');
$entitiesDescriptor->setAttribute('Name', $id);
$xml->appendChild($entitiesDescriptor);
/* Build EntityDescriptor elements for them. */
foreach ($entities as $entity => $sets) {
$entityDescriptor = NULL;
foreach ($sets as $set => $metadata) {
if (!array_key_exists('entityDescriptor', $metadata)) {
/* One of the sets doesn't contain an EntityDescriptor element. */
$entityDescriptor = FALSE;
break;
}
if ($entityDescriptor == NULL) {
/* First EntityDescriptor elements. */
$entityDescriptor = $metadata['entityDescriptor'];
continue;
}
assert('is_string($entityDescriptor)');
if ($entityDescriptor !== $metadata['entityDescriptor']) {
/* Entity contains multiple different EntityDescriptor elements. */
$entityDescriptor = FALSE;
break;
}
}
if (is_string($entityDescriptor) && !$reconstruct) {
/* All metadata sets for the entity contain the same entity descriptor. Use that one. */
$tmp = new DOMDocument();
$tmp->loadXML(base64_decode($entityDescriptor));
$entityDescriptor = $tmp->documentElement;
} else {
$tmp = new SimpleSAML_Metadata_SAMLBuilder($entity,
$aggregatorConfig->getValue('maxCache', NULL), $aggregatorConfig->getValue('maxDuration', NULL));
$orgmeta = NULL;
foreach ($sets as $set => $metadata) {
$tmp->addMetadata($set, $metadata);
$orgmeta = $metadata;
}
$tmp->addOrganizationInfo($orgmeta);
$entityDescriptor = $tmp->getEntityDescriptor();
}
$entitiesDescriptor->appendChild($xml->importNode($entityDescriptor, TRUE));
}
if (isset($_REQUEST['exclude']))
$aggregator->exclude($_REQUEST['exclude']);
/* Sign the metadata if enabled. */
if ($aggregatorConfig->getBoolean('sign.enable', FALSE)) {
$privateKey = $aggregatorConfig->getString('sign.privatekey');
$privateKeyPass = $aggregatorConfig->getString('sign.privatekey_pass', NULL);
$certificate = $aggregatorConfig->getString('sign.certificate');
$signer = new SimpleSAML_XML_Signer(array(
'privatekey' => $privateKey,
'privatekey_pass' => $privateKeyPass,
'certificate' => $certificate,
'id' => 'ID',
));
$signer->sign($entitiesDescriptor, $entitiesDescriptor, $entitiesDescriptor->firstChild);
}
$xml = $aggregator->getMetadataDocument();
/* Show the metadata. */
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment