From 7bea57bbdb4a6d6b3de3f40a3401384c3e36d8a3 Mon Sep 17 00:00:00 2001
From: Olav Morken <olav.morken@uninett.no>
Date: Tue, 15 May 2012 09:39:12 +0000
Subject: [PATCH] saml:SP: Add support for specifying bindings and required
 attributes in metadata.

This patch adds two enhancements to metadata generation:
 * Support for specifying attributes that are required.
 * Support for removing some of the AssertionConsumerService bindings.

Thanks to Benjamin Andresen for implementing this!

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@3092 44740490-163a-0410-bde0-09ae8108e29a
---
 modules/saml/docs/sp.txt         | 46 +++++++++++++++++
 modules/saml/www/sp/metadata.php | 85 +++++++++++++++++++-------------
 2 files changed, 97 insertions(+), 34 deletions(-)

diff --git a/modules/saml/docs/sp.txt b/modules/saml/docs/sp.txt
index 5e5c66a0c..673d69ca9 100644
--- a/modules/saml/docs/sp.txt
+++ b/modules/saml/docs/sp.txt
@@ -105,6 +105,38 @@ Here we will list some examples for this authentication source.
     ),
 
 
+### Specifying attributes and required attributes
+
+    An SP that wants eduPersonPrincipalName and mail, where eduPersonPrincipalName should be listed as required:
+
+    'example-attributes => array(
+        'saml:SP',
+        'name' => array( //Name required for AttributeConsumingService-element.
+            'en' => 'Example service',
+            'no' => 'Eksempeltjeneste',
+        ),
+        'attributes' => array(
+            'eduPersonPrincipalName',
+            'mail',
+        )
+        'attributes.required' => array (
+            'eduPersonPrincipalName',
+        ),
+        'attributes.NameFormat' => 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
+    ),
+
+
+### Limiting supported AssertionConsumerService endpoint bindings
+
+    'example-acs-limit' => array(
+        'saml:SP',
+        'acs.Bindings' => array(
+            'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
+            'urn:oasis:names:tc:SAML:1.0:profiles:browser-post',
+        ),
+    ),
+
+
 ### Requesting a specific authentication method.
 
     $auth = new SimpleSAML_Auth_Simple('default-sp');
@@ -127,6 +159,16 @@ Here we will list some examples for this authentication source.
 Options
 -------
 
+`acs.Bindings`
+: List of bindings the SP should support. If it is unset, all will be added.
+: Possible values:
+
+    * `urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST`
+    * `urn:oasis:names:tc:SAML:1.0:profiles:browser-post`
+    * `urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact`
+    * `urn:oasis:names:tc:SAML:1.0:profiles:artifact-01`
+    * `urn:oasis:names:tc:SAML:2.0:profiles:holder-of-key:SSO:browser`
+
 `assertion.encryption`
 :   Whether assertions received by this SP must be encrypted. The default value is `FALSE`.
     If this option is set to `TRUE`, unencrypted assertions will be rejected.
@@ -147,6 +189,10 @@ Options
 `attributes.NameFormat`
 :   The `NameFormat` for the requested attributes.
 
+`attributes.required`
+: If you have attributes added you can here specify which should be marked as required.
+: The attributes should still be present in `attributes`.
+
 `AuthnContextClassRef`
 :   The SP can request authentication with a specific authentication context class.
     One example of usage could be if the IdP supports both username/password authentication as well as software-PKI.
diff --git a/modules/saml/www/sp/metadata.php b/modules/saml/www/sp/metadata.php
index b19a39219..25e9d7fd2 100644
--- a/modules/saml/www/sp/metadata.php
+++ b/modules/saml/www/sp/metadata.php
@@ -1,6 +1,5 @@
 <?php
 
-
 if (!array_key_exists('PATH_INFO', $_SERVER)) {
 	throw new SimpleSAML_Error_BadRequest('Missing authentication source id in metadata URL');
 }
@@ -48,36 +47,48 @@ if ($store instanceof SimpleSAML_Store_SQL) {
 	$sp->SingleLogoutService[] = $slo;
 }
 
