Skip to content
Snippets Groups Projects
Configuration.php 48.9 KiB
Newer Older
        return $ret;
    }


    /**
     * This function retrieves an optional configuration option with a string or an array of strings.
     *
     * If the configuration option is a string, it will be converted to an array with a single string
     *
     * @param string         $name The name of the option.
     * @param string[]|null  $default A default value which will be returned if the option isn't found.
     *                         The default value can be null or an array of strings.
     *
     * @return string[]|null The option with the given name, or $default if the option isn't found
     *                         and $default is specified.
     * @psalm-return         ($default is null ? array|null : array)
     *
     * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not a string or an array of strings.
     */
    public function getOptionalArrayizeString(string $name, ?array $default): ?array
    {
Tim van Dijen's avatar
Tim van Dijen committed
        $ret = $this->getOptionalArrayize($name, $default);
Tim van Dijen's avatar
Tim van Dijen committed
        Assert::nullOrAllString(
            $ret,
            sprintf(
                '%s: The option %s must be null, a string or an array of strings.',
                $this->location,
                var_export($name, true),
            ),
        );

        return $ret;
     * Retrieve an array as a \SimpleSAML\Configuration object.
     * This function will load the value of an option into a \SimpleSAML\Configuration object.
     *   The option must contain an array.
     * An exception will be thrown if this option isn't an array, or if this option isn't found.
     *
     * @param string $name The name of the option.
     * @return \SimpleSAML\Configuration The option with the given name,
     *
     * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not an array.
     */
    public function getConfigItem(string $name): Configuration
    {
        $ret = $this->getArray($name);

        return self::loadFromArray($ret, $this->location . '[' . var_export($name, true) . ']');
    }



    /**
     * Retrieve an optional array as a \SimpleSAML\Configuration object.
     *
     * This function will load the optional value of an option into a \SimpleSAML\Configuration object.
     *   The option must contain an array.
     *
     * An exception will be thrown if this option isn't an array, or if this option isn't found.
     *
     * @param string     $name The name of the option.
     * @param array|null $default A default value which will be used if the option isn't found. An empty Configuration
     *                     object will be returned if this parameter isn't given and the option doesn't exist.
     *                     This function will only return null if $default is set to null and the option doesn't exist.
     * @return \SimpleSAML\Configuration|null The option with the given name,
     *   or $default, converted into a Configuration object.
     * @psalm-return     ($default is array ? \SimpleSAML\Configuration : \SimpleSAML\Configuration|null)
Tim van Dijen's avatar
Tim van Dijen committed
     * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not an array.
    public function getOptionalConfigItem(string $name, ?array $default): ?Configuration
Tim van Dijen's avatar
Tim van Dijen committed
        $ret = $this->getOptionalArray($name, $default);
        if ($ret !== null) {
            return self::loadFromArray($ret, $this->location . '[' . var_export($name, true) . ']');
        }
        return null;
    }


    /**
     * Retrieve list of options.
     *
     * This function returns the name of all options which are defined in this
     * configuration file, as an array of strings.
     *
Tim van Dijen's avatar
Tim van Dijen committed
     * @return string[] Name of all options defined in this configuration file.
Tim van Dijen's avatar
Tim van Dijen committed
    public function getOptions(): array
    {
        return array_keys($this->configuration);
    }


    /**
     * Convert this configuration object back to an array.
     *
     * @return array An associative array with all configuration options and values.
     */
Tim van Dijen's avatar
Tim van Dijen committed
    public function toArray(): array
    {
        return $this->configuration;
    }


    /**
     * Retrieve the default binding for the given endpoint type.
     *
     * This function combines the current metadata type (SAML 2 / SAML 1.1)
     * with the endpoint type to determine which binding is the default.
     *
     * @param string $endpointType The endpoint type.
     *
     * @return string The default binding.
     * @throws \Exception If the default binding is missing for this endpoint type.
Tim van Dijen's avatar
Tim van Dijen committed
    private function getDefaultBinding(string $endpointType): string
    {
        $set = $this->getString('metadata-set');
        switch ($set . ':' . $endpointType) {
            case 'saml20-idp-remote:SingleSignOnService':
            case 'saml20-idp-remote:SingleLogoutService':
            case 'saml20-sp-remote:SingleLogoutService':
                return Constants::BINDING_HTTP_REDIRECT;
            case 'saml20-sp-remote:AssertionConsumerService':
                return Constants::BINDING_HTTP_POST;
            case 'saml20-idp-remote:ArtifactResolutionService':
                return Constants::BINDING_SOAP;
                throw new Exception('Missing default binding for ' . $endpointType . ' in ' . $set);
        }
    }


    /**
     * Helper function for dealing with metadata endpoints.
     *
     * @param string $endpointType The endpoint type.
     *
     * @return array Array of endpoints of the given type.
     * @throws \Exception If any element of the configuration options for this endpoint type is incorrect.
Tim van Dijen's avatar
Tim van Dijen committed
    public function getEndpoints(string $endpointType): array
        $loc = $this->location . '[' . var_export($endpointType, true) . ']:';

        if (!array_key_exists($endpointType, $this->configuration)) {
            // no endpoints of the given type
        }


        $eps = $this->configuration[$endpointType];
        if (is_string($eps)) {
            // for backwards-compatibility
            $eps = [$eps];
        } elseif (!is_array($eps)) {
            throw new Exception($loc . ': Expected array or string.');
        }


        foreach ($eps as $i => &$ep) {
            $iloc = $loc . '[' . var_export($i, true) . ']';

            if (is_string($ep)) {
                // for backwards-compatibility
                    'Location' => $ep,
                    'Binding'  => $this->getDefaultBinding($endpointType),
                $responseLocation = $this->getOptionalString($endpointType . 'Response', null);
                if ($responseLocation !== null) {
                    $ep['ResponseLocation'] = $responseLocation;
                }
            } elseif (!is_array($ep)) {
                throw new Exception($iloc . ': Expected a string or an array.');
            }

            if (!array_key_exists('Location', $ep)) {
                throw new Exception($iloc . ': Missing Location.');
            }
            if (!is_string($ep['Location'])) {
                throw new Exception($iloc . ': Location must be a string.');
            }

            if (!array_key_exists('Binding', $ep)) {
                throw new Exception($iloc . ': Missing Binding.');
            }
            if (!is_string($ep['Binding'])) {
                throw new Exception($iloc . ': Binding must be a string.');
            }

            if (array_key_exists('ResponseLocation', $ep)) {
                if (!is_string($ep['ResponseLocation'])) {
                    throw new Exception($iloc . ': ResponseLocation must be a string.');
                }
            }

            if (array_key_exists('index', $ep)) {
                if (!is_int($ep['index'])) {
                    throw new Exception($iloc . ': index must be an integer.');
                }
            }
        }

        return $eps;
    }


    /**
     * Find an endpoint of the given type, using a list of supported bindings as a way to prioritize.
     *
     * @param string $endpointType The endpoint type.
Tim van Dijen's avatar
Tim van Dijen committed
     * @param string[] $bindings Sorted array of acceptable bindings.
     * @param mixed  $default The default value to return if no matching endpoint is found. If no default is provided,
     *     an exception will be thrown.
     *
     * @return mixed|null The default endpoint.
     * @throws \Exception If no supported endpoint is found.
Tim van Dijen's avatar
Tim van Dijen committed
    public function getEndpointPrioritizedByBinding(
        string $endpointType,
        array $bindings,
        $default = self::REQUIRED_OPTION
Tim van Dijen's avatar
Tim van Dijen committed
    ) {
        $endpoints = $this->getEndpoints($endpointType);

        foreach ($bindings as $binding) {
            foreach ($endpoints as $ep) {
                if ($ep['Binding'] === $binding) {
                    return $ep;
                }
            }
        }

        if ($default === self::REQUIRED_OPTION) {
            $loc = $this->location . '[' . var_export($endpointType, true) . ']:';
            throw new Exception($loc . 'Could not find a supported ' . $endpointType . ' endpoint.');
        }

        return $default;
    }


    /**
     * Find the default endpoint of the given type.
     *
     * @param string $endpointType The endpoint type.
Tim van Dijen's avatar
Tim van Dijen committed
     * @param string[]|null $bindings Array with acceptable bindings. Can be null if any binding is allowed.
     * @param mixed  $default The default value to return if no matching endpoint is found. If no default is provided,
     *     an exception will be thrown.
     *
     * @return mixed The default endpoint, or the $default parameter if no acceptable endpoints are used.
     * @throws \Exception If no supported endpoint is found and no $default parameter is specified.
Tim van Dijen's avatar
Tim van Dijen committed
    public function getDefaultEndpoint(string $endpointType, array $bindings = null, $default = self::REQUIRED_OPTION)
    {
        $endpoints = $this->getEndpoints($endpointType);

        $defaultEndpoint = Utils\Config\Metadata::getDefaultEndpoint($endpoints, $bindings);
        if ($defaultEndpoint !== null) {
            return $defaultEndpoint;
        }

        if ($default === self::REQUIRED_OPTION) {
            $loc = $this->location . '[' . var_export($endpointType, true) . ']:';
            throw new Exception($loc . 'Could not find a supported ' . $endpointType . ' endpoint.');
        }

        return $default;
    }


    /**
     * Retrieve a string which may be localized into many languages.
     *
     * The default language returned is always 'en'.
     *
     * @param string $name The name of the option.
Tim van Dijen's avatar
Tim van Dijen committed
     * @param array  $default The default value.
Tim van Dijen's avatar
Tim van Dijen committed
     * @return array Associative array with language => string pairs.
Tim van Dijen's avatar
Tim van Dijen committed
     * @throws \SimpleSAML\Assert\AssertionFailedException
     *   If the translation is not an array or a string, or its index or value are not strings.
Tim van Dijen's avatar
Tim van Dijen committed
    public function getLocalizedString(string $name): array
Tim van Dijen's avatar
Tim van Dijen committed
        $ret = $this->getValue($name);

        if (is_string($ret)) {
Tim van Dijen's avatar
Tim van Dijen committed
            $ret = ['en' => $ret];
Tim van Dijen's avatar
Tim van Dijen committed
        Assert::isArray($ret, sprintf('%s: Must be an array or a string.', $this->location));

        foreach ($ret as $k => $v) {
Tim van Dijen's avatar
Tim van Dijen committed
            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)));
Tim van Dijen's avatar
Tim van Dijen committed
    /**
     * Retrieve an optional string which may be localized into many languages.
     *
     * The default language returned is always 'en'.
     *
     * @param string $name The name of the option.
     * @param array|null  $default The default value.
Tim van Dijen's avatar
Tim van Dijen committed
     *
     * @return array|null Associative array with language => string pairs, or the provided default value.
     * @psalm-return ($default is array ? array : array|null)
Tim van Dijen's avatar
Tim van Dijen committed
     *
     * @throws \SimpleSAML\Assert\AssertionFailedException
     *   If the translation is not an array or a string, or its index or value are not strings.
     */
    public function getOptionalLocalizedString(string $name, ?array $default): ?array
    {
        if (!$this->hasValue($name)) {
            // the option wasn't found, or it matches the default value. In any case, return this value
            return $default;
        }

        return $this->getLocalizedString($name);
    }


    /**
     * Get public key from metadata.
     *
     * @param string|null $use The purpose this key can be used for. (encryption or signing).
     * @param bool $required Whether the public key is required. If this is true, a
     *                       missing key will cause an exception. Default is false.
     * @param string $prefix The prefix which should be used when reading from the metadata
     *                       array. Defaults to ''.
     *
Tim van Dijen's avatar
Tim van Dijen committed
     * @return array Public key data, or empty array if no public key or was found.
     * @throws \Exception If the certificate or public key cannot be loaded from location.
Tim van Dijen's avatar
Tim van Dijen committed
     * @throws \SimpleSAML\Error\Exception If the location does not contain a valid PEM-encoded certificate, or there
     *                                     is no certificate in the metadata.
Tim van Dijen's avatar
Tim van Dijen committed
    public function getPublicKeys(?string $use = null, bool $required = false, string $prefix = ''): array
        if ($this->hasValue($prefix . 'keys')) {
            foreach ($this->getArray($prefix . 'keys') as $key) {
                if ($use !== null && isset($key[$use]) && !$key[$use]) {
                    continue;
                }
                if (isset($key['X509Certificate'])) {
                    // Strip whitespace from key
                    $key['X509Certificate'] = preg_replace('/\s+/', '', $key['X509Certificate']);
                }
                $ret[] = $key;
            }
Tim van Dijen's avatar
Tim van Dijen committed
            return $ret;
        } elseif ($this->hasValue($prefix . 'certData')) {
            $certData = $this->getString($prefix . 'certData');
            $certData = preg_replace('/\s+/', '', $certData);
            $keyName = $this->getOptionalString($prefix . 'key_name', null);
                    'encryption'      => true,
                    'signing'         => true,
                    'type'            => 'X509Certificate',
                    'X509Certificate' => $certData,
        } elseif ($this->hasValue($prefix . 'certificate')) {
            $location = $this->getString($prefix . 'certificate');
            $cryptoUtils = new Utils\Crypto();
            $data = $cryptoUtils->retrieveCertificate($location);
                throw new Exception(
                    $this->location . ': Unable to load certificate/public key from location "' . $location . '".'
            // extract certificate data (if this is a certificate)
            $pattern = '/^-----BEGIN CERTIFICATE-----([^-]*)^-----END CERTIFICATE-----/m';
            if (!preg_match($pattern, $data, $matches)) {
                throw new Error\Exception(
                    $this->location . ': Could not find PEM encoded certificate in "' . $location . '".'
                );
            }
            $certData = preg_replace('/\s+/', '', $matches[1]);
            $keyName = $this->getOptionalString($prefix . 'key_name', null);
                    'encryption'      => true,
                    'signing'         => true,
                    'type'            => 'X509Certificate',
                    'X509Certificate' => $certData,
        } elseif ($required === true) {
            throw new Error\Exception($this->location . ': Missing certificate in metadata.');
    /**
     * Clear any configuration information cached.
     * Allows for configuration files to be changed and reloaded during a given request. Most useful
     * when running phpunit tests and needing to alter config.php between test cases
Tim van Dijen's avatar
Tim van Dijen committed
    public static function clearInternalState(): void
        self::$configDirs = [];
        self::$instance = [];
        self::$loadedConfigs = [];