From 27fc2659dd538e64bd16b0cff86f4c4d027a1802 Mon Sep 17 00:00:00 2001
From: Tim van Dijen <tvdijen@gmail.com>
Date: Sat, 6 Jan 2018 23:03:52 +0100
Subject: [PATCH] PSR-2

---
 lib/SimpleSAML/Configuration.php            |   4 +
 lib/SimpleSAML/MetadataConfiguration.php    | 331 ++++++++
 modules/cdc/config-templates/module_cdc.php |  34 +-
 modules/cdc/lib/Auth/Process/CDC.php        | 126 +--
 modules/cdc/lib/Client.php                  |  97 +--
 modules/cdc/lib/Server.php                  | 816 ++++++++++----------
 6 files changed, 874 insertions(+), 534 deletions(-)
 create mode 100644 lib/SimpleSAML/MetadataConfiguration.php

diff --git a/lib/SimpleSAML/Configuration.php b/lib/SimpleSAML/Configuration.php
index ea553ee13..8110ff5d5 100644
--- a/lib/SimpleSAML/Configuration.php
+++ b/lib/SimpleSAML/Configuration.php
@@ -93,6 +93,10 @@ class SimpleSAML_Configuration implements \SimpleSAML\Utils\ClearableState
         $this->location = $location;
     }
 
