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 +