diff --git a/classes/element.registry.php b/classes/element.registry.php
index c33e56524..d564b6331 100644
--- a/classes/element.registry.php
+++ b/classes/element.registry.php
@@ -20,6 +20,9 @@
'http://eduid.cz/schema/metadata/1.0' => [
'RepublishRequest' => '\SimpleSAML\SAML2\XML\emd\RepublishRequest',
],
+ 'urn:mace:surf.nl:stepup:gssp-extensions' => [
+ 'UserAttributes' => '\SimpleSAML\SAML2\XML\gssp\UserAttributes',
+ ],
'urn:oasis:names:tc:SAML:2.0:metadata' => [
'AdditionalMetadataLocation' => '\SimpleSAML\SAML2\XML\md\AdditionalMetadataLocation',
'AffiliateMember' => '\SimpleSAML\SAML2\XML\md\AffiliateMember',
diff --git a/resources/schemas/gssp-extensions.xsd b/resources/schemas/gssp-extensions.xsd
new file mode 100644
index 000000000..c5536e60e
--- /dev/null
+++ b/resources/schemas/gssp-extensions.xsd
@@ -0,0 +1,24 @@
+
+
+
+
+
+ SAML Extension for use in the GSSP protocol: https://github.com/OpenConext/Stepup-Gateway/blob/develop/docs/GSSP.md
+ Version 1.0:
+ - Define UserAttributes element for use in AuthnRequest during GSSP enrollemnt
+
+
+
+
+
+
+
+
+
diff --git a/src/Constants.php b/src/Constants.php
index ac884ff07..c8e02e553 100644
--- a/src/Constants.php
+++ b/src/Constants.php
@@ -241,6 +241,11 @@ class Constants extends \SimpleSAML\XMLSecurity\Constants
*/
public const string NS_EMD = 'http://eduid.cz/schema/metadata/1.0';
+ /**
+ * The namespace for the GSSP-extensions.
+ */
+ public const string NS_GSSP = 'urn:mace:surf.nl:stepup:gssp-extensions';
+
/**
* The namespace for the SAML 2 HoK Web Browser SSO Profile.
*/
diff --git a/src/XML/gssp/AbstractGsspElement.php b/src/XML/gssp/AbstractGsspElement.php
new file mode 100644
index 000000000..428ed5abb
--- /dev/null
+++ b/src/XML/gssp/AbstractGsspElement.php
@@ -0,0 +1,22 @@
+attributes;
+ }
+
+
+ /**
+ * @throws \SimpleSAML\XMLSchema\Exception\InvalidDOMElementException
+ * if the qualified name of the supplied element is wrong
+ */
+ public static function fromXML(Dom\Element $xml): static
+ {
+ Assert::same($xml->localName, 'UserAttributes', InvalidDOMElementException::class);
+ Assert::same($xml->namespaceURI, UserAttributes::NS, InvalidDOMElementException::class);
+
+ return new static(
+ Attribute::getChildrenOfClass($xml),
+ );
+ }
+
+
+ /**
+ * Convert this UserAttributes to XML.
+ */
+ public function toXML(?Dom\Element $parent = null): Dom\Element
+ {
+ $e = $this->instantiateParentElement($parent);
+
+ foreach ($this->getAttributes() as $attribute) {
+ $attribute->toXML($e);
+ }
+
+ return $e;
+ }
+}
diff --git a/tests/SAML2/XML/gssp/UserAttributesTest.php b/tests/SAML2/XML/gssp/UserAttributesTest.php
new file mode 100644
index 000000000..46db69d2b
--- /dev/null
+++ b/tests/SAML2/XML/gssp/UserAttributesTest.php
@@ -0,0 +1,106 @@
+saveXml(self::$xmlRepresentation->documentElement);
+ $this->assertNotFalse($expectedXml);
+ $actualXml = strval($userAttributes);
+
+ $this->assertXmlStringEqualsXmlString($expectedXml, $actualXml);
+ }
+
+
+ /**
+ */
+ public function testMarshallingMissingAttributesThrowsException(): void
+ {
+ $this->expectException(MissingElementException::class);
+
+ new UserAttributes([]);
+ }
+
+
+ // unmarshalling
+
+
+ /**
+ */
+ public function testUnmarshallingMissingAttributesThrowsException(): void
+ {
+ $document = DOMDocumentFactory::fromString(
+ <<
+
+XML
+ ,
+ );
+
+ $this->expectException(MissingElementException::class);
+ UserAttributes::fromXML($document->documentElement);
+ }
+}
diff --git a/tests/SAML2/XML/samlp/AuthnRequestTest.php b/tests/SAML2/XML/samlp/AuthnRequestTest.php
index 059ac86c1..f4ac515a5 100644
--- a/tests/SAML2/XML/samlp/AuthnRequestTest.php
+++ b/tests/SAML2/XML/samlp/AuthnRequestTest.php
@@ -17,7 +17,9 @@
use SimpleSAML\SAML2\Type\SAMLStringValue;
use SimpleSAML\SAML2\Utils;
use SimpleSAML\SAML2\Utils\XPath;
-use SimpleSAML\SAML2\XML\aslo\Asynchronous;
+use SimpleSAML\SAML2\XML\gssp\UserAttributes;
+use SimpleSAML\SAML2\XML\saml\Attribute;
+use SimpleSAML\SAML2\XML\saml\AttributeValue;
use SimpleSAML\SAML2\XML\saml\Audience;
use SimpleSAML\SAML2\XML\saml\AudienceRestriction;
use SimpleSAML\SAML2\XML\saml\AuthnContextClassRef;
@@ -104,8 +106,15 @@ public function testMarshalling(): void
);
$extensions = new Extensions([
- // Note: this makes zero sense in an AuthnRequest, but we can't be picky in what extensions we accept
- new Asynchronous(),
+ new UserAttributes([
+ new Attribute(
+ name: SAMLStringValue::fromString('urn:mace:dir:attribute-def:mail'),
+ nameFormat: SAMLAnyURIValue::fromString(C::NAMEFORMAT_URI),
+ attributeValue: [
+ new AttributeValue(SAMLStringValue::fromString('user@example.com')),
+ ],
+ ),
+ ]),
]);
$authnRequest = new AuthnRequest(
@@ -191,8 +200,15 @@ public function testMarshallingElementOrdering(): void
// Create Extensions
$extensions = new Extensions([
- // Note: this makes zero sense in an AuthnRequest, but we can't be picky in what extensions we accept
- new Asynchronous(),
+ new UserAttributes([
+ new Attribute(
+ name: SAMLStringValue::fromString('urn:mace:dir:attribute-def:mail'),
+ nameFormat: SAMLAnyURIValue::fromString(C::NAMEFORMAT_URI),
+ attributeValue: [
+ new AttributeValue(SAMLStringValue::fromString('user@example.com')),
+ ],
+ ),
+ ]),
]);
$authnRequest = new AuthnRequest(
diff --git a/tests/resources/xml/gssp_UserAttributes.xml b/tests/resources/xml/gssp_UserAttributes.xml
new file mode 100644
index 000000000..cbf1f5abf
--- /dev/null
+++ b/tests/resources/xml/gssp_UserAttributes.xml
@@ -0,0 +1,5 @@
+
+
+ user@example.com
+
+
diff --git a/tests/resources/xml/samlp_AuthnRequest.xml b/tests/resources/xml/samlp_AuthnRequest.xml
index 6d8970f4c..3faa60ccd 100644
--- a/tests/resources/xml/samlp_AuthnRequest.xml
+++ b/tests/resources/xml/samlp_AuthnRequest.xml
@@ -1,7 +1,11 @@
-
+
https://gateway.stepup.org/saml20/sp/metadata
-
+
+
+ user@example.com
+
+
user@example.org