+    public function getLocation()
+    {
+        return $this->location;
+    }
 
     /**
      * Load the given configuration file.
diff --git a/lib/SimpleSAML/MetadataConfiguration.php b/lib/SimpleSAML/MetadataConfiguration.php
new file mode 100644
index 000000000..880d81d01
--- /dev/null
+++ b/lib/SimpleSAML/MetadataConfiguration.php
@@ -0,0 +1,331 @@
+<?php
+
+
+/**
+ * Metadata configuration of SimpleSAMLphp
+ *
+ * @author Andreas Aakre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
+ * @package SimpleSAMLphp
+ */
+class SimpleSAML_MetadataConfiguration extends \SimpleSAML_Configuration
+{
+    /**
+     * Initializes a configuration from the given array.
+     *
+     * @param array $config The configuration array.
+     * @param string $location The location which will be given when an error occurs.
+     */
+    public function __construct($config, $location)
+    {
+        assert(is_array($config));
+        assert(is_string($location));
+
+        parent::__construct($config, $location);
+    }
+
+
+    /**
+     * Retrieve the default binding for the given endpoint type.
+     *
+     * This function combines the current metadata type (SAML 2 / SAML 1.1)
+     * with the endpoint type to determine which binding is the default.
+     *
+     * @param string $endpointType The endpoint type.
+     *
+     * @return string The default binding.
+     *
+     * @throws Exception If the default binding is missing for this endpoint type.
+     */
+    private function getDefaultBinding($endpointType)
+    {
+        assert(is_string($endpointType));
+
+        $set = $this->getString('metadata-set');
+        switch ($set.':'.$endpointType) {
+            case 'saml20-idp-remote:SingleSignOnService':
+            case 'saml20-idp-remote:SingleLogoutService':
+            case 'saml20-sp-remote:SingleLogoutService':
+                return \SAML2\Constants::BINDING_HTTP_REDIRECT;
+            case 'saml20-sp-remote:AssertionConsumerService':
+                return \SAML2\Constants::BINDING_HTTP_POST;
+            case 'saml20-idp-remote:ArtifactResolutionService':
+                return \SAML2\Constants::BINDING_SOAP;
+            case 'shib13-idp-remote:SingleSignOnService':
+                return 'urn:mace:shibboleth:1.0:profiles:AuthnRequest';
+            case 'shib13-sp-remote:AssertionConsumerService':
+                return 'urn:oasis:names:tc:SAML:1.0:profiles:browser-post';
+            default:
+                throw new Exception('Missing default binding for '.$endpointType.' in '.$set);
+        }
+    }
+
+
+    /**
+     * Helper function for dealing with metadata endpoints.
+     *
+     * @param string $endpointType The endpoint type.
+     *
+     * @return array Array of endpoints of the given type.
+     *
+     * @throws Exception If any element of the configuration options for this endpoint type is incorrect.
+     */
+    public function getEndpoints($endpointType)
+    {
+        assert(is_string($endpointType));
+
+        $loc = $this->getLocation().'['.var_export($endpointType, true).']:';
+
+        $configuration = $this->toArray();
+        if (!array_key_exists($endpointType, $configuration)) {
+            // no endpoints of the given type
+            return array();
+        }
+
+
+        $eps = $configuration[$endpointType];
+        if (is_string($eps)) {
+            // for backwards-compatibility
+            $eps = array($eps);
+        } elseif (!is_array($eps)) {
+            throw new Exception($loc.': Expected array or string.');
+        }
+
+
+        foreach ($eps as $i => &$ep) {
+            $iloc = $loc.'['.var_export($i, true).']';
+
+            if (is_string($ep)) {
+                // for backwards-compatibility
+                $ep = array(
+                    'Location' => $ep,
+                    'Binding'  => $this->getDefaultBinding($endpointType),
+                );
+                $responseLocation = $this->getString($endpointType.'Response', null);
+                if ($responseLocation !== null) {
+                    $ep['ResponseLocation'] = $responseLocation;
+                }
+            } elseif (!is_array($ep)) {
+                throw new Exception($iloc.': Expected a string or an array.');
+            }
+
+            if (!array_key_exists('Location', $ep)) {
+                throw new Exception($iloc.': Missing Location.');
+            }
+            if (!is_string($ep['Location'])) {
+                throw new Exception($iloc.': Location must be a string.');
+            }
+
+            if (!array_key_exists('Binding', $ep)) {
+                throw new Exception($iloc.': Missing Binding.');
+            }
+            if (!is_string($ep['Binding'])) {
+                throw new Exception($iloc.': Binding must be a string.');
+            }
+
+            if (array_key_exists('ResponseLocation', $ep)) {
+                if (!is_string($ep['ResponseLocation'])) {
+                    throw new Exception($iloc.': ResponseLocation must be a string.');
+                }
+            }
+
+            if (array_key_exists('index', $ep)) {
+                if (!is_int($ep['index'])) {
+                    throw new Exception($iloc.': index must be an integer.');
+                }
+            }
+        }
+
+        return $eps;
+    }
+
+
+    /**
+     * Find an endpoint of the given type, using a list of supported bindings as a way to prioritize.
+     *
+     * @param string $endpointType The endpoint type.
+     * @param array  $bindings Sorted array of acceptable bindings.
+     * @param mixed  $default The default value to return if no matching endpoint is found. If no default is provided,
+     *     an exception will be thrown.
+     *
+     * @return array|null The default endpoint, or null if no acceptable endpoints are used.
+     *
+     * @throws Exception If no supported endpoint is found.
+     */
+    public function getEndpointPrioritizedByBinding($endpointType, array $bindings, $default = self::REQUIRED_OPTION)
+    {
+        assert(is_string($endpointType));
+
+        $endpoints = $this->getEndpoints($endpointType);
+
+        foreach ($bindings as $binding) {
+            foreach ($endpoints as $ep) {
+                if ($ep['Binding'] === $binding) {
+                    return $ep;
+                }
+            }
+        }
+
+        if ($default === self::REQUIRED_OPTION) {
+            $loc = $this->getLocation().'['.var_export($endpointType, true).']:';
+            throw new Exception($loc.'Could not find a supported '.$endpointType.' endpoint.');
+        }
+
+        return $default;
+    }
+
+
+    /**
+     * Find the default endpoint of the given type.
+     *
+     * @param string $endpointType The endpoint type.
+     * @param array  $bindings Array with acceptable bindings. Can be null if any binding is allowed.
+     * @param mixed  $default The default value to return if no matching endpoint is found. If no default is provided,
+     *     an exception will be thrown.
+     *
+     * @return array|null The default endpoint, or null if no acceptable endpoints are used.
+     *
+     * @throws Exception If no supported endpoint is found.
+     */
+    public function getDefaultEndpoint($endpointType, array $bindings = null, $default = self::REQUIRED_OPTION)
+    {
+        assert(is_string($endpointType));
+
+        $endpoints = $this->getEndpoints($endpointType);
+
+        $defaultEndpoint = \SimpleSAML\Utils\Config\Metadata::getDefaultEndpoint($endpoints, $bindings);
+        if ($defaultEndpoint !== null) {
+            return $defaultEndpoint;
+        }
+
+        if ($default === self::REQUIRED_OPTION) {
+            $loc = $this->getLocation().'['.var_export($endpointType, true).']:';
+            throw new Exception($loc.'Could not find a supported '.$endpointType.' endpoint.');
+        }
+
+        return $default;
+    }
+
+
+    /**
+     * Retrieve a string which may be localized into many languages.
+     *
+     * The default language returned is always 'en'.
+     *
+     * @param string $name The name of the option.
+     * @param mixed  $default The default value. If no default is given, and the option isn't found, an exception will
+     *     be thrown.
+     *
+     * @return array Associative array with language => string pairs.
+     *
+     * @throws Exception If the translation is not an array or a string, or its index or value are not strings.
+     */
+    public function getLocalizedString($name, $default = self::REQUIRED_OPTION)
+    {
+        assert(is_string($name));
+
+        $ret = $this->getValue($name, $default);
+        if ($ret === $default) {
+            // the option wasn't found, or it matches the default value. In any case, return this value
+            return $ret;
+        }
+
+        $loc = $this->getLocation().'['.var_export($name, true).']';
+
+        if (is_string($ret)) {
+            $ret = array('en' => $ret,);
+        }
+
+        if (!is_array($ret)) {
+            throw new Exception($loc.': Must be an array or a string.');
+        }
+
+        foreach ($ret as $k => $v) {
+            if (!is_string($k)) {
+                throw new Exception($loc.': Invalid language code: '.var_export($k, true));
+            }
+            if (!is_string($v)) {
+                throw new Exception($loc.'['.var_export($v, true).']: Must be a string.');
+            }
+        }
+
+        return $ret;
+    }
+
+
+    /**
+     * Get public key from metadata.
+     *
+     * @param string|null $use The purpose this key can be used for. (encryption or signing).
+     * @param bool $required Whether the public key is required. If this is true, a
+     *                       missing key will cause an exception. Default is false.
+     * @param string $prefix The prefix which should be used when reading from the metadata
+     *                       array. Defaults to ''.
+     *
+     * @return array 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 a file.
+     * @throws SimpleSAML_Error_Exception If the file does not contain a valid PEM-encoded certificate, or there is no
+     * certificate in the metadata.
+     */
+    public function getPublicKeys($use = null, $required = false, $prefix = '')
+    {
+        assert(is_bool($required));
+        assert(is_string($prefix));
+
+        if ($this->hasValue($prefix.'keys')) {
+            $ret = array();
+            foreach ($this->getArray($prefix.'keys') as $key) {
+                if ($use !== null && isset($key[$use]) && !$key[$use]) {
+                    continue;
+                }
+                if (isset($key['X509Certificate'])) {
+                    // Strip whitespace from key
+                    $key['X509Certificate'] = preg_replace('/\s+/', '', $key['X509Certificate']);
+                }
+                $ret[] = $key;
+            }
+            return $ret;
+        } elseif ($this->hasValue($prefix.'certData')) {
+            $certData = $this->getString($prefix.'certData');
+            $certData = preg_replace('/\s+/', '', $certData);
+            return array(
+                array(
+                    'encryption'      => true,
+                    'signing'         => true,
+                    'type'            => 'X509Certificate',
+                    'X509Certificate' => $certData,
+                ),
+            );
+        } elseif ($this->hasValue($prefix.'certificate')) {
+            $file = $this->getString($prefix.'certificate');
+            $file = \SimpleSAML\Utils\Config::getCertPath($file);
+            $data = @file_get_contents($file);
+
+            if ($data === false) {
+                throw new Exception($this->getLocation().': Unable to load certificate/public key from file "'.$file.'".');
+            }
+
+            // extract certificate data (if this is a certificate)
+            $pattern = '/^-----BEGIN CERTIFICATE-----([^-]*)^-----END CERTIFICATE-----/m';
+            if (!preg_match($pattern, $data, $matches)) {
+                throw new SimpleSAML_Error_Exception(
+                    $this->getLocation().': Could not find PEM encoded certificate in "'.$file.'".'
+                );
+            }
+            $certData = preg_replace('/\s+/', '', $matches[1]);
+
+            return array(
+                array(
+                    'encryption'      => true,
+                    'signing'         => true,
+                    'type'            => 'X509Certificate',
+                    'X509Certificate' => $certData,
+                ),
+            );
+        } elseif ($required === true) {
+            throw new SimpleSAML_Error_Exception($this->getLocation().': Missing certificate in metadata.');
+        } else {
+            return array();
+        }
+    }
+}
diff --git a/modules/cdc/config-templates/module_cdc.php b/modules/cdc/config-templates/module_cdc.php
index 77b83bdce..0e5013c87 100644
--- a/modules/cdc/config-templates/module_cdc.php
+++ b/modules/cdc/config-templates/module_cdc.php
@@ -1,24 +1,22 @@
 <?php
 
 $config = array(
-	'example.org' => array(
+    'example.org' => array(
+        /*
+         * The shared key for this CDC server.
+         */
+        'key' => 'ExampleSharedKey',
 
-		/*
-		 * The shared key for this CDC server.
-		 */
-		'key' => 'ExampleSharedKey',
+        /*
+         * The URL to the server script.
+         */
+        'server' => 'https://my-cdc.example.org/simplesaml/module.php/cdc/server.php',
 
-		/*
-		 * The URL to the server script.
-		 */
-		'server' => 'https://my-cdc.example.org/simplesaml/module.php/cdc/server.php',
-
-		/*
-		 * The lifetime of our cookie, in seconds.
-		 *
-		 * If this is 0, the cookie will expire when the browser is closed.
-		 */
-		'cookie.lifetime' => 0,
-
-	),
+        /*
+         * The lifetime of our cookie, in seconds.
+         *
+         * If this is 0, the cookie will expire when the browser is closed.
+         */
+        'cookie.lifetime' => 0,
+    ),
 );
