Skip to content

Commit

Permalink
Add support for xs:dateTime AttributeValue and improve tests (#362)
Browse files Browse the repository at this point in the history
* Add support for xs:dateTime AttributeValue and improve tests

* Raise coverage
  • Loading branch information
tvdijen authored Sep 2, 2024
1 parent a30604e commit 1e2f2f9
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 9 deletions.
47 changes: 38 additions & 9 deletions src/SAML2/XML/saml/AttributeValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace SimpleSAML\SAML2\XML\saml;

use DateTimeImmutable;
use DateTimeInterface;
use DOMElement;
use SimpleSAML\Assert\Assert;
use SimpleSAML\SAML2\Constants as C;
Expand All @@ -27,16 +29,18 @@ class AttributeValue extends AbstractSamlElement
/**
* Create an AttributeValue.
*
* @param string|int|null|\SimpleSAML\XML\AbstractElement $value The value of this element. Can be one of:
* The value of this element. Can be one of:
* - string
* - int
* - null
* - \DateTimeInterface
* - \SimpleSAML\XML\AbstractElement
*
* @param string|int|null|\DateTimeInterface|\SimpleSAML\XML\AbstractElement $value
* @throws \SimpleSAML\Assert\AssertionFailedException if the supplied value is neither a string or a DOMElement
*/
final public function __construct(
protected string|int|null|AbstractElement $value,
protected string|int|null|DateTimeInterface|AbstractElement $value,
) {
}

Expand All @@ -48,18 +52,23 @@ final public function __construct(
*/
public function getXsiType(): string
{
$type = gettype($this->value);
$value = $this->getValue();
$type = gettype($value);

switch ($type) {
case "integer":
return "xs:integer";
case "NULL":
return "xs:nil";
case "object":
if ($value instanceof DateTimeInterface) {
return 'xs:dateTime';
}

return sprintf(
'%s:%s',
$this->value::getNamespacePrefix(),
AbstractElement::getClassName(get_class($this->value)),
$value::getNamespacePrefix(),
AbstractElement::getClassName(get_class($value)),
);
default:
return "xs:string";
Expand Down Expand Up @@ -111,14 +120,26 @@ public static function fromXML(DOMElement $xml): static
$xml->hasAttributeNS(C::NS_XSI, "type") &&
$xml->getAttributeNS(C::NS_XSI, "type") === "xs:integer"
) {
Assert::numeric($xml->textContent);

// we have an integer as value
$value = intval($xml->textContent);
} elseif (
$xml->hasAttributeNS(C::NS_XSI, "type") &&
$xml->getAttributeNS(C::NS_XSI, "type") === "xs:dateTime"
) {
Assert::validDateTime($xml->textContent);

// we have a dateTime as value
$value = new DateTimeImmutable($xml->textContent);
} elseif (
// null value
$xml->hasAttributeNS(C::NS_XSI, "nil") &&
($xml->getAttributeNS(C::NS_XSI, "nil") === "1" ||
$xml->getAttributeNS(C::NS_XSI, "nil") === "true")
) {
Assert::isEmpty($xml->textContent);

$value = null;
} else {
$value = $xml->textContent;
Expand All @@ -139,26 +160,34 @@ public function toXML(DOMElement $parent = null): DOMElement
{
$e = parent::instantiateParentElement($parent);

$type = gettype($this->value);
$value = $this->getValue();
$type = gettype($value);

switch ($type) {
case "integer":
// make sure that the xs namespace is available in the AttributeValue
$e->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xsi', C::NS_XSI);
$e->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xs', C::NS_XS);
$e->setAttributeNS(C::NS_XSI, 'xsi:type', 'xs:integer');
$e->textContent = strval($this->getValue());
$e->textContent = strval($value);
break;
case "NULL":
$e->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xsi', C::NS_XSI);
$e->setAttributeNS(C::NS_XSI, 'xsi:nil', '1');
$e->textContent = '';
break;
case "object":
$this->getValue()->toXML($e);
if ($value instanceof DateTimeInterface) {
$e->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xsi', C::NS_XSI);
$e->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xs', C::NS_XS);
$e->setAttributeNS(C::NS_XSI, 'xsi:type', 'xs:dateTime');
$e->textContent = $value->format(C::DATETIME_FORMAT);
} else {
$value->toXML($e);
}
break;
default: // string
$e->textContent = $this->getValue();
$e->textContent = $value;
break;
}

Expand Down
5 changes: 5 additions & 0 deletions tests/SAML2/XML/saml/AttributeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace SimpleSAML\Test\SAML2\XML\saml;

use DateTimeImmutable;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;
Expand Down Expand Up @@ -32,6 +33,7 @@
*/
#[Group('saml')]
#[CoversClass(Attribute::class)]
#[CoversClass(AttributeValue::class)]
#[CoversClass(AbstractSamlElement::class)]
final class AttributeTest extends TestCase
{
Expand Down Expand Up @@ -88,6 +90,9 @@ public function testMarshalling(): void
[
new AttributeValue('FirstValue'),
new AttributeValue('SecondValue'),
new AttributeValue(3),
new AttributeValue(new DateTimeImmutable('2024-04-04T04:44:44Z')),
new AttributeValue(null),
],
[$attr1, $attr2],
);
Expand Down
50 changes: 50 additions & 0 deletions tests/SAML2/XML/saml/AttributeValueTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace SimpleSAML\Test\SAML2\XML\saml;

use DateTimeImmutable;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;
Expand Down Expand Up @@ -77,13 +78,62 @@ public function testMarshallingString(): void
}


/**
* Test creating an AttributeValue from scratch using an integer.
*/
public function testMarshallingInteger(): void
{
$av = new AttributeValue(3);

$this->assertEquals(3, $av->getValue());
$this->assertEquals('xs:integer', $av->getXsiType());

$nssaml = C::NS_SAML;
$nsxs = C::NS_XS;
$nsxsi = C::NS_XSI;
$xml = <<<XML
<saml:AttributeValue xmlns:saml="{$nssaml}" xmlns:xsi="{$nsxsi}" xmlns:xs="{$nsxs}" xsi:type="xs:integer">3</saml:AttributeValue>
XML;
$this->assertEquals(
$xml,
strval($av),
);
}


/**
* Test creating an AttributeValue from scratch using an dateTime.
*/
public function testMarshallingDateTime(): void
{
$av = new AttributeValue(new DateTimeImmutable("2024-04-04T04:44:44Z"));

/** @var \DateTimeInterface $value */
$value = $av->getValue();
$this->assertEquals('2024-04-04T04:44:44Z', $value->format(C::DATETIME_FORMAT));
$this->assertEquals('xs:dateTime', $av->getXsiType());

$nssaml = C::NS_SAML;
$nsxs = C::NS_XS;
$nsxsi = C::NS_XSI;
$xml = <<<XML
<saml:AttributeValue xmlns:saml="{$nssaml}" xmlns:xsi="{$nsxsi}" xmlns:xs="{$nsxs}" xsi:type="xs:dateTime">2024-04-04T04:44:44Z</saml:AttributeValue>
XML;
$this->assertEquals(
$xml,
strval($av),
);
}


/**
*/
public function testMarshallingNull(): void
{
$av = new AttributeValue(null);
$this->assertNull($av->getValue());
$this->assertEquals('xs:nil', $av->getXsiType());

$nssaml = C::NS_SAML;
$nsxsi = C::NS_XSI;
$xml = <<<XML
Expand Down
3 changes: 3 additions & 0 deletions tests/resources/xml/saml_Attribute.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<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">
<saml:AttributeValue>FirstValue</saml:AttributeValue>
<saml:AttributeValue>SecondValue</saml:AttributeValue>
<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>
<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>
<saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="1"/>
</saml:Attribute>

0 comments on commit 1e2f2f9

Please sign in to comment.