Skip to content

Commit

Permalink
Add saml:Conditions element
Browse files Browse the repository at this point in the history
  • Loading branch information
tvdijen committed Feb 28, 2024
1 parent c44a745 commit 2419cad
Show file tree
Hide file tree
Showing 6 changed files with 322 additions and 1 deletion.
183 changes: 183 additions & 0 deletions src/SAML11/XML/saml/AbstractConditionsType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\SAML11\XML\saml;

use DateTimeImmutable;
use DOMElement;
use SimpleSAML\Assert\Assert;
use SimpleSAML\SAML11\Constants as C;
use SimpleSAML\SAML11\Exception\ProtocolViolationException;
use SimpleSAML\XML\Exception\InvalidDOMElementException;

use preg_match;

/**
* SAML ConditionsType abstract data type.
*
* @package simplesamlphp/saml11
*/
abstract class AbstractConditionsType extends AbstractSamlElement
{
/**
* Initialize a saml:ConditionsType from scratch
*
* @param array<\SimpleSAML\SAML11\XML\saml\AudienceRestrictionCondition> $audienceRestrictionCondition
* @param array<\SimpleSAML\SAML11\XML\saml\DoNotCacheCondition> $doNotCacheCondition
* @param array<\SimpleSAML\SAML11\XML\saml\AbstractCondition> $condition
* @param \DateTimeImmutable|null $notBefore
* @param \DateTimeImmutable|null $notOnOrAfter
*/
final public function __construct(
protected array $audienceRestrictionCondition = [],
protected array $doNotCacheCondition = [],
protected array $condition = [],
protected ?DateTimeImmutable $notBefore = null,
protected ?DateTimeImmutable $notOnOrAfter = null,
) {
Assert::allIsInstanceOf($audienceRestrictionCondition, AudienceRestrictionCondition::class);
Assert::allIsInstanceOf($doNotCacheCondition, DoNotCacheCondition::class);
Assert::allIsInstanceOf($condition, AbstractCondition::class);
}


/**
* Collect the value of the notBefore-property
*
* @return \DateTimeImmutable|null
*/
public function getNotBefore(): ?DateTimeImmutable
{
return $this->notBefore;
}


/**
* Collect the value of the notOnOrAfter-property
*
* @return \DateTimeImmutable|null
*/
public function getNotOnOrAfter(): ?DateTimeImmutable
{
return $this->notOnOrAfter;
}


/**
* Collect the value of the audienceRestrictionCondition-property
*
* @return array<\SimpleSAML\SAML11\XML\saml\AudienceRestrictionCondition>
*/
public function getAudienceRestrictionCondition(): array
{
return $this->audienceRestrictionCondition;
}


/**
* Collect the value of the doNotCacheCondition-property
*
* @return array<\SimpleSAML\SAML11\XML\saml\DoNotCacheCondition>
*/
public function getDoNotCacheCondition(): array
{
return $this->doNotCacheCondition;
}


/**
* Collect the value of the condition-property
*
* @return array<\SimpleSAML\SAML11\XML\saml\Condition>
*/
public function getCondition(): array
{
return $this->condition;
}


/**
* Test if an object, at the state it's in, would produce an empty XML-element
*
* @return bool
*/
public function isEmptyElement(): bool
{
return empty($this->getAudienceRestrictionCondition())
&& empty($this->getDoNotCacheCondition())
&& empty($this->getCondition())
&& empty($this->getNotBefore())
&& empty($this->getNotOnOrAfter());
}


/**
* Convert XML into an ConditionsType
*
* @param \DOMElement $xml The XML element we should load
* @return static
*
* @throws \SimpleSAML\XML\Exception\InvalidDOMElementException
* if the qualified name of the supplied element is wrong
*/
public static function fromXML(DOMElement $xml): static
{
Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class);
Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class);