diff --git a/modules/cdc/lib/Auth/Process/CDC.php b/modules/cdc/lib/Auth/Process/CDC.php
index bdcf4b706..9641da2ab 100644
--- a/modules/cdc/lib/Auth/Process/CDC.php
+++ b/modules/cdc/lib/Auth/Process/CDC.php
@@ -5,67 +5,67 @@
  *
  * @package SimpleSAMLphp
  */
-class sspmod_cdc_Auth_Process_CDC extends SimpleSAML_Auth_ProcessingFilter {
-
-
-	/**
-	 * Our CDC domain.
-	 *
-	 * @var string
-	 */
-	private $domain;
-
-
-	/**
-	 * Our CDC client.
-	 *
-	 * @var sspmod_cdc_Client
-	 */
-	private $client;
-
-
-	/**
-	 * Initialize this filter.
-	 *
-	 * @param array $config  Configuration information about this filter.
-	 * @param mixed $reserved  For future use.
-	 */
-	public function __construct($config, $reserved) {
-		parent::__construct($config, $reserved);
-		assert(is_array($config));
-
-		if (!isset($config['domain'])) {
-			throw new SimpleSAML_Error_Exception('Missing domain option in cdc:CDC filter.');
-		}
-		$this->domain = (string)$config['domain'];
-
-		$this->client = new sspmod_cdc_Client($this->domain);
-	}
-
-
-	/**
-	 * Redirect to page setting CDC.
-	 *
-	 * @param array &$state  The request state.
-	 */
-	public function process(&$state) {
-		assert(is_array($state));
-
-		if (!isset($state['Source']['entityid'])) {
-			SimpleSAML\Logger::warning('saml:CDC: Could not find IdP entityID.');
-			return;
-		}
-
-		// Save state and build request
-		$id = SimpleSAML_Auth_State::saveState($state, 'cdc:resume');
-
-		$returnTo = SimpleSAML\Module::getModuleURL('cdc/resume.php', array('domain' => $this->domain));
-
-		$params = array(
-			'id' => $id,
-			'entityID' => $state['Source']['entityid'],
-		);
-		$this->client->sendRequest($returnTo, 'append', $params);
-	}
-
+class sspmod_cdc_Auth_Process_CDC extends SimpleSAML_Auth_ProcessingFilter
+{
+    /**
+     * Our CDC domain.
+     *
+     * @var string
+     */
+    private $domain;
+
+
+    /**
+     * Our CDC client.
+     *
+     * @var sspmod_cdc_Client
+     */
+    private $client;
+
+
+    /**
+     * Initialize this filter.
+     *
+     * @param array $config  Configuration information about this filter.
+     * @param mixed $reserved  For future use.
+     */
+    public function __construct($config, $reserved)
+    {
+        parent::__construct($config, $reserved);
+        assert(is_array($config));
+
+        if (!isset($config['domain'])) {
+            throw new SimpleSAML_Error_Exception('Missing domain option in cdc:CDC filter.');
+        }
+        $this->domain = (string)$config['domain'];
+
+        $this->client = new sspmod_cdc_Client($this->domain);
+    }
+
+
+    /**
+     * Redirect to page setting CDC.
+     *
+     * @param array &$state  The request state.
+     */
+    public function process(&$state)
+    {
+        assert(is_array($state));
+
+        if (!isset($state['Source']['entityid'])) {
+            SimpleSAML\Logger::warning('saml:CDC: Could not find IdP entityID.');
+            return;
+        }
+
+        // Save state and build request
+        $id = SimpleSAML_Auth_State::saveState($state, 'cdc:resume');
+
+        $returnTo = SimpleSAML\Module::getModuleURL('cdc/resume.php', array('domain' => $this->domain));
+
+        $params = array(
+            'id' => $id,
+            'entityID' => $state['Source']['entityid'],
+        );
+        $this->client->sendRequest($returnTo, 'append', $params);
+    }
 }
diff --git a/modules/cdc/lib/Client.php b/modules/cdc/lib/Client.php
index 41c119a56..ab59fa628 100644
--- a/modules/cdc/lib/Client.php
+++ b/modules/cdc/lib/Client.php
@@ -5,62 +5,63 @@
  *
  * @package SimpleSAMLphp
  */
-class sspmod_cdc_Client {
+class sspmod_cdc_Client
+{
+    /**
+     * Our CDC domain.
+     *
+     * @var string
+     */
+    private $domain;
 
-	/**
-	 * Our CDC domain.
-	 *
-	 * @var string
-	 */
-	private $domain;
 
+    /**
+     * The CDC server we send requests to.
+     *
+     * @var sspmod_cdc_Server|NULL
+     */
+    private $server;
 
-	/**
-	 * The CDC server we send requests to.
-	 *
-	 * @var sspmod_cdc_Server|NULL
-	 */
-	private $server;
 
+    /**
+     * Initialize a CDC client.
+     *
+     * @param string $domain  The domain we should query the server for.
+     */
+    public function __construct($domain)
+    {
+        assert(is_string($domain));
 
-	/**
-	 * Initialize a CDC client.
-	 *
-	 * @param string $domain  The domain we should query the server for.
-	 */
-	public function __construct($domain) {
-		assert(is_string($domain));
+        $this->domain = $domain;
+        $this->server = new sspmod_cdc_Server($domain);
+    }
 
-		$this->domain = $domain;
-		$this->server = new sspmod_cdc_Server($domain);
-	}
 
+    /**
+     * Receive a CDC response.
+     *
+     * @return array|NULL  The response, or NULL if no response is received.
+     */
+    public function getResponse()
+    {
+        return $this->server->getResponse();
+    }
 
-	/**
-	 * Receive a CDC response.
-	 *
-	 * @return array|NULL  The response, or NULL if no response is received.
-	 */
-	public function getResponse() {
 
-		return $this->server->getResponse();
-	}
-
-
-	/**
-	 * Send a request.
-	 *
-	 * @param string $returnTo  The URL we should return to afterwards.
-	 * @param string $op  The operation we are performing.
-	 * @param array $params  Additional parameters.
-	 */
-	public function sendRequest($returnTo, $op, array $params = array()) {
-		assert(is_string($returnTo));
-		assert(is_string($op));
-
-		$params['op'] = $op;
-		$params['return'] = $returnTo;
-		$this->server->sendRequest($params);
-	}
+    /**
+     * Send a request.
+     *
+     * @param string $returnTo  The URL we should return to afterwards.
+     * @param string $op  The operation we are performing.
+     * @param array $params  Additional parameters.
+     */
+    public function sendRequest($returnTo, $op, array $params = array())
+    {
+        assert(is_string($returnTo));
+        assert(is_string($op));
 
+        $params['op'] = $op;
+        $params['return'] = $returnTo;
+        $this->server->sendRequest($params);
+    }
 }
diff --git a/modules/cdc/lib/Server.php b/modules/cdc/lib/Server.php
index 569a33d98..22e15a1a4 100644
--- a/modules/cdc/lib/Server.php
+++ b/modules/cdc/lib/Server.php
@@ -5,409 +5,415 @@
  *
  * @package SimpleSAMLphp
  */
-class sspmod_cdc_Server {
-
-	/**
-	 * The domain.
-	 *
-	 * @var string
-	 */
-	private $domain;
-
-
-	/**
-	 * The URL to the server.
-	 *
-	 * @var string
-	 */
-	private $server;
-
-
-	/**
-	 * Our shared key.
-	 *
-	 * @var string
-	 */
-	private $key;
-
-
-	/**
-	 * The lifetime of our cookie, in seconds.
-	 *
-	 * If this is 0, the cookie will expire when the browser is closed.
-	 *
-	 * @param int
-	 */
-	private $cookieLifetime;
-
-
-	/**
-	 * Initialize a CDC server.
-	 *
-	 * @param string $domain  The domain we are a server for.
-	 */
-	public function __construct($domain) {
-		assert(is_string($domain));
-
-		$cdcConfig = SimpleSAML_Configuration::getConfig('module_cdc.php');
-		$config = $cdcConfig->getConfigItem($domain, NULL);
-
-		if ($config === NULL) {
-			throw new SimpleSAML_Error_Exception('Unknown CDC domain: ' . var_export($domain, TRUE));
-		}
-
-		$this->domain = $domain;
-		$this->server = $config->getString('server');
-		$this->key = $config->getString('key');
-		$this->cookieLifetime = $config->getInteger('cookie.lifetime', 0);
-
-		if ($this->key === 'ExampleSharedKey') {
-			throw new SimpleSAML_Error_Exception('Key for CDC domain ' . var_export($domain, TRUE) . ' not changed from default.');
-		}
-	}
-
-
-	/**
-	 * Send a request to this CDC server.
-	 *
-	 * @param array $request  The CDC request.
-	 */
-	public function sendRequest(array $request) {
-		assert(isset($request['return']));
-		assert(isset($request['op']));
-
-		$request['domain'] = $this->domain;
-		$this->send($this->server, 'CDCRequest', $request);
-	}
-
-
-	/**
-	 * Parse and validate response received from a CDC server.
-	 *
-	 * @return array|NULL  The response, or NULL if no response is received.
-	 */
-	public function getResponse() {
-
-		$response = self::get('CDCResponse');
-		if ($response === NULL) {
-			return NULL;
-		}
-
-		if ($response['domain'] !== $this->domain) {
-			throw new SimpleSAML_Error_Exception('Response received from wrong domain.');
-		}
-
-		$this->validate('CDCResponse');
-
-		return $response;
-	}
-
-
-	/**
-	 * Parse and process a CDC request.
-	 */
-	public static function processRequest() {
-		$request = self::get('CDCRequest');
-		if ($request === NULL) {
-			throw new SimpleSAML_Error_BadRequest('Missing "CDCRequest" parameter.');
-		}
-
-		$domain = $request['domain'];
-		$server = new sspmod_cdc_Server($domain);
-
-		$server->validate('CDCRequest');
-
-		$server->handleRequest($request);
-	}
-
-
-	/**
-	 * Handle a parsed CDC requst.
-	 *
-	 * @param array $request
-	 */
-	private function handleRequest(array $request) {
-
-		if (!isset($request['op'])) {
-			throw new SimpleSAML_Error_BadRequest('Missing "op" in CDC request.');
-		}
-		$op = (string)$request['op'];
-
-		SimpleSAML\Logger::info('Received CDC request with "op": ' . var_export($op, TRUE));
-
-		if (!isset($request['return'])) {
-			throw new SimpleSAML_Error_BadRequest('Missing "return" in CDC request.');
-		}
-		$return = (string)$request['return'];
-
-		switch ($op) {
-		case 'append':
-			$response = $this->handleAppend($request);
-			break;
-		case 'delete':
-			$response = $this->handleDelete($request);
-			break;
-		case 'read':
-			$response = $this->handleRead($request);
-			break;
-		default:
-			$response = 'unknown-op';
-		}
-
-		if (is_string($response)) {
-			$response = array(
-				'status' => $response,
-			);
-		}
-
-		$response['op'] = $op;
-		if (isset($request['id'])) {
-			$response['id'] = (string)$request['id'];
-		}
-		$response['domain'] = $this->domain;
-
-		$this->send($return, 'CDCResponse', $response);
-	}
-
-
-	/**
-	 * Handle an append request.
-	 *
-	 * @param array $request  The request.
-	 * @return array  The response.
-	 */
-	private function handleAppend(array $request) {
-
-		if (!isset($request['entityID'])) {
-			throw new SimpleSAML_Error_BadRequest('Missing entityID in append request.');
-		}
-		$entityID = (string)$request['entityID'];
-
-		$list = $this->getCDC();
-
-		$prevIndex = array_search($entityID, $list, TRUE);
-		if ($prevIndex !== FALSE) {
-			unset($list[$prevIndex]);
-		}
-		$list[] = $entityID;
-
-		$this->setCDC($list);
-
-		return 'ok';
-	}
-
-
-	/**
-	 * Handle a delete request.
-	 *
-	 * @param array $request  The request.
-	 * @return array  The response.
-	 */
-	private function handleDelete(array $request) {
-		$params = array(
-			'path' => '/',
-			'domain' => '.' . $this->domain,
-			'secure' => TRUE,
-			'httponly' => FALSE,
-		);
-
-        \SimpleSAML\Utils\HTTP::setCookie('_saml_idp', NULL, $params, FALSE);
-		return 'ok';
-	}
-
-
-	/**
-	 * Handle a read request.
-	 *
-	 * @param array $request  The request.
-	 * @return array  The response.
-	 */
-	private function handleRead(array $request) {
-
-		$list = $this->getCDC();
-
-		return array(
-			'status' => 'ok',
-			'cdc' => $list,
-		);
-	}
-
-
-	/**
-	 * Helper function for parsing and validating a CDC message.
-	 *
-	 * @param string $parameter  The name of the query parameter.
-	 * @return array|NULL  The response, or NULL if no response is received.
-	 */
-	private static function get($parameter) {
-		assert(is_string($parameter));
-
-		if (!isset($_REQUEST[$parameter])) {
-			return NULL;
-		}
-		$message = (string)$_REQUEST[$parameter];
-
-		$message = @base64_decode($message);
-		if ($message === FALSE) {
-			throw new SimpleSAML_Error_BadRequest('Error base64-decoding CDC message.');
-		}
-
-		$message = @json_decode($message, TRUE);
-		if ($message === FALSE) {
-			throw new SimpleSAML_Error_BadRequest('Error json-decoding CDC message.');
-		}
-
-		if (!isset($message['timestamp'])) {
-			throw new SimpleSAML_Error_BadRequest('Missing timestamp in CDC message.');
-		}
-		$timestamp = (int)$message['timestamp'];
-
-		if ($timestamp + 60 < time()) {
-			throw new SimpleSAML_Error_BadRequest('CDC signature has expired.');
-		}
-		if ($timestamp - 60 > time()) {
-			throw new SimpleSAML_Error_BadRequest('CDC signature from the future.');
-		}
-
-		if (!isset($message['domain'])) {
-			throw new SimpleSAML_Error_BadRequest('Missing domain in CDC message.');
-		}
-
-		return $message;
-	}
-
-
-	/**
-	 * Helper function for validating the signature on a CDC message.
-	 *
-	 * Will throw an exception if the message is invalid.
-	 *
-	 * @param string $parameter  The name of the query parameter.
-	 */
-	private function validate($parameter) {
-		assert(is_string($parameter));
-		assert(isset($_REQUEST[$parameter]));
-
-		$message = (string)$_REQUEST[$parameter];
-
-		if (!isset($_REQUEST['Signature'])) {
-			throw new SimpleSAML_Error_BadRequest('Missing Signature on CDC message.');
-		}
-		$signature = (string)$_REQUEST['Signature'];
-
-		$cSignature = $this->calcSignature($message);
-		if ($signature !== $cSignature) {
-			throw new SimpleSAML_Error_BadRequest('Invalid signature on CDC message.');
-		}
-	}
-
-
-	/**
-	 * Helper function for sending CDC messages.
-	 *
-	 * @param string $to  The URL the message should be delivered to.
-	 * @param string $parameter  The query parameter the message should be sent in.
-	 * @param array $message  The CDC message.
-	 */
-	private function send($to, $parameter, array $message) {
-		assert(is_string($to));
-		assert(is_string($parameter));
-
-		$message['timestamp'] = time();
-		$message = json_encode($message);
-		$message = base64_encode($message);
-
-		$signature = $this->calcSignature($message);
-
-		$params = array(
-			$parameter => $message,
-			'Signature' => $signature,
-		);
-
-		$url = \SimpleSAML\Utils\HTTP::addURLParameters($to, $params);
-		if (strlen($url) < 2048) {
-			\SimpleSAML\Utils\HTTP::redirectTrustedURL($url);
-		} else {
-			\SimpleSAML\Utils\HTTP::submitPOSTData($to, $params);
-		}
-	}
-
-
-	/**
-	 * Calculate the signature on the given message.
-	 *
-	 * @param string $rawMessage  The base64-encoded message.
-	 * @return string  The signature.
-	 */
-	private function calcSignature($rawMessage) {
-		assert(is_string($rawMessage));
-
-		return sha1($this->key . $rawMessage . $this->key);
-	}
-
-
-	/**
-	 * Get the IdP entities saved in the common domain cookie.
-	 *
-	 * @return array  List of IdP entities.
-	 */
-	private function getCDC() {
-
-		if (!isset($_COOKIE['_saml_idp'])) {
-			return array();
-		}
-
-		$ret = (string)$_COOKIE['_saml_idp'];
-		$ret = explode(' ', $ret);
-		foreach ($ret as &$idp) {
-			$idp = base64_decode($idp);
-			if ($idp === FALSE) {
-				// Not properly base64 encoded
-				SimpleSAML\Logger::warning('CDC - Invalid base64-encoding of CDC entry.');
-				return array();
-			}
-		}
-
-		return $ret;
-	}
-
-
-	/**
-	 * Build a CDC cookie string.
-	 *
-	 * @param array $list  The list of IdPs.
-	 * @return string  The CDC cookie value.
-	 */
-	function setCDC(array $list) {
-
-		foreach ($list as &$value) {
-			$value = base64_encode($value);
-		}
-
-		$cookie = implode(' ', $list);
-
-		while (strlen($cookie) > 4000) {
-			// The cookie is too long. Remove the oldest elements until it is short enough
-			$tmp = explode(' ', $cookie, 2);
-			if (count($tmp) === 1) {
-				/*
-				 * We are left with a single entityID whose base64
-				 * representation is too long to fit in a cookie.
-				 */
-				break;
-			}
-			$cookie = $tmp[1];
-		}
-
-		$params = array(
-			'lifetime' => $this->cookieLifetime,
-			'path' => '/',
-			'domain' => '.' . $this->domain,
-			'secure' => TRUE,
-			'httponly' => FALSE,
-		);
-
-        \SimpleSAML\Utils\HTTP::setCookie('_saml_idp', $cookie, $params, FALSE);
-	}
-
+class sspmod_cdc_Server
+{
+    /**
+     * The domain.
+     *
+     * @var string
+     */
+    private $domain;
+
+
+    /**
+     * The URL to the server.
+     *
+     * @var string
+     */
+    private $server;
+
+
+    /**
+     * Our shared key.
+     *
+     * @var string
+     */
+    private $key;
+
+
+    /**
+     * The lifetime of our cookie, in seconds.
+     *
+     * If this is 0, the cookie will expire when the browser is closed.
+     *
+     * @param int
+     */
+    private $cookieLifetime;
+
+
+    /**
+     * Initialize a CDC server.
+     *
+     * @param string $domain  The domain we are a server for.
+     */
+    public function __construct($domain)
+    {
+        assert(is_string($domain));
+
+        $cdcConfig = SimpleSAML_Configuration::getConfig('module_cdc.php');
+        $config = $cdcConfig->getConfigItem($domain, null);
+
+        if ($config === null) {
+            throw new SimpleSAML_Error_Exception('Unknown CDC domain: ' . var_export($domain, true));
+        }
+
+        $this->domain = $domain;
+        $this->server = $config->getString('server');
+        $this->key = $config->getString('key');
+        $this->cookieLifetime = $config->getInteger('cookie.lifetime', 0);
+
+        if ($this->key === 'ExampleSharedKey') {
+            throw new SimpleSAML_Error_Exception('Key for CDC domain ' . var_export($domain, true) . ' not changed from default.');
+        }
+    }
+
+
+    /**
+     * Send a request to this CDC server.
+     *
+     * @param array $request  The CDC request.
+     */
+    public function sendRequest(array $request)
+    {
+        assert(isset($request['return']));
+        assert(isset($request['op']));
+
+        $request['domain'] = $this->domain;
+        $this->send($this->server, 'CDCRequest', $request);
+    }
+
+
+    /**
+     * Parse and validate response received from a CDC server.
+     *
+     * @return array|NULL  The response, or NULL if no response is received.
+     */
+    public function getResponse()
+    {
+        $response = self::get('CDCResponse');
+        if ($response === null) {
+            return null;
+        }
+
+        if ($response['domain'] !== $this->domain) {
+            throw new SimpleSAML_Error_Exception('Response received from wrong domain.');
+        }
+
+        $this->validate('CDCResponse');
+
+        return $response;
+    }
+
+
+    /**
+     * Parse and process a CDC request.
+     */
+    public static function processRequest()
+    {
+        $request = self::get('CDCRequest');
+        if ($request === null) {
+            throw new SimpleSAML_Error_BadRequest('Missing "CDCRequest" parameter.');
+        }
+
+        $domain = $request['domain'];
+        $server = new sspmod_cdc_Server($domain);
+
+        $server->validate('CDCRequest');
+        $server->handleRequest($request);
+    }
+
+
+    /**
+     * Handle a parsed CDC requst.
+     *
+     * @param array $request
+     */
+    private function handleRequest(array $request)
+    {
+        if (!isset($request['op'])) {
+            throw new SimpleSAML_Error_BadRequest('Missing "op" in CDC request.');
+        }
+        $op = (string)$request['op'];
+
+        SimpleSAML\Logger::info('Received CDC request with "op": ' . var_export($op, true));
+
+        if (!isset($request['return'])) {
+            throw new SimpleSAML_Error_BadRequest('Missing "return" in CDC request.');
+        }
+        $return = (string)$request['return'];
+
+        switch ($op) {
+            case 'append':
+                $response = $this->handleAppend($request);
+                break;
+            case 'delete':
+                $response = $this->handleDelete($request);
+                break;
+            case 'read':
+                $response = $this->handleRead($request);
+                break;
+            default:
+                $response = 'unknown-op';
+        }
+
+        if (is_string($response)) {
+            $response = array(
+                'status' => $response,
+            );
+        }
+
+        $response['op'] = $op;
+        if (isset($request['id'])) {
+            $response['id'] = (string)$request['id'];
+        }
+        $response['domain'] = $this->domain;
+
+        $this->send($return, 'CDCResponse', $response);
+    }
+
+
+    /**
+     * Handle an append request.
+     *
+     * @param array $request  The request.
+     * @return array  The response.
+     */
+    private function handleAppend(array $request)
+    {
+        if (!isset($request['entityID'])) {
+            throw new SimpleSAML_Error_BadRequest('Missing entityID in append request.');
+        }
+        $entityID = (string)$request['entityID'];
+
+        $list = $this->getCDC();
+
+        $prevIndex = array_search($entityID, $list, true);
+        if ($prevIndex !== false) {
+            unset($list[$prevIndex]);
+        }
+        $list[] = $entityID;
+
+        $this->setCDC($list);
+
+        return 'ok';
+    }
+
+
+    /**
+     * Handle a delete request.
+     *
+     * @param array $request  The request.
+     * @return array  The response.
+     */
+    private function handleDelete(array $request)
+    {
+        $params = array(
+            'path' => '/',
+            'domain' => '.' . $this->domain,
+            'secure' => true,
+            'httponly' => false,
+        );
+
+        \SimpleSAML\Utils\HTTP::setCookie('_saml_idp', null, $params, false);
+        return 'ok';
+    }
+
+
+    /**
+     * Handle a read request.
+     *
+     * @param array $request  The request.
+     * @return array  The response.
+     */
+    private function handleRead(array $request)
+    {
+        $list = $this->getCDC();
+
+        return array(
+            'status' => 'ok',
+            'cdc' => $list,
+        );
+    }
+
+
+    /**
+     * Helper function for parsing and validating a CDC message.
+     *
+     * @param string $parameter  The name of the query parameter.
+     * @return array|NULL  The response, or NULL if no response is received.
+     */
+    private static function get($parameter)
+    {
+        assert(is_string($parameter));
+
+        if (!isset($_REQUEST[$parameter])) {
+            return null;
+        }
+        $message = (string)$_REQUEST[$parameter];
+
+        $message = @base64_decode($message);
+        if ($message === false) {
+            throw new SimpleSAML_Error_BadRequest('Error base64-decoding CDC message.');
+        }
+
+        $message = @json_decode($message, true);
+        if ($message === false) {
+            throw new SimpleSAML_Error_BadRequest('Error json-decoding CDC message.');
+        }
+
+        if (!isset($message['timestamp'])) {
+            throw new SimpleSAML_Error_BadRequest('Missing timestamp in CDC message.');
+        }
+        $timestamp = (int)$message['timestamp'];
+
+        if ($timestamp + 60 < time()) {
+            throw new SimpleSAML_Error_BadRequest('CDC signature has expired.');
+        }
+        if ($timestamp - 60 > time()) {
+            throw new SimpleSAML_Error_BadRequest('CDC signature from the future.');
+        }
+
+        if (!isset($message['domain'])) {
+            throw new SimpleSAML_Error_BadRequest('Missing domain in CDC message.');
+        }
+
+        return $message;
+    }
+
+
+    /**
+     * Helper function for validating the signature on a CDC message.
+     *
+     * Will throw an exception if the message is invalid.
+     *
+     * @param string $parameter  The name of the query parameter.
+     */
+    private function validate($parameter)
+    {
+        assert(is_string($parameter));
+        assert(isset($_REQUEST[$parameter]));
+
+        $message = (string)$_REQUEST[$parameter];
+
+        if (!isset($_REQUEST['Signature'])) {
+            throw new SimpleSAML_Error_BadRequest('Missing Signature on CDC message.');
+        }
+        $signature = (string)$_REQUEST['Signature'];
+
+        $cSignature = $this->calcSignature($message);
+        if ($signature !== $cSignature) {
+            throw new SimpleSAML_Error_BadRequest('Invalid signature on CDC message.');
+        }
+    }
+
+
+    /**
+     * Helper function for sending CDC messages.
+     *
+     * @param string $to  The URL the message should be delivered to.
+     * @param string $parameter  The query parameter the message should be sent in.
+     * @param array $message  The CDC message.
+     */
+    private function send($to, $parameter, array $message)
+    {
+        assert(is_string($to));
+        assert(is_string($parameter));
+
+        $message['timestamp'] = time();
+        $message = json_encode($message);
+        $message = base64_encode($message);
+
+        $signature = $this->calcSignature($message);
+
+        $params = array(
+            $parameter => $message,
+            'Signature' => $signature,
+        );
+
+        $url = \SimpleSAML\Utils\HTTP::addURLParameters($to, $params);
+        if (strlen($url) < 2048) {
+            \SimpleSAML\Utils\HTTP::redirectTrustedURL($url);
+        } else {
+            \SimpleSAML\Utils\HTTP::submitPOSTData($to, $params);
+        }
+    }
+
+
+    /**
+     * Calculate the signature on the given message.
+     *
+     * @param string $rawMessage  The base64-encoded message.
+     * @return string  The signature.
+     */
+    private function calcSignature($rawMessage)
+    {
+        assert(is_string($rawMessage));
+
+        return sha1($this->key . $rawMessage . $this->key);
+    }
+
+
+    /**
+     * Get the IdP entities saved in the common domain cookie.
+     *
+     * @return array  List of IdP entities.
+     */
+    private function getCDC()
+    {
+        if (!isset($_COOKIE['_saml_idp'])) {
+            return array();
+        }
+
+        $ret = (string)$_COOKIE['_saml_idp'];
+        $ret = explode(' ', $ret);
+        foreach ($ret as &$idp) {
+            $idp = base64_decode($idp);
+            if ($idp === false) {
+                // Not properly base64 encoded
+                SimpleSAML\Logger::warning('CDC - Invalid base64-encoding of CDC entry.');
+                return array();
+            }
+        }
+
+        return $ret;
+    }
+
+
+    /**
+     * Build a CDC cookie string.
+     *
+     * @param array $list  The list of IdPs.
+     * @return string  The CDC cookie value.
+     */
+    function setCDC(array $list)
+    {
+        foreach ($list as &$value) {
+            $value = base64_encode($value);
+        }
+
+        $cookie = implode(' ', $list);
+
+        while (strlen($cookie) > 4000) {
+            // The cookie is too long. Remove the oldest elements until it is short enough
+            $tmp = explode(' ', $cookie, 2);
+            if (count($tmp) === 1) {
+                /*
+                 * We are left with a single entityID whose base64
+                 * representation is too long to fit in a cookie.
+                 */
+                break;
+            }
+            $cookie = $tmp[1];
+        }
+
+        $params = array(
+            'lifetime' => $this->cookieLifetime,
+            'path' => '/',
+            'domain' => '.' . $this->domain,
+            'secure' => true,
+            'httponly' => false,
+        );
+
+        \SimpleSAML\Utils\HTTP::setCookie('_saml_idp', $cookie, $params, false);
+    }
 }
-- 
GitLab