From e19ed30ac75ce580601f3d1b3fdea92f8ebaa917 Mon Sep 17 00:00:00 2001 From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Date: Mon, 4 Aug 2025 12:09:18 +0200 Subject: [PATCH] feat(UI): xml validation (#8429) --- ui/CHANGELOG.md | 1 + .../integrations/saml/saml-config-form.tsx | 97 ++++++++++++++++++- 2 files changed, 95 insertions(+), 3 deletions(-) diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index 83661eb731..b16731cf73 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to the **Prowler UI** are documented in this file. - Lighthouse banner [(#8259)](https://github.com/prowler-cloud/prowler/pull/8259) - Amazon AWS S3 integration [(#8391)](https://github.com/prowler-cloud/prowler/pull/8391) - Github provider support [(#8405)](https://github.com/prowler-cloud/prowler/pull/8405) +- XML validation for SAML metadata in the UI [(#8429)](https://github.com/prowler-cloud/prowler/pull/8429) ### 🔄 Changed diff --git a/ui/components/integrations/saml/saml-config-form.tsx b/ui/components/integrations/saml/saml-config-form.tsx index c9e71d628d..63443d57cd 100644 --- a/ui/components/integrations/saml/saml-config-form.tsx +++ b/ui/components/integrations/saml/saml-config-form.tsx @@ -13,6 +13,96 @@ import { SnippetChip } from "@/components/ui/entities"; import { FormButtons } from "@/components/ui/form"; import { apiBaseUrl } from "@/lib"; +const validateXMLContent = ( + xmlContent: string, +): { isValid: boolean; error?: string } => { + try { + // Basic checks + if (!xmlContent || !xmlContent.trim()) { + return { + isValid: false, + error: "XML content is empty.", + }; + } + + const trimmedContent = xmlContent.trim(); + + // Check if it starts and ends with XML tags + if (!trimmedContent.startsWith("<") || !trimmedContent.endsWith(">")) { + return { + isValid: false, + error: "Content does not appear to be valid XML format.", + }; + } + + // Use DOMParser to validate XML structure + const parser = new DOMParser(); + const xmlDoc = parser.parseFromString(xmlContent, "text/xml"); + + // Check for parser errors + const parserError = xmlDoc.querySelector("parsererror"); + if (parserError) { + const errorText = parserError.textContent || "Unknown XML parsing error"; + return { + isValid: false, + error: `XML parsing error: ${errorText.substring(0, 100)}...`, + }; + } + + // Check if the document has a root element + if (!xmlDoc.documentElement) { + return { + isValid: false, + error: "XML does not have a valid root element.", + }; + } + + // Optional: Check for basic SAML metadata structure + const rootElement = xmlDoc.documentElement; + const rootTagName = rootElement.tagName.toLowerCase(); + + // Check if it looks like SAML metadata (common root elements) + const samlRootElements = [ + "entitydescriptor", + "entitiesDescriptor", + "metadata", + "md:entitydescriptor", + "md:entitiesdescriptor", + ]; + + const isSamlMetadata = samlRootElements.some((element) => + rootTagName.includes(element.toLowerCase()), + ); + + if (!isSamlMetadata) { + // Check for common SAML namespace attributes + const xmlString = xmlContent.toLowerCase(); + const hasSamlNamespace = + xmlString.includes("saml") || + xmlString.includes("urn:oasis:names:tc:saml") || + xmlString.includes("metadata"); + + if (!hasSamlNamespace) { + return { + isValid: false, + error: + "The XML file does not appear to be SAML metadata. Please ensure you're uploading the correct SAML metadata file from your Identity Provider.", + }; + } + } + + return { isValid: true }; + } catch (error) { + return { + isValid: false, + error: + error instanceof Error + ? error.message + : "Failed to validate XML content.", + }; + } +}; + export const SamlConfigForm = ({ setIsOpen, samlConfig, @@ -109,12 +199,13 @@ export const SamlConfigForm = ({ reader.onload = (e) => { const content = e.target?.result as string; - // Basic XML validation - if (!content.trim().startsWith("<") || !content.includes("