Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update of pull/1984 against 2.3 (A new MetaDataStorageHandlerDirectory class to load metadata from subdirectories) #2018

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion docs/simplesamlphp-maintenance.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ Configure your master group by setting `store.redis.mastergroup` (`mymaster` by

## Metadata storage

Several metadata storage backends are available by default, including `flatfile`, `serialize`, `mdq` and
Several metadata storage backends are available by default, including `flatfile`, `directory`, `serialize`, `mdq` and
[`pdo`](https://simplesamlphp.org/docs/stable/simplesamlphp-metadata-pdostoragehandler). Here you have an
example configuration of different metadata sources in use at the same time:

Expand All @@ -207,9 +207,18 @@ example configuration of different metadata sources in use at the same time:
['type' => 'flatfile'],
['type' => 'flatfile', 'directory' => 'metadata/metarefresh-kalmar'],
['type' => 'serialize', 'directory' => 'metadata/metarefresh-ukaccess'],
['type' => 'directory'],
['type' => 'directory', 'directory' => 'metadata/somewhere-else'],
],
```

The directory type storage backend will look in subdirectories such as
saml20-idp-hosted.d and saml20-sp-remote.d in the given directory (or
the 'metadatadir' configuration option in 'config.php' by default).
All xml and php files found in those subdirectories will be loaded. It is
currently an error to have subdirectories inside the
saml20-sp-remote.d directories.

You may also implement your own metadata storage handler, in a very similar way to how you would implement
your own session handler. Your class **must** extend the `\SimpleSAML\Metadata\MetaDataStorageSource` class
and override the methods needed to change the backend used. This class **must** also be located in the
Expand Down
180 changes: 180 additions & 0 deletions src/SimpleSAML/Metadata/MetaDataStorageHandlerDirectory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\Metadata;

use Exception;
use SimpleSAML\Assert\Assert;
use SimpleSAML\Configuration;
use SimpleSAML\Logger;

use function array_key_exists;
use function is_array;

/**
* A directory that contains metadata files.
* Instantiation of session handler objects should be done through
* the class method getMetadataHandler().
*
* @package SimpleSAMLphp
* This was created based on the MetaDataStorageHandlerFlatFile.php source in February 2024.
*/

class MetaDataStorageHandlerDirectory extends MetaDataStorageSource
{
/**
* This is the directory we will load metadata files from. The path will always end
* with a '/'.
*
* @var string
*/
private string $directory = '/';


/**
* This is an associative array which stores the different metadata sets we have loaded.
*
* @var array
*/
private array $cachedMetadata = [];


/**
* 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(array $config)
{
parent::__construct();

// get the configuration
$globalConfig = Configuration::getInstance();

// find the path to the directory we should search for metadata in
if (array_key_exists('directory', $config)) {
$this->directory = $config['directory'] ?: 'metadata/';
} else {
$this->directory = $globalConfig->getOptionalString('metadatadir', 'metadata/');
}

/* Resolve this directory relative to the SimpleSAMLphp directory (unless it is
* an absolute path).
*/

/** @var string $base */
$base = $globalConfig->resolvePath($this->directory);
$this->directory = $base . '/';
}


/**
* This function loads the given set of metadata files in the 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|null An associative array with the metadata,
* or null if we are unable to load metadata from the given file.
* @throws \Exception If the metadata set cannot be loaded.
*/
private function load(string $set): ?array
{
$metadatasetdir = $this->directory . $set . '.d';

if (!$this->fileSystem->exists($metadatasetdir)) {
return null;
}

/** @psalm-var mixed $metadata We cannot be sure what the include below will do with this var */
$metadata = [];

$dh = @opendir($metadatasetdir);
if ($dh === false) {
Logger::warning(
'Directory metadata handler: Unable to open directory: ' . var_export($metadatasetdir, true)
);
return $metadata;
}

while (($entry = readdir($dh)) !== false) {
if ($entry[0] === '.') {
// skip '..', '.' and hidden files
continue;
}

$path = $metadatasetdir . DIRECTORY_SEPARATOR . $entry;
if (is_dir($path)) {
Logger::warning(
'Directory metadata handler: Metadata directory contained a directory where only files should ' .
'exist: ' . var_export($path, true)
);
continue;
}

if (str_ends_with($path, '.php') || str_ends_with($path, '.xml')) {
$type = 'flatfile';
if (str_ends_with($path, '.xml')) {
$type = 'xml';
}
Logger::info("loading set $set metadata file at $path");

$config = [ 'type' => $type, 'file' => $path ];
$source = MetaDataStorageSource::getSource($config);
$md = $source->getMetadataSet($set);
$metadata = array_merge($metadata, $md);
}
}




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(string $set): array
{
if (array_key_exists($set, $this->cachedMetadata)) {
return $this->cachedMetadata[$set];
}

$metadataSet = $this->load($set);
if ($metadataSet === null) {
$metadataSet = [];
}

// add the entity id of an entry to each entry in the metadata
foreach ($metadataSet as $entityId => &$entry) {
$entry['entityid'] = $entityId;
// check we're not seeing the entityID from the metadata-template
if ($set === 'saml20-idp-hosted') {
Assert::notEq(
$entityId,
'urn:x-simplesamlphp:example-idp',
'Please set a valid and unique entityID',
);
}
}

$this->cachedMetadata[$set] = $metadataSet;
return $metadataSet;
}
}
15 changes: 15 additions & 0 deletions src/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ class MetaDataStorageHandlerFlatFile extends MetaDataStorageSource
private string $directory = '/';


/**
* Optional explicit file path to load metadata from
*
* @var string
*/
private ?string $file = null;

/**
* This is an associative array which stores the different metadata sets we have loaded.
*
Expand All @@ -44,6 +51,7 @@ class MetaDataStorageHandlerFlatFile extends MetaDataStorageSource
* possible elements:
* - 'directory': The directory we should load metadata from. The default directory is
* set in the 'metadatadir' configuration option in 'config.php'.
* - 'file': full path to load metadata from. This is used by the MetaDataStorageHandlerDirectory class.
*
* @param array $config An associative array with the configuration for this handler.
*/
Expand All @@ -61,6 +69,10 @@ protected function __construct(array $config)
$this->directory = $globalConfig->getOptionalString('metadatadir', 'metadata/');
}

if (array_key_exists('file', $config)) {
$this->file = $globalConfig->resolvePath($config['file']);
}

/* Resolve this directory relative to the SimpleSAMLphp directory (unless it is
* an absolute path).
*/
Expand All @@ -84,6 +96,9 @@ protected function __construct(array $config)
private function load(string $set): ?array
{
$metadatasetfile = $this->directory . $set . '.php';
if ($this->file) {
$metadatasetfile = $this->file;
}

if (!$this->fileSystem->exists($metadatasetfile)) {
return null;
Expand Down
2 changes: 2 additions & 0 deletions src/SimpleSAML/Metadata/MetaDataStorageSource.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ public static function getSource(array $sourceConfig): MetaDataStorageSource
return new Sources\MDQ($sourceConfig);
case 'pdo':
return new MetaDataStorageHandlerPdo($sourceConfig);
case 'directory':
return new MetaDataStorageHandlerDirectory($sourceConfig);
default:
// metadata store from module
try {
Expand Down
Loading