-$acs = new SAML2_XML_md_IndexedEndpointType();
-$acs->index = 0;
-$acs->Binding = SAML2_Const::BINDING_HTTP_POST;
-$acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml2-acs.php/' . $sourceId);
-$sp->AssertionConsumerService[] = $acs;
-
-$acs = new SAML2_XML_md_IndexedEndpointType();
-$acs->index = 1;
-$acs->Binding = 'urn:oasis:names:tc:SAML:1.0:profiles:browser-post';
-$acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml1-acs.php/' . $sourceId);
-$sp->AssertionConsumerService[] = $acs;
-
-$acs = new SAML2_XML_md_IndexedEndpointType();
-$acs->index = 2;
-$acs->Binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact';
-$acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml2-acs.php/' . $sourceId);
-$sp->AssertionConsumerService[] = $acs;
-
-$acs = new SAML2_XML_md_IndexedEndpointType();
-$acs->index = 3;
-$acs->Binding = 'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01';
-$acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml1-acs.php/' . $sourceId . '/artifact');
-$sp->AssertionConsumerService[] = $acs;
-
-$acs = new SAML2_XML_md_IndexedEndpointType();
-$acs->index = 4;
-$acs->Binding = 'urn:oasis:names:tc:SAML:2.0:profiles:holder-of-key:SSO:browser';
-$acs->ProtocolBinding = SAML2_Const::BINDING_HTTP_POST;
-$acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml2-acs.php/' . $sourceId);
-$sp->AssertionConsumerService[] = $acs;
+$assertionsconsumerservicesdefault = array(
+	'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
+	'urn:oasis:names:tc:SAML:1.0:profiles:browser-post',
+	'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact',
+	'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01',
+	'urn:oasis:names:tc:SAML:2.0:profiles:holder-of-key:SSO:browser',
+);
+
+$assertionsconsumerservices = $spconfig->getArray('acs.Bindings', $assertionsconsumerservicesdefault);
+
+$index = 0;
+foreach ($assertionsconsumerservices as $services) {
+
+	$acs = new SAML2_XML_md_IndexedEndpointType();
+	$acs->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);
+		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);
+		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);
+		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');
+		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->ProtocolBinding = SAML2_Const::BINDING_HTTP_POST;
+		$acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml2-acs.php/' . $sourceId);
+		break;
+	}
+	$sp->AssertionConsumerService[] = $acs;
+	$index++;
+}
+
 
 $keys = array();
 $certInfo = SimpleSAML_Utilities::loadPublicKey($spconfig, FALSE, 'new_');
@@ -99,7 +110,6 @@ if ($certInfo !== NULL && array_key_exists('certData', $certInfo)) {
 		'encryption' => TRUE,
 		'X509Certificate' => $certInfo['certData'],
 	);
-
 } else {
 	$hasNewCert = FALSE;
 }
@@ -124,14 +134,17 @@ if ($certInfo !== NULL && array_key_exists('certData', $certInfo)) {
 		'encryption' => ($hasNewCert ? FALSE : TRUE),
 		'X509Certificate' => $certInfo['certData'],
 	);
-
 } else {
 	$certData = NULL;
 }
 
 $name = $spconfig->getLocalizedString('name', NULL);
 $attributes = $spconfig->getArray('attributes', array());
+
 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;
@@ -149,6 +162,10 @@ if ($name !== NULL && !empty($attributes)) {
 		$a = new SAML2_XML_md_RequestedAttribute();
 		$a->Name = $attribute;
 		$a->NameFormat = $nameFormat;
+		// Is the attribute required
+		if (in_array($attribute, $attributesrequired))
+			$a->isRequired = true;
+
 		$acs->RequestedAttribute[] = $a;
 	}
 
@@ -163,6 +180,7 @@ if ($name !== NULL && !empty($attributes)) {
 	}
 }
 
+
 $orgName = $spconfig->getLocalizedString('OrganizationName', NULL);
 if ($orgName !== NULL) {
 	$o = new SAML2_XML_md_Organization();
@@ -233,5 +251,4 @@ if (array_key_exists('output', $_REQUEST) && $_REQUEST['output'] == 'xhtml') {
 	header('Content-Type: application/samlmetadata+xml');
 	echo($xml);
 }
-
 ?>
-- 
GitLab