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