$notBefore = self::getOptionalAttribute($xml, 'NotBefore');
// Strip sub-seconds - See paragraph 1.2.2 of SAML core specifications
$notBefore = preg_replace('/([.][0-9]+Z)$/', 'Z', $notBefore, 1);

Assert::validDateTimeZulu($notBefore, ProtocolViolationException::class);
$notBefore = new DateTimeImmutable($notBefore);

$notOnOrAfter = self::getOptionalAttribute($xml, 'NotOnOrAfter');
// Strip sub-seconds - See paragraph 1.2.2 of SAML core specifications
$notOnOrAfter = preg_replace('/([.][0-9]+Z)$/', 'Z', $notOnOrAfter, 1);

Assert::validDateTimeZulu($notOnOrAfter, ProtocolViolationException::class);
$notOnOrAfter = new DateTimeImmutable($notOnOrAfter);

$audienceRestrictionCondition = AudienceRestrictionCondition::getChildrenOfClass($xml);
$doNotCacheCondition = DoNotCacheCondition::getChildrenOfClass($xml);
$condition = AbstractCondition::getChildrenOfClass($xml);

return new static($audienceRestrictionCondition, $doNotCacheCondition, $condition, $notBefore, $notOnOrAfter);
}


/**
* Convert this ConditionsType to XML.
*
* @param \DOMElement $parent The element we are converting to XML.
* @return \DOMElement The XML element after adding the data corresponding to this ConditionsType.
*/
public function toXML(DOMElement $parent = null): DOMElement
{
$e = $this->instantiateParentElement($parent);

if ($this->getNotBefore() !== null) {
$e->setAttribute('NotBefore', $this->getNotBefore()->format(C::DATETIME_FORMAT));
}

if ($this->getNotOnOrAfter() !== null) {
$e->setAttribute('NotOnOrAfter', $this->getNotOnOrAfter()->format(C::DATETIME_FORMAT));
}

foreach ($this->getAudienceRestrictionCondition() as $audienceRestrictionCondition) {
$audienceRestrictionCondition->toXML($e);
}

foreach ($this->getDoNotCacheCondition() as $doNotCacheCondition) {
$doNotCacheCondition->toXML($e);
}

foreach ($this->getCondition() as $condition) {
$condition->toXML($e);
}

return $e;
}
}
14 changes: 14 additions & 0 deletions src/SAML11/XML/saml/Conditions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\SAML11\XML\saml;

/**
* Class representing a saml:Conditions element.
*
* @package simplesamlphp/saml11
*/
final class Conditions extends AbstractConditionsType
{
}
16 changes: 15 additions & 1 deletion tests/resources/schemas/simplesamlphp.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,19 @@

<!-- End CustomStatement -->

</schema>
<!-- Start CustomCondition -->

<element name="CustomCondition" type="ssp:CustomConditionType"/>
<complexType name="CustomConditionType">
<complexContent>
<extension base="saml:ConditionAbstractType">
<sequence>
<element ref="saml:Audience" maxOccurs="unbounded"/>
</sequence>
</extension>
</complexContent>
</complexType>

<!-- End CustomCondition -->

</schema>
9 changes: 9 additions & 0 deletions tests/resources/xml/saml_Conditions.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<saml:Conditions xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" NotBefore="2023-01-24T09:42:26Z" NotOnOrAfter="2023-01-24T09:47:26Z">
<saml:AudienceRestrictionCondition>
<saml:Audience>urn:x-simplesamlphp:audience</saml:Audience>
</saml:AudienceRestrictionCondition>
<saml:DoNotCacheCondition xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" />
<saml:Condition xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:ssp="urn:x-simplesamlphp:namespace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ssp:CustomConditionType">
<saml:Audience>urn:some:audience</saml:Audience>
</saml:Condition>
</saml:Conditions>
4 changes: 4 additions & 0 deletions tests/src/SAML11/XML/saml/ConditionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use SimpleSAML\SAML11\XML\saml\UnknownCondition;
use SimpleSAML\Test\SAML11\CustomCondition;
use SimpleSAML\XML\DOMDocumentFactory;
use SimpleSAML\XML\TestUtils\SchemaValidationTestTrait;
use SimpleSAML\XML\TestUtils\SerializableElementTestTrait;

