From cc2372d2260d3a7a284b844c4b85ebf13ad2f479 Mon Sep 17 00:00:00 2001
From: Tim van Dijen <tvdijen@gmail.com>
Date: Sat, 5 Feb 2022 12:28:09 +0100
Subject: [PATCH] Refactor getInteger/getOptionalInteger

---
 lib/SimpleSAML/Auth/State.php                 |   2 +-
 lib/SimpleSAML/Configuration.php              | 116 +++++++++++-------
 lib/SimpleSAML/Locale/Language.php            |   2 +-
 lib/SimpleSAML/Logger.php                     |   2 +-
 .../Logger/SyslogLoggingHandler.php           |   2 +-
 lib/SimpleSAML/Memcache.php                   |   2 +-
 lib/SimpleSAML/Metadata/SAMLBuilder.php       |   7 +-
 lib/SimpleSAML/Module.php                     |   2 +-
 lib/SimpleSAML/Session.php                    |   8 +-
 lib/SimpleSAML/SessionHandler.php             |   2 +-
 lib/SimpleSAML/SessionHandlerStore.php        |   2 +-
 lib/SimpleSAML/Store/RedisStore.php           |   4 +-
 .../lib/Auth/Process/ExtendIdPSession.php     |   2 +-
 modules/core/www/idp/logout-iframe-post.php   |   4 +-
 modules/saml/lib/Auth/Source/SP.php           |   8 +-
 modules/saml/lib/IdP/SAML2.php                |  12 +-
 modules/saml/lib/Message.php                  |  10 +-
 tests/lib/SimpleSAML/ConfigurationTest.php    | 104 +++++++++++-----
 18 files changed, 184 insertions(+), 107 deletions(-)

diff --git a/lib/SimpleSAML/Auth/State.php b/lib/SimpleSAML/Auth/State.php
index b43f48f1b..69544cf8f 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 e20140ac7..d06328a2c 100644
--- a/lib/SimpleSAML/Configuration.php
+++ b/lib/SimpleSAML/Configuration.php
@@ -653,36 +653,47 @@ 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)),
+        );
 
-        if (!is_int($ret)) {
-            throw new \Exception(
-                $this->location . ': The option ' . var_export($name, true) .
-                ' is not a valid integer value.'
-            );
+        return $ret;
+    }
+
+
+    /**
+     * This function retrieves an 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.
+     *
+     * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not an integer.
+     */
+    public function getOptionalInteger(string $name, ?int $default): ?int
+    {
+        if (!$this->hasValue($name)) {
+            // the option wasn't found, or it matches the default value. In any case, return this value
+            return $default;
         }
 
-        return $ret;
+        return $this->getInteger($name);
     }
 
 
@@ -697,33 +708,56 @@ 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);
 
-        if ($ret === $default) {
-            // the option wasn't found, or it matches the default value. In any case, return this value
-            return $ret;
-        }
+        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 < $minimum || $ret > $maximum) {
-            throw new \Exception(
-                $this->location . ': Value of option ' . var_export($name, true) .
-                ' is out of range. Value is ' . $ret . ', allowed range is ['
-                . $minimum . ' - ' . $maximum . ']'
-            );
+        return $ret;
+    }
+
+
+    /**
+     * 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.
+     *
+     * @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
+    {
+        if (!$this->hasValue($name)) {
+            // the option wasn't found, or it matches the default value. In any case, return this value
+            return $default;
         }
 
-        return $ret;
+        return $this->getInteger($name, $minimum, $maximum);
     }
 
 
diff --git a/lib/SimpleSAML/Locale/Language.php b/lib/SimpleSAML/Locale/Language.php
index 30b5e51bc..ba8b826cf 100644
--- a/lib/SimpleSAML/Locale/Language.php
+++ b/lib/SimpleSAML/Locale/Language.php
@@ -434,7 +434,7 @@ class Language
 
         $name = $config->getOptionalString('language.cookie.name', 'language');
         $params = [
-            'lifetime' => ($config->getInteger('language.cookie.lifetime', 60 * 60 * 24 * 900)),
+            '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)),
diff --git a/lib/SimpleSAML/Logger.php b/lib/SimpleSAML/Logger.php
index c6cb93848..15575a58e 100644
--- a/lib/SimpleSAML/Logger.php
+++ b/lib/SimpleSAML/Logger.php
@@ -449,7 +449,7 @@ 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)) {
diff --git a/lib/SimpleSAML/Logger/SyslogLoggingHandler.php b/lib/SimpleSAML/Logger/SyslogLoggingHandler.php
index 9f7afb60c..40a0923f6 100644
--- a/lib/SimpleSAML/Logger/SyslogLoggingHandler.php
+++ b/lib/SimpleSAML/Logger/SyslogLoggingHandler.php
@@ -27,7 +27,7 @@ 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(
diff --git a/lib/SimpleSAML/Memcache.php b/lib/SimpleSAML/Memcache.php
index 1969fa09b..493db7f30 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/SAMLBuilder.php b/lib/SimpleSAML/Metadata/SAMLBuilder.php
index d0bfd6e0a..7e4933078 100644
--- a/lib/SimpleSAML/Metadata/SAMLBuilder.php
+++ b/lib/SimpleSAML/Metadata/SAMLBuilder.php
@@ -431,9 +431,12 @@ class SAMLBuilder
          * of requested attributes
          */
         $attributeconsumer = new AttributeConsumingService();
