Skip to content

Commit ce1bc58

Browse files
committed
Merge pull request #21 from simplesamlphp/improve-transforms
Update ds:Transforms to limit its support
2 parents 410a1e1 + 9da3384 commit ce1bc58

17 files changed

Lines changed: 619 additions & 156 deletions

src/XML/ds/Transform.php

Lines changed: 116 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,110 +5,162 @@
55
namespace SimpleSAML\XMLSecurity\XML\ds;
66

77
use DOMElement;
8-
use SimpleSAML\Assert\Assert;
9-
use SimpleSAML\XML\Chunk;
10-
use SimpleSAML\XML\Exception\InvalidDOMElementException;
8+
use SimpleSAML\XMLSecurity\Constants as C;
9+
use SimpleSAML\XMLSecurity\XML\ec\InclusiveNamespaces;
10+
use Webmozart\Assert\Assert;
1111

1212
/**
13-
* Class representing a ds:Transform element.
13+
* Class representing transforms.
1414
*
1515
* @package simplesamlphp/xml-security
1616
*/
17-
final class Transform extends AbstractDsElement
17+
class Transform extends AbstractDsElement
1818
{
19-
/** @var string */
20-
protected string $Algorithm;
19+
/**
20+
* The algorithm used for this transform.
21+
*
22+
* @var string
23+
*/
24+
protected string $algorithm;
25+
26+
/**
27+
* An XPath object.
28+
*
29+
* @var XPath|null
30+
*/
31+
protected ?XPath $xpath = null;
2132

22-
/** @var \SimpleSAML\XML\Chunk[] */
23-
protected array $elements = [];
33+
/**
34+
* An InclusiveNamespaces object.
35+
*
36+
* @var InclusiveNamespaces|null
37+
*/
38+
protected ?InclusiveNamespaces $inclusiveNamespaces = null;
2439

2540

2641
/**
27-
* Initialize a ds:Transform
42+
* Initialize the Transform element.
2843
*
29-
* @param string $Algorithm
30-
* @param \SimpleSAML\XML\Chunk[] $elements
44+
* @param string $algorithm
45+
* @param XPath|null $xpath
46+
* @param InclusiveNamespaces|null $prefixes
3147
*/
3248
public function __construct(
33-
string $Algorithm,
34-
array $elements = []
49+
string $algorithm,
50+
?XPath $xpath = null,
51+
?InclusiveNamespaces $inclusiveNamespaces = null
3552
) {
36-
$this->setElements($elements);
37-
$this->setAlgorithm($Algorithm);
53+
$this->setAlgorithm($algorithm);
54+
$this->setXPath($xpath);
55+
$this->setInclusiveNamespaces($inclusiveNamespaces);
3856
}
3957

4058

4159
/**
60+
* Get the algorithm associated with this transform.
61+
*
4262
* @return string
4363
*/
4464
public function getAlgorithm(): string
4565
{
46-
return $this->Algorithm;
66+
return $this->algorithm;
4767
}
4868

4969

5070
/**
51-
* @param string $Algorithm
52-
* @throws \SimpleSAML\Assert\AssertionFailedException
71+
* Set the value of the algorithm property.
72+
*
73+
* @param string $algorithm
5374
*/
54-
protected function setAlgorithm(string $Algorithm): void
75+
private function setAlgorithm(string $algorithm): void
5576
{
56-
Assert::notEmpty($Algorithm, 'Cannot set an empty algorithm in ' . static::NS_PREFIX . ':Transform.');
57-
$this->Algorithm = $Algorithm;
77+
$this->algorithm = $algorithm;
5878
}
5979

6080

6181
/**
62-
* Collect the elements
82+
* Get the XPath associated with this transform.
6383
*
64-
* @return \SimpleSAML\XML\Chunk[]
84+
* @return XPath|null
6585
*/
66-
public function getElements(): array
86+
public function getXPath(): ?XPath
6787
{
68-
return $this->elements;
88+
return $this->xpath;
6989
}
7090

7191

7292
/**
73-
* Set the value of the elements-property
93+
* Set and validate the XPath object.
7494
*
75-
* @param \SimpleSAML\XML\Chunk[] $elements
76-
* @throws \SimpleSAML\Assert\AssertionFailedException if the supplied array contains anything other than Chunk objects
95+
* @param XPath|null $XPath
7796
*/
78-
private function setElements(array $elements): void
97+
private function setXPath(?XPath $xpath): void
7998
{
80-
Assert::allIsInstanceOf($elements, Chunk::class);
81-
82-
$this->elements = $elements;
99+
if ($xpath === null) {
100+
return;
101+
}
102+
Assert::eq(
103+
$this->algorithm,
104+
C::XPATH_URI,
105+
'Transform algorithm "' . C::XPATH_URI . '" required if XPath provided.'
106+
);
107+
$this->xpath = $xpath;
83108
}
84109

85110

86111
/**
87-
* Convert XML into a Transform element
88-
*
89-
* @param \DOMElement $xml The XML element we should load
90-
* @return self
112+
* Get the InclusiveNamespaces associated with this transform.
91113
*
92-
* @throws \SimpleSAML\XML\Exception\InvalidDOMElementException
93-
* If the qualified name of the supplied element is wrong
114+
* @return InclusiveNamespaces|null
94115
*/
95-
public static function fromXML(DOMElement $xml): object
116+
public function getInclusiveNamespaces(): ?InclusiveNamespaces
96117
{
97-
Assert::same($xml->localName, 'Transform', InvalidDOMElementException::class);
98-
Assert::same($xml->namespaceURI, Transform::NS, InvalidDOMElementException::class);
99-
100-
$Algorithm = self::getAttribute($xml, 'Algorithm');
118+
return $this->inclusiveNamespaces;
119+
}
101120

102-
$elements = [];
103-
foreach ($xml->childNodes as $element) {
104-
if (!($element instanceof DOMElement)) {
105-
continue;
106-
}
107121

108-
$elements[] = new Chunk($element);
122+
/**
123+
* Set and validate the InclusiveNamespaces object.
124+
*
125+
* @param InclusiveNamespaces|null $inclusiveNamespaces
126+
*/
127+
private function setInclusiveNamespaces(?InclusiveNamespaces $inclusiveNamespaces)
128+
{
129+
if ($inclusiveNamespaces === null) {
130+
return;
109131
}
132+
Assert::oneOf(
133+
$this->algorithm,
134+
[
135+
C::C14N_INCLUSIVE_WITH_COMMENTS,
136+
C::C14N_EXCLUSIVE_WITHOUT_COMMENTS
137+
],
138+
'Transform algorithm "' . C::C14N_EXCLUSIVE_WITH_COMMENTS . '" or "' .
139+
C::C14N_EXCLUSIVE_WITHOUT_COMMENTS . '" required if InclusiveNamespaces provided.'
140+
);
141+
$this->inclusiveNamespaces = $inclusiveNamespaces;
142+
}
110143

111-
return new self($Algorithm, $elements);
144+
145+
/**
146+
* Convert XML into a Transform element.
147+
*
148+
* @param \DOMElement $xml The XML element we should load.
149+
* @return self
150+
*/
151+
public static function fromXML(DOMElement $xml): object
152+
{
153+
$alg = self::getAttribute($xml, 'Algorithm');
154+
$xpath = XPath::getChildrenOfClass($xml);
155+
Assert::maxCount($xpath, 1, 'Only one XPath element supported per Transform.');
156+
$prefixes = InclusiveNamespaces::getChildrenOfClass($xml);
157+
Assert::maxCount(
158+
$prefixes,
159+
1,
160+
'Only one InclusiveNamespaces element supported per Transform.'
161+
);
162+
163+
return new self($alg, array_pop($xpath), array_pop($prefixes));
112164
}
113165

114166

@@ -121,10 +173,20 @@ public static function fromXML(DOMElement $xml): object
121173
public function toXML(DOMElement $parent = null): DOMElement
122174
{
123175
$e = $this->instantiateParentElement($parent);
124-
$e->setAttribute('Algorithm', $this->Algorithm);
125176

126-
foreach ($this->elements as $element) {
127-
$e->appendChild($e->ownerDocument->importNode($element->getXML(), true));
177+
$e->setAttribute('Algorithm', $this->algorithm);
178+
179+
switch ($this->algorithm) {
180+
case C::XPATH_URI:
181+
if ($this->xpath !== null) {
182+
$this->xpath->toXML($e);
183+
}
184+
break;
185+
case C::C14N_EXCLUSIVE_WITH_COMMENTS:
186+
case C::C14N_EXCLUSIVE_WITHOUT_COMMENTS:
187+
if ($this->inclusiveNamespaces !== null) {
188+
$this->inclusiveNamespaces->toXML($e);
189+
}
128190
}
129191

130192
return $e;

src/XML/ds/XPath.php

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\XMLSecurity\XML\ds;
6+
7+
use DOMElement;
8+
use SimpleSAML\XML\Exception\InvalidDOMElementException;
9+
use Webmozart\Assert\Assert;
10+
11+
/**
12+
* Class implementing the XPath element.
13+
*
14+
* @package simplesamlphp/xml-security
15+
*/
16+
class XPath extends AbstractDsElement
17+
{
18+
/**
19+
* The XPath expression.
20+
*
21+
* @var string
22+
*/
23+
protected string $expression;
24+
25+
/**
26+
* A key-value array with namespaces, indexed by the prefixes used in the XPath expression.
27+
*
28+
* @var string[]
29+
*/
30+
protected array $namespaces = [];
31+
32+
33+
/**
34+
* Construct an XPath element.
35+
*
36+
* @param string $expression The XPath expression itself.
37+
* @param string[] $namespaces A key - value array with namespace definitions.
38+
*/
39+
public function __construct(string $expression, array $namespaces = null)
40+
{
41+
$this->setExpression($expression);
42+
$this->setNamespaces($namespaces);
43+
}
44+
45+
46+
/**
47+
* Get the actual XPath expression.
48+
*
49+
* @return string
50+
*/
51+
public function getExpression(): string
52+
{
53+
return $this->expression;
54+
}
55+
56+
57+
/**
58+
* Set the xpath expression itself.
59+
*
60+
* @param string $expression
61+
*/
62+
private function setExpression(string $expression): void
63+
{
64+
$this->expression = $expression;
65+
}
66+
67+
68+
/**
69+
* Get the list of namespaces used in this XPath expression, with their corresponding prefix as
70+
* the keys of each element in the array.
71+
*
72+
* @return string[]
73+
*/
74+
public function getNamespaces(): array
75+
{
76+
return $this->namespaces;
77+
}
78+
79+
80+
/**
81+
* Set the list of namespaces used in this XPath expression.
82+
*
83+
* @param string[] $namespaces
84+
*/
85+
private function setNamespaces(?array $namespaces): void
86+
{
87+
if ($namespaces === null) {
88+
return;
89+
}
90+
Assert::allString($namespaces);
91+
Assert::allString(array_keys($namespaces));
92+
$this->namespaces = $namespaces;
93+
}
94+
95+
96+
/**
97+
* Convert XML into a class instance
98+
*
99+
* @param DOMElement $xml
100+
* @return self
101+
*
102+
* @throws \SimpleSAML\XML\Exception\InvalidDOMElementException
103+
* If the qualified name of the supplied element is wrong
104+
*/
105+
public static function fromXML(DOMElement $xml): object
106+
{
107+
Assert::same($xml->localName, 'XPath', InvalidDOMElementException::class);
108+
Assert::same($xml->namespaceURI, self::NS, InvalidDOMElementException::class);
109+
110+
$namespaces = [];
111+
$xpath = new \DOMXPath($xml->ownerDocument);
112+
/** @var \DOMNode $ns */
113+
foreach ($xpath->query('namespace::*', $xml) as $ns) {
114+
if ($xml->getAttributeNode($ns->nodeName)) {
115+
$namespaces[str_replace('xmlns:', '', $ns->nodeName)] =
116+
$xml->getAttribute($ns->nodeName);
117+
}
118+
}
119+
120+
return new self($xml->textContent, $namespaces);
121+
}
122+
123+
124+
/**
125+
* @param DOMElement|null $parent
126+
* @return DOMElement
127+
*/
128+
public function toXML(DOMElement $parent = null): DOMElement
129+
{
130+
$e = $this->instantiateParentElement($parent);
131+
$e->textContent = $this->expression;
132+
133+
foreach ($this->namespaces as $prefix => $namespace) {
134+
$e->setAttribute('xmlns:' . $prefix, $namespace);
135+
}
136+
return $e;
137+
}
138+
}

0 commit comments

Comments
 (0)