diff --git a/classes/element.registry.php b/classes/element.registry.php
index 04b8d47de..c33e56524 100644
--- a/classes/element.registry.php
+++ b/classes/element.registry.php
@@ -3,6 +3,9 @@
declare(strict_types=1);
return [
+ 'urn:oasis:names:tc:SAML:2.0:protocol:ext:async-slo' => [
+ 'Asynchronous' => '\SimpleSAML\SAML2\XML\aslo\Asynchronous',
+ ],
'urn:oasis:names:tc:SAML:metadata:algsupport' => [
'DigestMethod' => '\SimpleSAML\SAML2\XML\alg\DigestMethod',
'SigningMethod' => '\SimpleSAML\SAML2\XML\alg\SigningMethod',
diff --git a/resources/schemas/saml-async-slo-v1.0.xsd b/resources/schemas/saml-async-slo-v1.0.xsd
new file mode 100644
index 000000000..2a64b8917
--- /dev/null
+++ b/resources/schemas/saml-async-slo-v1.0.xsd
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Constants.php b/src/Constants.php
index 968fd7817..ac884ff07 100644
--- a/src/Constants.php
+++ b/src/Constants.php
@@ -226,6 +226,11 @@ class Constants extends \SimpleSAML\XMLSecurity\Constants
*/
public const string NS_ALG = 'urn:oasis:names:tc:SAML:metadata:algsupport';
+ /**
+ * The namespace for the SAML 2 Asynchronous Single Logout Profile Extension
+ */
+ public const string NS_ASLO = 'urn:oasis:names:tc:SAML:2.0:protocol:ext:async-slo';
+
/**
* The namespace for the ECP protocol.
*/
diff --git a/src/XML/aslo/AbstractAsloElement.php b/src/XML/aslo/AbstractAsloElement.php
new file mode 100644
index 000000000..02fdd7938
--- /dev/null
+++ b/src/XML/aslo/AbstractAsloElement.php
@@ -0,0 +1,23 @@
+localName, 'Asynchronous', InvalidDOMElementException::class);
+ Assert::same($xml->namespaceURI, Asynchronous::NS, InvalidDOMElementException::class);
+
+ return new static();
+ }
+
+
+ /**
+ * Convert this Asynchronous to XML.
+ */
+ public function toXML(?Dom\Element $parent = null): Dom\Element
+ {
+ return $this->instantiateParentElement($parent);
+ }
+}
diff --git a/src/XML/aslo/SupportsAsynchronousTrait.php b/src/XML/aslo/SupportsAsynchronousTrait.php
new file mode 100644
index 000000000..b238525f7
--- /dev/null
+++ b/src/XML/aslo/SupportsAsynchronousTrait.php
@@ -0,0 +1,40 @@
+supportsAsynchronous;
+ }
+
+
+ /**
+ * @param array<\SimpleSAML\XML\Attribute> $namespacedAttributes
+ */
+ private function setSupportsAsynchronous(array $namespacedAttributes = []): void
+ {
+ foreach ($namespacedAttributes as $attr) {
+ if ($attr->getNamespaceURI() === C::NS_ASLO && $attr->getAttrName() === 'supportsAsynchronous') {
+ $this->supportsAsynchronous = BooleanValue::fromString($attr->getAttrValue()->getValue());
+ return;
+ }
+ }
+ }
+}
diff --git a/src/XML/md/SingleLogoutService.php b/src/XML/md/SingleLogoutService.php
index 6cfcf1842..eb4e0cd4f 100644
--- a/src/XML/md/SingleLogoutService.php
+++ b/src/XML/md/SingleLogoutService.php
@@ -4,11 +4,42 @@
namespace SimpleSAML\SAML2\XML\md;
+use SimpleSAML\SAML2\Type\SAMLAnyURIValue;
+use SimpleSAML\SAML2\XML\aslo\SupportsAsynchronousTrait;
+use SimpleSAML\XML\SchemaValidatableElementInterface;
+use SimpleSAML\XML\SchemaValidatableElementTrait;
+
/**
* SingleLogoutService element of type EndpointType
*
* @package simplesamlphp/saml2
*/
-final class SingleLogoutService extends AbstractEndpointType
+final class SingleLogoutService extends AbstractEndpointType implements SchemaValidatableElementInterface
{
+ use SchemaValidatableElementTrait;
+ use SupportsAsynchronousTrait;
+
+
+ /**
+ * SingleLogoutService constructor.
+ *
+ * @param \SimpleSAML\SAML2\Type\SAMLAnyURIValue $binding
+ * @param \SimpleSAML\SAML2\Type\SAMLAnyURIValue $location
+ * @param \SimpleSAML\SAML2\Type\SAMLAnyURIValue|null $responseLocation
+ * @param \SimpleSAML\XML\ElementInterface[] $children
+ * @param array<\SimpleSAML\XML\Attribute> $attributes
+ *
+ * @throws \SimpleSAML\Assert\AssertionFailedException
+ */
+ public function __construct(
+ protected SAMLAnyURIValue $binding,
+ protected SAMLAnyURIValue $location,
+ protected ?SAMLAnyURIValue $responseLocation = null,
+ array $children = [],
+ array $attributes = [],
+ ) {
+ $this->setSupportsAsynchronous($attributes);
+
+ parent::__construct($binding, $location, $responseLocation, $children, $attributes);
+ }
}
diff --git a/src/XML/samlp/Extensions.php b/src/XML/samlp/Extensions.php
index 5f7884e5d..d6534d1d7 100644
--- a/src/XML/samlp/Extensions.php
+++ b/src/XML/samlp/Extensions.php
@@ -9,6 +9,7 @@
use SimpleSAML\SAML2\Utils\XPath;
use SimpleSAML\SAML2\XML\ExtensionsTrait;
use SimpleSAML\XML\Chunk;
+use SimpleSAML\XML\Registry\ElementRegistry;
use SimpleSAML\XML\SchemaValidatableElementInterface;
use SimpleSAML\XML\SchemaValidatableElementTrait;
use SimpleSAML\XMLSchema\Exception\InvalidDOMElementException;
@@ -63,11 +64,18 @@ public static function fromXML(Dom\Element $xml): static
'Invalid Extensions element \'' . $xml->localName . '\'',
InvalidDOMElementException::class,
);
+
+ $registry = ElementRegistry::getInstance();
$ret = [];
/** @var \Dom\Element $node */
foreach (XPath::xpQuery($xml, './*', XPath::getXPath($xml)) as $node) {
- $ret[] = new Chunk($node);
+ $result = $registry->getElementHandler($node->namespaceURI, $node->localName);
+ if ($result !== null) {
+ $ret[] = $result::fromXML($node);
+ } else {
+ $ret[] = new Chunk($node);
+ }
}
return new static($ret);
diff --git a/tests/SAML2/XML/aslo/AsynchronousTest.php b/tests/SAML2/XML/aslo/AsynchronousTest.php
new file mode 100644
index 000000000..09e7609b8
--- /dev/null
+++ b/tests/SAML2/XML/aslo/AsynchronousTest.php
@@ -0,0 +1,57 @@
+saveXml(self::$xmlRepresentation->documentElement);
+ $this->assertNotFalse($expectedXml);
+ $actualXml = strval($asynchronous);
+
+ $this->assertXmlStringEqualsXmlString($expectedXml, $actualXml);
+ }
+}
diff --git a/tests/SAML2/XML/md/SingleLogoutServiceTest.php b/tests/SAML2/XML/md/SingleLogoutServiceTest.php
new file mode 100644
index 000000000..95245d0da
--- /dev/null
+++ b/tests/SAML2/XML/md/SingleLogoutServiceTest.php
@@ -0,0 +1,78 @@
+saveXml(self::$xmlRepresentation->documentElement);
+ $this->assertNotFalse($expectedXml);
+ $actualXml = strval($sloep);
+
+ $this->assertXmlStringEqualsXmlString($expectedXml, $actualXml);
+ }
+}
diff --git a/tests/SAML2/XML/samlp/AuthnRequestTest.php b/tests/SAML2/XML/samlp/AuthnRequestTest.php
index 08fcb58c4..4a4b679be 100644
--- a/tests/SAML2/XML/samlp/AuthnRequestTest.php
+++ b/tests/SAML2/XML/samlp/AuthnRequestTest.php
@@ -17,6 +17,7 @@
use SimpleSAML\SAML2\Type\SAMLStringValue;
use SimpleSAML\SAML2\Utils;
use SimpleSAML\SAML2\Utils\XPath;
+use SimpleSAML\SAML2\XML\aslo\Asynchronous;
use SimpleSAML\SAML2\XML\saml\Audience;
use SimpleSAML\SAML2\XML\saml\AudienceRestriction;
use SimpleSAML\SAML2\XML\saml\AuthnContextClassRef;
@@ -31,6 +32,7 @@
use SimpleSAML\SAML2\XML\samlp\AbstractSamlpElement;
use SimpleSAML\SAML2\XML\samlp\AuthnContextComparisonTypeEnum;
use SimpleSAML\SAML2\XML\samlp\AuthnRequest;
+use SimpleSAML\SAML2\XML\samlp\Extensions;
use SimpleSAML\SAML2\XML\samlp\GetComplete;
use SimpleSAML\SAML2\XML\samlp\IDPEntry;
use SimpleSAML\SAML2\XML\samlp\IDPList;
@@ -101,6 +103,10 @@ public function testMarshalling(): void
),
);
+ $extensions = new Extensions([
+ new Asynchronous(),
+ ]);
+
$authnRequest = new AuthnRequest(
subject: $subject,
issuer: new Issuer(
@@ -110,6 +116,7 @@ public function testMarshalling(): void
id: IDValue::fromString('_2b0226190ca1c22de6f66e85f5c95158'),
issueInstant: SAMLDateTimeValue::fromString('2014-09-22T13:42:00Z'),
destination: SAMLAnyURIValue::fromString('https://tiqr.stepup.org/idp/profile/saml2/Redirect/SSO'),
+ extensions: $extensions,
);
$expectedXml = self::$xmlRepresentation->saveXml(self::$xmlRepresentation->documentElement);
@@ -181,6 +188,11 @@ public function testMarshallingElementOrdering(): void
[$requesterId],
);
+ // Create Extensions
+ $extensions = new Extensions([
+ new Asynchronous(),
+ ]);
+
$authnRequest = new AuthnRequest(
id: IDValue::fromString('SomeIDValue'),
requestedAuthnContext: $rac,
@@ -192,27 +204,30 @@ public function testMarshallingElementOrdering(): void
SAMLStringValue::fromString('https://gateway.stepup.org/saml20/sp/metadata'),
),
scoping: $scoping,
+ extensions: $extensions,
);
$authnRequestElement = $authnRequest->toXML();
// Test for a Subject
$xpCache = XPath::getXPath($authnRequestElement);
- $authnRequestElements = XPath::xpQuery($authnRequestElement, './saml_assertion:Subject', $xpCache);
+ $authnRequestElements = XPath::xpQuery($authnRequestElement, './saml_assertion:Issuer', $xpCache);
$this->assertCount(1, $authnRequestElements);
// Test ordering of AuthnRequest contents
/** @var \Dom\Element[] $authnRequestElements */
$authnRequestElements = XPath::xpQuery(
$authnRequestElement,
- './saml_assertion:Subject/following-sibling::*',
+ './saml_assertion:Issuer/following-sibling::*',
$xpCache,
);
- $this->assertCount(4, $authnRequestElements);
- $this->assertEquals('samlp:NameIDPolicy', $authnRequestElements[0]->tagName);
- $this->assertEquals('saml:Conditions', $authnRequestElements[1]->tagName);
- $this->assertEquals('samlp:RequestedAuthnContext', $authnRequestElements[2]->tagName);
- $this->assertEquals('samlp:Scoping', $authnRequestElements[3]->tagName);
+ $this->assertCount(6, $authnRequestElements);
+ $this->assertEquals('samlp:Extensions', $authnRequestElements[0]->tagName);
+ $this->assertEquals('saml:Subject', $authnRequestElements[1]->tagName);
+ $this->assertEquals('samlp:NameIDPolicy', $authnRequestElements[2]->tagName);
+ $this->assertEquals('saml:Conditions', $authnRequestElements[3]->tagName);
+ $this->assertEquals('samlp:RequestedAuthnContext', $authnRequestElements[4]->tagName);
+ $this->assertEquals('samlp:Scoping', $authnRequestElements[5]->tagName);
}
diff --git a/tests/resources/xml/aslo_Asynchronous.xml b/tests/resources/xml/aslo_Asynchronous.xml
new file mode 100644
index 000000000..d4dcb6243
--- /dev/null
+++ b/tests/resources/xml/aslo_Asynchronous.xml
@@ -0,0 +1 @@
+
diff --git a/tests/resources/xml/md_SingleLogoutService.xml b/tests/resources/xml/md_SingleLogoutService.xml
new file mode 100644
index 000000000..871e8766d
--- /dev/null
+++ b/tests/resources/xml/md_SingleLogoutService.xml
@@ -0,0 +1 @@
+
diff --git a/tests/resources/xml/samlp_AuthnRequest.xml b/tests/resources/xml/samlp_AuthnRequest.xml
index 5eea6f099..6d8970f4c 100644
--- a/tests/resources/xml/samlp_AuthnRequest.xml
+++ b/tests/resources/xml/samlp_AuthnRequest.xml
@@ -1,5 +1,8 @@
-
+
https://gateway.stepup.org/saml20/sp/metadata
+
+
+
user@example.org