Revision: 2264
Author: olavmrk
Date: Wed Apr 21 05:56:46 2010
Log: aggregator2-module
http://code.google.com/p/simplesamlphp/source/detail?r=2264
Added:
/trunk/modules/aggregator2
/trunk/modules/aggregator2/bin
/trunk/modules/aggregator2/bin/update.php
/trunk/modules/aggregator2/config-templates
/trunk/modules/aggregator2/config-templates/module_aggregator2.php
/trunk/modules/aggregator2/default-disable
/trunk/modules/aggregator2/docs
/trunk/modules/aggregator2/docs/aggregator2.txt
/trunk/modules/aggregator2/hooks
/trunk/modules/aggregator2/hooks/hook_cron.php
/trunk/modules/aggregator2/lib
/trunk/modules/aggregator2/lib/Aggregator.php
/trunk/modules/aggregator2/lib/EntitySource.php
/trunk/modules/aggregator2/www
/trunk/modules/aggregator2/www/get.php
=======================================
--- /dev/null
+++ /trunk/modules/aggregator2/bin/update.php Wed Apr 21 05:56:46 2010
@@ -0,0 +1,16 @@
+#!/usr/bin/env php
+<?php
+
+require_once(dirname(dirname(dirname(dirname(__FILE__)))) .
'/lib/_autoload.php');
+
+$name = basename($argv[0]);
+
+if ($argc < 2) {
+ fprintf(STDERR, "$name: Missing id of aggregator.\n");
+ exit(1);
+}
+
+$id = $argv[1];
+
+$aggregator = sspmod_aggregator2_Aggregator::getAggregator($id);
+$aggregator->updateCache();
=======================================
--- /dev/null
+++ /trunk/modules/aggregator2/config-templates/module_aggregator2.php Wed
Apr 21 05:56:46 2010
@@ -0,0 +1,113 @@
+<?php
+
+/* This is the configuration file for the aggregator2-module. */
+$config = array(
+
+ /*
+ * 'example' will be one set of aggregated metadata.
+ * The aggregated metadata can be retrieved from:
+ * https://.../simplesaml/module.php/aggregator2/get.php?id=example
+ */
+ 'example' => array(
+
+ /* 'sources' is an array with the places we want to fetch metadata from.
*/
+ 'sources' => array(
+ /* Metadata validated by the https-certificate of the
server. */
+ array(
+ /* The URL we should fetch the metadata from. */
+ 'url' => 'https://sp.example.org/metadata.xml',
+
+ /*
+ * To enable validation of the
https-certificate, we must
+ * specify a file with valid CA certificates.
+ *
+ * This can be an absolute path, or a path
relative to the
+ * cert-directory.
+ */
+ 'ssl.cafile' =>
'/etc/ssl/certs/ca-certificates.crt',
+ ),
+
+ /* Metadata validated by its signature. */
+ array(
+ /* The URL we should fetch the metadata from. */
+ 'url' => 'http://idp.example.org/metadata.xml',
+
+ /*
+ * To verify the signature in the metadata, we
must specify
+ * a certificate that should be used. Note:
This cannot
+ * be a CA certificate.
+ *
+ * This can be an absolute path, or a path
relative to the
+ * cert-directory.
+ */
+ 'cert' => 'idp.example.org.crt',
+ ),
+
+ /* Metadata from a file. */
+ array(
+ 'url' => '/var/simplesaml/somemetadata.xml',
+ ),
+
+ ),
+
+ /*
+ * Update this metadata during this cron tag.
+ *
+ * For this option to work, you must configure the cron-module,
+ * and also add a cache directory.
+ *
+ * This option is optional. If cron is not configured, the
metadata
+ * caches will be updated when receiving requests for metadata.
+ */
+ 'cron.tag' => 'hourly',
+
+ /*
+ * The directory we will store downloaded and generated
metadata.
+ * This directory must be writeable by the web-server.
+ *
+ * This option is optional, but if unspecified, every request
for the
+ * aggregated metadata will result in the aggregator fetching
and
+ * parsing all metadata sources.
+ */
+ 'cache.directory' => '/var/cache/simplesaml-aggregator2',
+
+ /*
+ * This is the number of seconds we will cache the metadata file we
generate.
+ * This should be a longer time than the interval between each time the
cron
+ * job is executed.
+ *
+ * This option is optional. If unspecified, the metadata will be
generated
+ * on every request.
+ */
+ 'cache.generated' => 24*60*60,
+
+ /*
+ * The generated metadata will have a validUntil set to the time it is
generated
+ * plus this number of seconds.
+ */
+ 'valid.length' => 7*24*60*60,
+
+ /*
+ * The private key we should use to sign the metadata, in
pem-format.
+ *
+ * This is optional. If it is not specified, the metadata will not be
signed.
+ */
+ 'sign.privatekey' => 'metadata.pem',
+
+ /*
+ * The password for the private key.
+ *
+ * Optional, the private key is assumed to be unencrypted if
this option
+ * isn't set.
+ */
+ 'sign.privatekey_pass' => 'secret',
+
+ /*
+ * The certificate that corresponds to the private key.
+ *
+ * If specified, the certificate will be included in the signature in
the metadata.
+ */
+ 'sign.certificate' => 'metadata.crt',
+ ),
+
+);
=======================================
--- /dev/null
+++ /trunk/modules/aggregator2/default-disable Wed Apr 21 05:56:46 2010
@@ -0,0 +1,3 @@
+This file indicates that the default state of this module
+is disabled. To enable, create a file named enable in the
+same directory as this file.
=======================================
--- /dev/null
+++ /trunk/modules/aggregator2/docs/aggregator2.txt Wed Apr 21 05:56:46 2010
@@ -0,0 +1,113 @@
+aggregator2 Module
+==================
+
+This is an experimental module for aggregating metadata.
+It is designed to preserve most of the common metadata items, and also
attempt to preserve unknown elements.
+
+*Note*: This aggregator only works on XML metadata, and does its work
independently of the of other parts of simpleSAMLphp, such as the
`metarefresh` module.
+
+
+Configuration
+-------------
+
+This module is configured through the `config/module_aggregator2.php`
configuration file.
+An example file is available in `modules/aggregator2/config-templates/`:
+
+ cd /var/simplesaml
+ cp modules/aggregator2/config-templates/module_aggregator2.php config/
+
+The configuration file contains one or more aggregators in the
configuration array.
+The index in the configuration array gives the identifier of the
aggregator.
+
+
+### Aggregator entry configuration
+
+The aggregator can be configured with the following options:
+
+`sources`
+: Array which describes which metadata we should download.
+
+`cron.tag`
+: Can be used to periodically run an update.
+ Only useful when you have enabled caching of metadata.
+
+`cache.directory`
+: A path to a directory where the aggregator will cache downloaded and
generated metadata.
+ This directory must be writeable by the webserver.
+
+`cache.generated`
+: The number of seconds generated metadata should be cached.
+ If this option is unset, the generated metadata will not be cached.
+
+`valid.length`
+: The number of seconds the generated metadata should be valid.
+ This is used to set the validUntil attribute on the generated metadata.
+ The default is one week.
+
+: *Note*: The `cache.generated` option must be smaller than this option,
otherwise you will end up returning outdated metadata.
+
+`ssl.cafile`
+: This option enables validation of the server certificate when fetching
metadata over https.
+ It must be set to a path to a PEM-file which contains one or more
valid CA certificates.
+ The path can be absolute, or it can be relative to the
`cert`-directory.
+
+: *Note*: This option can be overridden for each metadata source.
+
+`sign.privatekey`
+: The private key that should be used to sign the metadata, in PEM
format.
+ The path to the private key can be absolute, or it can be relative to
the `cert`-directory.
+
+`sign.privatekey_pass`
+: The password for the private key.
+ If this option is unset, the private key is assumed to be unencrypted.
+
+`sign.certificate`
+: The certificate which contains the public key corresponding to the
private key, in PEM format.
+ This certificate is included in the generated metadata.
+ The path to the certificate can be absolute, or it can be relative to
the `cert`-directory.
+
+
+### Aggregator source configuration
+
+`url`
+: The URL the metadata should be fetched from.
+
+`ssl.cafile`
+: This option enables validation of the server certificate when fetching
metadata over https.
+ It must be the path to a PEM-file which contains one or more valid CA
certificates.
+ The path can be absolute, or it can be relative to the
`cert`-directory.
+
+: *Note*: This option overrides the aggregator option.
+
+`cert`
+: Check the signature on the metadata against the specified certificate.
+ The path to the certificate can be absolute, or it can be relative to
the `cert`-directory.
+
+: *Note*: This can not be a CA certificate.
+ Validation against a CA certificate is not supported.
+
+
+Retrieving aggregated metadata
+------------------------------
+
+The metadata can be downloaded from the following location:
+
+
http://<server>/simplesaml/modules.php/aggregator2/get.php?id=<aggregator
id>
+
+
+Asynchronous metadata updates
+-----------------------------
+
+By default, the `aggregator2` module will update the metadata when
receiving a request.
+For performance reasons, it is recommended to run the updates
asynchronously.
+By doing this, the aggregated metadata will be generated in the background.
+
+To enable this, you must configure a cache directory with the
`cache.directory` option.
+This directory must be writeable by the web server.
+You can then enable caching of generated metadata by setting the
`cache.generated` option to the number of seconds the metadata can be
cached.
+
+You will now have a configuration that caches both downloaded and
generated metadata.
+It will however still update the metadata when the user accesses the
aggregator endpoint
+To update the generated metadata in the background, you must add a
`cron.tag` option.
+This option must reference a cron tag entry configured in
`module_cron.php`.
+Once this is done, your aggregated metadata will be updated everytime that
cron entry is executed.
=======================================
--- /dev/null
+++ /trunk/modules/aggregator2/hooks/hook_cron.php Wed Apr 21 05:56:46 2010
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * cron hook to update aggregator2 metadata.
+ *
+ * @param array &$croninfo Output
+ */
+function aggregator2_hook_cron(&$croninfo) {
+ assert('is_array($croninfo)');
+ assert('array_key_exists("summary", $croninfo)');
+ assert('array_key_exists("tag", $croninfo)');
+
+ $cronTag = $croninfo['tag'];
+
+ $config = SimpleSAML_Configuration::getConfig('module_aggregator2.php');
+ $config = $config->toArray();
+
+ foreach ($config as $id => $c) {
+ if (!isset($c['cron.tag'])) {
+ continue;
+ }
+ if ($c['cron.tag'] !== $cronTag) {
+ continue;
+ }
+
+ try {
+ $a = sspmod_aggregator2_Aggregator::getAggregator($id);
+ $a->updateCache();
+ } catch (Exception $e) {
+ $croninfo['summary'][] = 'Error during aggregator2 cacheupdate: ' .
$e->getMessage();
+ }
+ }
+}
=======================================
--- /dev/null
+++ /trunk/modules/aggregator2/lib/Aggregator.php Wed Apr 21 05:56:46 2010
@@ -0,0 +1,453 @@
+<?php
+
+/**
+ * Class which implements a basic metadata aggregator.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class sspmod_aggregator2_Aggregator {
+
+ /**
+ * The ID of this aggregator.
+ *
+ * @var string
+ */
+ protected $id;
+
+
+ /**
+ * Our log "location".
+ *
+ * @var string
+ */
+ protected $logLoc;
+
+
+ /**
+ * Which cron-tag this should be updated in.
+ *
+ * @var string|NULL
+ */
+ protected $cronTag;
+
+
+ /**
+ * Absolute path to a cache directory.
+ *
+ * @var string|NULL
+ */
+ protected $cacheDirectory;
+
+
+ /**
+ * The entity sources.
+ *
+ * Array of sspmod_aggregator2_EntitySource objects.
+ *
+ * @var array
+ */
+ protected $sources = array();
+
+
+ /**
+ * How long the generated metadata should be valid, as a number of
seconds.
+ *
+ * This is used to set the validUntil attribute on the generated
EntityDescriptor.
+ *
+ * @var int
+ */
+ protected $validLength;
+
+
+ /**
+ * Duration we should cache generated metadata.
+ *
+ * @var int
+ */
+ protected $cacheGenerated;
+
+
+ /**
+ * The key we should use to sign the metadata.
+ *
+ * @var string|NULL
+ */
+ protected $signKey;
+
+
+ /**
+ * The password for the private key.
+ *
+ * @var string|NULL
+ */
+ protected $signKeyPass;
+
+
+ /**
+ * The certificate of the key we sign the metadata with.
+ *
+ * @var string|NULL
+ */
+ protected $signCert;
+
+
+ /**
+ * The CA certificate file that should be used to validate
https-connections.
+ *
+ * @var string|NULL
+ */
+ protected $sslCAFile;
+
+
+ /**
+ * The cache ID for our generated metadata.
+ *
+ * @var string
+ */
+ protected $cacheId;
+
+
+ /**
+ * The cache tag for our generated metadata.
+ *
+ * This tag is used to make sure that a config change
+ * invalidates our cached metadata.
+ *
+ * @var string
+ */
+ protected $cacheTag;
+
+
+ /**
+ * Initialize this aggregator.
+ *
+ * @param string $id The id of this aggregator.
+ * @param SimpleSAML_Configuration $config The configuration for this
aggregator.
+ */
+ protected function __construct($id, SimpleSAML_Configuration $config) {
+ assert('is_string($id)');
+
+ $this->id = $id;
+ $this->logLoc = 'aggregator2:' . $this->id . ': ';
+
+ $this->cronTag = $config->getString('cron.tag', NULL);
+
+ $this->cacheDirectory = $config->getString('cache.directory',
NULL);
+ if ($this->cacheDirectory !== NULL) {
+ $this->cacheDirectory =
SimpleSAML_Utilities::resolvePath($this->cacheDirectory);
+ }
+
+ $this->cacheGenerated = $config->getInteger('cache.generated',
NULL);
+ if ($this->cacheGenerated !== NULL) {
+ $this->cacheId = sha1($this->id);
+ $this->cacheTag = sha1(serialize($config));
+ }
+
+ $this->validLength = $config->getInteger('valid.length',
7*24*60*60);
+
+ $globalConfig = SimpleSAML_Configuration::getInstance();
+ $certDir = $globalConfig->getPathValue('certdir', 'cert/');
+
+ $signKey = $config->getString('sign.privatekey', NULL);
+ if ($signKey !== NULL) {
+ $signKey = SimpleSAML_Utilities::resolvePath($signKey,
$certDir);
+ $this->signKey = @file_get_contents($signKey);
+ if ($this->signKey === NULL) {
+ throw new SimpleSAML_Error_Exception('Unable to load private key
from ' . var_export($signKey, TRUE));
+ }
+ }
+
+ $this->signKeyPass = $config->getString('sign.privatekey_pass',
NULL);
+
+ $signCert = $config->getString('sign.certificate', NULL);
+ if ($signCert !== NULL) {
+ $signCert =
SimpleSAML_Utilities::resolvePath($signCert, $certDir);
+ $this->signCert = @file_get_contents($signCert);
+ if ($this->signCert === NULL) {
+ throw new SimpleSAML_Error_Exception('Unable to load certificate file
from ' . var_export($signCert, TRUE));
+ }
+ }
+
+
+ $this->sslCAFile = $config->getString('ssl.cafile', NULL);
+
+ $this->initSources($config->getConfigList('sources'));
+ }
+
+
+ /**
+ * Populate the sources array.
+ *
+ * This is called from the constructor, and can be overridden in
subclasses.
+ *
+ * @param array $sources The sources as an array of
SimpleSAML_Configuration objects.
+ */
+ protected function initSources(array $sources) {
+
+ foreach ($sources as $source) {
+ $this->sources[] = new
sspmod_aggregator2_EntitySource($this, $source);
+ }
+ }
+
+
+ /**
+ * Return an instance of the aggregator with the given id.
+ *
+ * @param string $id The id of the aggregator.
+ */
+ public static function getAggregator($id) {
+ assert('is_string($id)');
+
+ $config =
SimpleSAML_Configuration::getConfig('module_aggregator2.php');
+ return new sspmod_aggregator2_Aggregator($id,
$config->getConfigItem($id));
+ }
+
+
+ /**
+ * Retrieve the ID of the aggregator.
+ *
+ * @return string The ID of this aggregator.
+ */
+ public function getId() {
+ return $this->id;
+ }
+
+
+ /**
+ * Add an item to the cache.
+ *
+ * @param string $id The identifier of this data.
+ * @param string $data The data.
+ * @param int $expires The timestamp the data expires.
+ * @param string|NULL $tag An extra tag that can be used to verify the
validity of the cached data.
+ */
+ public function addCacheItem($id, $data, $expires, $tag = NULL) {
+ assert('is_string($id)');
+ assert('is_string($data)');
+ assert('is_int($expires)');
+ assert('is_null($tag) || is_string($tag)');
+
+ $cacheFile = $this->cacheDirectory . '/' . $id;
+ try {
+ SimpleSAML_Utilities::writeFile($cacheFile, $data);
+ } catch (Exception $e) {
+ SimpleSAML_Logger::warning($this->logLoc . 'Unable to write to cache
file ' . var_export($cacheFile, TRUE));
+ return;
+ }
+
+ $expireInfo = (string)$expires;
+ if ($tag !== NULL) {
+ $expireInfo .= ':' . $tag;
+ }
+
+ $expireFile = $cacheFile . '.expire';
+ try {
+ SimpleSAML_Utilities::writeFile($expireFile,
$expireInfo);
+ } catch (Exception $e) {
+ SimpleSAML_Logger::warning($this->logLoc . 'Unable to write expiration
info to ' . var_export($expireFile, TRUE));
+ return $metadata;
+ }
+
+ }
+
+
+ /**
+ * Check validity of cached data.
+ *
+ * @param string $id The identifier of this data.
+ * @param string $tag The tag that was passed to addCacheItem.
+ * @return bool TRUE if the data is valid, FALSE if not.
+ */
+ public function isCacheValid($id, $tag = NULL) {
+ assert('is_string($id)');
+ assert('is_null($tag) || is_string($tag)');
+
+ $cacheFile = $this->cacheDirectory . '/' . $id;
+ if (!file_exists($cacheFile)) {
+ return FALSE;
+ }
+
+ $expireFile = $cacheFile . '.expire';
+ if (!file_exists($expireFile)) {
+ return FALSE;
+ }
+
+ $expireData = @file_get_contents($expireFile);
+ if ($expireData === FALSE) {
+ return FALSE;
+ }
+
+ $expireData = explode(':', $expireData, 2);
+
+ $expireTime = (int)$expireData[0];
+ if ($expireTime <= time()) {
+ return FALSE;
+ }
+
+ if (count($expireData) === 1) {
+ $expireTag = NULL;
+ } else {
+ $expireTag = $expireData[1];
+ }
+ if ($expireTag !== $tag) {
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+
+ /**
+ * Get the cache item.
+ *
+ * @param string $id The identifier of this data.
+ * @param string $tag The tag that was passed to addCacheItem.
+ * @return string|NULL The cache item, or NULL if it isn't cached or if
it is expired.
+ */
+ public function getCacheItem($id, $tag = NULL) {
+ assert('is_string($id)');
+ assert('is_null($tag) || is_string($tag)');
+
+ if (!$this->isCacheValid($id, $tag)) {
+ return NULL;
+ }
+
+ $cacheFile = $this->cacheDirectory . '/' . $id;
+ return @file_get_contents($cacheFile);
+ }
+
+
+ /**
+ * Get the cache filename for the specific id.
+ *
+ * @param string $id The identifier of the cached data.
+ * @return string|NULL The filename, or NULL if the cache file doesn't
exist.
+ */
+ public function getCacheFile($id) {
+ assert('is_string($id)');
+
+ $cacheFile = $this->cacheDirectory . '/' . $id;
+ if (!file_exists($cacheFile)) {
+ return NULL;
+ }
+
+ return $cacheFile;
+ }
+
+
+ /**
+ * Retrieve the SSL CA file path, if it is set.
+ *
+ * @return string|NULL The SSL CA file path.
+ */
+ public function getCAFile() {
+
+ return $this->sslCAFile;
+ }
+
+
+ /**
+ * Sign the generated EntitiesDescriptor.
+ */
+ protected function addSignature(SAML2_SignedElement $element) {
+
+ if ($this->signKey === NULL) {
+ return;
+ }
+
+ $privateKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type'
=> 'private'));
+ if ($this->signKeyPass !== NULL) {
+ $privateKey->passphrase = $this->signKeyPass;
+ }
+ $privateKey->loadKey($this->signKey, FALSE);
+
+
+ $element->setSignatureKey($privateKey);
+
+ if ($this->signCert !== NULL) {
+ $element->setCertificates(array($this->signCert));
+ }
+ }
+
+
+ /**
+ * Retrieve all entities as an EntitiesDescriptor.
+ *
+ * @return SAML2_XML_md_EntitiesDescriptor The entities.
+ */
+ protected function getEntitiesDescriptor() {
+
+ $ret = new SAML2_XML_md_EntitiesDescriptor();
+ foreach ($this->sources as $source) {
+ $m = $source->getMetadata();
+ if ($m === NULL) {
+ continue;
+ }
+ $ret->children[] = $m;
+ }
+
+ $ret->validUntil = time() + $this->validLength;
+
+ return $ret;
+ }
+
+
+ /**
+ * Retrieve the complete, signed metadata as text.
+ *
+ * This function will write the new metadata to the cache file, but will
not return
+ * the cached metadata.
+ *
+ * @return string The metadata, as text.
+ */
+ public function updateCachedMetadata() {
+
+ $ed = $this->getEntitiesDescriptor();
+ $this->addSignature($ed);
+
+ $xml = $ed->toXML();
+ $xml = $xml->ownerDocument->saveXML($xml);
+
+ if ($this->cacheGenerated !== NULL) {
+ SimpleSAML_Logger::debug($this->logLoc . 'Saving generated metadata to
cache.');
+ $this->addCacheItem($this->cacheId, $xml, time() +
$this->cacheGenerated, $this->cacheTag);
+ }
+
+ return $xml;
+
+ }
+
+
+ /**
+ * Retrieve the complete, signed metadata as text.
+ *
+ * @return string The metadata, as text.
+ */
+ public function getMetadata() {
+
+ if ($this->cacheGenerated !== NULL) {
+ $xml = $this->getCacheItem($this->cacheId,
$this->cacheTag);
+ if ($xml !== NULL) {
+ SimpleSAML_Logger::debug($this->logLoc . 'Loaded generated metadata
from cache.');
+ return $xml;
+ }
+ }
+
+ return $this->updateCachedMetadata();
+ }
+
+
+ /**
+ * Update the cached copy of our metadata.
+ */
+ public function updateCache() {
+
+ foreach ($this->sources as $source) {
+ $source->updateCache();
+ }
+
+ $this->updateCachedMetadata();
+ }
+
+}
=======================================
--- /dev/null
+++ /trunk/modules/aggregator2/lib/EntitySource.php Wed Apr 21 05:56:46 2010
@@ -0,0 +1,274 @@
+<?php
+
+/**
+ * Class for loading metadata from files and URLs.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class sspmod_aggregator2_EntitySource {
+
+ /**
+ * Our log "location".
+ *
+ * @var string
+ */
+ protected $logLoc;
+
+
+ /**
+ * The aggregator we belong to.
+ *
+ * @var sspmod_aggregator2_Aggregator
+ */
+ protected $aggregator;
+
+
+ /**
+ * The URL we should fetch it from.
+ *
+ * @var string
+ */
+ protected $url;
+
+
+ /**
+ * The SSL CA file that should be used to validate the connection.
+ *
+ * @var string|NULL
+ */
+ protected $sslCAFile;
+
+
+ /**
+ * The certificate we should use to validate downloaded metadata.
+ *
+ * @var string|NULL
+ */
+ protected $certificate;
+
+
+ /**
+ * The parsed metadata.
+ *
+ * @var
SAML2_XML_md_EntitiesDescriptor|SAML2_XML_md_EntityDescriptor|NULL
+ */
+ protected $metadata;
+
+
+ /**
+ * The cache ID.
+ *
+ * @var string
+ */
+ protected $cacheId;
+
+
+ /**
+ * The cache tag.
+ *
+ * @var string
+ */
+ protected $cacheTag;
+
+
+ /**
+ * Whether we have attempted to update the cache already.
+ *
+ * @var bool
+ */
+ protected $updateAttempted;
+
+
+ /**
+ * Initialize this EntitySource.
+ *
+ * @param SimpleSAML_Configuration $config The configuration.
+ */
+ public function __construct(sspmod_aggregator2_Aggregator $aggregator,
SimpleSAML_Configuration $config) {
+
+ $this->logLoc = 'aggregator2:' . $aggregator->getId() . ': ';
+ $this->aggregator = $aggregator;
+
+ $this->url = $config->getString('url');
+ $this->sslCAFile = $config->getString('ssl.cafile', NULL);
+ if ($this->sslCAFile === NULL) {
+ $this->sslCAFile = $aggregator->getCAFile();
+ }
+
+ $this->certificate = $config->getString('cert', NULL);
+
+ $this->cacheId = sha1($this->url);
+ $this->cacheTag = sha1(serialize($config));
+ }
+
+
+ /**
+ * Retrieve and parse the metadata.
+ *
+ * @return SAML2_XML_md_EntitiesDescriptor|SAML2_XML_md_EntityDescriptor|
NULL
+ * The downloaded metadata or NULL if we were unable to download or parse
it.
+ */
+ private function downloadMetadata() {
+
+ SimpleSAML_Logger::debug($this->logLoc . 'Downloading metadata
from ' .
+ var_export($this->url, TRUE));
+
+ $context = array('ssl' => array());
+ if ($this->sslCAFile !== NULL) {
+ $context['ssl']['cafile'] =
SimpleSAML_Utilities::resolveCert($this->sslCAFile);
+ SimpleSAML_Logger::debug($this->logLoc . 'Validating https connection
against CA certificate(s) found in ' .
+ var_export($context['ssl']['cafile'], TRUE));
+ $context['ssl']['verify_peer'] = TRUE;
+ $context['ssl']['CN_match'] = parse_url($this->url,
PHP_URL_HOST);
+ }
+
+ $context = stream_context_create($context);
+
+ $data = file_get_contents($this->url, 0, $context);
+ if ($data === FALSE || $data === NULL) {
+ SimpleSAML_Logger::error($this->logLoc . 'Unable to load metadata
from ' .
+ var_export($this->url, TRUE));
+ return NULL;
+ }
+
+ $doc = new DOMDocument();
+ $res = $doc->loadXML($data);
+ if (!$res) {
+ SimpleSAML_Logger::error($this->logLoc . 'Error parsing
XML from ' .
+ var_export($this->url, TRUE));
+ return NULL;
+ }
+
+ $root =
SAML2_Utils::xpQuery($doc->firstChild, '/saml_metadata:EntityDescriptor|
/saml_metadata:EntitiesDescriptor');
+ if (count($root) === 0) {
+ SimpleSAML_Logger::error($this->logLoc . 'No <EntityDescriptor> or
<EntitiesDescriptor> in metadata from ' .
+ var_export($this->url, TRUE));
+ return NULL;
+ }
+
+ if (count($root) > 1) {
+ SimpleSAML_Logger::error($this->logLoc . 'More than one
<EntityDescriptor> or <EntitiesDescriptor> in metadata from ' .
+ var_export($this->url, TRUE));
+ return NULL;
+ }
+
+ $root = $root[0];
+ try {
+ if ($root->localName === 'EntityDescriptor') {
+ $md = new SAML2_XML_md_EntityDescriptor($root);
+ } else {
+ $md = new
SAML2_XML_md_EntitiesDescriptor($root);
+ }
+ } catch (Exception $e) {
+ SimpleSAML_Logger::error($this->logLoc . 'Unable to parse metadata
from ' .
+ var_export($this->url, TRUE) . ': ' .
$e->getMessage());
+ return NULL;
+ }
+
+
+ if ($this->certificate !== NULL) {
+ $file =
SimpleSAML_Utilities::resolveCert($this->certificate);
+ $certData = file_get_contents($file);
+ if ($certData === FALSE) {
+ throw new SimpleSAML_Error_Exception('Error loading certificate
from ' . var_export($file, TRUE));
+ }
+
+ /* Extract the public key from the certificate for
validation. */
+ $key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1,
array('type'=>'public'));
+ $key->loadKey($file, TRUE);
+
+ if (!$md->validate($key)) {
+ SimpleSAML_Logger::error($this->logLoc . 'Error validating signature
on metadata.');
+ return NULL;
+ }
+ SimpleSAML_Logger::debug($this->logLoc . 'Validated signature on
metadata from ' . var_export($this->url, TRUE));
+ }
+
+ return $md;
+ }
+
+
+ /**
+ * Attempt to update our cache file.
+ */
+ public function updateCache() {
+
+ if ($this->updateAttempted) {
+ return;
+ }
+ $this->updateAttempted = TRUE;
+
+ $this->metadata = $this->downloadMetadata();
+ if ($this->metadata === NULL) {
+ return;
+ }
+
+ $expires = time() + 24*60*60; /* Default expires in one day. */
+
+ if ($this->metadata->validUntil !== NULL && $this->metadata->validUntil
< $expires) {
+ $expires = $this->metadata->validUntil;
+ }
+
+ if ($this->metadata->cacheDuration !== NULL) {
+ try {
+ $durationTo =
SimpleSAML_Utilities::parseDuration($this->metadata->cacheDuration);
+ } catch (Exception $e) {
+ SimpleSAML_Logger::warning($this->logLoc . 'Invalid cacheDuration in
metadata from ' .
+ var_export($this->url, TRUE) . ': ' .
var_export($this->metadata->cacheDuration, TRUE));
+ return;
+ }
+ if ($durationTo < $expires) {
+ $expires = $durationTo;
+ }
+ }
+
+ $metadataSerialized = serialize($this->metadata);
+
+ $this->aggregator->addCacheItem($this->cacheId, $metadataSerialized,
$expires, $this->cacheTag);
+ }
+
+
+ /**
+ * Retrieve the metadata file.
+ *
+ * This function will check its cached copy, to see whether it can be
used.
+ *
+ * @return SAML2_XML_md_EntityDescriptor|SAML2_XML_md_EntitiesDescriptor|
NULL The downloaded metadata.
+ */
+ public function getMetadata() {
+
+ if ($this->metadata !== NULL) {
+ /* We have already downloaded the metdata. */
+ return $this->metadata;
+ }
+
+ if (!$this->aggregator->isCacheValid($this->cacheId,
$this->cacheTag)) {
+ $this->updateCache();
+ if ($this->metadata !== NULL) {
+ return $this->metadata;
+ }
+ /* We were unable to update the cache - use cached
metadata. */
+ }
+
+
+ $cacheFile = $this->aggregator->getCacheFile($this->cacheId);
+
+ if (!file_exists($cacheFile)) {
+ SimpleSAML_Logger::error($this->logLoc . 'No cached metadata
available.');
+ return NULL;
+ }
+
+ SimpleSAML_Logger::debug($this->logLoc . 'Using cached metadata
from ' .
+ var_export($cacheFile, TRUE));
+
+ $metadata = file_get_contents($cacheFile);
+ if ($metadata !== NULL) {
+ $this->metadata = unserialize($metadata);
+ return $this->metadata;
+ }
+
+ return NULL;
+ }
+
+}
=======================================
--- /dev/null
+++ /trunk/modules/aggregator2/www/get.php Wed Apr 21 05:56:46 2010
@@ -0,0 +1,14 @@
+<?php
+
+if (!isset($_REQUEST['id'])) {
+ throw new SimpleSAML_Error_BadRequest('Missing required id-parameter.');
+}
+
+$id = (string)$_REQUEST['id'];
+
+$aggregator = sspmod_aggregator2_Aggregator::getAggregator($id);
+$xml = $aggregator->getMetadata();
+
+header('Content-Type: application/samlmetadata+xml');
+header('Content-Length: ' . strlen($xml));
+echo($xml);
--
You received this message because you are subscribed to the Google Groups
"simpleSAMLphp commits" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/simplesamlphp-commits?hl=en.