From 01fc8ce183e7433f98dcc24ab84e1186fe98cff2 Mon Sep 17 00:00:00 2001 From: stl-steve-moore Date: Thu, 8 Jun 2023 20:30:11 -0500 Subject: [PATCH 1/4] Begin path to testing the SOAP header for transactions sent by an Initiating Gateway Added a RequestHeader object and code to parse the SOAP header and to value header values using both XPath and labels. The labels will go away, and the XPath will remain. --- .../registrymsg/common/RequestHeader.java | 247 ++++++++++++++++++ .../common/RequestHeaderParser.java | 116 ++++++++ .../toolkit/testengine/engine/Validator.java | 173 ++++++++++++ .../nist/toolkit/utilities/xml/XmlUtil.java | 21 ++ .../SoapAssertion/SoapHeaderValidator.groovy | 185 +++++++++++++ .../SoapAssertion/StoredQueryValidator.groovy | 6 +- 6 files changed, 747 insertions(+), 1 deletion(-) create mode 100644 registry-msg-formats/src/main/java/gov/nist/toolkit/registrymsg/common/RequestHeader.java create mode 100644 registry-msg-formats/src/main/java/gov/nist/toolkit/registrymsg/common/RequestHeaderParser.java create mode 100644 xdstools2/src/main/webapp/toolkitx/testkit/plugins/SoapAssertion/SoapHeaderValidator.groovy diff --git a/registry-msg-formats/src/main/java/gov/nist/toolkit/registrymsg/common/RequestHeader.java b/registry-msg-formats/src/main/java/gov/nist/toolkit/registrymsg/common/RequestHeader.java new file mode 100644 index 0000000000..795419b5f1 --- /dev/null +++ b/registry-msg-formats/src/main/java/gov/nist/toolkit/registrymsg/common/RequestHeader.java @@ -0,0 +1,247 @@ +package gov.nist.toolkit.registrymsg.common; + +import org.apache.axiom.om.OMAttribute; +import org.apache.axiom.om.OMElement; + +import java.util.HashMap; +import java.util.List; + +public class RequestHeader { +// String home; +// String queryId; +// OMElement adhocQueryElement; +// OMElement adhocQueryRequestElement; +// OMAttribute homeAtt; +// String patientId = null; +// List documentEntryObjectTypeList = null; + OMElement omElement; + HashMap attributeStatement = new HashMap<>(); + String samlAssertionID = null; + String samlAssertionIssueInstant = null; + String samlAssertionVersion = null; + String samlAssertionIssuer = null; + String samlCanonicalizationMethodAlgorithm = null; + String samlSignatureMethodAlgorithm = null; + String samlDigestMethodAlgorithm = null; + String samlDigestValue = null; + String samlSignatureValue = null; + String samlX509Certificate = null; + String samlRSAKyValueModulus = null; + String samlRSAKeyValueExponent = null; + String samlNHINHomeCommunityID = null; + String samlIHEHomeCommunityID = null; + String samlPurposeOfUseCode = null; + String samlPurposeOfUseCodeSystem = null; + String samlPurposeOfUseCSP = null; + String samlPurposeOfUseValidatedAttributes = null; + + + /* + public String getHome() { + return home; + } + public String getQueryId() { + return queryId; + } + public OMElement getAdhocQueryElement() { + return adhocQueryElement; + } + public OMElement getRequestHeaderElement() { + return adhocQueryRequestElement; + } + public OMAttribute getHomeAtt() { + return homeAtt; + } + public String getPatientId() { return patientId; } + + public OMElement getAttributeStatementAttribute(String name) { + OMElement e = attributeStatement.get(name); + return e; + } + + public void setAttributeStatement(HashMap attributeStatement) { + this.attributeStatement = attributeStatement; + } + + public List getDocumentEntryObjectTypeList() { + return documentEntryObjectTypeList; + } + + */ + public OMElement getAttributeStatementAttribute(String name) { + OMElement e = attributeStatement.get(name); + return e; + } + + public OMElement getOmElement() { + return omElement; + } + + public void setOmElement(OMElement omElement) { + this.omElement = omElement; + } + + public HashMap getAttributeStatement() { + return attributeStatement; + } + + public String getSamlAssertionID() { + return samlAssertionID; + } + + public void setSamlAssertionID(String samlAssertionID) { + this.samlAssertionID = samlAssertionID; + } + + public String getSamlAssertionIssueInstant() { + return samlAssertionIssueInstant; + } + + public void setSamlAssertionIssueInstant(String samlAssertionIssueInstant) { + this.samlAssertionIssueInstant = samlAssertionIssueInstant; + } + + public String getSamlAssertionVersion() { + return samlAssertionVersion; + } + + public void setSamlAssertionVersion(String samlAssertionVersion) { + this.samlAssertionVersion = samlAssertionVersion; + } + + public String getSamlAssertionIssuer() { + return samlAssertionIssuer; + } + + public void setSamlAssertionIssuer(String samlAssertionIssuer) { + this.samlAssertionIssuer = samlAssertionIssuer; + } + + public String getSamlCanonicalizationMethodAlgorithm() { + return samlCanonicalizationMethodAlgorithm; + } + + public void setSamlCanonicalizationMethodAlgorithm(String samlCanonicalizationMethodAlgorithm) { + this.samlCanonicalizationMethodAlgorithm = samlCanonicalizationMethodAlgorithm; + } + + public String getSamlSignatureMethodAlgorithm() { + return samlSignatureMethodAlgorithm; + } + + public void setSamlSignatureMethodAlgorithm(String samlSignatureMethodAlgorithm) { + this.samlSignatureMethodAlgorithm = samlSignatureMethodAlgorithm; + } + + public String getSamlDigestMethodAlgorithm() { + return samlDigestMethodAlgorithm; + } + + public void setSamlDigestMethodAlgorithm(String samlDigestMethodAlgorithm) { + this.samlDigestMethodAlgorithm = samlDigestMethodAlgorithm; + } + + public String getSamlDigestValue() { + return samlDigestValue; + } + + public void setSamlDigestValue(String samlDigestValue) { + this.samlDigestValue = samlDigestValue; + } + + public String getSamlSignatureValue() { + return samlSignatureValue; + } + + public void setSamlSignatureValue(String samlSignatureValue) { + this.samlSignatureValue = samlSignatureValue; + } + + public String getSamlX509Certificate() { + return samlX509Certificate; + } + + public void setSamlX509Certificate(String samlX509Certificate) { + this.samlX509Certificate = samlX509Certificate; + } + + public String getSamlRSAKyValueModulus() { + return samlRSAKyValueModulus; + } + + public void setSamlRSAKyValueModulus(String samlRSAKyValueModulus) { + this.samlRSAKyValueModulus = samlRSAKyValueModulus; + } + + public String getSamlRSAKeyValueExponent() { + return samlRSAKeyValueExponent; + } + + public void setSamlRSAKeyValueExponent(String samlRSAKeyValueExponent) { + this.samlRSAKeyValueExponent = samlRSAKeyValueExponent; + } + + public String getSamlNHINHomeCommunityID() { + return samlNHINHomeCommunityID; + } + + public void setSamlNHINHomeCommunityID(String samlNHINHomeCommunityID) { + this.samlNHINHomeCommunityID = samlNHINHomeCommunityID; + } + + public String getSamlIHEHomeCommunityID() { + return samlIHEHomeCommunityID; + } + + public void setSamlIHEHomeCommunityID(String samlIHEHomeCommunityID) { + this.samlIHEHomeCommunityID = samlIHEHomeCommunityID; + } + + public String getSamlPurposeOfUseCode() { + return samlPurposeOfUseCode; + } + + public void setSamlPurposeOfUseCode(String samlPurposeOfUseCode) { + this.samlPurposeOfUseCode = samlPurposeOfUseCode; + } + + public String getSamlPurposeOfUseCodeSystem() { + return samlPurposeOfUseCodeSystem; + } + + public void setSamlPurposeOfUseCodeSystem(String samlPurposeOfUseCodeSystem) { + this.samlPurposeOfUseCodeSystem = samlPurposeOfUseCodeSystem; + } + + public String getSamlPurposeOfUseCSP() { + return samlPurposeOfUseCSP; + } + + public void setSamlPurposeOfUseCSP(String samlPurposeOfUseCSP) { + this.samlPurposeOfUseCSP = samlPurposeOfUseCSP; + } + + public String getSamlPurposeOfUseValidatedAttributes() { + return samlPurposeOfUseValidatedAttributes; + } + + public void setSamlPurposeOfUseValidatedAttributes(String samlPurposeOfUseValidatedAttributes) { + this.samlPurposeOfUseValidatedAttributes = samlPurposeOfUseValidatedAttributes; + } + + public String getAttributeValue(String name) { + String rtn = null; + OMElement e = attributeStatement.get(name); + if (e != null) { + OMElement child = e.getFirstElement(); + if (child != null) { + rtn = child.getText(); + } + } + return rtn; + } + + public String toString() { + return "RequestHeader: "; + } +} diff --git a/registry-msg-formats/src/main/java/gov/nist/toolkit/registrymsg/common/RequestHeaderParser.java b/registry-msg-formats/src/main/java/gov/nist/toolkit/registrymsg/common/RequestHeaderParser.java new file mode 100644 index 0000000000..e9773d231a --- /dev/null +++ b/registry-msg-formats/src/main/java/gov/nist/toolkit/registrymsg/common/RequestHeaderParser.java @@ -0,0 +1,116 @@ +package gov.nist.toolkit.registrymsg.common; + +import gov.nist.toolkit.commondatatypes.MetadataSupport; +import gov.nist.toolkit.utilities.xml.XmlUtil; +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.xpath.AXIOMXPath; + +import javax.xml.namespace.QName; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +public class RequestHeaderParser { + OMElement ele; + RequestHeader header = new RequestHeader(); + + + public RequestHeaderParser(OMElement ele) { + this.ele = ele; + } + + public RequestHeader getRequestHeader() throws Exception { + parse(); + return header; + } + + public void parse() throws Exception { + String zz = ele.getLocalName(); + if (!ele.getLocalName().equals("Header")) { + // Something is wrong + return; + } + + AXIOMXPath xpathExpression = new AXIOMXPath ("//*[local-name()='Security']/*[local-name()='Assertion']/*[local-name()='AttributeStatement']"); + Object o = xpathExpression.selectSingleNode(ele); + if (o == null) return; + OMElement omEle = (OMElement) o; + Iterator iterator = omEle.getChildElements(); + HashMap attributeListMap = new HashMap<>(); + while (iterator.hasNext()) { + OMElement x = iterator.next(); + String name = x.getAttributeValue(new QName("Name")); + attributeListMap.put(name, x.cloneOMElement()); + } + + header.omElement = omEle; + header.attributeStatement = attributeListMap; + + header.samlAssertionID = evaluateXPath(ele, "//*[local-name()='Security']/*[local-name()='Assertion']", "ID"); + header.samlAssertionIssueInstant = evaluateXPath(ele, "//*[local-name()='Security']/*[local-name()='Assertion']", "IssueInstant"); + header.samlAssertionVersion = evaluateXPath(ele, "//*[local-name()='Security']/*[local-name()='Assertion']", "Version"); + header.samlAssertionIssuer = evaluateXPath(ele, "//*[local-name()='Security']/*[local-name()='Assertion']/*[local-name()='Issuer']"); + header.samlCanonicalizationMethodAlgorithm = evaluateXPath(ele, "//*[local-name()='Security']/*[local-name()='Assertion']/*[local-name()='Signature']/*[local-name()='SignedInfo']/*[local-name()='CanonicalizationMethod']", "Algorithm"); + header.samlSignatureMethodAlgorithm = evaluateXPath(ele, "//*[local-name()='Security']/*[local-name()='Assertion']/*[local-name()='Signature']/*[local-name()='SignedInfo']/*[local-name()='SignatureMethod']", "Algorithm"); + header.samlDigestMethodAlgorithm = evaluateXPath(ele, "//*[local-name()='Security']/*[local-name()='Assertion']/*[local-name()='Signature']/*[local-name()='SignedInfo']/*[local-name()='Reference']/*[local-name()='DigestMethod']", "Algorithm"); + header.samlDigestValue = evaluateXPath(ele, "//*[local-name()='Security']/*[local-name()='Assertion']/*[local-name()='Signature']/*[local-name()='SignedInfo']/*[local-name()='Reference']/*[local-name()='DigestValue']"); + header.samlSignatureValue = evaluateXPath(ele, "//*[local-name()='Security']/*[local-name()='Assertion']/*[local-name()='Signature']/*[local-name()='SignatureValue']"); + header.samlX509Certificate = evaluateXPath(ele, "//*[local-name()='Security']/*[local-name()='Assertion']/*[local-name()='Signature']/*[local-name()='KeyInfo']/*[local-name()='X509Data']/*[local-name()='X509Certificate']"); + header.samlRSAKyValueModulus = evaluateXPath(ele, "//*[local-name()='Security']/*[local-name()='Assertion']/*[local-name()='Signature']/*[local-name()='KeyInfo']/*[local-name()='KeyValue']/*[local-name()='RSAKeyValue']/*[local-name()='Modulus']"); + header.samlRSAKeyValueExponent = evaluateXPath(ele, "//*[local-name()='Security']/*[local-name()='Assertion']/*[local-name()='Signature']/*[local-name()='KeyInfo']/*[local-name()='KeyValue']/*[local-name()='RSAKeyValue']/*[local-name()='Exponent']"); + + header.samlNHINHomeCommunityID = header.getAttributeValue("urn:nhin:names:saml:homeCommunityId"); + header.samlIHEHomeCommunityID = header.getAttributeValue("urn:ihe:iti:xca:2010:homeCommunityId"); + header.samlPurposeOfUseCSP = header.getAttributeValue("csp"); + header.samlPurposeOfUseValidatedAttributes = header.getAttributeValue("validated_attributes"); + + + + + System.out.println("SAML Assertion ID: " + header.samlAssertionID); + } + + String evaluateXPath(OMElement ele, String expression) throws Exception { + AXIOMXPath xpathExpression = new AXIOMXPath (expression); + Object o = xpathExpression.selectSingleNode(ele); + if (o == null) return null; + + OMElement omEle = (OMElement) o; + String rtn = omEle.getText(); + return rtn; + } + + String evaluateXPath(OMElement ele, String expression, String attribute) throws Exception { + try { + AXIOMXPath xpathExpression = new AXIOMXPath(expression); + Object o = xpathExpression.selectSingleNode(ele); + if (o == null) return null; + + OMElement omEle = (OMElement) o; + String rtn = null; + rtn = omEle.getAttributeValue(new QName(attribute)); + + return rtn; + } catch (Exception e) { + String z = e.toString(); + throw e; + } + } + + List parseValueList(OMElement e, String xpath) throws Exception { + ArrayList rtn = new ArrayList<>(); + + AXIOMXPath xpathExpression = new AXIOMXPath (xpath); + List objectList = xpathExpression.selectNodes(ele); + if (objectList != null) { + for (Object o: objectList) { + OMElement omEle = (OMElement) o; + String text = omEle.getText(); + rtn.add(text); + } + } + return rtn; + } +} diff --git a/test-engine/src/main/java/gov/nist/toolkit/testengine/engine/Validator.java b/test-engine/src/main/java/gov/nist/toolkit/testengine/engine/Validator.java index ace71672ca..2fda2d591a 100755 --- a/test-engine/src/main/java/gov/nist/toolkit/testengine/engine/Validator.java +++ b/test-engine/src/main/java/gov/nist/toolkit/testengine/engine/Validator.java @@ -5,6 +5,7 @@ import gov.nist.toolkit.registrymsg.registry.AdhocQueryRequest; import gov.nist.toolkit.registrymsg.repository.RetrieveItemRequestModel; import gov.nist.toolkit.registrymsg.repository.RetrieveRequestModel; +import gov.nist.toolkit.registrymsg.common.RequestHeader; import gov.nist.toolkit.commondatatypes.MetadataSupport; import gov.nist.toolkit.registrymsg.registry.RegistryResponseParser; import gov.nist.toolkit.simcommon.server.SimDb; @@ -53,6 +54,7 @@ public class Validator { Metadata m; AdhocQueryRequest request; SqParams storedQueryParams; + RequestHeader requestHeader; RetrieveRequestModel retrieveRequestModel; StringBuffer errs = new StringBuffer(); boolean error = false; @@ -95,6 +97,11 @@ public Validator setRequest(AdhocQueryRequest request) { return this; } + public Validator setRequestHeader(RequestHeader requestHeader) { + this.requestHeader = requestHeader; + return this; + } + public Validator setStoredQueryParams(SqParams storedQueryParams) { this.storedQueryParams = storedQueryParams; return this; @@ -1081,6 +1088,23 @@ public boolean namedFieldCompare(String field, String expectedValue) throws Meta return rtn; } + public boolean namedFieldCompare(String field, String section, String XPath, String attribute, String comment, String expectedValue) throws Exception { + String submittedValue = null; + if (field == null || field.equals("")) { + submittedValue = extractNamedField(section, XPath, attribute, comment); + } else { + submittedValue = extractNamedField(field); + } + + boolean rtn = true; + if (!expectedValue.equals(submittedValue)) { + err("Metadata Content Failure, key: " + field + ", expectedValue: " + expectedValue + ", submittedValue: " + submittedValue); + err(section + " XPath: " + XPath + " @ " + attribute); + rtn = false; + } + return rtn; + } + public boolean namedFieldContains(String field, String expectedValue) throws XdsInternalException, MetadataException { boolean rtn = true; List stringList = extractNamedFieldAsList(field); @@ -1098,6 +1122,56 @@ public boolean namedFieldContains(String field, String expectedValue) throws Xds return rtn; } + public boolean namedFieldIsPresent(String field) throws MetadataException { + String submittedValue = extractNamedField(field); + if (submittedValue != null) + return true; + + err("Content failure. A value was not discovered for this key: " + field); + return false; + } + + public boolean namedFieldIsPresent(String field, String section, String XPath, String attribute, String comment) throws Exception { + String submittedValue = null; + if (field == null || field.equals("")) { + submittedValue = extractNamedField(section, XPath, attribute, comment); + } else { + submittedValue = extractNamedField(field); + } + + if (submittedValue != null) + return true; + + err("Content failure. A value was not discovered for this field: " + field + " " + comment + " " + section); + err("XPath: " + XPath + " @ " + attribute); + return false; + } + + public boolean namedFieldIsNotEmpty(String field, String section, String XPath, String attribute, String comment) throws Exception { + String submittedValue = null; + if (field == null || field.equals("")) { + submittedValue = extractNamedField(section, XPath, attribute, comment); + } else { + submittedValue = extractNamedField(field); + } + + if ((submittedValue != null) && (!submittedValue.isEmpty())) + return true; + + err("Content failure. A value was not discovered for this field: " + field + " " + comment + " " + section); + err("XPath: " + XPath + " @ " + attribute); + return false; + } + + public boolean namedFieldIsNotPresent(String field) throws MetadataException { + String submittedValue = extractNamedField(field); + if (submittedValue == null) + return true; + + err("Content failure. A value was discovered when the expectation was no value for this key: " + field); + return false; + } + private List extractNamedFieldAsList(String field) throws XdsInternalException, MetadataException { List rtn = null; switch(field) { @@ -1130,6 +1204,7 @@ private String extractNamedField(String field) throws MetadataException { case "AdhocQuery.DocumentEntry.objectType": rtn = firstValue(request.getDocumentEntryObjectTypeList()); break; + case "XCR.homeCommunityId": models = retrieveRequestModel.getModels(); rtn = models.get(0).getHomeId(); @@ -1142,9 +1217,87 @@ private String extractNamedField(String field) throws MetadataException { models = retrieveRequestModel.getModels(); rtn = models.get(0).getDocumentId(); break; + case "SoapHeader.SAML.PoU.Code": +// OMElement pouElement = requestHeader.getAttributeStatementAttribute("urn:oasis:names:tc:xspa:1.0:subject:purposeofuse"); + rtn = getPurposeOfUseCode(); + break; + case "SoapHeader.SAML.PoU.CodeScheme": + rtn = getPurposeOfUseCodeSystem(); + break; + case "SoapHeader.SAML.Assertion@ID": + rtn = requestHeader.getSamlAssertionID(); + break; + case "SoapHeader.SAML.Assertion@IssueInstant": + rtn = requestHeader.getSamlAssertionIssueInstant(); + break; + case "SoapHeader.SAML.Assertion@Version": + rtn = requestHeader.getSamlAssertionVersion(); + break; + case "SoapHeader.SAML.Assertion.Issuer": + rtn = requestHeader.getSamlAssertionIssuer(); + break; + case "SoapHeader.SAML.Sig.CanonicalizationMethod@Algorithm": + rtn = requestHeader.getSamlCanonicalizationMethodAlgorithm(); + break; + case "SoapHeader.SAML.Sig.SignatureMethod@Algorithm": + rtn = requestHeader.getSamlSignatureMethodAlgorithm(); + break; + case "SoapHeader.SAML.Sig.DigestMethod@Algorithm": + rtn = requestHeader.getSamlDigestMethodAlgorithm(); + break; + case "SoapHeader.SAML.Sig.DigestValue": + rtn = requestHeader.getSamlDigestValue(); + break; + case "SoapHeader.SAML.Sig.SignatureValue": + rtn = requestHeader.getSamlSignatureValue(); + break; + case "SoapHeader.SAML.Sig.X509Certificate": + rtn = requestHeader.getSamlX509Certificate(); + break; + case "SoapHeader.SAML.Sig.RSAKeyValue.Modulus": + rtn = requestHeader.getSamlRSAKyValueModulus(); + break; + case "SoapHeader.SAML.Sig.RSAKeyValue.Exponent": + rtn = requestHeader.getSamlRSAKeyValueExponent(); + break; + case "SoapHeader.SAML.NHIN.HCID": + rtn = requestHeader.getSamlNHINHomeCommunityID(); + break; + case "SoapHeader.SAML.IHE.HCID": + rtn = requestHeader.getSamlIHEHomeCommunityID(); + break; + case "SoapHeader.SAML.PoU.csp": + rtn = requestHeader.getSamlPurposeOfUseCSP(); + break; + case "SoapHeader.SAML.PoU.validated_attributes": + rtn = requestHeader.getSamlPurposeOfUseValidatedAttributes(); + break; + default: + rtn = null; + break; + } + return rtn; + } + + private String extractNamedField(String section, String XPath, String attribute, String comment) throws Exception { + String rtn = null; + + OMElement sectionElement = null; + switch (section) { + case "requestHeader": + sectionElement = requestHeader.getOmElement(); + break; default: break; } + if (sectionElement != null ) { + if (attribute != null) { + rtn = XmlUtil.getStringFromXPath(sectionElement, XPath, attribute); + } else { + rtn = XmlUtil.getStringFromXPath(sectionElement, XPath); + } + } + return rtn; } @@ -1156,6 +1309,26 @@ private String firstValue(List valueList) { return rtn; } + private String getPurposeOfUseCode() { + String rtn = ""; + OMElement pouElement = requestHeader.getAttributeStatementAttribute("urn:oasis:names:tc:xspa:1.0:subject:purposeofuse"); + if (pouElement != null) { + OMElement value = XmlUtil.firstChildChain(pouElement, "AttributeValue", "PurposeOfUse"); + rtn = (value == null) ? "" : value.getAttributeValue(new QName("code")); + } + return rtn; + } + + private String getPurposeOfUseCodeSystem() { + String rtn = null; + OMElement pouElement = requestHeader.getAttributeStatementAttribute("urn:oasis:names:tc:xspa:1.0:subject:purposeofuse"); + if (pouElement != null) { + OMElement value = XmlUtil.firstChildChain(pouElement, "AttributeValue", "PurposeOfUse"); + rtn = (value == null) ? "" : value.getAttributeValue(new QName("codeSystem")); + } + return rtn; + } + public void run_test_assertions(OMElement xml, OMElement instruction_output) throws MetadataException, XdsInternalException, MetadataValidationException { diff --git a/utilities/src/main/java/gov/nist/toolkit/utilities/xml/XmlUtil.java b/utilities/src/main/java/gov/nist/toolkit/utilities/xml/XmlUtil.java index de284d51d8..6f75f5fe5a 100755 --- a/utilities/src/main/java/gov/nist/toolkit/utilities/xml/XmlUtil.java +++ b/utilities/src/main/java/gov/nist/toolkit/utilities/xml/XmlUtil.java @@ -11,6 +11,7 @@ import org.apache.axiom.om.OMFactory; import org.apache.axiom.om.OMNamespace; import org.apache.axiom.om.util.AXIOMUtil; +import org.apache.axiom.om.xpath.AXIOMXPath; import org.apache.commons.lang3.StringUtils; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; @@ -375,4 +376,24 @@ public static void truncateDocuments(OMElement element) { return; } + public static String getStringFromXPath(OMElement element, String XPath) throws Exception{ + String rtn = null; + AXIOMXPath xpathExpression = new AXIOMXPath (XPath); + Object o = xpathExpression.selectSingleNode(element); + if (o != null) { + rtn = ((OMElement)o).getText(); + } + return rtn; + } + + public static String getStringFromXPath(OMElement element, String XPath, String attribute) throws Exception{ + String rtn = null; + AXIOMXPath xpathExpression = new AXIOMXPath (XPath); + Object o = xpathExpression.selectSingleNode(element); + if (o != null) { + rtn = ((OMElement)o).getAttributeValue(new QName(attribute)); + } + return rtn; + } + } diff --git a/xdstools2/src/main/webapp/toolkitx/testkit/plugins/SoapAssertion/SoapHeaderValidator.groovy b/xdstools2/src/main/webapp/toolkitx/testkit/plugins/SoapAssertion/SoapHeaderValidator.groovy new file mode 100644 index 0000000000..550569c325 --- /dev/null +++ b/xdstools2/src/main/webapp/toolkitx/testkit/plugins/SoapAssertion/SoapHeaderValidator.groovy @@ -0,0 +1,185 @@ +package war.toolkitx.testkit.plugins.SoapAssertion + +import gov.nist.toolkit.registrymsg.registry.AdhocQueryRequest +import gov.nist.toolkit.registrymsg.registry.AdhocQueryRequestParser +import gov.nist.toolkit.registrymsg.common.RequestHeader +import gov.nist.toolkit.registrymsg.common.RequestHeaderParser +import gov.nist.toolkit.testengine.engine.Validator +import gov.nist.toolkit.testengine.engine.SimReference +import gov.nist.toolkit.testengine.engine.SoapSimulatorTransaction +import gov.nist.toolkit.testengine.engine.validations.ValidaterResult +import gov.nist.toolkit.testengine.engine.validations.soap.AbstractSoapValidater +import gov.nist.toolkit.testengine.engine.Validator + +import gov.nist.toolkit.registrymetadata.Metadata +import gov.nist.toolkit.registrymetadata.MetadataParser +import gov.nist.toolkit.utilities.xml.Util +import gov.nist.toolkit.valregmsg.registry.storedquery.support.ParamParser +import gov.nist.toolkit.valregmsg.registry.storedquery.support.SqParams +import org.apache.axiom.om.OMElement + +/** + * Runs an MetadataContent validator through this plugin. @see Validator#run_test_assertions. + */ +class SoapHeaderValidator extends AbstractSoapValidater { + /** + * Required parameter + */ + String requestMsgExpectedContent + /** + * Optional parameter + */ + String requestMsgECCount + /** + * Required parameter + */ + String responseMsgExpectedContent + /** + * Optional parameter + */ + String responseMsgECCount + + String method + String key + String value + String codeValue + String codingScheme + String codeDisplayName + String XPath + String attribute + String section + String comment + String reference + + /** + * Optional parameter + */ + String metadataValidationFile; + + SoapHeaderValidator() { + filterDescription = 'Runs a Soap Header validator (Validator#run_test_assertions) through this plugin.' + } + + @Override + ValidaterResult validate(SoapSimulatorTransaction sst) { + reset() // Clear log + boolean requestMatch = false + boolean responseMatch = false + if (!requestMsgExpectedContent && !responseMsgExpectedContent) { + String illegalArg = "Either requestMsgExpectedContent attribute or responseMsgExpectedContent attribute must be specified. See Validator#run_test_assertion for a list of codes." + error(illegalArg) + throw new IllegalArgumentException(illegalArg) + } + if (requestMsgExpectedContent && sst) { + // For Debugging only -- this log creates too many messages +// log("Processing request from eventId: ${transactionInstance?.simDbEvent?.eventId} simLogUrl: ${transactionInstance?.simDbEvent?.simLogUrl}") + if (sst.requestBody) { + ParamParser paramsParser = new ParamParser(); + SqParams params = paramsParser.parse(Util.parse_xml(sst.requestBody), true); + AdhocQueryRequestParser parser = new AdhocQueryRequestParser(Util.parse_xml(sst.requestBody)) + AdhocQueryRequest r = parser.getAdhocQueryRequest() + RequestHeaderParser headerParser = new RequestHeaderParser(Util.parse_xml(sst.requestHeader)) + RequestHeader requestHeader = headerParser.getRequestHeader() + String errors = ""; + if (requestMsgExpectedContent.equals("SoapHeader")) { + Validator v = new Validator().setRequest(r).setStoredQueryParams(params).setRequestHeader(requestHeader) + switch (method) { + case "single": + if (!v.namedFieldCompare(key,section, XPath, attribute, comment, value)) { + errors = v.getErrors() + } + break; + case "singleCode": + if (!v.namedMetadataCompareCode(key, codeValue, codingScheme, codeDisplayName)) { + errors = v.getErrors() + } + break; + case "containsCode": + if (!v.namedMetadataContainsCode(key, codeValue, codingScheme, codeDisplayName)) { + errors = v.getErrors() + } + break; + case "contains": + if (!v.namedFieldContains(key, value)) { + errors = v.getErrors() + } + break; + case "isPresent": + if (!v.namedFieldIsPresent(key,section, XPath, attribute, comment)) { + errors = v.getErrors(); + } + break; + case "isNotEmpty": + if (!v.namedFieldIsNotEmpty(key,section, XPath, attribute, comment)) { + errors = v.getErrors(); + } + break; + case "isNotPresent": + if (!v.namedFieldIsNotPresent(key)) { + errors = v.getErrors(); + } + break; + default: + errors="Unrecognized Stored Query validation method:" + method + ". Expecting one of single, singleCode, containsCode, contains."; + break; + } + } else { + Validator v = new Validator().setM(m) + v.run_test_assertion(requestMsgExpectedContent, Integer.parseInt(requestMsgECCount?:"-1")) + errors = v.getErrors() + } + + if (errors.length() > 0) { + error("Request", errors) + } + requestMatch = sst.request instanceof String && !isErrors() + } else { + error("Request","Null transactionInstance or its request body is null") + } + } + + if (responseMsgExpectedContent && sst) { +// For Debugging only -- this log creates too many messages +// log("Processing response from eventId: ${transactionInstance?.simDbEvent?.eventId} simLogUrl: ${transactionInstance?.simDbEvent?.simLogUrl}") + if (sst.responseBody) { + OMElement regresp = Util.parse_xml(sst.responseBody) + + Metadata m = new Metadata(regresp, false, false) + Validator v = new Validator().setM(m) + v.run_test_assertion(responseMsgExpectedContent, Integer.parseInt(responseMsgECCount?:"-1")) + String errors = v.getErrors() + + if (errors.length() > 0) { + error("Response", errors) + } + responseMatch = sst.response instanceof String && !isErrors() + } else { + error("Response", "Null transactionInstance or its response body is null") + } + } + + boolean match = false + if (requestMsgExpectedContent && responseMsgExpectedContent) { + match = requestMatch && responseMatch + } else if (requestMsgExpectedContent) { + match = requestMatch + } else if (responseMsgExpectedContent) { + match = responseMatch + } + + new ValidaterResult(sst, this.copy(), match) + } + + AbstractSoapValidater copy() { + SoapHeaderValidator mcv = new SoapHeaderValidator() + mcv.responseMsgExpectedContent = responseMsgExpectedContent + mcv.requestMsgExpectedContent = requestMsgExpectedContent + mcv.responseMsgECCount = responseMsgECCount + mcv.requestMsgECCount = requestMsgECCount + mcv.metadataValidationFile = metadataValidationFile + mcv.simReference = new SimReference(simReference?.simId, simReference?.transactionType, simReference?.actorType) + mcv.errors = this.errors + mcv.setLog(new StringBuilder(this.log)) + mcv + } +} diff --git a/xdstools2/src/main/webapp/toolkitx/testkit/plugins/SoapAssertion/StoredQueryValidator.groovy b/xdstools2/src/main/webapp/toolkitx/testkit/plugins/SoapAssertion/StoredQueryValidator.groovy index 9a62d4525e..9265587699 100644 --- a/xdstools2/src/main/webapp/toolkitx/testkit/plugins/SoapAssertion/StoredQueryValidator.groovy +++ b/xdstools2/src/main/webapp/toolkitx/testkit/plugins/SoapAssertion/StoredQueryValidator.groovy @@ -2,6 +2,8 @@ package war.toolkitx.testkit.plugins.SoapAssertion import gov.nist.toolkit.registrymsg.registry.AdhocQueryRequest import gov.nist.toolkit.registrymsg.registry.AdhocQueryRequestParser +import gov.nist.toolkit.registrymsg.common.RequestHeader +import gov.nist.toolkit.registrymsg.common.RequestHeaderParser import gov.nist.toolkit.testengine.engine.Validator import gov.nist.toolkit.testengine.engine.SimReference import gov.nist.toolkit.testengine.engine.SoapSimulatorTransaction @@ -71,9 +73,11 @@ class StoredQueryValidator extends AbstractSoapValidater { SqParams params = paramsParser.parse(Util.parse_xml(sst.requestBody), true); AdhocQueryRequestParser parser = new AdhocQueryRequestParser(Util.parse_xml(sst.requestBody)) AdhocQueryRequest r = parser.getAdhocQueryRequest() + RequestHeaderParser headerParser = new RequestHeaderParser(Util.parse_xml(sst.requestHeader)) + RequestHeader requestHeader = headerParser.getRequestHeader() String errors = ""; if (requestMsgExpectedContent.equals("StoredQuery")) { - Validator v = new Validator().setRequest(r).setStoredQueryParams(params) + Validator v = new Validator().setRequest(r).setStoredQueryParams(params).setRequestHeader(requestHeader) switch (method) { case "single": if (!v.namedFieldCompare(key, value)) { From 0e6c2289b8b4d44ac8519faf2d054c339f5dc45e Mon Sep 17 00:00:00 2001 From: stl-steve-moore Date: Thu, 15 Jun 2023 19:25:12 -0500 Subject: [PATCH 2/4] Improve granularity of XPath based testing for initiating systems Now we have distinct tests for isPresent and isNotEmpty when looking for transactions sent by an initiating system. More cleanup is needed to have the same functionality for the keyword based tests and also for the tests of attributes. This commit works for element values, but is not intended to work with XPath testing of attributes for those distinct use cases. --- .../toolkit/testengine/engine/Validator.java | 89 ++++++++++++------- .../nist/toolkit/utilities/xml/XmlUtil.java | 6 ++ .../SoapAssertion/SoapHeaderValidator.groovy | 2 +- 3 files changed, 64 insertions(+), 33 deletions(-) diff --git a/test-engine/src/main/java/gov/nist/toolkit/testengine/engine/Validator.java b/test-engine/src/main/java/gov/nist/toolkit/testengine/engine/Validator.java index 2fda2d591a..13cef24d59 100755 --- a/test-engine/src/main/java/gov/nist/toolkit/testengine/engine/Validator.java +++ b/test-engine/src/main/java/gov/nist/toolkit/testengine/engine/Validator.java @@ -1079,7 +1079,7 @@ private List extractSlotValues(OMElement e, String slotName) throws Meta For example: AdhocQuery.DocumentEntry.xx */ public boolean namedFieldCompare(String field, String expectedValue) throws MetadataException { - String submittedValue = extractNamedField(field); + String submittedValue = extractNamedFieldString(field); boolean rtn = true; if (!expectedValue.equals(submittedValue)) { err("Metadata Content Failure, key: " + field + ", expectedValue: " + expectedValue + ", submittedValue: " + submittedValue); @@ -1090,18 +1090,22 @@ public boolean namedFieldCompare(String field, String expectedValue) throws Meta public boolean namedFieldCompare(String field, String section, String XPath, String attribute, String comment, String expectedValue) throws Exception { String submittedValue = null; + boolean rtn = true; if (field == null || field.equals("")) { - submittedValue = extractNamedField(section, XPath, attribute, comment); + submittedValue = extractNamedFieldString(section, XPath, attribute, comment); + if (!expectedValue.equals(submittedValue)) { + err("Metadata Content Failure, comment: " + comment + ", expectedValue: " + expectedValue + ", submittedValue: " + submittedValue); + err(section + " XPath: " + XPath.replaceAll("=", " _EQ_ ") + " @ " + attribute); + rtn = false; + } } else { - submittedValue = extractNamedField(field); + submittedValue = extractNamedFieldString(field); + if (!expectedValue.equals(submittedValue)) { + err("Metadata Content Failure, key: " + field + ", expectedValue: " + expectedValue + ", submittedValue: " + submittedValue); + rtn = false; + } } - boolean rtn = true; - if (!expectedValue.equals(submittedValue)) { - err("Metadata Content Failure, key: " + field + ", expectedValue: " + expectedValue + ", submittedValue: " + submittedValue); - err(section + " XPath: " + XPath + " @ " + attribute); - rtn = false; - } return rtn; } @@ -1123,7 +1127,7 @@ public boolean namedFieldContains(String field, String expectedValue) throws Xds } public boolean namedFieldIsPresent(String field) throws MetadataException { - String submittedValue = extractNamedField(field); + String submittedValue = extractNamedFieldString(field); if (submittedValue != null) return true; @@ -1131,40 +1135,45 @@ public boolean namedFieldIsPresent(String field) throws MetadataException { return false; } - public boolean namedFieldIsPresent(String field, String section, String XPath, String attribute, String comment) throws Exception { - String submittedValue = null; + public boolean namedFieldIsPresent(String field, String section, String XPath, String comment) throws Exception { + boolean rtn = false; if (field == null || field.equals("")) { - submittedValue = extractNamedField(section, XPath, attribute, comment); + OMElement e = extractNamedFieldElement(section, XPath, comment); + rtn = (e != null); } else { - submittedValue = extractNamedField(field); + String submittedValue = extractNamedFieldString(field); + rtn = (submittedValue != null); } - - if (submittedValue != null) - return true; - - err("Content failure. A value was not discovered for this field: " + field + " " + comment + " " + section); - err("XPath: " + XPath + " @ " + attribute); - return false; + if (!rtn) { + err("Content failure. A value was not discovered for this field: " + field + " " + comment + " " + section); + err("XPath: " + XPath.replaceAll("=", " _EQ_ ")); + } + return rtn; } public boolean namedFieldIsNotEmpty(String field, String section, String XPath, String attribute, String comment) throws Exception { String submittedValue = null; + boolean rtn = true; if (field == null || field.equals("")) { - submittedValue = extractNamedField(section, XPath, attribute, comment); + submittedValue = extractNamedFieldString(section, XPath, attribute, comment); + if ((submittedValue == null) || (submittedValue.isEmpty())) { + rtn = false; + err("Content failure. A value was not discovered for this field: " + comment + " " + section); + err("XPath: " + XPath.replaceAll("=", " _EQ_ ") + " @ " + attribute); + } } else { - submittedValue = extractNamedField(field); + submittedValue = extractNamedFieldString(field); + if ((submittedValue == null) || (submittedValue.isEmpty())) { + rtn = false; + err("Content failure. A value was not discovered for this field: " + field + " " + comment + " " + section); + } } - if ((submittedValue != null) && (!submittedValue.isEmpty())) - return true; - - err("Content failure. A value was not discovered for this field: " + field + " " + comment + " " + section); - err("XPath: " + XPath + " @ " + attribute); - return false; + return rtn; } public boolean namedFieldIsNotPresent(String field) throws MetadataException { - String submittedValue = extractNamedField(field); + String submittedValue = extractNamedFieldString(field); if (submittedValue == null) return true; @@ -1185,7 +1194,7 @@ private List extractNamedFieldAsList(String field) throws XdsInternalExc return rtn; } - private String extractNamedField(String field) throws MetadataException { + private String extractNamedFieldString(String field) throws MetadataException { String rtn = ""; List models; switch(field) { @@ -1279,7 +1288,7 @@ private String extractNamedField(String field) throws MetadataException { return rtn; } - private String extractNamedField(String section, String XPath, String attribute, String comment) throws Exception { + private String extractNamedFieldString(String section, String XPath, String attribute, String comment) throws Exception { String rtn = null; OMElement sectionElement = null; @@ -1300,6 +1309,22 @@ private String extractNamedField(String section, String XPath, String attribute, return rtn; } + private OMElement extractNamedFieldElement(String section, String XPath, String comment) throws Exception { + OMElement sectionElement = null; + switch (section) { + case "requestHeader": + sectionElement = requestHeader.getOmElement(); + break; + default: + break; + } + OMElement rtn = null; + if (sectionElement != null ) { + rtn = XmlUtil.getElementFromXPath(sectionElement, XPath); + } + + return rtn; + } private String firstValue(List valueList) { String rtn = ""; diff --git a/utilities/src/main/java/gov/nist/toolkit/utilities/xml/XmlUtil.java b/utilities/src/main/java/gov/nist/toolkit/utilities/xml/XmlUtil.java index 6f75f5fe5a..f8c1d19d9b 100755 --- a/utilities/src/main/java/gov/nist/toolkit/utilities/xml/XmlUtil.java +++ b/utilities/src/main/java/gov/nist/toolkit/utilities/xml/XmlUtil.java @@ -396,4 +396,10 @@ public static String getStringFromXPath(OMElement element, String XPath, String return rtn; } + public static OMElement getElementFromXPath(OMElement element, String XPath) throws Exception{ + AXIOMXPath xpathExpression = new AXIOMXPath (XPath); + OMElement o = (OMElement) xpathExpression.selectSingleNode(element); + return o; + } + } diff --git a/xdstools2/src/main/webapp/toolkitx/testkit/plugins/SoapAssertion/SoapHeaderValidator.groovy b/xdstools2/src/main/webapp/toolkitx/testkit/plugins/SoapAssertion/SoapHeaderValidator.groovy index 550569c325..457ad4fd4c 100644 --- a/xdstools2/src/main/webapp/toolkitx/testkit/plugins/SoapAssertion/SoapHeaderValidator.groovy +++ b/xdstools2/src/main/webapp/toolkitx/testkit/plugins/SoapAssertion/SoapHeaderValidator.groovy @@ -105,7 +105,7 @@ class SoapHeaderValidator extends AbstractSoapValidater { } break; case "isPresent": - if (!v.namedFieldIsPresent(key,section, XPath, attribute, comment)) { + if (!v.namedFieldIsPresent(key,section, XPath, comment)) { errors = v.getErrors(); } break; From 3021ac829643f8c2b4904ef3fcf98a791bf27344 Mon Sep 17 00:00:00 2001 From: stl-steve-moore Date: Sun, 18 Jun 2023 20:31:30 -0500 Subject: [PATCH 3/4] Jira 589: Add another named field to support testing the destination Home Community ID when running tests on XCQ transactions sent by an Initiating Gateway. --- .../java/gov/nist/toolkit/testengine/engine/Validator.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test-engine/src/main/java/gov/nist/toolkit/testengine/engine/Validator.java b/test-engine/src/main/java/gov/nist/toolkit/testengine/engine/Validator.java index 13cef24d59..09de323308 100755 --- a/test-engine/src/main/java/gov/nist/toolkit/testengine/engine/Validator.java +++ b/test-engine/src/main/java/gov/nist/toolkit/testengine/engine/Validator.java @@ -937,6 +937,9 @@ private String extractNamedMetadata(String metadataField) throws MetadataExcepti case "AdhocQuery.DocumentEntry.patientId": rtn = request.getPatientId(); break; + case "AdhocQuery.home": + rtn = request.getHome(); + break; default: rtn = "Validator::extractNamedMetadata does not understand: " + metadataField; From 9f3d87a156b20295675269a87421fa6d2f8d6a29 Mon Sep 17 00:00:00 2001 From: stl-steve-moore Date: Sun, 18 Jun 2023 20:33:14 -0500 Subject: [PATCH 4/4] Jira 588: Comment out the code in SoapHeaderValidator.groovy that was looking for an Adhoc Query request. If we need this, it belongs in the validator for Adhoc Queries. --- .../plugins/SoapAssertion/SoapHeaderValidator.groovy | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/xdstools2/src/main/webapp/toolkitx/testkit/plugins/SoapAssertion/SoapHeaderValidator.groovy b/xdstools2/src/main/webapp/toolkitx/testkit/plugins/SoapAssertion/SoapHeaderValidator.groovy index 457ad4fd4c..833a029d8b 100644 --- a/xdstools2/src/main/webapp/toolkitx/testkit/plugins/SoapAssertion/SoapHeaderValidator.groovy +++ b/xdstools2/src/main/webapp/toolkitx/testkit/plugins/SoapAssertion/SoapHeaderValidator.groovy @@ -76,13 +76,14 @@ class SoapHeaderValidator extends AbstractSoapValidater { if (sst.requestBody) { ParamParser paramsParser = new ParamParser(); SqParams params = paramsParser.parse(Util.parse_xml(sst.requestBody), true); - AdhocQueryRequestParser parser = new AdhocQueryRequestParser(Util.parse_xml(sst.requestBody)) - AdhocQueryRequest r = parser.getAdhocQueryRequest() +// AdhocQueryRequestParser parser = new AdhocQueryRequestParser(Util.parse_xml(sst.requestBody)) +// AdhocQueryRequest r = parser.getAdhocQueryRequest() RequestHeaderParser headerParser = new RequestHeaderParser(Util.parse_xml(sst.requestHeader)) RequestHeader requestHeader = headerParser.getRequestHeader() String errors = ""; if (requestMsgExpectedContent.equals("SoapHeader")) { - Validator v = new Validator().setRequest(r).setStoredQueryParams(params).setRequestHeader(requestHeader) +// Validator v = new Validator().setRequest(r).setStoredQueryParams(params).setRequestHeader(requestHeader) + Validator v = new Validator().setStoredQueryParams(params).setRequestHeader(requestHeader) switch (method) { case "single": if (!v.namedFieldCompare(key,section, XPath, attribute, comment, value)) {