diff --git a/modules/saml/lib/Auth/Source/SP.php b/modules/saml/lib/Auth/Source/SP.php
index 100776bf43daf54dce15435b238a671c6ebc62eb..6d325025483f3bd798484467ca7218f4e4e71cd4 100644
--- a/modules/saml/lib/Auth/Source/SP.php
+++ b/modules/saml/lib/Auth/Source/SP.php
@@ -1,794 +1,800 @@
 <?php
 
-class sspmod_saml_Auth_Source_SP extends SimpleSAML_Auth_Source {
-
-	/**
-	 * The entity ID of this SP.
-	 *
-	 * @var string
-	 */
-	private $entityId;
-
-
-	/**
-	 * The metadata of this SP.
-	 *
-	 * @var SimpleSAML_Configuration.
-	 */
-	private $metadata;
-
-
-	/**
-	 * The IdP the user is allowed to log into.
-	 *
-	 * @var string|NULL  The IdP the user can log into, or NULL if the user can log into all IdPs.
-	 */
-	private $idp;
-
-
-	/**
-	 * URL to discovery service.
-	 *
-	 * @var string|NULL
-	 */
-	private $discoURL;
-
-
-	/**
-	 * Constructor for SAML SP authentication source.
-	 *
-	 * @param array $info  Information about this authentication source.
-	 * @param array $config  Configuration.
-	 */
-	public function __construct($info, $config) {
-		assert(is_array($info));
-		assert(is_array($config));
-
-		// Call the parent constructor first, as required by the interface
-		parent::__construct($info, $config);
-
-		if (!isset($config['entityID'])) {
-			$config['entityID'] = $this->getMetadataURL();
-		}
-
-		/* For compatibility with code that assumes that $metadata->getString('entityid') gives the entity id. */
-		$config['entityid'] = $config['entityID'];
-
-		$this->metadata = SimpleSAML_Configuration::loadFromArray($config, 'authsources[' . var_export($this->authId, TRUE) . ']');
-		$this->entityId = $this->metadata->getString('entityID');
-		$this->idp = $this->metadata->getString('idp', NULL);
-		$this->discoURL = $this->metadata->getString('discoURL', NULL);
-		
-		if (empty($this->discoURL) && SimpleSAML\Module::isModuleEnabled('discojuice')) {
-			$this->discoURL = SimpleSAML\Module::getModuleURL('discojuice/central.php');
-		}
-	}
-
-
-	/**
-	 * Retrieve the URL to the metadata of this SP.
-	 *
-	 * @return string  The metadata URL.
-	 */
-	public function getMetadataURL() {
-
-		return SimpleSAML\Module::getModuleURL('saml/sp/metadata.php/' . urlencode($this->authId));
-	}
-
-
-	/**
-	 * Retrieve the entity id of this SP.
-	 *
-	 * @return string  The entity id of this SP.
-	 */
-	public function getEntityId() {
-
-		return $this->entityId;
-	}
-
-
-	/**
-	 * Retrieve the metadata of this SP.
-	 *
-	 * @return SimpleSAML_Configuration  The metadata of this SP.
-	 */
-	public function getMetadata() {
-
-		return $this->metadata;
-
-	}
-
-
-	/**
-	 * Retrieve the metadata of an IdP.
-	 *
-	 * @param string $entityId  The entity id of the IdP.
-	 * @return SimpleSAML_Configuration  The metadata of the IdP.
-	 */
-	public function getIdPMetadata($entityId) {
-		assert(is_string($entityId));
-
-		if ($this->idp !== NULL && $this->idp !== $entityId) {
-			throw new SimpleSAML_Error_Exception('Cannot retrieve metadata for IdP ' . var_export($entityId, TRUE) .
-				' because it isn\'t a valid IdP for this SP.');
-		}
-
-		$metadataHandler = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
-
-		// First, look in saml20-idp-remote.
-		try {
-			return $metadataHandler->getMetaDataConfig($entityId, 'saml20-idp-remote');
-		} catch (Exception $e) {
-			/* Metadata wasn't found. */
+class sspmod_saml_Auth_Source_SP extends SimpleSAML_Auth_Source
+{
+    /**
+     * The entity ID of this SP.
+     *
+     * @var string
+     */
+    private $entityId;
+
+    /**
+     * The metadata of this SP.
+     *
+     * @var SimpleSAML_Configuration.
+     */
+    private $metadata;
+
+    /**
+     * The IdP the user is allowed to log into.
+     *
+     * @var string|null  The IdP the user can log into, or null if the user can log into all IdPs.
+     */
+    private $idp;
+
+    /**
+     * URL to discovery service.
+     *
+     * @var string|null
+     */
+    private $discoURL;
+
+    /**
+     * Constructor for SAML SP authentication source.
+     *
+     * @param array $info  Information about this authentication source.
+     * @param array $config  Configuration.
+     */
+    public function __construct($info, $config)
+    {
+        assert(is_array($info));
+        assert(is_array($config));
+
+        // Call the parent constructor first, as required by the interface
+        parent::__construct($info, $config);
+
+        if (!isset($config['entityID'])) {
+            $config['entityID'] = $this->getMetadataURL();
+        }
+
+        /* For compatibility with code that assumes that $metadata->getString('entityid')
+         * gives the entity id. */
+        $config['entityid'] = $config['entityID'];
+
+        $this->metadata = SimpleSAML_Configuration::loadFromArray($config,
+            'authsources[' . var_export($this->authId, true) . ']');
+        $this->entityId = $this->metadata->getString('entityID');
+        $this->idp = $this->metadata->getString('idp', null);
+        $this->discoURL = $this->metadata->getString('discoURL', null);
+
+        if (empty($this->discoURL) && SimpleSAML\Module::isModuleEnabled('discojuice')) {
+            $this->discoURL = SimpleSAML\Module::getModuleURL('discojuice/central.php');
+        }
+    }
+
+    /**
+     * Retrieve the URL to the metadata of this SP.
+     *
+     * @return string  The metadata URL.
+     */
+    public function getMetadataURL()
+    {
+        return SimpleSAML\Module::getModuleURL('saml/sp/metadata.php/' . urlencode($this->authId));
+    }
+
+    /**
+     * Retrieve the entity id of this SP.
+     *
+     * @return string  The entity id of this SP.
+     */
+    public function getEntityId()
+    {
+        return $this->entityId;
+    }
+
+    /**
+     * Retrieve the metadata of this SP.
+     *
+     * @return SimpleSAML_Configuration  The metadata of this SP.
+     */
+    public function getMetadata()
+    {
+        return $this->metadata;
+    }
+
+    /**
+     * Retrieve the metadata of an IdP.
+     *
+     * @param string $entityId  The entity id of the IdP.
+     * @return SimpleSAML_Configuration  The metadata of the IdP.
+     */
+    public function getIdPMetadata($entityId)
+    {
+        assert(is_string($entityId));
+
+        if ($this->idp !== null && $this->idp !== $entityId) {
+            throw new SimpleSAML_Error_Exception('Cannot retrieve metadata for IdP ' .
+                var_export($entityId, true) .
+                ' because it isn\'t a valid IdP for this SP.');
+        }
+
+        $metadataHandler = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
+
+        // First, look in saml20-idp-remote.
+        try {
+            return $metadataHandler->getMetaDataConfig($entityId, 'saml20-idp-remote');
+        } catch (Exception $e) {
+            /* Metadata wasn't found. */
             SimpleSAML\Logger::debug('getIdpMetadata: ' . $e->getMessage());
-		}
+        }
 
-		/* Not found in saml20-idp-remote, look in shib13-idp-remote. */
-		try {
-			return $metadataHandler->getMetaDataConfig($entityId, 'shib13-idp-remote');
-		} catch (Exception $e) {
-			/* Metadata wasn't found. */
+        /* Not found in saml20-idp-remote, look in shib13-idp-remote. */
+        try {
+            return $metadataHandler->getMetaDataConfig($entityId, 'shib13-idp-remote');
+        } catch (Exception $e) {
+            /* Metadata wasn't found. */
             SimpleSAML\Logger::debug('getIdpMetadata: ' . $e->getMessage());
-		}
-
-		/* Not found. */
-		throw new SimpleSAML_Error_Exception('Could not find the metadata of an IdP with entity ID ' . var_export($entityId, TRUE));
-	}
-
-
-	/**
-	 * Send a SAML1 SSO request to an IdP.
-	 *
-	 * @param SimpleSAML_Configuration $idpMetadata  The metadata of the IdP.
-	 * @param array $state  The state array for the current authentication.
-	 */
-	private function startSSO1(SimpleSAML_Configuration $idpMetadata, array $state) {
-
-		$idpEntityId = $idpMetadata->getString('entityid');
-
-		$state['saml:idp'] = $idpEntityId;
-
-		$ar = new \SimpleSAML\XML\Shib13\AuthnRequest();
-		$ar->setIssuer($this->entityId);
-
-		$id = SimpleSAML_Auth_State::saveState($state, 'saml:sp:sso');
-		$ar->setRelayState($id);
-
-		$useArtifact = $idpMetadata->getBoolean('saml1.useartifact', NULL);
-		if ($useArtifact === NULL) {
-			$useArtifact = $this->metadata->getBoolean('saml1.useartifact', FALSE);
-		}
-
-		if ($useArtifact) {
-			$shire = SimpleSAML\Module::getModuleURL('saml/sp/saml1-acs.php/' . $this->authId . '/artifact');
-		} else {
-			$shire = SimpleSAML\Module::getModuleURL('saml/sp/saml1-acs.php/' . $this->authId);
-		}
-
-		$url = $ar->createRedirect($idpEntityId, $shire);
-
-		SimpleSAML\Logger::debug('Starting SAML 1 SSO to ' . var_export($idpEntityId, TRUE) .
-			' from ' . var_export($this->entityId, TRUE) . '.');
-		\SimpleSAML\Utils\HTTP::redirectTrustedURL($url);
-	}
-
-
-	/**
-	 * Send a SAML2 SSO request to an IdP.
-	 *
-	 * @param SimpleSAML_Configuration $idpMetadata  The metadata of the IdP.
-	 * @param array $state  The state array for the current authentication.
-	 */
-	private function startSSO2(SimpleSAML_Configuration $idpMetadata, array $state) {
-	
-		if (isset($state['saml:ProxyCount']) && $state['saml:ProxyCount'] < 0) {
-			SimpleSAML_Auth_State::throwException(
-				$state,
-				new \SimpleSAML\Module\saml\Error\ProxyCountExceeded(\SAML2\Constants::STATUS_RESPONDER)
-			);
-		}
-
-		$ar = sspmod_saml_Message::buildAuthnRequest($this->metadata, $idpMetadata);
-
-		$ar->setAssertionConsumerServiceURL(SimpleSAML\Module::getModuleURL('saml/sp/saml2-acs.php/' . $this->authId));
-
-		if (isset($state['SimpleSAML_Auth_Source.ReturnURL'])) {
-			$ar->setRelayState($state['SimpleSAML_Auth_Source.ReturnURL']);
-		}
-
-		if (isset($state['saml:AuthnContextClassRef'])) {
-			$accr = SimpleSAML\Utils\Arrays::arrayize($state['saml:AuthnContextClassRef']);
-			$comp = SAML2\Constants::COMPARISON_EXACT;
-			if (isset($state['saml:AuthnContextComparison']) && in_array($state['AuthnContextComparison'], array(
-						SAML2\Constants::COMPARISON_EXACT,
-						SAML2\Constants::COMPARISON_MINIMUM,
-						SAML2\Constants::COMPARISON_MAXIMUM,
-						SAML2\Constants::COMPARISON_BETTER,
-			), true)) {
-				$comp = $state['saml:AuthnContextComparison'];
-			}
-			$ar->setRequestedAuthnContext(array('AuthnContextClassRef' => $accr, 'Comparison' => $comp));
-		}
-
-		if (isset($state['ForceAuthn'])) {
-			$ar->setForceAuthn((bool)$state['ForceAuthn']);
-		}
-
-		if (isset($state['isPassive'])) {
-			$ar->setIsPassive((bool)$state['isPassive']);
-		}
-
-		if (isset($state['saml:NameID'])) {
-			if (!is_array($state['saml:NameID']) && !is_a($state['saml:NameID'], '\SAML2\XML\saml\NameID')) {
-				throw new SimpleSAML_Error_Exception('Invalid value of $state[\'saml:NameID\'].');
-			}
-			$ar->setNameId($state['saml:NameID']);
-		}
-
-		if (isset($state['saml:NameIDPolicy'])) {
-			if (is_string($state['saml:NameIDPolicy'])) {
-				$policy = array(
-					'Format' => (string)$state['saml:NameIDPolicy'],
-					'AllowCreate' => TRUE,
-				);
-			} elseif (is_array($state['saml:NameIDPolicy'])) {
-				$policy = $state['saml:NameIDPolicy'];
-			} else {
-				throw new SimpleSAML_Error_Exception('Invalid value of $state[\'saml:NameIDPolicy\'].');
-			}
-			$ar->setNameIdPolicy($policy);
-		}
-
-		if (isset($state['saml:IDPList'])) {
-			$IDPList = $state['saml:IDPList'];
-		} else {
-			$IDPList = array();
-		}
-		
-		$ar->setIDPList(array_unique(array_merge($this->metadata->getArray('IDPList', array()), 
-												$idpMetadata->getArray('IDPList', array()),
-												(array) $IDPList)));
-		
-		if (isset($state['saml:ProxyCount']) && $state['saml:ProxyCount'] !== null) {
-			$ar->setProxyCount($state['saml:ProxyCount']);
-		} elseif ($idpMetadata->getInteger('ProxyCount', null) !== null) {
-			$ar->setProxyCount($idpMetadata->getInteger('ProxyCount', null));
-		} elseif ($this->metadata->getInteger('ProxyCount', null) !== null) {
-			$ar->setProxyCount($this->metadata->getInteger('ProxyCount', null));
-		}
-		
-		$requesterID = array();
-		if (isset($state['saml:RequesterID'])) {
-			$requesterID = $state['saml:RequesterID'];
-		}
-		
-		if (isset($state['core:SP'])) {
-			$requesterID[] = $state['core:SP'];
-		}
-		
-		$ar->setRequesterID($requesterID);
-		
-		if (isset($state['saml:Extensions'])) {
-			$ar->setExtensions($state['saml:Extensions']);
-		}
-
-		// save IdP entity ID as part of the state
-		$state['ExpectedIssuer'] = $idpMetadata->getString('entityid');
-
-		$id = SimpleSAML_Auth_State::saveState($state, 'saml:sp:sso', TRUE);
-		$ar->setId($id);
-
-		SimpleSAML\Logger::debug('Sending SAML 2 AuthnRequest to ' . var_export($idpMetadata->getString('entityid'), TRUE));
-
-		/* Select appropriate SSO endpoint */
-		if ($ar->getProtocolBinding() === \SAML2\Constants::BINDING_HOK_SSO) {
-			$dst = $idpMetadata->getDefaultEndpoint('SingleSignOnService', array(
-				\SAML2\Constants::BINDING_HOK_SSO)
-			);
-		} else {
-			$dst = $idpMetadata->getDefaultEndpoint('SingleSignOnService', array(
-				\SAML2\Constants::BINDING_HTTP_REDIRECT,
-				\SAML2\Constants::BINDING_HTTP_POST)
-			);
-		}
-		$ar->setDestination($dst['Location']);
-
-		$b = \SAML2\Binding::getBinding($dst['Binding']);
-
-		$this->sendSAML2AuthnRequest($state, $b, $ar);
-
-		assert(false);
-	}
-
-
-	/**
-	 * Function to actually send the authentication request.
-	 *
-	 * This function does not return.
-	 *
-	 * @param array &$state  The state array.
-	 * @param \SAML2\Binding $binding  The binding.
-	 * @param \SAML2\AuthnRequest  $ar  The authentication request.
-	 */
-	public function sendSAML2AuthnRequest(array &$state, \SAML2\Binding $binding, \SAML2\AuthnRequest $ar) {
-		$binding->send($ar);
-		assert(false);
-	}
-
-
-	/**
-	 * Send a SSO request to an IdP.
-	 *
-	 * @param string $idp  The entity ID of the IdP.
-	 * @param array $state  The state array for the current authentication.
-	 */
-	public function startSSO($idp, array $state) {
-		assert(is_string($idp));
-
-		$idpMetadata = $this->getIdPMetadata($idp);
-
-		$type = $idpMetadata->getString('metadata-set');
-		switch ($type) {
-		case 'shib13-idp-remote':
-			$this->startSSO1($idpMetadata, $state);
-			assert(false); /* Should not return. */
-		case 'saml20-idp-remote':
-			$this->startSSO2($idpMetadata, $state);
-			assert(false); /* Should not return. */
-		default:
-			/* Should only be one of the known types. */
-			assert(false);
-		}
-	}
-
-
-	/**
-	 * Start an IdP discovery service operation.
-	 *
-	 * @param array $state  The state array.
-	 */
-	private function startDisco(array $state) {
-
-		$id = SimpleSAML_Auth_State::saveState($state, 'saml:sp:sso');
-
-		$discoURL = $this->discoURL;
-		if ($discoURL === NULL) {
-			/* Fallback to internal discovery service. */
-			$discoURL = SimpleSAML\Module::getModuleURL('saml/disco.php');
-		}
-
-		$returnTo = SimpleSAML\Module::getModuleURL('saml/sp/discoresp.php', array('AuthID' => $id));
-		
-		$params = array(
-			'entityID' => $this->entityId,
-			'return' => $returnTo,
-			'returnIDParam' => 'idpentityid'
-		);
-		
-		if(isset($state['saml:IDPList'])) {
-			$params['IDPList'] = $state['saml:IDPList'];
-		}
-
-		if (isset($state['isPassive']) && $state['isPassive']) {
-			$params['isPassive'] = 'true';
-		}
-
-		\SimpleSAML\Utils\HTTP::redirectTrustedURL($discoURL, $params);
-	}
-
-
-	/**
-	 * Start login.
-	 *
-	 * This function saves the information about the login, and redirects to the IdP.
-	 *
-	 * @param array &$state  Information about the current authentication.
-	 */
-	public function authenticate(&$state) {
-		assert(is_array($state));
-
-		/* We are going to need the authId in order to retrieve this authentication source later. */
-		$state['saml:sp:AuthId'] = $this->authId;
-
-		$idp = $this->idp;
-
-		if (isset($state['saml:idp'])) {
-			$idp = (string)$state['saml:idp'];
-		}
-
-		if (isset($state['saml:IDPList']) && sizeof($state['saml:IDPList']) > 0) {
-			// we have a SAML IDPList (we are a proxy): filter the list of IdPs available
-			$mdh = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
-			$known_idps = $mdh->getList();
-			$intersection = array_intersect($state['saml:IDPList'], array_keys($known_idps));
-
-			if (empty($intersection)) { // all requested IdPs are unknown
-				throw new SimpleSAML\Module\saml\Error\NoSupportedIDP(
-					\SAML2\Constants::STATUS_REQUESTER,
-					'None of the IdPs requested are supported by this proxy.'
-				);
-			}
-
-			if (!is_null($idp) && !in_array($idp, $intersection, true)) { // the IdP is enforced but not in the IDPList
-				throw new SimpleSAML\Module\saml\Error\NoAvailableIDP(
-					\SAML2\Constants::STATUS_REQUESTER,
-					'None of the IdPs requested are available to this proxy.'
-				);
-			}
-
-			if (is_null($idp) && sizeof($intersection) === 1) { // only one IdP requested or valid
-				$idp = current($state['saml:IDPList']);
-			}
-		}
-
-		if ($idp === null) {
-			$this->startDisco($state);
-			assert(false);
-		}
-
-		$this->startSSO($idp, $state);
-		assert(false);
-	}
-
-
-	/**
-	 * Re-authenticate an user.
-	 *
-	 * This function is called by the IdP to give the authentication source a chance to
-	 * interact with the user even in the case when the user is already authenticated.
-	 *
-	 * @param array &$state  Information about the current authentication.
-	 */
-	public function reauthenticate(array &$state) {
-		assert(is_array($state));
-
-		$session = SimpleSAML_Session::getSessionFromRequest();
-		$data = $session->getAuthState($this->authId);
-		foreach ($data as $k => $v) {
-			$state[$k] = $v;
-		}
-
-		// check if we have an IDPList specified in the request
-		if (isset($state['saml:IDPList']) && sizeof($state['saml:IDPList']) > 0 &&
-			!in_array($state['saml:sp:IdP'], $state['saml:IDPList'], true))
-		{
-			/*
-			 * The user has an existing, valid session. However, the SP provided a list of IdPs it accepts for
-			 * authentication, and the IdP the existing session is related to is not in that list.
-			 *
-			 * First, check if we recognize any of the IdPs requested.
-			 */
-
-			$mdh = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
-			$known_idps = $mdh->getList();
-			$intersection = array_intersect($state['saml:IDPList'], array_keys($known_idps));
-
-			if (empty($intersection)) { // all requested IdPs are unknown
-				throw new SimpleSAML\Module\saml\Error\NoSupportedIDP(
-					\SAML2\Constants::STATUS_REQUESTER,
-					'None of the IdPs requested are supported by this proxy.'
-				);
-			}
-
-			/*
-			 * We have at least one IdP in the IDPList that we recognize, and it's not the one currently in use. Let's
-			 * see if this proxy enforces the use of one single IdP.
-			 */
-			if (!is_null($this->idp) && !in_array($this->idp, $intersection, true)) { // an IdP is enforced but not requested
-				throw new SimpleSAML\Module\saml\Error\NoAvailableIDP(
-					\SAML2\Constants::STATUS_REQUESTER,
-					'None of the IdPs requested are available to this proxy.'
-				);
-			}
-
-			/*
-			 * We need to inform the user, and ask whether we should logout before starting the authentication process
-			 * again with a different IdP, or cancel the current SSO attempt.
-			 */
-			SimpleSAML\Logger::warning(
-				"Reauthentication after logout is needed. The IdP '${state['saml:sp:IdP']}' is not in the IDPList ".
-				"provided by the Service Provider '${state['core:SP']}'."
-			);
-
-			$state['saml:sp:IdPMetadata'] = $this->getIdPMetadata($state['saml:sp:IdP']);
-			$state['saml:sp:AuthId'] = $this->authId;
-			self::askForIdPChange($state);
-		}
-	}
-
-
-	/**
-	 * Ask the user to log out before being able to log in again with a different identity provider. Note that this
-	 * method is intended for instances of SimpleSAMLphp running as a SAML proxy, and therefore acting both as an SP
-	 * and an IdP at the same time.
-	 *
-	 * This method will never return.
-	 *
-	 * @param array $state The state array. The following keys must be defined in the array:
-	 * - 'saml:sp:IdPMetadata': a SimpleSAML_Configuration object containing the metadata of the IdP that authenticated
-	 *   the user in the current session.
-	 * - 'saml:sp:AuthId': the identifier of the current authentication source.
-	 * - 'core:IdP': the identifier of the local IdP.
-	 * - 'SPMetadata': an array with the metadata of this local SP.
-	 *
-	 * @throws SimpleSAML_Error_NoPassive In case the authentication request was passive.
-	 */
-	public static function askForIdPChange(array &$state)
-	{
-		assert(array_key_exists('saml:sp:IdPMetadata', $state));
-		assert(array_key_exists('saml:sp:AuthId', $state));
-		assert(array_key_exists('core:IdP', $state));
-		assert(array_key_exists('SPMetadata', $state));
-
-		if (isset($state['isPassive']) && (bool)$state['isPassive']) {
-			// passive request, we cannot authenticate the user
-			throw new SimpleSAML\Module\saml\Error\NoPassive(
-					\SAML2\Constants::STATUS_REQUESTER,
-                    'Reauthentication required'
+        }
+
+        /* Not found. */
+        throw new SimpleSAML_Error_Exception('Could not find the metadata of an IdP with entity ID ' .
+            var_export($entityId, true));
+    }
+
+    /**
+     * Send a SAML1 SSO request to an IdP.
+     *
+     * @param SimpleSAML_Configuration $idpMetadata  The metadata of the IdP.
+     * @param array $state  The state array for the current authentication.
+     */
+    private function startSSO1(SimpleSAML_Configuration $idpMetadata, array $state)
+    {
+        $idpEntityId = $idpMetadata->getString('entityid');
+
+        $state['saml:idp'] = $idpEntityId;
+
+        $ar = new \SimpleSAML\XML\Shib13\AuthnRequest();
+        $ar->setIssuer($this->entityId);
+
+        $id = SimpleSAML_Auth_State::saveState($state, 'saml:sp:sso');
+        $ar->setRelayState($id);
+
+        $useArtifact = $idpMetadata->getBoolean('saml1.useartifact', null);
+        if ($useArtifact === null) {
+            $useArtifact = $this->metadata->getBoolean('saml1.useartifact', false);
+        }
+
+        if ($useArtifact) {
+            $shire = SimpleSAML\Module::getModuleURL('saml/sp/saml1-acs.php/' . $this->authId . '/artifact');
+        } else {
+            $shire = SimpleSAML\Module::getModuleURL('saml/sp/saml1-acs.php/' . $this->authId);
+        }
+
+        $url = $ar->createRedirect($idpEntityId, $shire);
+
+        SimpleSAML\Logger::debug('Starting SAML 1 SSO to ' . var_export($idpEntityId, true) .
+            ' from ' . var_export($this->entityId, true) . '.');
+        \SimpleSAML\Utils\HTTP::redirectTrustedURL($url);
+    }
+
+    /**
+     * Send a SAML2 SSO request to an IdP.
+     *
+     * @param SimpleSAML_Configuration $idpMetadata  The metadata of the IdP.
+     * @param array $state  The state array for the current authentication.
+     */
+    private function startSSO2(SimpleSAML_Configuration $idpMetadata, array $state)
+    {
+        if (isset($state['saml:ProxyCount']) && $state['saml:ProxyCount'] < 0) {
+            SimpleSAML_Auth_State::throwException(
+                $state,
+                new \SimpleSAML\Module\saml\Error\ProxyCountExceeded(\SAML2\Constants::STATUS_RESPONDER)
+            );
+        }
+
+        $ar = sspmod_saml_Message::buildAuthnRequest($this->metadata, $idpMetadata);
+
+        $ar->setAssertionConsumerServiceURL(SimpleSAML\Module::getModuleURL('saml/sp/saml2-acs.php/' . $this->authId));
+
+        if (isset($state['SimpleSAML_Auth_Source.ReturnURL'])) {
+            $ar->setRelayState($state['SimpleSAML_Auth_Source.ReturnURL']);
+        }
+
+        if (isset($state['saml:AuthnContextClassRef'])) {
+            $accr = SimpleSAML\Utils\Arrays::arrayize($state['saml:AuthnContextClassRef']);
+            $comp = SAML2\Constants::COMPARISON_EXACT;
+            if (isset($state['saml:AuthnContextComparison']) && in_array($state['AuthnContextComparison'], array(
+                        SAML2\Constants::COMPARISON_EXACT,
+                        SAML2\Constants::COMPARISON_MINIMUM,
+                        SAML2\Constants::COMPARISON_MAXIMUM,
+                        SAML2\Constants::COMPARISON_BETTER,
+            ), true)) {
+                $comp = $state['saml:AuthnContextComparison'];
+            }
+            $ar->setRequestedAuthnContext(array('AuthnContextClassRef' => $accr, 'Comparison' => $comp));
+        }
+
+        if (isset($state['ForceAuthn'])) {
+            $ar->setForceAuthn((bool)$state['ForceAuthn']);
+        }
+
+        if (isset($state['isPassive'])) {
+            $ar->setIsPassive((bool)$state['isPassive']);
+        }
+
+        if (isset($state['saml:NameID'])) {
+            if (!is_array($state['saml:NameID']) && !is_a($state['saml:NameID'], '\SAML2\XML\saml\NameID')) {
+                throw new SimpleSAML_Error_Exception('Invalid value of $state[\'saml:NameID\'].');
+            }
+            $ar->setNameId($state['saml:NameID']);
+        }
+
+        if (isset($state['saml:NameIDPolicy'])) {
+            if (is_string($state['saml:NameIDPolicy'])) {
+                $policy = array(
+                    'Format' => (string)$state['saml:NameIDPolicy'],
+                    'AllowCreate' => true,
+                );
+            } elseif (is_array($state['saml:NameIDPolicy'])) {
+                $policy = $state['saml:NameIDPolicy'];
+            } else {
+                throw new SimpleSAML_Error_Exception('Invalid value of $state[\'saml:NameIDPolicy\'].');
+            }
+            $ar->setNameIdPolicy($policy);
+        }
+
+        if (isset($state['saml:IDPList'])) {
+            $IDPList = $state['saml:IDPList'];
+        } else {
+            $IDPList = array();
+        }
+
+        $ar->setIDPList(array_unique(array_merge($this->metadata->getArray('IDPList', array()), 
+                                                $idpMetadata->getArray('IDPList', array()),
+                                                (array) $IDPList)));
+
+        if (isset($state['saml:ProxyCount']) && $state['saml:ProxyCount'] !== null) {
+            $ar->setProxyCount($state['saml:ProxyCount']);
+        } elseif ($idpMetadata->getInteger('ProxyCount', null) !== null) {
+            $ar->setProxyCount($idpMetadata->getInteger('ProxyCount', null));
+        } elseif ($this->metadata->getInteger('ProxyCount', null) !== null) {
+            $ar->setProxyCount($this->metadata->getInteger('ProxyCount', null));
+        }
+
+        $requesterID = array();
+        if (isset($state['saml:RequesterID'])) {
+            $requesterID = $state['saml:RequesterID'];
+        }
+
+        if (isset($state['core:SP'])) {
+            $requesterID[] = $state['core:SP'];
+        }
+
+        $ar->setRequesterID($requesterID);
+
+        if (isset($state['saml:Extensions'])) {
+            $ar->setExtensions($state['saml:Extensions']);
+        }
+
+        // save IdP entity ID as part of the state
+        $state['ExpectedIssuer'] = $idpMetadata->getString('entityid');
+
+        $id = SimpleSAML_Auth_State::saveState($state, 'saml:sp:sso', true);
+        $ar->setId($id);
+
+        SimpleSAML\Logger::debug('Sending SAML 2 AuthnRequest to ' .
+            var_export($idpMetadata->getString('entityid'), true));
+
+        /* Select appropriate SSO endpoint */
+        if ($ar->getProtocolBinding() === \SAML2\Constants::BINDING_HOK_SSO) {
+            $dst = $idpMetadata->getDefaultEndpoint('SingleSignOnService', array(
+                \SAML2\Constants::BINDING_HOK_SSO)
+            );
+        } else {
+            $dst = $idpMetadata->getDefaultEndpoint('SingleSignOnService', array(
+                \SAML2\Constants::BINDING_HTTP_REDIRECT,
+                \SAML2\Constants::BINDING_HTTP_POST)
+            );
+        }
+        $ar->setDestination($dst['Location']);
+
+        $b = \SAML2\Binding::getBinding($dst['Binding']);
+
+        $this->sendSAML2AuthnRequest($state, $b, $ar);
+
+        assert(false);
+    }
+
+    /**
+     * Function to actually send the authentication request.
+     *
+     * This function does not return.
+     *
+     * @param array &$state  The state array.
+     * @param \SAML2\Binding $binding  The binding.
+     * @param \SAML2\AuthnRequest  $ar  The authentication request.
+     */
+    public function sendSAML2AuthnRequest(array &$state, \SAML2\Binding $binding, \SAML2\AuthnRequest $ar)
+    {
+        $binding->send($ar);
+        assert(false);
+    }
+
+    /**
+     * Send a SSO request to an IdP.
+     *
+     * @param string $idp  The entity ID of the IdP.
+     * @param array $state  The state array for the current authentication.
+     */
+    public function startSSO($idp, array $state)
+    {
+        assert(is_string($idp));
+
+        $idpMetadata = $this->getIdPMetadata($idp);
+
+        $type = $idpMetadata->getString('metadata-set');
+        switch ($type) {
+        case 'shib13-idp-remote':
+            $this->startSSO1($idpMetadata, $state);
+            assert(false); /* Should not return. */
+        case 'saml20-idp-remote':
+            $this->startSSO2($idpMetadata, $state);
+            assert(false); /* Should not return. */
+        default:
+            /* Should only be one of the known types. */
+            assert(false);
+        }
+    }
+
+    /**
+     * Start an IdP discovery service operation.
+     *
+     * @param array $state  The state array.
+     */
+    private function startDisco(array $state)
+    {
+        $id = SimpleSAML_Auth_State::saveState($state, 'saml:sp:sso');
+
+        $discoURL = $this->discoURL;
+        if ($discoURL === null) {
+            /* Fallback to internal discovery service. */
+            $discoURL = SimpleSAML\Module::getModuleURL('saml/disco.php');
+        }
+
+        $returnTo = SimpleSAML\Module::getModuleURL('saml/sp/discoresp.php', array('AuthID' => $id));
+
+        $params = array(
+            'entityID' => $this->entityId,
+            'return' => $returnTo,
+            'returnIDParam' => 'idpentityid'
+        );
+
+        if(isset($state['saml:IDPList'])) {
+            $params['IDPList'] = $state['saml:IDPList'];
+        }
+
+        if (isset($state['isPassive']) && $state['isPassive']) {
+            $params['isPassive'] = 'true';
+        }
+
+        \SimpleSAML\Utils\HTTP::redirectTrustedURL($discoURL, $params);
+    }
+
+    /**
+     * Start login.
+     *
+     * This function saves the information about the login, and redirects to the IdP.
+     *
+     * @param array &$state  Information about the current authentication.
+     */
+    public function authenticate(&$state)
+    {
+        assert(is_array($state));
+
+        /* We are going to need the authId in order to retrieve this authentication source later. */
+        $state['saml:sp:AuthId'] = $this->authId;
+
+        $idp = $this->idp;
+
+        if (isset($state['saml:idp'])) {
+            $idp = (string)$state['saml:idp'];
+        }
+
+        if (isset($state['saml:IDPList']) && sizeof($state['saml:IDPList']) > 0) {
+            // we have a SAML IDPList (we are a proxy): filter the list of IdPs available
+            $mdh = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
+            $known_idps = $mdh->getList();
+            $intersection = array_intersect($state['saml:IDPList'], array_keys($known_idps));
+
+            if (empty($intersection)) {
+                // all requested IdPs are unknown
+                throw new SimpleSAML\Module\saml\Error\NoSupportedIDP(
+                    \SAML2\Constants::STATUS_REQUESTER,
+                    'None of the IdPs requested are supported by this proxy.'
+                );
+            }
+
+            if (!is_null($idp) && !in_array($idp, $intersection, true)) {
+                // the IdP is enforced but not in the IDPList
+                throw new SimpleSAML\Module\saml\Error\NoAvailableIDP(
+                    \SAML2\Constants::STATUS_REQUESTER,
+                    'None of the IdPs requested are available to this proxy.'
+                );
+            }
+
+            if (is_null($idp) && sizeof($intersection) === 1) {
+                // only one IdP requested or valid
+                $idp = current($state['saml:IDPList']);
+            }
+        }
+
+        if ($idp === null) {
+            $this->startDisco($state);
+            assert(false);
+        }
+
+        $this->startSSO($idp, $state);
+        assert(false);
+    }
+
+    /**
+     * Re-authenticate an user.
+     *
+     * This function is called by the IdP to give the authentication source a chance to
+     * interact with the user even in the case when the user is already authenticated.
+     *
+     * @param array &$state  Information about the current authentication.
+     */
+    public function reauthenticate(array &$state)
+    {
+        assert(is_array($state));
+
+        $session = SimpleSAML_Session::getSessionFromRequest();
+        $data = $session->getAuthState($this->authId);
+        foreach ($data as $k => $v) {
+            $state[$k] = $v;
+        }
+
+        // check if we have an IDPList specified in the request
+        if (isset($state['saml:IDPList']) && sizeof($state['saml:IDPList']) > 0 &&
+            !in_array($state['saml:sp:IdP'], $state['saml:IDPList'], true))
+        {
+            /*
+             * The user has an existing, valid session. However, the SP
+             * provided a list of IdPs it accepts for authentication, and
+             * the IdP the existing session is related to is not in that list.
+             *
+             * First, check if we recognize any of the IdPs requested.
+             */
+
+            $mdh = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
+            $known_idps = $mdh->getList();
+            $intersection = array_intersect($state['saml:IDPList'], array_keys($known_idps));
+
+            if (empty($intersection)) {
+                // all requested IdPs are unknown
+                throw new SimpleSAML\Module\saml\Error\NoSupportedIDP(
+                    \SAML2\Constants::STATUS_REQUESTER,
+                    'None of the IdPs requested are supported by this proxy.'
+                );
+            }
+
+            /*
+             * We have at least one IdP in the IDPList that we recognize, and
+             * it's not the one currently in use. Let's see if this proxy
+             * enforces the use of one single IdP.
+             */
+            if (!is_null($this->idp) && !in_array($this->idp, $intersection, true)) {
+                // an IdP is enforced but not requested
+                throw new SimpleSAML\Module\saml\Error\NoAvailableIDP(
+                    \SAML2\Constants::STATUS_REQUESTER,
+                    'None of the IdPs requested are available to this proxy.'
+                );
+            }
+
+            /*
+             * We need to inform the user, and ask whether we should logout before
+             * starting the authentication process again with a different IdP, or
+             * cancel the current SSO attempt.
+             */
+            SimpleSAML\Logger::warning(
+                "Reauthentication after logout is needed. The IdP '${state['saml:sp:IdP']}' is not in the IDPList ".
+                "provided by the Service Provider '${state['core:SP']}'."
             );
-		}
-
-		// save the state WITHOUT a restart URL, so that we don't try an IdP-initiated login if something goes wrong
-		$id = SimpleSAML_Auth_State::saveState($state, 'saml:proxy:invalid_idp', true);
-		$url = SimpleSAML\Module::getModuleURL('saml/proxy/invalid_session.php');
-		SimpleSAML\Utils\HTTP::redirectTrustedURL($url, array('AuthState' => $id));
-		assert(false);
-	}
-
-
-	/**
-	 * Log the user out before logging in again.
-	 *
-	 * This method will never return.
-	 *
-	 * @param array $state The state array.
-	 */
-	public static function reauthLogout(array $state)
-	{
-		SimpleSAML\Logger::debug('Proxy: logging the user out before re-authentication.');
-
-		if (isset($state['Responder'])) {
-			$state['saml:proxy:reauthLogout:PrevResponder'] = $state['Responder'];
-		}
-		$state['Responder'] = array('sspmod_saml_Auth_Source_SP', 'reauthPostLogout');
-
-		$idp = SimpleSAML_IdP::getByState($state);
-		$idp->handleLogoutRequest($state, null);
-		assert(false);
-	}
-
-
-	/**
-	 * Complete login operation after re-authenticating the user on another IdP.
-	 *
-	 * @param array $state  The authentication state.
-	 */
-	public static function reauthPostLogin(array $state) {
-		assert(isset($state['ReturnCallback']));
-
-		// Update session state
-		$session = SimpleSAML_Session::getSessionFromRequest();
-		$authId = $state['saml:sp:AuthId'];
-		$session->doLogin($authId, SimpleSAML_Auth_State::getPersistentAuthData($state));
-
-		// resume the login process
-		call_user_func($state['ReturnCallback'], $state);
-		assert(false);
-	}
-
-
-	/**
-	 * Post-logout handler for re-authentication.
-	 *
-	 * This method will never return.
-	 *
-	 * @param SimpleSAML_IdP $idp The IdP we are logging out from.
-	 * @param array &$state The state array with the state during logout.
-	 */
-	public static function reauthPostLogout(SimpleSAML_IdP $idp, array $state) {
-		assert(isset($state['saml:sp:AuthId']));
-
-		SimpleSAML\Logger::debug('Proxy: logout completed.');
-
-		if (isset($state['saml:proxy:reauthLogout:PrevResponder'])) {
-			$state['Responder'] = $state['saml:proxy:reauthLogout:PrevResponder'];
-		}
-
-		$sp = SimpleSAML_Auth_Source::getById($state['saml:sp:AuthId'], 'sspmod_saml_Auth_Source_SP');
-		/** @var sspmod_saml_Auth_Source_SP $authSource */
-		SimpleSAML\Logger::debug('Proxy: logging in again.');
-		$sp->authenticate($state);
-		assert(false);
-	}
-
-
-	/**
-	 * Start a SAML 2 logout operation.
-	 *
-	 * @param array $state  The logout state.
-	 */
-	public function startSLO2(&$state) {
-		assert(is_array($state));
-		assert(array_key_exists('saml:logout:IdP', $state));
-		assert(array_key_exists('saml:logout:NameID', $state));
-		assert(array_key_exists('saml:logout:SessionIndex', $state));
-
-		$id = SimpleSAML_Auth_State::saveState($state, 'saml:slosent');
-
-		$idp = $state['saml:logout:IdP'];
-		$nameId = $state['saml:logout:NameID'];
-		$sessionIndex = $state['saml:logout:SessionIndex'];
-
-		$idpMetadata = $this->getIdPMetadata($idp);
-
-		$endpoint = $idpMetadata->getEndpointPrioritizedByBinding('SingleLogoutService', array(
-			\SAML2\Constants::BINDING_HTTP_REDIRECT,
-			\SAML2\Constants::BINDING_HTTP_POST), FALSE);
-		if ($endpoint === FALSE) {
-			SimpleSAML\Logger::info('No logout endpoint for IdP ' . var_export($idp, TRUE) . '.');
-			return;
-		}
-
-		$lr = sspmod_saml_Message::buildLogoutRequest($this->metadata, $idpMetadata);
-		$lr->setNameId($nameId);
-		$lr->setSessionIndex($sessionIndex);
-		$lr->setRelayState($id);
-		$lr->setDestination($endpoint['Location']);
-
-		$encryptNameId = $idpMetadata->getBoolean('nameid.encryption', NULL);
-		if ($encryptNameId === NULL) {
-			$encryptNameId = $this->metadata->getBoolean('nameid.encryption', FALSE);
-		}
-		if ($encryptNameId) {
-			$lr->encryptNameId(sspmod_saml_Message::getEncryptionKey($idpMetadata));
-		}
-
-		$b = \SAML2\Binding::getBinding($endpoint['Binding']);
-		$b->send($lr);
-
-		assert(false);
-	}
-
-
-	/**
-	 * Start logout operation.
-	 *
-	 * @param array $state  The logout state.
-	 */
-	public function logout(&$state) {
-		assert(is_array($state));
-		assert(array_key_exists('saml:logout:Type', $state));
-
-		$logoutType = $state['saml:logout:Type'];
-		switch ($logoutType) {
-		case 'saml1':
-			/* Nothing to do. */
-			return;
-		case 'saml2':
-			$this->startSLO2($state);
-			return;
-		default:
-			/* Should never happen. */
-			assert(false);
-		}
-	}
-
-
-	/**
-	 * Handle a response from a SSO operation.
-	 *
-	 * @param array $state  The authentication state.
-	 * @param string $idp  The entity id of the IdP.
-	 * @param array $attributes  The attributes.
-	 */
-	public function handleResponse(array $state, $idp, array $attributes) {
-		assert(is_string($idp));
-		assert(array_key_exists('LogoutState', $state));
-		assert(array_key_exists('saml:logout:Type', $state['LogoutState']));
-		
-		$idpMetadata = $this->getIdpMetadata($idp);
-
-		$spMetadataArray = $this->metadata->toArray();
-		$idpMetadataArray = $idpMetadata->toArray();
-
-		/* Save the IdP in the state array. */
-		$state['saml:sp:IdP'] = $idp;
-		$state['PersistentAuthData'][] = 'saml:sp:IdP';
-
-		$authProcState = array(
-			'saml:sp:IdP' => $idp,
-			'saml:sp:State' => $state,
-			'ReturnCall' => array('sspmod_saml_Auth_Source_SP', 'onProcessingCompleted'),
-
-			'Attributes' => $attributes,
-			'Destination' => $spMetadataArray,
-			'Source' => $idpMetadataArray,
-		);
-
-		if (isset($state['saml:sp:NameID'])) {
-			$authProcState['saml:sp:NameID'] = $state['saml:sp:NameID'];
-		}
-		if (isset($state['saml:sp:SessionIndex'])) {
-			$authProcState['saml:sp:SessionIndex'] = $state['saml:sp:SessionIndex'];
-		}
-
-		$pc = new SimpleSAML_Auth_ProcessingChain($idpMetadataArray, $spMetadataArray, 'sp');
-		$pc->processState($authProcState);
-
-		self::onProcessingCompleted($authProcState);
-	}
-
-
-	/**
-	 * Handle a logout request from an IdP.
-	 *
-	 * @param string $idpEntityId  The entity ID of the IdP.
-	 */
-	public function handleLogout($idpEntityId) {
-		assert(is_string($idpEntityId));
-
-		/* Call the logout callback we registered in onProcessingCompleted(). */
-		$this->callLogoutCallback($idpEntityId);
-	}
-
-
-	/**
-	 * Handle an unsolicited login operations.
-	 *
-	 * This method creates a session from the information received. It will then redirect to the given URL. This is used
-	 * to handle IdP initiated SSO. This method will never return.
-	 *
-	 * @param string $authId The id of the authentication source that received the request.
-	 * @param array $state A state array.
-	 * @param string $redirectTo The URL we should redirect the user to after updating the session. The function will
-	 * check if the URL is allowed, so there is no need to manually check the URL on beforehand. Please refer to the
-	 * 'trusted.url.domains' configuration directive for more information about allowing (or disallowing) URLs.
-	 */
-	public static function handleUnsolicitedAuth($authId, array $state, $redirectTo) {
-		assert(is_string($authId));
-		assert(is_string($redirectTo));
-
-		$session = SimpleSAML_Session::getSessionFromRequest();
-		$session->doLogin($authId, SimpleSAML_Auth_State::getPersistentAuthData($state));
-
-		\SimpleSAML\Utils\HTTP::redirectUntrustedURL($redirectTo);
-	}
-
-
-	/**
-	 * Called when we have completed the procssing chain.
-	 *
-	 * @param array $authProcState  The processing chain state.
-	 */
-	public static function onProcessingCompleted(array $authProcState) {
-		assert(array_key_exists('saml:sp:IdP', $authProcState));
-		assert(array_key_exists('saml:sp:State', $authProcState));
-		assert(array_key_exists('Attributes', $authProcState));
-
-		$idp = $authProcState['saml:sp:IdP'];
-		$state = $authProcState['saml:sp:State'];
-
-		$sourceId = $state['saml:sp:AuthId'];
-		$source = SimpleSAML_Auth_Source::getById($sourceId);
-		if ($source === NULL) {
-			throw new Exception('Could not find authentication source with id ' . $sourceId);
-		}
-
-		/* Register a callback that we can call if we receive a logout request from the IdP. */
-		$source->addLogoutCallback($idp, $state);
-
-		$state['Attributes'] = $authProcState['Attributes'];
-
-		if (isset($state['saml:sp:isUnsolicited']) && (bool)$state['saml:sp:isUnsolicited']) {
-			if (!empty($state['saml:sp:RelayState'])) {
-				$redirectTo = $state['saml:sp:RelayState'];
-			} else {
-				$redirectTo = $source->getMetadata()->getString('RelayState', '/');
-			}
-			self::handleUnsolicitedAuth($sourceId, $state, $redirectTo);
-		}
-
-		SimpleSAML_Auth_Source::completeAuth($state);
-	}
 
+            $state['saml:sp:IdPMetadata'] = $this->getIdPMetadata($state['saml:sp:IdP']);
+            $state['saml:sp:AuthId'] = $this->authId;
+            self::askForIdPChange($state);
+        }
+    }
+
+
+    /**
+     * Ask the user to log out before being able to log in again with a
+     * different identity provider. Note that this method is intended for
+     * instances of SimpleSAMLphp running as a SAML proxy, and therefore
+     * acting both as an SP and an IdP at the same time.
+     *
+     * This method will never return.
+     *
+     * @param array $state The state array.
+     * The following keys must be defined in the array:
+     * - 'saml:sp:IdPMetadata': a SimpleSAML_Configuration object containing
+     *   the metadata of the IdP that authenticated the user in the current
+     *   session.
+     * - 'saml:sp:AuthId': the identifier of the current authentication source.
+     * - 'core:IdP': the identifier of the local IdP.
+     * - 'SPMetadata': an array with the metadata of this local SP.
+     *
+     * @throws SimpleSAML_Error_NoPassive In case the authentication request was passive.
+     */
+    public static function askForIdPChange(array &$state)
+    {
+        assert(array_key_exists('saml:sp:IdPMetadata', $state));
+        assert(array_key_exists('saml:sp:AuthId', $state));
+        assert(array_key_exists('core:IdP', $state));
+        assert(array_key_exists('SPMetadata', $state));
+
+        if (isset($state['isPassive']) && (bool)$state['isPassive']) {
+            // passive request, we cannot authenticate the user
+            throw new SimpleSAML\Module\saml\Error\NoPassive(
+                    \SAML2\Constants::STATUS_REQUESTER,
+                    'Reauthentication required'
+            );
+        }
+
+        // save the state WITHOUT a restart URL, so that we don't try an IdP-initiated login if something goes wrong
+        $id = SimpleSAML_Auth_State::saveState($state, 'saml:proxy:invalid_idp', true);
+        $url = SimpleSAML\Module::getModuleURL('saml/proxy/invalid_session.php');
+        SimpleSAML\Utils\HTTP::redirectTrustedURL($url, array('AuthState' => $id));
+        assert(false);
+    }
+
+    /**
+     * Log the user out before logging in again.
+     *
+     * This method will never return.
+     *
+     * @param array $state The state array.
+     */
+    public static function reauthLogout(array $state)
+    {
+        SimpleSAML\Logger::debug('Proxy: logging the user out before re-authentication.');
+
+        if (isset($state['Responder'])) {
+            $state['saml:proxy:reauthLogout:PrevResponder'] = $state['Responder'];
+        }
+        $state['Responder'] = array('sspmod_saml_Auth_Source_SP', 'reauthPostLogout');
+
+        $idp = SimpleSAML_IdP::getByState($state);
+        $idp->handleLogoutRequest($state, null);
+        assert(false);
+    }
+
+    /**
+     * Complete login operation after re-authenticating the user on another IdP.
+     *
+     * @param array $state  The authentication state.
+     */
+    public static function reauthPostLogin(array $state)
+    {
+        assert(isset($state['ReturnCallback']));
+
+        // Update session state
+        $session = SimpleSAML_Session::getSessionFromRequest();
+        $authId = $state['saml:sp:AuthId'];
+        $session->doLogin($authId, SimpleSAML_Auth_State::getPersistentAuthData($state));
+
+        // resume the login process
+        call_user_func($state['ReturnCallback'], $state);
+        assert(false);
+    }
+
+    /**
+     * Post-logout handler for re-authentication.
+     *
+     * This method will never return.
+     *
+     * @param SimpleSAML_IdP $idp The IdP we are logging out from.
+     * @param array &$state The state array with the state during logout.
+     */
+    public static function reauthPostLogout(SimpleSAML_IdP $idp, array $state)
+    {
+        assert(isset($state['saml:sp:AuthId']));
+
+        SimpleSAML\Logger::debug('Proxy: logout completed.');
+
+        if (isset($state['saml:proxy:reauthLogout:PrevResponder'])) {
+            $state['Responder'] = $state['saml:proxy:reauthLogout:PrevResponder'];
+        }
+
+        $sp = SimpleSAML_Auth_Source::getById($state['saml:sp:AuthId'], 'sspmod_saml_Auth_Source_SP');
+        /** @var sspmod_saml_Auth_Source_SP $authSource */
+        SimpleSAML\Logger::debug('Proxy: logging in again.');
+        $sp->authenticate($state);
+        assert(false);
+    }
+
+    /**
+     * Start a SAML 2 logout operation.
+     *
+     * @param array $state  The logout state.
+     */
+    public function startSLO2(&$state)
+    {
+        assert(is_array($state));
+        assert(array_key_exists('saml:logout:IdP', $state));
+        assert(array_key_exists('saml:logout:NameID', $state));
+        assert(array_key_exists('saml:logout:SessionIndex', $state));
+
+        $id = SimpleSAML_Auth_State::saveState($state, 'saml:slosent');
+
+        $idp = $state['saml:logout:IdP'];
+        $nameId = $state['saml:logout:NameID'];
+        $sessionIndex = $state['saml:logout:SessionIndex'];
+
+        $idpMetadata = $this->getIdPMetadata($idp);
+
+        $endpoint = $idpMetadata->getEndpointPrioritizedByBinding('SingleLogoutService', array(
+            \SAML2\Constants::BINDING_HTTP_REDIRECT,
+            \SAML2\Constants::BINDING_HTTP_POST), false);
+        if ($endpoint === false) {
+            SimpleSAML\Logger::info('No logout endpoint for IdP ' . var_export($idp, true) . '.');
+            return;
+        }
+
+        $lr = sspmod_saml_Message::buildLogoutRequest($this->metadata, $idpMetadata);
+        $lr->setNameId($nameId);
+        $lr->setSessionIndex($sessionIndex);
+        $lr->setRelayState($id);
+        $lr->setDestination($endpoint['Location']);
+
+        $encryptNameId = $idpMetadata->getBoolean('nameid.encryption', null);
+        if ($encryptNameId === null) {
+            $encryptNameId = $this->metadata->getBoolean('nameid.encryption', false);
+        }
+        if ($encryptNameId) {
+            $lr->encryptNameId(sspmod_saml_Message::getEncryptionKey($idpMetadata));
+        }
+
+        $b = \SAML2\Binding::getBinding($endpoint['Binding']);
+        $b->send($lr);
+
+        assert(false);
+    }
+
+    /**
+     * Start logout operation.
+     *
+     * @param array $state  The logout state.
+     */
+    public function logout(&$state)
+    {
+        assert(is_array($state));
+        assert(array_key_exists('saml:logout:Type', $state));
+
+        $logoutType = $state['saml:logout:Type'];
+        switch ($logoutType) {
+        case 'saml1':
+            /* Nothing to do. */
+            return;
+        case 'saml2':
+            $this->startSLO2($state);
+            return;
+        default:
+            /* Should never happen. */
+            assert(false);
+        }
+    }
+
+    /**
+     * Handle a response from a SSO operation.
+     *
+     * @param array $state  The authentication state.
+     * @param string $idp  The entity id of the IdP.
+     * @param array $attributes  The attributes.
+     */
+    public function handleResponse(array $state, $idp, array $attributes)
+    {
+        assert(is_string($idp));
+        assert(array_key_exists('LogoutState', $state));
+        assert(array_key_exists('saml:logout:Type', $state['LogoutState']));
+
+        $idpMetadata = $this->getIdpMetadata($idp);
+
+        $spMetadataArray = $this->metadata->toArray();
+        $idpMetadataArray = $idpMetadata->toArray();
+
+        /* Save the IdP in the state array. */
+        $state['saml:sp:IdP'] = $idp;
+        $state['PersistentAuthData'][] = 'saml:sp:IdP';
+
+        $authProcState = array(
+            'saml:sp:IdP' => $idp,
+            'saml:sp:State' => $state,
+            'ReturnCall' => array('sspmod_saml_Auth_Source_SP', 'onProcessingCompleted'),
+
+            'Attributes' => $attributes,
+            'Destination' => $spMetadataArray,
+            'Source' => $idpMetadataArray,
+        );
+
+        if (isset($state['saml:sp:NameID'])) {
+            $authProcState['saml:sp:NameID'] = $state['saml:sp:NameID'];
+        }
+        if (isset($state['saml:sp:SessionIndex'])) {
+            $authProcState['saml:sp:SessionIndex'] = $state['saml:sp:SessionIndex'];
+        }
+
+        $pc = new SimpleSAML_Auth_ProcessingChain($idpMetadataArray, $spMetadataArray, 'sp');
+        $pc->processState($authProcState);
+
+        self::onProcessingCompleted($authProcState);
+    }
+
+    /**
+     * Handle a logout request from an IdP.
+     *
+     * @param string $idpEntityId  The entity ID of the IdP.
+     */
+    public function handleLogout($idpEntityId)
+    {
+        assert(is_string($idpEntityId));
+
+        /* Call the logout callback we registered in onProcessingCompleted(). */
+        $this->callLogoutCallback($idpEntityId);
+    }
+
+    /**
+     * Handle an unsolicited login operations.
+     *
+     * This method creates a session from the information received. It will
+     * then redirect to the given URL. This is used to handle IdP initiated
+     * SSO. This method will never return.
+     *
+     * @param string $authId The id of the authentication source that received the request.
+     * @param array $state A state array.
+     * @param string $redirectTo The URL we should redirect the user to after updating
+     * the session. The function will check if the URL is allowed, so there is no need to
+     * manually check the URL on beforehand. Please refer to the 'trusted.url.domains'
+     * configuration directive for more information about allowing (or disallowing) URLs.
+     */
+    public static function handleUnsolicitedAuth($authId, array $state, $redirectTo)
+    {
+        assert(is_string($authId));
+        assert(is_string($redirectTo));
+
+        $session = SimpleSAML_Session::getSessionFromRequest();
+        $session->doLogin($authId, SimpleSAML_Auth_State::getPersistentAuthData($state));
+
+        \SimpleSAML\Utils\HTTP::redirectUntrustedURL($redirectTo);
+    }
+
+    /**
+     * Called when we have completed the procssing chain.
+     *
+     * @param array $authProcState  The processing chain state.
+     */
+    public static function onProcessingCompleted(array $authProcState)
+    {
+        assert(array_key_exists('saml:sp:IdP', $authProcState));
+        assert(array_key_exists('saml:sp:State', $authProcState));
+        assert(array_key_exists('Attributes', $authProcState));
+
+        $idp = $authProcState['saml:sp:IdP'];
+        $state = $authProcState['saml:sp:State'];
+
+        $sourceId = $state['saml:sp:AuthId'];
+        $source = SimpleSAML_Auth_Source::getById($sourceId);
+        if ($source === null) {
+            throw new Exception('Could not find authentication source with id ' . $sourceId);
+        }
+
+        /* Register a callback that we can call if we receive a logout request from the IdP. */
+        $source->addLogoutCallback($idp, $state);
+
+        $state['Attributes'] = $authProcState['Attributes'];
+
+        if (isset($state['saml:sp:isUnsolicited']) && (bool)$state['saml:sp:isUnsolicited']) {
+            if (!empty($state['saml:sp:RelayState'])) {
+                $redirectTo = $state['saml:sp:RelayState'];
+            } else {
+                $redirectTo = $source->getMetadata()->getString('RelayState', '/');
+            }
+            self::handleUnsolicitedAuth($sourceId, $state, $redirectTo);
+        }
+
+        SimpleSAML_Auth_Source::completeAuth($state);
+    }
 }