diff --git a/src/Constants.php b/src/Constants.php index b1155343..aba799ba 100644 --- a/src/Constants.php +++ b/src/Constants.php @@ -52,6 +52,11 @@ class Constants extends \SimpleSAML\SAML2\Constants */ public const NS_SEC_UTIL = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'; + /** + * The namespace for the Metadata Exchange protocol. + */ + public const NS_METADATA_EXCHANGE = 'http://schemas.xmlsoap.org/ws/2004/09/mex'; + /** * The schema-defined wsa fault codes */ diff --git a/src/XML/wst/AbstractBinaryExchangeType.php b/src/XML/wst/AbstractBinaryExchangeType.php index 0c16de65..a49d4570 100644 --- a/src/XML/wst/AbstractBinaryExchangeType.php +++ b/src/XML/wst/AbstractBinaryExchangeType.php @@ -22,7 +22,7 @@ abstract class AbstractBinaryExchangeType extends AbstractWstElement use ExtendableAttributesTrait; use StringElementTrait; - /** @var string|\SimpleSAML\XML\XsNamespace */ + /** The namespace-attribute for the xs:anyAttribute element */ public const XS_ANY_ATTR_NAMESPACE = NS::OTHER; diff --git a/src/XML/wsx/AbstractWsxElement.php b/src/XML/wsx/AbstractWsxElement.php new file mode 100644 index 00000000..106a2185 --- /dev/null +++ b/src/XML/wsx/AbstractWsxElement.php @@ -0,0 +1,22 @@ +setContent($content); + } +} diff --git a/src/XML/wsx/GetMetadata.php b/src/XML/wsx/GetMetadata.php new file mode 100644 index 00000000..b1e67872 --- /dev/null +++ b/src/XML/wsx/GetMetadata.php @@ -0,0 +1,127 @@ + $namespacedAttributes + */ + final public function __construct( + protected ?Dialect $dialect = null, + protected ?Identifier $identifier = null, + array $namespacedAttributes = [] + ) { + $this->setAttributesNS($namespacedAttributes); + } + + + /** + * Get the dialect property. + * + * @return \SimpleSAML\WSSecurity\XML\wsx\Dialect|null + */ + public function getDialect(): ?Dialect + { + return $this->dialect; + } + + + /** + * Get the identifier property. + * + * @return \SimpleSAML\WSSecurity\XML\wsx\Identifier|null + */ + public function getIdentifier(): ?Identifier + { + return $this->identifier; + } + + + /** + * Test if an object, at the state it's in, would produce an empty XML-element + * + * @return bool + */ + public function isEmptyElement(): bool + { + return empty($this->getDialect()) + && empty($this->getIdentifier()) + && empty($this->getAttributesNS()); + } + + + /** + * Create an instance of this object from its XML representation. + * + * @param \DOMElement $xml + * @return static + * + * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException + * if the qualified name of the supplied element is wrong + */ + public static function fromXML(DOMElement $xml): static + { + Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class); + Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class); + + $dialect = Dialect::getChildrenOfClass($xml); + Assert::maxCount($dialect, 1, TooManyElementsException::class); + + $identifier = Identifier::getChildrenOfClass($xml); + Assert::maxCount($identifier, 1, TooManyElementsException::class); + + return new static( + array_pop($dialect), + array_pop($identifier), + self::getAttributesNSFromXML($xml), + ); + } + + + /** + * Add this GetMetadata to an XML element. + * + * @param \DOMElement $parent The element we should append this GetMetadata to. + * @return \DOMElement + */ + public function toXML(DOMElement $parent = null): DOMElement + { + $e = parent::instantiateParentElement($parent); + + $this->getDialect()?->toXML($e); + $this->getIdentifier()?->toXML($e); + + foreach ($this->getAttributesNS() as $attr) { + $attr->toXML($e); + } + + return $e; + } +} diff --git a/src/XML/wsx/Identifier.php b/src/XML/wsx/Identifier.php new file mode 100644 index 00000000..f28b286d --- /dev/null +++ b/src/XML/wsx/Identifier.php @@ -0,0 +1,26 @@ +setContent($content); + } +} diff --git a/src/XML/wsx/Location.php b/src/XML/wsx/Location.php new file mode 100644 index 00000000..d3b9e67b --- /dev/null +++ b/src/XML/wsx/Location.php @@ -0,0 +1,26 @@ +setContent($content); + } +} diff --git a/src/XML/wsx/Metadata.php b/src/XML/wsx/Metadata.php new file mode 100644 index 00000000..5ed17ace --- /dev/null +++ b/src/XML/wsx/Metadata.php @@ -0,0 +1,132 @@ + $metadataSection + * @param array<\SimpleSAML\XML\SerializableElementInterface> $children + * @param array<\SimpleSAML\XML\Attribute> $namespacedAttributes + */ + final public function __construct( + protected array $metadataSection = [], + array $children = [], + array $namespacedAttributes = [] + ) { + $this->setElements($children); + $this->setAttributesNS($namespacedAttributes); + } + + + /** + * Get the child property. + * + * @return array<\SimpleSAML\WSSecurity\XML\wsx\MetadataSection> + */ + public function getMetadataSection(): array + { + return $this->metadataSection; + } + + + /** + * Test if an object, at the state it's in, would produce an empty XML-element + * + * @return bool + */ + public function isEmptyElement(): bool + { + return empty($this->getMetadataSection()) + && empty($this->getElements()) + && empty($this->getAttributesNS()); + } + + + /** + * Create an instance of this object from its XML representation. + * + * @param \DOMElement $xml + * @return static + * + * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException + * if the qualified name of the supplied element is wrong + */ + public static function fromXML(DOMElement $xml): static + { + Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class); + Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class); + + $children = []; + foreach ($xml->childNodes as $child) { + if (!($child instanceof DOMElement)) { + continue; + } elseif ($child->namespaceURI === static::NS) { + continue; + } + + $children[] = new Chunk($child); + } + + return new static( + MetadataSection::getChildrenOfClass($xml), + $children, + self::getAttributesNSFromXML($xml), + ); + } + + + /** + * Add this Metadata to an XML element. + * + * @param \DOMElement $parent The element we should append this Metadata to. + * @return \DOMElement + */ + public function toXML(DOMElement $parent = null): DOMElement + { + $e = parent::instantiateParentElement($parent); + + foreach ($this->getMetadataSection() as $metadataSection) { + $metadataSection->toXML($e); + } + + foreach ($this->getElements() as $elt) { + if (!$elt->isEmptyElement()) { + $elt->toXML($e); + } + } + + foreach ($this->getAttributesNS() as $attr) { + $attr->toXML($e); + } + + return $e; + } +} diff --git a/src/XML/wsx/MetadataReference.php b/src/XML/wsx/MetadataReference.php new file mode 100644 index 00000000..9d143941 --- /dev/null +++ b/src/XML/wsx/MetadataReference.php @@ -0,0 +1,88 @@ +setElements($children); + } + + + /** + * Create an instance of this object from its XML representation. + * + * @param \DOMElement $xml + * @return static + * + * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException + * if the qualified name of the supplied element is wrong + */ + public static function fromXML(DOMElement $xml): static + { + Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class); + Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class); + + $children = []; + foreach ($xml->childNodes as $child) { + if (!($child instanceof DOMElement)) { + continue; + } elseif ($child->namespaceURI === static::NS) { + continue; + } + + $children[] = new Chunk($child); + } + + return new static($children); + } + + + /** + * Add this MetadataReference to an XML element. + * + * @param \DOMElement $parent The element we should append this MetadataReference to. + * @return \DOMElement + */ + public function toXML(DOMElement $parent = null): DOMElement + { + $e = parent::instantiateParentElement($parent); + + foreach ($this->getElements() as $child) { + if (!$child->isEmptyElement()) { + $child->toXML($e); + } + } + + return $e; + } +} diff --git a/src/XML/wsx/MetadataSection.php b/src/XML/wsx/MetadataSection.php new file mode 100644 index 00000000..706414ec --- /dev/null +++ b/src/XML/wsx/MetadataSection.php @@ -0,0 +1,155 @@ + $namespacedAttributes + */ + final public function __construct( + protected SerializableElementInterface|MetadataReference|Location $child, + protected string $Dialect, + protected ?string $Identifier = null, + array $namespacedAttributes = [] + ) { + Assert::validURI($Dialect); + Assert::nullOrValidURI($Identifier); + + $this->setAttributesNS($namespacedAttributes); + } + + + /** + * Get the child property. + * + * @return (\SimpleSAML\XML\SerializableElementInterface| + * \SimpleSAML\WSSecurity\XML\wsx\MetadataReference| + * \SimpleSAML\WSSecurity\XML\wsx\Location) + */ + public function getChild(): SerializableElementInterface|MetadataReference|Location + { + return $this->child; + } + + + /** + * Get the Dialect property. + * + * @return string + */ + public function getDialect(): string + { + return $this->Dialect; + } + + + /** + * Get the Identifier property. + * + * @return string|null + */ + public function getIdentifier(): ?string + { + return $this->Identifier; + } + + + /** + * Create an instance of this object from its XML representation. + * + * @param \DOMElement $xml + * @return static + * + * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException + * if the qualified name of the supplied element is wrong + */ + public static function fromXML(DOMElement $xml): static + { + Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class); + Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class); + + $children = []; + foreach ($xml->childNodes as $child) { + if (!($child instanceof DOMElement)) { + continue; + } elseif ($child->namespaceURI === static::NS) { + if ($child->localName === 'MetadataReference') { + $children[] = MetadataReference::fromXML($child); + } elseif ($child->localName === 'Location') { + $children[] = Location::fromXML($child); + } + continue; + } + + $children[] = new Chunk($child); + } + + Assert::minCount($children, 1, MissingElementException::class); + Assert::maxCount($children, 1, TooManyElementsException::class); + + return new static( + array_pop($children), + self::getAttribute($xml, 'Dialect'), + self::getOptionalAttribute($xml, 'Identifier', null), + self::getAttributesNSFromXML($xml), + ); + } + + + /** + * Add this MetadataSection to an XML element. + * + * @param \DOMElement $parent The element we should append this MetadataSection to. + * @return \DOMElement + */ + public function toXML(DOMElement $parent = null): DOMElement + { + $e = parent::instantiateParentElement($parent); + $e->setAttribute('Dialect', $this->getDialect()); + + if ($this->getIdentifier() !== null) { + $e->setAttribute('Identifier', $this->getIdentifier()); + } + + $this->getChild()->toXML($e); + + foreach ($this->getAttributesNS() as $attr) { + $attr->toXML($e); + } + + return $e; + } +} diff --git a/tests/WSSecurity/XML/wsx/DialectTest.php b/tests/WSSecurity/XML/wsx/DialectTest.php new file mode 100644 index 00000000..f6ed722f --- /dev/null +++ b/tests/WSSecurity/XML/wsx/DialectTest.php @@ -0,0 +1,59 @@ +assertEquals( + self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement), + strval($dialect), + ); + } +} diff --git a/tests/WSSecurity/XML/wsx/GetMetadataTest.php b/tests/WSSecurity/XML/wsx/GetMetadataTest.php new file mode 100644 index 00000000..f804f286 --- /dev/null +++ b/tests/WSSecurity/XML/wsx/GetMetadataTest.php @@ -0,0 +1,67 @@ +assertEquals( + self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement), + strval($getMetadata) + ); + } +} diff --git a/tests/WSSecurity/XML/wsx/IdentifierTest.php b/tests/WSSecurity/XML/wsx/IdentifierTest.php new file mode 100644 index 00000000..f1a487d1 --- /dev/null +++ b/tests/WSSecurity/XML/wsx/IdentifierTest.php @@ -0,0 +1,59 @@ +assertEquals( + self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement), + strval($identifier), + ); + } +} diff --git a/tests/WSSecurity/XML/wsx/LocationTest.php b/tests/WSSecurity/XML/wsx/LocationTest.php new file mode 100644 index 00000000..7fb867e6 --- /dev/null +++ b/tests/WSSecurity/XML/wsx/LocationTest.php @@ -0,0 +1,59 @@ +assertEquals( + self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement), + strval($location), + ); + } +} diff --git a/tests/WSSecurity/XML/wsx/MetadataReferenceTest.php b/tests/WSSecurity/XML/wsx/MetadataReferenceTest.php new file mode 100644 index 00000000..47e2089e --- /dev/null +++ b/tests/WSSecurity/XML/wsx/MetadataReferenceTest.php @@ -0,0 +1,89 @@ +SomeChunk' + )->documentElement; + } + + + // test marshalling + + + /** + * Test creating an MetadataReference object from scratch. + */ + public function testMarshalling(): void + { + $attr1 = new Attribute('urn:x-simplesamlphp:namespace', 'ssp', 'test1', 'value1'); + $attr2 = new Attribute('urn:x-simplesamlphp:namespace', 'ssp', 'test2', 'value2'); + + $chunk = new Chunk(self::$customContent); + + $endpointReference = new EndpointReference( + new Address('https://login.microsoftonline.com/login.srf', [$attr2]), + [], + [], + [$chunk], + [$attr1], + ); + + $metadataReference = new MetadataReference([$endpointReference]); + + $this->assertEquals( + self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement), + strval($metadataReference) + ); + } +} diff --git a/tests/WSSecurity/XML/wsx/MetadataSectionTest.php b/tests/WSSecurity/XML/wsx/MetadataSectionTest.php new file mode 100644 index 00000000..415812ae --- /dev/null +++ b/tests/WSSecurity/XML/wsx/MetadataSectionTest.php @@ -0,0 +1,67 @@ +assertEquals( + self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement), + strval($metadataSection) + ); + } +} diff --git a/tests/WSSecurity/XML/wsx/MetadataTest.php b/tests/WSSecurity/XML/wsx/MetadataTest.php new file mode 100644 index 00000000..07513947 --- /dev/null +++ b/tests/WSSecurity/XML/wsx/MetadataTest.php @@ -0,0 +1,86 @@ +Some', + ); + + $metadataSection = new MetadataSection( + new Location('urn:x-simplesamlphp:namespace'), + 'urn:x-simplesamlphp:namespace', + 'urn:x-simplesamlphp:namespace', + [$attr2], + ); + + $metadata = new Metadata([$metadataSection], [new Chunk($child->documentElement)], [$attr1]); + + $this->assertEquals( + self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement), + strval($metadata) + ); + } + + + /** + */ + public function testMarshallingEmpty(): void + { + $metadata = new Metadata(); + + $this->assertTrue($metadata->isEmptyElement()); + } +} diff --git a/tests/resources/xml/wsx_Dialect.xml b/tests/resources/xml/wsx_Dialect.xml new file mode 100644 index 00000000..3ffcdf11 --- /dev/null +++ b/tests/resources/xml/wsx_Dialect.xml @@ -0,0 +1 @@ +urn:x-simplesamlphp:namespace diff --git a/tests/resources/xml/wsx_GetMetadata.xml b/tests/resources/xml/wsx_GetMetadata.xml new file mode 100644 index 00000000..f28a0f78 --- /dev/null +++ b/tests/resources/xml/wsx_GetMetadata.xml @@ -0,0 +1,4 @@ + + urn:x-simplesamlphp:namespace + urn:x-simplesamlphp:namespace + diff --git a/tests/resources/xml/wsx_Identifier.xml b/tests/resources/xml/wsx_Identifier.xml new file mode 100644 index 00000000..39651b91 --- /dev/null +++ b/tests/resources/xml/wsx_Identifier.xml @@ -0,0 +1 @@ +urn:x-simplesamlphp:namespace diff --git a/tests/resources/xml/wsx_Location.xml b/tests/resources/xml/wsx_Location.xml new file mode 100644 index 00000000..b4a0d7e9 --- /dev/null +++ b/tests/resources/xml/wsx_Location.xml @@ -0,0 +1 @@ +urn:x-simplesamlphp:namespace diff --git a/tests/resources/xml/wsx_Metadata.xml b/tests/resources/xml/wsx_Metadata.xml new file mode 100644 index 00000000..749d26bc --- /dev/null +++ b/tests/resources/xml/wsx_Metadata.xml @@ -0,0 +1,6 @@ + + + urn:x-simplesamlphp:namespace + + Some + diff --git a/tests/resources/xml/wsx_MetadataReference.xml b/tests/resources/xml/wsx_MetadataReference.xml new file mode 100644 index 00000000..25972d9c --- /dev/null +++ b/tests/resources/xml/wsx_MetadataReference.xml @@ -0,0 +1,6 @@ + + + https://login.microsoftonline.com/login.srf + SomeChunk + + diff --git a/tests/resources/xml/wsx_MetadataSection.xml b/tests/resources/xml/wsx_MetadataSection.xml new file mode 100644 index 00000000..79982e62 --- /dev/null +++ b/tests/resources/xml/wsx_MetadataSection.xml @@ -0,0 +1,3 @@ + + urn:x-simplesamlphp:namespace +