diff --git a/docs/source/simplesamlphp-idp.xml b/docs/source/simplesamlphp-idp.xml index 2dd43f8611f0ae679d63fa151dd271f45771cf81..916c175c604d0fea342154b9ad7ee7e6b3035ca9 100644 --- a/docs/source/simplesamlphp-idp.xml +++ b/docs/source/simplesamlphp-idp.xml @@ -893,6 +893,28 @@ openssl x509 -req -days 60 -in server2.csr -signkey server2.key -out server2.crt trusted SPs using the Shibboleth 1.3 protocol. This configuration is very similar to configuring SAML 2.0 metadata; please find information in the previous chapter.</para> + + <section> + <title>Scoped attributes</title> + + <para>It is possible to configure some attributes to be scoped, and + include the Scope-attribute on the attribute values. This is enabled + by setting the <literal>scopedattributes</literal>-option in either + <filename>shib13-sp-remote.php</filename> or + <filename>shib13-idp-hosted.php</filename>. If both are defined, the SP + configuration is used.</para> + + <para>This option is an array of the names of attributes which should be + treated as scoped. The matching is case-sensitive. A scoped attribute + will be split on the first <literal>@</literal>-sign, and the first part + will be used as the attribute value, while the last part will be used as + the scope.</para> + + <programlisting>/* + * Example of scopedattributes option. Will use scoped attribute for eduPersonPrincipalName. + */ +'scopedattributes' => array('eduPersonPrincipalName'),</programlisting> + </section> </section> <section> diff --git a/lib/SimpleSAML/XML/Shib13/AuthnResponse.php b/lib/SimpleSAML/XML/Shib13/AuthnResponse.php index c0bb13a1d120e8ec11ce088e476aa099012b9f3e..b3612f0e88564c28d9b51f219ae49fe8ff61cc81 100644 --- a/lib/SimpleSAML/XML/Shib13/AuthnResponse.php +++ b/lib/SimpleSAML/XML/Shib13/AuthnResponse.php @@ -168,6 +168,12 @@ class SimpleSAML_XML_Shib13_AuthnResponse extends SimpleSAML_XML_AuthnResponse { $value = $attribute->textContent; $name = $attribute->parentNode->getAttribute('AttributeName'); + if ($attribute->hasAttribute('Scope')) { + $scopePart = '@' . $attribute->getAttribute('Scope'); + } else { + $scopePart = ''; + } + if(!is_string($name)) { throw new Exception('Shib13 Attribute node without an AttributeName.'); } @@ -179,10 +185,10 @@ class SimpleSAML_XML_Shib13_AuthnResponse extends SimpleSAML_XML_AuthnResponse { if ($base64) { $encodedvalues = explode('_', $value); foreach($encodedvalues AS $v) { - $attributes[$name][] = base64_decode($v); + $attributes[$name][] = base64_decode($v) . $scopePart; } } else { - $attributes[$name][] = $value; + $attributes[$name][] = $value . $scopePart; } } } @@ -238,6 +244,27 @@ class SimpleSAML_XML_Shib13_AuthnResponse extends SimpleSAML_XML_AuthnResponse { $idpmd = $this->metadata->getMetaData($idpentityid, 'shib13-idp-hosted'); $spmd = $this->metadata->getMetaData($spentityid, 'shib13-sp-remote'); + + if (array_key_exists('scopedattributes', $spmd)) { + $scopedAttributes = $spmd['scopedattributes']; + $scopedAttributesSource = 'the shib13-sp-remote sp \'' . $spentityid . '\''; + } elseif (array_key_exists('scopedattributes', $idpmd)) { + $scopedAttributes = $idpmd['scopedattributes']; + $scopedAttributesSource = 'the shib13-idp-hosted idp \'' . $idpentityid . '\''; + } else { + $scopedAttributes = array(); + } + if (!is_array($scopedAttributes)) { + throw new Exception('The \'scopedattributes\' option in ' . $scopedAttributesSource . + ' should be an array of attribute names.'); + } + foreach ($scopedAttributes as $an) { + if (!is_string($an)) { + throw new Exception('Invalid attribute name in the \'scopedattributes\' option in ' . + $scopedAttributesSource . ': ' . var_export($an, TRUE)); + } + } + $id = SimpleSAML_Utilities::generateID(); $issueInstant = SimpleSAML_Utilities::generateTimestamp(); @@ -271,7 +298,7 @@ class SimpleSAML_XML_Shib13_AuthnResponse extends SimpleSAML_XML_AuthnResponse { </Subject>'; foreach ($attributes AS $name => $value) { - $encodedattributes .= $this->enc_attribute($name, $value, $base64); + $encodedattributes .= $this->enc_attribute($name, $value, $base64, $scopedAttributes); } $encodedattributes .= '</AttributeStatement>'; @@ -321,11 +348,44 @@ class SimpleSAML_XML_Shib13_AuthnResponse extends SimpleSAML_XML_AuthnResponse { + /** + * Format a shib13 attribute. + * + * @param string $name Name of the attribute. + * @param array $values Values of the attribute (as an array of strings). + * @param bool $base64 Whether the attriubte values should be base64-encoded. + * @param array $scopedAttributes Array of attributes names which are scoped. + * @return string The attribute encoded as an XML-string. + */ + private function enc_attribute($name, $values, $base64, $scopedAttributes) { + assert('is_string($name)'); + assert('is_array($values)'); + assert('is_bool($base64)'); + assert('is_array($scopedAttributes)'); + + if (in_array($name, $scopedAttributes, TRUE)) { + $scoped = TRUE; + } else { + $scoped = FALSE; + } - private function enc_attribute($name, $values, $base64 = false) { $attr = '<Attribute AttributeName="' . htmlspecialchars($name) . '" AttributeNamespace="urn:mace:shibboleth:1.0:attributeNamespace:uri">'; foreach ($values AS $value) { - $attr .= '<AttributeValue>' . ($base64 ? base64_encode($value) : htmlspecialchars($value) ) . '</AttributeValue>'; + + $scopePart = ''; + if ($scoped) { + $tmp = explode('@', $value, 2); + if (count($tmp) === 2) { + $value = $tmp[0]; + $scopePart = ' Scope="' . htmlspecialchars($tmp[1]) . '"'; + } + } + + if ($base64) { + $value = base64_encode($value); + } + + $attr .= '<AttributeValue' . $scopePart . '>' . htmlspecialchars($value) . '</AttributeValue>'; } $attr .= '</Attribute>'; diff --git a/www/admin/metadata.php b/www/admin/metadata.php index 6b6b677fd9ab10cfb31d62fea82143db4e51d742..b7357c549036112262cf324d1d7764d12e36b687 100644 --- a/www/admin/metadata.php +++ b/www/admin/metadata.php @@ -102,7 +102,7 @@ try { foreach ($metalist AS $entityid => $mentry) { $results[$entityid] = SimpleSAML_Utilities::checkAssocArrayRules($mentry, array('entityid', 'host', 'privatekey', 'certificate', 'auth'), - array('name', 'requireconsent', 'authority', 'privatekey_pass', 'attributemap', 'attributealter') + array('name', 'requireconsent', 'authority', 'privatekey_pass', 'attributemap', 'attributealter', 'scopedattributes') ); } $et->data['metadata.shib13-idp-hosted'] = $results; @@ -112,7 +112,7 @@ try { foreach ($metalist AS $entityid => $mentry) { $results[$entityid] = SimpleSAML_Utilities::checkAssocArrayRules($mentry, array('entityid', 'AssertionConsumerService'), - array('base64attributes', 'audience', 'attributemap', 'attributealter', 'simplesaml.attributes', 'attributes', 'name', 'description', 'metadata.sign.enable', 'metadata.sign.privatekey', 'metadata.sign.privatekey_pass', 'metadata.sign.certificate') + array('base64attributes', 'audience', 'attributemap', 'attributealter', 'simplesaml.attributes', 'attributes', 'name', 'description', 'metadata.sign.enable', 'metadata.sign.privatekey', 'metadata.sign.privatekey_pass', 'metadata.sign.certificate', 'scopedattributes') ); } $et->data['metadata.shib13-sp-remote'] = $results;