From 3fc3e1508ea2b74e3eb5d28fd0c99daff3a9e100 Mon Sep 17 00:00:00 2001 From: Jaime Perez Crespo <jaime.perez@uninett.no> Date: Fri, 31 Jul 2015 11:49:35 +0200 Subject: [PATCH] Reformat SimpleSAML_Configuration. --- lib/SimpleSAML/Configuration.php | 2348 +++++++++++++++--------------- 1 file changed, 1212 insertions(+), 1136 deletions(-) diff --git a/lib/SimpleSAML/Configuration.php b/lib/SimpleSAML/Configuration.php index 358ca75ca..d56768a2a 100644 --- a/lib/SimpleSAML/Configuration.php +++ b/lib/SimpleSAML/Configuration.php @@ -1,1146 +1,1222 @@ <?php + /** * Configuration of SimpleSAMLphp * * @author Andreas Aakre Solberg, UNINETT AS. <andreas.solberg@uninett.no> * @package simpleSAMLphp */ -class SimpleSAML_Configuration { - - /** - * A default value which means that the given option is required. - */ - const REQUIRED_OPTION = '___REQUIRED_OPTION___'; - - - /** - * Associative array with mappings from instance-names to configuration objects. - */ - private static $instance = array(); - - - /** - * Configration directories. - * - * This associative array contains the mappings from configuration sets to - * configuration directories. - */ - private static $configDirs = array(); - - - /** - * Cache of loaded configuration files. - * - * The index in the array is the full path to the file. - */ - private static $loadedConfigs = array(); - - - /** - * The configuration array. - */ - private $configuration; - - - /** - * The location which will be given when an error occurs. - */ - private $location; - - - /** - * The file this configuration was loaded from. - */ - private $filename = 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($config, $location) { - assert('is_array($config)'); - assert('is_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. - */ - private static function loadFromFile($filename, $required) { - assert('is_string($filename)'); - assert('is_bool($required)'); - - if (array_key_exists($filename, self::$loadedConfigs)) { - return self::$loadedConfigs[$filename]; - } - - if (file_exists($filename)) { - $config = 'UNINITIALIZED'; - - /* The file initializes a variable named '$config'. */ - require($filename); - - /* Check that $config is initialized to an array. */ - if (!is_array($config)) { - throw new Exception('Invalid configuration file: ' . $filename); - } - - } elseif ($required) { - /* File does not exist, but is required. */ - throw new Exception('Missing configuration file: ' . $filename); - - } else { - /* File does not exist, but is optional. */ - $config = array(); - } - - $cfg = new SimpleSAML_Configuration($config, $filename); - $cfg->filename = $filename; - - self::$loadedConfigs[$filename] = $cfg; - - 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($path, $configSet = 'simplesaml') { - assert('is_string($path)'); - assert('is_string($configSet)'); - - self::$configDirs[$configSet] = $path; - } - - - /** - * 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'. - */ - public static function getConfig($filename = 'config.php', $configSet = 'simplesaml') { - assert('is_string($filename)'); - assert('is_string($configSet)'); - - if (!array_key_exists($configSet, self::$configDirs)) { - if ($configSet !== 'simplesaml') { - throw new Exception('Configuration set \'' . $configSet . '\' not initialized.'); - } else { - self::$configDirs['simplesaml'] = dirname(dirname(dirname(__FILE__))) . '/config'; - } - } - - $dir = self::$configDirs[$configSet]; - $filePath = $dir . '/' . $filename; - 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. - */ - public static function getOptionalConfig($filename = 'config.php', $configSet = 'simplesaml') { - assert('is_string($filename)'); - assert('is_string($configSet)'); - - if (!array_key_exists($configSet, self::$configDirs)) { - if ($configSet !== 'simplesaml') { - throw new Exception('Configuration set \'' . $configSet . '\' not initialized.'); - } else { - self::$configDirs['simplesaml'] = dirname(dirname(dirname(__FILE__))) . '/config'; - } - } - - $dir = self::$configDirs[$configSet]; - $filePath = $dir . '/' . $filename; - 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. - * @return SimpleSAML_Configuration The configuration object. - */ - public static function loadFromArray($config, $location = '[ARRAY]') { - assert('is_array($config)'); - assert('is_string($location)'); - - return new SimpleSAML_Configuration($config, $location); - } - - - /** - * 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. Depreceated. - * @return SimpleSAML_Configuration The configuration object. - */ - public static function getInstance($instancename = 'simplesaml') { - assert('is_string($instancename)'); - - if ($instancename === 'simplesaml') { - return self::getConfig(); - } - - if (!array_key_exists($instancename, self::$instance)) - throw new Exception('Configuration with name ' . $instancename . ' is not initialized.'); - return self::$instance[$instancename]; - } - - - /** - * Initialize a instance name with the given configuration file. - * - * @see setConfigDir() - * @depreceated This function is superseeded by the setConfigDir function. - */ - public static function init($path, $instancename = 'simplesaml', $configfilename = 'config.php') { - assert('is_string($path)'); - assert('is_string($instancename)'); - assert('is_string($configfilename)'); - - if ($instancename === 'simplesaml') { - /* For backwards compatibility. */ - self::setConfigDir($path, 'simplesaml'); - } - - /* Check if we already have loaded the given config - return the existing instance if we have. */ - if(array_key_exists($instancename, self::$instance)) { - return self::$instance[$instancename]; - } - - self::$instance[$instancename] = self::loadFromFile($path . '/' . $configfilename, TRUE); - } - - - /** - * Load a configuration file which is located in the same directory as this configuration file. - * - * @see getConfig() - * @depreceated This function is superseeded by the getConfig() function. - */ - public function copyFromBase($instancename, $filename) { - assert('is_string($instancename)'); - assert('is_string($filename)'); - assert('$this->filename !== NULL'); - - /* Check if we already have loaded the given config - return the existing instance if we have. */ - if(array_key_exists($instancename, self::$instance)) { - return self::$instance[$instancename]; - } - - $dir = dirname($this->filename); - - self::$instance[$instancename] = self::loadFromFile($dir . '/' . $filename, TRUE); - return self::$instance[$instancename]; - } - - - /** - * Retrieve the current version of simpleSAMLphp. - * - * @return string - */ - public function getVersion() { - return 'trunk'; - } - - - /** - * Retrieve a 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. This can be set to SimpleSAML_Configuration::REQUIRED_OPTION, which will - * cause an exception to be thrown if the option isn't found. - * @return mixed The configuration option with name $name, or $default if the option was not found. - */ - public function getValue($name, $default = NULL) { - - /* Return the default value if the option is unset. */ - if (!array_key_exists($name, $this->configuration)) { - if($default === self::REQUIRED_OPTION) { - throw new Exception($this->location . ': Could not retrieve the required option ' . - var_export($name, TRUE)); - } - return $default; - } - - return $this->configuration[$name]; - } - - - /** - * Check whether an key in the configuration exists. - * - * @param string $name The key in the configuration to look for. - * - * @return boolean If the value is set in this configuration. - */ - public function hasValue($name) { - return array_key_exists($name, $this->configuration); - } - /** - * 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($names) { - foreach($names AS $name) if ($this->hasValue($name)) return TRUE; - return FALSE; - } - - /** - * Retrieve the absolute path of the simpleSAMLphp installation, - * relative to the root of the website. - * - * For example: simplesaml/ - * - * The path will always end with a '/' and never have a leading slash. - * - * @return string The absolute path relative to the root of the website. - */ - public function getBaseURL() { - $baseURL = $this->getString('baseurlpath', 'simplesaml/'); - - if (preg_match('/^\*(.*)$/D', $baseURL, $matches)) { - /* deprecated behaviour, will be removed in the future */ - return \SimpleSAML\Utils\HTTP::getFirstPathElement(false) . $matches[1]; - } - - if (preg_match('#^https?://[^/]*/(.*)$#', $baseURL, $matches)) { - /* we have a full url, we need to strip the path */ - return $matches[1]; - } elseif ($baseURL === '' || $baseURL === '/') { - /* Root directory of site. */ - return ''; - } elseif (preg_match('#^/?([^/]?.*/)#D', $baseURL, $matches)) { - /* local path only */ - return $matches[1]; - } else { - /* invalid format */ - throw new SimpleSAML_Error_Exception('Incorrect format for option \'baseurlpath\'. Value is: "'. - $this->getString('baseurlpath', 'simplesaml/') . '". Valid format is in the form'. - ' [(http|https)://(hostname|fqdn)[:port]]/[path/to/simplesaml/].'); - } - } - - - /** - * 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($path) { - if($path === NULL) { - return NULL; - } - - assert('is_string($path)'); - - /* Prepend path with basedir if it doesn't start with a slash or a Windows - * drive letter (e.g. "C:\"). We assume getBaseDir ends with a slash. - */ - if ($path[0] !== '/' && - !(preg_match('@^[a-z]:[\\\\/]@i', $path, $matches) && is_dir($matches[0]))) - $path = $this->getBaseDir() . $path; - - /* Remove trailing slashes. */ - while (substr($path, -1) === '/') { - $path = substr($path, 0, -1); - } - - return $path; - } - - - /** - * 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($name, $default = NULL) { - - /* Return the default value if the option is unset. */ - if (!array_key_exists($name, $this->configuration)) { - $path = $default; - } else { - $path = $this->configuration[$name]; - } - - if ($path === NULL) { - return NULL; - } - - return $this->resolvePath($path) . '/'; - } - - - /** - * 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. - */ - public function getBaseDir() { - /* Check if a directory is configured in the configuration - * file. - */ - $dir = $this->getString('basedir', NULL); - if($dir !== NULL) { - /* Add trailing slash if it is missing. */ - if(substr($dir, -1) !== '/') { - $dir .= '/'; - } - - return $dir; - } - - /* The directory wasn't set in the configuration file. Our - * path is <base directory>/lib/SimpleSAML/Configuration.php - */ - - $dir = __FILE__; - assert('basename($dir) === "Configuration.php"'); - - $dir = dirname($dir); - assert('basename($dir) === "SimpleSAML"'); - - $dir = dirname($dir); - assert('basename($dir) === "lib"'); - - $dir = dirname($dir); - - /* Add trailing slash. */ - $dir .= '/'; - - 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, and no - * default value is given. - * - * @param string $name The name of the option. - * @param mixed $default A default value which will be returned if the option isn't found. The option will be - * required if this parameter isn't given. The default value can be any value, including - * NULL. - * @return boolean|mixed The option with the given name, or $default if the option isn't found and $default is specified. - */ - public function getBoolean($name, $default = self::REQUIRED_OPTION) { - assert('is_string($name)'); - - $ret = $this->getValue($name, $default); - - if($ret === $default) { - /* The option wasn't found, or it matches the default value. In any case, return - * this value. - */ - return $ret; - } - - if(!is_bool($ret)) { - throw new Exception($this->location . ': The option ' . var_export($name, TRUE) . - ' is not a valid boolean value.'); - } - - 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, and no - * default value is given. - * - * @param string $name The name of the option. - * @param mixed $default A default value which will be returned if the option isn't found. The option will be - * required if this parameter isn't given. The default value can be any value, including - * NULL. - * @return string|mixed The option with the given name, or $default if the option isn't found and $default is specified. - */ - public function getString($name, $default = self::REQUIRED_OPTION) { - assert('is_string($name)'); - - $ret = $this->getValue($name, $default); - - if($ret === $default) { - /* The option wasn't found, or it matches the default value. In any case, return - * this value. - */ - return $ret; - } - - if(!is_string($ret)) { - throw new Exception($this->location . ': The option ' . var_export($name, TRUE) . - ' is not a valid string value.'); - } - - return $ret; - } - - - /** - * 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, and no - * default value is given. - * - * @param string $name The name of the option. - * @param mixed $default A default value which will be returned if the option isn't found. The option will be - * required if this parameter isn't given. The default value can be any value, including - * NULL. - * @return int|mixed The option with the given name, or $default if the option isn't found and $default is specified. - */ - public function getInteger($name, $default = self::REQUIRED_OPTION) { - assert('is_string($name)'); - - $ret = $this->getValue($name, $default); - - if($ret === $default) { - /* The option wasn't found, or it matches the default value. In any case, return - * this value. - */ - return $ret; - } - - if(!is_int($ret)) { - throw new Exception($this->location . ': The option ' . var_export($name, TRUE) . - ' is not a valid integer value.'); - } - - return $ret; - } - - - /** - * 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. - * @param mixed $default A default value which will be returned if the option isn't found. The option will be - * required if this parameter isn't given. The default value can be any value, including - * NULL. - * @return int|mixed The option with the given name, or $default if the option isn't found and $default is specified. - */ - public function getIntegerRange($name, $minimum, $maximum, $default = self::REQUIRED_OPTION) { - assert('is_string($name)'); - assert('is_int($minimum)'); - assert('is_int($maximum)'); - - $ret = $this->getInteger($name, $default); - - if($ret === $default) { - /* The option wasn't found, or it matches the default value. In any case, return - * this value. - */ - return $ret; - } - - if ($ret < $minimum || $ret > $maximum) { - throw new Exception($this->location . ': Value of option ' . var_export($name, TRUE) . - ' is out of range. Value is ' . $ret . ', allowed range is [' - . $minimum . ' - ' . $maximum . ']'); - } - - return $ret; - } - - - /** - * 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 can be mandatory or optional. If no default value is given, it will be considered to be - * mandatory, and an exception will be thrown if it isn't provided. If a default value is given, it - * is considered to be optional, and the default value is returned. 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. If this parameter - * isn't given, the option will be considered to be mandatory. 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 adn $default is given. - */ - public function getValueValidate($name, $allowedValues, $default = self::REQUIRED_OPTION) { - assert('is_string($name)'); - assert('is_array($allowedValues)'); - - $ret = $this->getValue($name, $default); - if($ret === $default) { - /* The option wasn't found, or it matches the default value. In any case, return - * this value. - */ - return $ret; - } - - if(!in_array($ret, $allowedValues, TRUE)) { - $strValues = array(); - foreach($allowedValues as $av) { - $strValues[] = var_export($av, TRUE); - } - $strValues = implode(', ', $strValues); - - throw new Exception($this->location . ': Invalid value given for the option ' . - var_export($name, TRUE) . '. It should have one of the following values: ' . - $strValues . '; but it had the following value: ' . var_export($ret, TRUE)); - } - - return $ret; - } - - - /** - * 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, and no - * default value is given. - * - * @param string $name The name of the option. - * @param mixed $default A default value which will be returned if the option isn't found. The option will be - * required if this parameter isn't given. 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 specified. - */ - public function getArray($name, $default = self::REQUIRED_OPTION) { - assert('is_string($name)'); - - $ret = $this->getValue($name, $default); - - if ($ret === $default) { - /* The option wasn't found, or it matches the default value. In any case, return - * this value. - */ - return $ret; - } - - if (!is_array($ret)) { - throw new Exception($this->location . ': The option ' . var_export($name, TRUE) . - ' is not an array.'); - } - - return $ret; - } - - - /** - * 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. - * @param mixed $default A default value which will be returned if the option isn't found. The option will be - * required if this parameter isn't given. The default value can be any value, including - * NULL. - * @return array The option with the given name, or $default if the option isn't found and $default is specified. - */ - public function getArrayize($name, $default = self::REQUIRED_OPTION) { - assert('is_string($name)'); - - $ret = $this->getValue($name, $default); - - if ($ret === $default) { - /* The option wasn't found, or it matches the default value. In any case, return - * this value. - */ - return $ret; - } - - if (!is_array($ret)) { - $ret = array($ret); - } - - 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. - * @param mixed $default A default value which will be returned if the option isn't found. The option will be - * required if this parameter isn't given. The default value can be any value, including - * NULL. - * @return array The option with the given name, or $default if the option isn't found and $default is specified. - */ - public function getArrayizeString($name, $default = self::REQUIRED_OPTION) { - assert('is_string($name)'); - - $ret = $this->getArrayize($name, $default); - - if ($ret === $default) { - /* The option wasn't found, or it matches the default value. In any case, return - * this value. - */ - return $ret; - } - - foreach ($ret as $value) { - if (!is_string($value)) { - throw new Exception($this->location . ': The option ' . var_export($name, TRUE) . - ' must be a string or an array of strings.'); - } - } - - 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, and no default value is given. - * - * @param string $name The name of the option. - * @param mixed $default A default value which will be returned if the option isn't found. The option will be - * required if this parameter isn't given. 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 specified. - */ - public function getConfigItem($name, $default = self::REQUIRED_OPTION) { - assert('is_string($name)'); - - $ret = $this->getValue($name, $default); - - if ($ret === $default) { - /* The option wasn't found, or it matches the default value. In any case, return - * this value. - */ - return $ret; - } - - if (!is_array($ret)) { - throw new Exception($this->location . ': The option ' . var_export($name, TRUE) . - ' is not an array.'); - } - - return self::loadFromArray($ret, $this->location . '[' . var_export($name, TRUE) . ']'); - } - - - /** - * Retrieve an array of arrays as an array of SimpleSAML_Configuration objects. - * - * This function will retrieve an option containing an array of arrays, and create an - * array of SimpleSAML_Configuration objects from that array. The indexes in the new - * array will be the same as the original indexes, but the values will be - * SimpleSAML_Configuration objects. - * - * An exception will be thrown if this option isn't an array of arrays, or if this option - * isn't found, and no default value is given. - * - * @param string $name The name of the option. - * @param string $location Name of the items in the array. - * @param mixed $default A default value which will be returned if the option isn't found. The option will be - * required if this parameter isn't given. 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 specified. - */ - public function getConfigList($name, $default = self::REQUIRED_OPTION) { - assert('is_string($name)'); - - $ret = $this->getValue($name, $default); - - if ($ret === $default) { - /* The option wasn't found, or it matches the default value. In any case, return - * this value. - */ - return $ret; - } - - if (!is_array($ret)) { - throw new Exception($this->location . ': The option ' . var_export($name, TRUE) . - ' is not an array.'); - } - - $out = array(); - foreach ($ret as $index => $config) { - $newLoc = $this->location . '[' . var_export($name, TRUE) . '][' . - var_export($index, TRUE) . ']'; - if (!is_array($config)) { - throw new Exception($newLoc . ': The value of this element was expected to be an array.'); - } - $out[$index] = self::loadFromArray($config, $newLoc); - } - - return $out; - } - - - /** - * 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 array Name of all options defined in this configuration file. - */ - public function getOptions() { - - return array_keys($this->configuration); - } - - - /** - * Convert this configuration object back to an array. - * - * @return array An associative array with all configuration options and values. - */ - public function toArray() { - - 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. - */ - private function getDefaultBinding($endpointType) { - assert('is_string($endpointType)'); - - $set = $this->getString('metadata-set'); - switch ($set.':'.$endpointType) { - case 'saml20-idp-remote:SingleSignOnService': - case 'saml20-idp-remote:SingleLogoutService': - case 'saml20-sp-remote:SingleLogoutService': - return SAML2_Const::BINDING_HTTP_REDIRECT; - case 'saml20-sp-remote:AssertionConsumerService': - return SAML2_Const::BINDING_HTTP_POST; - case 'saml20-idp-remote:ArtifactResolutionService': - return SAML2_Const::BINDING_SOAP; - case 'shib13-idp-remote:SingleSignOnService': - return 'urn:mace:shibboleth:1.0:profiles:AuthnRequest'; - case 'shib13-sp-remote:AssertionConsumerService': - return 'urn:oasis:names:tc:SAML:1.0:profiles:browser-post'; - default: - 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. - */ - public function getEndpoints($endpointType) { - assert('is_string($endpointType)'); - - $loc = $this->location . '[' . var_export($endpointType, TRUE) . ']:'; - - if (!array_key_exists($endpointType, $this->configuration)) { - /* No endpoints of the given type. */ - return array(); - } - - - $eps = $this->configuration[$endpointType]; - if (is_string($eps)) { - /* For backwards-compatibility. */ - $eps = array($eps); - } elseif (!is_array($eps)) { - throw new Exception($loc . ': Expected array or string.'); - } - - - foreach ($eps as $i => &$ep) { - $iloc = $loc . '[' . var_export($i, TRUE) . ']'; - - if (is_string($ep)) { - /* For backwards-compatibility. */ - $ep = array( - 'Location' => $ep, - 'Binding' => $this->getDefaultBinding($endpointType), - ); - $responseLocation = $this->getString($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 array $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 array|null The default endpoint, or NULL if no acceptable endpoints are used. - */ - public function getEndpointPrioritizedByBinding($endpointType, array $bindings, $default = self::REQUIRED_OPTION) { - assert('is_string($endpointType)'); - - $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 array $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 array|null The default endpoint, or NULL if no acceptable endpoints are used. - */ - public function getDefaultEndpoint($endpointType, array $bindings = NULL, $default = self::REQUIRED_OPTION) { - assert('is_string($endpointType)'); - - $endpoints = $this->getEndpoints($endpointType); - - $defaultEndpoint = \SimpleSAML\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. - * @param mixed $default The default value. If no default is given, and the option isn't found, an exception will be thrown. - * @return array Associative array with language=>string pairs. - */ - public function getLocalizedString($name, $default = self::REQUIRED_OPTION) { - assert('is_string($name)'); - - $ret = $this->getValue($name, $default); - if($ret === $default) { - /* The option wasn't found, or it matches the default value. In any case, return - * this value. - */ - return $ret; - } - - $loc = $this->location . '[' . var_export($name, TRUE) . ']'; - - if (is_string($ret)) { - $ret = array('en' => $ret,); - } - - if (!is_array($ret)) { - throw new Exception($loc . ': Must be an array or a string.'); - } - - foreach ($ret as $k => $v) { - if (!is_string($k)) { - throw new Exception($loc . ': Invalid language code: ' . var_export($k, TRUE)); - } - if (!is_string($v)) { - throw new Exception($loc . '[' . var_export($v, TRUE) . ']: Must be a string.'); - } - } - - return $ret; - } - - /** - * 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|null Public key data, or NULL if no public key or was found. - */ - public function getPublicKeys($use = NULL, $required = FALSE, $prefix = '') { - assert('is_bool($required)'); - assert('is_string($prefix)'); - - if ($this->hasValue($prefix . 'keys')) { - $ret = array(); - foreach ($this->getArray($prefix . 'keys') as $key) { - if ($use !== NULL && isset($key[$use]) && !$key[$use]) { - continue; - } - if (isset($key['X509Certificate'])) { - /* Strip whitespace from key. */ - $key['X509Certificate'] = preg_replace('/\s+/', '', $key['X509Certificate']); - } - $ret[] = $key; - - } - if (!empty($ret)) { - return $ret; - } - } elseif ($this->hasValue($prefix . 'certData')) { - $certData = $this->getString($prefix . 'certData'); - $certData = preg_replace('/\s+/', '', $certData); - return array( - array( - 'encryption' => TRUE, - 'signing' => TRUE, - 'type' => 'X509Certificate', - 'X509Certificate' => $certData, - ), - ); - } elseif ($this->hasValue($prefix . 'certificate')) { - $file = $this->getString($prefix . 'certificate'); - $file = \SimpleSAML\Utils\Config::getCertPath($file); - $data = @file_get_contents($file); - - if ($data === FALSE) { - throw new Exception($this->location . ': Unable to load certificate/public key from file "' . $file . '".'); - } - - /* Extract certificate data (if this is a certificate). */ - $pattern = '/^-----BEGIN CERTIFICATE-----([^-]*)^-----END CERTIFICATE-----/m'; - if (!preg_match($pattern, $data, $matches)) { - throw new SimpleSAML_Error_Exception($this->location . ': Could not find PEM encoded certificate in "' . $file . '".'); - } - $certData = preg_replace('/\s+/', '', $matches[1]); - - return array( - array( - 'encryption' => TRUE, - 'signing' => TRUE, - 'type' => 'X509Certificate', - 'X509Certificate' => $certData, - ), - ); - } - - if ($required) { - throw new SimpleSAML_Error_Exception($this->location . ': Missing certificate in metadata.'); - } else { - return NULL; - } - } +class SimpleSAML_Configuration +{ + + /** + * A default value which means that the given option is required. + */ + const REQUIRED_OPTION = '___REQUIRED_OPTION___'; + + + /** + * Associative array with mappings from instance-names to configuration objects. + */ + private static $instance = array(); + + + /** + * Configration directories. + * + * This associative array contains the mappings from configuration sets to + * configuration directories. + */ + private static $configDirs = array(); + + + /** + * Cache of loaded configuration files. + * + * The index in the array is the full path to the file. + */ + private static $loadedConfigs = array(); + + + /** + * The configuration array. + */ + private $configuration; + + + /** + * The location which will be given when an error occurs. + */ + private $location; + + + /** + * The file this configuration was loaded from. + */ + private $filename = 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($config, $location) + { + assert('is_array($config)'); + assert('is_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. + */ + private static function loadFromFile($filename, $required) + { + assert('is_string($filename)'); + assert('is_bool($required)'); + + if (array_key_exists($filename, self::$loadedConfigs)) { + return self::$loadedConfigs[$filename]; + } + + if (file_exists($filename)) { + $config = 'UNINITIALIZED'; + + /* The file initializes a variable named '$config'. */ + require($filename); + + /* Check that $config is initialized to an array. */ + if (!is_array($config)) { + throw new Exception('Invalid configuration file: '.$filename); + } + } elseif ($required) { + /* File does not exist, but is required. */ + throw new Exception('Missing configuration file: '.$filename); + } else { + /* File does not exist, but is optional. */ + $config = array(); + } + + $cfg = new SimpleSAML_Configuration($config, $filename); + $cfg->filename = $filename; + + self::$loadedConfigs[$filename] = $cfg; + + 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($path, $configSet = 'simplesaml') + { + assert('is_string($path)'); + assert('is_string($configSet)'); + + self::$configDirs[$configSet] = $path; + } + + + /** + * 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'. + */ + public static function getConfig($filename = 'config.php', $configSet = 'simplesaml') + { + assert('is_string($filename)'); + assert('is_string($configSet)'); + + if (!array_key_exists($configSet, self::$configDirs)) { + if ($configSet !== 'simplesaml') { + throw new Exception('Configuration set \''.$configSet.'\' not initialized.'); + } else { + self::$configDirs['simplesaml'] = dirname(dirname(dirname(__FILE__))).'/config'; + } + } + + $dir = self::$configDirs[$configSet]; + $filePath = $dir.'/'.$filename; + 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. + */ + public static function getOptionalConfig($filename = 'config.php', $configSet = 'simplesaml') + { + assert('is_string($filename)'); + assert('is_string($configSet)'); + + if (!array_key_exists($configSet, self::$configDirs)) { + if ($configSet !== 'simplesaml') { + throw new Exception('Configuration set \''.$configSet.'\' not initialized.'); + } else { + self::$configDirs['simplesaml'] = dirname(dirname(dirname(__FILE__))).'/config'; + } + } + + $dir = self::$configDirs[$configSet]; + $filePath = $dir.'/'.$filename; + 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. + * + * @return SimpleSAML_Configuration The configuration object. + */ + public static function loadFromArray($config, $location = '[ARRAY]') + { + assert('is_array($config)'); + assert('is_string($location)'); + + return new SimpleSAML_Configuration($config, $location); + } + + + /** + * 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. Depreceated. + * + * @return SimpleSAML_Configuration The configuration object. + */ + public static function getInstance($instancename = 'simplesaml') + { + assert('is_string($instancename)'); + + if ($instancename === 'simplesaml') { + return self::getConfig(); + } + + if (!array_key_exists($instancename, self::$instance)) { + throw new Exception('Configuration with name '.$instancename.' is not initialized.'); + } + return self::$instance[$instancename]; + } + + + /** + * Initialize a instance name with the given configuration file. + * + * @see setConfigDir() + * @depreceated This function is superseeded by the setConfigDir function. + */ + public static function init($path, $instancename = 'simplesaml', $configfilename = 'config.php') + { + assert('is_string($path)'); + assert('is_string($instancename)'); + assert('is_string($configfilename)'); + + if ($instancename === 'simplesaml') { + /* For backwards compatibility. */ + self::setConfigDir($path, 'simplesaml'); + } + + /* Check if we already have loaded the given config - return the existing instance if we have. */ + if (array_key_exists($instancename, self::$instance)) { + return self::$instance[$instancename]; + } + + self::$instance[$instancename] = self::loadFromFile($path.'/'.$configfilename, true); + } + + + /** + * Load a configuration file which is located in the same directory as this configuration file. + * + * @see getConfig() + * @depreceated This function is superseeded by the getConfig() function. + */ + public function copyFromBase($instancename, $filename) + { + assert('is_string($instancename)'); + assert('is_string($filename)'); + assert('$this->filename !== NULL'); + + /* Check if we already have loaded the given config - return the existing instance if we have. */ + if (array_key_exists($instancename, self::$instance)) { + return self::$instance[$instancename]; + } + + $dir = dirname($this->filename); + + self::$instance[$instancename] = self::loadFromFile($dir.'/'.$filename, true); + return self::$instance[$instancename]; + } + + + /** + * Retrieve the current version of simpleSAMLphp. + * + * @return string + */ + public function getVersion() + { + return 'trunk'; + } + + + /** + * Retrieve a 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. This can be set to SimpleSAML_Configuration::REQUIRED_OPTION, which will + * cause an exception to be thrown if the option isn't found. + * + * @return mixed The configuration option with name $name, or $default if the option was not found. + */ + public function getValue($name, $default = null) + { + + /* Return the default value if the option is unset. */ + if (!array_key_exists($name, $this->configuration)) { + if ($default === self::REQUIRED_OPTION) { + throw new Exception($this->location.': Could not retrieve the required option '. + var_export($name, true)); + } + return $default; + } + + return $this->configuration[$name]; + } + + + /** + * Check whether an key in the configuration exists. + * + * @param string $name The key in the configuration to look for. + * + * @return boolean If the value is set in this configuration. + */ + public function hasValue($name) + { + return array_key_exists($name, $this->configuration); + } + + + /** + * 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($names) + { + foreach ($names AS $name) { + if ($this->hasValue($name)) { + return true; + } + } + return false; + } + + + /** + * Retrieve the absolute path of the simpleSAMLphp installation, + * relative to the root of the website. + * + * For example: simplesaml/ + * + * The path will always end with a '/' and never have a leading slash. + * + * @return string The absolute path relative to the root of the website. + */ + public function getBaseURL() + { + $baseURL = $this->getString('baseurlpath', 'simplesaml/'); + + if (preg_match('/^\*(.*)$/D', $baseURL, $matches)) { + /* deprecated behaviour, will be removed in the future */ + return \SimpleSAML\Utils\HTTP::getFirstPathElement(false).$matches[1]; + } + + if (preg_match('#^https?://[^/]*/(.*)$#', $baseURL, $matches)) { + /* we have a full url, we need to strip the path */ + return $matches[1]; + } elseif ($baseURL === '' || $baseURL === '/') { + /* Root directory of site. */ + return ''; + } elseif (preg_match('#^/?([^/]?.*/)#D', $baseURL, $matches)) { + /* local path only */ + return $matches[1]; + } else { + /* invalid format */ + throw new SimpleSAML_Error_Exception('Incorrect format for option \'baseurlpath\'. Value is: "'. + $this->getString('baseurlpath', 'simplesaml/').'". Valid format is in the form'. + ' [(http|https)://(hostname|fqdn)[:port]]/[path/to/simplesaml/].'); + } + } + + + /** + * 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($path) + { + if ($path === null) { + return null; + } + + assert('is_string($path)'); + + /* Prepend path with basedir if it doesn't start with a slash or a Windows + * drive letter (e.g. "C:\"). We assume getBaseDir ends with a slash. + */ + if ($path[0] !== '/' && + !(preg_match('@^[a-z]:[\\\\/]@i', $path, $matches) && is_dir($matches[0])) + ) { + $path = $this->getBaseDir().$path; + } + + /* Remove trailing slashes. */ + while (substr($path, -1) === '/') { + $path = substr($path, 0, -1); + } + + return $path; + } + + + /** + * 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($name, $default = null) + { + + /* Return the default value if the option is unset. */ + if (!array_key_exists($name, $this->configuration)) { + $path = $default; + } else { + $path = $this->configuration[$name]; + } + + if ($path === null) { + return null; + } + + return $this->resolvePath($path).'/'; + } + + + /** + * 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. + */ + public function getBaseDir() + { + /* Check if a directory is configured in the configuration + * file. + */ + $dir = $this->getString('basedir', null); + if ($dir !== null) { + /* Add trailing slash if it is missing. */ + if (substr($dir, -1) !== '/') { + $dir .= '/'; + } + + return $dir; + } + + /* The directory wasn't set in the configuration file. Our + * path is <base directory>/lib/SimpleSAML/Configuration.php + */ + + $dir = __FILE__; + assert('basename($dir) === "Configuration.php"'); + + $dir = dirname($dir); + assert('basename($dir) === "SimpleSAML"'); + + $dir = dirname($dir); + assert('basename($dir) === "lib"'); + + $dir = dirname($dir); + + /* Add trailing slash. */ + $dir .= '/'; + + 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, and no + * default value is given. + * + * @param string $name The name of the option. + * @param mixed $default A default value which will be returned if the option isn't found. The option will be + * required if this parameter isn't given. The default value can be any value, including + * NULL. + * + * @return boolean|mixed The option with the given name, or $default if the option isn't found and $default is + * specified. + */ + public function getBoolean($name, $default = self::REQUIRED_OPTION) + { + assert('is_string($name)'); + + $ret = $this->getValue($name, $default); + + if ($ret === $default) { + /* The option wasn't found, or it matches the default value. In any case, return + * this value. + */ + return $ret; + } + + if (!is_bool($ret)) { + throw new Exception($this->location.': The option '.var_export($name, true). + ' is not a valid boolean value.'); + } + + 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, and no + * default value is given. + * + * @param string $name The name of the option. + * @param mixed $default A default value which will be returned if the option isn't found. The option will be + * required if this parameter isn't given. The default value can be any value, including + * NULL. + * + * @return string|mixed The option with the given name, or $default if the option isn't found and $default is + * specified. + */ + public function getString($name, $default = self::REQUIRED_OPTION) + { + assert('is_string($name)'); + + $ret = $this->getValue($name, $default); + + if ($ret === $default) { + /* The option wasn't found, or it matches the default value. In any case, return + * this value. + */ + return $ret; + } + + if (!is_string($ret)) { + throw new Exception($this->location.': The option '.var_export($name, true). + ' is not a valid string value.'); + } + + return $ret; + } + + + /** + * 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, and no + * default value is given. + * + * @param string $name The name of the option. + * @param mixed $default A default value which will be returned if the option isn't found. The option will be + * required if this parameter isn't given. The default value can be any value, including + * NULL. + * + * @return int|mixed The option with the given name, or $default if the option isn't found and $default is + * specified. + */ + public function getInteger($name, $default = self::REQUIRED_OPTION) + { + assert('is_string($name)'); + + $ret = $this->getValue($name, $default); + + if ($ret === $default) { + /* The option wasn't found, or it matches the default value. In any case, return + * this value. + */ + return $ret; + } + + if (!is_int($ret)) { + throw new Exception($this->location.': The option '.var_export($name, true). + ' is not a valid integer value.'); + } + + return $ret; + } + + + /** + * 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. + * @param mixed $default A default value which will be returned if the option isn't found. The option will be + * required if this parameter isn't given. The default value can be any value, including + * NULL. + * + * @return int|mixed The option with the given name, or $default if the option isn't found and $default is + * specified. + */ + public function getIntegerRange($name, $minimum, $maximum, $default = self::REQUIRED_OPTION) + { + assert('is_string($name)'); + assert('is_int($minimum)'); + assert('is_int($maximum)'); + + $ret = $this->getInteger($name, $default); + + if ($ret === $default) { + /* The option wasn't found, or it matches the default value. In any case, return + * this value. + */ + return $ret; + } + + if ($ret < $minimum || $ret > $maximum) { + throw new Exception($this->location.': Value of option '.var_export($name, true). + ' is out of range. Value is '.$ret.', allowed range is [' + .$minimum.' - '.$maximum.']'); + } + + return $ret; + } + + + /** + * 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 can be mandatory or optional. If no default value is given, it will be considered to be + * mandatory, and an exception will be thrown if it isn't provided. If a default value is given, it + * is considered to be optional, and the default value is returned. 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. If this parameter + * isn't given, the option will be considered to be mandatory. 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 adn $default is given. + */ + public function getValueValidate($name, $allowedValues, $default = self::REQUIRED_OPTION) + { + assert('is_string($name)'); + assert('is_array($allowedValues)'); + + $ret = $this->getValue($name, $default); + if ($ret === $default) { + /* The option wasn't found, or it matches the default value. In any case, return + * this value. + */ + return $ret; + } + + if (!in_array($ret, $allowedValues, true)) { + $strValues = array(); + foreach ($allowedValues as $av) { + $strValues[] = var_export($av, true); + } + $strValues = implode(', ', $strValues); + + throw new Exception($this->location.': Invalid value given for the option '. + var_export($name, true).'. It should have one of the following values: '. + $strValues.'; but it had the following value: '.var_export($ret, true)); + } + + return $ret; + } + + + /** + * 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, and no + * default value is given. + * + * @param string $name The name of the option. + * @param mixed $default A default value which will be returned if the option isn't found. The option will be + * required if this parameter isn't given. 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 specified. + */ + public function getArray($name, $default = self::REQUIRED_OPTION) + { + assert('is_string($name)'); + + $ret = $this->getValue($name, $default); + + if ($ret === $default) { + /* The option wasn't found, or it matches the default value. In any case, return + * this value. + */ + return $ret; + } + + if (!is_array($ret)) { + throw new Exception($this->location.': The option '.var_export($name, true). + ' is not an array.'); + } + + return $ret; + } + + + /** + * 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. + * @param mixed $default A default value which will be returned if the option isn't found. The option will be + * required if this parameter isn't given. The default value can be any value, including + * NULL. + * + * @return array The option with the given name, or $default if the option isn't found and $default is specified. + */ + public function getArrayize($name, $default = self::REQUIRED_OPTION) + { + assert('is_string($name)'); + + $ret = $this->getValue($name, $default); + + if ($ret === $default) { + /* The option wasn't found, or it matches the default value. In any case, return + * this value. + */ + return $ret; + } + + if (!is_array($ret)) { + $ret = array($ret); + } + + 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. + * @param mixed $default A default value which will be returned if the option isn't found. The option will be + * required if this parameter isn't given. The default value can be any value, including + * NULL. + * + * @return array The option with the given name, or $default if the option isn't found and $default is specified. + */ + public function getArrayizeString($name, $default = self::REQUIRED_OPTION) + { + assert('is_string($name)'); + + $ret = $this->getArrayize($name, $default); + + if ($ret === $default) { + /* The option wasn't found, or it matches the default value. In any case, return + * this value. + */ + return $ret; + } + + foreach ($ret as $value) { + if (!is_string($value)) { + throw new Exception($this->location.': The option '.var_export($name, true). + ' must be a string or an array of strings.'); + } + } + + 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, and no default value is given. + * + * @param string $name The name of the option. + * @param mixed $default A default value which will be returned if the option isn't found. The option will be + * required if this parameter isn't given. 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 specified. + */ + public function getConfigItem($name, $default = self::REQUIRED_OPTION) + { + assert('is_string($name)'); + + $ret = $this->getValue($name, $default); + + if ($ret === $default) { + /* The option wasn't found, or it matches the default value. In any case, return + * this value. + */ + return $ret; + } + + if (!is_array($ret)) { + throw new Exception($this->location.': The option '.var_export($name, true). + ' is not an array.'); + } + + return self::loadFromArray($ret, $this->location.'['.var_export($name, true).']'); + } + + + /** + * Retrieve an array of arrays as an array of SimpleSAML_Configuration objects. + * + * This function will retrieve an option containing an array of arrays, and create an + * array of SimpleSAML_Configuration objects from that array. The indexes in the new + * array will be the same as the original indexes, but the values will be + * SimpleSAML_Configuration objects. + * + * An exception will be thrown if this option isn't an array of arrays, or if this option + * isn't found, and no default value is given. + * + * @param string $name The name of the option. + * @param string $location Name of the items in the array. + * @param mixed $default A default value which will be returned if the option isn't found. The option will be + * required if this parameter isn't given. 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 specified. + */ + public function getConfigList($name, $default = self::REQUIRED_OPTION) + { + assert('is_string($name)'); + + $ret = $this->getValue($name, $default); + + if ($ret === $default) { + /* The option wasn't found, or it matches the default value. In any case, return + * this value. + */ + return $ret; + } + + if (!is_array($ret)) { + throw new Exception($this->location.': The option '.var_export($name, true). + ' is not an array.'); + } + + $out = array(); + foreach ($ret as $index => $config) { + $newLoc = $this->location.'['.var_export($name, true).']['. + var_export($index, true).']'; + if (!is_array($config)) { + throw new Exception($newLoc.': The value of this element was expected to be an array.'); + } + $out[$index] = self::loadFromArray($config, $newLoc); + } + + return $out; + } + + + /** + * 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 array Name of all options defined in this configuration file. + */ + public function getOptions() + { + + return array_keys($this->configuration); + } + + + /** + * Convert this configuration object back to an array. + * + * @return array An associative array with all configuration options and values. + */ + public function toArray() + { + + 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. + */ + private function getDefaultBinding($endpointType) + { + assert('is_string($endpointType)'); + + $set = $this->getString('metadata-set'); + switch ($set.':'.$endpointType) { + case 'saml20-idp-remote:SingleSignOnService': + case 'saml20-idp-remote:SingleLogoutService': + case 'saml20-sp-remote:SingleLogoutService': + return SAML2_Const::BINDING_HTTP_REDIRECT; + case 'saml20-sp-remote:AssertionConsumerService': + return SAML2_Const::BINDING_HTTP_POST; + case 'saml20-idp-remote:ArtifactResolutionService': + return SAML2_Const::BINDING_SOAP; + case 'shib13-idp-remote:SingleSignOnService': + return 'urn:mace:shibboleth:1.0:profiles:AuthnRequest'; + case 'shib13-sp-remote:AssertionConsumerService': + return 'urn:oasis:names:tc:SAML:1.0:profiles:browser-post'; + default: + 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. + */ + public function getEndpoints($endpointType) + { + assert('is_string($endpointType)'); + + $loc = $this->location.'['.var_export($endpointType, true).']:'; + + if (!array_key_exists($endpointType, $this->configuration)) { + /* No endpoints of the given type. */ + return array(); + } + + + $eps = $this->configuration[$endpointType]; + if (is_string($eps)) { + /* For backwards-compatibility. */ + $eps = array($eps); + } elseif (!is_array($eps)) { + throw new Exception($loc.': Expected array or string.'); + } + + + foreach ($eps as $i => &$ep) { + $iloc = $loc.'['.var_export($i, true).']'; + + if (is_string($ep)) { + /* For backwards-compatibility. */ + $ep = array( + 'Location' => $ep, + 'Binding' => $this->getDefaultBinding($endpointType), + ); + $responseLocation = $this->getString($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 array $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 array|null The default endpoint, or NULL if no acceptable endpoints are used. + */ + public function getEndpointPrioritizedByBinding($endpointType, array $bindings, $default = self::REQUIRED_OPTION) + { + assert('is_string($endpointType)'); + + $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 array $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 array|null The default endpoint, or NULL if no acceptable endpoints are used. + */ + public function getDefaultEndpoint($endpointType, array $bindings = null, $default = self::REQUIRED_OPTION) + { + assert('is_string($endpointType)'); + + $endpoints = $this->getEndpoints($endpointType); + + $defaultEndpoint = \SimpleSAML\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. + * @param mixed $default The default value. If no default is given, and the option isn't found, an exception will + * be thrown. + * + * @return array Associative array with language=>string pairs. + */ + public function getLocalizedString($name, $default = self::REQUIRED_OPTION) + { + assert('is_string($name)'); + + $ret = $this->getValue($name, $default); + if ($ret === $default) { + /* The option wasn't found, or it matches the default value. In any case, return + * this value. + */ + return $ret; + } + + $loc = $this->location.'['.var_export($name, true).']'; + + if (is_string($ret)) { + $ret = array('en' => $ret,); + } + + if (!is_array($ret)) { + throw new Exception($loc.': Must be an array or a string.'); + } + + foreach ($ret as $k => $v) { + if (!is_string($k)) { + throw new Exception($loc.': Invalid language code: '.var_export($k, true)); + } + if (!is_string($v)) { + throw new Exception($loc.'['.var_export($v, true).']: Must be a string.'); + } + } + + return $ret; + } + + + /** + * 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|null Public key data, or NULL if no public key or was found. + */ + public function getPublicKeys($use = null, $required = false, $prefix = '') + { + assert('is_bool($required)'); + assert('is_string($prefix)'); + + if ($this->hasValue($prefix.'keys')) { + $ret = array(); + foreach ($this->getArray($prefix.'keys') as $key) { + if ($use !== null && isset($key[$use]) && !$key[$use]) { + continue; + } + if (isset($key['X509Certificate'])) { + /* Strip whitespace from key. */ + $key['X509Certificate'] = preg_replace('/\s+/', '', $key['X509Certificate']); + } + $ret[] = $key; + } + if (!empty($ret)) { + return $ret; + } + } elseif ($this->hasValue($prefix.'certData')) { + $certData = $this->getString($prefix.'certData'); + $certData = preg_replace('/\s+/', '', $certData); + return array( + array( + 'encryption' => true, + 'signing' => true, + 'type' => 'X509Certificate', + 'X509Certificate' => $certData, + ), + ); + } elseif ($this->hasValue($prefix.'certificate')) { + $file = $this->getString($prefix.'certificate'); + $file = \SimpleSAML\Utils\Config::getCertPath($file); + $data = @file_get_contents($file); + + if ($data === false) { + throw new Exception($this->location.': Unable to load certificate/public key from file "'.$file.'".'); + } + + /* Extract certificate data (if this is a certificate). */ + $pattern = '/^-----BEGIN CERTIFICATE-----([^-]*)^-----END CERTIFICATE-----/m'; + if (!preg_match($pattern, $data, $matches)) { + throw new SimpleSAML_Error_Exception( + $this->location.': Could not find PEM encoded certificate in "'.$file.'".' + ); + } + $certData = preg_replace('/\s+/', '', $matches[1]); + + return array( + array( + 'encryption' => true, + 'signing' => true, + 'type' => 'X509Certificate', + 'X509Certificate' => $certData, + ), + ); + } + + if ($required) { + throw new SimpleSAML_Error_Exception($this->location.': Missing certificate in metadata.'); + } else { + return null; + } + } } -- GitLab