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 support for xs:dateTime AttributeValue and improve tests #362

Merged
merged 2 commits into from
Sep 2, 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
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>
Loading