From 98df2221c5af8175095228eb83f4f510c714414b Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Tue, 27 Feb 2024 18:26:14 +0100 Subject: [PATCH] Implement saml:Statement --- src/SAML11/XML/saml/AbstractStatement.php | 123 ----------------- src/SAML11/XML/saml/AbstractStatementType.php | 113 ++++++++++++++- src/SAML11/XML/saml/UnknownStatement.php | 50 +++++++ tests/resources/schemas/simplesamlphp.xsd | 46 +++++++ tests/resources/xml/saml_Statement.xml | 3 + tests/src/SAML11/CustomStatement.php | 101 ++++++++++++++ tests/src/SAML11/XML/saml/StatementTest.php | 129 ++++++++++++++++++ 7 files changed, 440 insertions(+), 125 deletions(-) delete mode 100644 src/SAML11/XML/saml/AbstractStatement.php create mode 100644 src/SAML11/XML/saml/UnknownStatement.php create mode 100644 tests/resources/schemas/simplesamlphp.xsd create mode 100644 tests/resources/xml/saml_Statement.xml create mode 100644 tests/src/SAML11/CustomStatement.php create mode 100644 tests/src/SAML11/XML/saml/StatementTest.php diff --git a/src/SAML11/XML/saml/AbstractStatement.php b/src/SAML11/XML/saml/AbstractStatement.php deleted file mode 100644 index 928e25d..0000000 --- a/src/SAML11/XML/saml/AbstractStatement.php +++ /dev/null @@ -1,123 +0,0 @@ - extension point. - * - * @package simplesamlphp/saml11 - */ -abstract class AbstractStatement extends AbstractStatementType implements ExtensionPointInterface -{ - use ExtensionPointTrait; - - /** @var string */ - public const LOCALNAME = 'Statement'; - - - /** - * Initialize a custom saml:Statement element. - * - * @param string $type - */ - protected function __construct( - protected string $type, - ) { - } - - - /** - * @inheritDoc - */ - public function getXsiType(): string - { - return $this->type; - } - - - /** - * Convert an XML element into a Statement. - * - * @param \DOMElement $xml The root XML element - * @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, 'Statement', InvalidDOMElementException::class); - Assert::same($xml->namespaceURI, C::NS_SAML, InvalidDOMElementException::class); - Assert::true( - $xml->hasAttributeNS(C::NS_XSI, 'type'), - 'Missing required xsi:type in element.', - SchemaViolationException::class, - ); - - $type = $xml->getAttributeNS(C::NS_XSI, 'type'); - Assert::validQName($type, SchemaViolationException::class); - - // first, try to resolve the type to a full namespaced version - $qname = explode(':', $type, 2); - if (count($qname) === 2) { - list($prefix, $element) = $qname; - } else { - $prefix = null; - list($element) = $qname; - } - $ns = $xml->lookupNamespaceUri($prefix); - $type = ($ns === null) ? $element : implode(':', [$ns, $element]); - - // now check if we have a handler registered for it - $handler = Utils::getContainer()->getExtensionHandler($type); - if ($handler === null) { - // we don't have a handler, proceed with unknown statement - return new UnknownStatement(new Chunk($xml), $type); - } - - Assert::subclassOf( - $handler, - AbstractStatement::class, - 'Elements implementing Statement must extend \SimpleSAML\SAML11\XML\saml\AbstractStatement.', - ); - return $handler::fromXML($xml); - } - - - /** - * Convert this Statement to XML. - * - * @param \DOMElement $parent The element we are converting to XML. - * @return \DOMElement The XML element after adding the data corresponding to this Statement. - */ - public function toXML(DOMElement $parent = null): DOMElement - { - $e = $this->instantiateParentElement($parent); - $e->setAttributeNS( - 'http://www.w3.org/2000/xmlns/', - 'xmlns:' . static::getXsiTypePrefix(), - static::getXsiTypeNamespaceURI(), - ); - - $type = new XMLAttribute(C::NS_XSI, 'xsi', 'type', $this->getXsiType()); - $type->toXML($e); - - return $e; - } -} diff --git a/src/SAML11/XML/saml/AbstractStatementType.php b/src/SAML11/XML/saml/AbstractStatementType.php index 498699e..b8eed47 100644 --- a/src/SAML11/XML/saml/AbstractStatementType.php +++ b/src/SAML11/XML/saml/AbstractStatementType.php @@ -4,11 +4,120 @@ namespace SimpleSAML\SAML11\XML\saml; +use DOMElement; +use SimpleSAML\Assert\Assert; +use SimpleSAML\SAML11\Constants as C; +use SimpleSAML\SAML11\Utils; +use SimpleSAML\SAML11\XML\ExtensionPointInterface; +use SimpleSAML\SAML11\XML\ExtensionPointTrait; +use SimpleSAML\XML\Attribute as XMLAttribute; +use SimpleSAML\XML\Chunk; +use SimpleSAML\XML\Exception\InvalidDOMElementException; +use SimpleSAML\XML\Exception\SchemaViolationException; + +use function count; +use function explode; + /** - * Base abstract class for all Statement types. + * Class implementing the extension point. * * @package simplesamlphp/saml11 */ -abstract class AbstractStatementType extends AbstractSamlElement +abstract class AbstractStatementType extends AbstractSamlElement implements ExtensionPointInterface { + use ExtensionPointTrait; + + /** @var string */ + public const LOCALNAME = 'Statement'; + + + /** + * Initialize a custom saml:Statement element. + * + * @param string $type + */ + protected function __construct( + protected string $type, + ) { + } + + + /** + * @inheritDoc + */ + public function getXsiType(): string + { + return $this->type; + } + + + /** + * Convert an XML element into a Statement. + * + * @param \DOMElement $xml The root XML element + * @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, 'Statement', InvalidDOMElementException::class); + Assert::same($xml->namespaceURI, C::NS_SAML, InvalidDOMElementException::class); + Assert::true( + $xml->hasAttributeNS(C::NS_XSI, 'type'), + 'Missing required xsi:type in element.', + SchemaViolationException::class, + ); + + $type = $xml->getAttributeNS(C::NS_XSI, 'type'); + Assert::validQName($type, SchemaViolationException::class); + + // first, try to resolve the type to a full namespaced version + $qname = explode(':', $type, 2); + if (count($qname) === 2) { + list($prefix, $element) = $qname; + } else { + $prefix = null; + list($element) = $qname; + } + $ns = $xml->lookupNamespaceUri($prefix); + $type = ($ns === null) ? $element : implode(':', [$ns, $element]); + + // now check if we have a handler registered for it + $handler = Utils::getContainer()->getExtensionHandler($type); + if ($handler === null) { + // we don't have a handler, proceed with unknown statement + return new UnknownStatement(new Chunk($xml), $type); + } + + Assert::subclassOf( + $handler, + AbstractStatement::class, + 'Elements implementing Statement must extend \SimpleSAML\SAML11\XML\saml\AbstractStatement.', + ); + return $handler::fromXML($xml); + } + + + /** + * Convert this Statement to XML. + * + * @param \DOMElement $parent The element we are converting to XML. + * @return \DOMElement The XML element after adding the data corresponding to this Statement. + */ + public function toXML(DOMElement $parent = null): DOMElement + { + $e = $this->instantiateParentElement($parent); + $e->setAttributeNS( + 'http://www.w3.org/2000/xmlns/', + 'xmlns:' . static::getXsiTypePrefix(), + static::getXsiTypeNamespaceURI(), + ); + + $type = new XMLAttribute(C::NS_XSI, 'xsi', 'type', $this->getXsiType()); + $type->toXML($e); + + return $e; + } } diff --git a/src/SAML11/XML/saml/UnknownStatement.php b/src/SAML11/XML/saml/UnknownStatement.php new file mode 100644 index 0000000..bb3c55d --- /dev/null +++ b/src/SAML11/XML/saml/UnknownStatement.php @@ -0,0 +1,50 @@ +chunk; + } + + + /** + * Convert this unknown statement to XML. + * + * @param \DOMElement|null $parent The element we are converting to XML. + * @return \DOMElement The XML element after adding the data corresponding to this unknown statement. + */ + public function toXML(DOMElement $parent = null): DOMElement + { + return $this->getRawStatement()->toXML($parent); + } +} diff --git a/tests/resources/schemas/simplesamlphp.xsd b/tests/resources/schemas/simplesamlphp.xsd new file mode 100644 index 0000000..b607d41 --- /dev/null +++ b/tests/resources/schemas/simplesamlphp.xsd @@ -0,0 +1,46 @@ + + + + + + ]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/resources/xml/saml_Statement.xml b/tests/resources/xml/saml_Statement.xml new file mode 100644 index 0000000..84f9724 --- /dev/null +++ b/tests/resources/xml/saml_Statement.xml @@ -0,0 +1,3 @@ + + urn:some:audience + diff --git a/tests/src/SAML11/CustomStatement.php b/tests/src/SAML11/CustomStatement.php new file mode 100644 index 0000000..f208f3e --- /dev/null +++ b/tests/src/SAML11/CustomStatement.php @@ -0,0 +1,101 @@ +audience; + } + + + /** + * Convert XML into an Statement + * + * @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, 'Statement', InvalidDOMElementException::class); + Assert::notNull($xml->namespaceURI, InvalidDOMElementException::class); + Assert::same($xml->namespaceURI, AbstractStatementType::NS, InvalidDOMElementException::class); + Assert::true( + $xml->hasAttributeNS(C::NS_XSI, 'type'), + 'Missing required xsi:type in element.', + InvalidDOMElementException::class, + ); + + $type = $xml->getAttributeNS(C::NS_XSI, 'type'); + Assert::same($type, self::XSI_TYPE_PREFIX . ':' . self::XSI_TYPE_NAME); + + $audience = Audience::getChildrenOfClass($xml); + + return new static($audience); + } + + + /** + * Convert this Statement to XML. + * + * @param \DOMElement $parent The element we are converting to XML. + * @return \DOMElement The XML element after adding the data corresponding to this Statement. + */ + public function toXML(DOMElement $parent = null): DOMElement + { + $e = parent::toXML($parent); + + foreach ($this->audience as $audience) { + $audience->toXML($e); + } + + return $e; + } +} diff --git a/tests/src/SAML11/XML/saml/StatementTest.php b/tests/src/SAML11/XML/saml/StatementTest.php new file mode 100644 index 0000000..125996a --- /dev/null +++ b/tests/src/SAML11/XML/saml/StatementTest.php @@ -0,0 +1,129 @@ +registerExtensionHandler(CustomStatement::class); + ContainerSingleton::setContainer($container); + } + + + /** + */ + public static function tearDownAfterClass(): void + { + ContainerSingleton::setContainer(self::$containerBackup); + } + + + // marshalling + + + /** + */ + public function testMarshalling(): void + { + $statement = new CustomStatement( + [new Audience('urn:some:audience')], + ); + + $this->assertEquals( + self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement), + strval($statement), + ); + } + + + // unmarshalling + + + /** + * Test unmarshalling a registered class + */ + public function testUnmarshalling(): void + { + $statement = CustomStatement::fromXML(self::$xmlRepresentation->documentElement); + $this->assertInstanceOf(CustomStatement::class, $statement); + + $this->assertEquals('ssp:CustomStatementType', $statement->getXsiType()); + $audience = $statement->getAudience(); + $this->assertCount(1, $audience); + $this->assertEquals('urn:some:audience', $audience[0]->getContent()); + + $this->assertEquals( + self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement), + strval($statement), + ); + } + + + /** + */ + public function testUnmarshallingUnregistered(): void + { + $element = clone self::$xmlRepresentation->documentElement; + $element->setAttributeNS(C::NS_XSI, 'xsi:type', 'ssp:UnknownStatementType'); + + $statement = AbstractStatementType::fromXML($element); + + $this->assertInstanceOf(UnknownStatement::class, $statement); + $this->assertEquals('urn:x-simplesamlphp:namespace:UnknownStatementType', $statement->getXsiType()); + + $chunk = $statement->getRawStatement(); + $this->assertEquals('saml', $chunk->getPrefix()); + $this->assertEquals('Statement', $chunk->getLocalName()); + $this->assertEquals(C::NS_SAML, $chunk->getNamespaceURI()); + + $this->assertEquals($element->ownerDocument?->saveXML($element), strval($statement)); + } +}