use function dirname;
Expand All @@ -30,6 +31,7 @@
*/
final class ConditionTest extends TestCase
{
use SchemaValidationTestTrait;
use SerializableElementTestTrait;


Expand All @@ -45,6 +47,8 @@ public static function setUpBeforeClass(): void

self::$testedClass = CustomCondition::class;

self::$schemaFile = dirname(__FILE__, 5) . '/resources/schemas/simplesamlphp.xsd';

self::$xmlRepresentation = DOMDocumentFactory::fromFile(
dirname(__FILE__, 5) . '/resources/xml/saml_Condition.xml',
);
Expand Down
97 changes: 97 additions & 0 deletions tests/src/SAML11/XML/saml/ConditionsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\Test\SAML11\XML\saml;

use DateTimeImmutable;
use PHPUnit\Framework\TestCase;
use SimpleSAML\SAML11\Constants as C;
use SimpleSAML\SAML11\XML\saml\Audience;
use SimpleSAML\SAML11\XML\saml\AudienceRestrictionCondition;
use SimpleSAML\SAML11\XML\saml\Condition;
use SimpleSAML\SAML11\XML\saml\Conditions;
use SimpleSAML\SAML11\XML\saml\DoNotCacheCondition;
use SimpleSAML\Test\SAML11\CustomCondition;
use SimpleSAML\XML\DOMDocumentFactory;
use SimpleSAML\XML\TestUtils\SchemaValidationTestTrait;
use SimpleSAML\XML\TestUtils\SerializableElementTestTrait;

use function dirname;
use function strval;

/**
* Tests for Conditions elements.
*
* @covers \SimpleSAML\SAML11\XML\saml\Conditions
* @covers \SimpleSAML\SAML11\XML\saml\AbstractConditionsType
* @covers \SimpleSAML\SAML11\XML\saml\AbstractSamlElement
*
* @package simplesamlphp/saml11
*/
final class ConditionsTest extends TestCase
{
use SchemaValidationTestTrait;
use SerializableElementTestTrait;


/**
*/
public static function setUpBeforeClass(): void
{
self::$schemaFile = dirname(__FILE__, 5) . '/resources/schemas/simplesamlphp.xsd';

self::$testedClass = Conditions::class;

self::$xmlRepresentation = DOMDocumentFactory::fromFile(
dirname(__FILE__, 5) . '/resources/xml/saml_Conditions.xml',
);
}


// marshalling


/**
* Test creating an Conditions from scratch
*/
public function testMarshalling(): void
{
$audience = new Audience('urn:x-simplesamlphp:audience');
$audienceRestrictionCondition = new AudienceRestrictionCondition([$audience]);

$doNotCacheCondition = new DoNotCacheCondition();

$condition = new CustomCondition(
[new Audience('urn:some:audience')],
);

$conditions = new Conditions(
[$audienceRestrictionCondition],
[$doNotCacheCondition],
[$condition],
new DateTimeImmutable('2023-01-24T09:42:26Z'),
new DateTimeImmutable('2023-01-24T09:47:26Z'),
);

$this->assertEquals(
self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement),
strval($conditions),
);
}


/**
* Adding an empty Conditions element should yield an empty element.
*/
public function testMarshallingEmptyElement(): void
{
$samlns = C::NS_SAML;
$conditions = new Conditions([]);
$this->assertEquals(
"<saml:Conditions xmlns:saml=\"$samlns\"/>",
strval($conditions),
);
$this->assertTrue($conditions->isEmptyElement());
}
}

0 comments on commit 2419cad

Please sign in to comment.