From 132a0dd2bfbdd8646161c68d244c504eadbc14a2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaime=20P=C3=A9rez=20Crespo?= <jaime.perez@uninett.no>
Date: Tue, 26 Feb 2013 11:24:51 +0000
Subject: [PATCH] Support for UIInfo elements in SP metadata. Bugfix with SP
 metadata signing.

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@3226 44740490-163a-0410-bde0-09ae8108e29a
---
 lib/SimpleSAML/Metadata/SAMLBuilder.php |  11 +-
 modules/saml/www/sp/metadata.php        | 162 ++++++++++--------------
 2 files changed, 75 insertions(+), 98 deletions(-)

diff --git a/lib/SimpleSAML/Metadata/SAMLBuilder.php b/lib/SimpleSAML/Metadata/SAMLBuilder.php
index 5d21c78cc..923864807 100644
--- a/lib/SimpleSAML/Metadata/SAMLBuilder.php
+++ b/lib/SimpleSAML/Metadata/SAMLBuilder.php
@@ -318,6 +318,8 @@ class SimpleSAML_Metadata_SAMLBuilder {
 			return;
 		}
 
+		$attributesrequired = $metadata->getArray('attributes.required', array());
+
 		/*
 		 * Add an AttributeConsumingService element with information as name and description and list
 		 * of requested attributes
@@ -336,6 +338,9 @@ class SimpleSAML_Metadata_SAMLBuilder {
 			if ($nameFormat !== SAML2_Const::NAMEFORMAT_UNSPECIFIED) {
 				$t->NameFormat = $nameFormat;
 			}
+			if (in_array($attribute, $attributesrequired)) {
+				$t->isRequired = true;
+			}
 			$attributeconsumer->RequestedAttribute[] = $t;
 		}
 
@@ -383,16 +388,18 @@ class SimpleSAML_Metadata_SAMLBuilder {
 	 * Add SAML 2.0 SP metadata.
 	 *
 	 * @param array $metadata  The metadata.
+	 * @param array $protocols The protocols supported.
 	 */
