Skip to content

Commit 1e2f2f9

Browse files
authored
Add support for xs:dateTime AttributeValue and improve tests (#362)
* Add support for xs:dateTime AttributeValue and improve tests * Raise coverage
1 parent a30604e commit 1e2f2f9

4 files changed

Lines changed: 96 additions & 9 deletions

File tree

src/SAML2/XML/saml/AttributeValue.php

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace SimpleSAML\SAML2\XML\saml;
66

7+
use DateTimeImmutable;
8+
use DateTimeInterface;
79
use DOMElement;
810
use SimpleSAML\Assert\Assert;
911
use SimpleSAML\SAML2\Constants as C;
@@ -27,16 +29,18 @@ class AttributeValue extends AbstractSamlElement
2729
/**
2830
* Create an AttributeValue.
2931
*
30-
* @param string|int|null|\SimpleSAML\XML\AbstractElement $value The value of this element. Can be one of:
32+
* The value of this element. Can be one of:
3133
* - string
3234
* - int
3335
* - null
36+
* - \DateTimeInterface
3437
* - \SimpleSAML\XML\AbstractElement
3538
*
39+
* @param string|int|null|\DateTimeInterface|\SimpleSAML\XML\AbstractElement $value
3640
* @throws \SimpleSAML\Assert\AssertionFailedException if the supplied value is neither a string or a DOMElement
3741
*/
3842
final public function __construct(
39-
protected string|int|null|AbstractElement $value,
43+
protected string|int|null|DateTimeInterface|AbstractElement $value,
4044
) {
4145
}
4246

@@ -48,18 +52,23 @@ final public function __construct(
4852
*/
4953
public function getXsiType(): string
5054
{
51-
$type = gettype($this->value);
55+
$value = $this->getValue();
56+
$type = gettype($value);
5257

5358
switch ($type) {
5459
case "integer":
5560
return "xs:integer";
5661
case "NULL":
5762
return "xs:nil";
5863
case "object":
64+
if ($value instanceof DateTimeInterface) {
65+
return 'xs:dateTime';
66+
}
67+
5968
return sprintf(
6069
'%s:%s',
61-
$this->value::getNamespacePrefix(),
62-
AbstractElement::getClassName(get_class($this->value)),
70+
$value::getNamespacePrefix(),
71+
AbstractElement::getClassName(get_class($value)),
6372
);
6473
default:
6574
return "xs:string";
@@ -111,14 +120,26 @@ public static function fromXML(DOMElement $xml): static
111120
$xml->hasAttributeNS(C::NS_XSI, "type") &&
112121
$xml->getAttributeNS(C::NS_XSI, "type") === "xs:integer"
113122
) {
123+
Assert::numeric($xml->textContent);
124+
114125
// we have an integer as value
115126
$value = intval($xml->textContent);
127+
} elseif (
128+
$xml->hasAttributeNS(C::NS_XSI, "type") &&
129+
$xml->getAttributeNS(C::NS_XSI, "type") === "xs:dateTime"
130+
) {
131+
Assert::validDateTime($xml->textContent);
132+
133+
// we have a dateTime as value
134+
$value = new DateTimeImmutable($xml->textContent);
116135
} elseif (
117136
// null value
118137
$xml->hasAttributeNS(C::NS_XSI, "nil") &&
119138
($xml->getAttributeNS(C::NS_XSI, "nil") === "1" ||
120139
$xml->getAttributeNS(C::NS_XSI, "nil") === "true")
121140
) {
141+
Assert::isEmpty($xml->textContent);
142+
122143
$value = null;
123144
} else {
124145
$value = $xml->textContent;
@@ -139,26 +160,34 @@ public function toXML(DOMElement $parent = null): DOMElement
139160
{
140161
$e = parent::instantiateParentElement($parent);
141162

142-
$type = gettype($this->value);
163+
$value = $this->getValue();
164+
$type = gettype($value);
143165

144166
switch ($type) {
145167
case "integer":
146168
// make sure that the xs namespace is available in the AttributeValue
147169
$e->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xsi', C::NS_XSI);
148170
$e->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xs', C::NS_XS);
149171
$e->setAttributeNS(C::NS_XSI, 'xsi:type', 'xs:integer');
150-
$e->textContent = strval($this->getValue());
172+
$e->textContent = strval($value);
151173
break;
152174
case "NULL":
153175
$e->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xsi', C::NS_XSI);
154176
$e->setAttributeNS(C::NS_XSI, 'xsi:nil', '1');
155177
$e->textContent = '';
156178
break;
157179
case "object":
158-
$this->getValue()->toXML($e);
180+
if ($value instanceof DateTimeInterface) {
181+
$e->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xsi', C::NS_XSI);
182+
$e->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xs', C::NS_XS);
183+
$e->setAttributeNS(C::NS_XSI, 'xsi:type', 'xs:dateTime');
184+
$e->textContent = $value->format(C::DATETIME_FORMAT);
185+
} else {
186+
$value->toXML($e);
187+
}
159188
break;
160189
default: // string
161-
$e->textContent = $this->getValue();
190+
$e->textContent = $value;
162191
break;
163192
}
164193

tests/SAML2/XML/saml/AttributeTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace SimpleSAML\Test\SAML2\XML\saml;
66

7+
use DateTimeImmutable;
78
use PHPUnit\Framework\Attributes\CoversClass;
89
use PHPUnit\Framework\Attributes\Group;
910
use PHPUnit\Framework\TestCase;
@@ -32,6 +33,7 @@
3233
*/
3334
#[Group('saml')]
3435
#[CoversClass(Attribute::class)]
36+
#[CoversClass(AttributeValue::class)]
3537
#[CoversClass(AbstractSamlElement::class)]
3638
final class AttributeTest extends TestCase
3739
{
@@ -88,6 +90,9 @@ public function testMarshalling(): void
8890
[
8991
new AttributeValue('FirstValue'),
9092
new AttributeValue('SecondValue'),
93+
new AttributeValue(3),
94+
new AttributeValue(new DateTimeImmutable('2024-04-04T04:44:44Z')),
95+
new AttributeValue(null),
9196
],
9297
[$attr1, $attr2],
9398
);

tests/SAML2/XML/saml/AttributeValueTest.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace SimpleSAML\Test\SAML2\XML\saml;
66

7+
use DateTimeImmutable;
78
use PHPUnit\Framework\Attributes\CoversClass;
89
use PHPUnit\Framework\Attributes\Group;
910
use PHPUnit\Framework\TestCase;
@@ -77,13 +78,62 @@ public function testMarshallingString(): void
7778
}
7879

7980

81+
/**
82+
* Test creating an AttributeValue from scratch using an integer.
83+
*/
84+
public function testMarshallingInteger(): void
85+
{
86+
$av = new AttributeValue(3);
87+
88+
$this->assertEquals(3, $av->getValue());
89+
$this->assertEquals('xs:integer', $av->getXsiType());
90+
91+
$nssaml = C::NS_SAML;
92+
$nsxs = C::NS_XS;
93+
$nsxsi = C::NS_XSI;
94+
$xml = <<<XML
95+
<saml:AttributeValue xmlns:saml="{$nssaml}" xmlns:xsi="{$nsxsi}" xmlns:xs="{$nsxs}" xsi:type="xs:integer">3</saml:AttributeValue>
96+
XML;
97+
$this->assertEquals(
98+
$xml,
99+
strval($av),
100+
);
101+
}
102+
103+
104+
/**
105+
* Test creating an AttributeValue from scratch using an dateTime.
106+
*/
107+
public function testMarshallingDateTime(): void
108+
{
109+
$av = new AttributeValue(new DateTimeImmutable("2024-04-04T04:44:44Z"));
110+
111+
/** @var \DateTimeInterface $value */
112+
$value = $av->getValue();
113+
$this->assertEquals('2024-04-04T04:44:44Z', $value->format(C::DATETIME_FORMAT));
114+
$this->assertEquals('xs:dateTime', $av->getXsiType());
115+
116+
$nssaml = C::NS_SAML;
117+
$nsxs = C::NS_XS;
118+
$nsxsi = C::NS_XSI;
119+
$xml = <<<XML
120+
<saml:AttributeValue xmlns:saml="{$nssaml}" xmlns:xsi="{$nsxsi}" xmlns:xs="{$nsxs}" xsi:type="xs:dateTime">2024-04-04T04:44:44Z</saml:AttributeValue>
121+
XML;
122+
$this->assertEquals(
123+
$xml,
124+
strval($av),
125+
);
126+
}
127+
128+
80129
/**
81130
*/
82131
public function testMarshallingNull(): void
83132
{
84133
$av = new AttributeValue(null);
85134
$this->assertNull($av->getValue());
86135
$this->assertEquals('xs:nil', $av->getXsiType());
136+
87137
$nssaml = C::NS_SAML;
88138
$nsxsi = C::NS_XSI;
89139
$xml = <<<XML
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
<saml:Attribute xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Name="TheName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" FriendlyName="TheFriendlyName" test:attr1="testval1" test:attr2="testval2" xmlns:test="urn:test:something">
22
<saml:AttributeValue>FirstValue</saml:AttributeValue>
33
<saml:AttributeValue>SecondValue</saml:AttributeValue>
4+
<saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:integer">3</saml:AttributeValue>
5+
<saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:dateTime">2024-04-04T04:44:44Z</saml:AttributeValue>
6+
<saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="1"/>
47
</saml:Attribute>

0 commit comments

Comments
 (0)