diff --git a/src/XML/sp/AbstractIssuedTokenType.php b/src/XML/sp/AbstractIssuedTokenType.php
new file mode 100644
index 00000000..22a85a18
--- /dev/null
+++ b/src/XML/sp/AbstractIssuedTokenType.php
@@ -0,0 +1,185 @@
+ $elts
+ * @param list<\SimpleSAML\XML\Attribute> $namespacedAttributes
+ */
+ final public function __construct(
+ protected RequestSecurityTokenTemplate $requestSecurityTokenTemplate,
+ protected Issuer|IssuerName|null $issuer,
+ ?IncludeToken $includeToken = null,
+ array $elts = [],
+ array $namespacedAttributes = []
+ ) {
+ $this->setIncludeToken($includeToken);
+ $this->setElements($elts);
+ $this->setAttributesNS($namespacedAttributes);
+ }
+
+
+ /**
+ * Collect the value of the Issuer property.
+ *
+ * @return \SimpleSAML\WSSecurity\XML\sp\Issuer|\SimpleSAML\WSSecurity\XML\sp\IssuerName
+ */
+ public function getIssuer(): Issuer|IssuerName
+ {
+ return $this->issuer;
+ }
+
+
+ /**
+ * Collect the value of the RequestSecurityTokenTemplate property.
+ *
+ * @return \SimpleSAML\WSSecurity\XML\sp\RequestSecurityTokenTemplate
+ */
+ public function getRequestSecurityTokenTemplate(): RequestSecurityTokenTemplate
+ {
+ return $this->requestSecurityTokenTemplate;
+ }
+
+
+ /**
+ * Initialize an IssuedTokenType.
+ *
+ * Note: this method cannot be used when extending this class, if the constructor has a different signature.
+ *
+ * @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
+ {
+ $qualifiedName = static::getClassName(static::class);
+ Assert::eq(
+ $xml->localName,
+ $qualifiedName,
+ sprintf('Unexpected name for IssuedTokenType: %s. Expected: %s.', $xml->localName, $qualifiedName),
+ InvalidDOMElementException::class
+ );
+
+ $issuer = Issuer::getChildrenOfClass($xml);
+ $issuerName = IssuerName::getChildrenOfClass($xml);
+ $issuer = array_merge($issuer, $issuerName);
+
+ $requestSecurityTokenTemplate = RequestSecurityTokenTemplate::getChildrenOfClass($xml);
+ Assert::minCount($requestSecurityTokenTemplate, 1, MissingElementException::class);
+ Assert::maxCount($requestSecurityTokenTemplate, 1, TooManyElementsException::class);
+
+ try {
+ $includeToken = IncludeToken::from(self::getOptionalAttribute($xml, 'IncludeToken', null));
+ } catch (ValueError) {
+ self::getOptionalAttribute($xml, 'IncludeToken', null);
+ }
+
+ $elements = [];
+ foreach ($xml->childNodes as $element) {
+ if ($element->namespaceURI === static::NS) {
+ continue;
+ } elseif (!($element instanceof DOMElement)) {
+ continue;
+ }
+
+ $elements[] = new Chunk($element);
+ }
+
+ $namespacedAttributes = self::getAttributesNSFromXML($xml);
+ foreach ($namespacedAttributes as $i => $attr) {
+ if ($attr->getNamespaceURI() === null) {
+ if ($attr->getAttrName() === 'IncludeToken') {
+ unset($namespacedAttributes[$i]);
+ break;
+ }
+ }
+ }
+
+
+ return new static(
+ $requestSecurityTokenTemplate[0],
+ array_pop($issuer),
+ $includeToken,
+ $elements,
+ $namespacedAttributes,
+ );
+ }
+
+
+ /**
+ * Convert this element to XML.
+ *
+ * @param \DOMElement|null $parent The element we should append this element to.
+ * @return \DOMElement
+ */
+ public function toXML(DOMElement $parent = null): DOMElement
+ {
+ $e = $this->instantiateParentElement($parent);
+
+ if ($this->getIncludeToken() !== null) {
+ $e->setAttribute(
+ 'IncludeToken',
+ is_string($this->getIncludeToken()) ? $this->getIncludeToken() : $this->getIncludeToken()->value,
+ );
+ }
+
+ if ($this->getIssuer() !== null) {
+ $this->getIssuer()->toXML($e);
+ }
+
+ $this->getRequestSecurityTokenTemplate()->toXML($e);
+
+ foreach ($this->getElements() as $elt) {
+ $elt->toXML($e);
+ }
+
+ foreach ($this->getAttributesNS() as $attr) {
+ $attr->toXML($e);
+ }
+
+ return $e;
+ }
+}
diff --git a/src/XML/sp/AbstractRequestSecurityTokenTemplateType.php b/src/XML/sp/AbstractRequestSecurityTokenTemplateType.php
new file mode 100644
index 00000000..78910483
--- /dev/null
+++ b/src/XML/sp/AbstractRequestSecurityTokenTemplateType.php
@@ -0,0 +1,154 @@
+ $elts
+ * @param list<\SimpleSAML\XML\Attribute> $namespacedAttributes
+ */
+ final public function __construct(
+ protected ?string $trustVersion = null,
+ array $elts = [],
+ array $namespacedAttributes = []
+ ) {
+ Assert::nullOrValidURI($trustVersion);
+
+ $this->setElements($elts);
+ $this->setAttributesNS($namespacedAttributes);
+ }
+
+
+ /**
+ * Collect the value of the trustVersion property.
+ *
+ * @return string|null
+ */
+ public function getTrustVersion(): ?string
+ {
+ return $this->trustVersion;
+ }
+
+
+ /**
+ * 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->trustVersion) && empty($this->elements) && empty($this->namespacedAttributes);
+ }
+
+
+ /**
+ * Initialize an RequestSecurityTokenTemplateType.
+ *
+ * Note: this method cannot be used when extending this class, if the constructor has a different signature.
+ *
+ * @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
+ {
+ $qualifiedName = static::getClassName(static::class);
+ Assert::eq(
+ $xml->localName,
+ $qualifiedName,
+ sprintf(
+ 'Unexpected name for RequestSecurityTokenTemplateType: %s. Expected: %s.',
+ $xml->localName,
+ $qualifiedName,
+ ),
+ InvalidDOMElementException::class
+ );
+
+ $elements = [];
+ foreach ($xml->childNodes as $element) {
+ if ($element->namespaceURI === static::NS) {
+ continue;
+ } elseif (!($element instanceof DOMElement)) {
+ continue;
+ }
+
+ $elements[] = new Chunk($element);
+ }
+
+ $namespacedAttributes = self::getAttributesNSFromXML($xml);
+ foreach ($namespacedAttributes as $i => $attr) {
+ if ($attr->getNamespaceURI() === null) {
+ if ($attr->getAttrName() === 'TrustVersion') {
+ unset($namespacedAttributes[$i]);
+ break;
+ }
+ }
+ }
+
+ return new static(
+ self::getOptionalAttribute($xml, 'TrustVersion', null),
+ $elements,
+ $namespacedAttributes,
+ );
+ }
+
+
+ /**
+ * Convert this element to XML.
+ *
+ * @param \DOMElement|null $parent The element we should append this element to.
+ * @return \DOMElement
+ */
+ public function toXML(DOMElement $parent = null): DOMElement
+ {
+ $e = $this->instantiateParentElement($parent);
+
+ if ($this->getTrustVersion() !== null) {
+ $e->setAttribute('TrustVersion', $this->getTrustVersion());
+ }
+
+ foreach ($this->getElements() as $elt) {
+ $elt->toXML($e);
+ }
+
+ foreach ($this->getAttributesNS() as $attr) {
+ $attr->toXML($e);
+ }
+
+ return $e;
+ }
+}
diff --git a/src/XML/sp/IncludeTokenTypeTrait.php b/src/XML/sp/IncludeTokenTypeTrait.php
index fe7dbc83..f08b2b07 100644
--- a/src/XML/sp/IncludeTokenTypeTrait.php
+++ b/src/XML/sp/IncludeTokenTypeTrait.php
@@ -19,17 +19,17 @@ trait IncludeTokenTypeTrait
/**
* The included token.
*
- * @var \SimpleSAML\WSSecurity\XML\sp\IncludeToken|string
+ * @var \SimpleSAML\WSSecurity\XML\sp\IncludeToken|string|null
*/
- protected IncludeToken|string $includeToken;
+ protected IncludeToken|string|null $includeToken;
/**
* Collect the value of the includeToken-property
*
- * @return \SimpleSAML\WSSecurity\XML\sp\IncludeToken|string
+ * @return \SimpleSAML\WSSecurity\XML\sp\IncludeToken|string|null
*/
- public function getIncludeToken(): IncludeToken|string
+ public function getIncludeToken(): IncludeToken|string|null
{
return $this->includeToken;
}
@@ -38,9 +38,9 @@ public function getIncludeToken(): IncludeToken|string
/**
* Set the value of the includeToken-property
*
- * @param \SimpleSAML\WSSecurity\XML\sp\IncludeToken|string $includeToken
+ * @param \SimpleSAML\WSSecurity\XML\sp\IncludeToken|string|null $includeToken
*/
- protected function setIncludeToken(IncludeToken|string $includeToken): void
+ protected function setIncludeToken(IncludeToken|string|null $includeToken): void
{
if (is_string($includeToken)) {
Assert::validURI($includeToken);
diff --git a/src/XML/sp/IssuedToken.php b/src/XML/sp/IssuedToken.php
new file mode 100644
index 00000000..d0b84212
--- /dev/null
+++ b/src/XML/sp/IssuedToken.php
@@ -0,0 +1,16 @@
+setContent($content);
+ }
+
+
+ /**
+ * Validate the content of the element.
+ *
+ * @param string $content The value to go in the XML textContent
+ * @throws \SimpleSAML\XML\Exception\SchemaViolationExcetion on failure
+ * @return void
+ */
+ protected function validateContent(string $content): void
+ {
+ Assert::validURI($content);
+ }
+}
diff --git a/src/XML/sp/RequestSecurityTokenTemplate.php b/src/XML/sp/RequestSecurityTokenTemplate.php
new file mode 100644
index 00000000..8d907428
--- /dev/null
+++ b/src/XML/sp/RequestSecurityTokenTemplate.php
@@ -0,0 +1,14 @@
+some'
+ )->documentElement);
+ $requestSecurityTokenTemplate = new RequestSecurityTokenTemplate('urn:x-simplesamlphp:version', [$chunk], [$attr]);
+
+ $issuedToken = new IssuedToken($requestSecurityTokenTemplate, $issuer, IncludeToken::Always, [$chunk], [$attr]);
+ $issuedTokenElement = $issuedToken->toXML();
+
+ // Test for a Issuer
+ $xpCache = XPath::getXPath($issuedTokenElement);
+ $issuedTokenElements = XPath::xpQuery($issuedTokenElement, './sp:IssuerName', $xpCache);
+ $this->assertCount(1, $issuedTokenElements);
+
+ // Test ordering of IssuedToken contents
+ /** @psalm-var \DOMElement[] $issuedTokenElements */
+ $issuedTokenElements = XPath::xpQuery($issuedTokenElement, './sp:IssuerName/following-sibling::*', $xpCache);
+ $this->assertCount(2, $issuedTokenElements);
+ $this->assertEquals('sp:RequestSecurityTokenTemplate', $issuedTokenElements[0]->tagName);
+ $this->assertEquals('ssp:Chunk', $issuedTokenElements[1]->tagName);
+ }
+
+
+ // test marshalling
+
+
+ /**
+ * Test that creating a RequiredParts from scratch works.
+ */
+ public function testMarshalling(): void
+ {
+ $attr = new XMLAttribute(C::NAMESPACE, 'ssp', 'attr1', 'value1');
+ $chunk = new Chunk(DOMDocumentFactory::fromString(
+ 'some'
+ )->documentElement);
+
+ $issuer = new IssuerName('urn:x-simplesamlphp:issuer');
+ $requestSecurityTokenTemplate = new RequestSecurityTokenTemplate('urn:x-simplesamlphp:version', [$chunk], [$attr]);
+
+ $issuedToken = new IssuedToken($requestSecurityTokenTemplate, $issuer, IncludeToken::Always, [$chunk], [$attr]);
+ $this->assertEquals(
+ self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement),
+ strval($issuedToken),
+ );
+ }
+}
diff --git a/tests/WSSecurity/XML/sp/IssuerNameTest.php b/tests/WSSecurity/XML/sp/IssuerNameTest.php
new file mode 100644
index 00000000..2c33e593
--- /dev/null
+++ b/tests/WSSecurity/XML/sp/IssuerNameTest.php
@@ -0,0 +1,57 @@
+assertEquals(
+ self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement),
+ strval($issuerName),
+ );
+ }
+}
diff --git a/tests/WSSecurity/XML/sp/IssuerTest.php b/tests/WSSecurity/XML/sp/IssuerTest.php
new file mode 100644
index 00000000..a72cedac
--- /dev/null
+++ b/tests/WSSecurity/XML/sp/IssuerTest.php
@@ -0,0 +1,100 @@
+Pears'
+ )->documentElement;
+
+ self::$metadataContent = DOMDocumentFactory::fromString(
+ 'Apples'
+ )->documentElement;
+
+ self::$customContent = DOMDocumentFactory::fromString(
+ 'SomeChunk'
+ )->documentElement;
+ }
+
+
+ // test marshalling
+
+
+ /**
+ * Test creating an Issuer object from scratch.
+ */
+ public function testMarshalling(): void
+ {
+ $attr1 = new Attribute('urn:x-simplesamlphp:namespace', 'ssp', 'test1', 'value1');
+ $attr2 = new Attribute('urn:x-simplesamlphp:namespace', 'ssp', 'test2', 'value2');
+ $attr3 = new Attribute('urn:x-simplesamlphp:namespace', 'ssp', 'test3', 'value3');
+ $attr4 = new Attribute('urn:x-simplesamlphp:namespace', 'ssp', 'test4', 'value4');
+
+ $referenceParameters = new ReferenceParameters([new Chunk(self::$referenceParametersContent)], [$attr4]);
+ $metadata = new Metadata([new Chunk(self::$metadataContent)], [$attr3]);
+ $chunk = new Chunk(self::$customContent);
+
+ $issuer = new Issuer(
+ new Address('https://login.microsoftonline.com/login.srf', [$attr2]),
+ [$referenceParameters],
+ [$metadata],
+ [$chunk],
+ [$attr1],
+ );
+
+ $this->assertEquals(
+ self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement),
+ strval($issuer)
+ );
+ }
+}
diff --git a/tests/WSSecurity/XML/sp/NestedPolicyTypeTestTrait.php b/tests/WSSecurity/XML/sp/NestedPolicyTypeTestTrait.php
index cdcb3fc9..e46b0529 100644
--- a/tests/WSSecurity/XML/sp/NestedPolicyTypeTestTrait.php
+++ b/tests/WSSecurity/XML/sp/NestedPolicyTypeTestTrait.php
@@ -64,9 +64,9 @@ public function testMarshallingWithoutNSAttr(): void
*/
public function testMarshallingWithoutChildren(): void
{
- $xml = '';
+ $xml = '';
$localName = static::$testedClass::getLocalName();
- $xml = sprintf($xml, $localName, C::NS_SEC_POLICY, $localName);
+ $xml = sprintf($xml, $localName, C::NS_SEC_POLICY, C::NAMESPACE);
$xmlRepresentation = DOMDocumentFactory::fromString($xml);
$qns = new static::$testedClass([], [static::$attr]);
diff --git a/tests/WSSecurity/XML/sp/RequestSecurityTokenTemplateTest.php b/tests/WSSecurity/XML/sp/RequestSecurityTokenTemplateTest.php
new file mode 100644
index 00000000..04ea5c17
--- /dev/null
+++ b/tests/WSSecurity/XML/sp/RequestSecurityTokenTemplateTest.php
@@ -0,0 +1,78 @@
+some'
+ )->documentElement);
+
+ $RequestSecurityTokenTemplateElements = new RequestSecurityTokenTemplate('urn:x-simplesamlphp:version', [$chunk], [$attr]);
+ $this->assertEquals(
+ self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement),
+ strval($RequestSecurityTokenTemplateElements),
+ );
+ }
+
+
+ /**
+ * Adding an empty RequestSecurityTokenTemplate element should yield an empty element.
+ */
+ public function testMarshallingEmptyElement(): void
+ {
+ $spns = C::NS_SEC_POLICY;
+ $RequestSecurityTokenTemplate = new RequestSecurityTokenTemplate();
+ $this->assertEquals(
+ "",
+ strval($RequestSecurityTokenTemplate),
+ );
+ $this->assertTrue($RequestSecurityTokenTemplate->isEmptyElement());
+ }
+}
diff --git a/tests/resources/xml/sp_IssuedToken.xml b/tests/resources/xml/sp_IssuedToken.xml
new file mode 100644
index 00000000..5d16f4f1
--- /dev/null
+++ b/tests/resources/xml/sp_IssuedToken.xml
@@ -0,0 +1,7 @@
+
+ urn:x-simplesamlphp:issuer
+
+ some
+
+ some
+
diff --git a/tests/resources/xml/sp_Issuer.xml b/tests/resources/xml/sp_Issuer.xml
new file mode 100644
index 00000000..4fd8d0b9
--- /dev/null
+++ b/tests/resources/xml/sp_Issuer.xml
@@ -0,0 +1,14 @@
+
+ https://login.microsoftonline.com/login.srf
+
+
+ Pears
+
+
+
+
+ Apples
+
+
+ SomeChunk
+
diff --git a/tests/resources/xml/sp_IssuerName.xml b/tests/resources/xml/sp_IssuerName.xml
new file mode 100644
index 00000000..309293cf
--- /dev/null
+++ b/tests/resources/xml/sp_IssuerName.xml
@@ -0,0 +1 @@
+urn:x-simplesamlphp:namespace
diff --git a/tests/resources/xml/sp_RequestSecurityTokenTemplate.xml b/tests/resources/xml/sp_RequestSecurityTokenTemplate.xml
new file mode 100644
index 00000000..cf3dba3d
--- /dev/null
+++ b/tests/resources/xml/sp_RequestSecurityTokenTemplate.xml
@@ -0,0 +1,3 @@
+
+ some
+