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..e928a97a3
--- /dev/null
+++ b/src/XML/aslo/SupportsAsynchronousTrait.php
@@ -0,0 +1,34 @@
+supportsAsynchronous;
+ }
+
+
+ /**
+ * @param \SimpleSAML\XMLSchema\Type\BooleanValue $supportsAsynchronous|null
+ */
+ private function setSupportsAsynchronous(?BooleanValue $supportsAsynchronous): void
+ {
+ $this->supportsAsynchronous = $supportsAsynchronous;
+ }
+}
diff --git a/src/XML/md/SingleLogoutService.php b/src/XML/md/SingleLogoutService.php
index 6cfcf1842..414a94a76 100644
--- a/src/XML/md/SingleLogoutService.php
+++ b/src/XML/md/SingleLogoutService.php
@@ -4,6 +4,13 @@
namespace SimpleSAML\SAML2\XML\md;
+use Dom;
+use SimpleSAML\SAML2\Assert\Assert;
+use SimpleSAML\SAML2\Type\SAMLAnyURIValue;
+use SimpleSAML\SAML2\XML\aslo\SupportsAsynchronousTrait;
+use SimpleSAML\XMLSchema\Exception\InvalidDOMElementException;
+use SimpleSAML\XMLSchema\Type\BooleanValue;
+
/**
* SingleLogoutService element of type EndpointType
*
@@ -11,4 +18,79 @@
*/
final class SingleLogoutService extends AbstractEndpointType
{
+ 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\XMLSchema\Type\BooleanValue $supportsAsynchronous
+ * @param \SimpleSAML\XML\ElementInterface[] $children
+ * @param array<\SimpleSAML\XML\Attribute> $attributes
+ *
+ * @throws \SimpleSAML\Assert\AssertionFailedException
+ */
+ public function __construct(
+ SAMLAnyURIValue $binding,
+ SAMLAnyURIValue $location,
+ ?SAMLAnyURIValue $responseLocation = null,
+ protected ?BooleanValue $supportsAsynchronous = null,
+ array $children = [],
+ array $attributes = [],
+ ) {
+ $this->setSupportsAsynchronous($supportsAsynchronous);
+
+ parent::__construct($binding, $location, $responseLocation, $children, $attributes);
+ }
+
+
+ /**
+ * Initialize a SingleLogoutService.
+ *
+ * @param \Dom\Element $xml The XML element we should load.
+ *
+ * @throws \SimpleSAML\XMLSchema\Exception\InvalidDOMElementException
+ * if the qualified name of the supplied element is wrong
+ * @throws \SimpleSAML\XMLSchema\Exception\MissingAttributeException
+ * if the supplied element is missing any of the mandatory attributes
+ */
+ public static function fromXML(Dom\Element $xml): static
+ {
+ $qualifiedName = static::getClassName(static::class);
+ Assert::eq(
+ $xml->localName,
+ $qualifiedName,
+ 'Unexpected name for endpoint: ' . $xml->localName . '. Expected: ' . $qualifiedName . '.',
+ InvalidDOMElementException::class,
+ );
+
+ return new static(
+ self::getAttribute($xml, 'Binding', SAMLAnyURIValue::class),
+ self::getAttribute($xml, 'Location', SAMLAnyURIValue::class),
+ self::getOptionalAttribute($xml, 'ResponseLocation', SAMLAnyURIValue::class, null),
+ self::getOptionalAttribute($xml, 'supportsAsynchronous', BooleanValue::class, null),
+ self::getChildElementsFromXML($xml),
+ self::getAttributesNSFromXML($xml),
+ );
+ }
+
+
+ /**
+ * Add this endpoint to an XML element.
+ *
+ * @param \Dom\Element $parent The element we should append this endpoint to.
+ */
+ public function toXML(?Dom\Element $parent = null): Dom\Element
+ {
+ $e = parent::toXML($parent);
+
+ if ($this->getSupportsAsynchronous() !== null) {
+ $e->setAttribute('supportsAsynchronous', $this->getSupportsAsynchronous()->getValue());
+ }
+
+ return $e;
+ }
}
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/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 @@
+