From 3b7b3047ca644f00f57bc025264b23052713bf00 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Sun, 15 Dec 2024 10:42:59 +0100 Subject: [PATCH] Perform optional schema validation (default: true) --- src/Binding.php | 48 ++++++++++++++++++++++++---- src/Binding/HTTPPost.php | 6 ++-- src/Binding/HTTPRedirect.php | 5 ++- src/Binding/SOAP.php | 6 +++- src/SAML2/Entity/ServiceProvider.php | 3 ++ src/SOAPClient.php | 11 +++++++ 6 files changed, 69 insertions(+), 10 deletions(-) diff --git a/src/Binding.php b/src/Binding.php index e4ca793d7..f863646ca 100644 --- a/src/Binding.php +++ b/src/Binding.php @@ -25,6 +25,20 @@ */ abstract class Binding { + /** + * The schema to be used for schema validation + * + * @var string + */ + protected static string $schemaFile = 'resources/schemas/saml-schema-protocol-2.0.xsd'; + + /** + * Whether or not to perform schema validation + * + * @var bool + */ + protected bool $schemaValidation = true; + /** * The RelayState associated with the message. * @@ -157,7 +171,20 @@ public function getDestination(): ?string /** - * Set the RelayState associated with he message. + * Override the destination of a message. + * + * Set to null to use the destination set in the message. + * + * @param string|null $destination The destination the message should be delivered to. + */ + public function setDestination(?string $destination = null): void + { + $this->destination = $destination; + } + + + /** + * Set the RelayState associated with the message. * * @param string|null $relayState The RelayState. */ @@ -179,15 +206,24 @@ public function getRelayState(): ?string /** - * Override the destination of a message. + * Set the schema validation for the message. * - * Set to null to use the destination set in the message. + * @param bool $schemaValidation + */ + public function setSchemaValidation(bool $schemaValidation): void + { + $this->schemaValidation = $schemaValidation; + } + + + /** + * Get the schema validation setting. * - * @param string|null $destination The destination the message should be delivered to. + * @return bool */ - public function setDestination(?string $destination = null): void + public function getSchemaValidation(): bool { - $this->destination = $destination; + return $this->schemaValidation; } diff --git a/src/Binding/HTTPPost.php b/src/Binding/HTTPPost.php index acfcda229..0228f9c91 100644 --- a/src/Binding/HTTPPost.php +++ b/src/Binding/HTTPPost.php @@ -91,9 +91,11 @@ public function receive(ServerRequestInterface $request): AbstractMessage } $msgStr = base64_decode($msgStr, true); - $msgStr = DOMDocumentFactory::fromString($msgStr)->saveXML(); - $document = DOMDocumentFactory::fromString($msgStr); + $document = DOMDocumentFactory::fromString( + xml: $msgStr, + schemaFile: $this->getSchemaValidation() ? self::$schemaFile : null, + ); Utils::getContainer()->debugMessage($document->documentElement, 'in'); $msg = MessageFactory::fromXML($document->documentElement); diff --git a/src/Binding/HTTPRedirect.php b/src/Binding/HTTPRedirect.php index f0357c01b..4dba36dfb 100644 --- a/src/Binding/HTTPRedirect.php +++ b/src/Binding/HTTPRedirect.php @@ -148,7 +148,10 @@ public function receive(ServerRequestInterface $request): AbstractMessage throw new Exception('Error while inflating SAML message.'); } - $document = DOMDocumentFactory::fromString($message); + $document = DOMDocumentFactory::fromString( + xml: $message, + schemaFile: $this->getSchemaValidation() ? self::$schemaFile : null, + ); Utils::getContainer()->debugMessage($document->documentElement, 'in'); $message = MessageFactory::fromXML($document->documentElement); diff --git a/src/Binding/SOAP.php b/src/Binding/SOAP.php index 9b0b35ec1..04c35e5af 100644 --- a/src/Binding/SOAP.php +++ b/src/Binding/SOAP.php @@ -101,8 +101,12 @@ public function receive(/** @scrutinizer ignore-unused */ServerRequestInterface $xpCache = XPath::getXPath($document->documentElement); /** @var \DOMElement[] $results */ $results = XPath::xpQuery($xml, '/SOAP-ENV:Envelope/SOAP-ENV:Body/*[1]', $xpCache); + $document = DOMDocumentFactory::fromString( + xml: $results[0]->ownerDocument->saveXML($results[0]), + schemaFile: $this->getSchemaValidation() ? self::$schemaFile : null, + ); - return MessageFactory::fromXML($results[0]); + return MessageFactory::fromXML($document->documentElement); } diff --git a/src/SAML2/Entity/ServiceProvider.php b/src/SAML2/Entity/ServiceProvider.php index d3deb4fd8..c317074da 100644 --- a/src/SAML2/Entity/ServiceProvider.php +++ b/src/SAML2/Entity/ServiceProvider.php @@ -57,6 +57,7 @@ final class ServiceProvider protected bool $responseWasSigned; /** + * @param bool $performSchemaValidation Whether messages must be validated against the schema * @param bool $encryptedAssertions Whether assertions must be encrypted * @param bool $disableScoping Whether to send the samlp:Scoping element in requests * @param bool $enableUnsolicited Whether to process unsolicited responses @@ -68,6 +69,7 @@ final class ServiceProvider public function __construct( protected MetadataProviderInterface $metadataProvider, protected Metadata\ServiceProvider $spMetadata, + protected readonly bool $performSchemaValidation = true, protected readonly bool $encryptedAssertions = false, protected readonly bool $disableScoping = false, protected readonly bool $enableUnsolicited = false, @@ -139,6 +141,7 @@ public function receiveResponse(ServerRequestInterface $request): Response $binding->setSPMetadata($this->spMetadata); } + $binding->setSchemaValidation($this->performSchemaValidation); $rawResponse = $binding->receive($request); Assert::isInstanceOf($rawResponse, Response::class, ResourceNotRecognizedException::class); // Wrong type of msg diff --git a/src/SOAPClient.php b/src/SOAPClient.php index 96fc762c8..301b80a28 100644 --- a/src/SOAPClient.php +++ b/src/SOAPClient.php @@ -172,6 +172,17 @@ public function send( ); } + $xpCache = XPath::getXPath($document->documentElement); + /** @var \DOMElement[] $results */ + $results = XPath::xpQuery($xml, '/SOAP-ENV:Envelope/SOAP-ENV:Body/*[1]', $xpCache); + + // This is already too late to perform schema validation. + // TODO: refactor the SOAPClient and artifact binding. The SOAPClient should be a generic tool from xml-soap + $document = DOMDocumentFactory::fromString( + xml: $results[0]->ownerDocument->saveXML(), + schemaFile: $this->getSchemaValidation() ? self::$schemaFile : null, + ); + // Extract the message from the response /** @var \SimpleSAML\XML\SerializableElementInterface[] $messages */ $messages = $env->getBody()->getElements();