Skip to content

Commit

Permalink
Merge pull request #28 from simplesamlphp/feature/elt-exclusion-list
Browse files Browse the repository at this point in the history
Add exclusion-list for ExtendableElementTrait
  • Loading branch information
tvdijen authored Sep 13, 2024
2 parents f9cdf8d + f3e30c2 commit 8501582
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 1 deletion.
97 changes: 97 additions & 0 deletions src/ExtendableElementTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

namespace SimpleSAML\XML;

use DOMElement;
use RuntimeException;
use SimpleSAML\Assert\Assert;
use SimpleSAML\XML\Chunk;
use SimpleSAML\XML\Constants as C;
use SimpleSAML\XML\Registry\ElementRegistry;
use SimpleSAML\XML\XsNamespace as NS;

use function array_diff;
Expand All @@ -30,6 +32,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 list<\SimpleSAML\XML\SerializableElementInterface> $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($elt) : $handler::fromXML($elt);
}
} 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->childNodes as $elt) {
if (!($elt instanceof DOMElement)) {
continue;
} elseif (in_array([$elt->namespaceURI, $elt->localName], $exclusionList, true)) {
continue;
} elseif (!in_array($elt->namespaceURI, $namespace, true)) {
continue;
}

$handler = $registry->getElementHandler($elt->namespaceURI, $elt->localName);
$elements[] = ($handler === null) ? Chunk::fromXML($elt) : $handler::fromXML($elt);
}
}

return $elements;
}


/**
* Set an array with all elements present.
*
Expand Down Expand Up @@ -102,6 +177,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;
}

Expand Down Expand Up @@ -131,4 +213,19 @@ public function getElementNamespace(): array|NS

return static::XS_ANY_ELT_NAMESPACE;
}


/**
* Get the exclusions list for getChildElementsFromXML.
*
* @return array<string, string>
*/
public static function getElementExclusions(): array
{
if (defined('static::XS_ANY_ELT_EXCLUSIONS')) {
return static::XS_ANY_ELT_EXCLUSIONS;
}

return [];
}
}
5 changes: 5 additions & 0 deletions tests/Utils/ExtendableElement.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ class ExtendableElement extends AbstractElement
/** @var \SimpleSAML\XML\XsNamespace|array<int, \SimpleSAML\XML\XsNamespace> */
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.
Expand Down
27 changes: 27 additions & 0 deletions tests/XML/ExtendableElementTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,22 @@ public function testMarshalling(): void
$dummyDocument2 = DOMDocumentFactory::fromString(
'<dummy:Chunk xmlns:dummy="urn:custom:dummy">some</dummy:Chunk>',
);
$dummyDocument3 = DOMDocumentFactory::fromString(
'<other:Chunk xmlns:other="urn:custom:other">some</other:Chunk>',
);

/** @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),
],
);

Expand All @@ -69,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');
}
}
2 changes: 1 addition & 1 deletion tests/resources/schemas/simplesamlphp.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<complexType name="ExtendableElementType">
<sequence>
<element ref="ssp:Chunk"/>
<any namespace="##other" processContents="lax"/>
<any namespace="##any" processContents="lax"/>
<!-- (1,1) elements from (1,1) external namespace -->
</sequence>
</complexType>
Expand Down

0 comments on commit 8501582

Please sign in to comment.