-	public function addMetadataSP20($metadata) {
+	public function addMetadataSP20($metadata, $protocols = array(SAML2_Const::NS_SAMLP)) {
 		assert('is_array($metadata)');
+		assert('is_array($protocols)');
 		assert('isset($metadata["entityid"])');
 		assert('isset($metadata["metadata-set"])');
 
 		$metadata = SimpleSAML_Configuration::loadFromArray($metadata, $metadata['entityid']);
 
 		$e = new SAML2_XML_md_SPSSODescriptor();
-		$e->protocolSupportEnumeration[] = 'urn:oasis:names:tc:SAML:2.0:protocol';
+		$e->protocolSupportEnumeration = $protocols;
 
 
 		$this->addExtensions($metadata, $e);
diff --git a/modules/saml/www/sp/metadata.php b/modules/saml/www/sp/metadata.php
index 34dacffb8..b6e4681da 100644
--- a/modules/saml/www/sp/metadata.php
+++ b/modules/saml/www/sp/metadata.php
@@ -19,32 +19,22 @@ $entityId = $source->getEntityId();
 $spconfig = $source->getMetadata();
 
 $metaArray20 = array(
-	'AssertionConsumerService' => SimpleSAML_Module::getModuleURL('saml/sp/saml2-acs.php/' . $sourceId),
 	'SingleLogoutService' => SimpleSAML_Module::getModuleURL('saml/sp/saml2-logout.php/' . $sourceId),
 );
 
-$ed = new SAML2_XML_md_EntityDescriptor();
-$ed->entityID = $entityId;
-
-$sp = new SAML2_XML_md_SPSSODescriptor();
-$ed->RoleDescriptor[] = $sp;
-$sp->protocolSupportEnumeration = array(
-	'urn:oasis:names:tc:SAML:1.1:protocol',
-	'urn:oasis:names:tc:SAML:2.0:protocol'
-);
-
-$slo = new SAML2_XML_md_EndpointType();
-$slo->Binding = SAML2_Const::BINDING_HTTP_REDIRECT;
-$slo->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml2-logout.php/' . $sourceId);
-$sp->SingleLogoutService[] = $slo;
-
 $store = SimpleSAML_Store::getInstance();
 if ($store instanceof SimpleSAML_Store_SQL) {
 	/* We can properly support SOAP logout. */
-	$slo = new SAML2_XML_md_EndpointType();
-	$slo->Binding = SAML2_Const::BINDING_SOAP;
-	$slo->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml2-logout.php/' . $sourceId);
-	$sp->SingleLogoutService[] = $slo;
+	$metaArray20['SingleLogoutService'] = array(
+		array(
+			'Binding' => SAML2_Const::BINDING_HTTP_REDIRECT,
+			'Location' => SimpleSAML_Module::getModuleURL('saml/sp/saml2-logout.php/' . $sourceId),
+		),
+		array(
+			'Binding' => SAML2_Const::BINDING_SOAP,
+			'Location' => SimpleSAML_Module::getModuleURL('saml/sp/saml2-logout.php/' . $sourceId),
+		),
+	);
 }
 
 $assertionsconsumerservicesdefault = array(
@@ -54,44 +44,45 @@ $assertionsconsumerservicesdefault = array(
 	'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01',
 );
 
-if ($spconfig->getBoolean('saml20.hok.assertion', FALSE)) {
+if ($spconfig->getString('ProtocolBinding', '') == 'urn:oasis:names:tc:SAML:2.0:profiles:holder-of-key:SSO:browser') {
 	$assertionsconsumerservicesdefault[] = 	'urn:oasis:names:tc:SAML:2.0:profiles:holder-of-key:SSO:browser';
 }
 
 $assertionsconsumerservices = $spconfig->getArray('acs.Bindings', $assertionsconsumerservicesdefault);
 
 $index = 0;
+$eps = array();
 foreach ($assertionsconsumerservices as $services) {
 
-	$acs = new SAML2_XML_md_IndexedEndpointType();
-	$acs->index = $index;
+	$acsArray = array('index' => $index);
 	switch ($services) {
 	case 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST':
-		$acs->Binding = SAML2_Const::BINDING_HTTP_POST;
-		$acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml2-acs.php/' . $sourceId);
+		$acsArray['Binding'] = SAML2_Const::BINDING_HTTP_POST;
+		$acsArray['Location'] = SimpleSAML_Module::getModuleURL('saml/sp/saml2-acs.php/' . $sourceId);
 		break;
 	case 'urn:oasis:names:tc:SAML:1.0:profiles:browser-post':
-		$acs->Binding = 'urn:oasis:names:tc:SAML:1.0:profiles:browser-post';
-		$acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml1-acs.php/' . $sourceId);
+		$acsArray['Binding'] = 'urn:oasis:names:tc:SAML:1.0:profiles:browser-post';
+		$acsArray['Location'] = SimpleSAML_Module::getModuleURL('saml/sp/saml1-acs.php/' . $sourceId);
 		break;
 	case 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact':
-		$acs->Binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact';
-		$acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml2-acs.php/' . $sourceId);
+		$acsArray['Binding'] = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact';
+		$acsArray['Location'] = SimpleSAML_Module::getModuleURL('saml/sp/saml2-acs.php/' . $sourceId);
 		break;
 	case 'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01':
-		$acs->Binding = 'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01';
-		$acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml1-acs.php/' . $sourceId . '/artifact');
+		$acsArray['Binding'] = 'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01';
+		$acsArray['Location'] = SimpleSAML_Module::getModuleURL('saml/sp/saml1-acs.php/' . $sourceId . '/artifact');
 		break;
 	case 'urn:oasis:names:tc:SAML:2.0:profiles:holder-of-key:SSO:browser':
-		$acs->Binding = 'urn:oasis:names:tc:SAML:2.0:profiles:holder-of-key:SSO:browser';
-		$acs->setAttributeNS(SAML2_Const::NS_HOK, 'hoksso:ProtocolBinding', SAML2_Const::BINDING_HTTP_POST);
-		$acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml2-acs.php/' . $sourceId);
+		$acsArray['Binding'] = 'urn:oasis:names:tc:SAML:2.0:profiles:holder-of-key:SSO:browser';
+		$acsArray['Location'] = SimpleSAML_Module::getModuleURL('saml/sp/saml2-acs.php/' . $sourceId);
+		$acsArray['hoksso:ProtocolBinding'] = SAML2_Const::BINDING_HTTP_REDIRECT;
 		break;
 	}
-	$sp->AssertionConsumerService[] = $acs;
+	$eps[] = $acsArray;
 	$index++;
 }
 
+$metaArray20['AssertionConsumerService'] = $eps;
 
 $keys = array();
 $certInfo = SimpleSAML_Utilities::loadPublicKey($spconfig, FALSE, 'new_');
@@ -99,13 +90,6 @@ if ($certInfo !== NULL && array_key_exists('certData', $certInfo)) {
 	$hasNewCert = TRUE;
 
 	$certData = $certInfo['certData'];
-	$kd = SAML2_Utils::createKeyDescriptor($certData);
-	$kd->use = 'signing';
-	$sp->KeyDescriptor[] = $kd;
-
-	$kd = SAML2_Utils::createKeyDescriptor($certData);
-	$kd->use = 'encryption';
-	$sp->KeyDescriptor[] = $kd;
 
 	$keys[] = array(
 		'type' => 'X509Certificate',
@@ -120,16 +104,6 @@ if ($certInfo !== NULL && array_key_exists('certData', $certInfo)) {
 $certInfo = SimpleSAML_Utilities::loadPublicKey($spconfig);
 if ($certInfo !== NULL && array_key_exists('certData', $certInfo)) {
 	$certData = $certInfo['certData'];
-	$kd = SAML2_Utils::createKeyDescriptor($certData);
-	$kd->use = 'signing';
-	$sp->KeyDescriptor[] = $kd;
-
-	if (!$hasNewCert) {
-		/* Don't include the old certificate for encryption when we have a newer certificate. */
-		$kd = SAML2_Utils::createKeyDescriptor($certData);
-		$kd->use = 'encryption';
-		$sp->KeyDescriptor[] = $kd;
-	}
 
 	$keys[] = array(
 		'type' => 'X509Certificate',
@@ -148,18 +122,6 @@ if ($name !== NULL && !empty($attributes)) {
 
 	$attributesrequired = $spconfig->getArray('attributes.required', array());
 
-	/* We have everything necessary to add an AttributeConsumingService. */
-	$acs = new SAML2_XML_md_AttributeConsumingService();
-	$sp->AttributeConsumingService[] = $acs;
-
-	$acs->index = 0;
-	$acs->ServiceName = $name;
-
-	$description = $spconfig->getLocalizedString('description', NULL);
-	if ($description !== NULL) {
-		$acs->ServiceDescription = $description;
-	}
-
 	$nameFormat = $spconfig->getString('attributes.NameFormat', NULL);
 	foreach ($attributes as $attribute) {
 		$a = new SAML2_XML_md_RequestedAttribute();
@@ -169,9 +131,10 @@ if ($name !== NULL && !empty($attributes)) {
 		if (in_array($attribute, $attributesrequired))
 			$a->isRequired = true;
 
-		$acs->RequestedAttribute[] = $a;
 	}
 
+	$metaArray20['attributes.required'] = $attributesrequired;
+
 	$metaArray20['name'] = $name;
 	if ($description !== NULL) {
 		$metaArray20['description'] = $description;
@@ -183,63 +146,70 @@ if ($name !== NULL && !empty($attributes)) {
 	}
 }
 
-
+// add organization info
 $orgName = $spconfig->getLocalizedString('OrganizationName', NULL);
 if ($orgName !== NULL) {
-	$o = new SAML2_XML_md_Organization();
-	$o->OrganizationName = $orgName;
+	$metaArray20['OrganizationName'] = $orgName;
 
-	$o->OrganizationDisplayName = $spconfig->getLocalizedString('OrganizationDisplayName', NULL);
-	if ($o->OrganizationDisplayName === NULL) {
-		$o->OrganizationDisplayName = $orgName;
+	$metaArray20['OrganizationDisplayName'] = $spconfig->getLocalizedString('OrganizationDisplayName', NULL);
+	if ($metaArray20['OrganizationDisplayName'] === NULL) {
+		$metaArray20['OrganizationDisplayName'] = $orgName;
 	}
 
-	$o->OrganizationURL = $spconfig->getLocalizedString('OrganizationURL', NULL);
-	if ($o->OrganizationURL === NULL) {
+	$metaArray20['OrganizationURL'] = $spconfig->getLocalizedString('OrganizationURL', NULL);
+	if ($metaArray20['OrganizationURL'] === NULL) {
 		throw new SimpleSAML_Error_Exception('If OrganizationName is set, OrganizationURL must also be set.');
 	}
-
-	$ed->Organization = $o;
-
-	$metaArray20['OrganizationName'] = $orgName;
-	$metaArray20['OrganizationDisplayName'] = $o->OrganizationDisplayName;
-	$metaArray20['OrganizationURL'] = $o->OrganizationURL;
 }
 
-$c = new SAML2_XML_md_ContactPerson();
-$c->contactType = 'technical';
-
+// add technical contact
 $email = $config->getString('technicalcontact_email', NULL);
-if ($email !== NULL) {
-	$c->EmailAddress = array($email);
-}
+$contact = array('emailAddress' => $email);
 
 $name = $config->getString('technicalcontact_name', NULL);
 if ($name === NULL) {
 	/* Nothing to do here... */
 } elseif (preg_match('@^(.*?)\s*,\s*(.*)$@D', $name, $matches)) {
-	$c->SurName = $matches[1];
-	$c->GivenName = $matches[2];
+	$contact['surName'] = $matches[1];
+	$contact['givenName'] = $matches[2];
 } elseif (preg_match('@^(.*?)\s+(.*)$@D', $name, $matches)) {
-	$c->GivenName = $matches[1];
-	$c->SurName = $matches[2];
+	$contact['givenName'] = $matches[1];
+	$contact['surName'] = $matches[2];
 } else {
-	$c->GivenName = $name;
+	$contact['givenName'] = $name;
 }
-$ed->ContactPerson[] = $c;
-
-$xml = $ed->toXML();
-SimpleSAML_Utilities::formatDOMElement($xml);
-$xml = $xml->ownerDocument->saveXML($xml);
 
+// add certificate
 if (count($keys) === 1) {
 	$metaArray20['certData'] = $keys[0]['X509Certificate'];
 } elseif (count($keys) > 1) {
 	$metaArray20['keys'] = $keys;
 }
 
+// add UIInfo extension
+if ($spconfig->hasValue('UIInfo')) {
+	$metaArray20['UIInfo'] = $spconfig->getArray('UIInfo');
+}
+
+$supported_protocols = array('urn:oasis:names:tc:SAML:1.1:protocol', SAML2_Const::NS_SAMLP);
+
+$metaArray20['metadata-set'] = 'saml20-sp-remote';
+$metaArray20['entityid'] = $entityId;
+
+$metaBuilder = new SimpleSAML_Metadata_SAMLBuilder($entityId);
+$metaBuilder->addMetadataSP20($metaArray20, $supported_protocols);
+$metaBuilder->addOrganizationInfo($metaArray20);
+$metaBuilder->addContact('technical', $contact);
+
+$xml = $metaBuilder->getEntityDescriptorText();
+
+unset($metaArray20['attributes.required']);
+unset($metaArray20['UIInfo']);
+unset($metaArray20['metadata-set']);
+unset($metaArray20['entityid']);
+
 /* Sign the metadata if enabled. */
-$xml = SimpleSAML_Metadata_Signer::sign($xml, $sp, 'SAML 2 SP');
+$xml = SimpleSAML_Metadata_Signer::sign($xml, $spconfig->toArray(), 'SAML 2 SP');
 
 if (array_key_exists('output', $_REQUEST) && $_REQUEST['output'] == 'xhtml') {
 
-- 
GitLab