diff --git a/docs/simplesamlphp-upgrade-notes-2.0.md b/docs/simplesamlphp-upgrade-notes-2.0.md index f83e997826e72b4eeffe60f9d466603e20387d0d..1e9be1b1d5cb04153d3bdbecd2b12cdca6f13e0a 100644 --- a/docs/simplesamlphp-upgrade-notes-2.0.md +++ b/docs/simplesamlphp-upgrade-notes-2.0.md @@ -96,3 +96,17 @@ processing filters or interface with the SimpleSAMLphp development API. - lib/SimpleSAML/Store/Memcache.php has been renamed to lib/SimpleSAML/Store/MemcacheStore.php - lib/SimpleSAML/Store/Redis.php has been renamed to lib/SimpleSAML/Store/RedisStore.php +- The following methods have had their signature changed: + - Configuration::getValue + - Configuration::getBoolean + - Configuration::getString + - Configuration::getInteger + - Configuration::getIntegerRange + - Configuration::getValueValidate + - Configuration::getArray + - Configuration::getArrayize + - Configuration::getArrayizeString + - Configuration::getConfigItem + - Configuration::getLocalizedString + + All of these methods no longer accept a default as their last parameter. Use their getOptional* counterparts instead. diff --git a/lib/SimpleSAML/Auth/ProcessingChain.php b/lib/SimpleSAML/Auth/ProcessingChain.php index eeba0d6b8f6a3b3d1889b64a19491b9c73db3ca2..1de3dbb43b5d922171e939d7bdc94a16972c32dd 100644 --- a/lib/SimpleSAML/Auth/ProcessingChain.php +++ b/lib/SimpleSAML/Auth/ProcessingChain.php @@ -61,7 +61,7 @@ class ProcessingChain public function __construct(array $idpMetadata, array $spMetadata, string $mode = 'idp') { $config = Configuration::getInstance(); - $configauthproc = $config->getArray('authproc.' . $mode, null); + $configauthproc = $config->getOptionalArray('authproc.' . $mode, null); if (!empty($configauthproc)) { $configfilters = self::parseFilterList($configauthproc); diff --git a/lib/SimpleSAML/Auth/Simple.php b/lib/SimpleSAML/Auth/Simple.php index ca806d3759652af6fc2caa80233349612daa9efa..350901fbe87f079ef7c359c2bf586d4095bfd157 100644 --- a/lib/SimpleSAML/Auth/Simple.php +++ b/lib/SimpleSAML/Auth/Simple.php @@ -46,8 +46,7 @@ class Simple $config = Configuration::getInstance(); } $this->authSource = $authSource; - /** @psalm-var \SimpleSAML\Configuration $this->app_config */ - $this->app_config = $config->getConfigItem('application'); + $this->app_config = $config->getOptionalConfigItem('application', []); if ($session === null) { $session = Session::getSessionFromRequest(); @@ -385,7 +384,7 @@ class Simple $port = ''; } - $base = trim($this->app_config->getString( + $base = trim($this->app_config->getOptionalString( 'baseURL', $scheme . '://' . $host . $port ), '/'); diff --git a/lib/SimpleSAML/Auth/Source.php b/lib/SimpleSAML/Auth/Source.php index 79fec9e021c594b0b0d8e72f0575c45de7be22ad..1b54babb9f0e2e9be2d20defd36a48d674c99a0c 100644 --- a/lib/SimpleSAML/Auth/Source.php +++ b/lib/SimpleSAML/Auth/Source.php @@ -344,7 +344,7 @@ abstract class Source // for now - load and parse config file $config = Configuration::getConfig('authsources.php'); - $authConfig = $config->getArray($authId, null); + $authConfig = $config->getOptionalArray($authId, null); if ($authConfig === null) { if ($type !== null) { throw new Error\Exception( diff --git a/lib/SimpleSAML/Auth/State.php b/lib/SimpleSAML/Auth/State.php index b43f48f1b1b7242adb1e824aeab6e04e84c03642..69544cf8f099bafce2b7d3f8fcb24633a0acef03 100644 --- a/lib/SimpleSAML/Auth/State.php +++ b/lib/SimpleSAML/Auth/State.php @@ -181,7 +181,7 @@ class State { if (self::$stateTimeout === null) { $globalConfig = Configuration::getInstance(); - self::$stateTimeout = $globalConfig->getInteger('session.state.timeout', 60 * 60); + self::$stateTimeout = $globalConfig->getOptionalInteger('session.state.timeout', 60 * 60); } return self::$stateTimeout; diff --git a/lib/SimpleSAML/Configuration.php b/lib/SimpleSAML/Configuration.php index 2f07bd17e370ff13951a02dce6aaf367d27f3739..54a6d9edbd0002e4735a6c58cda9942476e1eb6f 100644 --- a/lib/SimpleSAML/Configuration.php +++ b/lib/SimpleSAML/Configuration.php @@ -6,6 +6,7 @@ namespace SimpleSAML; use SAML2\Constants; use SimpleSAML\Assert\Assert; +use SimpleSAML\Assert\AssertionFailedException; use SimpleSAML\Error; use SimpleSAML\Utils; @@ -348,29 +349,41 @@ class Configuration implements Utils\ClearableState /** * 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. + * @param string $name Name of the configuration option. + * @return mixed The configuration option with name $name. * - * @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 getValue(string $name) + { + 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 \Exception If the required option cannot be retrieved. + * @throws \SimpleSAML\Assert\AssertionFailedException If the required option cannot be retrieved. */ - public function getValue(string $name, $default = null) + public function getOptionalValue(string $name, $default) { // 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) - ); - } + if (!$this->hasValue($name)) { return $default; } - return $this->configuration[$name]; + return $this->configuration[$name] ?? $default; } @@ -416,7 +429,7 @@ class Configuration implements Utils\ClearableState */ public function getBasePath(): string { - $baseURL = $this->getString('baseurlpath', 'simplesaml/'); + $baseURL = $this->getOptionalString('baseurlpath', 'simplesaml/'); if (preg_match('#^https?://[^/]*(?:/(.+/?)?)?$#', $baseURL, $matches)) { // we have a full url, we need to strip the path @@ -441,7 +454,7 @@ class Configuration implements Utils\ClearableState $c['baseurlpath'] = $httpUtils->guessBasePath(); throw new Error\CriticalConfigurationError( 'Incorrect format for option \'baseurlpath\'. Value is: "' . - $this->getString('baseurlpath', 'simplesaml/') . '". Valid format is in the form' . + $this->getOptionalString('baseurlpath', 'simplesaml/') . '". Valid format is in the form' . ' [(http|https)://(hostname|fqdn)[:port]]/[path/to/simplesaml/].', $this->filename, $c @@ -515,7 +528,7 @@ class Configuration implements Utils\ClearableState public function getBaseDir(): string { // check if a directory is configured in the configuration file - $dir = $this->getString('basedir', null); + $dir = $this->getOptionalString('basedir', null); if ($dir !== null) { // add trailing slash if it is missing if (substr($dir, -1) !== DIRECTORY_SEPARATOR) { @@ -547,34 +560,52 @@ class Configuration implements Utils\ClearableState /** * 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. + * 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. - * @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. + * @param string $name The name of the option. + * @return boolean The option with the given name. * - * @return boolean|mixed The option with the given name, or $default if the option isn't found and $default is - * specified. - * - * @throws \Exception If the option is not boolean. + * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not boolean. */ - public function getBoolean(string $name, $default = self::REQUIRED_OPTION) + public function getBoolean(string $name): bool { - $ret = $this->getValue($name, $default); + $ret = $this->getValue($name); - if ($ret === $default) { - // the option wasn't found, or it matches the default value. In any case, return this value - return $ret; - } + Assert::boolean( + $ret, + sprintf('%s: The option %s is not a valid boolean value.', $this->location, var_export($name, true)), + ); + + return $ret; + } - if (!is_bool($ret)) { - throw new \Exception( - $this->location . ': The option ' . var_export($name, true) . - ' is not a valid boolean value.' - ); - } + + /** + * 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 null ? null : bool) + * + * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not boolean. + */ + public function getOptionalBoolean(string $name, ?bool $default): ?bool + { + $ret = $this->getOptionalValue($name, $default); + + Assert::nullOrBoolean( + $ret, + sprintf( + '%s: The option %s is not a valid boolean value or null.', + $this->location, + var_export($name, true) + ), + ); return $ret; } @@ -583,34 +614,52 @@ class Configuration implements Utils\ClearableState /** * 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. + * 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. - * @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. + * @param string $name The name of the option. + * @return string The option with the given name. * - * @return string|mixed The option with the given name, or $default if the option isn't found and $default is - * specified. - * - * @throws \Exception If the option is not a string. + * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not a string. */ - public function getString(string $name, $default = self::REQUIRED_OPTION) + public function getString(string $name): string { - $ret = $this->getValue($name, $default); + $ret = $this->getValue($name); - if ($ret === $default) { - // the option wasn't found, or it matches the default value. In any case, return this value - return $ret; - } + Assert::string( + $ret, + sprintf('%s: The option %s is not a valid string value.', $this->location, var_export($name, true)), + ); + + return $ret; + } - if (!is_string($ret)) { - throw new \Exception( - $this->location . ': The option ' . var_export($name, true) . - ' is not a valid string value.' - ); - } + + /** + * 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 null ? null : string) + * + * @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) + ), + ); return $ret; } @@ -619,34 +668,52 @@ class Configuration implements Utils\ClearableState /** * 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. + * An exception will be thrown if this option isn't an integer, or if this option isn't found. * - * @return int|mixed The option with the given name, or $default if the option isn't found and $default is - * specified. + * @param string $name The name of the option. + * @return int The option with the given name. * - * @throws \Exception If the option is not an integer. + * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not an integer. */ - public function getInteger(string $name, $default = self::REQUIRED_OPTION) + public function getInteger(string $name): int { - $ret = $this->getValue($name, $default); + $ret = $this->getValue($name); - if ($ret === $default) { - // the option wasn't found, or it matches the default value. In any case, return this value - return $ret; - } + Assert::integer( + $ret, + sprintf('%s: The option %s is not a valid integer value.', $this->location, var_export($name, true)), + ); + + return $ret; + } - if (!is_int($ret)) { - throw new \Exception( - $this->location . ': The option ' . var_export($name, true) . - ' is not a valid integer value.' - ); - } + + /** + * 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 mixed $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 null ? null : int) + * + * @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) + ), + ); return $ret; } @@ -663,31 +730,63 @@ class Configuration implements Utils\ClearableState * @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. + * @return int The option with the given name. * - * @throws \Exception If the option is not in the range specified. + * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not in the range specified. */ - public function getIntegerRange(string $name, int $minimum, int $maximum, $default = self::REQUIRED_OPTION) + public function getIntegerRange(string $name, int $minimum, int $maximum): int { - $ret = $this->getInteger($name, $default); + $ret = $this->getInteger($name); + + 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), + ), + ); - if ($ret === $default) { - // the option wasn't found, or it matches the default value. In any case, return this value - return $ret; - } + 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 . ']' - ); - } + + /** + * 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 null ? null : int) + * + * @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), + ), + ); return $ret; } @@ -699,42 +798,63 @@ class Configuration implements Utils\ClearableState * 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. + * 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. - * @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. + * @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, or $default if the option isn't found and $default is given. + * @return mixed The option with the given name. * - * @throws \Exception If the option does not have any of the allowed values. + * @throws \SimpleSAML\Assert\AssertionFailedException If the option does not have any of the allowed values. */ - public function getValueValidate(string $name, array $allowedValues, $default = self::REQUIRED_OPTION) + public function getValueValidate(string $name, 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; - } + $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), + ), + ); - if (!in_array($ret, $allowedValues, true)) { - $strValues = []; - foreach ($allowedValues as $av) { - $strValues[] = var_export($av, true); - } - $strValues = implode(', ', $strValues); + return $ret; + } - 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) - ); - } + + /** + * 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, $default) + { + $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), + ), + ); return $ret; } @@ -743,31 +863,49 @@ class Configuration implements Utils\ClearableState /** * 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. + * 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 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. * - * @return array|mixed The option with the given name, or $default if the option isn't found and $default is + * @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 * specified. + * @psalm-return ($default is null ? null : array) * - * @throws \Exception If the option is not an array. + * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not an array. */ - public function getArray(string $name, $default = self::REQUIRED_OPTION) + public function getOptionalArray(string $name, ?array $default): ?array { - $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; - } + $ret = $this->getOptionalValue($name, $default); - if (!is_array($ret)) { - throw new \Exception($this->location . ': The option ' . var_export($name, true) . ' is not an array.'); - } + Assert::nullOrIsArray( + $ret, + sprintf('%s: The option %s is not an array or null.', $this->location, var_export($name, true)), + ); return $ret; } @@ -779,21 +917,37 @@ class Configuration implements Utils\ClearableState * 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 mixed The option with the given name, or $default if the option isn't found and $default is specified. + * @return array The option with the given name. */ - public function getArrayize(string $name, $default = self::REQUIRED_OPTION) + public function getArrayize(string $name): array { - $ret = $this->getValue($name, $default); + $ret = $this->getValue($name); - 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 = [$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 ? null : array) + */ + public function getOptionalArrayize(string $name, $default): ?array + { + $ret = $this->getOptionalValue($name, $default); + if (!is_array($ret)) { $ret = [$ret]; } @@ -808,31 +962,53 @@ class Configuration implements Utils\ClearableState * 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 mixed The option with the given name, or $default if the option isn't found and $default is specified. + * @return string[] The option with the given name. * - * @throws \Exception If the option is not a string or an array of strings. + * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not a string or an array of strings. */ - public function getArrayizeString(string $name, $default = self::REQUIRED_OPTION) + public function getArrayizeString(string $name): array { - $ret = $this->getArrayize($name, $default); + $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), + ), + ); - if ($ret === $default) { - // the option wasn't found, or it matches the default value. In any case, return this value - return $ret; - } + 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.' - ); - } - } + + /** + * 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 ? 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 + { + $ret = $this->getOptionalArrayize($name, $default); + + 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; } @@ -841,41 +1017,49 @@ class Configuration implements Utils\ClearableState /** * 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. + * 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. + * 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 \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. + * 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 if the option isn't found and $default is specified. + * or $default, converted into a Configuration object. + * @psalm-return ($default is null ? null : \SimpleSAML\Configuration) * - * @throws \Exception If the option is not an array. + * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not an array. */ - public function getConfigItem(string $name, $default = []): ?Configuration + public function getOptionalConfigItem(string $name, ?array $default): ?Configuration { - $ret = $this->getValue($name, $default); + $ret = $this->getOptionalArray($name, $default); - if ($ret === null) { - // the option wasn't found, or it is explicitly null - // do not instantiate a new Configuration instance, but just return null - return null; - } - - 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) . ']'); + return ($ret === null) ? null : self::loadFromArray($ret, $this->location . '[' . var_export($name, true) . ']'); } @@ -971,7 +1155,7 @@ class Configuration implements Utils\ClearableState 'Location' => $ep, 'Binding' => $this->getDefaultBinding($endpointType), ]; - $responseLocation = $this->getString($endpointType . 'Response', null); + $responseLocation = $this->getOptionalString($endpointType . 'Response', null); if ($responseLocation !== null) { $ep['ResponseLocation'] = $responseLocation; } @@ -1082,44 +1266,57 @@ class Configuration implements Utils\ClearableState * 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. + * @param array $default The default value. * - * @return mixed Associative array with language => string pairs, or the provided default value. + * @return array Associative array with language => string pairs. * - * @throws \Exception If the translation is not an array or a string, or its index or value are not strings. + * @throws \SimpleSAML\Assert\AssertionFailedException + * If the translation is not an array or a string, or its index or value are not strings. */ - public function getLocalizedString(string $name, $default = self::REQUIRED_OPTION) + public function getLocalizedString(string $name): array { - $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) . ']'; + $ret = $this->getValue($name); if (is_string($ret)) { $ret = ['en' => $ret]; } - if (!is_array($ret)) { - throw new \Exception($loc . ': Must be an array or a string.'); - } + Assert::isArray($ret, sprintf('%s: Must be an array or a string.', $this->location)); 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.'); - } + 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 mixed $default The default value. + * + * @return array|null Associative array with language => string pairs, or the provided default value. + * @psalm-return ($default is null ? null : array) + * + * @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. * @@ -1153,7 +1350,7 @@ class Configuration implements Utils\ClearableState } elseif ($this->hasValue($prefix . 'certData')) { $certData = $this->getString($prefix . 'certData'); $certData = preg_replace('/\s+/', '', $certData); - $keyName = $this->getString($prefix . 'key_name', null); + $keyName = $this->getOptionalString($prefix . 'key_name', null); return [ [ 'name' => $keyName, @@ -1184,7 +1381,7 @@ class Configuration implements Utils\ClearableState ); } $certData = preg_replace('/\s+/', '', $matches[1]); - $keyName = $this->getString($prefix . 'key_name', null); + $keyName = $this->getOptionalString($prefix . 'key_name', null); return [ [ diff --git a/lib/SimpleSAML/Database.php b/lib/SimpleSAML/Database.php index 01bd51eea20a2d12033d550b1882c27d04bad2f0..239b88eb69a7074edc45703ce0f770cc6c08cbed 100644 --- a/lib/SimpleSAML/Database.php +++ b/lib/SimpleSAML/Database.php @@ -84,28 +84,28 @@ class Database */ private function __construct(Configuration $config) { - $driverOptions = $config->getArray('database.driver_options', []); - if ($config->getBoolean('database.persistent', true)) { + $driverOptions = $config->getOptionalArray('database.driver_options', []); + if ($config->getOptionalBoolean('database.persistent', true)) { $driverOptions[PDO::ATTR_PERSISTENT] = true; } // connect to the primary $this->dbPrimary = $this->connect( $config->getString('database.dsn'), - $config->getString('database.username', null), - $config->getString('database.password', null), + $config->getOptionalString('database.username', null), + $config->getOptionalString('database.password', null), $driverOptions ); // TODO: deprecated: the "database.slave" terminology is preserved here for backwards compatibility. - if ($config->getArray('database.slaves', null) !== null) { + if ($config->getOptionalArray('database.slaves', null) !== null) { Logger::warning( 'The "database.slaves" config option is deprecated. ' . 'Please update your configuration to use "database.secondaries".' ); } // connect to any configured secondaries, preserving legacy config option - $secondaries = $config->getArray('database.secondaries', $config->getArray('database.slaves', [])); + $secondaries = $config->getOptionalArray('database.secondaries', $config->getOptionalArray('database.slaves', [])); foreach ($secondaries as $secondary) { array_push( $this->dbSecondaries, @@ -117,7 +117,7 @@ class Database ) ); } - $this->tablePrefix = $config->getString('database.prefix', ''); + $this->tablePrefix = $config->getOptionalString('database.prefix', ''); } @@ -133,13 +133,14 @@ class Database $assembledConfig = [ 'primary' => [ 'database.dsn' => $config->getString('database.dsn'), - 'database.username' => $config->getString('database.username', null), - 'database.password' => $config->getString('database.password', null), - 'database.prefix' => $config->getString('database.prefix', ''), - 'database.persistent' => $config->getBoolean('database.persistent', true), + 'database.username' => $config->getOptionalString('database.username', null), + 'database.password' => $config->getOptionalString('database.password', null), + 'database.prefix' => $config->getOptionalString('database.prefix', ''), + 'database.persistent' => $config->getOptionalBoolean('database.persistent', true), ], + // TODO: deprecated: the "database.slave" terminology is preserved here for backwards compatibility. - 'secondaries' => $config->getArray('database.secondaries', $config->getArray('database.slaves', [])), + 'secondaries' => $config->getOptionalArray('database.secondaries', $config->getOptionalArray('database.slaves', [])), ]; return sha1(serialize($assembledConfig)); diff --git a/lib/SimpleSAML/Error/Error.php b/lib/SimpleSAML/Error/Error.php index f5a4b2d284e9cfc2424a980e01a8586346b7100a..bce003aa70e2f1088811f8bbe738759c7f563679 100644 --- a/lib/SimpleSAML/Error/Error.php +++ b/lib/SimpleSAML/Error/Error.php @@ -230,7 +230,7 @@ class Error extends Exception $config = Configuration::getInstance(); $data = []; - $data['showerrors'] = $config->getBoolean('showerrors', true); + $data['showerrors'] = $config->getOptionalBoolean('showerrors', true); $data['error'] = $errorData; $data['errorCode'] = $this->errorCode; $data['parameters'] = $this->parameters; @@ -242,8 +242,8 @@ class Error extends Exception // check if there is a valid technical contact email address if ( - $config->getBoolean('errorreporting', true) - && $config->getString('technicalcontact_email', 'na@example.org') !== 'na@example.org' + $config->getOptionalBoolean('errorreporting', true) + && $config->getOptionalString('technicalcontact_email', 'na@example.org') !== 'na@example.org' ) { // enable error reporting $httpUtils = new Utils\HTTP(); @@ -262,7 +262,7 @@ class Error extends Exception } } - $show_function = $config->getArray('errors.show_function', null); + $show_function = $config->getOptionalArray('errors.show_function', null); if (isset($show_function)) { Assert::isCallable($show_function); $this->setHTTPCode(); diff --git a/lib/SimpleSAML/Error/Exception.php b/lib/SimpleSAML/Error/Exception.php index 40c5569941c7974d7372af2217598b0588e3ec8c..e75fbbab37600cf2d030ad04fd173dc150f6e8e5 100644 --- a/lib/SimpleSAML/Error/Exception.php +++ b/lib/SimpleSAML/Error/Exception.php @@ -200,7 +200,7 @@ class Exception extends \Exception protected function logBacktrace(int $level = Logger::DEBUG): void { // Do nothing if backtraces have been disabled in config. - $debug = Configuration::getInstance()->getArray('debug', ['backtraces' => true]); + $debug = Configuration::getInstance()->getOptionalArray('debug', ['backtraces' => true]); if (array_key_exists('backtraces', $debug) && $debug['backtraces'] === false) { return; } diff --git a/lib/SimpleSAML/IdP.php b/lib/SimpleSAML/IdP.php index 884d934306429549408cdf587e362709f62bf6c5..59fc0de1c9810f988bcccd7c17cae4b542230748 100644 --- a/lib/SimpleSAML/IdP.php +++ b/lib/SimpleSAML/IdP.php @@ -81,12 +81,12 @@ class IdP $globalConfig = Configuration::getInstance(); if (substr($id, 0, 6) === 'saml2:') { - if (!$globalConfig->getBoolean('enable.saml20-idp', false)) { + if (!$globalConfig->getOptionalBoolean('enable.saml20-idp', false)) { throw new Error\Exception('enable.saml20-idp disabled in config.php.'); } $this->config = $metadata->getMetaDataConfig(substr($id, 6), 'saml20-idp-hosted'); } elseif (substr($id, 0, 5) === 'adfs:') { - if (!$globalConfig->getBoolean('enable.adfs-idp', false)) { + if (!$globalConfig->getOptionalBoolean('enable.adfs-idp', false)) { throw new Error\Exception('enable.adfs-idp disabled in config.php.'); } $this->config = $metadata->getMetaDataConfig(substr($id, 5), 'adfs-idp-hosted'); @@ -423,7 +423,7 @@ class IdP public function getLogoutHandler(): LogoutHandlerInterface { // find the logout handler - $logouttype = $this->getConfig()->getString('logouttype', 'traditional'); + $logouttype = $this->getConfig()->getOptionalString('logouttype', 'traditional'); switch ($logouttype) { case 'traditional': $handler = TraditionalLogoutHandler::class; diff --git a/lib/SimpleSAML/Locale/Language.php b/lib/SimpleSAML/Locale/Language.php index 3ae24285f5ec4c47b0ac774a71a4a3fe07cb5eab..da982f670b9b2e8984aa834531fc2ac80c8fa864 100644 --- a/lib/SimpleSAML/Locale/Language.php +++ b/lib/SimpleSAML/Locale/Language.php @@ -153,14 +153,14 @@ class Language { $this->configuration = $configuration; $this->availableLanguages = $this->getInstalledLanguages(); - $this->defaultLanguage = $this->configuration->getString('language.default', self::FALLBACKLANGUAGE); - $this->languageParameterName = $this->configuration->getString('language.parameter.name', 'language'); - $this->customFunction = $this->configuration->getArray('language.get_language_function', null); - $this->rtlLanguages = $this->configuration->getArray('language.rtl', []); + $this->defaultLanguage = $this->configuration->getOptionalString('language.default', self::FALLBACKLANGUAGE); + $this->languageParameterName = $this->configuration->getOptionalString('language.parameter.name', 'language'); + $this->customFunction = $this->configuration->getOptionalArray('language.get_language_function', null); + $this->rtlLanguages = $this->configuration->getOptionalArray('language.rtl', []); if (isset($_GET[$this->languageParameterName])) { $this->setLanguage( $_GET[$this->languageParameterName], - $this->configuration->getBoolean('language.parameter.setcookie', true) + $this->configuration->getOptionalBoolean('language.parameter.setcookie', true) ); } } @@ -173,7 +173,10 @@ class Language */ private function getInstalledLanguages(): array { - $configuredAvailableLanguages = $this->configuration->getArray('language.available', [self::FALLBACKLANGUAGE]); + $configuredAvailableLanguages = $this->configuration->getOptionalArray( + 'language.available', + [self::FALLBACKLANGUAGE] + ); $availableLanguages = []; foreach ($configuredAvailableLanguages as $code) { if (array_key_exists($code, self::$language_names) && isset(self::$language_names[$code])) { @@ -403,8 +406,8 @@ class Language public static function getLanguageCookie(): ?string { $config = Configuration::getInstance(); - $availableLanguages = $config->getArray('language.available', [self::FALLBACKLANGUAGE]); - $name = $config->getString('language.cookie.name', 'language'); + $availableLanguages = $config->getOptionalArray('language.available', [self::FALLBACKLANGUAGE]); + $name = $config->getOptionalString('language.cookie.name', 'language'); if (isset($_COOKIE[$name])) { $language = strtolower((string) $_COOKIE[$name]); @@ -426,20 +429,20 @@ class Language { $language = strtolower($language); $config = Configuration::getInstance(); - $availableLanguages = $config->getArray('language.available', [self::FALLBACKLANGUAGE]); + $availableLanguages = $config->getOptionalArray('language.available', [self::FALLBACKLANGUAGE]); if (!in_array($language, $availableLanguages, true) || headers_sent()) { return; } - $name = $config->getString('language.cookie.name', 'language'); + $name = $config->getOptionalString('language.cookie.name', 'language'); $params = [ - 'lifetime' => ($config->getInteger('language.cookie.lifetime', 60 * 60 * 24 * 900)), - 'domain' => ($config->getString('language.cookie.domain', '')), - 'path' => ($config->getString('language.cookie.path', '/')), - 'secure' => ($config->getBoolean('language.cookie.secure', false)), - 'httponly' => ($config->getBoolean('language.cookie.httponly', false)), - 'samesite' => ($config->getString('language.cookie.samesite', null)), + 'lifetime' => ($config->getOptionalInteger('language.cookie.lifetime', 60 * 60 * 24 * 900)), + 'domain' => ($config->getOptionalString('language.cookie.domain', '')), + 'path' => ($config->getOptionalString('language.cookie.path', '/')), + 'secure' => ($config->getOptionalBoolean('language.cookie.secure', false)), + 'httponly' => ($config->getOptionalBoolean('language.cookie.httponly', false)), + 'samesite' => ($config->getOptionalString('language.cookie.samesite', null)), ]; $httpUtils = new Utils\HTTP(); diff --git a/lib/SimpleSAML/Locale/Localization.php b/lib/SimpleSAML/Locale/Localization.php index d88127b04580fdda9fd63fe67d3ccfc052e230ab..1dbb5c1f6ac6af03606ea878393755d3840308c8 100644 --- a/lib/SimpleSAML/Locale/Localization.php +++ b/lib/SimpleSAML/Locale/Localization.php @@ -268,7 +268,7 @@ class Localization { $this->addDomain($this->localeDir, 'attributes'); - list($theme,) = explode(':', $this->configuration->getString('theme.use', 'default')); + list($theme,) = explode(':', $this->configuration->getOptionalString('theme.use', 'default')); if ($theme !== 'default') { $this->addModuleDomain($theme, null, 'attributes'); } diff --git a/lib/SimpleSAML/Logger.php b/lib/SimpleSAML/Logger.php index c390ca82e94d2194bd95886ad619cd2d02d1be6e..15575a58e071525cc931eb4b3f380a8034dda92b 100644 --- a/lib/SimpleSAML/Logger.php +++ b/lib/SimpleSAML/Logger.php @@ -449,11 +449,11 @@ class Logger $config = Configuration::getInstance(); // setting minimum log_level - self::$logLevel = $config->getInteger('logging.level', self::INFO); + self::$logLevel = $config->getOptionalInteger('logging.level', self::INFO); // get the metadata handler option from the configuration if (is_null($handler)) { - $handler = $config->getString( + $handler = $config->getOptionalString( 'logging.handler', php_sapi_name() === 'cli' || defined('STDIN') ? 'stderr' : 'syslog' ); @@ -473,7 +473,7 @@ class Logger $handler = $known_handlers[$handler]; } - self::$format = $config->getString('logging.format', self::$format); + self::$format = $config->getOptionalString('logging.format', self::$format); try { /** @var \SimpleSAML\Logger\LoggingHandlerInterface */ diff --git a/lib/SimpleSAML/Logger/ErrorLogLoggingHandler.php b/lib/SimpleSAML/Logger/ErrorLogLoggingHandler.php index 0ccf91d2354f3bb1262f4baabf50dbc209948c48..9c989d725e33e2dfc3d61482b2e620356d226b86 100644 --- a/lib/SimpleSAML/Logger/ErrorLogLoggingHandler.php +++ b/lib/SimpleSAML/Logger/ErrorLogLoggingHandler.php @@ -49,7 +49,7 @@ class ErrorLogLoggingHandler implements LoggingHandlerInterface $this->processname = preg_replace( '/[\x00-\x1F\x7F\xA0]/u', '', - $config->getString('logging.processname', 'SimpleSAMLphp') + $config->getOptionalString('logging.processname', 'SimpleSAMLphp') ); } diff --git a/lib/SimpleSAML/Logger/FileLoggingHandler.php b/lib/SimpleSAML/Logger/FileLoggingHandler.php index ec06001f32844188d321faf2d0e556d8258910ef..179080bb2436ee1750e0c55f7a97a4ade2616aa2 100644 --- a/lib/SimpleSAML/Logger/FileLoggingHandler.php +++ b/lib/SimpleSAML/Logger/FileLoggingHandler.php @@ -54,13 +54,13 @@ class FileLoggingHandler implements LoggingHandlerInterface { // get the metadata handler option from the configuration $this->logFile = $config->getPathValue('loggingdir', 'log/') . - $config->getString('logging.logfile', 'simplesamlphp.log'); + $config->getOptionalString('logging.logfile', 'simplesamlphp.log'); // Remove any non-printable characters before storing $this->processname = preg_replace( '/[\x00-\x1F\x7F\xA0]/u', '', - $config->getString('logging.processname', 'SimpleSAMLphp') + $config->getOptionalString('logging.processname', 'SimpleSAMLphp') ); if (@file_exists($this->logFile)) { diff --git a/lib/SimpleSAML/Logger/StandardErrorLoggingHandler.php b/lib/SimpleSAML/Logger/StandardErrorLoggingHandler.php index 2083f9d3224bee76769a208a27316169b49fa682..1fd967955ee9bed379d36fc458829c19198b2d84 100644 --- a/lib/SimpleSAML/Logger/StandardErrorLoggingHandler.php +++ b/lib/SimpleSAML/Logger/StandardErrorLoggingHandler.php @@ -26,7 +26,7 @@ class StandardErrorLoggingHandler extends FileLoggingHandler $this->processname = preg_replace( '/[\x00-\x1F\x7F\xA0]/u', '', - $config->getString('logging.processname', 'SimpleSAMLphp') + $config->getOptionalString('logging.processname', 'SimpleSAMLphp') ); $this->logFile = 'php://stderr'; } diff --git a/lib/SimpleSAML/Logger/SyslogLoggingHandler.php b/lib/SimpleSAML/Logger/SyslogLoggingHandler.php index cdfcf0aa3686c1c6cad259ebb8cc544777547d86..40a0923f6bfb1d25a275ff480266049ff7018385 100644 --- a/lib/SimpleSAML/Logger/SyslogLoggingHandler.php +++ b/lib/SimpleSAML/Logger/SyslogLoggingHandler.php @@ -27,13 +27,13 @@ class SyslogLoggingHandler implements LoggingHandlerInterface */ public function __construct(Configuration $config) { - $facility = $config->getInteger('logging.facility', defined('LOG_LOCAL5') ? constant('LOG_LOCAL5') : LOG_USER); + $facility = $config->getOptionalInteger('logging.facility', defined('LOG_LOCAL5') ? constant('LOG_LOCAL5') : LOG_USER); // Remove any non-printable characters before storing $processname = preg_replace( '/[\x00-\x1F\x7F\xA0]/u', '', - $config->getString('logging.processname', 'SimpleSAMLphp') + $config->getOptionalString('logging.processname', 'SimpleSAMLphp') ); // Setting facility to LOG_USER (only valid in Windows), enable log level rewrite on windows systems diff --git a/lib/SimpleSAML/Memcache.php b/lib/SimpleSAML/Memcache.php index 1969fa09b550285472f407d7f1fee15b10b0dfaf..493db7f30b6b191d7f85609823493676fe327569 100644 --- a/lib/SimpleSAML/Memcache.php +++ b/lib/SimpleSAML/Memcache.php @@ -375,7 +375,7 @@ class Memcache $config = Configuration::getInstance(); // get the expire-value from the configuration - $expire = $config->getInteger('memcache_store.expires', 0); + $expire = $config->getOptionalInteger('memcache_store.expires', 0); // it must be a positive integer if ($expire < 0) { diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php b/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php index 4291327288cd4bac81a45765a73582091d3e6cf4..5d8727e759916452e4368e549dd4a1141316abf8 100644 --- a/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php +++ b/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php @@ -66,11 +66,11 @@ class MetaDataStorageHandler implements ClearableState { $config = Configuration::getInstance(); - $sourcesConfig = $config->getArray('metadata.sources', null); + $sourcesConfig = $config->getOptionalArray('metadata.sources', null); // for backwards compatibility, and to provide a default configuration if ($sourcesConfig === null) { - $type = $config->getString('metadata.handler', 'flatfile'); + $type = $config->getOptionalString('metadata.handler', 'flatfile'); $sourcesConfig = [['type' => $type]]; } diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php index 3f6e9c5090ec78fffcc7ce930d765e0dcff0ebf2..8b1f39b0a256d01c8de964d1ca2852ef815ac1b8 100644 --- a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php +++ b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php @@ -52,7 +52,7 @@ class MetaDataStorageHandlerFlatFile extends MetaDataStorageSource if (array_key_exists('directory', $config)) { $this->directory = $config['directory'] ?: 'metadata/'; } else { - $this->directory = $globalConfig->getString('metadatadir', 'metadata/'); + $this->directory = $globalConfig->getOptionalString('metadatadir', 'metadata/'); } /* Resolve this directory relative to the SimpleSAMLphp directory (unless it is diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php index ef7ed5c63317baef3cbe81225d15ae25994bf6fd..1dcf7d1cf19265f523bbd52617e4dd0a64d6654c 100644 --- a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php +++ b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php @@ -45,7 +45,6 @@ class MetaDataStorageHandlerSerialize extends MetaDataStorageSource $globalConfig = Configuration::getInstance(); $cfgHelp = Configuration::loadFromArray($config, 'serialize metadata source'); - $this->directory = $cfgHelp->getString('directory'); /* Resolve this directory relative to the SimpleSAMLphp directory (unless it is diff --git a/lib/SimpleSAML/Metadata/SAMLBuilder.php b/lib/SimpleSAML/Metadata/SAMLBuilder.php index f0f96a0a31acd96e21fb110f124b868ed6bf7784..58269de85f48c4c1e47b21478f8de80e8bb02fc9 100644 --- a/lib/SimpleSAML/Metadata/SAMLBuilder.php +++ b/lib/SimpleSAML/Metadata/SAMLBuilder.php @@ -416,32 +416,31 @@ class SAMLBuilder SPSSODescriptor $spDesc, Configuration $metadata ): void { - $attributes = $metadata->getArray('attributes', []); - $name = $metadata->getLocalizedString('name', null); + $attributes = $metadata->getOptionalArray('attributes', []); + $name = $metadata->getOptionalLocalizedString('name', null); if ($name === null || count($attributes) == 0) { // we cannot add an AttributeConsumingService without name and attributes return; } - $attributesrequired = $metadata->getArray('attributes.required', []); + $attributesrequired = $metadata->getOptionalArray('attributes.required', []); /* * Add an AttributeConsumingService element with information as name and description and list * of requested attributes */ $attributeconsumer = new AttributeConsumingService(); - - $attributeconsumer->setIndex($metadata->getInteger('attributes.index', 0)); + $attributeconsumer->setIndex($metadata->getOptionalInteger('attributes.index', 0)); if ($metadata->hasValue('attributes.isDefault')) { - $attributeconsumer->setIsDefault($metadata->getBoolean('attributes.isDefault', false)); + $attributeconsumer->setIsDefault($metadata->getOptionalBoolean('attributes.isDefault', false)); } $attributeconsumer->setServiceName($name); - $attributeconsumer->setServiceDescription($metadata->getLocalizedString('description', [])); + $attributeconsumer->setServiceDescription($metadata->getOptionalLocalizedString('description', [])); - $nameFormat = $metadata->getString('attributes.NameFormat', Constants::NAMEFORMAT_URI); + $nameFormat = $metadata->getOptionalString('attributes.NameFormat', Constants::NAMEFORMAT_URI); foreach ($attributes as $friendlyName => $attribute) { $t = new RequestedAttribute(); $t->setName($attribute); @@ -519,10 +518,10 @@ class SAMLBuilder $e->setSingleLogoutService(self::createEndpoints($metadata->getEndpoints('SingleLogoutService'), false)); - $e->setNameIDFormat($metadata->getArrayizeString('NameIDFormat', [])); + $e->setNameIDFormat($metadata->getOptionalArrayizeString('NameIDFormat', [])); $endpoints = $metadata->getEndpoints('AssertionConsumerService'); - foreach ($metadata->getArrayizeString('AssertionConsumerService.artifact', []) as $acs) { + foreach ($metadata->getOptionalArrayizeString('AssertionConsumerService.artifact', []) as $acs) { $endpoints[] = [ 'Binding' => Constants::BINDING_HTTP_ARTIFACT, 'Location' => $acs, @@ -534,7 +533,7 @@ class SAMLBuilder $this->entityDescriptor->addRoleDescriptor($e); - foreach ($metadata->getArray('contacts', []) as $contact) { + foreach ($metadata->getOptionalArray('contacts', []) as $contact) { if (array_key_exists('contactType', $contact) && array_key_exists('emailAddress', $contact)) { $this->addContact(Utils\Config\Metadata::getContact($contact)); } @@ -576,13 +575,13 @@ class SAMLBuilder $e->setSingleLogoutService(self::createEndpoints($metadata->getEndpoints('SingleLogoutService'), false)); - $e->setNameIDFormat($metadata->getArrayizeString('NameIDFormat', [])); + $e->setNameIDFormat($metadata->getOptionalArrayizeString('NameIDFormat', [])); $e->setSingleSignOnService(self::createEndpoints($metadata->getEndpoints('SingleSignOnService'), false)); $this->entityDescriptor->addRoleDescriptor($e); - foreach ($metadata->getArray('contacts', []) as $contact) { + foreach ($metadata->getOptionalArray('contacts', []) as $contact) { if (array_key_exists('contactType', $contact) && array_key_exists('emailAddress', $contact)) { $this->addContact(Utils\Config\Metadata::getContact($contact)); } @@ -604,7 +603,7 @@ class SAMLBuilder $metadata = Configuration::loadFromArray($metadata, $metadata['entityid']); $e = new AttributeAuthorityDescriptor(); - $e->setProtocolSupportEnumeration($metadata->getArray('protocols', [Constants::NS_SAMLP])); + $e->setProtocolSupportEnumeration($metadata->getOptionalArray('protocols', [Constants::NS_SAMLP])); $this->addExtensions($metadata, $e); $this->addCertificate($e, $metadata); @@ -615,7 +614,7 @@ class SAMLBuilder false )); - $e->setNameIDFormat($metadata->getArrayizeString('NameIDFormat', [])); + $e->setNameIDFormat($metadata->getOptionalArrayizeString('NameIDFormat', [])); $this->entityDescriptor->addRoleDescriptor($e); } diff --git a/lib/SimpleSAML/Metadata/Signer.php b/lib/SimpleSAML/Metadata/Signer.php index 01c1cffc3596fcce924c6cffa83cda8ff7edcf55..10dd1208a74805265e7be1ad8d3edcca5225be54 100644 --- a/lib/SimpleSAML/Metadata/Signer.php +++ b/lib/SimpleSAML/Metadata/Signer.php @@ -62,8 +62,8 @@ class Signer } // then we look for default values in the global configuration - $privatekey = $config->getString('metadata.sign.privatekey', null); - $certificate = $config->getString('metadata.sign.certificate', null); + $privatekey = $config->getOptionalString('metadata.sign.privatekey', null); + $certificate = $config->getOptionalString('metadata.sign.certificate', null); if ($privatekey !== null || $certificate !== null) { if ($privatekey === null || $certificate === null) { throw new \Exception( @@ -75,7 +75,7 @@ class Signer } $ret = ['privatekey' => $privatekey, 'certificate' => $certificate]; - $privatekey_pass = $config->getString('metadata.sign.privatekey_pass', null); + $privatekey_pass = $config->getOptionalString('metadata.sign.privatekey_pass', null); if ($privatekey_pass !== null) { $ret['privatekey_pass'] = $privatekey_pass; } @@ -144,7 +144,7 @@ class Signer return $entityMetadata['metadata.sign.enable']; } - return $config->getBoolean('metadata.sign.enable', false); + return $config->getOptionalBoolean('metadata.sign.enable', false); } @@ -178,7 +178,7 @@ class Signer } $alg = $entityMetadata['metadata.sign.algorithm']; } else { - $alg = $config->getString('metadata.sign.algorithm', XMLSecurityKey::RSA_SHA256); + $alg = $config->getOptionalString('metadata.sign.algorithm', XMLSecurityKey::RSA_SHA256); } $supported_algs = [ diff --git a/lib/SimpleSAML/Module.php b/lib/SimpleSAML/Module.php index 7d84ddd15d68a08c25a648444cff4921a2162ade..c23e4f6430a83e351564def48a4b84b86157a552 100644 --- a/lib/SimpleSAML/Module.php +++ b/lib/SimpleSAML/Module.php @@ -117,7 +117,7 @@ class Module public static function isModuleEnabled(string $module): bool { $config = Configuration::getOptionalConfig(); - return self::isModuleEnabledWithConf($module, $config->getArray('module.enable', self::$core_modules)); + return self::isModuleEnabledWithConf($module, $config->getOptionalArray('module.enable', self::$core_modules)); } @@ -298,19 +298,18 @@ class Module } } - /** @psalm-var \SimpleSAML\Configuration $assetConfig */ + $assetConfig = $config->getConfigItem('assets'); - /** @psalm-var \SimpleSAML\Configuration $cacheConfig */ $cacheConfig = $assetConfig->getConfigItem('caching'); $response = new BinaryFileResponse($path); $response->setCache([ // "public" allows response caching even if the request was authenticated, // which is exactly what we want for static resources 'public' => true, - 'max_age' => strval($cacheConfig->getInteger('max_age', 86400)) + 'max_age' => strval($cacheConfig->getOptionalInteger('max_age', 86400)) ]); $response->setAutoLastModified(); - if ($cacheConfig->getBoolean('etag', false)) { + if ($cacheConfig->getOptionalBoolean('etag', false)) { $response->setAutoEtag(); } $response->isNotModified($request); @@ -522,7 +521,7 @@ class Module public static function callHooks(string $hook, &$data = null): void { $modules = self::getModules(); - $config = Configuration::getOptionalConfig()->getArray('module.enable', []); + $config = Configuration::getOptionalConfig()->getOptionalArray('module.enable', []); sort($modules); foreach ($modules as $module) { if (!self::isModuleEnabledWithConf($module, $config)) { diff --git a/lib/SimpleSAML/Session.php b/lib/SimpleSAML/Session.php index 401fd6ade6777427523960471778c80236a4603f..0b317b1a55e4801b1ff6d2f088cabfa735d0aa48 100644 --- a/lib/SimpleSAML/Session.php +++ b/lib/SimpleSAML/Session.php @@ -180,7 +180,7 @@ class Session implements Utils\ClearableState $this->markDirty(); // initialize data for session check function if defined - $checkFunction = self::$config->getValue('session.check_function', null); + $checkFunction = self::$config->getOptionalValue('session.check_function', null); if (is_callable($checkFunction)) { call_user_func($checkFunction, $this, true); } @@ -355,7 +355,7 @@ class Session implements Utils\ClearableState $globalConfig = Configuration::getInstance(); if ($session->authToken !== null) { - $authTokenCookieName = $globalConfig->getString( + $authTokenCookieName = $globalConfig->getOptionalString( 'session.authtoken.cookiename', 'SimpleSAMLAuthToken' ); @@ -371,7 +371,7 @@ class Session implements Utils\ClearableState } // run session check function if defined - $checkFunction = $globalConfig->getValue('session.check_function', null); + $checkFunction = $globalConfig->getOptionalValue('session.check_function', null); if (is_callable($checkFunction)) { $check = call_user_func($checkFunction, $session); if ($check !== true) { @@ -575,7 +575,7 @@ class Session implements Utils\ClearableState public function setRememberMeExpire(int $lifetime = null): void { if ($lifetime === null) { - $lifetime = self::$config->getInteger('session.rememberme.lifetime', 14 * 86400); + $lifetime = self::$config->getOptionalInteger('session.rememberme.lifetime', 14 * 86400); } $this->rememberMeExpire = time() + $lifetime; @@ -611,7 +611,7 @@ class Session implements Utils\ClearableState $data['AuthnInstant'] = time(); } - $maxSessionExpire = time() + self::$config->getInteger('session.duration', 8 * 60 * 60); + $maxSessionExpire = time() + self::$config->getOptionalInteger('session.duration', 8 * 60 * 60); if (!isset($data['Expire']) || $data['Expire'] > $maxSessionExpire) { // unset, or beyond our session lifetime. Clamp it to our maximum session lifetime $data['Expire'] = $maxSessionExpire; @@ -653,14 +653,14 @@ class Session implements Utils\ClearableState !$this->transient && (!empty($data['RememberMe']) || $this->rememberMeExpire !== null) - && self::$config->getBoolean('session.rememberme.enable', false) + && self::$config->getOptionalBoolean('session.rememberme.enable', false) ) { $this->setRememberMeExpire(); } else { $httpUtils = new Utils\HTTP(); try { $httpUtils->setCookie( - self::$config->getString('session.authtoken.cookiename', 'SimpleSAMLAuthToken'), + self::$config->getOptionalString('session.authtoken.cookiename', 'SimpleSAMLAuthToken'), $this->authToken, $sessionHandler->getCookieParams() ); @@ -790,7 +790,7 @@ class Session implements Utils\ClearableState if ($this->authToken !== null) { $httpUtils = new Utils\HTTP(); $httpUtils->setCookie( - self::$config->getString('session.authtoken.cookiename', 'SimpleSAMLAuthToken'), + self::$config->getOptionalString('session.authtoken.cookiename', 'SimpleSAMLAuthToken'), $this->authToken, $params ); @@ -809,7 +809,7 @@ class Session implements Utils\ClearableState $this->markDirty(); if ($expire === null) { - $expire = time() + self::$config->getInteger('session.duration', 8 * 60 * 60); + $expire = time() + self::$config->getOptionalInteger('session.duration', 8 * 60 * 60); } $this->authData[$authority]['Expire'] = $expire; @@ -884,7 +884,7 @@ class Session implements Utils\ClearableState if ($timeout === null) { // use the default timeout - $timeout = self::$config->getInteger('session.datastore.timeout', null); + $timeout = self::$config->getOptionalInteger('session.datastore.timeout', null); if ($timeout !== null) { if ($timeout <= 0) { throw new \Exception( diff --git a/lib/SimpleSAML/SessionHandler.php b/lib/SimpleSAML/SessionHandler.php index 324c4f52117b8a3eecf8f93bbe51b02603febe69..b15f0513d31e331d5f28a23f80756f1203ba59a0 100644 --- a/lib/SimpleSAML/SessionHandler.php +++ b/lib/SimpleSAML/SessionHandler.php @@ -136,7 +136,7 @@ abstract class SessionHandler private static function createSessionHandler(): void { $config = Configuration::getInstance(); - $storeType = $config->getString('store.type', 'phpsession'); + $storeType = $config->getOptionalString('store.type', 'phpsession'); $store = StoreFactory::getInstance($storeType); if ($store === false) { @@ -158,11 +158,11 @@ abstract class SessionHandler $config = Configuration::getInstance(); return [ - 'lifetime' => $config->getInteger('session.cookie.lifetime', 0), - 'path' => $config->getString('session.cookie.path', '/'), - 'domain' => $config->getString('session.cookie.domain', null), - 'secure' => $config->getBoolean('session.cookie.secure', false), - 'samesite' => $config->getString('session.cookie.samesite', null), + 'lifetime' => $config->getOptionalInteger('session.cookie.lifetime', 0), + 'path' => $config->getOptionalString('session.cookie.path', '/'), + 'domain' => $config->getOptionalString('session.cookie.domain', null), + 'secure' => $config->getOptionalBoolean('session.cookie.secure', false), + 'samesite' => $config->getOptionalString('session.cookie.samesite', null), 'httponly' => true, ]; } diff --git a/lib/SimpleSAML/SessionHandlerCookie.php b/lib/SimpleSAML/SessionHandlerCookie.php index 86bad84468289287e7ec569af70858ea43d60135..1c0d2223c4c36fb826d3dc239a7e3cfad8964eec 100644 --- a/lib/SimpleSAML/SessionHandlerCookie.php +++ b/lib/SimpleSAML/SessionHandlerCookie.php @@ -45,7 +45,7 @@ abstract class SessionHandlerCookie extends SessionHandler parent::__construct(); $config = Configuration::getInstance(); - $this->cookie_name = $config->getString('session.cookie.name', 'SimpleSAMLSessionID'); + $this->cookie_name = $config->getOptionalString('session.cookie.name', 'SimpleSAMLSessionID'); } diff --git a/lib/SimpleSAML/SessionHandlerPHP.php b/lib/SimpleSAML/SessionHandlerPHP.php index 1f6b22f8f0624c336e2aee52f88c6656647e1c5a..b8e83a88588bca95bde6dad9f9b34fbddc513644 100644 --- a/lib/SimpleSAML/SessionHandlerPHP.php +++ b/lib/SimpleSAML/SessionHandlerPHP.php @@ -48,7 +48,7 @@ class SessionHandlerPHP extends SessionHandler parent::__construct(); $config = Configuration::getInstance(); - $this->cookie_name = $config->getString( + $this->cookie_name = $config->getOptionalString( 'session.phpsession.cookiename', ini_get('session.name') ?: 'PHPSESSID' ); @@ -92,7 +92,7 @@ class SessionHandlerPHP extends SessionHandler ]); } - $savepath = $config->getString('session.phpsession.savepath', null); + $savepath = $config->getOptionalString('session.phpsession.savepath', null); if (!empty($savepath)) { session_save_path($savepath); } @@ -287,13 +287,13 @@ class SessionHandlerPHP extends SessionHandler 'You cannot set both the session.phpsession.limitedpath and session.cookie.path options.' ); } elseif ($config->hasValue('session.phpsession.limitedpath')) { - $ret['path'] = $config->getBoolean( + $ret['path'] = $config->getOptionalBoolean( 'session.phpsession.limitedpath', false ) ? $config->getBasePath() : '/'; } - $ret['httponly'] = $config->getBoolean('session.phpsession.httponly', true); + $ret['httponly'] = $config->getOptionalBoolean('session.phpsession.httponly', true); return $ret; } diff --git a/lib/SimpleSAML/SessionHandlerStore.php b/lib/SimpleSAML/SessionHandlerStore.php index f4f89e71068def0cc4206318dfd850ce233394d5..a995551d877db949306b1191306171fbd6d49de5 100644 --- a/lib/SimpleSAML/SessionHandlerStore.php +++ b/lib/SimpleSAML/SessionHandlerStore.php @@ -79,7 +79,7 @@ class SessionHandlerStore extends SessionHandlerCookie $sessionId = $session->getSessionId(); $config = Configuration::getInstance(); - $sessionDuration = $config->getInteger('session.duration', 8 * 60 * 60); + $sessionDuration = $config->getOptionalInteger('session.duration', 8 * 60 * 60); $expire = time() + $sessionDuration; $this->store->set('session', $sessionId, $session, $expire); diff --git a/lib/SimpleSAML/Stats.php b/lib/SimpleSAML/Stats.php index 4b3d020accd5a0f6fc0769244ddf1dd0c9a8f62b..1ce45cce678f86f49be9e5645640f04093095c33 100644 --- a/lib/SimpleSAML/Stats.php +++ b/lib/SimpleSAML/Stats.php @@ -56,7 +56,7 @@ class Stats private static function initOutputs(): void { $config = Configuration::getInstance(); - $outputCfgs = $config->getArray('statistics.out', []); + $outputCfgs = $config->getOptionalArray('statistics.out', []); self::$outputs = []; foreach ($outputCfgs as $cfg) { diff --git a/lib/SimpleSAML/Store/MemcacheStore.php b/lib/SimpleSAML/Store/MemcacheStore.php index b67a1f8acd774b21e1c53082d3057a013101aee7..0c74907e24fac174be77d025705998229347f467 100644 --- a/lib/SimpleSAML/Store/MemcacheStore.php +++ b/lib/SimpleSAML/Store/MemcacheStore.php @@ -29,7 +29,7 @@ class MemcacheStore implements StoreInterface public function __construct() { $config = Configuration::getInstance(); - $this->prefix = $config->getString('memcache_store.prefix', 'simpleSAMLphp'); + $this->prefix = $config->getOptionalString('memcache_store.prefix', 'simpleSAMLphp'); } diff --git a/lib/SimpleSAML/Store/RedisStore.php b/lib/SimpleSAML/Store/RedisStore.php index d78871fb6dfc4fb1dd9f8c8b24419f6de1824957..7781d3c107122c88ffa31060b10f2360808c90dc 100644 --- a/lib/SimpleSAML/Store/RedisStore.php +++ b/lib/SimpleSAML/Store/RedisStore.php @@ -35,11 +35,11 @@ class RedisStore implements StoreInterface if ($redis === null) { $config = Configuration::getInstance(); - $host = $config->getString('store.redis.host', 'localhost'); - $port = $config->getInteger('store.redis.port', 6379); - $prefix = $config->getString('store.redis.prefix', 'SimpleSAMLphp'); - $password = $config->getString('store.redis.password', ''); - $database = $config->getInteger('store.redis.database', 0); + $host = $config->getOptionalString('store.redis.host', 'localhost'); + $port = $config->getOptionalInteger('store.redis.port', 6379); + $prefix = $config->getOptionalString('store.redis.prefix', 'SimpleSAMLphp'); + $password = $config->getOptionalString('store.redis.password', null); + $database = $config->getOptionalInteger('store.redis.database', 0); $redis = new Client( [ diff --git a/lib/SimpleSAML/Store/SQLStore.php b/lib/SimpleSAML/Store/SQLStore.php index eef08f25bf542b72a7b1d433804e64439b3a5271..3bdf83dcc85811712cfef54681ff1c15b4a072c8 100644 --- a/lib/SimpleSAML/Store/SQLStore.php +++ b/lib/SimpleSAML/Store/SQLStore.php @@ -55,10 +55,10 @@ class SQLStore implements StoreInterface $config = Configuration::getInstance(); $dsn = $config->getString('store.sql.dsn'); - $username = $config->getString('store.sql.username', null); - $password = $config->getString('store.sql.password', null); - $options = $config->getArray('store.sql.options', null); - $this->prefix = $config->getString('store.sql.prefix', 'simpleSAMLphp'); + $username = $config->getOptionalString('store.sql.username', null); + $password = $config->getOptionalString('store.sql.password', null); + $options = $config->getOptionalArray('store.sql.options', null); + $this->prefix = $config->getOptionalString('store.sql.prefix', 'simpleSAMLphp'); try { $this->pdo = new PDO($dsn, $username, $password, $options); } catch (PDOException $e) { diff --git a/lib/SimpleSAML/Utils/Config/Metadata.php b/lib/SimpleSAML/Utils/Config/Metadata.php index 0ec059974b21c0966e78efecfa463a9b02ab8853..f7a1a389aaa29328fb81157b4a51a641a1f0ca0f 100644 --- a/lib/SimpleSAML/Utils/Config/Metadata.php +++ b/lib/SimpleSAML/Utils/Config/Metadata.php @@ -277,11 +277,11 @@ class Metadata // handle current configurations specifying an array in the NameIDPolicy config option $nameIdPolicy_cf = Configuration::loadFromArray($nameIdPolicy); $policy = [ - 'Format' => $nameIdPolicy_cf->getString('Format', Constants::NAMEID_TRANSIENT), - 'AllowCreate' => $nameIdPolicy_cf->getBoolean('AllowCreate', true), + 'Format' => $nameIdPolicy_cf->getOptionalString('Format', Constants::NAMEID_TRANSIENT), + 'AllowCreate' => $nameIdPolicy_cf->getOptionalBoolean('AllowCreate', true), ]; - $spNameQualifier = $nameIdPolicy_cf->getString('SPNameQualifier', false); - if ($spNameQualifier !== false) { + $spNameQualifier = $nameIdPolicy_cf->getOptionalString('SPNameQualifier', null); + if ($spNameQualifier !== null) { $policy['SPNameQualifier'] = $spNameQualifier; } } elseif ($nameIdPolicy === null) { diff --git a/lib/SimpleSAML/Utils/Crypto.php b/lib/SimpleSAML/Utils/Crypto.php index 0be2ec3ee136158723abd9f7977f952124cb34d2..5d741a4b1df67c80e0d165be5e953a36d587e175 100644 --- a/lib/SimpleSAML/Utils/Crypto.php +++ b/lib/SimpleSAML/Utils/Crypto.php @@ -203,7 +203,7 @@ class Crypto string $prefix = '', bool $full_path = false ): ?array { - $file = $metadata->getString($prefix . 'privatekey', null); + $file = $metadata->getOptionalString($prefix . 'privatekey', null); if ($file === null) { // no private key found if ($required) { @@ -225,7 +225,7 @@ class Crypto $ret = [ 'PEM' => $data, - 'password' => $metadata->getString($prefix . 'privatekey_pass', null), + 'password' => $metadata->getOptionalString($prefix . 'privatekey_pass', null), ]; return $ret; diff --git a/lib/SimpleSAML/Utils/EMail.php b/lib/SimpleSAML/Utils/EMail.php index 8a99fe0de908ad7da546db779a2c9b51f343e344..60f98b85293c920251dbbc1f774760082d99b305 100644 --- a/lib/SimpleSAML/Utils/EMail.php +++ b/lib/SimpleSAML/Utils/EMail.php @@ -65,7 +65,7 @@ class EMail public function getDefaultMailAddress(): string { $config = Configuration::getInstance(); - $address = $config->getString('technicalcontact_email', 'na@example.org'); + $address = $config->getOptionalString('technicalcontact_email', 'na@example.org'); $address = preg_replace('/^mailto:/i', '', $address); if ('na@example.org' === $address) { throw new \Exception('technicalcontact_email must be changed from the default value'); @@ -228,8 +228,8 @@ class EMail { $config = Configuration::getInstance(); $EMail->setTransportMethod( - $config->getString('mail.transport.method', 'mail'), - $config->getArrayize('mail.transport.options', []) + $config->getOptionalString('mail.transport.method', 'mail'), + $config->getOptionalArrayize('mail.transport.options', []) ); return $EMail; diff --git a/lib/SimpleSAML/Utils/HTTP.php b/lib/SimpleSAML/Utils/HTTP.php index 402901b67f67f9459cfbbe83ea6a72dc60f59cc6..e264a4e2516b0da2b924422398ca6898f97e8b52 100644 --- a/lib/SimpleSAML/Utils/HTTP.php +++ b/lib/SimpleSAML/Utils/HTTP.php @@ -375,7 +375,7 @@ class HTTP // get the white list of domains if ($trustedSites === null) { - $trustedSites = Configuration::getInstance()->getValue('trusted.url.domains', []); + $trustedSites = Configuration::getInstance()->getOptionalArray('trusted.url.domains', []); } // validates the URL's host is among those allowed @@ -406,10 +406,10 @@ class HTTP $self_host = $this->getSelfHostWithNonStandardPort(); - $trustedRegex = Configuration::getInstance()->getValue('trusted.url.regex', false); + $trustedRegex = Configuration::getInstance()->getOptionalValue('trusted.url.regex', null); $trusted = false; - if ($trustedRegex) { + if (!in_array($trustedRegex, [null, false])) { // add self host to the white list $trustedSites[] = preg_quote($self_host); foreach ($trustedSites as $regex) { @@ -455,13 +455,13 @@ class HTTP { $config = Configuration::getInstance(); - $proxy = $config->getString('proxy', null); + $proxy = $config->getOptionalString('proxy', null); if ($proxy !== null) { if (!isset($context['http']['proxy'])) { $context['http']['proxy'] = $proxy; } - $proxy_auth = $config->getString('proxy.auth', false); - if ($proxy_auth !== false) { + $proxy_auth = $config->getOptionalString('proxy.auth', null); + if ($proxy_auth !== null) { $context['http']['header'] = "Proxy-Authorization: Basic " . base64_encode($proxy_auth); } if (!isset($context['http']['request_fulluri'])) { @@ -638,7 +638,7 @@ class HTTP public function getBaseURL(): string { $globalConfig = Configuration::getInstance(); - $baseURL = $globalConfig->getString('baseurlpath', 'simplesaml/'); + $baseURL = $globalConfig->getOptionalString('baseurlpath', 'simplesaml/'); if (preg_match('#^https?://.*/?$#D', $baseURL, $matches)) { // full URL in baseurlpath, override local server values @@ -688,7 +688,7 @@ class HTTP public function getPOSTRedirectURL(string $destination, array $data): string { $config = Configuration::getInstance(); - $allowed = $config->getBoolean('enable.http_post', false); + $allowed = $config->getOptionalBoolean('enable.http_post', false); if ($allowed && preg_match("#^http:#", $destination) && $this->isHTTPS()) { // we need to post the data to HTTP @@ -802,10 +802,9 @@ class HTTP * current URI, so we need to build it back from the PHP environment, unless we have a base URL specified * for this case in the configuration. First, check if that's the case. */ + $appcfg = $cfg->getOptionalConfigItem('application', null); + $appurl = ($appcfg !== null) ? $appcfg->getOptionalString('baseURL', null) : null; - /** @var \SimpleSAML\Configuration $appcfg */ - $appcfg = $cfg->getConfigItem('application'); - $appurl = $appcfg->getString('baseURL', ''); if (!empty($appurl)) { $protocol = parse_url($appurl, PHP_URL_SCHEME); $hostname = parse_url($appurl, PHP_URL_HOST); @@ -1181,7 +1180,7 @@ class HTTP } $config = Configuration::getInstance(); - $allowed = $config->getBoolean('enable.http_post', false); + $allowed = $config->getOptionalBoolean('enable.http_post', false); if ($allowed && preg_match("#^http:#", $destination) && $this->isHTTPS()) { // we need to post the data to HTTP diff --git a/lib/SimpleSAML/Utils/System.php b/lib/SimpleSAML/Utils/System.php index 33efcae4e170c20a3f3aa0fab6380c0470998972..e2dde4be3c3a5a42c1ef147aa2c50293d50e1993 100644 --- a/lib/SimpleSAML/Utils/System.php +++ b/lib/SimpleSAML/Utils/System.php @@ -75,7 +75,7 @@ class System $globalConfig = Configuration::getInstance(); $tempDir = rtrim( - $globalConfig->getString( + $globalConfig->getOptionalString( 'tempdir', sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'simplesaml' ), diff --git a/lib/SimpleSAML/Utils/Time.php b/lib/SimpleSAML/Utils/Time.php index b152edb05b8c724acf8f61cbed2210543433a75a..f0e37c2b40e9036895be94cdf6f78ba392fd3a63 100644 --- a/lib/SimpleSAML/Utils/Time.php +++ b/lib/SimpleSAML/Utils/Time.php @@ -56,7 +56,7 @@ class Time $globalConfig = Configuration::getInstance(); - $timezone = $globalConfig->getString('timezone', null); + $timezone = $globalConfig->getOptionalString('timezone', null); if ($timezone !== null) { if (!date_default_timezone_set($timezone)) { throw new Error\Exception('Invalid timezone set in the "timezone" option in config.php.'); diff --git a/lib/SimpleSAML/Utils/XML.php b/lib/SimpleSAML/Utils/XML.php index 51928aaffe7f7fdf041e0b5e4082cfe2806d5291..a77d9892cd5231f517497dd576a4f4ead96e1d68 100644 --- a/lib/SimpleSAML/Utils/XML.php +++ b/lib/SimpleSAML/Utils/XML.php @@ -52,7 +52,7 @@ class XML } // see if debugging is enabled for XML validation - $debug = Configuration::getInstance()->getArray('debug', ['validatexml' => false]); + $debug = Configuration::getInstance()->getOptionalArray('debug', ['validatexml' => false]); if ( !( @@ -100,7 +100,7 @@ class XML } // see if debugging is enabled for SAML messages - $debug = Configuration::getInstance()->getArray('debug', ['saml' => false]); + $debug = Configuration::getInstance()->getOptionalArray('debug', ['saml' => false]); if ( !( diff --git a/lib/SimpleSAML/XHTML/IdPDisco.php b/lib/SimpleSAML/XHTML/IdPDisco.php index 5abcd97c39ea631ec3dac8273f05a1d297b758a6..852a2d6de6f0afb7420047c01fbcc8dbce1c5dc2 100644 --- a/lib/SimpleSAML/XHTML/IdPDisco.php +++ b/lib/SimpleSAML/XHTML/IdPDisco.php @@ -243,7 +243,7 @@ class IdPDisco return null; } - if (!$this->config->getBoolean('idpdisco.validate', true)) { + if (!$this->config->getOptionalBoolean('idpdisco.validate', true)) { return $idp; } @@ -308,7 +308,7 @@ class IdPDisco */ protected function getSavedIdP(): ?string { - if (!$this->config->getBoolean('idpdisco.enableremember', false)) { + if (!$this->config->getOptionalBoolean('idpdisco.enableremember', false)) { // saving of IdP choices is disabled return null; } @@ -402,7 +402,7 @@ class IdPDisco */ protected function saveIdP(): bool { - if (!$this->config->getBoolean('idpdisco.enableremember', false)) { + if (!$this->config->getOptionalBoolean('idpdisco.enableremember', false)) { // saving of IdP choices is disabled return false; } @@ -513,7 +513,7 @@ class IdPDisco $httpUtils = new Utils\HTTP(); $idp = $this->getTargetIdP(); if ($idp !== null) { - $extDiscoveryStorage = $this->config->getString('idpdisco.extDiscoveryStorage', null); + $extDiscoveryStorage = $this->config->getOptionalString('idpdisco.extDiscoveryStorage', null); if ($extDiscoveryStorage !== null) { $this->log('Choice made [' . $idp . '] (Forwarding to external discovery storage)'); $httpUtils->redirectTrustedURL($extDiscoveryStorage, [ @@ -576,7 +576,7 @@ class IdPDisco * Make use of an XHTML template to present the select IdP choice to the user. Currently the supported options * is either a drop down menu or a list view. */ - switch ($this->config->getString('idpdisco.layout', 'links')) { + switch ($this->config->getOptionalString('idpdisco.layout', 'links')) { case 'dropdown': $templateFile = 'selectidp-dropdown.twig'; break; @@ -618,8 +618,8 @@ class IdPDisco $t->data['returnIDParam'] = $this->returnIdParam; $t->data['entityID'] = $this->spEntityId; $t->data['urlpattern'] = htmlspecialchars($httpUtils->getSelfURLNoQuery()); - $t->data['rememberenabled'] = $this->config->getBoolean('idpdisco.enableremember', false); - $t->data['rememberchecked'] = $this->config->getBoolean('idpdisco.rememberchecked', false); + $t->data['rememberenabled'] = $this->config->getOptionalBoolean('idpdisco.enableremember', false); + $t->data['rememberchecked'] = $this->config->getOptionalBoolean('idpdisco.rememberchecked', false); $t->send(); } } diff --git a/lib/SimpleSAML/XHTML/Template.php b/lib/SimpleSAML/XHTML/Template.php index 5ad664905b14648e909f624f0f4c1cfd36d60de2..8b0c421aeea76ac00ea28c2859312987cf8b6c68 100644 --- a/lib/SimpleSAML/XHTML/Template.php +++ b/lib/SimpleSAML/XHTML/Template.php @@ -129,7 +129,7 @@ class Template extends Response // parse config to find theme and module theme is in, if any list($this->theme['module'], $this->theme['name']) = $this->findModuleAndTemplateName( - $this->configuration->getString('theme.use', 'default') + $this->configuration->getOptionalString('theme.use', 'default') ); // initialize internationalization system @@ -137,9 +137,9 @@ class Template extends Response $this->localization = new Localization($configuration); // check if we need to attach a theme controller - $controller = $this->configuration->getString('theme.controller', false); + $controller = $this->configuration->getOptionalString('theme.controller', null); if ( - $controller + $controller !== null && class_exists($controller) && in_array(TemplateControllerInterface::class, class_implements($controller)) ) { @@ -265,8 +265,8 @@ class Template extends Response */ private function setupTwig(): Environment { - $auto_reload = $this->configuration->getBoolean('template.auto_reload', true); - $cache = $this->configuration->getString('template.cache', false); + $auto_reload = $this->configuration->getOptionalBoolean('template.auto_reload', true); + $cache = $this->configuration->getOptionalString('template.cache', null); // set up template paths $loader = $this->setupTwigTemplatepaths(); @@ -287,7 +287,7 @@ class Template extends Response // set up translation $options = [ 'auto_reload' => $auto_reload, - 'cache' => $cache, + 'cache' => $cache ?? false, 'strict_variables' => true, ]; @@ -299,7 +299,7 @@ class Template extends Response $twig->addFunction(new TwigFunction('moduleURL', [Module::class, 'getModuleURL'])); // initialize some basic context - $langParam = $this->configuration->getString('language.parameter.name', 'language'); + $langParam = $this->configuration->getOptionalString('language.parameter.name', 'language'); $twig->addGlobal('languageParameterName', $langParam); $twig->addGlobal('currentLanguage', $this->translator->getLanguage()->getLanguage()); $twig->addGlobal('isRTL', false); // language RTL configuration @@ -312,7 +312,7 @@ class Template extends Response } $twig->addGlobal('queryParams', $queryParams); $twig->addGlobal('templateId', str_replace('.twig', '', $this->normalizeTemplateName($this->template))); - $twig->addGlobal('isProduction', $this->configuration->getBoolean('production', true)); + $twig->addGlobal('isProduction', $this->configuration->getOptionalBoolean('production', true)); $twig->addGlobal('baseurlpath', ltrim($this->configuration->getBasePath(), '/')); // add a filter for translations out of arrays @@ -488,7 +488,7 @@ class Template extends Response $this->data['year'] = date('Y'); - $this->data['header'] = $this->configuration->getValue('theme.header', 'SimpleSAMLphp'); + $this->data['header'] = $this->configuration->getOptionalString('theme.header', 'SimpleSAMLphp'); } /** diff --git a/modules/admin/lib/Controller/Config.php b/modules/admin/lib/Controller/Config.php index f29180ab7dc5cfb94cd693ba2c04fee5f4664343..aeba9b7e9edc94689072e589678130c0a8aaf608 100644 --- a/modules/admin/lib/Controller/Config.php +++ b/modules/admin/lib/Controller/Config.php @@ -132,7 +132,7 @@ class Config ] ], 'enablematrix' => [ - 'saml20idp' => $this->config->getBoolean('enable.saml20-idp', false), + 'saml20idp' => $this->config->getOptionalBoolean('enable.saml20-idp', false), ], 'funcmatrix' => $this->getPrerequisiteChecks(), 'logouturl' => $this->authUtils->getAdminLogoutURL(), @@ -202,7 +202,7 @@ class Config 'enabled' => version_compare(phpversion(), '7.4', '>=') ] ]; - $store = $this->config->getString('store.type', ''); + $store = $this->config->getOptionalString('store.type', null); // check dependencies used via normal functions $functions = [ @@ -267,7 +267,7 @@ class Config ] ], 'curl_init' => [ - 'required' => $this->config->getBoolean('admin.checkforupdates', true) ? 'required' : 'optional', + 'required' => $this->config->getOptionalBoolean('admin.checkforupdates', true) ? 'required' : 'optional', 'descr' => [ 'optional' => Translate::noop( 'cURL (might be required by some modules)' @@ -354,19 +354,19 @@ class Config $matrix[] = [ 'required' => 'optional', 'descr' => Translate::noop('The <code>technicalcontact_email</code> configuration option should be set'), - 'enabled' => $this->config->getString('technicalcontact_email', 'na@example.org') !== 'na@example.org', + 'enabled' => $this->config->getOptionalString('technicalcontact_email', 'na@example.org') !== 'na@example.org', ]; $matrix[] = [ 'required' => 'required', 'descr' => Translate::noop('The auth.adminpassword configuration option must be set'), - 'enabled' => $this->config->getString('auth.adminpassword', '123') !== '123', + 'enabled' => $this->config->getOptionalString('auth.adminpassword', '123') !== '123', ]; $cryptoUtils = new Utils\Crypto(); // perform some sanity checks on the configured certificates - if ($this->config->getBoolean('enable.saml20-idp', false) !== false) { + if ($this->config->getOptionalBoolean('enable.saml20-idp', false) !== false) { $handler = MetaDataStorageHandler::getMetadataHandler(); try { $metadata = $handler->getMetaDataCurrent('saml20-idp-hosted'); @@ -401,7 +401,7 @@ class Config } } - if ($this->config->getBoolean('metadata.sign.enable', false) !== false) { + if ($this->config->getOptionalBoolean('metadata.sign.enable', false) !== false) { $private = $cryptoUtils->loadPrivateKey($this->config, false, 'metadata.sign.'); $public = $cryptoUtils->loadPublicKey($this->config, false, 'metadata.sign.'); $matrix[] = [ @@ -442,7 +442,7 @@ class Config } // make sure we have a secret salt set - if ($this->config->getValue('secretsalt') === 'defaultsecretsalt') { + if ($this->config->getString('secretsalt') === 'defaultsecretsalt') { $warnings[] = Translate::noop( '<strong>The configuration uses the default secret salt</strong>. Make sure to modify the <code>' . 'secretsalt</code> option in the SimpleSAMLphp configuration in production environments. <a ' . @@ -455,7 +455,7 @@ class Config * Check for updates. Store the remote result in the session so we don't need to fetch it on every access to * this page. */ - if ($this->config->getBoolean('admin.checkforupdates', true) && $this->config->getVersion() !== 'master') { + if ($this->config->getOptionalBoolean('admin.checkforupdates', true) && $this->config->getVersion() !== 'master') { if (!function_exists('curl_init')) { $warnings[] = Translate::noop( 'The cURL PHP extension is missing. Cannot check for SimpleSAMLphp updates.' @@ -468,8 +468,8 @@ class Config curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_USERAGENT, 'SimpleSAMLphp'); curl_setopt($ch, CURLOPT_TIMEOUT, 2); - curl_setopt($ch, CURLOPT_PROXY, $this->config->getString('proxy', null)); - curl_setopt($ch, CURLOPT_PROXYUSERPWD, $this->config->getValue('proxy.auth', null)); + curl_setopt($ch, CURLOPT_PROXY, $this->config->getOptionalString('proxy', null)); + curl_setopt($ch, CURLOPT_PROXYUSERPWD, $this->config->getOptionalValue('proxy.auth', null)); $response = curl_exec($ch); if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) === 200) { diff --git a/modules/admin/lib/Controller/Federation.php b/modules/admin/lib/Controller/Federation.php index cada26dd1d5bb51149d24547918235704450e78e..6969ca17daae9ba435a6bc093da65c1d3de6cdc3 100644 --- a/modules/admin/lib/Controller/Federation.php +++ b/modules/admin/lib/Controller/Federation.php @@ -123,9 +123,9 @@ class Federation 'remote' => [ 'saml20-idp-remote' => !empty($hostedSPs) ? $this->mdHandler->getList('saml20-idp-remote', true) : [], - 'saml20-sp-remote' => $this->config->getBoolean('enable.saml20-idp', false) === true + 'saml20-sp-remote' => $this->config->getOptionalBoolean('enable.saml20-idp', false) === true ? $this->mdHandler->getList('saml20-sp-remote', true) : [], - 'adfs-sp-remote' => ($this->config->getBoolean('enable.adfs-idp', false) === true) && + 'adfs-sp-remote' => ($this->config->getOptionalBoolean('enable.adfs-idp', false) === true) && Module::isModuleEnabled('adfs') ? $this->mdHandler->getList('adfs-sp-remote', true) : [], ], ]; @@ -190,7 +190,7 @@ class Federation $entities = []; // SAML 2 - if ($this->config->getBoolean('enable.saml20-idp', false)) { + if ($this->config->getOptionalBoolean('enable.saml20-idp', false)) { try { $idps = $this->mdHandler->getList('saml20-idp-hosted'); $saml2entities = []; @@ -230,7 +230,7 @@ class Federation } // ADFS - if ($this->config->getBoolean('enable.adfs-idp', false) && Module::isModuleEnabled('adfs')) { + if ($this->config->getOptionalBoolean('enable.adfs-idp', false) && Module::isModuleEnabled('adfs')) { try { $idps = $this->mdHandler->getList('adfs-idp-hosted'); $adfsentities = []; @@ -329,9 +329,9 @@ class Federation } // get the name - $name = $source->getMetadata()->getLocalizedString( + $name = $source->getMetadata()->getOptionalLocalizedString( 'name', - $source->getMetadata()->getLocalizedString('OrganizationDisplayName', $source->getAuthId()) + $source->getMetadata()->getOptionalLocalizedString('OrganizationDisplayName', ['en' => $source->getAuthId()]) ); $builder = new SAMLBuilder($source->getEntityId()); diff --git a/modules/core/hooks/hook_sanitycheck.php b/modules/core/hooks/hook_sanitycheck.php index 53c0d9e8219c9874f7dbe3f68ee26f6bcefba2b8..09befb775d91d00371986930bf23c730dcf3c898 100644 --- a/modules/core/hooks/hook_sanitycheck.php +++ b/modules/core/hooks/hook_sanitycheck.php @@ -18,13 +18,13 @@ function core_hook_sanitycheck(array &$hookinfo): void $config = Configuration::getInstance(); - if ($config->getString('auth.adminpassword', '123') === '123') { + if ($config->getOptionalString('auth.adminpassword', '123') === '123') { $hookinfo['errors'][] = '[core] Password in config.php is not set properly'; } else { $hookinfo['info'][] = '[core] Password in config.php is set properly'; } - if ($config->getString('technicalcontact_email', 'na@example.org') === 'na@example.org') { + if ($config->getOptionalString('technicalcontact_email', 'na@example.org') === 'na@example.org') { $hookinfo['errors'][] = '[core] In config.php technicalcontact_email is not set properly'; } else { $hookinfo['info'][] = '[core] In config.php technicalcontact_email is set properly'; diff --git a/modules/core/lib/Auth/Process/ExtendIdPSession.php b/modules/core/lib/Auth/Process/ExtendIdPSession.php index 09ce3d08c3a1077cb05daf0bca7df3fc116436e0..f3931f506c0b8c92dcd3cd98e8cf7601ec53c15f 100644 --- a/modules/core/lib/Auth/Process/ExtendIdPSession.php +++ b/modules/core/lib/Auth/Process/ExtendIdPSession.php @@ -28,7 +28,7 @@ class ExtendIdPSession extends Auth\ProcessingFilter $delta = $state['Expire'] - $now; $globalConfig = Configuration::getInstance(); - $sessionDuration = $globalConfig->getInteger('session.duration', 28800); // 8*60*60 + $sessionDuration = $globalConfig->getOptionalInteger('session.duration', 28800); // 8*60*60 // Extend only if half of session duration already passed if ($delta >= ($sessionDuration * 0.5)) { @@ -46,7 +46,7 @@ class ExtendIdPSession extends Auth\ProcessingFilter if ( !empty($state['RememberMe']) && $rememberMeExpire !== null - && $globalConfig->getBoolean('session.rememberme.enable', false) + && $globalConfig->getOptionalBoolean('session.rememberme.enable', false) ) { $session->setRememberMeExpire(); return; diff --git a/modules/core/lib/Auth/Process/ScopeAttribute.php b/modules/core/lib/Auth/Process/ScopeAttribute.php index aec51485d558bab25749c58de9c564abf12e0ac4..11241324ab67eff713199622628414970d597a3b 100644 --- a/modules/core/lib/Auth/Process/ScopeAttribute.php +++ b/modules/core/lib/Auth/Process/ScopeAttribute.php @@ -60,7 +60,7 @@ class ScopeAttribute extends Auth\ProcessingFilter $this->scopeAttribute = $cfg->getString('scopeAttribute'); $this->sourceAttribute = $cfg->getString('sourceAttribute'); $this->targetAttribute = $cfg->getString('targetAttribute'); - $this->onlyIfEmpty = $cfg->getBoolean('onlyIfEmpty', false); + $this->onlyIfEmpty = $cfg->getOptionalBoolean('onlyIfEmpty', false); } diff --git a/modules/core/lib/Auth/Source/AdminPassword.php b/modules/core/lib/Auth/Source/AdminPassword.php index 0af50a1ed782534d5c814bada7e7c90311135a59..467a798f10fc74dced06d7a11c0d2a5ea5922d94 100644 --- a/modules/core/lib/Auth/Source/AdminPassword.php +++ b/modules/core/lib/Auth/Source/AdminPassword.php @@ -50,7 +50,7 @@ class AdminPassword extends UserPassBase protected function login(string $username, string $password): array { $config = Configuration::getInstance(); - $adminPassword = $config->getString('auth.adminpassword', '123'); + $adminPassword = $config->getOptionalString('auth.adminpassword', '123'); if ($adminPassword === '123') { // We require that the user changes the password throw new Error\Error('NOTSET'); diff --git a/modules/core/lib/Auth/UserPassBase.php b/modules/core/lib/Auth/UserPassBase.php index 661159d84302c6bdfb155ee483a2da0265b0e828..d596eb638db74312ffeed2102f783f3b67cffac9 100644 --- a/modules/core/lib/Auth/UserPassBase.php +++ b/modules/core/lib/Auth/UserPassBase.php @@ -120,8 +120,8 @@ abstract class UserPassBase extends Auth\Source // get the "remember me" config options $sspcnf = Configuration::getInstance(); - $this->rememberMeEnabled = $sspcnf->getBoolean('session.rememberme.enable', false); - $this->rememberMeChecked = $sspcnf->getBoolean('session.rememberme.checked', false); + $this->rememberMeEnabled = $sspcnf->getOptionalBoolean('session.rememberme.enable', false); + $this->rememberMeChecked = $sspcnf->getOptionalBoolean('session.rememberme.checked', false); } diff --git a/modules/core/lib/Stats/Output/Log.php b/modules/core/lib/Stats/Output/Log.php index 58c554b574c555616847965b98b59545b5402ec7..86d740270e804d6c266ec17d432dc9c78e0a974d 100644 --- a/modules/core/lib/Stats/Output/Log.php +++ b/modules/core/lib/Stats/Output/Log.php @@ -29,7 +29,7 @@ class Log extends \SimpleSAML\Stats\Output */ public function __construct(Configuration $config) { - $logLevel = $config->getString('level', 'notice'); + $logLevel = $config->getOptionalString('level', 'notice'); $this->logger = [Logger::class, $logLevel]; if (!is_callable($this->logger)) { throw new \Exception('Invalid log level: ' . var_export($logLevel, true)); diff --git a/modules/core/www/idp/logout-iframe-post.php b/modules/core/www/idp/logout-iframe-post.php index 6dc9c8d35ddf9dbff909f717d1d2d052d0b1d4ac..d20af29fc730946e66356db79aba6132653d97df 100644 --- a/modules/core/www/idp/logout-iframe-post.php +++ b/modules/core/www/idp/logout-iframe-post.php @@ -30,15 +30,15 @@ $lr = \SimpleSAML\Module\saml\Message::buildLogoutRequest($idpMetadata, $spMetad $lr->setSessionIndex($association['saml:SessionIndex']); $lr->setNameId($association['saml:NameID']); -$assertionLifetime = $spMetadata->getInteger('assertion.lifetime', null); +$assertionLifetime = $spMetadata->getOptionalInteger('assertion.lifetime', null); if ($assertionLifetime === null) { - $assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300); + $assertionLifetime = $idpMetadata->getOptionalInteger('assertion.lifetime', 300); } $lr->setNotOnOrAfter(time() + $assertionLifetime); -$encryptNameId = $spMetadata->getBoolean('nameid.encryption', null); +$encryptNameId = $spMetadata->getOptionalBoolean('nameid.encryption', null); if ($encryptNameId === null) { - $encryptNameId = $idpMetadata->getBoolean('nameid.encryption', false); + $encryptNameId = $idpMetadata->getOptionalBoolean('nameid.encryption', false); } if ($encryptNameId) { $lr->encryptNameId(\SimpleSAML\Module\saml\Message::getEncryptionKey($spMetadata)); diff --git a/modules/cron/hooks/hook_cron.php b/modules/cron/hooks/hook_cron.php index 843d4dd5dc894568456a0c288fc75cab8f4e2b78..c21b3015d23c935779d6fbecefde39182fa42dad 100644 --- a/modules/cron/hooks/hook_cron.php +++ b/modules/cron/hooks/hook_cron.php @@ -15,7 +15,7 @@ function cron_hook_cron(array &$croninfo): void $cronconfig = Configuration::getConfig('module_cron.php'); - if ($cronconfig->getValue('debug_message', true)) { + if ($cronconfig->getOptionalBoolean('debug_message', true)) { $croninfo['summary'][] = 'Cron did run tag [' . $croninfo['tag'] . '] at ' . date(DATE_RFC822); } } diff --git a/modules/cron/lib/Controller/Cron.php b/modules/cron/lib/Controller/Cron.php index 42e1c62270330db6e971fe2a2a454b5f3cbff2b4..80346cd1217478f1fd27e28dedf45f9143aed345 100644 --- a/modules/cron/lib/Controller/Cron.php +++ b/modules/cron/lib/Controller/Cron.php @@ -85,8 +85,8 @@ class Cron { $this->authUtils->requireAdmin(); - $key = $this->cronconfig->getValue('key', 'secret'); - $tags = $this->cronconfig->getValue('allowed_tags', []); + $key = $this->cronconfig->getOptionalString('key', 'secret'); + $tags = $this->cronconfig->getOptionalArray('allowed_tags', []); $def = [ 'weekly' => "22 0 * * 0", @@ -127,7 +127,7 @@ class Cron */ public function run(string $tag, string $key, string $output = 'xhtml'): Response { - $configKey = $this->cronconfig->getValue('key', 'secret'); + $configKey = $this->cronconfig->getOptionalString('key', 'secret'); if ($key !== $configKey) { Logger::error('Cron - Wrong key provided. Cron will not run.'); exit; @@ -146,7 +146,7 @@ class Cron $croninfo = $cron->runTag($tag); $summary = $croninfo['summary']; - if ($this->cronconfig->getValue('sendemail', true) && count($summary) > 0) { + if ($this->cronconfig->getOptionalBoolean('sendemail', true) && count($summary) > 0) { $mail = new Utils\EMail('SimpleSAMLphp cron report'); $mail->setData(['url' => $url, 'tag' => $croninfo['tag'], 'summary' => $croninfo['summary']]); try { diff --git a/modules/multiauth/lib/Auth/Source/MultiAuth.php b/modules/multiauth/lib/Auth/Source/MultiAuth.php index 8af4fa0372630fdd6028fcb2a1e66594c3772cd7..9b974105e59009dce4ef49ca5f6892c8fc704dc9 100644 --- a/modules/multiauth/lib/Auth/Source/MultiAuth.php +++ b/modules/multiauth/lib/Auth/Source/MultiAuth.php @@ -80,7 +80,7 @@ class MultiAuth extends Auth\Source } $globalConfiguration = Configuration::getInstance(); - $defaultLanguage = $globalConfiguration->getString('language.default', 'en'); + $defaultLanguage = $globalConfiguration->getOptionalString('language.default', 'en'); $authsources = Configuration::getConfig('authsources.php'); $this->sources = []; @@ -108,7 +108,7 @@ class MultiAuth extends Auth\Source $css_class = $info['css-class']; } else { // Use the authtype as the css class - $authconfig = $authsources->getArray($source, null); + $authconfig = $authsources->getOptionalArray($source, null); if (!array_key_exists(0, $authconfig) || !is_string($authconfig[0])) { $css_class = ""; } else { diff --git a/modules/multiauth/lib/Controller/DiscoController.php b/modules/multiauth/lib/Controller/DiscoController.php index 09f577e9fc99acdb7cd132d183901eff8a0d27ca..57591e81ae9b316382a4d77522ca992b953df6ed 100644 --- a/modules/multiauth/lib/Controller/DiscoController.php +++ b/modules/multiauth/lib/Controller/DiscoController.php @@ -129,7 +129,7 @@ class DiscoController $t = new Template($this->config, 'multiauth:selectsource.twig'); - $defaultLanguage = $this->config->getString('language.default', 'en'); + $defaultLanguage = $this->config->getOptionalString('language.default', 'en'); $language = $t->getTranslator()->getLanguage()->getLanguage(); $sources = $state[MultiAuth::SOURCESID]; diff --git a/modules/saml/lib/Auth/Source/SP.php b/modules/saml/lib/Auth/Source/SP.php index 34986aeecde77d5cdeb65e85466f0d5ff13bfeaa..e17726f5a07115c32faabf6278a880edf1e3bf94 100644 --- a/modules/saml/lib/Auth/Source/SP.php +++ b/modules/saml/lib/Auth/Source/SP.php @@ -94,9 +94,9 @@ class SP extends \SimpleSAML\Auth\Source 'authsources[' . var_export($this->authId, true) . ']' ); $this->entityId = $this->metadata->getString('entityID'); - $this->idp = $this->metadata->getString('idp', null); - $this->discoURL = $this->metadata->getString('discoURL', null); - $this->disable_scoping = $this->metadata->getBoolean('disable_scoping', false); + $this->idp = $this->metadata->getOptionalString('idp', null); + $this->discoURL = $this->metadata->getOptionalString('discoURL', null); + $this->disable_scoping = $this->metadata->getOptionalBoolean('disable_scoping', false); } @@ -141,7 +141,7 @@ class SP extends \SimpleSAML\Auth\Source if ($this->metadata->hasValue('NameIDPolicy')) { $format = $this->metadata->getValue('NameIDPolicy'); if (is_array($format)) { - $metadata['NameIDFormat'] = Configuration::loadFromArray($format)->getString( + $metadata['NameIDFormat'] = Configuration::loadFromArray($format)->getOptionalString( 'Format', Constants::NAMEID_TRANSIENT ); @@ -151,8 +151,8 @@ class SP extends \SimpleSAML\Auth\Source } // add attributes - $name = $this->metadata->getLocalizedString('name', null); - $attributes = $this->metadata->getArray('attributes', []); + $name = $this->metadata->getOptionalLocalizedString('name', null); + $attributes = $this->metadata->getOptionalArray('attributes', []); if ($name !== null) { if (!empty($attributes)) { $metadata['name'] = $name; @@ -176,11 +176,11 @@ class SP extends \SimpleSAML\Auth\Source } // add organization info - $org = $this->metadata->getLocalizedString('OrganizationName', null); + $org = $this->metadata->getOptionalLocalizedString('OrganizationName', null); if ($org !== null) { $metadata['OrganizationName'] = $org; - $metadata['OrganizationDisplayName'] = $this->metadata->getLocalizedString('OrganizationDisplayName', $org); - $metadata['OrganizationURL'] = $this->metadata->getLocalizedString('OrganizationURL', null); + $metadata['OrganizationDisplayName'] = $this->metadata->getOptionalLocalizedString('OrganizationDisplayName', $org); + $metadata['OrganizationURL'] = $this->metadata->getOptionalLocalizedString('OrganizationURL', null); if ($metadata['OrganizationURL'] === null) { throw new Error\Exception( 'If OrganizationName is set, OrganizationURL must also be set.' @@ -189,18 +189,18 @@ class SP extends \SimpleSAML\Auth\Source } // add contacts - $contacts = $this->metadata->getArray('contacts', []); + $contacts = $this->metadata->getOptionalArray('contacts', []); foreach ($contacts as $contact) { $metadata['contacts'][] = Utils\Config\Metadata::getContact($contact); } // add technical contact $globalConfig = Configuration::getInstance(); - $email = $globalConfig->getString('technicalcontact_email', 'na@example.org'); - if ($email && $email !== 'na@example.org') { + $email = $globalConfig->getOptionalString('technicalcontact_email', 'na@example.org'); + if (!empty($email) && $email !== 'na@example.org') { $contact = [ 'emailAddress' => $email, - 'givenName' => $globalConfig->getString('technicalcontact_name', null), + 'givenName' => $globalConfig->getOptionalString('technicalcontact_name', null), 'contactType' => 'technical', ]; $metadata['contacts'][] = Utils\Config\Metadata::getContact($contact); @@ -339,11 +339,11 @@ class SP extends \SimpleSAML\Auth\Source Constants::BINDING_HTTP_POST, Constants::BINDING_HTTP_ARTIFACT, ]; - if ($this->metadata->getString('ProtocolBinding', '') === Constants::BINDING_HOK_SSO) { + if ($this->metadata->getOptionalString('ProtocolBinding', null) === Constants::BINDING_HOK_SSO) { $default[] = Constants::BINDING_HOK_SSO; } - $bindings = $this->metadata->getArray('acs.Bindings', $default); + $bindings = $this->metadata->getOptionalArray('acs.Bindings', $default); $index = 0; foreach ($bindings as $service) { switch ($service) { @@ -387,10 +387,10 @@ class SP extends \SimpleSAML\Auth\Source private function getSLOEndpoints(): array { $config = Configuration::getInstance(); - $storeType = $config->getString('store.type', 'phpsession'); + $storeType = $config->getOptionalString('store.type', 'phpsession'); $store = StoreFactory::getInstance($storeType); - $bindings = $this->metadata->getArray( + $bindings = $this->metadata->getOptionalArray( 'SingleLogoutServiceBinding', [ Constants::BINDING_HTTP_REDIRECT, @@ -398,7 +398,7 @@ class SP extends \SimpleSAML\Auth\Source ] ); $defaultLocation = Module::getModuleURL('saml/sp/saml2-logout.php/' . $this->getAuthId()); - $location = $this->metadata->getString('SingleLogoutServiceLocation', $defaultLocation); + $location = $this->metadata->getOptionalString('SingleLogoutServiceLocation', $defaultLocation); $endpoints = []; foreach ($bindings as $binding) { @@ -441,7 +441,7 @@ class SP extends \SimpleSAML\Auth\Source $arrayUtils = new Utils\Arrays(); $accr = null; - if ($idpMetadata->getString('AuthnContextClassRef', false)) { + if ($idpMetadata->getOptionalString('AuthnContextClassRef', null) !== null) { $accr = $arrayUtils->arrayize($idpMetadata->getString('AuthnContextClassRef')); } elseif (isset($state['saml:AuthnContextClassRef'])) { $accr = $arrayUtils->arrayize($state['saml:AuthnContextClassRef']); @@ -449,7 +449,7 @@ class SP extends \SimpleSAML\Auth\Source if ($accr !== null) { $comp = Constants::COMPARISON_EXACT; - if ($idpMetadata->getString('AuthnContextComparison', false)) { + if ($idpMetadata->getOptionalString('AuthnContextComparison', null) !== null) { $comp = $idpMetadata->getString('AuthnContextComparison'); } elseif ( isset($state['saml:AuthnContextComparison']) @@ -532,17 +532,17 @@ class SP extends \SimpleSAML\Auth\Source $requesterID = []; /* Only check for real info for Scoping element if we are going to send Scoping element */ - if ($this->disable_scoping !== true && $idpMetadata->getBoolean('disable_scoping', false) !== true) { + if ($this->disable_scoping !== true && $idpMetadata->getOptionalBoolean('disable_scoping', false) !== true) { if (isset($state['saml:IDPList'])) { $IDPList = $state['saml:IDPList']; } if (isset($state['saml:ProxyCount']) && $state['saml:ProxyCount'] !== null) { $ar->setProxyCount($state['saml:ProxyCount']); - } elseif ($idpMetadata->getInteger('ProxyCount', null) !== null) { - $ar->setProxyCount($idpMetadata->getInteger('ProxyCount', null)); - } elseif ($this->metadata->getInteger('ProxyCount', null) !== null) { - $ar->setProxyCount($this->metadata->getInteger('ProxyCount', null)); + } elseif ($idpMetadata->hasValue('ProxyCount')) { + $ar->setProxyCount($idpMetadata->getInteger('ProxyCount')); + } elseif ($this->metadata->hasValue('ProxyCount')) { + $ar->setProxyCount($this->metadata->getInteger('ProxyCount')); } $requesterID = []; @@ -560,8 +560,8 @@ class SP extends \SimpleSAML\Auth\Source $ar->setIDPList( array_unique( array_merge( - $this->metadata->getArray('IDPList', []), - $idpMetadata->getArray('IDPList', []), + $this->metadata->getOptionalArray('IDPList', []), + $idpMetadata->getOptionalArray('IDPList', []), (array) $IDPList ) ) @@ -573,11 +573,11 @@ class SP extends \SimpleSAML\Auth\Source // Otherwise use extensions that might be defined in the local SP (only makes sense in a proxy scenario) if (isset($state['saml:Extensions']) && count($state['saml:Extensions']) > 0) { $ar->setExtensions($state['saml:Extensions']); - } elseif ($this->metadata->getArray('saml:Extensions', null) !== null) { + } elseif ($this->metadata->getOptionalArray('saml:Extensions', null) !== null) { $ar->setExtensions($this->metadata->getArray('saml:Extensions')); } - $providerName = $this->metadata->getString("ProviderName", null); + $providerName = $this->metadata->getOptionalString("ProviderName", null); if ($providerName !== null) { $ar->setProviderName($providerName); } @@ -990,13 +990,13 @@ class SP extends \SimpleSAML\Auth\Source if (isset($state['saml:logout:Extensions']) && count($state['saml:logout:Extensions']) > 0) { $lr->setExtensions($state['saml:logout:Extensions']); - } elseif ($this->metadata->getArray('saml:logout:Extensions', null) !== null) { + } elseif ($this->metadata->getOptionalArray('saml:logout:Extensions', null) !== null) { $lr->setExtensions($this->metadata->getArray('saml:logout:Extensions')); } - $encryptNameId = $idpMetadata->getBoolean('nameid.encryption', null); + $encryptNameId = $idpMetadata->getOptionalBoolean('nameid.encryption', null); if ($encryptNameId === null) { - $encryptNameId = $this->metadata->getBoolean('nameid.encryption', false); + $encryptNameId = $this->metadata->getOptionalBoolean('nameid.encryption', false); } if ($encryptNameId) { $lr->encryptNameId(Module\saml\Message::getEncryptionKey($idpMetadata)); @@ -1141,7 +1141,7 @@ class SP extends \SimpleSAML\Auth\Source if (!empty($state['saml:sp:RelayState'])) { $redirectTo = $state['saml:sp:RelayState']; } else { - $redirectTo = $source->getMetadata()->getString('RelayState', '/'); + $redirectTo = $source->getMetadata()->getOptionalString('RelayState', '/'); } self::handleUnsolicitedAuth($sourceId, $state, $redirectTo); diff --git a/modules/saml/lib/IdP/SAML2.php b/modules/saml/lib/IdP/SAML2.php index f5bbb817d54ef0ace446afa37c9c529a25ee1bde..da86d9c1c78a12b94d24b4380dc9819aebfac538 100644 --- a/modules/saml/lib/IdP/SAML2.php +++ b/modules/saml/lib/IdP/SAML2.php @@ -255,7 +255,7 @@ class SAML2 $skipEndpointValidation = false; if ($authnRequestSigned === true) { - $skipEndpointValidationWhenSigned = $spMetadata->getValue('skipEndpointValidationWhenSigned', false); + $skipEndpointValidationWhenSigned = $spMetadata->getOptionalValue('skipEndpointValidationWhenSigned', false); if (is_bool($skipEndpointValidationWhenSigned) === true) { $skipEndpointValidation = $skipEndpointValidationWhenSigned; } elseif (is_callable($skipEndpointValidationWhenSigned) === true) { @@ -305,13 +305,13 @@ class SAML2 $httpUtils = new Utils\HTTP(); $supportedBindings = [Constants::BINDING_HTTP_POST]; - if ($idpMetadata->getBoolean('saml20.sendartifact', false)) { + if ($idpMetadata->getOptionalBoolean('saml20.sendartifact', false)) { $supportedBindings[] = Constants::BINDING_HTTP_ARTIFACT; } - if ($idpMetadata->getBoolean('saml20.hok.assertion', false)) { + if ($idpMetadata->getOptionalBoolean('saml20.hok.assertion', false)) { $supportedBindings[] = Constants::BINDING_HOK_SSO; } - if ($idpMetadata->getBoolean('saml20.ecp', false)) { + if ($idpMetadata->getOptionalBoolean('saml20.ecp', false)) { $supportedBindings[] = Constants::BINDING_PAOS; } @@ -456,13 +456,13 @@ class SAML2 throw new Exception('Unable to use any of the ACS endpoints found for SP \'' . $spEntityId . '\''); } - $IDPList = array_unique(array_merge($IDPList, $spMetadata->getArrayizeString('IDPList', []))); + $IDPList = array_unique(array_merge($IDPList, $spMetadata->getOptionalArrayizeString('IDPList', []))); if ($ProxyCount === null) { - $ProxyCount = $spMetadata->getInteger('ProxyCount', null); + $ProxyCount = $spMetadata->getOptionalInteger('ProxyCount', null); } if (!$forceAuthn) { - $forceAuthn = $spMetadata->getBoolean('ForceAuthn', false); + $forceAuthn = $spMetadata->getOptionalBoolean('ForceAuthn', false); } $sessionLostParams = [ @@ -662,7 +662,7 @@ class SAML2 'idpEntityID' => $idpMetadata->getString('entityid'), ]); - $spStatsId = $spMetadata->getString('core:statistics-id', $spEntityId); + $spStatsId = $spMetadata->getOptionalString('core:statistics-id', $spEntityId); Logger::stats('saml20-idp-SLO spinit ' . $spStatsId . ' ' . $idpMetadata->getString('entityid')); $state = [ @@ -796,7 +796,7 @@ class SAML2 'entityid' => $entityid, 'SingleSignOnService' => $sso, 'SingleLogoutService' => $slo, - 'NameIDFormat' => $config->getArrayizeString('NameIDFormat', [Constants::NAMEID_TRANSIENT]), + 'NameIDFormat' => $config->getOptionalArrayizeString('NameIDFormat', [Constants::NAMEID_TRANSIENT]), ]; $cryptoUtils = new Utils\Crypto(); @@ -844,7 +844,7 @@ class SAML2 $metadata['keys'] = $keys; // add ArtifactResolutionService endpoint, if enabled - if ($config->getBoolean('saml20.sendartifact', false)) { + if ($config->getOptionalBoolean('saml20.sendartifact', false)) { $metadata['ArtifactResolutionService'][] = [ 'index' => 0, 'Binding' => Constants::BINDING_SOAP, @@ -853,7 +853,7 @@ class SAML2 } // add Holder of Key, if enabled - if ($config->getBoolean('saml20.hok.assertion', false)) { + if ($config->getOptionalBoolean('saml20.hok.assertion', false)) { array_unshift( $metadata['SingleSignOnService'], [ @@ -865,7 +865,7 @@ class SAML2 } // add ECP profile, if enabled - if ($config->getBoolean('saml20.ecp', false)) { + if ($config->getOptionalBoolean('saml20.ecp', false)) { $metadata['SingleSignOnService'][] = [ 'index' => 0, 'Binding' => Constants::BINDING_SOAP, @@ -876,7 +876,7 @@ class SAML2 // add organization information if ($config->hasValue('OrganizationName')) { $metadata['OrganizationName'] = $config->getLocalizedString('OrganizationName'); - $metadata['OrganizationDisplayName'] = $config->getLocalizedString( + $metadata['OrganizationDisplayName'] = $config->getOptionalLocalizedString( 'OrganizationDisplayName', $metadata['OrganizationName'] ); @@ -937,11 +937,11 @@ class SAML2 } $globalConfig = Configuration::getInstance(); - $email = $globalConfig->getString('technicalcontact_email', false); - if ($email && $email !== 'na@example.org') { + $email = $globalConfig->getOptionalString('technicalcontact_email', 'na@example.org'); + if (!empty($email) && $email !== 'na@example.org') { $contact = [ 'emailAddress' => $email, - 'givenName' => $globalConfig->getString('technicalcontact_name', null), + 'givenName' => $globalConfig->getOptionalString('technicalcontact_name', null), 'contactType' => 'technical', ]; $metadata['contacts'][] = Utils\Config\Metadata::getContact($contact); @@ -965,9 +965,9 @@ class SAML2 Configuration $spMetadata, array &$state ): ?string { - $attribute = $spMetadata->getString('simplesaml.nameidattribute', null); + $attribute = $spMetadata->getOptionalString('simplesaml.nameidattribute', null); if ($attribute === null) { - $attribute = $idpMetadata->getString('simplesaml.nameidattribute', null); + $attribute = $idpMetadata->getOptionalString('simplesaml.nameidattribute', null); if ($attribute === null) { Logger::error('Unable to generate NameID. Check the simplesaml.nameidattribute option.'); return null; @@ -1001,9 +1001,9 @@ class SAML2 Configuration $spMetadata, array $attributes ): array { - $base64Attributes = $spMetadata->getBoolean('base64attributes', null); + $base64Attributes = $spMetadata->getOptionalBoolean('base64attributes', null); if ($base64Attributes === null) { - $base64Attributes = $idpMetadata->getBoolean('base64attributes', false); + $base64Attributes = $idpMetadata->getOptionalBoolean('base64attributes', false); } if ($base64Attributes) { @@ -1012,8 +1012,8 @@ class SAML2 $defaultEncoding = 'string'; } - $srcEncodings = $idpMetadata->getArray('attributeencodings', []); - $dstEncodings = $spMetadata->getArray('attributeencodings', []); + $srcEncodings = $idpMetadata->getOptionalArray('attributeencodings', []); + $dstEncodings = $spMetadata->getOptionalArray('attributeencodings', []); /* * Merge the two encoding arrays. Encodings specified in the target metadata @@ -1083,21 +1083,21 @@ class SAML2 Configuration $spMetadata ): string { // try SP metadata first - $attributeNameFormat = $spMetadata->getString('attributes.NameFormat', null); + $attributeNameFormat = $spMetadata->getOptionalString('attributes.NameFormat', null); if ($attributeNameFormat !== null) { return $attributeNameFormat; } - $attributeNameFormat = $spMetadata->getString('AttributeNameFormat', null); + $attributeNameFormat = $spMetadata->getOptionalString('AttributeNameFormat', null); if ($attributeNameFormat !== null) { return $attributeNameFormat; } // look in IdP metadata - $attributeNameFormat = $idpMetadata->getString('attributes.NameFormat', null); + $attributeNameFormat = $idpMetadata->getOptionalString('attributes.NameFormat', null); if ($attributeNameFormat !== null) { return $attributeNameFormat; } - $attributeNameFormat = $idpMetadata->getString('AttributeNameFormat', null); + $attributeNameFormat = $idpMetadata->getOptionalString('AttributeNameFormat', null); if ($attributeNameFormat !== null) { return $attributeNameFormat; } @@ -1129,9 +1129,9 @@ class SAML2 $httpUtils = new Utils\HTTP(); $now = time(); - $signAssertion = $spMetadata->getBoolean('saml20.sign.assertion', null); + $signAssertion = $spMetadata->getOptionalBoolean('saml20.sign.assertion', null); if ($signAssertion === null) { - $signAssertion = $idpMetadata->getBoolean('saml20.sign.assertion', true); + $signAssertion = $idpMetadata->getOptionalBoolean('saml20.sign.assertion', true); } $config = Configuration::getInstance(); @@ -1146,14 +1146,14 @@ class SAML2 $issuer->setFormat(Constants::NAMEID_ENTITY); $a->setIssuer($issuer); - $audience = array_merge([$spMetadata->getString('entityid')], $spMetadata->getArray('audience', [])); + $audience = array_merge([$spMetadata->getString('entityid')], $spMetadata->getOptionalArray('audience', [])); $a->setValidAudiences($audience); $a->setNotBefore($now - 30); - $assertionLifetime = $spMetadata->getInteger('assertion.lifetime', null); + $assertionLifetime = $spMetadata->getOptionalInteger('assertion.lifetime', null); if ($assertionLifetime === null) { - $assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300); + $assertionLifetime = $idpMetadata->getOptionalInteger('assertion.lifetime', 300); } $a->setNotOnOrAfter($now + $assertionLifetime); @@ -1171,7 +1171,7 @@ class SAML2 $sessionStart = $state['AuthnInstant']; } - $sessionLifetime = $config->getInteger('session.duration', 8 * 60 * 60); + $sessionLifetime = $config->getOptionalInteger('session.duration', 8 * 60 * 60); $a->setSessionNotOnOrAfter($sessionStart + $sessionLifetime); $randomUtils = new Utils\Random(); @@ -1190,7 +1190,7 @@ class SAML2 $hokAssertion = true; } if ($hokAssertion === null) { - $hokAssertion = $idpMetadata->getBoolean('saml20.hok.assertion', false); + $hokAssertion = $idpMetadata->getOptionalBoolean('saml20.hok.assertion', false); } if ($hokAssertion) { @@ -1238,7 +1238,7 @@ class SAML2 $a->setSubjectConfirmation([$sc]); // add attributes - if ($spMetadata->getBoolean('simplesaml.attributes', true)) { + if ($spMetadata->getOptionalBoolean('simplesaml.attributes', true)) { $attributeNameFormat = self::getAttributeNameFormat($idpMetadata, $spMetadata); $a->setAttributeNameFormat($attributeNameFormat); $attributes = self::encodeAttributes($idpMetadata, $spMetadata, $state['Attributes']); @@ -1254,9 +1254,11 @@ class SAML2 if ($nameIdFormat === null || !isset($state['saml:NameID'][$nameIdFormat])) { // either not set in request, or not set to a format we supply. Fall back to old generation method - $nameIdFormat = current($spMetadata->getArrayizeString('NameIDFormat', [])); + $nameIdFormat = current($spMetadata->getOptionalArrayizeString('NameIDFormat', [])); if ($nameIdFormat === false) { - $nameIdFormat = current($idpMetadata->getArrayizeString('NameIDFormat', [Constants::NAMEID_TRANSIENT])); + $nameIdFormat = current( + $idpMetadata->getOptionalArrayizeString('NameIDFormat', [Constants::NAMEID_TRANSIENT]) + ); } } @@ -1264,7 +1266,7 @@ class SAML2 $nameId = $state['saml:NameID'][$nameIdFormat]; $nameId->setFormat($nameIdFormat); } else { - $spNameQualifier = $spMetadata->getString('SPNameQualifier', null); + $spNameQualifier = $spMetadata->getOptionalString('SPNameQualifier', null); if ($spNameQualifier === null) { $spNameQualifier = $spMetadata->getString('entityid'); } @@ -1293,9 +1295,9 @@ class SAML2 $a->setNameId($nameId); - $encryptNameId = $spMetadata->getBoolean('nameid.encryption', null); + $encryptNameId = $spMetadata->getOptionalBoolean('nameid.encryption', null); if ($encryptNameId === null) { - $encryptNameId = $idpMetadata->getBoolean('nameid.encryption', false); + $encryptNameId = $idpMetadata->getOptionalBoolean('nameid.encryption', false); } if ($encryptNameId) { $a->encryptNameId(\SimpleSAML\Module\saml\Message::getEncryptionKey($spMetadata)); @@ -1324,9 +1326,9 @@ class SAML2 Configuration $spMetadata, Assertion $assertion ) { - $encryptAssertion = $spMetadata->getBoolean('assertion.encryption', null); + $encryptAssertion = $spMetadata->getOptionalBoolean('assertion.encryption', null); if ($encryptAssertion === null) { - $encryptAssertion = $idpMetadata->getBoolean('assertion.encryption', false); + $encryptAssertion = $idpMetadata->getOptionalBoolean('assertion.encryption', false); } if (!$encryptAssertion) { // we are _not_ encrypting this assertion, and are therefore done @@ -1334,12 +1336,12 @@ class SAML2 } - $sharedKey = $spMetadata->getString('sharedkey', null); + $sharedKey = $spMetadata->getOptionalString('sharedkey', null); if ($sharedKey !== null) { - $algo = $spMetadata->getString('sharedkey_algorithm', null); + $algo = $spMetadata->getOptionalString('sharedkey_algorithm', null); if ($algo === null) { // If no algorithm is configured, use a sane default - $algo = $idpMetadata->getString('sharedkey_algorithm', XMLSecurityKey::AES128_GCM); + $algo = $idpMetadata->getOptionalString('sharedkey_algorithm', XMLSecurityKey::AES128_GCM); } $key = new XMLSecurityKey($algo); @@ -1397,15 +1399,15 @@ class SAML2 $lr->setSessionIndex($association['saml:SessionIndex']); $lr->setNameId($association['saml:NameID']); - $assertionLifetime = $spMetadata->getInteger('assertion.lifetime', null); + $assertionLifetime = $spMetadata->getOptionalInteger('assertion.lifetime', null); if ($assertionLifetime === null) { - $assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300); + $assertionLifetime = $idpMetadata->getOptionalInteger('assertion.lifetime', 300); } $lr->setNotOnOrAfter(time() + $assertionLifetime); - $encryptNameId = $spMetadata->getBoolean('nameid.encryption', null); + $encryptNameId = $spMetadata->getOptionalBoolean('nameid.encryption', null); if ($encryptNameId === null) { - $encryptNameId = $idpMetadata->getBoolean('nameid.encryption', false); + $encryptNameId = $idpMetadata->getOptionalBoolean('nameid.encryption', false); } if ($encryptNameId) { $lr->encryptNameId(\SimpleSAML\Module\saml\Message::getEncryptionKey($spMetadata)); @@ -1429,9 +1431,9 @@ class SAML2 Configuration $spMetadata, string $consumerURL ): Response { - $signResponse = $spMetadata->getBoolean('saml20.sign.response', null); + $signResponse = $spMetadata->getOptionalBoolean('saml20.sign.response', null); if ($signResponse === null) { - $signResponse = $idpMetadata->getBoolean('saml20.sign.response', true); + $signResponse = $idpMetadata->getOptionalBoolean('saml20.sign.response', true); } $r = new Response(); diff --git a/modules/saml/lib/IdP/SQLNameID.php b/modules/saml/lib/IdP/SQLNameID.php index b0dda950c285e032be7d928d63e59545f1b5ef33..24e2e0f85c49d2c15865f3e67955bc911666ed0c 100644 --- a/modules/saml/lib/IdP/SQLNameID.php +++ b/modules/saml/lib/IdP/SQLNameID.php @@ -159,7 +159,7 @@ class SQLNameID private static function getStore(): Store\SQLStore { $config = Configuration::getInstance(); - $storeType = $config->getString('store.type', 'phpsession'); + $storeType = $config->getOptionalString('store.type', 'phpsession'); $store = StoreFactory::getInstance($storeType); Assert::isInstanceOf( diff --git a/modules/saml/lib/Message.php b/modules/saml/lib/Message.php index d69935c5cc92be7759a0701d66ea1c3998a035ef..32108fc3eaed8088b9c3b3c79ae915efdf521054 100644 --- a/modules/saml/lib/Message.php +++ b/modules/saml/lib/Message.php @@ -43,7 +43,7 @@ class Message Configuration $dstMetadata, SignedElement $element ): void { - $dstPrivateKey = $dstMetadata->getString('signature.privatekey', null); + $dstPrivateKey = $dstMetadata->getOptionalString('signature.privatekey', null); $cryptoUtils = new Utils\Crypto(); if ($dstPrivateKey !== null) { @@ -56,9 +56,9 @@ class Message $certArray = $cryptoUtils->loadPublicKey($srcMetadata, false); } - $algo = $dstMetadata->getString('signature.algorithm', null); + $algo = $dstMetadata->getOptionalString('signature.algorithm', null); if ($algo === null) { - $algo = $srcMetadata->getString('signature.algorithm', XMLSecurityKey::RSA_SHA256); + $algo = $srcMetadata->getOptionalString('signature.algorithm', XMLSecurityKey::RSA_SHA256); } $privateKey = new XMLSecurityKey($algo, ['type' => 'private']); @@ -97,21 +97,21 @@ class Message ): void { $signingEnabled = null; if ($message instanceof LogoutRequest || $message instanceof LogoutResponse) { - $signingEnabled = $srcMetadata->getBoolean('sign.logout', null); + $signingEnabled = $srcMetadata->getOptionalBoolean('sign.logout', null); if ($signingEnabled === null) { - $signingEnabled = $dstMetadata->getBoolean('sign.logout', null); + $signingEnabled = $dstMetadata->getOptionalBoolean('sign.logout', null); } } elseif ($message instanceof AuthnRequest) { - $signingEnabled = $srcMetadata->getBoolean('sign.authnrequest', null); + $signingEnabled = $srcMetadata->getOptionalBoolean('sign.authnrequest', null); if ($signingEnabled === null) { - $signingEnabled = $dstMetadata->getBoolean('sign.authnrequest', null); + $signingEnabled = $dstMetadata->getOptionalBoolean('sign.authnrequest', null); } } if ($signingEnabled === null) { - $signingEnabled = $dstMetadata->getBoolean('redirect.sign', null); + $signingEnabled = $dstMetadata->getOptionalBoolean('redirect.sign', null); if ($signingEnabled === null) { - $signingEnabled = $srcMetadata->getBoolean('redirect.sign', false); + $signingEnabled = $srcMetadata->getOptionalBoolean('redirect.sign', false); } } if (!$signingEnabled) { @@ -203,14 +203,14 @@ class Message ): bool { $enabled = null; if ($message instanceof LogoutRequest || $message instanceof LogoutResponse) { - $enabled = $srcMetadata->getBoolean('validate.logout', null); + $enabled = $srcMetadata->getOptionalBoolean('validate.logout', null); if ($enabled === null) { - $enabled = $dstMetadata->getBoolean('validate.logout', null); + $enabled = $dstMetadata->getOptionalBoolean('validate.logout', null); } } elseif ($message instanceof AuthnRequest) { - $enabled = $srcMetadata->getBoolean('validate.authnrequest', null); + $enabled = $srcMetadata->getOptionalBoolean('validate.authnrequest', null); if ($enabled === null) { - $enabled = $dstMetadata->getBoolean('validate.authnrequest', null); + $enabled = $dstMetadata->getOptionalBoolean('validate.authnrequest', null); } } @@ -222,9 +222,9 @@ class Message ) { $enabled = true; } elseif ($enabled === null) { - $enabled = $srcMetadata->getBoolean('redirect.validate', null); + $enabled = $srcMetadata->getOptionalBoolean('redirect.validate', null); if ($enabled === null) { - $enabled = $dstMetadata->getBoolean('redirect.validate', false); + $enabled = $dstMetadata->getOptionalBoolean('redirect.validate', false); } } @@ -254,15 +254,15 @@ class Message Configuration $dstMetadata, $encryptionMethod = null ): array { - $sharedKey = $srcMetadata->getString('sharedkey', null); + $sharedKey = $srcMetadata->getOptionalString('sharedkey', null); if ($sharedKey !== null) { if ($encryptionMethod !== null) { $algo = $encryptionMethod->getAlgorithm(); } else { - $algo = $srcMetadata->getString('sharedkey_algorithm', null); + $algo = $srcMetadata->getOptionalString('sharedkey_algorithm', null); if ($algo === null) { // If no algorithm is supplied or configured, use a sane default as a last resort - $algo = $dstMetadata->getString('sharedkey_algorithm', XMLSecurityKey::AES128_GCM); + $algo = $dstMetadata->getOptionalString('sharedkey_algorithm', XMLSecurityKey::AES128_GCM); } } @@ -320,9 +320,9 @@ class Message Configuration $srcMetadata, Configuration $dstMetadata ): array { - $blacklist = $srcMetadata->getArray('encryption.blacklisted-algorithms', null); + $blacklist = $srcMetadata->getOptionalArray('encryption.blacklisted-algorithms', null); if ($blacklist === null) { - $blacklist = $dstMetadata->getArray('encryption.blacklisted-algorithms', [XMLSecurityKey::RSA_1_5]); + $blacklist = $dstMetadata->getOptionalArray('encryption.blacklisted-algorithms', [XMLSecurityKey::RSA_1_5]); } return $blacklist; } @@ -349,9 +349,9 @@ class Message Assert::isInstanceOfAny($assertion, [Assertion::class, EncryptedAssertion::class]); if ($assertion instanceof Assertion) { - $encryptAssertion = $srcMetadata->getBoolean('assertion.encryption', null); + $encryptAssertion = $srcMetadata->getOptionalBoolean('assertion.encryption', null); if ($encryptAssertion === null) { - $encryptAssertion = $dstMetadata->getBoolean('assertion.encryption', false); + $encryptAssertion = $dstMetadata->getOptionalBoolean('assertion.encryption', false); } if ($encryptAssertion) { /* The assertion was unencrypted, but we have encryption enabled. */ @@ -480,10 +480,10 @@ class Message $ar->setNameIdPolicy($policy); } - $ar->setForceAuthn($spMetadata->getBoolean('ForceAuthn', false)); - $ar->setIsPassive($spMetadata->getBoolean('IsPassive', false)); + $ar->setForceAuthn($spMetadata->getOptionalBoolean('ForceAuthn', false)); + $ar->setIsPassive($spMetadata->getOptionalBoolean('IsPassive', false)); - $protbind = $spMetadata->getValueValidate('ProtocolBinding', [ + $protbind = $spMetadata->getOptionalValueValidate('ProtocolBinding', [ Constants::BINDING_HTTP_POST, Constants::BINDING_HOK_SSO, Constants::BINDING_HTTP_ARTIFACT, @@ -495,12 +495,16 @@ class Message $issuer = new Issuer(); $issuer->setValue($spMetadata->getString('entityid')); $ar->setIssuer($issuer); - $ar->setAssertionConsumerServiceIndex($spMetadata->getInteger('AssertionConsumerServiceIndex', null)); - $ar->setAttributeConsumingServiceIndex($spMetadata->getInteger('AttributeConsumingServiceIndex', null)); + $ar->setAssertionConsumerServiceIndex( + $spMetadata->getOptionalInteger('AssertionConsumerServiceIndex', null) + ); + $ar->setAttributeConsumingServiceIndex( + $spMetadata->getOptionalInteger('AttributeConsumingServiceIndex', null) + ); if ($spMetadata->hasValue('AuthnContextClassRef')) { $accr = $spMetadata->getArrayizeString('AuthnContextClassRef'); - $comp = $spMetadata->getValueValidate('AuthnContextComparison', [ + $comp = $spMetadata->getOptionalValueValidate('AuthnContextComparison', [ Constants::COMPARISON_EXACT, Constants::COMPARISON_MINIMUM, Constants::COMPARISON_MAXIMUM, @@ -652,7 +656,7 @@ class Message // check various properties of the assertion $config = Configuration::getInstance(); - $allowed_clock_skew = $config->getInteger('assertion.allowed_clock_skew', 180); + $allowed_clock_skew = $config->getOptionalInteger('assertion.allowed_clock_skew', 180); $options = [ 'options' => [ 'default' => 180, @@ -702,9 +706,9 @@ class Message } // is SSO with HoK enabled? IdP remote metadata overwrites SP metadata configuration - $hok = $idpMetadata->getBoolean('saml20.hok.assertion', null); + $hok = $idpMetadata->getOptionalBoolean('saml20.hok.assertion', null); if ($hok === null) { - $hok = $spMetadata->getBoolean('saml20.hok.assertion', false); + $hok = $spMetadata->getOptionalBoolean('saml20.hok.assertion', false); } if ($method === Constants::CM_BEARER && $hok) { $lastError = 'Bearer SubjectConfirmation received, but Holder-of-Key SubjectConfirmation needed'; @@ -824,7 +828,7 @@ class Message // as far as we can tell, the assertion is valid // maybe we need to base64 decode the attributes in the assertion? - if ($idpMetadata->getBoolean('base64attributes', false)) { + if ($idpMetadata->getOptionalBoolean('base64attributes', false)) { $attributes = $assertion->getAttributes(); $newAttributes = []; foreach ($attributes as $name => $values) { @@ -880,7 +884,7 @@ class Message */ public static function getEncryptionKey(Configuration $metadata): XMLSecurityKey { - $sharedKey = $metadata->getString('sharedkey', null); + $sharedKey = $metadata->getOptionalString('sharedkey', null); if ($sharedKey !== null) { $key = new XMLSecurityKey(XMLSecurityKey::AES128_CBC); $key->loadKey($sharedKey); diff --git a/modules/saml/lib/SP/LogoutStore.php b/modules/saml/lib/SP/LogoutStore.php index 6911e0eb70b452dafc8fd48c1c592e50408cce50..09f46cc1fc73231c9466e7da566870e79564f4e0 100644 --- a/modules/saml/lib/SP/LogoutStore.php +++ b/modules/saml/lib/SP/LogoutStore.php @@ -214,7 +214,7 @@ class LogoutStore } $config = Configuration::getInstance(); - $storeType = $config->getString('store.type', 'phpsession'); + $storeType = $config->getOptionalString('store.type', 'phpsession'); $store = StoreFactory::getInstance($storeType); if ($store === false) { @@ -253,7 +253,7 @@ class LogoutStore public static function logoutSessions(string $authId, NameID $nameId, array $sessionIndexes) { $config = Configuration::getInstance(); - $storeType = $config->getString('store.type', 'phpsession'); + $storeType = $config->getOptionalString('store.type', 'phpsession'); $store = StoreFactory::getInstance($storeType); if ($store === false) { diff --git a/modules/saml/www/sp/metadata.php b/modules/saml/www/sp/metadata.php index f330dd7c9266e7219e4f021bc1d8fec238acb817..4992710fbee9066de45a92f2ad0318876df0641b 100644 --- a/modules/saml/www/sp/metadata.php +++ b/modules/saml/www/sp/metadata.php @@ -13,7 +13,7 @@ if (!array_key_exists('PATH_INFO', $_SERVER)) { } $config = Configuration::getInstance(); -if ($config->getBoolean('admin.protectmetadata', false)) { +if ($config->getOptionalBoolean('admin.protectmetadata', false)) { $authUtils = new Utils\Auth(); $authUtils->requireAdmin(); } @@ -34,7 +34,7 @@ $entityId = $source->getEntityId(); $spconfig = $source->getMetadata(); $metaArray20 = $source->getHostedMetadata(); -$storeType = $config->getString('store.type', 'phpsession'); +$storeType = $config->getOptionalString('store.type', 'phpsession'); $store = StoreFactory::getInstance($storeType); $metaBuilder = new Metadata\SAMLBuilder($entityId); diff --git a/modules/saml/www/sp/saml2-acs.php b/modules/saml/www/sp/saml2-acs.php index 3da83aa49567b33b6132e80e5e222e61f68a53ce..091a657813690d08647599358bf8d24d0a89cc06 100644 --- a/modules/saml/www/sp/saml2-acs.php +++ b/modules/saml/www/sp/saml2-acs.php @@ -101,7 +101,7 @@ if (!empty($stateId)) { } } -$enableUnsolicited = $spMetadata->getBoolean('enable_unsolicited', true); +$enableUnsolicited = $spMetadata->getOptionalBoolean('enable_unsolicited', true); if ($state === null && $enableUnsolicited === false) { throw new Error\BadRequest('Unsolicited responses are denied by configuration.'); } @@ -119,7 +119,7 @@ if ($state) { Assert::keyExists($state, 'ExpectedIssuer'); if ($state['ExpectedIssuer'] !== $issuer) { $idpMetadata = $source->getIdPMetadata($issuer); - $idplist = $idpMetadata->getArrayize('IDPList', []); + $idplist = $idpMetadata->getOptionalArrayize('IDPList', []); if (!in_array($state['ExpectedIssuer'], $idplist, true)) { Logger::warning( 'The issuer of the response not match to the identity provider we sent the request to.' @@ -128,7 +128,7 @@ if ($state) { } } else { // this is an unsolicited response - $relaystate = $spMetadata->getString('RelayState', $response->getRelayState()); + $relaystate = $spMetadata->getOptionalString('RelayState', $response->getRelayState()); $state = [ 'saml:sp:isUnsolicited' => true, 'saml:sp:AuthId' => $sourceId, @@ -159,7 +159,7 @@ $attributes = []; $foundAuthnStatement = false; $config = Configuration::getInstance(); -$storeType = $config->getString('store.type', 'phpsession'); +$storeType = $config->getOptionalString('store.type', 'phpsession'); $store = StoreFactory::getInstance($storeType); diff --git a/tests/lib/SimpleSAML/ConfigurationTest.php b/tests/lib/SimpleSAML/ConfigurationTest.php index 7950667680e9eeb135ad10c7f5d9845716d3f35b..8509797ca209c2919b17bb3793c0504ea923fe0d 100644 --- a/tests/lib/SimpleSAML/ConfigurationTest.php +++ b/tests/lib/SimpleSAML/ConfigurationTest.php @@ -6,6 +6,7 @@ namespace SimpleSAML\Test; use Exception; use SAML2\Constants; +use SimpleSAML\Assert\AssertionFailedException; use SimpleSAML\Configuration; use SimpleSAML\Error; use SimpleSAML\TestUtils\ClearStateTestCase; @@ -68,25 +69,31 @@ class ConfigurationTest extends ClearStateTestCase 'exists_true' => true, 'exists_null' => null, ]); - $this->assertEquals($c->getValue('missing'), null); - $this->assertEquals($c->getValue('missing', true), true); - $this->assertEquals($c->getValue('missing', true), true); - $this->assertEquals($c->getValue('exists_true'), true); + // Normal use + $this->assertTrue($c->getValue('exists_true')); + $this->assertNull($c->getValue('exists_null')); - $this->assertEquals($c->getValue('exists_null'), null); - $this->assertEquals($c->getValue('exists_null', false), null); + // Missing option + $this->expectException(AssertionFailedException::class); + $c->getValue('missing'); } /** - * Test \SimpleSAML\Configuration::getValue(), REQUIRED_OPTION flag. + * Test \SimpleSAML\Configuration::getOptionalValue(). */ - public function testGetValueRequired(): void + public function testGetOptionalValue(): void { - $this->expectException(Exception::class); - $c = Configuration::loadFromArray([]); - $c->getValue('missing', Configuration::REQUIRED_OPTION); + $c = Configuration::loadFromArray([ + 'exists_true' => true, + ]); + + // Normal use + $this->assertTrue($c->getOptionalValue('exists_true', 'something else')); + + // Missing option + $this->assertNull($c->getOptionalValue('missing', null)); } @@ -249,34 +256,48 @@ class ConfigurationTest extends ClearStateTestCase $c = Configuration::loadFromArray([ 'true_opt' => true, 'false_opt' => false, + 'wrong_opt' => 'true', ]); - $this->assertEquals($c->getBoolean('missing_opt', '--missing--'), '--missing--'); - $this->assertEquals($c->getBoolean('true_opt', '--missing--'), true); - $this->assertEquals($c->getBoolean('false_opt', '--missing--'), false); - } + // Normal use + $this->assertTrue($c->getBoolean('true_opt')); + $this->assertFalse($c->getBoolean('false_opt')); - /** - * Test \SimpleSAML\Configuration::getBoolean() missing option - */ - public function testGetBooleanMissing(): void - { - $this->expectException(Exception::class); - $c = Configuration::loadFromArray([]); + // Missing option + $this->expectException(AssertionFailedException::class); $c->getBoolean('missing_opt'); + + // Invalid option type + $this->expectException(AssertionFailedException::class); + $c->getBoolean('wrong_opt'); } /** - * Test \SimpleSAML\Configuration::getBoolean() wrong option + * Test \SimpleSAML\Configuration::getOptionalBoolean() */ - public function testGetBooleanWrong(): void + public function testGetOptionalBoolean(): void { - $this->expectException(Exception::class); $c = Configuration::loadFromArray([ - 'wrong' => 'true', + 'true_opt' => true, + 'false_opt' => false, + 'wrong_opt' => 'true', ]); - $c->getBoolean('wrong'); + + // Normal use + $this->assertTrue($c->getOptionalBoolean('true_opt', true)); + $this->assertTrue($c->getOptionalBoolean('true_opt', false)); + $this->assertFalse($c->getOptionalBoolean('false_opt', false)); + $this->assertFalse($c->getOptionalBoolean('false_opt', true)); + + // Missing option + $this->assertEquals($c->getOptionalBoolean('missing_opt', null), null); + $this->assertEquals($c->getOptionalBoolean('missing_opt', false), false); + $this->assertEquals($c->getOptionalBoolean('missing_opt', true), true); + + // Invalid option type + $this->expectException(AssertionFailedException::class); + $c->getOptionalBoolean('wrong_opt', null); } @@ -287,33 +308,42 @@ class ConfigurationTest extends ClearStateTestCase { $c = Configuration::loadFromArray([ 'str_opt' => 'Hello World!', + 'wrong_opt' => true, ]); - $this->assertEquals($c->getString('missing_opt', '--missing--'), '--missing--'); - $this->assertEquals($c->getString('str_opt', '--missing--'), 'Hello World!'); - } + // Normal use + $this->assertEquals($c->getString('str_opt'), 'Hello World!'); - /** - * Test \SimpleSAML\Configuration::getString() missing option - */ - public function testGetStringMissing(): void - { - $this->expectException(Exception::class); - $c = Configuration::loadFromArray([]); + // Missing option + $this->expectException(AssertionFailedException::class); $c->getString('missing_opt'); + + // Invalid option type + $this->expectException(AssertionFailedException::class); + $c->getString('wrong_opt'); } /** - * Test \SimpleSAML\Configuration::getString() wrong option + * Test \SimpleSAML\Configuration::getOptionalString() missing option */ - public function testGetStringWrong(): void + public function testGetOptionalString(): void { - $this->expectException(Exception::class); $c = Configuration::loadFromArray([ - 'wrong' => false, + 'str_opt' => 'Hello World!', + 'wrong_opt' => true, ]); - $c->getString('wrong'); + + // Normal use + $this->assertEquals($c->getOptionalString('str_opt', 'Hello World!'), 'Hello World!'); + $this->assertEquals($c->getOptionalString('str_opt', 'something else'), 'Hello World!'); + + // Missing option + $this->assertEquals($c->getOptionalString('missing_opt', 'Hello World!'), 'Hello World!'); + + // Invalid option type + $this->expectException(AssertionFailedException::class); + $c->getOptionalString('wrong_opt', 'Hello World!'); } @@ -324,33 +354,42 @@ class ConfigurationTest extends ClearStateTestCase { $c = Configuration::loadFromArray([ 'int_opt' => 42, + 'wrong_opt' => 'test', ]); - $this->assertEquals($c->getInteger('missing_opt', '--missing--'), '--missing--'); - $this->assertEquals($c->getInteger('int_opt', '--missing--'), 42); - } + // Normal use + $this->assertEquals($c->getInteger('int_opt'), 42); - /** - * Test \SimpleSAML\Configuration::getInteger() missing option - */ - public function testGetIntegerMissing(): void - { - $this->expectException(Exception::class); - $c = Configuration::loadFromArray([]); + // Missing option + $this->expectException(AssertionFailedException::class); $c->getInteger('missing_opt'); + + // Invalid option type + $this->expectException(AssertionFailedException::class); + $c->getInteger('wrong_opt'); } /** - * Test \SimpleSAML\Configuration::getInteger() wrong option + * Test \SimpleSAML\Configuration::getOptionalInteger() */ - public function testGetIntegerWrong(): void + public function testGetOptionalInteger(): void { - $this->expectException(Exception::class); $c = Configuration::loadFromArray([ - 'wrong' => '42', + 'int_opt' => 42, + 'wrong_opt' => 'test', ]); - $c->getInteger('wrong'); + + + // Normal use + $this->assertEquals($c->getOptionalInteger('int_opt', 42), 42); + + // Missing option + $this->assertEquals($c->getOptionalInteger('missing_opt', 32), 32); + + // Invalid option type + $this->expectException(AssertionFailedException::class); + $c->getOptionalInteger('wrong_opt', 10); } @@ -360,36 +399,63 @@ class ConfigurationTest extends ClearStateTestCase public function testGetIntegerRange(): void { $c = Configuration::loadFromArray([ - 'int_opt' => 42, + 'min_opt' => 0, + 'max_opt' => 100, + 'wrong_opt' => 'test', ]); - $this->assertEquals($c->getIntegerRange('missing_opt', 0, 100, '--missing--'), '--missing--'); - $this->assertEquals($c->getIntegerRange('int_opt', 0, 100), 42); - } + // Normal use + $this->assertEquals($c->getIntegerRange('min_opt', 0, 100), 0); + $this->assertEquals($c->getIntegerRange('max_opt', 0, 100), 100); - /** - * Test \SimpleSAML\Configuration::getIntegerRange() below limit - */ - public function testGetIntegerRangeBelow(): void - { - $this->expectException(Exception::class); - $c = Configuration::loadFromArray([ - 'int_opt' => 9, - ]); - $this->assertEquals($c->getIntegerRange('int_opt', 10, 100), 42); + // Missing option + $this->expectException(AssertionFailedException::class); + $c->getIntegerRange('missing_opt', 0, 100); + + // Invalid option type + $this->expectException(AssertionFailedException::class); + $c->getIntegerRange('wrong_opt', 0, 100); + + // Below range + $this->expectException(AssertionFailedException::class); + $c->getIntegerRange('min_opt', 1, 100); + + // Above range + $this->expectException(AssertionFailedException::class); + $c->getIntegerRange('max_opt', 0, 99); } /** - * Test \SimpleSAML\Configuration::getIntegerRange() above limit + * Test \SimpleSAML\Configuration::getOptionalIntegerRange() */ - public function testGetIntegerRangeAbove(): void + public function testGetOptionalIntegerRange(): void { - $this->expectException(Exception::class); $c = Configuration::loadFromArray([ - 'int_opt' => 101, + 'min_opt' => 0, + 'max_opt' => 100, + 'wrong_opt' => 'test', ]); - $this->assertEquals($c->getIntegerRange('int_opt', 10, 100), 42); + + + // Normal use + $this->assertEquals($c->getOptionalIntegerRange('min_opt', 0, 100, 50), 0); + $this->assertEquals($c->getOptionalIntegerRange('max_opt', 0, 100, 50), 100); + + // Missing option + $this->assertEquals($c->getOptionalIntegerRange('missing_opt', 0, 100, 50), 50); + + // Invalid option type + $this->expectException(AssertionFailedException::class); + $c->getOptionalIntegerRange('wrong_opt', 0, 100, null); + + // Below range + $this->expectException(AssertionFailedException::class); + $c->getOptionalIntegerRange('min_opt', 1, 100, null); + + // Above range + $this->expectException(AssertionFailedException::class); + $c->getOptionalIntegerRange('max_opt', 0, 99, null); } @@ -401,21 +467,39 @@ class ConfigurationTest extends ClearStateTestCase $c = Configuration::loadFromArray([ 'opt' => 'b', ]); - $this->assertEquals($c->getValueValidate('missing_opt', ['a', 'b', 'c'], '--missing--'), '--missing--'); + + // Normal use $this->assertEquals($c->getValueValidate('opt', ['a', 'b', 'c']), 'b'); + + // Value not allowed + $this->expectException(AssertionFailedException::class); + $c->getValueValidate('opt', ['d', 'e', 'f']); + + // Missing option + $this->expectException(AssertionFailedException::class); + $c->getValueValidate('missing_opt', ['a', 'b', 'c']); } /** - * Test \SimpleSAML\Configuration::getValueValidate() wrong option + * Test \SimpleSAML\Configuration::getOptionalValueValidate() */ - public function testGetValueValidateWrong(): void + public function testGetOptionalValueValidate(): void { - $this->expectException(Exception::class); $c = Configuration::loadFromArray([ - 'opt' => 'd', + 'opt' => 'b', ]); - $c->getValueValidate('opt', ['a', 'b', 'c']); + + // Normal use + $this->assertEquals($c->getOptionalValueValidate('opt', ['a', 'b', 'c'], 'f'), 'b'); + + // Missing option + $this->assertEquals($c->getOptionalValueValidate('missing_opt', ['a', 'b', 'c'], 'b'), 'b'); + + // Value not allowed + $this->expectException(AssertionFailedException::class); + $c->getOptionalValueValidate('opt', ['d', 'e', 'f'], 'c'); + $c->getOptionalValueValidate('missing_opt', ['d', 'e', 'f'], 'c'); } @@ -426,22 +510,41 @@ class ConfigurationTest extends ClearStateTestCase { $c = Configuration::loadFromArray([ 'opt' => ['a', 'b', 'c'], + 'wrong_opt' => false, ]); - $this->assertEquals($c->getArray('missing_opt', '--missing--'), '--missing--'); + + // Normal use $this->assertEquals($c->getArray('opt'), ['a', 'b', 'c']); + + // Missing option + $this->expectException(AssertionFailedException::class); + $c->getArray('missing_opt'); + + // Value not allowed + $this->expectException(AssertionFailedException::class); + $c->getArray('wrong_opt'); } /** - * Test \SimpleSAML\Configuration::getArray() wrong option + * Test \SimpleSAML\Configuration::getOptionalArray() */ - public function testGetArrayWrong(): void + public function testGetOptionalArray(): void { - $this->expectException(Exception::class); $c = Configuration::loadFromArray([ - 'opt' => 'not_an_array', + 'opt' => ['a', 'b', 'c'], + 'wrong_opt' => false, ]); - $c->getArray('opt'); + + // Normal use + $this->assertEquals($c->getOptionalArray('opt', ['d', 'e', 'f']), ['a', 'b', 'c']); + + // Missing option + $this->assertEquals($c->getOptionalArray('missing_opt', ['d', 'e', 'f']), ['d', 'e', 'f']); + + // Value not allowed + $this->expectException(AssertionFailedException::class); + $c->getArray('wrong_opt'); } @@ -455,10 +558,36 @@ class ConfigurationTest extends ClearStateTestCase 'opt_int' => 42, 'opt_str' => 'string', ]); - $this->assertEquals($c->getArrayize('missing_opt', '--missing--'), '--missing--'); + + // Normal use $this->assertEquals($c->getArrayize('opt'), ['a', 'b', 'c']); $this->assertEquals($c->getArrayize('opt_int'), [42]); $this->assertEquals($c->getArrayize('opt_str'), ['string']); + + // Missing option + $this->expectException(AssertionFailedException::class); + $c->getArrayize('missing_opt'); + } + + + /** + * Test \SimpleSAML\Configuration::getOptionalArrayize() + */ + public function testGetOptionalArrayize(): void + { + $c = Configuration::loadFromArray([ + 'opt' => ['a', 'b', 'c'], + 'opt_int' => 42, + 'opt_str' => 'string', + ]); + + // Normal use + $this->assertEquals($c->getOptionalArrayize('opt', ['d']), ['a', 'b', 'c']); + $this->assertEquals($c->getOptionalArrayize('opt_int', [1]), [42]); + $this->assertEquals($c->getOptionalArrayize('opt_str', ['test']), ['string']); + + // Missing option + $this->assertEquals($c->getOptionalArrayize('missing_opt', ['test']), ['test']); } @@ -470,24 +599,44 @@ class ConfigurationTest extends ClearStateTestCase $c = Configuration::loadFromArray([ 'opt' => ['a', 'b', 'c'], 'opt_str' => 'string', + 'opt_wrong' => 4, ]); - $this->assertEquals($c->getArrayizeString('missing_opt', '--missing--'), '--missing--'); + + // Normale use $this->assertEquals($c->getArrayizeString('opt'), ['a', 'b', 'c']); $this->assertEquals($c->getArrayizeString('opt_str'), ['string']); + + // Missing option + $this->expectException(AssertionFailedException::class); + $c->getArrayizeString('missing_opt'); + + // Wrong option + $this->expectException(AssertionFailedException::class); + $c->getArrayizeString('opt_wrong'); } /** - * Test \SimpleSAML\Configuration::getArrayizeString() option - * with an array that contains something that isn't a string. + * Test \SimpleSAML\Configuration::getOptionalArrayizeString() */ - public function testGetArrayizeStringWrongValue(): void + public function testGetOptionalArrayizeString(): void { - $this->expectException(Exception::class); $c = Configuration::loadFromArray([ - 'opt' => ['a', 'b', 42], + 'opt' => ['a', 'b', 'c'], + 'opt_str' => 'string', + 'opt_wrong' => 4, ]); - $c->getArrayizeString('opt'); + + // Normale use + $this->assertEquals($c->getOptionalArrayizeString('opt', ['d']), ['a', 'b', 'c']); + $this->assertEquals($c->getOptionalArrayizeString('opt_str', ['test']), ['string']); + + // Missing option + $this->assertEquals($c->getOptionalArrayizeString('missing_opt', ['test']), ['test']); + + // Wrong option + $this->expectException(AssertionFailedException::class); + $c->getOptionalArrayizeString('opt_wrong', ['test']); } @@ -499,25 +648,30 @@ class ConfigurationTest extends ClearStateTestCase $c = Configuration::loadFromArray([ 'opt' => ['a' => 42], ]); - $this->assertNull($c->getConfigItem('missing_opt', null)); + $opt = $c->getConfigItem('opt'); - $notOpt = $c->getConfigItem('notOpt'); $this->assertInstanceOf(Configuration::class, $opt); - $this->assertInstanceOf(Configuration::class, $notOpt); - $this->assertEquals($opt->getValue('a'), 42); + + // Missing option + $this->expectException(AssertionFailedException::class); + $c->getConfigItem('missing_opt'); } /** - * Test \SimpleSAML\Configuration::getConfigItem() wrong option + * Test \SimpleSAML\Configuration::getOptionalConfigItem() */ - public function testGetConfigItemWrong(): void + public function testGetOptionalConfigItem(): void { - $this->expectException(Exception::class); $c = Configuration::loadFromArray([ - 'opt' => 'not_an_array', + 'opt' => ['a' => 42], ]); - $c->getConfigItem('opt'); + + $opt = $c->getOptionalConfigItem('opt', null); + $this->assertInstanceOf(Configuration::class, $opt); + + // Missing option + $this->assertNull($c->getOptionalConfigItem('missing_opt', null)); } @@ -858,9 +1012,11 @@ class ConfigurationTest extends ClearStateTestCase 'no' => 'Hei Verden!', ], ]); - $this->assertEquals($c->getLocalizedString('missing_opt', '--missing--'), '--missing--'); $this->assertEquals($c->getLocalizedString('str_opt'), ['en' => 'Hello World!']); $this->assertEquals($c->getLocalizedString('str_array'), ['en' => 'Hello World!', 'no' => 'Hei Verden!']); + + $this->expectException(AssertionFailedException::class); + $c->getLocalizedString('missing_opt'); } @@ -924,7 +1080,7 @@ class ConfigurationTest extends ClearStateTestCase $virtualFile = 'nonexistent-preload.php'; Configuration::setPreLoadedConfig($c, $virtualFile); $nc = Configuration::getConfig($virtualFile); - $this->assertEquals('value', $nc->getValue('key', null)); + $this->assertEquals('value', $nc->getOptionalValue('key', null)); } @@ -940,10 +1096,10 @@ class ConfigurationTest extends ClearStateTestCase ]; // test loading a custom instance Configuration::loadFromArray($c, '', 'dummy'); - $this->assertEquals('value', Configuration::getInstance('dummy')->getValue('key', null)); + $this->assertEquals('value', Configuration::getInstance('dummy')->getOptionalValue('key', null)); // test loading the default instance Configuration::loadFromArray($c, '', 'simplesaml'); - $this->assertEquals('value', Configuration::getInstance()->getValue('key', null)); + $this->assertEquals('value', Configuration::getInstance()->getOptionalValue('key', null)); } } diff --git a/tests/lib/SimpleSAML/Store/StoreFactoryTest.php b/tests/lib/SimpleSAML/Store/StoreFactoryTest.php index 3164d5f1c67dfe7cebaa00870f39fd53b16cd538..27a421916c813f64dd052bc7f3121d2ee977d41f 100644 --- a/tests/lib/SimpleSAML/Store/StoreFactoryTest.php +++ b/tests/lib/SimpleSAML/Store/StoreFactoryTest.php @@ -32,7 +32,7 @@ class StoreFactoryTest extends TestCase Configuration::loadFromArray([], '[ARRAY]', 'simplesaml'); $config = Configuration::getInstance(); - $storeType = $config->getString('store.type', 'phpsession'); + $storeType = $config->getOptionalString('store.type', 'phpsession'); /** @var false $store */ $store = StoreFactory::getInstance($storeType); @@ -66,7 +66,7 @@ class StoreFactoryTest extends TestCase public function memcacheStore(): void { Configuration::loadFromArray([ - 'store.type' => 'memcache', + 'store.type' => 'memcache', ], '[ARRAY]', 'simplesaml'); $config = Configuration::getInstance(); @@ -166,7 +166,7 @@ class StoreFactoryTest extends TestCase protected function tearDown(): void { $config = Configuration::getInstance(); - $storeType = $config->getString('store.type', 'phpsession'); + $storeType = $config->getOptionalString('store.type', 'phpsession'); /** @var \SimpleSAML\Store\StoreInterface $store */ $store = StoreFactory::getInstance($storeType); diff --git a/www/errorreport.php b/www/errorreport.php index 4af111475ab3a28e0dcdb8fa36e94166e60d1387..5200c8dc301bc5506040a42e28a3b374dbb513fa 100644 --- a/www/errorreport.php +++ b/www/errorreport.php @@ -48,7 +48,7 @@ $data['version'] = $config->getVersion(); $data['hostname'] = php_uname('n'); $data['directory'] = dirname(dirname(__FILE__)); -if ($config->getBoolean('errorreporting', true)) { +if ($config->getOptionalBoolean('errorreporting', true)) { $mail = new SimpleSAML\Utils\EMail('SimpleSAMLphp error report from ' . $email); $mail->setData($data); if ($email) { diff --git a/www/index.php b/www/index.php index 2e366c1d2c491de59e7939593c84be965103d022..a01831ec12fd6e37e32ad5a880a897dd08205c44 100644 --- a/www/index.php +++ b/www/index.php @@ -5,5 +5,5 @@ require_once('_include.php'); $config = \SimpleSAML\Configuration::getInstance(); $httpUtils = new \SimpleSAML\Utils\HTTP(); -$redirect = $config->getString('frontpage.redirect', SimpleSAML\Module::getModuleURL('core/welcome')); +$redirect = $config->getOptionalString('frontpage.redirect', SimpleSAML\Module::getModuleURL('core/welcome')); $httpUtils->redirectTrustedURL($redirect); diff --git a/www/saml2/idp/ArtifactResolutionService.php b/www/saml2/idp/ArtifactResolutionService.php index 30b3652acc772e7bbaf33b3e1ed87e71b2e432d8..6ba0ea8b3a85e2d6428d4dd42c7de09172720d93 100644 --- a/www/saml2/idp/ArtifactResolutionService.php +++ b/www/saml2/idp/ArtifactResolutionService.php @@ -23,7 +23,7 @@ use SimpleSAML\Metadata; use SimpleSAML\Store\StoreFactory; $config = Configuration::getInstance(); -if (!$config->getBoolean('enable.saml20-idp', false) || !Module::isModuleEnabled('saml')) { +if (!$config->getOptionalBoolean('enable.saml20-idp', false) || !Module::isModuleEnabled('saml')) { throw new Error\Error('NOACCESS', null, 403); } @@ -31,11 +31,11 @@ $metadata = Metadata\MetaDataStorageHandler::getMetadataHandler(); $idpEntityId = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted'); $idpMetadata = $metadata->getMetaDataConfig($idpEntityId, 'saml20-idp-hosted'); -if (!$idpMetadata->getBoolean('saml20.sendartifact', false)) { +if (!$idpMetadata->getOptionalBoolean('saml20.sendartifact', false)) { throw new Error\Error('NOACCESS'); } -$storeType = $config->getString('store.type', 'phpsession'); +$storeType = $config->getOptionalString('store.type', 'phpsession'); $store = StoreFactory::getInstance($storeType); if ($store === false) { throw new Exception('Unable to send artifact without a datastore configured.'); diff --git a/www/saml2/idp/SSOService.php b/www/saml2/idp/SSOService.php index f139b2db3a8a60edfe3fedc8b12412533ebfbf1f..ac617b94c95fbc35bc4cade0aae5dd4bb88deb97 100644 --- a/www/saml2/idp/SSOService.php +++ b/www/saml2/idp/SSOService.php @@ -22,7 +22,7 @@ use SimpleSAML\Module; Logger::info('SAML2.0 - IdP.SSOService: Accessing SAML 2.0 IdP endpoint SSOService'); $config = Configuration::getInstance(); -if (!$config->getBoolean('enable.saml20-idp', false) || !Module::isModuleEnabled('saml')) { +if (!$config->getOptionalBoolean('enable.saml20-idp', false) || !Module::isModuleEnabled('saml')) { throw new Error\Error('NOACCESS', null, 403); } diff --git a/www/saml2/idp/SingleLogoutService.php b/www/saml2/idp/SingleLogoutService.php index 2a617332f0e0bf42e72cd311ac68e31b9b8aa9ec..df1d597b63e0cdd3f92af2507e92312204103430 100644 --- a/www/saml2/idp/SingleLogoutService.php +++ b/www/saml2/idp/SingleLogoutService.php @@ -22,7 +22,7 @@ use SimpleSAML\Utils; Logger::info('SAML2.0 - IdP.SingleLogoutService: Accessing SAML 2.0 IdP endpoint SingleLogoutService'); $config = Configuration::getInstance(); -if (!$config->getBoolean('enable.saml20-idp', false) || !Module::isModuleEnabled('saml')) { +if (!$config->getOptionalBoolean('enable.saml20-idp', false) || !Module::isModuleEnabled('saml')) { throw new Error\Error('NOACCESS', null, 403); } diff --git a/www/saml2/idp/initSLO.php b/www/saml2/idp/initSLO.php index 11961711827be96764b280b0a47d9e686c5d8f3d..5f49209038299ebebc998533a2361ffbaaefede9 100644 --- a/www/saml2/idp/initSLO.php +++ b/www/saml2/idp/initSLO.php @@ -14,7 +14,7 @@ use SimpleSAML\Utils; Logger::info('SAML2.0 - IdP.initSLO: Accessing SAML 2.0 IdP endpoint init Single Logout'); $config = Configuration::getInstance(); -if (!$config->getBoolean('enable.saml20-idp', false) || !Module::isModuleEnabled('saml')) { +if (!$config->getOptionalBoolean('enable.saml20-idp', false) || !Module::isModuleEnabled('saml')) { throw new Error\Error('NOACCESS', null, 403); } diff --git a/www/saml2/idp/metadata.php b/www/saml2/idp/metadata.php index 31d3d53f66c8d44a3513df9655e60da9bc25bf3c..2ef9f313864d3dc89f8d3df61b72090467dc985c 100644 --- a/www/saml2/idp/metadata.php +++ b/www/saml2/idp/metadata.php @@ -9,12 +9,12 @@ use SimpleSAML\Module\saml\IdP\SAML2 as SAML2_IdP; use SimpleSAML\Utils; $config = Configuration::getInstance(); -if (!$config->getBoolean('enable.saml20-idp', false) || !Module::isModuleEnabled('saml')) { +if (!$config->getOptionalBoolean('enable.saml20-idp', false) || !Module::isModuleEnabled('saml')) { throw new Error\Error('NOACCESS', null, 403); } // check if valid local session exists -if ($config->getBoolean('admin.protectmetadata', false)) { +if ($config->getOptionalBoolean('admin.protectmetadata', false)) { $authUtils = new Utils\Auth(); $authUtils->requireAdmin(); }