From a3ff374f9470520c0c9ac7ba2ae5db485417d626 Mon Sep 17 00:00:00 2001
From: Olav Morken <olav.morken@uninett.no>
Date: Fri, 15 Jan 2010 10:10:50 +0000
Subject: [PATCH] Add support for sending responses with the HTTP-Artifact
 binding.

This patch implements support for sending responses to authentication
requests via the HTTP-Artifact binding. To enable, add
'saml20.sendartifact' => TRUE in saml20-idp-hosted metadata. The IdP
should then send HTTP-Artifact responses to SPs that request it.

Note that this requires a working memcache server.

Thanks to Danny Bollaert for implementing support for this.

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@2121 44740490-163a-0410-bde0-09ae8108e29a
---
 docs/simplesamlphp-reference-idp-hosted.txt |  6 ++
 lib/SAML2/ArtifactResolve.php               | 62 ++++++++++++++++++
 lib/SAML2/ArtifactResponse.php              | 69 +++++++++++++++++++++
 lib/SAML2/Binding.php                       |  6 ++
 lib/SAML2/Const.php                         | 10 +++
 lib/SAML2/HTTPArtifact.php                  | 64 +++++++++++++++++++
 lib/SAML2/Message.php                       |  4 ++
 lib/SAML2/SOAP.php                          | 55 ++++++++++++++++
 lib/SAML2/Utils.php                         |  1 +
 lib/SimpleSAML/Configuration.php            |  2 +
 lib/SimpleSAML/Metadata/SAMLBuilder.php     |  4 ++
 modules/saml/lib/IdP/SAML2.php              | 50 +++++++++++++--
 www/saml2/idp/ArtifactResolutionService.php | 42 +++++++++++++
 www/saml2/idp/metadata.php                  | 31 +++++----
 14 files changed, 390 insertions(+), 16 deletions(-)
 create mode 100644 lib/SAML2/ArtifactResolve.php
 create mode 100644 lib/SAML2/ArtifactResponse.php
 create mode 100644 lib/SAML2/HTTPArtifact.php
 create mode 100644 lib/SAML2/SOAP.php
 create mode 100644 www/saml2/idp/ArtifactResolutionService.php

diff --git a/docs/simplesamlphp-reference-idp-hosted.txt b/docs/simplesamlphp-reference-idp-hosted.txt
index 7d904a710..1c1973808 100644
--- a/docs/simplesamlphp-reference-idp-hosted.txt
+++ b/docs/simplesamlphp-reference-idp-hosted.txt
@@ -143,6 +143,12 @@ The following SAML 2.0 options are available:
     configure your webserver to deliver this URL to the correct PHP
     page.
 
