From 79913edd6dafd4b3b9d63aaa097614d93f077c8f Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Thu, 12 Sep 2024 18:01:47 +0200 Subject: [PATCH] Add exclusion-list --- src/ExtendableElementTrait.php | 95 +++++++++++++++++++++++ tests/Utils/ExtendableElement.php | 5 ++ tests/XML/ExtendableElementTest.php | 35 ++++++++- tests/resources/schemas/simplesamlphp.xsd | 2 +- 4 files changed, 134 insertions(+), 3 deletions(-) diff --git a/src/ExtendableElementTrait.php b/src/ExtendableElementTrait.php index 1669a94..140ecb1 100644 --- a/src/ExtendableElementTrait.php +++ b/src/ExtendableElementTrait.php @@ -30,6 +30,79 @@ trait ExtendableElementTrait protected array $elements = []; + /** + * Parse an XML document and get the child elements from the specified namespace(s). + * The namespace defaults to the XS_ANY_ELT_NAMESPACE constant on the element. + * NOTE: In case the namespace is ##any, this method will also return local non-namespaced elements! + * + * @param \DOMElement $xml + * @param \SimpleSAML\XML\XsNamespace|array|null $namespace + * + * @return array $elements + */ + protected static function getChildElementsFromXML(DOMElement $xml, NS|array $namespace = null): array + { + $namespace = $namespace ?? static::XS_ANY_ELT_NAMESPACE; + $exclusionList = static::getElementExclusions(); + $registry = ElementRegistry::getInstance(); + $elements = []; + + // Validate namespace value + if (!is_array($namespace)) { + // Must be one of the predefined values + Assert::oneOf($namespace, NS::cases()); + + foreach ($xml->childNodes as $elt) { + if (!($elt instanceof DOMElement)) { + continue; + } elseif (in_array([$elt->namespaceURI, $elt->localName], $exclusionList, true)) { + continue; + } elseif ($namespace === NS::OTHER && in_array($elt->namespaceURI, [static::NS, null], true)) { + continue; + } elseif ($namespace === NS::TARGET && $elt->namespaceURI !== static::NS) { + continue; + } elseif ($namespace === NS::LOCAL && $elt->namespaceURI !== null) { + continue; + } + + $handler = $registry->getElementHandler($elt->namespaceURI, $elt->localName); + $elements[] = ($handler === null) ? Chunk::fromXML($xml) : $handler::fromXML($xml); + } + } else { + // Array must be non-empty and cannot contain ##any or ##other + Assert::notEmpty($namespace); + Assert::allStringNotEmpty($namespace); + Assert::allNotSame($namespace, NS::ANY); + Assert::allNotSame($namespace, NS::OTHER); + + // Replace the ##targetedNamespace with the actual namespace + if (($key = array_search(NS::TARGET, $namespace)) !== false) { + $namespace[$key] = static::NS; + } + + // Replace the ##local with null + if (($key = array_search(NS::LOCAL, $namespace)) !== false) { + $namespace[$key] = null; + } + + foreach ($xml->childElements as $elt) { + if (!($elt instanceof DOMElement)) { + continue; + } elseif (in_array([$elt->namespaceURI, $ely->localName], $exclusionList, true)) { + continue; + } elseif (!in_array($elt->namespaceURI, $namespace, true)) { + continue; + } + + $handler = $registry->getElementHandler($elt->namespaceURI, $elt->localName); + $elements[] = ($handler === null) ? Chunk::fromXML($xml) : $handler::fromXML($xml); + } + } + + return $elements; + } + + /** * Set an array with all elements present. * @@ -102,6 +175,13 @@ function (SerializableElementInterface $elt) { // XS_ANY_NS_ANY } + $exclusionList = static::getElementExclusions(); + foreach ($elements as $i => $elt) { + if (in_array([$elt->getNamespaceURI(), $elt->getLocalName()], $exclusionList, true)) { + unset($elements[$i]); + } + } + $this->elements = $elements; } @@ -131,4 +211,19 @@ public function getElementNamespace(): array|NS return static::XS_ANY_ELT_NAMESPACE; } + + + /** + * Get the exclusions list for getChildElementsFromXML. + * + * @return array + */ + public static function getElementExclusions(): array + { + if (defined('static::XS_ANY_ELT_EXCLUSIONS')) { + return static::XS_ANY_ELT_EXCLUSIONS; + } + + return []; + } } diff --git a/tests/Utils/ExtendableElement.php b/tests/Utils/ExtendableElement.php index 406a6df..4cf3842 100644 --- a/tests/Utils/ExtendableElement.php +++ b/tests/Utils/ExtendableElement.php @@ -34,6 +34,11 @@ class ExtendableElement extends AbstractElement /** @var \SimpleSAML\XML\XsNamespace|array */ public const XS_ANY_ELT_NAMESPACE = NS::ANY; + /** @var array{array{string, string}} */ + public const XS_ANY_ELT_EXCLUSIONS = [ + ['urn:custom:other', 'Chunk'], + ]; + /** * Get the namespace for the element. diff --git a/tests/XML/ExtendableElementTest.php b/tests/XML/ExtendableElementTest.php index 9d09ca1..a9b393f 100644 --- a/tests/XML/ExtendableElementTest.php +++ b/tests/XML/ExtendableElementTest.php @@ -45,18 +45,28 @@ public static function setUpBeforeClass(): void */ public function testMarshalling(): void { - $dummyDocument1 = DOMDocumentFactory::fromString('some'); - $dummyDocument2 = DOMDocumentFactory::fromString('some'); + $dummyDocument1 = DOMDocumentFactory::fromString( + 'some', + ); + $dummyDocument2 = DOMDocumentFactory::fromString( + 'some', + ); + $dummyDocument3 = DOMDocumentFactory::fromString( + 'some', + ); /** @var \DOMElement $dummyElement1 */ $dummyElement1 = $dummyDocument1->documentElement; /** @var \DOMElement $dummyElement2 */ $dummyElement2 = $dummyDocument2->documentElement; + /** @var \DOMElement $dummyElement3 */ + $dummyElement3 = $dummyDocument3->documentElement; $extendableElement = new ExtendableElement( [ new Chunk($dummyElement1), new Chunk($dummyElement2), + new Chunk($dummyElement3), ], ); @@ -65,4 +75,25 @@ public function testMarshalling(): void strval($extendableElement), ); } + + + /** + */ + public function testGetChildElementsFromXML(): void + { + /** @var \DOMElement $element */ + $element = self::$xmlRepresentation->documentElement; + + $elt = ExtendableElement::fromXML($element); + /** @var \SimpleSAML\XML\Chunk[] $elements */ + $elements = $elt->getElements(); + + $this->assertCount(2, $elements); + $this->assertEquals($elements[0]->getNamespaceURI(), 'urn:x-simplesamlphp:namespace'); + $this->assertEquals($elements[0]->getPrefix(), 'ssp'); + $this->assertEquals($elements[0]->getLocalName(), 'Chunk'); + $this->assertEquals($elements[1]->getNamespaceURI(), 'urn:custom:dummy'); + $this->assertEquals($elements[1]->getPrefix(), 'dummy'); + $this->assertEquals($elements[1]->getLocalName(), 'Chunk'); + } } diff --git a/tests/resources/schemas/simplesamlphp.xsd b/tests/resources/schemas/simplesamlphp.xsd index bc33ae8..a87b8ff 100644 --- a/tests/resources/schemas/simplesamlphp.xsd +++ b/tests/resources/schemas/simplesamlphp.xsd @@ -22,7 +22,7 @@ - +