From fc0ad2d460e690fdda04e80337d0425496d555bc Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Tue, 13 Oct 2026 16:30:53 +0200 Subject: [PATCH 1/4] Add gssp-extensions --- resources/schemas/gssp-extensions.xsd | 25 +++++ src/Constants.php | 5 + src/XML/gssp/AbstractGsspElement.php | 22 ++++ src/XML/gssp/UserAttributes.php | 77 ++++++++++++++ tests/SAML2/XML/gssp/UserAttributesTest.php | 106 ++++++++++++++++++++ tests/resources/xml/gssp_UserAttributes.xml | 5 + 6 files changed, 240 insertions(+) create mode 100644 resources/schemas/gssp-extensions.xsd create mode 100644 src/XML/gssp/AbstractGsspElement.php create mode 100644 src/XML/gssp/UserAttributes.php create mode 100644 tests/SAML2/XML/gssp/UserAttributesTest.php create mode 100644 tests/resources/xml/gssp_UserAttributes.xml diff --git a/resources/schemas/gssp-extensions.xsd b/resources/schemas/gssp-extensions.xsd new file mode 100644 index 000000000..573dcbcc5 --- /dev/null +++ b/resources/schemas/gssp-extensions.xsd @@ -0,0 +1,25 @@ + + + + + + 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/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 + + From 0538c3973e12adc75bd38f84af50f26623a4b786 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Tue, 13 Oct 2026 16:45:18 +0200 Subject: [PATCH 2/4] Test parsing gssp:UserAttributes as part of an AuthnRequest --- tests/SAML2/XML/samlp/AuthnRequestTest.php | 26 +++++++++++++++++----- tests/resources/xml/samlp_AuthnRequest.xml | 8 +++++-- 2 files changed, 27 insertions(+), 7 deletions(-) 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/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 From ac0c9afaa2c3a9c89f1a93908ed6fd05791be66f Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Tue, 13 Oct 2026 16:50:36 +0200 Subject: [PATCH 3/4] Update element registry --- classes/element.registry.php | 3 +++ 1 file changed, 3 insertions(+) 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', From e9164f463ead6ab14a6133ec20ece0c236470fd7 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Tue, 13 Oct 2026 18:42:24 +0200 Subject: [PATCH 4/4] Restore scheme modification --- resources/schemas/gssp-extensions.xsd | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/schemas/gssp-extensions.xsd b/resources/schemas/gssp-extensions.xsd index 573dcbcc5..c5536e60e 100644 --- a/resources/schemas/gssp-extensions.xsd +++ b/resources/schemas/gssp-extensions.xsd @@ -1,7 +1,6 @@