+`saml20.sendartifact`
+:   Set to `TRUE` to enable the IdP to send responses with the HTTP-Artifact binding.
+    Defaults to `FALSE`.
+
+:   Note that this requires a configured memcache server.
+
 `saml20.sign.response`
 :   Whether `<samlp:Response> messages should be signed.
     Defaults to `TRUE`.
diff --git a/lib/SAML2/ArtifactResolve.php b/lib/SAML2/ArtifactResolve.php
new file mode 100644
index 000000000..c7de093ba
--- /dev/null
+++ b/lib/SAML2/ArtifactResolve.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * The Artifact is part of the SAML 2.0 IdP code, and it builds an artifact object.
+ * I am using strings, because I find them easier to work with.
+ * I want to use this, to be consistent with the other saml2_requests
+ *
+ * @author Danny Bollaert, UGent AS. <danny.bollaert@ugent.be>
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_ArtifactResolve extends SAML2_Request {
+
+
+	private  $artifact;
+
+
+
+	public function __construct(DOMElement $xml = NULL) {
+		parent::__construct('ArtifactResolve', $xml);
+
+		if(!is_null($xml)){
+			$results = SAML2_Utils::xpQuery($xml, './saml_protocol:Artifact');
+			$this->artifact = $results[0]->textContent;
+		}
+
+	}
+
+
+	/**
+	 * Retrieve the Artifact in this response.
+	 *
+	 * @return string artifact.
+	 */
+	public function getArtifact() {
+		return $this->artifact;
+	}
+
+
+	/**
+	 * Set the artifact that should be included in this response.
+	 *
+	 * @param String  The $artifact.
+	 */
+	public function setArtifact($artifact) {
+
+		$this->artifact = $artifact;
+	}
+
+	/**
+	 * Convert the response message to an XML element.
+	 *
+	 * @return DOMElement  This response.
+	 */
+	public function toUnsignedXML() {
+
+		throw new Exception('Not SUPPORTED');
+	}
+
+
+
+
+}
diff --git a/lib/SAML2/ArtifactResponse.php b/lib/SAML2/ArtifactResponse.php
new file mode 100644
index 000000000..7d4fe5e90
--- /dev/null
+++ b/lib/SAML2/ArtifactResponse.php
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * The SAML2_ArtifactResponse, is the response to the SAML2_ArtifactResolve.
+ *
+ * @author Danny Bollaert, UGent AS. <danny.bollaert@ugent.be>
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_ArtifactResponse extends SAML2_StatusResponse {
+
+
+	/**
+	 * @var any contains saml2message XML
+	 */
+	private $any;
+
+
+	public function __construct(DOMElement $xml = NULL) {
+		parent::__construct('ArtifactResponse', $xml);
+
+		if(!is_null($xml)){
+
+			$status = SAML2_Utils::xpQuery($xml, './saml_protocol:Status');
+			assert('!empty($status)'); /* Will have failed during StatusResponse parsing. */
+
+			$status = $status[0];
+
+			for ($any = $status->nextSibling; $any !== NULL; $any = $any->nextSibling) {
+				if ($any instanceof DOMElement) {
+					$this->any = $any;
+					break;
+				}
+				/* Ignore comments and text nodes. */
+			}
+		}
+
+	}
+
+
+	public function setAny(DOMElement $any) {
+		assert('!is_null($any)');
+		$this->any = $any;
+	}
+
+
+	public function getAny() {
+		return $this->any;
+	}
+
+
+	/**
+	 * Convert the response message to an XML element.
+	 *
+	 * @return DOMElement  This response.
+	 */
+	public function toUnsignedXML() {
+
+		$root = parent::toUnsignedXML();
+		if (isset($this->any)) {
+			$node = $root->ownerDocument->importNode($this->any, TRUE);
+			$root->appendChild($node);
+
+		}
+
+		return $root;
+	}
+
+}
diff --git a/lib/SAML2/Binding.php b/lib/SAML2/Binding.php
index 0dc960c42..50fd2ccde 100644
--- a/lib/SAML2/Binding.php
+++ b/lib/SAML2/Binding.php
@@ -32,6 +32,8 @@ abstract class SAML2_Binding {
 			return new SAML2_HTTPPost();
 		case SAML2_Const::BINDING_HTTP_REDIRECT:
 			return new SAML2_HTTPRedirect();
+		case SAML2_Const::BINDING_HTTP_ARTIFACT:
+			return new SAML2_HTTPArtifact();
 		default:
 			throw new Exception('Unsupported binding: ' . var_export($urn, TRUE));
 		}
@@ -53,12 +55,16 @@ abstract class SAML2_Binding {
 		case 'GET':
 			if (array_key_exists('SAMLRequest', $_REQUEST) || array_key_exists('SAMLResponse', $_REQUEST)) {
 				return new SAML2_HTTPRedirect();
+			} elseif (array_key_exists('SAMLart', $_REQUEST) ){
+				return new SAML2_HTTPArtifact();
 			}
 			break;
 
 		case 'POST':
 			if (array_key_exists('SAMLRequest', $_REQUEST) || array_key_exists('SAMLResponse', $_REQUEST)) {
 				return new SAML2_HTTPPost();
+			} elseif (array_key_exits('CONTENT_TYPE', $_SERVER) && $_SERVER['CONTENT_TYPE'] === 'text/xml'){
+				return new SAML2_SOAP();
 			}
 			break;
 		}
diff --git a/lib/SAML2/Const.php b/lib/SAML2/Const.php
index 6f1494e69..79f03a642 100644
--- a/lib/SAML2/Const.php
+++ b/lib/SAML2/Const.php
@@ -29,6 +29,11 @@ class SAML2_Const {
 	 */
 	const BINDING_HTTP_REDIRECT = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect';
 
+	/**
+	 * The URN for the HTTP-ARTIFACT binding.
+	 */
+	const BINDING_HTTP_ARTIFACT = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact';
+
 	/**
 	 * The URN for the SOAP binding.
 	 */
@@ -68,6 +73,11 @@ class SAML2_Const {
 	const NAMEID_ENCRYPTED = 'urn:oasis:names:tc:SAML:2.0:nameid-format:encrypted';
 
 
+	/**
+	 * The namespace for the SOAP protocol.
+	 */
+	const NS_SOAP = 'http://schemas.xmlsoap.org/soap/envelope/';
+
 	/**
 	 * The namespace for the SAML 2 protocol.
 	 */
diff --git a/lib/SAML2/HTTPArtifact.php b/lib/SAML2/HTTPArtifact.php
new file mode 100644
index 000000000..0251a69ea
--- /dev/null
+++ b/lib/SAML2/HTTPArtifact.php
@@ -0,0 +1,64 @@
+<?php
+
+
+/**
+ * Class which implements the HTTP-Redirect binding.
+ *
+ * @author  Danny Bollaert, UGent AS. <danny.bollaert@ugent.be>
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_HTTPArtifact extends SAML2_Binding {
+
+	/**
+	 * Create the redirect URL for a message.
+	 *
+	 * @param SAML2_Message $message  The message.
+	 * @return string  The URL the user should be redirected to in order to send a message.
+	 */
+	public function getRedirectURL(SAML2_Message $message) {
+
+		$generatedId = pack('H*', ((string)  SimpleSAML_Utilities::stringToHex(SimpleSAML_Utilities::generateRandomBytes(20))));
+		$artifact = base64_encode("\x00\x04\x00\x00" . sha1($message->getIssuer(), TRUE) . $generatedId) ;
+		$artifactData = $message->toUnsignedXML();
+		$artifactDataString = $artifactData->ownerDocument->saveXML($artifactData);
+		SimpleSAML_Memcache::set('artifact:' . $artifact, $artifactDataString);
+		$params = array(
+			'SAMLart' => $artifact,
+		);
+		$relayState = $message->getRelayState();
+		if ($relayState !== NULL) {
+			$params['RelayState'] = $relayState;
+		}
+
+		return SimpleSAML_Utilities::addURLparameter($message->getDestination(), $params);
+	}
+
+
+	/**
+	 * Send a SAML 2 message using the HTTP-Redirect binding.
+	 *
+	 * Note: This function never returns.
+	 *
+	 * @param SAML2_Message $message  The message we should send.
+	 */
+	public function send(SAML2_Message $message) {
+
+		$destination = $this->getRedirectURL($message);
+		SimpleSAML_Utilities::redirect($destination);
+	}
+
+
+	/**
+	 * Receive a SAMLart.
+	 *
+	 * Throws an exception if it is unable receive the message.
+	 *
+	 * @return SAML2_Message  The received message.
+	 */
+	public function receive() {
+
+		throw new Exception('Receiving SAML2 Artifact messages not supported.');
+	}
+
+}
diff --git a/lib/SAML2/Message.php b/lib/SAML2/Message.php
index b54087eca..f17e89826 100644
--- a/lib/SAML2/Message.php
+++ b/lib/SAML2/Message.php
@@ -471,6 +471,10 @@ abstract class SAML2_Message implements SAML2_SignedElement {
 			return new SAML2_LogoutRequest($xml);
 		case 'Response':
 			return new SAML2_Response($xml);
+		case 'ArtifactResponse':
+			return new SAML2_ArtifactResponse($xml);
+		case 'ArtifactResolve':
+			return new SAML2_ArtifactResolve($xml);
 		default:
 			throw new Exception('Unknown SAML message: ' . var_export($xml->localName, TRUE));
 		}
diff --git a/lib/SAML2/SOAP.php b/lib/SAML2/SOAP.php
new file mode 100644
index 000000000..00c46fa64
--- /dev/null
+++ b/lib/SAML2/SOAP.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * Class which implements the SOAP binding.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_SOAP extends SAML2_Binding {
+
+	/**
+	 * Send a SAML 2 message using the SOAP binding.
+	 *
+	 * Note: This function never returns.
+	 *
+	 * @param SAML2_Message $message  The message we should send.
+	 */
+	public function send(SAML2_Message $message) {
+		header('Content-Type: text/xml',true);
+		$outputFromIdp = '<?xml version="1.0" encoding="UTF-8"?>';
+		$outputFromIdp .= '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">';
+		$outputFromIdp .= '<SOAP-ENV:Body>';
+		$xmlMessage = $message->toUnsignedXML();
+		$tempOutputFromIdp = $xmlMessage->ownerDocument->saveXML($xmlMessage);
+		$outputFromIdp .= $tempOutputFromIdp;
+		$outputFromIdp .= '</SOAP-ENV:Body>';
+		$outputFromIdp .= '</SOAP-ENV:Envelope>';
+		print($outputFromIdp);
+		exit(0);
+	}
+
+
+	/**
+	 * Receive a SAML 2 message sent using the HTTP-POST binding.
+	 *
+	 * Throws an exception if it is unable receive the message.
+	 *
+	 * @return SAML2_Message  The received message.
+	 */
+	public function receive() {
+
+		$postText = file_get_contents('php://input');
+
+		if(empty($postText)){
+			throw new SimpleSAML_Error_BadRequest('Invalid message received to AssertionConsumerService endpoint.');
+		}
+
+		$document = new DOMDocument();
+		$document->loadXML($postText);
+		$xml = $document->firstChild;
+		$results = SAML2_Utils::xpQuery($xml, '/soap-env:Envelope/soap-env:Body/*[1]');
+		return SAML2_Message::fromXML($results[0]);
+	}
+
+}
diff --git a/lib/SAML2/Utils.php b/lib/SAML2/Utils.php
index 7f1aaa63d..5a5df7466 100644
--- a/lib/SAML2/Utils.php
+++ b/lib/SAML2/Utils.php
@@ -115,6 +115,7 @@ class SAML2_Utils {
 
 		if ($xpCache === NULL || !$xpCache->document->isSameNode($node->ownerDocument)) {
 			$xpCache = new DOMXPath($node->ownerDocument);
+			$xpCache->registerNamespace('soap-env', SAML2_Const::NS_SOAP);
 			$xpCache->registerNamespace('saml_protocol', SAML2_Const::NS_SAMLP);
 			$xpCache->registerNamespace('saml_assertion', SAML2_Const::NS_SAML);
 			$xpCache->registerNamespace('ds', XMLSecurityDSig::XMLDSIGNS);
diff --git a/lib/SimpleSAML/Configuration.php b/lib/SimpleSAML/Configuration.php
index a388fc7c0..ba36047a9 100644
--- a/lib/SimpleSAML/Configuration.php
+++ b/lib/SimpleSAML/Configuration.php
@@ -860,6 +860,8 @@ class SimpleSAML_Configuration {
 			return SAML2_Const::BINDING_HTTP_REDIRECT;
 		case 'saml20-sp-remote:AssertionConsumerService':
 			return SAML2_Const::BINDING_HTTP_POST;
+		case 'saml20-idp-remote:ArtifactResolutionService':
+			return SAML2_Const::BINDING_SOAP;
 		case 'shib13-idp-remote:SingleSignOnService':
 			return 'urn:mace:shibboleth:1.0:profiles:AuthnRequest';
 		case 'shib13-sp-remote:AssertionConsumerService':
diff --git a/lib/SimpleSAML/Metadata/SAMLBuilder.php b/lib/SimpleSAML/Metadata/SAMLBuilder.php
index 5a274e6b6..70dfd6111 100644
--- a/lib/SimpleSAML/Metadata/SAMLBuilder.php
+++ b/lib/SimpleSAML/Metadata/SAMLBuilder.php
@@ -396,6 +396,10 @@ class SimpleSAML_Metadata_SAMLBuilder {
 			$e->appendChild($t);
 		}
 
+		if ($metadata->hasValue('ArtifactResolutionService')){
+			$this->addEndpoints($e, 'ArtifactResolutionService', $metadata->getEndpoints('ArtifactResolutionService'));
+		}
+
 		$this->addEndpoints($e, 'SingleSignOnService', $metadata->getEndpoints('SingleSignOnService'));
 
 		$this->entityDescriptor->appendChild($e);
diff --git a/modules/saml/lib/IdP/SAML2.php b/modules/saml/lib/IdP/SAML2.php
index 8fd3bfeea..746bf94e3 100644
--- a/modules/saml/lib/IdP/SAML2.php
+++ b/modules/saml/lib/IdP/SAML2.php
@@ -32,6 +32,16 @@ class sspmod_saml_IdP_SAML2 {
 		$relayState = $state['saml:RelayState'];
 		$consumerURL = $state['saml:ConsumerURL'];
 
+		if (isset($state['saml:Binding'])) {
+			$protocolBinding = $state['saml:Binding'];
+		} else {
+			/*
+			 * To allow for upgrading while people are logging in.
+			 * Should be removed in 1.7.
+			 */
+			$protocolBinding = SAML2_Const::BINDING_HTTP_POST;
+		}
+
 		$idp = SimpleSAML_IdP::getByState($state);
 
 		$idpMetadata = $idp->getConfig();
@@ -56,7 +66,7 @@ class sspmod_saml_IdP_SAML2 {
 		$session->setSessionNameId('saml20-sp-remote', $spEntityId, $nameId);
 
 		/* Send the response. */
-		$binding = new SAML2_HTTPPost();
+		$binding = SAML2_Binding::getBinding($protocolBinding);
 		$binding->setDestination(sspmod_SAML2_Message::getDebugDestination());
 		$binding->send($ar);
 	}
@@ -83,6 +93,16 @@ class sspmod_saml_IdP_SAML2 {
 		$relayState = $state['saml:RelayState'];
 		$consumerURL = $state['saml:ConsumerURL'];
 
+		if (isset($state['saml:Binding'])) {
+			$protocolBinding = $state['saml:Binding'];
+		} else {
+			/*
+			 * To allow for upgrading while people are logging in.
+			 * Should be removed in 1.7.
+			 */
+			$protocolBinding = SAML2_Const::BINDING_HTTP_POST;
+		}
+
 		$idp = SimpleSAML_IdP::getByState($state);
 
 		$idpMetadata = $idp->getConfig();
@@ -102,7 +122,7 @@ class sspmod_saml_IdP_SAML2 {
 			'Message' => $error->getStatusMessage(),
 		));
 
-		$binding = new SAML2_HTTPPost();
+		$binding = SAML2_Binding::getBinding($protocolBinding);
 		$binding->setDestination(sspmod_SAML2_Message::getDebugDestination());
 		$binding->send($ar);
 	}
@@ -118,6 +138,11 @@ class sspmod_saml_IdP_SAML2 {
 		$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
 		$idpMetadata = $idp->getConfig();
 
+		$supportedBindings = array(SAML2_Const::BINDING_HTTP_POST);
+		if ($idpMetadata->getBoolean('saml20.sendartifact', FALSE)) {
+			$supportedBindings[] = SAML2_Const::BINDING_HTTP_ARTIFACT;
+		}
+
 		if (isset($_REQUEST['spentityid'])) {
 			/* IdP initiated authentication. */
 
@@ -141,6 +166,11 @@ class sspmod_saml_IdP_SAML2 {
 				$relayState = NULL;
 			}
 
+			if (isset($_REQUEST['binding'])){
+				$protocolBinding = (string)$_REQUEST['binding'];
+			} else {
+				$protocolBinding = NULL;
+			}
 			$requestId = NULL;
 			$IDPList = array();
 			$forceAuthn = FALSE;
@@ -171,6 +201,7 @@ class sspmod_saml_IdP_SAML2 {
 			$requestId = $requestCache['RequestID'];
 			$forceAuthn = $requestCache['ForceAuthn'];
 			$isPassive = $requestCache['IsPassive'];
+			$protocolBinding = SAML2_Const::BINDING_HTTP_POST; /* HTTP-POST was the only supported binding before 1.6. */
 
 			if (isset($requestCache['IDPList'])) {
 				$IDPList = $requestCache['IDPList'];
@@ -208,15 +239,23 @@ class sspmod_saml_IdP_SAML2 {
 			$forceAuthn = $request->getForceAuthn();
 			$isPassive = $request->getIsPassive();
 			$consumerURL = $request->getAssertionConsumerServiceURL();
+			$protocolBinding = $request->getProtocolBinding();
 
 			SimpleSAML_Logger::info('SAML2.0 - IdP.SSOService: Incomming Authentication request: '. var_export($spEntityId, TRUE));
 		}
 
+		if ($protocolBinding === NULL || !in_array($protocolBinding, $supportedBindings, TRUE)) {
+			/*
+			 * No binding specified or unsupported binding requested - default to HTTP-POST.
+			 * TODO: Select any supported binding based on default endpoint?
+			 */
+			$protocolBinding = SAML2_Const::BINDING_HTTP_POST;
+		}
 
 		if ($consumerURL !== NULL) {
 			$found = FALSE;
 			foreach ($spMetadata->getEndpoints('AssertionConsumerService') as $ep) {
-				if ($ep['Binding'] !== SAML2_Const::BINDING_HTTP_POST) {
+				if ($ep['Binding'] !== $protocolBinding) {
 					continue;
 				}
 				if ($ep['Location'] !== $consumerURL) {
@@ -235,7 +274,7 @@ class sspmod_saml_IdP_SAML2 {
 		}
 		if ($consumerURL === NULL) {
 			/* Not specified or invalid. Use default. */
-			$consumerURL = $spMetadata->getDefaultEndpoint('AssertionConsumerService', array(SAML2_Const::BINDING_HTTP_POST));
+			$consumerURL = $spMetadata->getDefaultEndpoint('AssertionConsumerService', array($protocolBinding));
 			$consumerURL = $consumerURL['Location'];
 		}
 
@@ -248,7 +287,7 @@ class sspmod_saml_IdP_SAML2 {
 		$sessionLostParams = array(
 			'spentityid' => $spEntityId,
 			'cookieTime' => time(),
-			);
+		);
 		if ($relayState !== NULL) {
 			$sessionLostParams['RelayState'] = $relayState;
 		}
@@ -269,6 +308,7 @@ class sspmod_saml_IdP_SAML2 {
 			'ForceAuthn' => $forceAuthn,
 			'isPassive' => $isPassive,
 			'saml:ConsumerURL' => $consumerURL,
+			'saml:Binding' => $protocolBinding,
 		);
 
 		$idp->handleAuthenticationRequest($state);
diff --git a/www/saml2/idp/ArtifactResolutionService.php b/www/saml2/idp/ArtifactResolutionService.php
new file mode 100644
index 000000000..b1f10d8c6
--- /dev/null
+++ b/www/saml2/idp/ArtifactResolutionService.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * The ArtifactResolutionService receives the samlart from the sp.
+ * And when the artifact is found, it sends a SAML2_ArtifactResponse.
+ *
+ * @author Danny Bollaert, UGent AS. <danny.bollaert@ugent.be>
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+
+require_once('../../_include.php');
+
+$config = SimpleSAML_Configuration::getInstance();
+if (!$config->getBoolean('enable.saml20-idp', FALSE)) {
+	throw new SimpleSAML_Error_Error('NOACCESS');
+}
+
+$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
+$idpEntityId = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted');
+$idpMetadata = $metadata->getMetaDataConfig($idpEntityId, 'saml20-idp-hosted');
+
+if (!$idpMetadata->getBoolean('saml20.sendartifact', FALSE)) {
+	throw new SimpleSAML_Error_Error('NOACCESS');
+}
+
+$binding = new SAML2_SOAP();
+$request = $binding->receive();
+if (!($request instanceof SAML2_ArtifactResolve)) {
+	throw new Exception('Message received on ArtifactResolutionService wasn\'t a ArtifactResolve request.');
+}
+$artifact = $request->getArtifact();
+$responseData = SimpleSAML_Memcache::get('artifact:' . $artifact);
+$document = new DOMDocument();
+$document->loadXML($responseData);
+$responseXML = $document->firstChild;
+$artifactResponse = new SAML2_ArtifactResponse();
+
+$artifactResponse->setIssuer($idpEntityId);
+$artifactResponse->setInResponseTo($request->getId());
+$artifactResponse->setAny($responseXML);
+$binding->send($artifactResponse);
diff --git a/www/saml2/idp/metadata.php b/www/saml2/idp/metadata.php
index 3f172bd67..9c8dba5ee 100644
--- a/www/saml2/idp/metadata.php
+++ b/www/saml2/idp/metadata.php
@@ -27,10 +27,10 @@ try {
 		/* Only one valid certificate. */
 		$certFingerprint = $certFingerprint[0];
 	}
-	
+
 	$logouttype = 'traditional';
 	if (array_key_exists('logouttype', $idpmeta)) $logouttype = $idpmeta['logouttype'];
-	
+
 	$urlSLO = $metadata->getGenerated('SingleLogoutService', 'saml20-idp-hosted', array('logouttype' => $logouttype));
 	$urlSLOr = $metadata->getGenerated('SingleLogoutServiceResponse', 'saml20-idp-hosted', array('logouttype' => $logouttype));
 
@@ -47,6 +47,15 @@ try {
 		unset($metaArray['SingleLogoutServiceResponse']);
 	}
 
+	if (isset($idpmeta['saml20.sendartifact']) && $idpmeta['saml20.sendartifact'] === TRUE) {
+		/* Artifact sending enabled. */
+		$metaArray['ArtifactResolutionService'][] = array(
+			'index' => 0,
+			'Location' => SimpleSAML_Utilities::getBaseURL() . 'saml2/idp/ArtifactResolutionService.php',
+			'Binding' => SAML2_Const::BINDING_SOAP,
+		);
+	}
+
 	if (array_key_exists('NameIDFormat', $idpmeta)) {
 		$metaArray['NameIDFormat'] = $idpmeta['NameIDFormat'];
 	} else {
@@ -74,7 +83,7 @@ try {
 	$metaBuilder->addContact('technical', array(
 		'emailAddress' => $config->getString('technicalcontact_email', NULL),
 		'name' => $config->getString('technicalcontact_name', NULL),
-		));
+	));
 	$metaxml = $metaBuilder->getEntityDescriptorText();
 
 	/* Sign the metadata if enabled. */
@@ -82,30 +91,30 @@ try {
 
 	if (array_key_exists('output', $_GET) && $_GET['output'] == 'xhtml') {
 		$defaultidp = $config->getString('default-saml20-idp', NULL);
-		
+
 		$t = new SimpleSAML_XHTML_Template($config, 'metadata.php', 'admin');
-		
-	
+
+
 		$t->data['header'] = 'saml20-idp';
 		$t->data['metaurl'] = SimpleSAML_Utilities::selfURLNoQuery();
 		$t->data['metadata'] = htmlentities($metaxml);
 		$t->data['metadataflat'] = htmlentities($metaflat);
 		$t->data['defaultidp'] = $defaultidp;
 		$t->show();
-			
+
 	} else {
-	
+
 		header('Content-Type: application/xml');
-		
+
 		echo $metaxml;
 		exit(0);
 
 	}
 
 
-	
+
 } catch(Exception $exception) {
-	
+
 	SimpleSAML_Utilities::fatalError($session->getTrackID(), 'METADATA', $exception);
 
 }
-- 
GitLab