-
         $attributeconsumer->setIndex($metadata->getOptionalInteger('attributes.index', 0));
-        $attributeconsumer->setIsDefault($metadata->getOptionalBoolean('attributes.isDefault', false));
+
+        if ($metadata->hasValue('attributes.isDefault')) {
+            $attributeconsumer->setIsDefault($metadata->getBoolean('attributes.isDefault', false));
+        }
+
         $attributeconsumer->setServiceName($name);
         $attributeconsumer->setServiceDescription($metadata->getLocalizedString('description', []));
 
diff --git a/lib/SimpleSAML/Module.php b/lib/SimpleSAML/Module.php
index d3ea8d94a..cc3c132cc 100644
--- a/lib/SimpleSAML/Module.php
+++ b/lib/SimpleSAML/Module.php
@@ -307,7 +307,7 @@ class Module
             // "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->getOptionalBoolean('etag', false)) {
diff --git a/lib/SimpleSAML/Session.php b/lib/SimpleSAML/Session.php
index f108f8e56..2e25d0ebf 100644
--- a/lib/SimpleSAML/Session.php
+++ b/lib/SimpleSAML/Session.php
@@ -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;
@@ -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 402631f6d..b15f0513d 100644
--- a/lib/SimpleSAML/SessionHandler.php
+++ b/lib/SimpleSAML/SessionHandler.php
@@ -158,7 +158,7 @@ abstract class SessionHandler
         $config = Configuration::getInstance();
 
         return [
-            'lifetime' => $config->getInteger('session.cookie.lifetime', 0),
+            '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),
diff --git a/lib/SimpleSAML/SessionHandlerStore.php b/lib/SimpleSAML/SessionHandlerStore.php
index f4f89e710..a995551d8 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/Store/RedisStore.php b/lib/SimpleSAML/Store/RedisStore.php
index 53f08675c..7781d3c10 100644
--- a/lib/SimpleSAML/Store/RedisStore.php
+++ b/lib/SimpleSAML/Store/RedisStore.php
@@ -36,10 +36,10 @@ class RedisStore implements StoreInterface
             $config = Configuration::getInstance();
 
             $host = $config->getOptionalString('store.redis.host', 'localhost');
-            $port = $config->getInteger('store.redis.port', 6379);
+            $port = $config->getOptionalInteger('store.redis.port', 6379);
             $prefix = $config->getOptionalString('store.redis.prefix', 'SimpleSAMLphp');
             $password = $config->getOptionalString('store.redis.password', null);
-            $database = $config->getInteger('store.redis.database', 0);
+            $database = $config->getOptionalInteger('store.redis.database', 0);
 
             $redis = new Client(
                 [
diff --git a/modules/core/lib/Auth/Process/ExtendIdPSession.php b/modules/core/lib/Auth/Process/ExtendIdPSession.php
index 40f43e37a..f3931f506 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)) {
diff --git a/modules/core/www/idp/logout-iframe-post.php b/modules/core/www/idp/logout-iframe-post.php
index 31217b217..d20af29fc 100644
--- a/modules/core/www/idp/logout-iframe-post.php
+++ b/modules/core/www/idp/logout-iframe-post.php
@@ -30,9 +30,9 @@ $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);
 
diff --git a/modules/saml/lib/Auth/Source/SP.php b/modules/saml/lib/Auth/Source/SP.php
index 81ef882de..0c49cae5f 100644
--- a/modules/saml/lib/Auth/Source/SP.php
+++ b/modules/saml/lib/Auth/Source/SP.php
@@ -539,10 +539,10 @@ class SP extends \SimpleSAML\Auth\Source
 
             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 = [];
diff --git a/modules/saml/lib/IdP/SAML2.php b/modules/saml/lib/IdP/SAML2.php
index 733c0b113..c068e524e 100644
--- a/modules/saml/lib/IdP/SAML2.php
+++ b/modules/saml/lib/IdP/SAML2.php
@@ -458,7 +458,7 @@ class SAML2
 
         $IDPList = array_unique(array_merge($IDPList, $spMetadata->getArrayizeString('IDPList', [])));
         if ($ProxyCount === null) {
-            $ProxyCount = $spMetadata->getInteger('ProxyCount', null);
+            $ProxyCount = $spMetadata->getOptionalInteger('ProxyCount', null);
         }
 
         if (!$forceAuthn) {
@@ -1151,9 +1151,9 @@ class SAML2
 
         $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();
@@ -1397,9 +1397,9 @@ 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);
 
diff --git a/modules/saml/lib/Message.php b/modules/saml/lib/Message.php
index ade161862..8eb1c8428 100644
--- a/modules/saml/lib/Message.php
+++ b/modules/saml/lib/Message.php
@@ -495,8 +495,12 @@ 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');
@@ -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,
diff --git a/tests/lib/SimpleSAML/ConfigurationTest.php b/tests/lib/SimpleSAML/ConfigurationTest.php
index 38cdee13c..ba7a5dd5c 100644
--- a/tests/lib/SimpleSAML/ConfigurationTest.php
+++ b/tests/lib/SimpleSAML/ConfigurationTest.php
@@ -354,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);
     }
 
 
@@ -390,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);
     }
 
 
-- 
GitLab