diff --git a/modules/saml/lib/IdP/SAML2.php b/modules/saml/lib/IdP/SAML2.php
index 8fbf2355aa2b3ca55ddfd390a184109d01dad182..92e5363a0d82f6ebf57c62b01128a7dadf371f50 100644
--- a/modules/saml/lib/IdP/SAML2.php
+++ b/modules/saml/lib/IdP/SAML2.php
@@ -1,1054 +1,1149 @@
 <?php
 
+
+use RobRichards\XMLSecLibs\XMLSecurityKey;
+
 /**
  * IdP implementation for SAML 2.0 protocol.
  *
  * @package SimpleSAMLphp
  */
-class sspmod_saml_IdP_SAML2 {
-
-	/**
-	 * Send a response to the SP.
-	 *
-	 * @param array $state  The authentication state.
-	 */
-	public static function sendResponse(array $state) {
-		assert('isset($state["Attributes"])');
-		assert('isset($state["SPMetadata"])');
-		assert('isset($state["saml:ConsumerURL"])');
-		assert('array_key_exists("saml:RequestId", $state)'); // Can be NULL
-		assert('array_key_exists("saml:RelayState", $state)'); // Can be NULL.
-
-		$spMetadata = $state["SPMetadata"];
-		$spEntityId = $spMetadata['entityid'];
-		$spMetadata = SimpleSAML_Configuration::loadFromArray($spMetadata,
-			'$metadata[' . var_export($spEntityId, TRUE) . ']');
-
-		SimpleSAML\Logger::info('Sending SAML 2.0 Response to ' . var_export($spEntityId, TRUE));
-
-		$requestId = $state['saml:RequestId'];
-		$relayState = $state['saml:RelayState'];
-		$consumerURL = $state['saml:ConsumerURL'];
-		$protocolBinding = $state['saml:Binding'];
-
-		$idp = SimpleSAML_IdP::getByState($state);
-
-		$idpMetadata = $idp->getConfig();
-
-		$assertion = self::buildAssertion($idpMetadata, $spMetadata, $state);
-		
-		if (isset($state['saml:AuthenticatingAuthority'])) {
-			$assertion->setAuthenticatingAuthority($state['saml:AuthenticatingAuthority']);
-		}
-
-		// Create the session association (for logout).
-		$association = array(
-			'id' => 'saml:' . $spEntityId,
-			'Handler' => 'sspmod_saml_IdP_SAML2',
-			'Expires' => $assertion->getSessionNotOnOrAfter(),
-			'saml:entityID' => $spEntityId,
-			'saml:NameID' => $state['saml:idp:NameID'],
-			'saml:SessionIndex' => $assertion->getSessionIndex(),
-		);
-
-		// Maybe encrypt the assertion.
-		$assertion = self::encryptAssertion($idpMetadata, $spMetadata, $assertion);
-
-		/* Create the response. */
-		$ar = self::buildResponse($idpMetadata, $spMetadata, $consumerURL);
-		$ar->setInResponseTo($requestId);
-		$ar->setRelayState($relayState);
-		$ar->setAssertions(array($assertion));
-
-		/* Register the session association with the IdP. */
-		$idp->addAssociation($association);
-
-		$statsData = array(
-			'spEntityID' => $spEntityId,
-			'idpEntityID' => $idpMetadata->getString('entityid'),
-			'protocol' => 'saml2',
-		);
-		if (isset($state['saml:AuthnRequestReceivedAt'])) {
-			$statsData['logintime'] = microtime(TRUE) - $state['saml:AuthnRequestReceivedAt'];
-		}
-		SimpleSAML_Stats::log('saml:idp:Response', $statsData);
-
-		/* Send the response. */
-		$binding = \SAML2\Binding::getBinding($protocolBinding);
-		$binding->send($ar);
-	}
-
-
-	/**
-	 * Handle authentication error.
-	 *
-	 * SimpleSAML_Error_Exception $exception  The exception.
-	 * @param array $state  The error state.
-	 */
-	public static function handleAuthError(SimpleSAML_Error_Exception $exception, array $state) {
-		assert('isset($state["SPMetadata"])');
-		assert('isset($state["saml:ConsumerURL"])');
-		assert('array_key_exists("saml:RequestId", $state)'); // Can be NULL.
-		assert('array_key_exists("saml:RelayState", $state)'); // Can be NULL.
-
-		$spMetadata = $state["SPMetadata"];
-		$spEntityId = $spMetadata['entityid'];
-		$spMetadata = SimpleSAML_Configuration::loadFromArray($spMetadata,
-			'$metadata[' . var_export($spEntityId, TRUE) . ']');
-
-		$requestId = $state['saml:RequestId'];
-		$relayState = $state['saml:RelayState'];
-		$consumerURL = $state['saml:ConsumerURL'];
-		$protocolBinding = $state['saml:Binding'];
-
-		$idp = SimpleSAML_IdP::getByState($state);
-
-		$idpMetadata = $idp->getConfig();
-
-		$error = sspmod_saml_Error::fromException($exception);
-
-		SimpleSAML\Logger::warning("Returning error to SP with entity ID '".var_export($spEntityId, TRUE)."'.");
-		$exception->log(SimpleSAML\Logger::WARNING);
-
-		$ar = self::buildResponse($idpMetadata, $spMetadata, $consumerURL);
-		$ar->setInResponseTo($requestId);
-		$ar->setRelayState($relayState);
-
-		$status = array(
-			'Code' => $error->getStatus(),
-			'SubCode' => $error->getSubStatus(),
-			'Message' => $error->getStatusMessage(),
-		);
-		$ar->setStatus($status);
-
-		$statsData = array(
-			'spEntityID' => $spEntityId,
-			'idpEntityID' => $idpMetadata->getString('entityid'),
-			'protocol' => 'saml2',
-			'error' => $status,
-		);
-		if (isset($state['saml:AuthnRequestReceivedAt'])) {
-			$statsData['logintime'] = microtime(TRUE) - $state['saml:AuthnRequestReceivedAt'];
-		}
-		SimpleSAML_Stats::log('saml:idp:Response:error', $statsData);
-
-		$binding = \SAML2\Binding::getBinding($protocolBinding);
-		$binding->send($ar);
-	}
-
-
-	/**
-	 * Find SP AssertionConsumerService based on parameter in AuthnRequest.
-	 *
-	 * @param array $supportedBindings  The bindings we allow for the response.
-	 * @param SimpleSAML_Configuration $spMetadata  The metadata for the SP.
-	 * @param string|NULL $AssertionConsumerServiceURL  AssertionConsumerServiceURL from request.
-	 * @param string|NULL $ProtocolBinding  ProtocolBinding from request.
-	 * @param int|NULL $AssertionConsumerServiceIndex  AssertionConsumerServiceIndex from request.
-	 * @return array  Array with the Location and Binding we should use for the response.
-	 */
-	private static function getAssertionConsumerService(array $supportedBindings, SimpleSAML_Configuration $spMetadata,
-		$AssertionConsumerServiceURL, $ProtocolBinding, $AssertionConsumerServiceIndex) {
-		assert('is_string($AssertionConsumerServiceURL) || is_null($AssertionConsumerServiceURL)');
-		assert('is_string($ProtocolBinding) || is_null($ProtocolBinding)');
-		assert('is_int($AssertionConsumerServiceIndex) || is_null($AssertionConsumerServiceIndex)');
-
-		/* We want to pick the best matching endpoint in the case where for example
-		 * only the ProtocolBinding is given. We therefore pick endpoints with the
-		 * following priority:
-		 *  1. isDefault="true"
-		 *  2. isDefault unset
-		 *  3. isDefault="false"
-		 */
-		$firstNotFalse = NULL;
-		$firstFalse = NULL;
-		foreach ($spMetadata->getEndpoints('AssertionConsumerService') as $ep) {
-
-			if ($AssertionConsumerServiceURL !== NULL && $ep['Location'] !== $AssertionConsumerServiceURL) {
-				continue;
-			}
-			if ($ProtocolBinding !== NULL && $ep['Binding'] !== $ProtocolBinding) {
-				continue;
-			}
-			if ($AssertionConsumerServiceIndex !== NULL && $ep['index'] !== $AssertionConsumerServiceIndex) {
-				continue;
-			}
-
-			if (!in_array($ep['Binding'], $supportedBindings, TRUE)) {
-				/* The endpoint has an unsupported binding. */
-				continue;
-			}
-
-			/* We have an endpoint that matches all our requirements. Check if it is the best one. */
-
-			if (array_key_exists('isDefault', $ep)) {
-				if ($ep['isDefault'] === TRUE) {
-					/* This is the first matching endpoint with isDefault set to TRUE. */
-					return $ep;
-				}
-				/* isDefault is set to FALSE, but the endpoint is still useable. */
-				if ($firstFalse === NULL) {
-					/* This is the first endpoint that we can use. */
-					$firstFalse = $ep;
-				}
-			} else if ($firstNotFalse === NULL) {
-				/* This is the first endpoint without isDefault set. */
-				$firstNotFalse = $ep;
-			}
-		}
-
-		if ($firstNotFalse !== NULL) {
-			return $firstNotFalse;
-		} elseif ($firstFalse !== NULL) {
-			return $firstFalse;
-		}
-
-		SimpleSAML\Logger::warning('Authentication request specifies invalid AssertionConsumerService:');
-		if ($AssertionConsumerServiceURL !== NULL) {
-			SimpleSAML\Logger::warning('AssertionConsumerServiceURL: ' . var_export($AssertionConsumerServiceURL, TRUE));
-		}
-		if ($ProtocolBinding !== NULL) {
-			SimpleSAML\Logger::warning('ProtocolBinding: ' . var_export($ProtocolBinding, TRUE));
-		}
-		if ($AssertionConsumerServiceIndex !== NULL) {
-			SimpleSAML\Logger::warning('AssertionConsumerServiceIndex: ' . var_export($AssertionConsumerServiceIndex, TRUE));
-		}
-
-		/* We have no good endpoints. Our last resort is to just use the default endpoint. */
-		return $spMetadata->getDefaultEndpoint('AssertionConsumerService', $supportedBindings);
-	}
-
-
-	/**
-	 * Receive an authentication request.
-	 *
-	 * @param SimpleSAML_IdP $idp  The IdP we are receiving it for.
-	 */
-	public static function receiveAuthnRequest(SimpleSAML_IdP $idp) {
-
-		$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
-		$idpMetadata = $idp->getConfig();
-
-		$supportedBindings = array(\SAML2\Constants::BINDING_HTTP_POST);
-		if ($idpMetadata->getBoolean('saml20.sendartifact', FALSE)) {
-			$supportedBindings[] = \SAML2\Constants::BINDING_HTTP_ARTIFACT;
-		}
-		if ($idpMetadata->getBoolean('saml20.hok.assertion', FALSE)) {
-			$supportedBindings[] = \SAML2\Constants::BINDING_HOK_SSO;
-		}
-
-		if (isset($_REQUEST['spentityid'])) {
-			/* IdP initiated authentication. */
-
-			if (isset($_REQUEST['cookieTime'])) {
-				$cookieTime = (int)$_REQUEST['cookieTime'];
-				if ($cookieTime + 5 > time()) {
-					/*
-					 * Less than five seconds has passed since we were
-					 * here the last time. Cookies are probably disabled.
-					 */
-					\SimpleSAML\Utils\HTTP::checkSessionCookie(\SimpleSAML\Utils\HTTP::getSelfURL());
-				}
-			}
-
-			$spEntityId = (string)$_REQUEST['spentityid'];
-			$spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
-
-			if (isset($_REQUEST['RelayState'])) {
-				$relayState = (string)$_REQUEST['RelayState'];
-			} else {
-				$relayState = NULL;
-			}
-
-			if (isset($_REQUEST['binding'])){
-				$protocolBinding = (string)$_REQUEST['binding'];
-			} else {
-				$protocolBinding = NULL;
-			}
-
-			if (isset($_REQUEST['NameIDFormat'])) {
-				$nameIDFormat = (string)$_REQUEST['NameIDFormat'];
-			} else {
-				$nameIDFormat = NULL;
-			}
-
-			$requestId = NULL;
-			$IDPList = array();
-			$ProxyCount = NULL;
-			$RequesterID = NULL;
-			$forceAuthn = FALSE;
-			$isPassive = FALSE;
-			$consumerURL = NULL;
-			$consumerIndex = NULL;
-			$extensions = NULL;
-			$allowCreate = TRUE;
-			$authnContext = null;
-
-			$idpInit = TRUE;
-
-			SimpleSAML\Logger::info('SAML2.0 - IdP.SSOService: IdP initiated authentication: '. var_export($spEntityId, TRUE));
-
-		} else {
-
-			$binding = \SAML2\Binding::getCurrentBinding();
-			$request = $binding->receive();
-
-			if (!($request instanceof \SAML2\AuthnRequest)) {
-				throw new SimpleSAML_Error_BadRequest('Message received on authentication request endpoint wasn\'t an authentication request.');
-			}
-
-			$spEntityId = $request->getIssuer();
-			if ($spEntityId === NULL) {
-				throw new SimpleSAML_Error_BadRequest('Received message on authentication request endpoint without issuer.');
-			}
-			$spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
-
-			sspmod_saml_Message::validateMessage($spMetadata, $idpMetadata, $request);
-
-			$relayState = $request->getRelayState();
-
-			$requestId = $request->getId();
-			$IDPList = $request->getIDPList();
-			$ProxyCount = $request->getProxyCount();
-			if ($ProxyCount !== null) $ProxyCount--;
-			$RequesterID = $request->getRequesterID();
-			$forceAuthn = $request->getForceAuthn();
-			$isPassive = $request->getIsPassive();
-			$consumerURL = $request->getAssertionConsumerServiceURL();
-			$protocolBinding = $request->getProtocolBinding();
-			$consumerIndex = $request->getAssertionConsumerServiceIndex();
-			$extensions = $request->getExtensions();
-			$authnContext = $request->getRequestedAuthnContext();
-
-			$nameIdPolicy = $request->getNameIdPolicy();
-			if (isset($nameIdPolicy['Format'])) {
-				$nameIDFormat = $nameIdPolicy['Format'];
-			} else {
-				$nameIDFormat = NULL;
-			}
-			if (isset($nameIdPolicy['AllowCreate'])) {
-				$allowCreate = $nameIdPolicy['AllowCreate'];
-			} else {
-				$allowCreate = FALSE;
-			}
-
-			$idpInit = FALSE;
-
-			SimpleSAML\Logger::info('SAML2.0 - IdP.SSOService: incoming authentication request: '. var_export($spEntityId, TRUE));
-		}
-
-		SimpleSAML_Stats::log('saml:idp:AuthnRequest', array(
-			'spEntityID' => $spEntityId,
-			'idpEntityID' => $idpMetadata->getString('entityid'),
-			'forceAuthn' => $forceAuthn,
-			'isPassive' => $isPassive,
-			'protocol' => 'saml2',
-			'idpInit' => $idpInit,
-		));
-
-		$acsEndpoint = self::getAssertionConsumerService($supportedBindings, $spMetadata, $consumerURL, $protocolBinding, $consumerIndex);
-
-		$IDPList = array_unique(array_merge($IDPList, $spMetadata->getArrayizeString('IDPList', array())));
-		if ($ProxyCount === null) $ProxyCount = $spMetadata->getInteger('ProxyCount', null);
-
-		if (!$forceAuthn) {
-			$forceAuthn = $spMetadata->getBoolean('ForceAuthn', FALSE);
-		}
-
-		$sessionLostParams = array(
-			'spentityid' => $spEntityId,
-			'cookieTime' => time(),
-		);
-		if ($relayState !== NULL) {
-			$sessionLostParams['RelayState'] = $relayState;
-		}
-
-		$sessionLostURL = \SimpleSAML\Utils\HTTP::addURLParameters(
+class sspmod_saml_IdP_SAML2
+{
+
+    /**
+     * Send a response to the SP.
+     *
+     * @param array $state The authentication state.
+     */
+    public static function sendResponse(array $state)
+    {
+        assert('isset($state["Attributes"])');
+        assert('isset($state["SPMetadata"])');
+        assert('isset($state["saml:ConsumerURL"])');
+        assert('array_key_exists("saml:RequestId", $state)'); // Can be NULL
+        assert('array_key_exists("saml:RelayState", $state)'); // Can be NULL.
+
+        $spMetadata = $state["SPMetadata"];
+        $spEntityId = $spMetadata['entityid'];
+        $spMetadata = SimpleSAML_Configuration::loadFromArray(
+            $spMetadata,
+            '$metadata['.var_export($spEntityId, true).']'
+        );
+
+        SimpleSAML\Logger::info('Sending SAML 2.0 Response to '.var_export($spEntityId, true));
+
+        $requestId = $state['saml:RequestId'];
+        $relayState = $state['saml:RelayState'];
+        $consumerURL = $state['saml:ConsumerURL'];
+        $protocolBinding = $state['saml:Binding'];
+
+        $idp = SimpleSAML_IdP::getByState($state);
+
+        $idpMetadata = $idp->getConfig();
+
+        $assertion = self::buildAssertion($idpMetadata, $spMetadata, $state);
+
+        if (isset($state['saml:AuthenticatingAuthority'])) {
+            $assertion->setAuthenticatingAuthority($state['saml:AuthenticatingAuthority']);
+        }
+
+        // create the session association (for logout)
+        $association = array(
+            'id'                => 'saml:'.$spEntityId,
+            'Handler'           => 'sspmod_saml_IdP_SAML2',
+            'Expires'           => $assertion->getSessionNotOnOrAfter(),
+            'saml:entityID'     => $spEntityId,
+            'saml:NameID'       => $state['saml:idp:NameID'],
+            'saml:SessionIndex' => $assertion->getSessionIndex(),
+        );
+
+        // maybe encrypt the assertion
+        $assertion = self::encryptAssertion($idpMetadata, $spMetadata, $assertion);
+
+        // create the response
+        $ar = self::buildResponse($idpMetadata, $spMetadata, $consumerURL);
+        $ar->setInResponseTo($requestId);
+        $ar->setRelayState($relayState);
+        $ar->setAssertions(array($assertion));
+
+        // register the session association with the IdP
+        $idp->addAssociation($association);
+
+        $statsData = array(
+            'spEntityID'  => $spEntityId,
+            'idpEntityID' => $idpMetadata->getString('entityid'),
+            'protocol'    => 'saml2',
+        );
+        if (isset($state['saml:AuthnRequestReceivedAt'])) {
+            $statsData['logintime'] = microtime(true) - $state['saml:AuthnRequestReceivedAt'];
+        }
+        SimpleSAML_Stats::log('saml:idp:Response', $statsData);
+
+        // send the response
+        $binding = \SAML2\Binding::getBinding($protocolBinding);
+        $binding->send($ar);
+    }
+
+
+    /**
+     * Handle authentication error.
+     *
+     * SimpleSAML_Error_Exception $exception  The exception.
+     *
+     * @param array $state The error state.
+     */
+    public static function handleAuthError(SimpleSAML_Error_Exception $exception, array $state)
+    {
+        assert('isset($state["SPMetadata"])');
+        assert('isset($state["saml:ConsumerURL"])');
+        assert('array_key_exists("saml:RequestId", $state)'); // Can be NULL.
+        assert('array_key_exists("saml:RelayState", $state)'); // Can be NULL.
+
+        $spMetadata = $state["SPMetadata"];
+        $spEntityId = $spMetadata['entityid'];
+        $spMetadata = SimpleSAML_Configuration::loadFromArray(
+            $spMetadata,
+            '$metadata['.var_export($spEntityId, true).']'
+        );
+
+        $requestId = $state['saml:RequestId'];
+        $relayState = $state['saml:RelayState'];
+        $consumerURL = $state['saml:ConsumerURL'];
+        $protocolBinding = $state['saml:Binding'];
+
+        $idp = SimpleSAML_IdP::getByState($state);
+
+        $idpMetadata = $idp->getConfig();
+
+        $error = sspmod_saml_Error::fromException($exception);
+
+        SimpleSAML\Logger::warning("Returning error to SP with entity ID '".var_export($spEntityId, true)."'.");
+        $exception->log(SimpleSAML\Logger::WARNING);
+
+        $ar = self::buildResponse($idpMetadata, $spMetadata, $consumerURL);
+        $ar->setInResponseTo($requestId);
+        $ar->setRelayState($relayState);
+
+        $status = array(
+            'Code'    => $error->getStatus(),
+            'SubCode' => $error->getSubStatus(),
+            'Message' => $error->getStatusMessage(),
+        );
+        $ar->setStatus($status);
+
+        $statsData = array(
+            'spEntityID'  => $spEntityId,
+            'idpEntityID' => $idpMetadata->getString('entityid'),
+            'protocol'    => 'saml2',
+            'error'       => $status,
+        );
+        if (isset($state['saml:AuthnRequestReceivedAt'])) {
+            $statsData['logintime'] = microtime(true) - $state['saml:AuthnRequestReceivedAt'];
+        }
+        SimpleSAML_Stats::log('saml:idp:Response:error', $statsData);
+
+        $binding = \SAML2\Binding::getBinding($protocolBinding);
+        $binding->send($ar);
+    }
+
+
+    /**
+     * Find SP AssertionConsumerService based on parameter in AuthnRequest.
+     *
+     * @param array                    $supportedBindings The bindings we allow for the response.
+     * @param SimpleSAML_Configuration $spMetadata The metadata for the SP.
+     * @param string|NULL              $AssertionConsumerServiceURL AssertionConsumerServiceURL from request.
+     * @param string|NULL              $ProtocolBinding ProtocolBinding from request.
+     * @param int|NULL                 $AssertionConsumerServiceIndex AssertionConsumerServiceIndex from request.
+     *
+     * @return array  Array with the Location and Binding we should use for the response.
+     */
+    private static function getAssertionConsumerService(
+        array $supportedBindings,
+        SimpleSAML_Configuration $spMetadata,
+        $AssertionConsumerServiceURL,
+        $ProtocolBinding,
+        $AssertionConsumerServiceIndex
+    ) {
+        assert('is_string($AssertionConsumerServiceURL) || is_null($AssertionConsumerServiceURL)');
+        assert('is_string($ProtocolBinding) || is_null($ProtocolBinding)');
+        assert('is_int($AssertionConsumerServiceIndex) || is_null($AssertionConsumerServiceIndex)');
+
+        /* We want to pick the best matching endpoint in the case where for example
+         * only the ProtocolBinding is given. We therefore pick endpoints with the
+         * following priority:
+         *  1. isDefault="true"
+         *  2. isDefault unset
+         *  3. isDefault="false"
+         */
+        $firstNotFalse = null;
+        $firstFalse = null;
+        foreach ($spMetadata->getEndpoints('AssertionConsumerService') as $ep) {
+            if ($AssertionConsumerServiceURL !== null && $ep['Location'] !== $AssertionConsumerServiceURL) {
+                continue;
+            }
+            if ($ProtocolBinding !== null && $ep['Binding'] !== $ProtocolBinding) {
+                continue;
+            }
+            if ($AssertionConsumerServiceIndex !== null && $ep['index'] !== $AssertionConsumerServiceIndex) {
+                continue;
+            }
+
+            if (!in_array($ep['Binding'], $supportedBindings, true)) {
+                /* The endpoint has an unsupported binding. */
+                continue;
+            }
+
+            // we have an endpoint that matches all our requirements. Check if it is the best one
+
+            if (array_key_exists('isDefault', $ep)) {
+                if ($ep['isDefault'] === true) {
+                    // this is the first matching endpoint with isDefault set to true
+                    return $ep;
+                }
+                // isDefault is set to FALSE, but the endpoint is still usable
+                if ($firstFalse === null) {
+                    // this is the first endpoint that we can use
+                    $firstFalse = $ep;
+                }
+            } else {
+                if ($firstNotFalse === null) {
+                    // this is the first endpoint without isDefault set
+                    $firstNotFalse = $ep;
+                }
+            }
+        }
+
+        if ($firstNotFalse !== null) {
+            return $firstNotFalse;
+        } elseif ($firstFalse !== null) {
+            return $firstFalse;
+        }
+
+        SimpleSAML\Logger::warning('Authentication request specifies invalid AssertionConsumerService:');
+        if ($AssertionConsumerServiceURL !== null) {
+            SimpleSAML\Logger::warning('AssertionConsumerServiceURL: '.var_export($AssertionConsumerServiceURL, true));
+        }
+        if ($ProtocolBinding !== null) {
+            SimpleSAML\Logger::warning('ProtocolBinding: '.var_export($ProtocolBinding, true));
+        }
+        if ($AssertionConsumerServiceIndex !== null) {
+            SimpleSAML\Logger::warning(
+                'AssertionConsumerServiceIndex: '.var_export($AssertionConsumerServiceIndex, true)
+            );
+        }
+
+        // we have no good endpoints. Our last resort is to just use the default endpoint
+        return $spMetadata->getDefaultEndpoint('AssertionConsumerService', $supportedBindings);
+    }
+
+
+    /**
+     * Receive an authentication request.
+     *
+     * @param SimpleSAML_IdP $idp The IdP we are receiving it for.
+     * @throws SimpleSAML_Error_BadRequest In case an error occurs when trying to receive the request.
+     */
+    public static function receiveAuthnRequest(SimpleSAML_IdP $idp)
+    {
+
+        $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
+        $idpMetadata = $idp->getConfig();
+
+        $supportedBindings = array(\SAML2\Constants::BINDING_HTTP_POST);
+        if ($idpMetadata->getBoolean('saml20.sendartifact', false)) {
+            $supportedBindings[] = \SAML2\Constants::BINDING_HTTP_ARTIFACT;
+        }
+        if ($idpMetadata->getBoolean('saml20.hok.assertion', false)) {
+            $supportedBindings[] = \SAML2\Constants::BINDING_HOK_SSO;
+        }
+
+        if (isset($_REQUEST['spentityid'])) {
+            /* IdP initiated authentication. */
+
+            if (isset($_REQUEST['cookieTime'])) {
+                $cookieTime = (int) $_REQUEST['cookieTime'];
+                if ($cookieTime + 5 > time()) {
+                    /*
+                     * Less than five seconds has passed since we were
+                     * here the last time. Cookies are probably disabled.
+                     */
+                    \SimpleSAML\Utils\HTTP::checkSessionCookie(\SimpleSAML\Utils\HTTP::getSelfURL());
+                }
+            }
+
+            $spEntityId = (string) $_REQUEST['spentityid'];
+            $spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
+
+            if (isset($_REQUEST['RelayState'])) {
+                $relayState = (string) $_REQUEST['RelayState'];
+            } else {
+                $relayState = null;
+            }
+
+            if (isset($_REQUEST['binding'])) {
+                $protocolBinding = (string) $_REQUEST['binding'];
+            } else {
+                $protocolBinding = null;
+            }
+
+            if (isset($_REQUEST['NameIDFormat'])) {
+                $nameIDFormat = (string) $_REQUEST['NameIDFormat'];
+            } else {
+                $nameIDFormat = null;
+            }
+
+            $requestId = null;
+            $IDPList = array();
+            $ProxyCount = null;
+            $RequesterID = null;
+            $forceAuthn = false;
+            $isPassive = false;
+            $consumerURL = null;
+            $consumerIndex = null;
+            $extensions = null;
+            $allowCreate = true;
+            $authnContext = null;
+
+            $idpInit = true;
+
+            SimpleSAML\Logger::info(
+                'SAML2.0 - IdP.SSOService: IdP initiated authentication: '.var_export($spEntityId, true)
+            );
+        } else {
+            $binding = \SAML2\Binding::getCurrentBinding();
+            $request = $binding->receive();
+
+            if (!($request instanceof \SAML2\AuthnRequest)) {
+                throw new SimpleSAML_Error_BadRequest(
+                    'Message received on authentication request endpoint wasn\'t an authentication request.'
+                );
+            }
+
+            $spEntityId = $request->getIssuer();
+            if ($spEntityId === null) {
+                throw new SimpleSAML_Error_BadRequest(
+                    'Received message on authentication request endpoint without issuer.'
+                );
+            }
+            $spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
+
+            sspmod_saml_Message::validateMessage($spMetadata, $idpMetadata, $request);
+
+            $relayState = $request->getRelayState();
+
+            $requestId = $request->getId();
+            $IDPList = $request->getIDPList();
+            $ProxyCount = $request->getProxyCount();
+            if ($ProxyCount !== null) {
+                $ProxyCount--;
+            }
+            $RequesterID = $request->getRequesterID();
+            $forceAuthn = $request->getForceAuthn();
+            $isPassive = $request->getIsPassive();
+            $consumerURL = $request->getAssertionConsumerServiceURL();
+            $protocolBinding = $request->getProtocolBinding();
+            $consumerIndex = $request->getAssertionConsumerServiceIndex();
+            $extensions = $request->getExtensions();
+            $authnContext = $request->getRequestedAuthnContext();
+
+            $nameIdPolicy = $request->getNameIdPolicy();
+            if (isset($nameIdPolicy['Format'])) {
+                $nameIDFormat = $nameIdPolicy['Format'];
+            } else {
+                $nameIDFormat = null;
+            }
+            if (isset($nameIdPolicy['AllowCreate'])) {
+                $allowCreate = $nameIdPolicy['AllowCreate'];
+            } else {
+                $allowCreate = false;
+            }
+
+            $idpInit = false;
+
+            SimpleSAML\Logger::info(
+                'SAML2.0 - IdP.SSOService: incoming authentication request: '.var_export($spEntityId, true)
+            );
+        }
+
+        SimpleSAML_Stats::log('saml:idp:AuthnRequest', array(
+            'spEntityID'  => $spEntityId,
+            'idpEntityID' => $idpMetadata->getString('entityid'),
+            'forceAuthn'  => $forceAuthn,
+            'isPassive'   => $isPassive,
+            'protocol'    => 'saml2',
+            'idpInit'     => $idpInit,
+        ));
+
+        $acsEndpoint = self::getAssertionConsumerService(
+            $supportedBindings,
+            $spMetadata,
+            $consumerURL,
+            $protocolBinding,
+            $consumerIndex
+        );
+
+        $IDPList = array_unique(array_merge($IDPList, $spMetadata->getArrayizeString('IDPList', array())));
+        if ($ProxyCount === null) {
+            $ProxyCount = $spMetadata->getInteger('ProxyCount', null);
+        }
+
+        if (!$forceAuthn) {
+            $forceAuthn = $spMetadata->getBoolean('ForceAuthn', false);
+        }
+
+        $sessionLostParams = array(
+            'spentityid' => $spEntityId,
+            'cookieTime' => time(),
+        );
+        if ($relayState !== null) {
+            $sessionLostParams['RelayState'] = $relayState;
+        }
+
+        $sessionLostURL = \SimpleSAML\Utils\HTTP::addURLParameters(
             \SimpleSAML\Utils\HTTP::getSelfURLNoQuery(),
-			$sessionLostParams);
-
-		$state = array(
-			'Responder' => array('sspmod_saml_IdP_SAML2', 'sendResponse'),
-			SimpleSAML_Auth_State::EXCEPTION_HANDLER_FUNC => array('sspmod_saml_IdP_SAML2', 'handleAuthError'),
-			SimpleSAML_Auth_State::RESTART => $sessionLostURL,
-
-			'SPMetadata' => $spMetadata->toArray(),
-			'saml:RelayState' => $relayState,
-			'saml:RequestId' => $requestId,
-			'saml:IDPList' => $IDPList,
-			'saml:ProxyCount' => $ProxyCount,
-			'saml:RequesterID' => $RequesterID,
-			'ForceAuthn' => $forceAuthn,
-			'isPassive' => $isPassive,
-			'saml:ConsumerURL' => $acsEndpoint['Location'],
-			'saml:Binding' => $acsEndpoint['Binding'],
-			'saml:NameIDFormat' => $nameIDFormat,
-			'saml:AllowCreate' => $allowCreate,
-			'saml:Extensions' => $extensions,
-			'saml:AuthnRequestReceivedAt' => microtime(TRUE),
-			'saml:RequestedAuthnContext' => $authnContext,
-		);
-
-		$idp->handleAuthenticationRequest($state);
-	}
-
-
-	/**
-	 * Send a logout request to a given association.
-	 *
-	 * @param SimpleSAML_IdP $idp  The IdP we are sending a logout request from.
-	 * @param array $association  The association that should be terminated.
-	 * @param string|NULL $relayState  An id that should be carried across the logout.
-	 */
-	public static function sendLogoutRequest(SimpleSAML_IdP $idp, array $association, $relayState) {
-		assert('is_string($relayState) || is_null($relayState)');
-
-		SimpleSAML\Logger::info('Sending SAML 2.0 LogoutRequest to: '. var_export($association['saml:entityID'], TRUE));
-
-		$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
-		$idpMetadata = $idp->getConfig();
-		$spMetadata = $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote');
-
-		SimpleSAML_Stats::log('saml:idp:LogoutRequest:sent', array(
-			'spEntityID' => $association['saml:entityID'],
-			'idpEntityID' => $idpMetadata->getString('entityid'),
-		));
-
-		$dst = $spMetadata->getEndpointPrioritizedByBinding('SingleLogoutService', array(
-			\SAML2\Constants::BINDING_HTTP_REDIRECT,
-			\SAML2\Constants::BINDING_HTTP_POST)
-		);
-		$binding = \SAML2\Binding::getBinding($dst['Binding']);
-		$lr = self::buildLogoutRequest($idpMetadata, $spMetadata, $association, $relayState);
-		$lr->setDestination($dst['Location']);
-
-		$binding->send($lr);
-	}
-
-
-	/**
-	 * Send a logout response.
-	 *
-	 * @param SimpleSAML_IdP $idp  The IdP we are sending a logout request from.
-	 * @param array &$state  The logout state array.
-	 */
-	public static function sendLogoutResponse(SimpleSAML_IdP $idp, array $state) {
-		assert('isset($state["saml:SPEntityId"])');
-		assert('isset($state["saml:RequestId"])');
-		assert('array_key_exists("saml:RelayState", $state)'); // Can be NULL.
-
-		$spEntityId = $state['saml:SPEntityId'];
-
-		$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
-		$idpMetadata = $idp->getConfig();
-		$spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
-
-		$lr = sspmod_saml_Message::buildLogoutResponse($idpMetadata, $spMetadata);
-		$lr->setInResponseTo($state['saml:RequestId']);
-		$lr->setRelayState($state['saml:RelayState']);
-
-		if (isset($state['core:Failed']) && $state['core:Failed']) {
-			$partial = TRUE;
-			$lr->setStatus(array(
-				'Code' => \SAML2\Constants::STATUS_SUCCESS,
-				'SubCode' => \SAML2\Constants::STATUS_PARTIAL_LOGOUT,
-			));
-			SimpleSAML\Logger::info('Sending logout response for partial logout to SP ' . var_export($spEntityId, TRUE));
-		} else {
-			$partial = FALSE;
-			SimpleSAML\Logger::debug('Sending logout response to SP ' . var_export($spEntityId, TRUE));
-		}
-
-		SimpleSAML_Stats::log('saml:idp:LogoutResponse:sent', array(
-			'spEntityID' => $spEntityId,
-			'idpEntityID' => $idpMetadata->getString('entityid'),
-			'partial' => $partial
-		));
-		$dst = $spMetadata->getEndpointPrioritizedByBinding('SingleLogoutService', array(
-			\SAML2\Constants::BINDING_HTTP_REDIRECT,
-			\SAML2\Constants::BINDING_HTTP_POST)
-		);
-		$binding = \SAML2\Binding::getBinding($dst['Binding']);
-		if (isset($dst['ResponseLocation'])) {
-			$dst = $dst['ResponseLocation'];
-		} else {
-			$dst = $dst['Location'];
-		}
-		$lr->setDestination($dst);
-
-		$binding->send($lr);
-	}
-
-
-	/**
-	 * Receive a logout message.
-	 *
-	 * @param SimpleSAML_IdP $idp  The IdP we are receiving it for.
-	 */
-	public static function receiveLogoutMessage(SimpleSAML_IdP $idp) {
-
-		$binding = \SAML2\Binding::getCurrentBinding();
-		$message = $binding->receive();
-
-		$spEntityId = $message->getIssuer();
-		if ($spEntityId === NULL) {
-			/* Without an issuer we have no way to respond to the message. */
-			throw new SimpleSAML_Error_BadRequest('Received message on logout endpoint without issuer.');
-		}
-
-		$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
-		$idpMetadata = $idp->getConfig();
-		$spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
-
-		sspmod_saml_Message::validateMessage($spMetadata, $idpMetadata, $message);
-
-		if ($message instanceof \SAML2\LogoutResponse) {
-
-			SimpleSAML\Logger::info('Received SAML 2.0 LogoutResponse from: '. var_export($spEntityId, TRUE));
-			$statsData = array(
-				'spEntityID' => $spEntityId,
-				'idpEntityID' => $idpMetadata->getString('entityid'),
-			);
-			if (!$message->isSuccess()) {
-				$statsData['error'] = $message->getStatus();
-			}
-			SimpleSAML_Stats::log('saml:idp:LogoutResponse:recv', $statsData);
-
-			$relayState = $message->getRelayState();
-
-			if (!$message->isSuccess()) {
-				$logoutError = sspmod_saml_Message::getResponseError($message);
-				SimpleSAML\Logger::warning('Unsuccessful logout. Status was: ' . $logoutError);
-			} else {
-				$logoutError = NULL;
-			}
-
-			$assocId = 'saml:' . $spEntityId;
-
-			$idp->handleLogoutResponse($assocId, $relayState, $logoutError);
-
-
-		} elseif ($message instanceof \SAML2\LogoutRequest) {
-
-			SimpleSAML\Logger::info('Received SAML 2.0 LogoutRequest from: '. var_export($spEntityId, TRUE));
-			SimpleSAML_Stats::log('saml:idp:LogoutRequest:recv', array(
-				'spEntityID' => $spEntityId,
-				'idpEntityID' => $idpMetadata->getString('entityid'),
-			));
-
-			$spStatsId = $spMetadata->getString('core:statistics-id', $spEntityId);
-			SimpleSAML\Logger::stats('saml20-idp-SLO spinit ' . $spStatsId . ' ' . $idpMetadata->getString('entityid'));
-
-			$state = array(
-				'Responder' => array('sspmod_saml_IdP_SAML2', 'sendLogoutResponse'),
-				'saml:SPEntityId' => $spEntityId,
-				'saml:RelayState' => $message->getRelayState(),
-				'saml:RequestId' => $message->getId(),
-			);
-
-			$assocId = 'saml:' . $spEntityId;
-			$idp->handleLogoutRequest($state, $assocId);
-
-		} else {
-			throw new SimpleSAML_Error_BadRequest('Unknown message received on logout endpoint: ' . get_class($message));
-		}
-
-	}
-
-
-	/**
+            $sessionLostParams
+        );
+
+        $state = array(
+            'Responder'                                   => array('sspmod_saml_IdP_SAML2', 'sendResponse'),
+            SimpleSAML_Auth_State::EXCEPTION_HANDLER_FUNC => array('sspmod_saml_IdP_SAML2', 'handleAuthError'),
+            SimpleSAML_Auth_State::RESTART                => $sessionLostURL,
+
+            'SPMetadata'                  => $spMetadata->toArray(),
+            'saml:RelayState'             => $relayState,
+            'saml:RequestId'              => $requestId,
+            'saml:IDPList'                => $IDPList,
+            'saml:ProxyCount'             => $ProxyCount,
+            'saml:RequesterID'            => $RequesterID,
+            'ForceAuthn'                  => $forceAuthn,
+            'isPassive'                   => $isPassive,
+            'saml:ConsumerURL'            => $acsEndpoint['Location'],
+            'saml:Binding'                => $acsEndpoint['Binding'],
+            'saml:NameIDFormat'           => $nameIDFormat,
+            'saml:AllowCreate'            => $allowCreate,
+            'saml:Extensions'             => $extensions,
+            'saml:AuthnRequestReceivedAt' => microtime(true),
+            'saml:RequestedAuthnContext'  => $authnContext,
+        );
+
+        $idp->handleAuthenticationRequest($state);
+    }
+
+
+    /**
+     * Send a logout request to a given association.
+     *
+     * @param SimpleSAML_IdP $idp The IdP we are sending a logout request from.
+     * @param array          $association The association that should be terminated.
+     * @param string|NULL    $relayState An id that should be carried across the logout.
+     */
+    public static function sendLogoutRequest(SimpleSAML_IdP $idp, array $association, $relayState)
+    {
+        assert('is_string($relayState) || is_null($relayState)');
+
+        SimpleSAML\Logger::info('Sending SAML 2.0 LogoutRequest to: '.var_export($association['saml:entityID'], true));
+
+        $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
+        $idpMetadata = $idp->getConfig();
+        $spMetadata = $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote');
+
+        SimpleSAML_Stats::log('saml:idp:LogoutRequest:sent', array(
+            'spEntityID'  => $association['saml:entityID'],
+            'idpEntityID' => $idpMetadata->getString('entityid'),
+        ));
+
+        $dst = $spMetadata->getEndpointPrioritizedByBinding(
+            'SingleLogoutService',
+            array(
+                \SAML2\Constants::BINDING_HTTP_REDIRECT,
+                \SAML2\Constants::BINDING_HTTP_POST
+            )
+        );
+        $binding = \SAML2\Binding::getBinding($dst['Binding']);
+        $lr = self::buildLogoutRequest($idpMetadata, $spMetadata, $association, $relayState);
+        $lr->setDestination($dst['Location']);
+
+        $binding->send($lr);
+    }
+
+
+    /**
+     * Send a logout response.
+     *
+     * @param SimpleSAML_IdP $idp The IdP we are sending a logout request from.
+     * @param array          &$state The logout state array.
+     */
+    public static function sendLogoutResponse(SimpleSAML_IdP $idp, array $state)
+    {
+        assert('isset($state["saml:SPEntityId"])');
+        assert('isset($state["saml:RequestId"])');
+        assert('array_key_exists("saml:RelayState", $state)'); // Can be NULL.
+
+        $spEntityId = $state['saml:SPEntityId'];
+
+        $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
+        $idpMetadata = $idp->getConfig();
+        $spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
+
+        $lr = sspmod_saml_Message::buildLogoutResponse($idpMetadata, $spMetadata);
+        $lr->setInResponseTo($state['saml:RequestId']);
+        $lr->setRelayState($state['saml:RelayState']);
+
+        if (isset($state['core:Failed']) && $state['core:Failed']) {
+            $partial = true;
+            $lr->setStatus(array(
+                'Code'    => \SAML2\Constants::STATUS_SUCCESS,
+                'SubCode' => \SAML2\Constants::STATUS_PARTIAL_LOGOUT,
+            ));
+            SimpleSAML\Logger::info('Sending logout response for partial logout to SP '.var_export($spEntityId, true));
+        } else {
+            $partial = false;
+            SimpleSAML\Logger::debug('Sending logout response to SP '.var_export($spEntityId, true));
+        }
+
+        SimpleSAML_Stats::log('saml:idp:LogoutResponse:sent', array(
+            'spEntityID'  => $spEntityId,
+            'idpEntityID' => $idpMetadata->getString('entityid'),
+            'partial'     => $partial
+        ));
+        $dst = $spMetadata->getEndpointPrioritizedByBinding(
+            'SingleLogoutService',
+            array(
+                \SAML2\Constants::BINDING_HTTP_REDIRECT,
+                \SAML2\Constants::BINDING_HTTP_POST
+            )
+        );
+        $binding = \SAML2\Binding::getBinding($dst['Binding']);
+        if (isset($dst['ResponseLocation'])) {
+            $dst = $dst['ResponseLocation'];
+        } else {
+            $dst = $dst['Location'];
+        }
+        $lr->setDestination($dst);
+
+        $binding->send($lr);
+    }
+
+
+    /**
+     * Receive a logout message.
+     *
+     * @param SimpleSAML_IdP $idp The IdP we are receiving it for.
+     * @throws SimpleSAML_Error_BadRequest In case an error occurs while trying to receive the logout message.
+     */
+    public static function receiveLogoutMessage(SimpleSAML_IdP $idp)
+    {
+
+        $binding = \SAML2\Binding::getCurrentBinding();
+        $message = $binding->receive();
+
+        $spEntityId = $message->getIssuer();
+        if ($spEntityId === null) {
+            /* Without an issuer we have no way to respond to the message. */
+            throw new SimpleSAML_Error_BadRequest('Received message on logout endpoint without issuer.');
+        }
+
+        $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
+        $idpMetadata = $idp->getConfig();
+        $spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
+
+        sspmod_saml_Message::validateMessage($spMetadata, $idpMetadata, $message);
+
+        if ($message instanceof \SAML2\LogoutResponse) {
+            SimpleSAML\Logger::info('Received SAML 2.0 LogoutResponse from: '.var_export($spEntityId, true));
+            $statsData = array(
+                'spEntityID'  => $spEntityId,
+                'idpEntityID' => $idpMetadata->getString('entityid'),
+            );
+            if (!$message->isSuccess()) {
+                $statsData['error'] = $message->getStatus();
+            }
+            SimpleSAML_Stats::log('saml:idp:LogoutResponse:recv', $statsData);
+
+            $relayState = $message->getRelayState();
+
+            if (!$message->isSuccess()) {
+                $logoutError = sspmod_saml_Message::getResponseError($message);
+                SimpleSAML\Logger::warning('Unsuccessful logout. Status was: '.$logoutError);
+            } else {
+                $logoutError = null;
+            }
+
+            $assocId = 'saml:'.$spEntityId;
+
+            $idp->handleLogoutResponse($assocId, $relayState, $logoutError);
+        } elseif ($message instanceof \SAML2\LogoutRequest) {
+            SimpleSAML\Logger::info('Received SAML 2.0 LogoutRequest from: '.var_export($spEntityId, true));
+            SimpleSAML_Stats::log('saml:idp:LogoutRequest:recv', array(
+                'spEntityID'  => $spEntityId,
+                'idpEntityID' => $idpMetadata->getString('entityid'),
+            ));
+
+            $spStatsId = $spMetadata->getString('core:statistics-id', $spEntityId);
+            SimpleSAML\Logger::stats('saml20-idp-SLO spinit '.$spStatsId.' '.$idpMetadata->getString('entityid'));
+
+            $state = array(
+                'Responder'       => array('sspmod_saml_IdP_SAML2', 'sendLogoutResponse'),
+                'saml:SPEntityId' => $spEntityId,
+                'saml:RelayState' => $message->getRelayState(),
+                'saml:RequestId'  => $message->getId(),
+            );
+
+            $assocId = 'saml:'.$spEntityId;
+            $idp->handleLogoutRequest($state, $assocId);
+        } else {
+            throw new SimpleSAML_Error_BadRequest('Unknown message received on logout endpoint: '.get_class($message));
+        }
+    }
+
+
+    /**
      * Retrieve a logout URL for a given logout association.
-	 *
-	 * @param SimpleSAML_IdP $idp  The IdP we are sending a logout request from.
-	 * @param array $association  The association that should be terminated.
-	 * @param string|NULL $relayState  An id that should be carried across the logout.
-	 */
-	public static function getLogoutURL(SimpleSAML_IdP $idp, array $association, $relayState) {
-		assert('is_string($relayState) || is_null($relayState)');
-
-		SimpleSAML\Logger::info('Sending SAML 2.0 LogoutRequest to: '. var_export($association['saml:entityID'], TRUE));
-
-		$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
-		$idpMetadata = $idp->getConfig();
-		$spMetadata = $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote');
-
-		$bindings = array(\SAML2\Constants::BINDING_HTTP_REDIRECT,
-						  \SAML2\Constants::BINDING_HTTP_POST);
-		$dst = $spMetadata->getEndpointPrioritizedByBinding('SingleLogoutService', $bindings);
-
-		if ($dst['Binding'] === \SAML2\Constants::BINDING_HTTP_POST) {
-			$params = array('association' => $association['id'], 'idp' => $idp->getId());
-			if ($relayState !== NULL) {
-				$params['RelayState'] = $relayState;
-			}
-			return SimpleSAML\Module::getModuleURL('core/idp/logout-iframe-post.php', $params);
-		}
-
-		$lr = self::buildLogoutRequest($idpMetadata, $spMetadata, $association, $relayState);
-		$lr->setDestination($dst['Location']);
-
-		$binding = new \SAML2\HTTPRedirect();
-		return $binding->getRedirectURL($lr);
-	}
-
-
-	/**
-	 * Retrieve the metadata for the given SP association.
-	 *
-	 * @param SimpleSAML_IdP $idp  The IdP the association belongs to.
-	 * @param array $association  The SP association.
-	 * @return SimpleSAML_Configuration  Configuration object for the SP metadata.
-	 */
-	public static function getAssociationConfig(SimpleSAML_IdP $idp, array $association) {
-		$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
-		try {
-			return $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote');
-		} catch (Exception $e) {
-			return SimpleSAML_Configuration::loadFromArray(array(), 'Unknown SAML 2 entity.');
-		}
-	}
-
-
-	/**
-	 * Calculate the NameID value that should be used.
-	 *
-	 * @param SimpleSAML_Configuration $idpMetadata  The metadata of the IdP.
-	 * @param SimpleSAML_Configuration $dstMetadata  The metadata of the SP.
-	 * @param array &$state  The authentication state of the user.
-	 * @return string  The NameID value.
-	 */
-	private static function generateNameIdValue(SimpleSAML_Configuration $idpMetadata,
-		SimpleSAML_Configuration $spMetadata, array &$state) {
-
-		$attribute = $spMetadata->getString('simplesaml.nameidattribute', NULL);
-		if ($attribute === NULL) {
-			$attribute = $idpMetadata->getString('simplesaml.nameidattribute', NULL);
-			if ($attribute === NULL) {
-				if (!isset($state['UserID'])) {
-					SimpleSAML\Logger::error('Unable to generate NameID. Check the userid.attribute option.');
-				}
-				$attributeValue = $state['UserID'];
-				$idpEntityId = $idpMetadata->getString('entityid');
-				$spEntityId = $spMetadata->getString('entityid');
-
-				$secretSalt = SimpleSAML\Utils\Config::getSecretSalt();
-
-				$uidData = 'uidhashbase' . $secretSalt;
-				$uidData .= strlen($idpEntityId) . ':' . $idpEntityId;
-				$uidData .= strlen($spEntityId) . ':' . $spEntityId;
-				$uidData .= strlen($attributeValue) . ':' . $attributeValue;
-				$uidData .= $secretSalt;
-
-				return hash('sha1', $uidData);
-			}
-		}
-
-		$attributes = $state['Attributes'];
-		if (!array_key_exists($attribute, $attributes)) {
-			SimpleSAML\Logger::error('Unable to add NameID: Missing ' . var_export($attribute, TRUE) .
-				' in the attributes of the user.');
-			return NULL;
-		}
-
-		return $attributes[$attribute][0];
-	}
-
-
-	/**
-	 * Helper function for encoding attributes.
-	 *
-	 * @param SimpleSAML_Configuration $idpMetadata  The metadata of the IdP.
-	 * @param SimpleSAML_Configuration $spMetadata  The metadata of the SP.
-	 * @param array $attributes  The attributes of the user
-	 * @return array  The encoded attributes.
-	 */
-	private static function encodeAttributes(SimpleSAML_Configuration $idpMetadata,
-		SimpleSAML_Configuration $spMetadata, array $attributes) {
-
-		$base64Attributes = $spMetadata->getBoolean('base64attributes', NULL);
-		if ($base64Attributes === NULL) {
-			$base64Attributes = $idpMetadata->getBoolean('base64attributes', FALSE);
-		}
-
-		if ($base64Attributes) {
-			$defaultEncoding = 'base64';
-		} else {
-			$defaultEncoding = 'string';
-		}
-
-		$srcEncodings = $idpMetadata->getArray('attributeencodings', array());
-		$dstEncodings = $spMetadata->getArray('attributeencodings', array());
-
-		/*
-		 * Merge the two encoding arrays. Encodings specified in the target metadata
-		 * takes precedence over the source metadata.
-		 */
-		$encodings = array_merge($srcEncodings, $dstEncodings);
-
-		$ret = array();
-		foreach ($attributes as $name => $values) {
-			$ret[$name] = array();
-			if (array_key_exists($name, $encodings)) {
-				$encoding = $encodings[$name];
-			} else {
-				$encoding = $defaultEncoding;
-			}
-
-			foreach ($values as $value) {
+     *
+     * @param SimpleSAML_IdP $idp The IdP we are sending a logout request from.
+     * @param array          $association The association that should be terminated.
+     * @param string|NULL    $relayState An id that should be carried across the logout.
+     *
+     * @return string The logout URL.
+     */
+    public static function getLogoutURL(SimpleSAML_IdP $idp, array $association, $relayState)
+    {
+        assert('is_string($relayState) || is_null($relayState)');
+
+        SimpleSAML\Logger::info('Sending SAML 2.0 LogoutRequest to: '.var_export($association['saml:entityID'], true));
+
+        $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
+        $idpMetadata = $idp->getConfig();
+        $spMetadata = $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote');
+
+        $bindings = array(
+            \SAML2\Constants::BINDING_HTTP_REDIRECT,
+            \SAML2\Constants::BINDING_HTTP_POST
+        );
+        $dst = $spMetadata->getEndpointPrioritizedByBinding('SingleLogoutService', $bindings);
+
+        if ($dst['Binding'] === \SAML2\Constants::BINDING_HTTP_POST) {
+            $params = array('association' => $association['id'], 'idp' => $idp->getId());
+            if ($relayState !== null) {
+                $params['RelayState'] = $relayState;
+            }
+            return SimpleSAML\Module::getModuleURL('core/idp/logout-iframe-post.php', $params);
+        }
+
+        $lr = self::buildLogoutRequest($idpMetadata, $spMetadata, $association, $relayState);
+        $lr->setDestination($dst['Location']);
+
+        $binding = new \SAML2\HTTPRedirect();
+        return $binding->getRedirectURL($lr);
+    }
+
+
+    /**
+     * Retrieve the metadata for the given SP association.
+     *
+     * @param SimpleSAML_IdP $idp The IdP the association belongs to.
+     * @param array          $association The SP association.
+     *
+     * @return SimpleSAML_Configuration  Configuration object for the SP metadata.
+     */
+    public static function getAssociationConfig(SimpleSAML_IdP $idp, array $association)
+    {
+        $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
+        try {
+            return $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote');
+        } catch (Exception $e) {
+            return SimpleSAML_Configuration::loadFromArray(array(), 'Unknown SAML 2 entity.');
+        }
+    }
+
+
+    /**
+     * Calculate the NameID value that should be used.
+     *
+     * @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
+     * @param SimpleSAML_Configuration $dstMetadata The metadata of the SP.
+     * @param array                    &$state The authentication state of the user.
+     *
+     * @return string  The NameID value.
+     */
+    private static function generateNameIdValue(
+        SimpleSAML_Configuration $idpMetadata,
+        SimpleSAML_Configuration $spMetadata,
+        array &$state
+    ) {
+
+        $attribute = $spMetadata->getString('simplesaml.nameidattribute', null);
+        if ($attribute === null) {
+            $attribute = $idpMetadata->getString('simplesaml.nameidattribute', null);
+            if ($attribute === null) {
+                if (!isset($state['UserID'])) {
+                    SimpleSAML\Logger::error('Unable to generate NameID. Check the userid.attribute option.');
+                }
+                $attributeValue = $state['UserID'];
+                $idpEntityId = $idpMetadata->getString('entityid');
+                $spEntityId = $spMetadata->getString('entityid');
+
+                $secretSalt = SimpleSAML\Utils\Config::getSecretSalt();
+
+                $uidData = 'uidhashbase'.$secretSalt;
+                $uidData .= strlen($idpEntityId).':'.$idpEntityId;
+                $uidData .= strlen($spEntityId).':'.$spEntityId;
+                $uidData .= strlen($attributeValue).':'.$attributeValue;
+                $uidData .= $secretSalt;
+
+                return hash('sha1', $uidData);
+            }
+        }
+
+        $attributes = $state['Attributes'];
+        if (!array_key_exists($attribute, $attributes)) {
+            SimpleSAML\Logger::error('Unable to add NameID: Missing '.var_export($attribute, true).
+                ' in the attributes of the user.');
+            return null;
+        }
+
+        return $attributes[$attribute][0];
+    }
+
+
+    /**
+     * Helper function for encoding attributes.
+     *
+     * @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
+     * @param SimpleSAML_Configuration $spMetadata The metadata of the SP.
+     * @param array $attributes The attributes of the user.
+     *
+     * @return array  The encoded attributes.
+     *
+     * @throws SimpleSAML_Error_Exception In case an unsupported encoding is specified by configuration.
+     */
+    private static function encodeAttributes(
+        SimpleSAML_Configuration $idpMetadata,
+        SimpleSAML_Configuration $spMetadata,
+        array $attributes
+    ) {
+
+        $base64Attributes = $spMetadata->getBoolean('base64attributes', null);
+        if ($base64Attributes === null) {
+            $base64Attributes = $idpMetadata->getBoolean('base64attributes', false);
+        }
+
+        if ($base64Attributes) {
+            $defaultEncoding = 'base64';
+        } else {
+            $defaultEncoding = 'string';
+        }
+
+        $srcEncodings = $idpMetadata->getArray('attributeencodings', array());
+        $dstEncodings = $spMetadata->getArray('attributeencodings', array());
+
+        /*
+         * Merge the two encoding arrays. Encodings specified in the target metadata
+         * takes precedence over the source metadata.
+         */
+        $encodings = array_merge($srcEncodings, $dstEncodings);
+
+        $ret = array();
+        foreach ($attributes as $name => $values) {
+            $ret[$name] = array();
+            if (array_key_exists($name, $encodings)) {
+                $encoding = $encodings[$name];
+            } else {
+                $encoding = $defaultEncoding;
+            }
+
+            foreach ($values as $value) {
                 // allow null values
                 if ($value === null) {
                     $ret[$name][] = $value;
                     continue;
                 }
 
-				$attrval = $value;
-				if ($value instanceof DOMNodeList) {
-					$attrval = new \SAML2\XML\saml\AttributeValue($value->item(0)->parentNode);
-				}
-
-				switch ($encoding) {
-				case 'string':
-					$value = (string)$attrval;
-					break;
-				case 'base64':
-					$value = base64_encode((string)$attrval);
-					break;
-				case 'raw':
-					if (is_string($value)) {
-						$doc = \SAML2\DOMDocumentFactory::fromString('<root>' . $value . '</root>');
-						$value = $doc->firstChild->childNodes;
-					}
-					assert('$value instanceof DOMNodeList || $value instanceof \SAML2\XML\saml\NameID');
-					break;
-				default:
-					throw new SimpleSAML_Error_Exception('Invalid encoding for attribute ' .
-						var_export($name, TRUE) . ': ' . var_export($encoding, TRUE));
-				}
-				$ret[$name][] = $value;
-			}
-		}
-
-		return $ret;
-	}
-
-
-	/**
-	 * Determine which NameFormat we should use for attributes.
-	 *
-	 * @param SimpleSAML_Configuration $idpMetadata  The metadata of the IdP.
-	 * @param SimpleSAML_Configuration $spMetadata  The metadata of the SP.
-	 * @return string  The NameFormat.
-	 */
-	private static function getAttributeNameFormat(SimpleSAML_Configuration $idpMetadata,
-		SimpleSAML_Configuration $spMetadata) {
-
-		/* Try SP metadata first. */
-		$attributeNameFormat = $spMetadata->getString('attributes.NameFormat', NULL);
-		if ($attributeNameFormat !== NULL) {
-			return $attributeNameFormat;
-		}
-		$attributeNameFormat = $spMetadata->getString('AttributeNameFormat', NULL);
-		if ($attributeNameFormat !== NULL) {
-			return $attributeNameFormat;
-		}
-
-		/* Look in IdP metadata. */
-		$attributeNameFormat = $idpMetadata->getString('attributes.NameFormat', NULL);
-		if ($attributeNameFormat !== NULL) {
-			return $attributeNameFormat;
-		}
-		$attributeNameFormat = $idpMetadata->getString('AttributeNameFormat', NULL);
-		if ($attributeNameFormat !== NULL) {
-			return $attributeNameFormat;
-		}
-
-		/* Default. */
-		return 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic';
-	}
-
-
-	/**
-	 * Build an assertion based on information in the metadata.
-	 *
-	 * @param SimpleSAML_Configuration $idpMetadata  The metadata of the IdP.
-	 * @param SimpleSAML_Configuration $spMetadata  The metadata of the SP.
-	 * @param array &$state  The state array with information about the request.
-	 * @return \SAML2\Assertion  The assertion.
-	 */
-	private static function buildAssertion(SimpleSAML_Configuration $idpMetadata,
-		SimpleSAML_Configuration $spMetadata, array &$state) {
-		assert('isset($state["Attributes"])');
-		assert('isset($state["saml:ConsumerURL"])');
-
-		$now = time();
-
-		$signAssertion = $spMetadata->getBoolean('saml20.sign.assertion', NULL);
-		if ($signAssertion === NULL) {
-			$signAssertion = $idpMetadata->getBoolean('saml20.sign.assertion', TRUE);
-		}
-
-		$config = SimpleSAML_Configuration::getInstance();
-
-		$a = new \SAML2\Assertion();
-		if ($signAssertion) {
-			sspmod_saml_Message::addSign($idpMetadata, $spMetadata, $a);
-		}
-
-		$a->setIssuer($idpMetadata->getString('entityid'));
-		$a->setValidAudiences(array($spMetadata->getString('entityid')));
-
-		$a->setNotBefore($now - 30);
-
-		$assertionLifetime = $spMetadata->getInteger('assertion.lifetime', NULL);
-		if ($assertionLifetime === NULL) {
-			$assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300);
-		}
-		$a->setNotOnOrAfter($now + $assertionLifetime);
-
-		if (isset($state['saml:AuthnContextClassRef'])) {
-			$a->setAuthnContext($state['saml:AuthnContextClassRef']);
-		} else {
-			$a->setAuthnContext(\SAML2\Constants::AC_PASSWORD);
-		}
-
-		$sessionStart = $now;
-		if (isset($state['AuthnInstant'])) {
-			$a->setAuthnInstant($state['AuthnInstant']);
-			$sessionStart = $state['AuthnInstant'];
-		}
-
-		$sessionLifetime = $config->getInteger('session.duration', 8*60*60);
-		$a->setSessionNotOnOrAfter($sessionStart + $sessionLifetime);
-
-		$a->setSessionIndex(SimpleSAML\Utils\Random::generateID());
-
-		$sc = new \SAML2\XML\saml\SubjectConfirmation();
-		$sc->SubjectConfirmationData = new \SAML2\XML\saml\SubjectConfirmationData();
-		$sc->SubjectConfirmationData->NotOnOrAfter = $now + $assertionLifetime;
-		$sc->SubjectConfirmationData->Recipient = $state['saml:ConsumerURL'];
-		$sc->SubjectConfirmationData->InResponseTo = $state['saml:RequestId'];
-
-		/* ProtcolBinding of SP's <AuthnRequest> overwrites IdP hosted metadata configuration. */
-		$hokAssertion = NULL;
-		if ($state['saml:Binding'] === \SAML2\Constants::BINDING_HOK_SSO) {
-		    $hokAssertion = TRUE;
-		}
-		if ($hokAssertion === NULL) {
-			$hokAssertion = $idpMetadata->getBoolean('saml20.hok.assertion', FALSE);
-		}
-
-		if ($hokAssertion) {
-			/* Holder-of-Key */
-			$sc->Method = \SAML2\Constants::CM_HOK;
-			if (\SimpleSAML\Utils\HTTP::isHTTPS()) {
-				if (isset($_SERVER['SSL_CLIENT_CERT']) && !empty($_SERVER['SSL_CLIENT_CERT'])) {
-					/* Extract certificate data (if this is a certificate). */
-					$clientCert = $_SERVER['SSL_CLIENT_CERT'];
-					$pattern = '/^-----BEGIN CERTIFICATE-----([^-]*)^-----END CERTIFICATE-----/m';
-					if (preg_match($pattern, $clientCert, $matches)) {
-						/* We have a client certificate from the browser which we add to the HoK assertion. */
-						$x509Certificate = new \SAML2\XML\ds\X509Certificate();
-						$x509Certificate->certificate = str_replace(array("\r", "\n", " "), '', $matches[1]);
-
-						$x509Data = new \SAML2\XML\ds\X509Data();
-						$x509Data->data[] = $x509Certificate;
-
-						$keyInfo = new \SAML2\XML\ds\KeyInfo();
-						$keyInfo->info[] = $x509Data;
-
-						$sc->SubjectConfirmationData->info[] = $keyInfo;
-					} else throw new SimpleSAML_Error_Exception('Error creating HoK assertion: No valid client certificate provided during TLS handshake with IdP');
-				} else throw new SimpleSAML_Error_Exception('Error creating HoK assertion: No client certificate provided during TLS handshake with IdP');
-			} else throw new SimpleSAML_Error_Exception('Error creating HoK assertion: No HTTPS connection to IdP, but required for Holder-of-Key SSO');
-		} else {
-			/* Bearer */
-			$sc->Method = \SAML2\Constants::CM_BEARER;
-		}
-		$a->setSubjectConfirmation(array($sc));
-
-		/* Add attributes. */
-
-		if ($spMetadata->getBoolean('simplesaml.attributes', TRUE)) {
-			$attributeNameFormat = self::getAttributeNameFormat($idpMetadata, $spMetadata);
-			$a->setAttributeNameFormat($attributeNameFormat);
-			$attributes = self::encodeAttributes($idpMetadata, $spMetadata, $state['Attributes']);
-			$a->setAttributes($attributes);
-		}
-
-
-		/* Generate the NameID for the assertion. */
-
-		if (isset($state['saml:NameIDFormat'])) {
-			$nameIdFormat = $state['saml:NameIDFormat'];
-		} else {
-			$nameIdFormat = NULL;
-		}
-
-		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 = $spMetadata->getString('NameIDFormat', NULL);
-			if ($nameIdFormat === NULL) {
-				$nameIdFormat = $idpMetadata->getString('NameIDFormat', \SAML2\Constants::NAMEID_TRANSIENT);
-			}
-		}
-
-		if (isset($state['saml:NameID'][$nameIdFormat])) {
-			$nameId = $state['saml:NameID'][$nameIdFormat];
-			$nameId['Format'] = $nameIdFormat;
-		} else {
-			$spNameQualifier = $spMetadata->getString('SPNameQualifier', NULL);
-			if ($spNameQualifier === NULL) {
-				$spNameQualifier = $spMetadata->getString('entityid');
-			}
-
-			if ($nameIdFormat === \SAML2\Constants::NAMEID_TRANSIENT) {
-				/* generate a random id */
-				$nameIdValue = SimpleSAML\Utils\Random::generateID();
-			} else {
-				/* this code will end up generating either a fixed assigned id (via nameid.attribute)
-				   or random id if not assigned/configured */
-				$nameIdValue = self::generateNameIdValue($idpMetadata, $spMetadata, $state);
-				if ($nameIdValue === NULL) {
-					SimpleSAML\Logger::warning('Falling back to transient NameID.');
-					$nameIdFormat = \SAML2\Constants::NAMEID_TRANSIENT;
-					$nameIdValue = SimpleSAML\Utils\Random::generateID();
-				}
-			}
-
-			$nameId = array(
-				'Format' => $nameIdFormat,
-				'Value' => $nameIdValue,
-				'SPNameQualifier' => $spNameQualifier,
-			);
-		}
-
-		$state['saml:idp:NameID'] = $nameId;
-
-		$a->setNameId($nameId);
-
-		$encryptNameId = $spMetadata->getBoolean('nameid.encryption', NULL);
-		if ($encryptNameId === NULL) {
-			$encryptNameId = $idpMetadata->getBoolean('nameid.encryption', FALSE);
-		}
-		if ($encryptNameId) {
-			$a->encryptNameId(sspmod_saml_Message::getEncryptionKey($spMetadata));
-		}
-
-		return $a;
-	}
-
-
-	/**
-	 * Encrypt an assertion.
-	 *
-	 * This function takes in a \SAML2\Assertion and encrypts it if encryption of
-	 * assertions are enabled in the metadata.
-	 *
-	 * @param SimpleSAML_Configuration $idpMetadata  The metadata of the IdP.
-	 * @param SimpleSAML_Configuration $spMetadata  The metadata of the SP.
-	 * @param \SAML2\Assertion $assertion  The assertion we are encrypting.
-	 * @return \SAML2\Assertion|\SAML2\EncryptedAssertion  The assertion.
-	 */
-	private static function encryptAssertion(SimpleSAML_Configuration $idpMetadata,
-		SimpleSAML_Configuration $spMetadata, \SAML2\Assertion $assertion) {
-
-		$encryptAssertion = $spMetadata->getBoolean('assertion.encryption', NULL);
-		if ($encryptAssertion === NULL) {
-			$encryptAssertion = $idpMetadata->getBoolean('assertion.encryption', FALSE);
-		}
-		if (!$encryptAssertion) {
-			/* We are _not_ encrypting this assertion, and are therefore done. */
-			return $assertion;
-		}
-
-
-		$sharedKey = $spMetadata->getString('sharedkey', NULL);
-		if ($sharedKey !== NULL) {
-			$key = new XMLSecurityKey(XMLSecurityKey::AES128_CBC);
-			$key->loadKey($sharedKey);
-		} else {
-			$keys = $spMetadata->getPublicKeys('encryption', TRUE);
-			$key = $keys[0];
-			switch ($key['type']) {
-			case 'X509Certificate':
-				$pemKey = "-----BEGIN CERTIFICATE-----\n" .
-					chunk_split($key['X509Certificate'], 64) .
-					"-----END CERTIFICATE-----\n";
-				break;
-			default:
-				throw new SimpleSAML_Error_Exception('Unsupported encryption key type: ' . $key['type']);
-			}
-
-			/* Extract the public key from the certificate for encryption. */
-			$key = new XMLSecurityKey(XMLSecurityKey::RSA_OAEP_MGF1P, array('type'=>'public'));
-			$key->loadKey($pemKey);
-		}
-
-		$ea = new \SAML2\EncryptedAssertion();
-		$ea->setAssertion($assertion, $key);
-		return $ea;
-	}
-
-
-	/**
-	 * Build a logout request based on information in the metadata.
-	 *
-	 * @param SimpleSAML_Configuration idpMetadata  The metadata of the IdP.
-	 * @param SimpleSAML_Configuration spMetadata  The metadata of the SP.
-	 * @param array $association  The SP association.
-	 * @param string|NULL $relayState  An id that should be carried across the logout.
-	 */
-	private static function buildLogoutRequest(SimpleSAML_Configuration $idpMetadata,
-		SimpleSAML_Configuration $spMetadata, array $association, $relayState) {
-
-		$lr = sspmod_saml_Message::buildLogoutRequest($idpMetadata, $spMetadata);
-		$lr->setRelayState($relayState);
-		$lr->setSessionIndex($association['saml:SessionIndex']);
-		$lr->setNameId($association['saml:NameID']);
-
-		$assertionLifetime = $spMetadata->getInteger('assertion.lifetime', NULL);
-		if ($assertionLifetime === NULL) {
-			$assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300);
-		}
-		$lr->setNotOnOrAfter(time() + $assertionLifetime);
-
-		$encryptNameId = $spMetadata->getBoolean('nameid.encryption', NULL);
-		if ($encryptNameId === NULL) {
-			$encryptNameId = $idpMetadata->getBoolean('nameid.encryption', FALSE);
-		}
-		if ($encryptNameId) {
-			$lr->encryptNameId(sspmod_saml_Message::getEncryptionKey($spMetadata));
-		}
-
-		return $lr;
-	}
-
-
-	/**
-	 * Build a authentication response based on information in the metadata.
-	 *
-	 * @param SimpleSAML_Configuration $idpMetadata  The metadata of the IdP.
-	 * @param SimpleSAML_Configuration $spMetadata  The metadata of the SP.
-	 * @param string $consumerURL  The Destination URL of the response.
-	 */
-	private static function buildResponse(SimpleSAML_Configuration $idpMetadata,
-		SimpleSAML_Configuration $spMetadata, $consumerURL) {
-
-		$signResponse = $spMetadata->getBoolean('saml20.sign.response', NULL);
-		if ($signResponse === NULL) {
-			$signResponse = $idpMetadata->getBoolean('saml20.sign.response', TRUE);
-		}
-
-		$r = new \SAML2\Response();
-
-		$r->setIssuer($idpMetadata->getString('entityid'));
-		$r->setDestination($consumerURL);
-
-		if ($signResponse) {
-			sspmod_saml_Message::addSign($idpMetadata, $spMetadata, $r);
-		}
-
-		return $r;
-	}
-
-}
\ No newline at end of file
+                $attrval = $value;
+                if ($value instanceof DOMNodeList) {
+                    $attrval = new \SAML2\XML\saml\AttributeValue($value->item(0)->parentNode);
+                }
+
+                switch ($encoding) {
+                    case 'string':
+                        $value = (string) $attrval;
+                        break;
+                    case 'base64':
+                        $value = base64_encode((string) $attrval);
+                        break;
+                    case 'raw':
+                        if (is_string($value)) {
+                            $doc = \SAML2\DOMDocumentFactory::fromString('<root>'.$value.'</root>');
+                            $value = $doc->firstChild->childNodes;
+                        }
+                        assert('$value instanceof DOMNodeList || $value instanceof \SAML2\XML\saml\NameID');
+                        break;
+                    default:
+                        throw new SimpleSAML_Error_Exception('Invalid encoding for attribute '.
+                            var_export($name, true).': '.var_export($encoding, true));
+                }
+                $ret[$name][] = $value;
+            }
+        }
+
+        return $ret;
+    }
+
+
+    /**
+     * Determine which NameFormat we should use for attributes.
+     *
+     * @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
+     * @param SimpleSAML_Configuration $spMetadata The metadata of the SP.
+     *
+     * @return string  The NameFormat.
+     */
+    private static function getAttributeNameFormat(
+        SimpleSAML_Configuration $idpMetadata,
+        SimpleSAML_Configuration $spMetadata
+    ) {
+
+        // try SP metadata first
+        $attributeNameFormat = $spMetadata->getString('attributes.NameFormat', null);
+        if ($attributeNameFormat !== null) {
+            return $attributeNameFormat;
+        }
+        $attributeNameFormat = $spMetadata->getString('AttributeNameFormat', null);
+        if ($attributeNameFormat !== null) {
+            return $attributeNameFormat;
+        }
+
+        // look in IdP metadata
+        $attributeNameFormat = $idpMetadata->getString('attributes.NameFormat', null);
+        if ($attributeNameFormat !== null) {
+            return $attributeNameFormat;
+        }
+        $attributeNameFormat = $idpMetadata->getString('AttributeNameFormat', null);
+        if ($attributeNameFormat !== null) {
+            return $attributeNameFormat;
+        }
+
+        // default
+        return 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic';
+    }
+
+
+    /**
+     * Build an assertion based on information in the metadata.
+     *
+     * @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
+     * @param SimpleSAML_Configuration $spMetadata The metadata of the SP.
+     * @param array &$state The state array with information about the request.
+     *
+     * @return \SAML2\Assertion  The assertion.
+     *
+     * @throws SimpleSAML_Error_Exception In case an error occurs when creating a holder-of-key assertion.
+     */
+    private static function buildAssertion(
+        SimpleSAML_Configuration $idpMetadata,
+        SimpleSAML_Configuration $spMetadata,
+        array &$state
+    ) {
+        assert('isset($state["Attributes"])');
+        assert('isset($state["saml:ConsumerURL"])');
+
+        $now = time();
+
+        $signAssertion = $spMetadata->getBoolean('saml20.sign.assertion', null);
+        if ($signAssertion === null) {
+            $signAssertion = $idpMetadata->getBoolean('saml20.sign.assertion', true);
+        }
+
+        $config = SimpleSAML_Configuration::getInstance();
+
+        $a = new \SAML2\Assertion();
+        if ($signAssertion) {
+            sspmod_saml_Message::addSign($idpMetadata, $spMetadata, $a);
+        }
+
+        $a->setIssuer($idpMetadata->getString('entityid'));
+        $a->setValidAudiences(array($spMetadata->getString('entityid')));
+
+        $a->setNotBefore($now - 30);
+
+        $assertionLifetime = $spMetadata->getInteger('assertion.lifetime', null);
+        if ($assertionLifetime === null) {
+            $assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300);
+        }
+        $a->setNotOnOrAfter($now + $assertionLifetime);
+
+        if (isset($state['saml:AuthnContextClassRef'])) {
+            $a->setAuthnContext($state['saml:AuthnContextClassRef']);
+        } else {
+            $a->setAuthnContext(\SAML2\Constants::AC_PASSWORD);
+        }
+
+        $sessionStart = $now;
+        if (isset($state['AuthnInstant'])) {
+            $a->setAuthnInstant($state['AuthnInstant']);
+            $sessionStart = $state['AuthnInstant'];
+        }
+
+        $sessionLifetime = $config->getInteger('session.duration', 8 * 60 * 60);
+        $a->setSessionNotOnOrAfter($sessionStart + $sessionLifetime);
+
+        $a->setSessionIndex(SimpleSAML\Utils\Random::generateID());
+
+        $sc = new \SAML2\XML\saml\SubjectConfirmation();
+        $sc->SubjectConfirmationData = new \SAML2\XML\saml\SubjectConfirmationData();
+        $sc->SubjectConfirmationData->NotOnOrAfter = $now + $assertionLifetime;
+        $sc->SubjectConfirmationData->Recipient = $state['saml:ConsumerURL'];
+        $sc->SubjectConfirmationData->InResponseTo = $state['saml:RequestId'];
+
+        // ProtcolBinding of SP's <AuthnRequest> overwrites IdP hosted metadata configuration
+        $hokAssertion = null;
+        if ($state['saml:Binding'] === \SAML2\Constants::BINDING_HOK_SSO) {
+            $hokAssertion = true;
+        }
+        if ($hokAssertion === null) {
+            $hokAssertion = $idpMetadata->getBoolean('saml20.hok.assertion', false);
+        }
+
+        if ($hokAssertion) {
+            // Holder-of-Key
+            $sc->Method = \SAML2\Constants::CM_HOK;
+            if (\SimpleSAML\Utils\HTTP::isHTTPS()) {
+                if (isset($_SERVER['SSL_CLIENT_CERT']) && !empty($_SERVER['SSL_CLIENT_CERT'])) {
+                    // extract certificate data (if this is a certificate)
+                    $clientCert = $_SERVER['SSL_CLIENT_CERT'];
+                    $pattern = '/^-----BEGIN CERTIFICATE-----([^-]*)^-----END CERTIFICATE-----/m';
+                    if (preg_match($pattern, $clientCert, $matches)) {
+                        // we have a client certificate from the browser which we add to the HoK assertion
+                        $x509Certificate = new \SAML2\XML\ds\X509Certificate();
+                        $x509Certificate->certificate = str_replace(array("\r", "\n", " "), '', $matches[1]);
+
+                        $x509Data = new \SAML2\XML\ds\X509Data();
+                        $x509Data->data[] = $x509Certificate;
+
+                        $keyInfo = new \SAML2\XML\ds\KeyInfo();
+                        $keyInfo->info[] = $x509Data;
+
+                        $sc->SubjectConfirmationData->info[] = $keyInfo;
+                    } else {
+                        throw new SimpleSAML_Error_Exception(
+                            'Error creating HoK assertion: No valid client certificate provided during TLS handshake '.
+                            'with IdP'
+                        );
+                    }
+                } else {
+                    throw new SimpleSAML_Error_Exception(
+                        'Error creating HoK assertion: No client certificate provided during TLS handshake with IdP'
+                    );
+                }
+            } else {
+                throw new SimpleSAML_Error_Exception(
+                    'Error creating HoK assertion: No HTTPS connection to IdP, but required for Holder-of-Key SSO'
+                );
+            }
+        } else {
+            // Bearer
+            $sc->Method = \SAML2\Constants::CM_BEARER;
+        }
+        $a->setSubjectConfirmation(array($sc));
+
+        // add attributes
+        if ($spMetadata->getBoolean('simplesaml.attributes', true)) {
+            $attributeNameFormat = self::getAttributeNameFormat($idpMetadata, $spMetadata);
+            $a->setAttributeNameFormat($attributeNameFormat);
+            $attributes = self::encodeAttributes($idpMetadata, $spMetadata, $state['Attributes']);
+            $a->setAttributes($attributes);
+        }
+
+        // generate the NameID for the assertion
+        if (isset($state['saml:NameIDFormat'])) {
+            $nameIdFormat = $state['saml:NameIDFormat'];
+        } else {
+            $nameIdFormat = null;
+        }
+
+        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 = $spMetadata->getString('NameIDFormat', null);
+            if ($nameIdFormat === null) {
+                $nameIdFormat = $idpMetadata->getString('NameIDFormat', \SAML2\Constants::NAMEID_TRANSIENT);
+            }
+        }
+
+        if (isset($state['saml:NameID'][$nameIdFormat])) {
+            $nameId = $state['saml:NameID'][$nameIdFormat];
+            $nameId['Format'] = $nameIdFormat;
+        } else {
+            $spNameQualifier = $spMetadata->getString('SPNameQualifier', null);
+            if ($spNameQualifier === null) {
+                $spNameQualifier = $spMetadata->getString('entityid');
+            }
+
+            if ($nameIdFormat === \SAML2\Constants::NAMEID_TRANSIENT) {
+                // generate a random id
+                $nameIdValue = SimpleSAML\Utils\Random::generateID();
+            } else {
+                /* this code will end up generating either a fixed assigned id (via nameid.attribute)
+                   or random id if not assigned/configured */
+                $nameIdValue = self::generateNameIdValue($idpMetadata, $spMetadata, $state);
+                if ($nameIdValue === null) {
+                    SimpleSAML\Logger::warning('Falling back to transient NameID.');
+                    $nameIdFormat = \SAML2\Constants::NAMEID_TRANSIENT;
+                    $nameIdValue = SimpleSAML\Utils\Random::generateID();
+                }
+            }
+
+            $nameId = array(
+                'Format'          => $nameIdFormat,
+                'Value'           => $nameIdValue,
+                'SPNameQualifier' => $spNameQualifier,
+            );
+        }
+
+        $state['saml:idp:NameID'] = $nameId;
+
+        $a->setNameId($nameId);
+
+        $encryptNameId = $spMetadata->getBoolean('nameid.encryption', null);
+        if ($encryptNameId === null) {
+            $encryptNameId = $idpMetadata->getBoolean('nameid.encryption', false);
+        }
+        if ($encryptNameId) {
+            $a->encryptNameId(sspmod_saml_Message::getEncryptionKey($spMetadata));
+        }
+
+        return $a;
+    }
+
+
+    /**
+     * Encrypt an assertion.
+     *
+     * This function takes in a \SAML2\Assertion and encrypts it if encryption of
+     * assertions are enabled in the metadata.
+     *
+     * @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
+     * @param SimpleSAML_Configuration $spMetadata The metadata of the SP.
+     * @param \SAML2\Assertion $assertion The assertion we are encrypting.
+     *
+     * @return \SAML2\Assertion|\SAML2\EncryptedAssertion  The assertion.
+     *
+     * @throws SimpleSAML_Error_Exception In case the encryption key type is not supported.
+     */
+    private static function encryptAssertion(
+        SimpleSAML_Configuration $idpMetadata,
+        SimpleSAML_Configuration $spMetadata,
+        \SAML2\Assertion $assertion
+    ) {
+
+        $encryptAssertion = $spMetadata->getBoolean('assertion.encryption', null);
+        if ($encryptAssertion === null) {
+            $encryptAssertion = $idpMetadata->getBoolean('assertion.encryption', false);
+        }
+        if (!$encryptAssertion) {
+            // we are _not_ encrypting this assertion, and are therefore done
+            return $assertion;
+        }
+
+
+        $sharedKey = $spMetadata->getString('sharedkey', null);
+        if ($sharedKey !== null) {
+            $key = new XMLSecurityKey(XMLSecurityKey::AES128_CBC);
+            $key->loadKey($sharedKey);
+        } else {
+            $keys = $spMetadata->getPublicKeys('encryption', true);
+            $key = $keys[0];
+            switch ($key['type']) {
+                case 'X509Certificate':
+                    $pemKey = "-----BEGIN CERTIFICATE-----\n".
+                        chunk_split($key['X509Certificate'], 64).
+                        "-----END CERTIFICATE-----\n";
+                    break;
+                default:
+                    throw new SimpleSAML_Error_Exception('Unsupported encryption key type: '.$key['type']);
+            }
+
+            // extract the public key from the certificate for encryption
+            $key = new XMLSecurityKey(XMLSecurityKey::RSA_OAEP_MGF1P, array('type' => 'public'));
+            $key->loadKey($pemKey);
+        }
+
+        $ea = new \SAML2\EncryptedAssertion();
+        $ea->setAssertion($assertion, $key);
+        return $ea;
+    }
+
+
+    /**
+     * Build a logout request based on information in the metadata.
+     *
+     * @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
+     * @param SimpleSAML_Configuration $spMetadata The metadata of the SP.
+     * @param array $association The SP association.
+     * @param string|null $relayState An id that should be carried across the logout.
+     *
+     * @return \SAML2\LogoutResponse The corresponding SAML2 logout response.
+     */
+    private static function buildLogoutRequest(
+        SimpleSAML_Configuration $idpMetadata,
+        SimpleSAML_Configuration $spMetadata,
+        array $association,
+        $relayState
+    ) {
+
+        $lr = sspmod_saml_Message::buildLogoutRequest($idpMetadata, $spMetadata);
+        $lr->setRelayState($relayState);
+        $lr->setSessionIndex($association['saml:SessionIndex']);
+        $lr->setNameId($association['saml:NameID']);
+
+        $assertionLifetime = $spMetadata->getInteger('assertion.lifetime', null);
+        if ($assertionLifetime === null) {
+            $assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300);
+        }
+        $lr->setNotOnOrAfter(time() + $assertionLifetime);
+
+        $encryptNameId = $spMetadata->getBoolean('nameid.encryption', null);
+        if ($encryptNameId === null) {
+            $encryptNameId = $idpMetadata->getBoolean('nameid.encryption', false);
+        }
+        if ($encryptNameId) {
+            $lr->encryptNameId(sspmod_saml_Message::getEncryptionKey($spMetadata));
+        }
+
+        return $lr;
+    }
+
+
+    /**
+     * Build a authentication response based on information in the metadata.
+     *
+     * @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
+     * @param SimpleSAML_Configuration $spMetadata The metadata of the SP.
+     * @param string                   $consumerURL The Destination URL of the response.
+     *
+     * @return \SAML2\Response The SAML2 response corresponding to the given data.
+     */
+    private static function buildResponse(
+        SimpleSAML_Configuration $idpMetadata,
+        SimpleSAML_Configuration $spMetadata,
+        $consumerURL
+    ) {
+
+        $signResponse = $spMetadata->getBoolean('saml20.sign.response', null);
+        if ($signResponse === null) {
+            $signResponse = $idpMetadata->getBoolean('saml20.sign.response', true);
+        }
+
+        $r = new \SAML2\Response();
+
+        $r->setIssuer($idpMetadata->getString('entityid'));
+        $r->setDestination($consumerURL);
+
+        if ($signResponse) {
+            sspmod_saml_Message::addSign($idpMetadata, $spMetadata, $r);
+        }
+
+        return $r;
+    }
+}