Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add exclusion-list for ExtendableElementTrait #28

Merged
merged 6 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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