Newer
Older
Jaime Perez Crespo
committed
namespace SimpleSAML;
use Exception;
use ParseError;
use SimpleSAML\Assert\Assert;
use SimpleSAML\SAML2\Constants as C;
use Symfony\Component\Filesystem\Filesystem;
use function array_key_exists;
use function array_keys;
use function dirname;
use function interface_exists;
use function is_array;
use function is_int;
use function is_string;
use function ob_end_clean;
use function ob_get_length;
use function ob_start;
use function preg_match;
use function preg_replace;
use function rtrim;
use function substr;
use function var_export;
/**
* Configuration of SimpleSAMLphp
Andreas Åkre Solberg
committed
*
* @package SimpleSAMLphp
class Configuration implements Utils\ClearableState
/**
* The release version of this package
*/
/**
* A default value which means that the given option is required.
*
* @var string
public const REQUIRED_OPTION = '___REQUIRED_OPTION___';
Tim van Dijen
committed
/**
* The default security-headers to be sent on responses.
*/
public const DEFAULT_SECURITY_HEADERS = [
'Content-Security-Policy' =>
"default-src 'none'; " .
"frame-ancestors 'self'; " .
"object-src 'none'; " .
"script-src 'self'; " .
"style-src 'self'; " .
"font-src 'self'; " .
"connect-src 'self'; " .
"img-src 'self' data:; " .
"base-uri 'none'",
'Referrer-Policy' => 'origin-when-cross-origin',
Tim van Dijen
committed
];
/**
* Associative array with mappings from instance-names to configuration objects.
* @var array<string, \SimpleSAML\Configuration>
* Configuration directories.
*
* This associative array contains the mappings from configuration sets to
* configuration directories.
/**
* Cache of loaded configuration files.
*
* The index in the array is the full path to the file.
/**
* The configuration array.
/**
* The location which will be given when an error occurs.
*
* @var string
/**
* The file this configuration was loaded from.
*
* @var string|null
/**
* Initializes a configuration from the given array.
*
* @param array $config The configuration array.
* @param string $location The location which will be given when an error occurs.
*/
public function __construct(array $config, string $location)
{
$this->configuration = $config;
$this->location = $location;
}
/**
* Load the given configuration file.
*
* @param string $filename The full path of the configuration file.
* @param bool $required Whether the file is required.
* @return \SimpleSAML\Configuration The configuration file. An exception will be thrown if the
* configuration file is missing.
* @throws \Exception If the configuration file is invalid or missing.
private static function loadFromFile(string $filename, bool $required): Configuration
{
if (array_key_exists($filename, self::$loadedConfigs)) {
return self::$loadedConfigs[$filename];
}
$fileSystem = new Filesystem();
if ($fileSystem->exists($filename)) {
// the file initializes a variable named '$config'
Jaime Perez Crespo
committed
ob_start();
if (interface_exists('Throwable', false)) {
} catch (ParseError $e) {
self::$loadedConfigs[$filename] = self::loadFromArray([], '[ARRAY]', 'simplesaml');
throw new Error\ConfigurationError($e->getMessage(), $filename, []);
}
} else {
require($filename);
}
Jaime Perez Crespo
committed
$spurious_output = ob_get_length() > 0;
ob_end_clean();
Jaime Perez Crespo
committed
// check that $config exists
if (!isset($config)) {
throw new Error\ConfigurationError(
Jaime Perez Crespo
committed
'$config is not defined in the configuration file.',
$filename
);
}
// check that $config is initialized to an array
throw new Error\ConfigurationError(
Jaime Perez Crespo
committed
'$config is not an array.',
$filename
);
}
// check that $config is not empty
if (empty($config)) {
throw new Error\ConfigurationError(
Jaime Perez Crespo
committed
'$config is empty.',
$filename
);
// file does not exist, but is required
throw new Error\ConfigurationError('Missing configuration file', $filename);
Jaime Perez Crespo
committed
// file does not exist, but is optional, so return an empty configuration object without saving it
$cfg = new Configuration([], $filename);
Jaime Perez Crespo
committed
$cfg->filename = $filename;
return $cfg;
$cfg = new Configuration($config, $filename);
self::$loadedConfigs[$filename] = $cfg;
Jaime Perez Crespo
committed
if ($spurious_output) {
Logger::warning(
Jaime Perez Crespo
committed
"The configuration file '$filename' generates output. Please review your configuration."
);
}
return $cfg;
}
/**
* Set the directory for configuration files for the given configuration set.
*
* @param string $path The directory which contains the configuration files.
* @param string $configSet The configuration set. Defaults to 'simplesaml'.
*/
public static function setConfigDir(string $path, string $configSet = 'simplesaml'): void
{
self::$configDirs[$configSet] = $path;
}
/**
* Store a pre-initialized configuration.
*
* Allows consumers to create configuration objects without having them
* loaded from a file.
*
* @param \SimpleSAML\Configuration $config The configuration object to store
* @param string $filename The name of the configuration file.
* @param string $configSet The configuration set. Optional, defaults to 'simplesaml'.
public static function setPreLoadedConfig(
Configuration $config,
string $filename = 'config.php',
string $configSet = 'simplesaml'
): void {
if (!array_key_exists($configSet, self::$configDirs)) {
if ($configSet !== 'simplesaml') {
throw new Exception('Configuration set \'' . $configSet . '\' not initialized.');
self::$configDirs['simplesaml'] = dirname(__FILE__, 3) . '/config';
}
}
$dir = self::$configDirs[$configSet];
self::$loadedConfigs[$filePath] = $config;
}
* Load a configuration file from a configuration set.
*
* @param string $filename The name of the configuration file.
* @param string $configSet The configuration set. Optional, defaults to 'simplesaml'.
* @return \SimpleSAML\Configuration The Configuration object.
* @throws \Exception If the configuration set is not initialized.
public static function getConfig(
string $filename = 'config.php',
string $configSet = 'simplesaml'
): Configuration {
if (!array_key_exists($configSet, self::$configDirs)) {
if ($configSet !== 'simplesaml') {
throw new Exception('Configuration set \'' . $configSet . '\' not initialized.');
$configUtils = new Utils\Config();
self::$configDirs['simplesaml'] = $configUtils->getConfigDir();
}
}
$dir = self::$configDirs[$configSet];
return self::loadFromFile($filePath, true);
}
/**
* Load a configuration file from a configuration set.
*
* This function will return a configuration object even if the file does not exist.
*
* @param string $filename The name of the configuration file.
* @param string $configSet The configuration set. Optional, defaults to 'simplesaml'.
*
* @return \SimpleSAML\Configuration A configuration object.
* @throws \Exception If the configuration set is not initialized.
public static function getOptionalConfig(
string $filename = 'config.php',
string $configSet = 'simplesaml'
): Configuration {
if (!array_key_exists($configSet, self::$configDirs)) {
if ($configSet !== 'simplesaml') {
throw new Exception('Configuration set \'' . $configSet . '\' not initialized.');
$configUtils = new Utils\Config();
self::$configDirs['simplesaml'] = $configUtils->getConfigDir();
}
$dir = self::$configDirs[$configSet];
return self::loadFromFile($filePath, false);
}
/**
* Loads a configuration from the given array.
*
* @param array $config The configuration array.
* @param string $location The location which will be given when an error occurs. Optional.
* @param string|null $instance The name of this instance. If specified, the configuration will be loaded and an
* instance with that name will be kept for it to be retrieved later with getInstance($instance). If null, the
* configuration will not be kept for later use. Defaults to null.
* @return \SimpleSAML\Configuration The configuration object.
public static function loadFromArray(
array $config,
string $location = '[ARRAY]',
?string $instance = null
): Configuration {
$c = new Configuration($config, $location);
if ($instance !== null) {
self::$instance[$instance] = $c;
}
return $c;
}
/**
* Get a configuration file by its instance name.
*
* This function retrieves a configuration file by its instance name. The instance
* name is initialized by the init function, or by copyFromBase function.
*
* If no configuration file with the given instance name is found, an exception will
* be thrown.
*
* @param string $instancename The instance name of the configuration file. Deprecated.
* @return \SimpleSAML\Configuration The configuration object.
* @throws \Exception If the configuration with $instancename name is not initialized.
public static function getInstance(string $instancename = 'simplesaml'): Configuration
// check if the instance exists already
if (array_key_exists($instancename, self::$instance)) {
return self::$instance[$instancename];
}
if ($instancename === 'simplesaml') {
Jaime Perez Crespo
committed
try {
return self::getConfig();
} catch (Error\ConfigurationError $e) {
throw Error\CriticalConfigurationError::fromException($e);
Jaime Perez Crespo
committed
}
throw new Error\CriticalConfigurationError(
'Configuration with name ' . $instancename . ' is not initialized.'
Jaime Perez Crespo
committed
);
* Retrieve the current version of SimpleSAMLphp.
}
/**
* Retrieve a configuration option set in config.php.
*
* @param string $name Name of the configuration option.
* @return mixed The configuration option with name $name.
* @throws \SimpleSAML\Assert\AssertionFailedException If the required option cannot be retrieved.
*/
{
Assert::true(
$this->hasValue($name),
sprintf('%s: Could not retrieve the required option %s.', $this->location, var_export($name, true)),
);
return $this->configuration[$name];
}
/**
* Retrieve an optional configuration option set in config.php.
* @param string $name Name of the configuration option.
* @param mixed $default Default value of the configuration option.
This parameter will default to null if not specified.
*
* @return mixed The configuration option with name $name, or $default if the option was not found.
*
* @throws \SimpleSAML\Assert\AssertionFailedException If the required option cannot be retrieved.
public function getOptionalValue(string $name, mixed $default): mixed
// return the default value if the option is unset
return $this->configuration[$name];
*
* @param string $name The key in the configuration to look for.
*
* @return boolean If the value is set in this configuration.
*/
return array_key_exists($name, $this->configuration) && !is_null($this->configuration[$name]);
}
/**
* Check whether any key of the set given exists in the configuration.
*
* @param array $names An array of options to look for.
*
* @return boolean If any of the keys in $names exist in the configuration
*/
public function hasValueOneOf(array $names): bool
if ($this->hasValue($name)) {
return true;
}
}
return false;
}
Jaime Perez Crespo
committed
/**
* Retrieve the absolute path pointing to the SimpleSAMLphp installation.
*
* The path is guaranteed to start and end with a slash ('/'). E.g.: /simplesaml/
*
* @return string The absolute path where SimpleSAMLphp can be reached in the web server.
*
* @throws \SimpleSAML\Error\CriticalConfigurationError If the format of 'baseurlpath' is incorrect.
Jaime Perez Crespo
committed
*/
Jaime Perez Crespo
committed
{
$baseURL = $this->getOptionalString('baseurlpath', 'simplesaml/');
Jaime Perez Crespo
committed
if (preg_match('#^https?://[^/]*(?:/(.+/?)?)?$#', $baseURL, $matches)) {
// We have a full url, we need to strip the path.
Jaime Perez Crespo
committed
if (!array_key_exists(1, $matches)) {
Jaime Perez Crespo
committed
return '/';
Jaime Perez Crespo
committed
}
}
if ($baseURL === '' || $baseURL === '/') {
// Root directory of site.
Jaime Perez Crespo
committed
return '/';
}
if (preg_match('#^/?((?:[^/\s]+/?)+)#', $baseURL, $matches)) {
// Local path only.
/**
* Invalid 'baseurlpath'. We cannot recover from this.
* Throw a critical exception and try to be graceful
* with the configuration. Use a guessed base path instead of the one provided.
*/
$c = $this->toArray();
$httpUtils = new Utils\HTTP();
$c['baseurlpath'] = $httpUtils->guessBasePath();
throw new Error\CriticalConfigurationError(
'Incorrect format for option \'baseurlpath\'. Value is: "' .
$this->getOptionalString('baseurlpath', 'simplesaml/') . '". Valid format is in the form' .
' [(http|https)://(hostname|fqdn)[:port]]/[path/to/simplesaml/].',
$this->filename,
$c
);
* This function resolves a path which may be relative to the SimpleSAMLphp base directory.
*
* The path will never end with a '/'.
*
* @param string|null $path The path we should resolve. This option may be null.
* @return string|null $path if $path is an absolute path, or $path prepended with the base directory of this
* SimpleSAMLphp installation. We will return NULL if $path is null.
public function resolvePath(?string $path): ?string
{
if ($path === null) {
return null;
}
$sysUtils = new Utils\System();
return $sysUtils->resolvePath($path, $this->getBaseDir());
}
/**
* Retrieve a path configuration option set in config.php.
*
* The function will always return an absolute path unless the option is not set. It will then return the default
* value.
* It checks if the value starts with a slash, and prefixes it with the value from getBaseDir if it doesn't.
* @param string $name Name of the configuration option.
* @param string|null $default Default value of the configuration option. This parameter will default to null if
* not specified.
*
* @return string|null The path configuration option with name $name, or $default if the option was not found.
public function getPathValue(string $name, ?string $default = null): ?string
// return the default value if the option is unset
if (!array_key_exists($name, $this->configuration)) {
$path = $default;
} else {
$path = $this->configuration[$name];
}
$path = $this->resolvePath($path);
if ($path === null) {
return null;
}
* Retrieve the base directory for this SimpleSAMLphp installation.
*
* This function first checks the 'basedir' configuration option. If this option is undefined or null, then we
* fall back to looking at the current filename.
* @return string The absolute path to the base directory for this SimpleSAMLphp installation. This path will
* always end with a slash.
// check if a directory is configured in the configuration file
$dir = $this->getOptionalString('basedir', null);
// add trailing slash if it is missing
if (substr($dir, -1) !== DIRECTORY_SEPARATOR) {
$dir .= DIRECTORY_SEPARATOR;
}
return $dir;
}
// the directory wasn't set in the configuration file, path is <base directory>/src/SimpleSAML/Configuration.php
Assert::same(basename($dir), 'Configuration.php');
Assert::same(basename($dir), 'SimpleSAML');
// Add trailing directory separator
$dir .= DIRECTORY_SEPARATOR;
return $dir;
}
/**
* This function retrieves a boolean configuration option.
*
* An exception will be thrown if this option isn't a boolean, or if this option isn't found.
* @param string $name The name of the option.
* @return boolean The option with the given name.
* @throws \SimpleSAML\Assert\AssertionFailedException If the option is not boolean.
public function getBoolean(string $name): bool
Assert::boolean(
$ret,
sprintf('%s: The option %s is not a valid boolean value.', $this->location, var_export($name, true)),
);
return $ret;
}
/**
* This function retrieves a boolean configuration option.
*
* An exception will be thrown if this option isn't a boolean.
*
* @param string $name The name of the option.
* @param bool|null $default A default value which will be returned if the option isn't found.
* The default value can be null or a boolean.
*
* @return bool|null The option with the given name, or $default.
* @psalm-return ($default is bool ? bool : bool|null)
*
* @throws \SimpleSAML\Assert\AssertionFailedException If the option is not boolean.
*/
public function getOptionalBoolean(string $name, ?bool $default): ?bool
{
Assert::nullOrBoolean(
$ret,
sprintf(
'%s: The option %s is not a valid boolean value or null.',
$this->location,
var_export($name, true)
),
);
return $ret;
}
/**
* This function retrieves a string configuration option.
*
* An exception will be thrown if this option isn't a string, or if this option isn't found.
* @param string $name The name of the option.
* @return string The option with the given name.
* @throws \SimpleSAML\Assert\AssertionFailedException If the option is not a string.
public function getString(string $name): string
Assert::string(
$ret,
sprintf('%s: The option %s is not a valid string value.', $this->location, var_export($name, true)),
);
return $ret;
}
/**
* This function retrieves an optional string configuration option.
*
* An exception will be thrown if this option isn't a string.
*
* @param string $name The name of the option.
* @param string|null $default A default value which will be returned if the option isn't found.
* The default value can be null or a string.
*
* @return string|null The option with the given name, or $default if the option isn't found.
* @psalm-return ($default is string ? string : string|null)
*
* @throws \SimpleSAML\Assert\AssertionFailedException If the option is not a string.
*/
public function getOptionalString(string $name, ?string $default): ?string
{
$ret = $this->getOptionalValue($name, $default);
Assert::nullOrString(
$ret,
sprintf(
'%s: The option %s is not a valid string value or null.',
$this->location,
var_export($name, true)
),
);
}
/**
* This function retrieves an integer configuration option.
*
* An exception will be thrown if this option isn't an integer, or if this option isn't found.
* @param string $name The name of the option.
* @return int The option with the given name.
* @throws \SimpleSAML\Assert\AssertionFailedException If the option is not an integer.
public function getInteger(string $name): int
Assert::integer(
$ret,
sprintf('%s: The option %s is not a valid integer value.', $this->location, var_export($name, true)),
);
return $ret;
}
/**
* This function retrieves an optional integer configuration option.
*
* An exception will be thrown if this option isn't an integer.
*
* @param string $name The name of the option.
* @param int|null $default A default value which will be returned if the option isn't found.
* The default value can be null or an integer.
*
* @return int|null The option with the given name, or $default if the option isn't found.
* @psalm-return ($default is int ? int : int|null)
*
* @throws \SimpleSAML\Assert\AssertionFailedException If the option is not an integer.
*/
public function getOptionalInteger(string $name, ?int $default): ?int
{
$ret = $this->getOptionalValue($name, $default);
Assert::nullOrInteger(
$ret,
sprintf(
'%s: The option %s is not a valid integer value or null.',
$this->location,
var_export($name, true)
),
);
}
/**
* This function retrieves an integer configuration option where the value must be in the specified range.
*
* An exception will be thrown if:
* - the option isn't an integer
* - the option isn't found, and no default value is given
* - the value is outside of the allowed range
*
* @param string $name The name of the option.
* @param int $minimum The smallest value which is allowed.
* @param int $maximum The largest value which is allowed.
*
* @return int The option with the given name.
* @throws \SimpleSAML\Assert\AssertionFailedException If the option is not in the range specified.
public function getIntegerRange(string $name, int $minimum, int $maximum): int
Assert::range(
$ret,
$minimum,
$maximum,
sprintf(
'%s: Value of option %s is out of range. Value is %%s, allowed range is [%%2$s - %%3$s]',
$this->location,
var_export($name, true),
),
);
return $ret;
}
/**
* This function retrieves an optional integer configuration option where the value must be in the specified range.
*
* An exception will be thrown if:
* - the option isn't an integer
* - the value is outside of the allowed range
*
* @param string $name The name of the option.
* @param int $minimum The smallest value which is allowed.
* @param int $maximum The largest value which is allowed.
* @param int|null $default A default value which will be returned if the option isn't found.
* The default value can be null or an integer.
*
* @return int|null The option with the given name, or $default if the option isn't found and $default is
* specified.
* @psalm-return ($default is int ? int : int|null)
*
* @throws \SimpleSAML\Assert\AssertionFailedException If the option is not in the range specified.
*/
public function getOptionalIntegerRange(string $name, int $minimum, int $maximum, ?int $default): ?int
{
$ret = $this->getOptionalInteger($name, $default);
Assert::nullOrRange(
$ret,
$minimum,
$maximum,
sprintf(
'%s: Value of option %s is out of range. Value is %%s, allowed range is [%%2$s - %%3$s] or null.',
$this->location,
var_export($name, true),
),
);
}
/**
* Retrieve a configuration option with one of the given values.
*
* This will check that the configuration option matches one of the given values. The match will use
* strict comparison. An exception will be thrown if it does not match.
*
* The option is mandatory and an exception will be thrown if it isn't provided.
* @param string $name The name of the option.
* @param array $allowedValues The values the option is allowed to take, as an array.
* @return mixed The option with the given name.
* @throws \SimpleSAML\Assert\AssertionFailedException If the option does not have any of the allowed values.
public function getValueValidate(string $name, array $allowedValues): mixed
$ret = $this->getValue($name);
Assert::oneOf(
$ret,
$allowedValues,
sprintf(
'%s: Invalid value given for option %s. It should have one of: %%2$s; but got: %%s.',
$this->location,
var_export($name, true),
),
);
return $ret;
}
/**
* Retrieve an optional configuration option with one of the given values.
*
* This will check that the configuration option matches one of the given values. The match will use
* strict comparison. An exception will be thrown if it does not match.
*
* The option is optional. The default value is automatically included in the list of allowed values.
*
* @param string $name The name of the option.
* @param array $allowedValues The values the option is allowed to take, as an array.
* @param mixed $default The default value which will be returned if the option isn't found.
* The default value can be any value, including null.
*
* @return mixed The option with the given name, or $default if the option isn't found and $default is given.
*
* @throws \SimpleSAML\Assert\AssertionFailedException If the option does not have any of the allowed values.
*/
public function getOptionalValueValidate(string $name, array $allowedValues, mixed $default): mixed
$ret = $this->getOptionalValue($name, $default);
Assert::nullOrOneOf(
$ret,
$allowedValues,
sprintf(
'%s: Invalid value given for option %s. It should have one of: %%2$s or null; but got: %%s.',
$this->location,
var_export($name, true),
),
);
}
/**
* This function retrieves an array configuration option.
*
* An exception will be thrown if this option isn't an array, or if this option isn't found.
*
* @param string $name The name of the option.
* @return array The option with the given name.
* @throws \SimpleSAML\Assert\AssertionFailedException If the option is not an array.
*/
public function getArray(string $name): array
{
$ret = $this->getValue($name);
Assert::isArray(
$ret,
sprintf('%s: The option %s is not an array.', $this->location, var_export($name, true)),
);
return $ret;
}
/**
* This function retrieves an optional array configuration option.
*
* An exception will be thrown if this option isn't an array, or if this option isn't found.
*
* @param string $name The name of the option.
* @param array|null $default A default value which will be returned if the option isn't found.
* The default value can be null or an array.
*
* @return array|null The option with the given name, or $default if the option isn't found and $default is
* @psalm-return ($default is array ? array : array|null)
* @throws \SimpleSAML\Assert\AssertionFailedException If the option is not an array.
public function getOptionalArray(string $name, ?array $default): ?array
$ret = $this->getOptionalValue($name, $default);
Assert::nullOrIsArray(
$ret,
sprintf('%s: The option %s is not an array or null.', $this->location, var_export($name, true)),
);
}
/**
* This function retrieves an array configuration option.
*
* If the configuration option isn't an array, it will be converted to an array.
*
* @param string $name The name of the option.
*
* @return array The option with the given name.
public function getArrayize(string $name): array
if (!is_array($ret)) {
$ret = [$ret];
}
return $ret;
}
/**
* This function retrieves an optional array configuration option.
*
* If the configuration option isn't an array, it will be converted to an array.
*
* @param string $name The name of the option.
* @param array|null $default A default value which will be returned if the option isn't found.
* The default value can be null or an array.
*
* @return array|null The option with the given name.
* @psalm-return ($default is null ? array|null : array)
*/
public function getOptionalArrayize(string $name, $default): ?array
{
}
return $ret;
}
/**
* This function retrieves a configuration option with a string or an array of strings.
*
* If the configuration option is a string, it will be converted to an array with a single string
*
* @param string $name The name of the option.
* @return string[] The option with the given name.
* @throws \SimpleSAML\Assert\AssertionFailedException If the option is not a string or an array of strings.
public function getArrayizeString(string $name): array
$ret = $this->getArrayize($name);
Assert::allString(
$ret,
sprintf(
'%s: The option %s must be a string or an array of strings.',
$this->location,
var_export($name, true),
),
);
return $ret;
}
/**
* This function retrieves an optional configuration option with a string or an array of strings.
*
* If the configuration option is a string, it will be converted to an array with a single string
*
* @param string $name The name of the option.
* @param string[]|null $default A default value which will be returned if the option isn't found.
* The default value can be null or an array of strings.
*
* @return string[]|null The option with the given name, or $default if the option isn't found
* and $default is specified.
* @psalm-return ($default is null ? array|null : array)
*
* @throws \SimpleSAML\Assert\AssertionFailedException If the option is not a string or an array of strings.
*/
public function getOptionalArrayizeString(string $name, ?array $default): ?array
{
Assert::nullOrAllString(
$ret,
sprintf(
'%s: The option %s must be null, a string or an array of strings.',
$this->location,
var_export($name, true),
),
);
return $ret;
* Retrieve an array as a \SimpleSAML\Configuration object.
* This function will load the value of an option into a \SimpleSAML\Configuration object.
* The option must contain an array.
* An exception will be thrown if this option isn't an array, or if this option isn't found.
*
* @param string $name The name of the option.
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
* @return \SimpleSAML\Configuration The option with the given name,
*
* @throws \SimpleSAML\Assert\AssertionFailedException If the option is not an array.
*/
public function getConfigItem(string $name): Configuration
{
$ret = $this->getArray($name);
return self::loadFromArray($ret, $this->location . '[' . var_export($name, true) . ']');
}
/**
* Retrieve an optional array as a \SimpleSAML\Configuration object.
*
* This function will load the optional value of an option into a \SimpleSAML\Configuration object.
* The option must contain an array.
*
* An exception will be thrown if this option isn't an array, or if this option isn't found.
*
* @param string $name The name of the option.
* @param array|null $default A default value which will be used if the option isn't found. An empty Configuration
* object will be returned if this parameter isn't given and the option doesn't exist.
* This function will only return null if $default is set to null and the option doesn't exist.
* @return \SimpleSAML\Configuration|null The option with the given name,
* or $default, converted into a Configuration object.
* @psalm-return ($default is array ? \SimpleSAML\Configuration : \SimpleSAML\Configuration|null)
* @throws \SimpleSAML\Assert\AssertionFailedException If the option is not an array.
public function getOptionalConfigItem(string $name, ?array $default): ?Configuration
if ($ret !== null) {
return self::loadFromArray($ret, $this->location . '[' . var_export($name, true) . ']');
}
return null;
}
/**
* Retrieve list of options.
*
* This function returns the name of all options which are defined in this
* configuration file, as an array of strings.
*
* @return string[] Name of all options defined in this configuration file.
{
return array_keys($this->configuration);
}
/**
* Convert this configuration object back to an array.
*
* @return array An associative array with all configuration options and values.
*/
{
return $this->configuration;
}
/**
* Retrieve the default binding for the given endpoint type.
*
* This function combines the current metadata type (SAML 2 / SAML 1.1)
* with the endpoint type to determine which binding is the default.
*
* @param string $endpointType The endpoint type.
*
* @return string The default binding.
* @throws \Exception If the default binding is missing for this endpoint type.
private function getDefaultBinding(string $endpointType): string
{
$set = $this->getString('metadata-set');
case 'saml20-idp-remote:SingleSignOnService':
case 'saml20-idp-remote:SingleLogoutService':
case 'saml20-sp-remote:SingleLogoutService':
case 'saml20-sp-remote:AssertionConsumerService':
case 'saml20-idp-remote:ArtifactResolutionService':
throw new Exception('Missing default binding for ' . $endpointType . ' in ' . $set);
}
}
/**
* Helper function for dealing with metadata endpoints.
*
* @param string $endpointType The endpoint type.
*
* @return array Array of endpoints of the given type.
* @throws \Exception If any element of the configuration options for this endpoint type is incorrect.
public function getEndpoints(string $endpointType): array
$loc = $this->location . '[' . var_export($endpointType, true) . ']:';
if (!array_key_exists($endpointType, $this->configuration)) {
// no endpoints of the given type
}
$eps = $this->configuration[$endpointType];
if (is_string($eps)) {
throw new Exception($loc . ': Expected array or string.');
}
foreach ($eps as $i => &$ep) {
$iloc = $loc . '[' . var_export($i, true) . ']';
'Location' => $ep,
'Binding' => $this->getDefaultBinding($endpointType),
$responseLocation = $this->getOptionalString($endpointType . 'Response', null);
if ($responseLocation !== null) {
$ep['ResponseLocation'] = $responseLocation;
}
} elseif (!is_array($ep)) {
throw new Exception($iloc . ': Expected a string or an array.');
}
if (!array_key_exists('Location', $ep)) {
throw new Exception($iloc . ': Missing Location.');
}
if (!is_string($ep['Location'])) {
throw new Exception($iloc . ': Location must be a string.');
}
if (!array_key_exists('Binding', $ep)) {
throw new Exception($iloc . ': Missing Binding.');
}
if (!is_string($ep['Binding'])) {
throw new Exception($iloc . ': Binding must be a string.');
}
if (array_key_exists('ResponseLocation', $ep)) {
if (!is_string($ep['ResponseLocation'])) {
throw new Exception($iloc . ': ResponseLocation must be a string.');
}
}
if (array_key_exists('index', $ep)) {
if (!is_int($ep['index'])) {
throw new Exception($iloc . ': index must be an integer.');
}
}
}
return $eps;
}
/**
* Find an endpoint of the given type, using a list of supported bindings as a way to prioritize.
*
* @param string $endpointType The endpoint type.
* @param string[] $bindings Sorted array of acceptable bindings.
* @param mixed $default The default value to return if no matching endpoint is found. If no default is provided,
* an exception will be thrown.
*
* @return mixed|null The default endpoint.
* @throws \Exception If no supported endpoint is found.
public function getEndpointPrioritizedByBinding(
string $endpointType,
array $bindings,
mixed $default = self::REQUIRED_OPTION,
): mixed {
$endpoints = $this->getEndpoints($endpointType);
foreach ($bindings as $binding) {
foreach ($endpoints as $ep) {
if ($ep['Binding'] === $binding) {
return $ep;
}
}
}
if ($default === self::REQUIRED_OPTION) {
$loc = $this->location . '[' . var_export($endpointType, true) . ']:';
throw new Exception($loc . 'Could not find a supported ' . $endpointType . ' endpoint.');
}
return $default;
}
/**
* Find the default endpoint of the given type.
*
* @param string $endpointType The endpoint type.
* @param string[]|null $bindings Array with acceptable bindings. Can be null if any binding is allowed.
* @param mixed $default The default value to return if no matching endpoint is found. If no default is provided,
* an exception will be thrown.
*
* @return mixed The default endpoint, or the $default parameter if no acceptable endpoints are used.
* @throws \Exception If no supported endpoint is found and no $default parameter is specified.
public function getDefaultEndpoint(
string $endpointType,
array $bindings = null,
mixed $default = self::REQUIRED_OPTION,
): mixed {
$endpoints = $this->getEndpoints($endpointType);
$defaultEndpoint = Utils\Config\Metadata::getDefaultEndpoint($endpoints, $bindings);
if ($defaultEndpoint !== null) {
return $defaultEndpoint;
}
if ($default === self::REQUIRED_OPTION) {
$loc = $this->location . '[' . var_export($endpointType, true) . ']:';
throw new Exception($loc . 'Could not find a supported ' . $endpointType . ' endpoint.');
}
return $default;
}
/**
* Retrieve a string which may be localized into many languages.
*
* The default language returned is always 'en'.
*
* @param string $name The name of the option.
* @return array Associative array with language => string pairs.
* @throws \SimpleSAML\Assert\AssertionFailedException
* If the translation is not an array or a string, or its index or value are not strings.
Assert::isArray($ret, sprintf('%s: Must be an array or a string.', $this->location));
foreach ($ret as $k => $v) {
Assert::string($k, sprintf('%s: Invalid language code: %s', $this->location, var_export($k, true)));
Assert::string($v, sprintf('%s[%s]: Must be a string.', $this->location, var_export($v, true)));
}
return $ret;
}
/**
* Retrieve an optional string which may be localized into many languages.
*
* The default language returned is always 'en'.
*
* @param string $name The name of the option.
* @param array|null $default The default value.
*
* @return array|null Associative array with language => string pairs, or the provided default value.
* @psalm-return ($default is array ? array : array|null)
*
* @throws \SimpleSAML\Assert\AssertionFailedException
* If the translation is not an array or a string, or its index or value are not strings.
*/
public function getOptionalLocalizedString(string $name, ?array $default): ?array
{
if (!$this->hasValue($name)) {
// the option wasn't found, or it matches the default value. In any case, return this value
return $default;
}
return $this->getLocalizedString($name);
}
/**
* Get public key from metadata.
*
* @param string|null $use The purpose this key can be used for. (encryption or signing).
* @param bool $required Whether the public key is required. If this is true, a
* missing key will cause an exception. Default is false.
* @param string $prefix The prefix which should be used when reading from the metadata
* array. Defaults to ''.
*
* @return array Public key data, or empty array if no public key or was found.
* @throws \Exception If the certificate or public key cannot be loaded from location.
* @throws \SimpleSAML\Error\Exception If the location does not contain a valid PEM-encoded certificate, or there
* is no certificate in the metadata.
public function getPublicKeys(?string $use = null, bool $required = false, string $prefix = ''): array
foreach ($this->getArray($prefix . 'keys') as $key) {
if ($use !== null && isset($key[$use]) && !$key[$use]) {
continue;
}
if (isset($key['X509Certificate'])) {
$key['X509Certificate'] = preg_replace('/\s+/', '', $key['X509Certificate']);
}
$ret[] = $key;
}
} elseif ($this->hasValue($prefix . 'certData')) {
$certData = $this->getString($prefix . 'certData');
$certData = preg_replace('/\s+/', '', $certData);
$keyName = $this->getOptionalString($prefix . 'key_name', null);
'encryption' => true,
'signing' => true,
'type' => 'X509Certificate',
'X509Certificate' => $certData,
} elseif ($this->hasValue($prefix . 'certificate')) {
$location = $this->getString($prefix . 'certificate');
$cryptoUtils = new Utils\Crypto();
$data = $cryptoUtils->retrieveCertificate($location);
if ($data === null) {
$this->location . ': Unable to load certificate/public key from location "' . $location . '".'
// extract certificate data (if this is a certificate)
$pattern = '/^-----BEGIN CERTIFICATE-----([^-]*)^-----END CERTIFICATE-----/m';
if (!preg_match($pattern, $data, $matches)) {
throw new Error\Exception(
$this->location . ': Could not find PEM encoded certificate in "' . $location . '".'
);
}
$certData = preg_replace('/\s+/', '', $matches[1]);
$keyName = $this->getOptionalString($prefix . 'key_name', null);
'encryption' => true,
'signing' => true,
'type' => 'X509Certificate',
'X509Certificate' => $certData,
} elseif ($required === true) {
throw new Error\Exception($this->location . ': Missing certificate in metadata.');
/**
* Clear any configuration information cached.
* Allows for configuration files to be changed and reloaded during a given request. Most useful
* when running phpunit tests and needing to alter config.php between test cases
public static function clearInternalState(): void
self::$configDirs = [];
self::$instance = [];
self::$loadedConfigs = [];