diff --git a/oxalis-ng-api/src/main/java/network/oxalis/ng/api/lang/OxalisResourceException.java b/oxalis-ng-api/src/main/java/network/oxalis/ng/api/lang/OxalisResourceException.java new file mode 100644 index 00000000..a867a780 --- /dev/null +++ b/oxalis-ng-api/src/main/java/network/oxalis/ng/api/lang/OxalisResourceException.java @@ -0,0 +1,11 @@ +package network.oxalis.ng.api.lang; + +public class OxalisResourceException extends OxalisRuntimeException { + public OxalisResourceException(String message) { + super(message); + } + + public OxalisResourceException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/oxalis-ng-extension/oxalis-ng-as4/pom.xml b/oxalis-ng-extension/oxalis-ng-as4/pom.xml index f232bcef..5b56da17 100644 --- a/oxalis-ng-extension/oxalis-ng-as4/pom.xml +++ b/oxalis-ng-extension/oxalis-ng-as4/pom.xml @@ -21,7 +21,8 @@ ~ permissions and limitations under the Licence. --> - + 4.0.0 @@ -33,7 +34,7 @@ oxalis-ng-as4 jar - Oxalis-NG :: Ext :: AS4 + Oxalis-NG :: Ext :: AS4 Extension adding AS4 support to Oxalis-NG https://github.com/OxalisCommunity/oxalis-ng @@ -340,6 +341,14 @@ src/main/resources true + + src/main/xsd + false + + **/*.xsd + **/*.dtd + + diff --git a/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/config/As4Conf.java b/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/config/As4Conf.java index fa6a1ea0..1702e062 100644 --- a/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/config/As4Conf.java +++ b/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/config/As4Conf.java @@ -39,5 +39,10 @@ public enum As4Conf { @Path("oxalis.as4.type") @DefaultValue("peppol") - TYPE + TYPE, + + @Path("oxalis.as4.receipt.validation") + @DefaultValue("logging") // options: none, logging, strict + RECEIPT_VALIDATION, + } diff --git a/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/outbound/As4MessageSender.java b/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/outbound/As4MessageSender.java index f82814b4..b7b67fe3 100644 --- a/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/outbound/As4MessageSender.java +++ b/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/outbound/As4MessageSender.java @@ -36,11 +36,13 @@ import javax.net.ssl.KeyManager; import javax.xml.namespace.QName; + import jakarta.xml.soap.SOAPMessage; import jakarta.xml.ws.BindingProvider; import jakarta.xml.ws.Dispatch; import jakarta.xml.ws.Service; import jakarta.xml.ws.soap.SOAPBinding; + import java.io.IOException; import java.io.InputStream; import java.util.*; @@ -64,9 +66,10 @@ public class As4MessageSender { private final MerlinProvider merlinProvider; private final PolicyService policyService; private final String browserType; + private final As4ReceiptValidationInInterceptor receiptValidationInInterceptor; @Inject - public As4MessageSender(MessagingProvider messagingProvider, MessageIdGenerator messageIdGenerator, Settings settings, Settings as4settings, CompressionUtil compressionUtil, Settings httpConfSettings, TransmissionResponseConverter transmissionResponseConverter, MerlinProvider merlinProvider, PolicyService policyService, BrowserTypeProvider browserTypeProvider) { + public As4MessageSender(MessagingProvider messagingProvider, MessageIdGenerator messageIdGenerator, Settings settings, Settings as4settings, CompressionUtil compressionUtil, Settings httpConfSettings, TransmissionResponseConverter transmissionResponseConverter, MerlinProvider merlinProvider, PolicyService policyService, BrowserTypeProvider browserTypeProvider, As4ReceiptValidationInInterceptor receiptValidationInInterceptor) { this.messagingProvider = messagingProvider; this.messageIdGenerator = messageIdGenerator; this.settings = settings; @@ -77,6 +80,7 @@ public As4MessageSender(MessagingProvider messagingProvider, MessageIdGenerator this.merlinProvider = merlinProvider; this.policyService = policyService; this.browserType = browserTypeProvider.getBrowserType(); + this.receiptValidationInInterceptor = receiptValidationInInterceptor; } public TransmissionResponse send(TransmissionRequest request) throws OxalisAs4TransmissionException { @@ -173,6 +177,11 @@ private DispatchImpl createDispatch(TransmissionRequest request) th final Client client = dispatch.getClient(); + // Add receipt validation interceptor for Peppol transmissions + if (AS4Constants.PEPPOL.equalsIgnoreCase(as4settings.getString(As4Conf.TYPE))) { + client.getInInterceptors().add(receiptValidationInInterceptor); + } + if (AS4Constants.CEF_CONFORMANCE.equalsIgnoreCase(as4settings.getString(As4Conf.TYPE))) { client.getInInterceptors().add(getLoggingBeforeSecurityInInterceptor()); } diff --git a/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/outbound/As4OutboundModule.java b/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/outbound/As4OutboundModule.java index 8eaaf3f8..33c3c32c 100644 --- a/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/outbound/As4OutboundModule.java +++ b/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/outbound/As4OutboundModule.java @@ -61,4 +61,10 @@ public String getAgreementRef() { return new PeppolConfiguration(); } + + @Singleton + @Provides + public As4ReceiptValidationInInterceptor getAs4ReceiptValidationInInterceptor(Settings settings) { + return new As4ReceiptValidationInInterceptor(settings); + } } diff --git a/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/outbound/As4ReceiptValidationInInterceptor.java b/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/outbound/As4ReceiptValidationInInterceptor.java new file mode 100644 index 00000000..df5ae4f5 --- /dev/null +++ b/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/outbound/As4ReceiptValidationInInterceptor.java @@ -0,0 +1,255 @@ +package network.oxalis.ng.as4.outbound; + +import jakarta.xml.soap.SOAPException; +import jakarta.xml.soap.SOAPMessage; +import lombok.extern.slf4j.Slf4j; +import network.oxalis.ng.api.settings.Settings; +import network.oxalis.ng.as4.config.As4Conf; +import network.oxalis.ng.as4.lang.OxalisAs4Exception; +import network.oxalis.ng.as4.lang.OxalisAs4TransmissionException; +import network.oxalis.ng.as4.util.AS4ErrorCode; +import network.oxalis.ng.as4.util.Constants; +import network.oxalis.ng.as4.util.PeppolAs4ReceiptValidator; +import network.oxalis.ng.as4.util.SchemaValidatorFactory; +import network.oxalis.ng.as4.util.SchemaValidatorFactory.DefaultValidationErrorHandler; +import org.apache.cxf.binding.soap.SoapMessage; +import org.apache.cxf.interceptor.Fault; +import org.apache.cxf.phase.AbstractPhaseInterceptor; +import org.apache.cxf.phase.Phase; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.xml.transform.dom.DOMSource; +import javax.xml.validation.Validator; +import java.io.IOException; + +/** + * CXF interceptor that validates AS4 receipt messages in the outbound response chain. + * + *

This interceptor performs two-tier validation of AS4 receipts received as responses to user messages: + *

    + *
  1. Schema Validation: Validates against core AS4 schemas (SOAP 1.2, ebMS v3, EBBP signals, XMLDsig)
  2. + *
  3. Conformance Validation: Validates PEPPOL-specific requirements (e.g., NonRepudiationInformation presence)
  4. + *
+ * + *

Validation Modes: + *

    + *
  • NONE: Validation completely skipped
  • + *
  • LOGGING (default): Validation failures logged as warnings, processing continues
  • + *
  • STRICT: Validation failures throw Fault, terminating the exchange
  • + *
+ * + *

Configuration: + * Set mode via {@code oxalis.as4.receipt.validation} property: + *

+ * oxalis.as4.receipt.validation=STRICT  # Fail fast on invalid receipts
+ * oxalis.as4.receipt.validation=LOGGING # Log issues but continue (default)
+ * oxalis.as4.receipt.validation=NONE    # Skip validation entirely
+ * 
+ * + * @see SchemaValidatorFactory for schema-level validation + * @see PeppolAs4ReceiptValidator for PEPPOL conformance rules + */ + +@Slf4j +public class As4ReceiptValidationInInterceptor extends AbstractPhaseInterceptor { + + // Validation mode determines how interceptor responds on validation failures (default: LOGGING) + private final ValidationMode validationMode; + + + /** + * Validation mode determines how interceptor responds on validation failures (default: LOGGING) + */ + public enum ValidationMode { + NONE, // Skip validation + LOGGING, // Log errors but continue + STRICT; // Throw exception on errors and prevent further processing + + public static ValidationMode parseValidationMode(Settings as4Settings) { + + String modeStr = as4Settings.getString(As4Conf.RECEIPT_VALIDATION); + + if (modeStr == null) { + return ValidationMode.LOGGING; + } + + try { + return ValidationMode.valueOf(modeStr.toUpperCase()); + } catch (IllegalArgumentException e) { + log.warn("Unknown validation mode '{}', defaulting to LOGGING", modeStr); + return ValidationMode.LOGGING; + } + } + } + + /** + * Creates an interceptor with validation mode parsed from AS4 settings. + * Runs in {@link Phase#POST_PROTOCOL} by default. + * + * @param as4Settings configuration settings containing validation mode + */ + public As4ReceiptValidationInInterceptor(Settings as4Settings) { + this(Phase.POST_PROTOCOL, ValidationMode.parseValidationMode(as4Settings)); + } + + /** + * Creates an interceptor with explicit phase and validation mode. + * Used primarily for testing or custom configuration scenarios, if required. + * + * @param phase CXF interceptor phase (typically {@link Phase#POST_PROTOCOL}) + * @param validationMode how to handle validation failures + */ + public As4ReceiptValidationInInterceptor(String phase, ValidationMode validationMode) { + super(phase); + this.validationMode = validationMode; + } + + + /** + * Intercepts SOAP responses for outbound messages and validates AS4 receipts. + * + *

Processing Flow: + *

    + *
  1. Extract SOAPMessage from CXF message context
  2. + *
  3. Check if message is an AS4 receipt (has eb:Receipt element)
  4. + *
  5. If receipt: perform schema validation + PEPPOL conformance validation
  6. + *
  7. If validation fails: handle according to {@link #validationMode}
  8. + *
  9. If not a receipt or validation disabled: pass through with no effect
  10. + *
+ * + * @param message the CXF SOAP message being processed + * @throws Fault if validation fails in STRICT mode + */ + @Override + public void handleMessage(SoapMessage message) throws Fault { + + SOAPMessage response = message.getContent(SOAPMessage.class); + + if (response == null) { + log.warn("SOAPMessage content is null, skipping receipt validation"); + return; + } + + // Only validate if it's a receipt message, otherwise skip validation (other non-receipt signals responses) + if (isReceiptMessage(response)) { + try { + + validateReceiptSchema(response); + + validatePeppolConfProfileRules(response); + + } catch (OxalisAs4Exception e) { + handleValidationFailure(e); + } + } + + } + + // validates the receipt against the core AS4 schema, including WS-Security and ebms v3, ebbp singals schemas, but excluding PEPPOL-specific schema rules + private void validateReceiptSchema(SOAPMessage response) throws OxalisAs4Exception { + + try { + Node messagingNode = getMessagingNode(response); + Validator validator = SchemaValidatorFactory.getReceiptValidator(); + + DefaultValidationErrorHandler errorHandler = SchemaValidatorFactory.newDefaultErrorHandler(); + validator.setErrorHandler(errorHandler); + + validator.validate(new DOMSource(messagingNode)); + + if (errorHandler.hasErrors()) { + throw new OxalisAs4Exception("Receipt schema validation failed: " + errorHandler.getErrorsAsString(), AS4ErrorCode.EBMS_0302); + } + log.debug("Receipt schema validated successfully."); + } catch (IOException | SAXException | OxalisAs4TransmissionException e) { + throw new OxalisAs4Exception("Error occurred during schema validation: " + e.getMessage(), e, AS4ErrorCode.EBMS_0302); + } + } + + // validates the receipt against PEPPOL-specific conformance rules + private void validatePeppolConfProfileRules(SOAPMessage response) throws OxalisAs4Exception { + Node receiptNode = getReceiptNode(response); + PeppolAs4ReceiptValidator.validateConformance(receiptNode); + } + + // private helpers + private Node getMessagingNode(SOAPMessage response) throws OxalisAs4TransmissionException { + try { + NodeList messagingNodeList = response.getSOAPHeader().getElementsByTagNameNS("*", "Messaging"); + + if (messagingNodeList.getLength() != 1) { + throw new OxalisAs4TransmissionException("Header contains zero or multiple eb:Messaging elements, should only contain one"); + } + + return messagingNodeList.item(0); + } catch (SOAPException e) { + throw new OxalisAs4TransmissionException("Could not access response header", e); + } + } + + + private Node getReceiptNode(SOAPMessage response) throws OxalisAs4Exception { + try { + NodeList receiptNodeList = response.getSOAPHeader().getElementsByTagNameNS(Constants.EBMS_NAMESPACE, "Receipt"); + + + if (receiptNodeList.getLength() != 1) { + throw new OxalisAs4Exception("Signal message contains multiple Receipt nodes", AS4ErrorCode.EBMS_0302); + } + + return receiptNodeList.item(0); + } catch (SOAPException e) { + throw new OxalisAs4Exception("Could not extract eb:Receipt node from response message", e); + } + } + + private boolean isReceiptMessage(SOAPMessage response) { + try { + NodeList receiptNodeList = response.getSOAPHeader().getElementsByTagNameNS(Constants.EBMS_NAMESPACE, "Receipt"); + return receiptNodeList.getLength() > 0; + } catch (SOAPException e) { + log.warn("Could not access response header to determine if it's a receipt message, assuming it's not a receipt message."); + return false; + } + } + + /** + * Handles validation failures according to the configured {@link ValidationMode}. + * + *

Failure handling by Mode: + *

    + *
  • LOGGING: Logs warning with error details, allows processing to continue
  • + *
  • STRICT: Logs error and throws CXF Fault, terminating the exchange
  • + *
  • NONE: Should not reach here, but logs debug a message if it does
  • + *
+ * + *

Debug logging includes full exception stack trace in LOGGING mode for troubleshooting + * without disrupting production traffic. + * + * @param e the validation exception that occurred + * @throws Fault if in STRICT mode or unknown mode + */ + private void handleValidationFailure(OxalisAs4Exception e) { + switch (validationMode) { + case LOGGING: // default mode + log.warn("Receipt validation failed (mode: LOGGING - continuing): \n\t{}", e.getMessage()); + if (log.isDebugEnabled()) { + log.debug("Validation failure details:", e); + } + break; + case STRICT: + log.error("Receipt validation failed (mode: STRICT - aborting): \n\t{}", e.getMessage()); + throw new Fault(e); + case NONE: + // Should not reach here since validation is skipped, but for the sake of completeness it's here + log.debug("Validation skipped (mode: NONE)"); + break; + default: + log.warn("Unknown validation mode {} - treating as STRICT", validationMode); + throw new Fault(e); + } + } +} + diff --git a/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/util/AS4ErrorCode.java b/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/util/AS4ErrorCode.java index 514e69fc..8aed5a43 100644 --- a/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/util/AS4ErrorCode.java +++ b/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/util/AS4ErrorCode.java @@ -23,7 +23,9 @@ public enum AS4ErrorCode { EBMS_0201("EBMS:0201", "DysfunctionalReliability", PROCESSING, SECURITY), EBMS_0202("EBMS:0202", "DeliveryFailure", COMMUNICATION, SECURITY), + // https://docs.oasis-open.org/ebxml-msg/ebms/v3.0/profiles/AS4-profile/v1.0/os/AS4-profile-v1.0-os.html [section 3.6] EBMS_0301("EBMS:0301", "MissingReceipt", COMMUNICATION, EBMS), + EBMS_0302("EBMS:0302", "InvalidReceipt", COMMUNICATION, EBMS), EBMS_0303("EBMS:0303", "DecompressionFailure", COMMUNICATION, EBMS); private String errorCode; diff --git a/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/util/As4NamespaceContext.java b/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/util/As4NamespaceContext.java new file mode 100644 index 00000000..4fc85d77 --- /dev/null +++ b/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/util/As4NamespaceContext.java @@ -0,0 +1,109 @@ +package network.oxalis.ng.as4.util; + +import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; +import java.util.Iterator; +import java.util.Map; + +/** + * Namespace context for AS4/ebMS/PEPPOL XML processing with bidirectional prefix-URI mappings. + * + *

This class provides a centralized, immutable mapping of XML namespace prefixes to URIs + * for all AS4-related specifications. It implements the JAXP {@link NamespaceContext} interface + * required by XPath expressions, XSLT transformations, and XML parsing operations. + * + *

Supported Namespaces: + *

    + *
  • env: SOAP 1.2 envelope (mandatory for AS4)
  • + *
  • eb: ebMS v3.0 messaging headers (core AS4 protocol)
  • + *
  • ebbp: EBBP signals (receipts and errors)
  • + *
  • ds: XML Digital Signature (WS-Security)
  • + *
  • sbdh: Standard Business Document Header (PEPPOL envelope)
  • + *
+ * + *

Additional Notes: + *

    + *
  • Immutable: Namespace mappings cannot be modified (thread-safe by design)
  • + *
  • Singleton: Single instance shared across the application (reduces overhead)
  • + *
  • Thread-safety: This class is designed for thread-safety. The singleton instance and all mappings are immutable.
  • + *
  • Bidirectional: Supports both prefix→URI and URI→prefix lookups
  • + *
  • Standard Prefixes: Uses conventional prefixes matching specification documentation
  • + *
  • Fallback Behavior: Unrecognized prefixes return XMLConstants.NULL_NS_URI, and unrecognized URIs throw IllegalArgumentException
  • + *
+ * + * @see javax.xml.namespace.NamespaceContext + * @see Constants for the actual namespace URI definitions + */ +public final class As4NamespaceContext implements NamespaceContext { + + // prefix to URI mappings + private static final Map PREFIX_TO_URI; + + static { + PREFIX_TO_URI = Map.ofEntries( + Map.entry("env", Constants.SOAP12_ENV_NAMESPACE), + Map.entry("eb", Constants.EBMS_NAMESPACE), + Map.entry("ebbp", Constants.EBBP_SIGNALS_NAMESPACE), + Map.entry("ds", Constants.DSIG_NAMESPACE), + Map.entry("sbdh", Constants.SBDH_NAMESPACE) + ); + } + + // singleton scope + private static final As4NamespaceContext INSTANCE = new As4NamespaceContext(); + + // private constructor to prevent instantiation + private As4NamespaceContext() { + } + + // public accessor for singleton instance + public static As4NamespaceContext getInstance() { + return INSTANCE; + } + + + // prefix to namespace URI mapping + @Override + public String getNamespaceURI(String prefix) { + if (prefix == null) { + throw new IllegalArgumentException("Prefix cannot be null"); + } + + String uri = PREFIX_TO_URI.get(prefix); + + if (uri != null) { + return uri; + } + + return XMLConstants.NULL_NS_URI; + } + + // namespace URI to prefix mapping + @Override + public String getPrefix(String namespaceURI) { + + if (namespaceURI == null) { + throw new IllegalArgumentException("Namespace URI cannot be null"); + } + + return PREFIX_TO_URI.entrySet().stream() + .filter(e -> e.getValue().equals(namespaceURI)) + .map(Map.Entry::getKey) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No prefix found for namespace URI: " + namespaceURI)); + } + + @Override + public Iterator getPrefixes(String namespaceURI) { + return PREFIX_TO_URI.entrySet().stream() + .filter(e -> e.getValue().equals(namespaceURI)) + .map(Map.Entry::getKey) + .iterator(); + + } + + // return all mappings + public Map getAllMappings() { + return Map.copyOf(PREFIX_TO_URI); + } +} diff --git a/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/util/Constants.java b/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/util/Constants.java index 3ac477d7..3764472d 100644 --- a/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/util/Constants.java +++ b/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/util/Constants.java @@ -8,6 +8,10 @@ public class Constants { public static final String EBMS_NAMESPACE = "http://docs.oasis-open.org/ebxml-msg/ebms/v3.0/ns/core/200704/"; + public static final String EBBP_SIGNALS_NAMESPACE = "http://docs.oasis-open.org/ebxml-bp/ebbp-signals-2.0"; + public static final String DSIG_NAMESPACE = "http://www.w3.org/2000/09/xmldsig#"; + public static final String SOAP12_ENV_NAMESPACE = "http://www.w3.org/2003/05/soap-envelope"; + public static final String SBDH_NAMESPACE = "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader"; public static final QName MESSAGING_QNAME = new QName(EBMS_NAMESPACE, "Messaging", "eb"); public static final QName USER_MESSAGE_QNAME = new QName(EBMS_NAMESPACE, "UserMessage"); diff --git a/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/util/PeppolAs4ReceiptValidator.java b/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/util/PeppolAs4ReceiptValidator.java new file mode 100644 index 00000000..82053c32 --- /dev/null +++ b/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/util/PeppolAs4ReceiptValidator.java @@ -0,0 +1,161 @@ +package network.oxalis.ng.as4.util; + +import lombok.extern.slf4j.Slf4j; +import network.oxalis.ng.as4.lang.OxalisAs4Exception; +import network.oxalis.ng.as4.outbound.As4ReceiptValidationInInterceptor; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +/** + * Validates PEPPOL-specific conformance rules for AS4 receipt messages. + * + *

This validator enforces requirements beyond standard Oasis AS4 that are mandated by the + * PEPPOL AS4 Profile, which extends the CEF eDelivery AS4 Profile. These + * additional rules ensure legal non-repudiation and interoperability across the PEPPOL network. + * + *

Specification References: + *

+ * + * @see As4NamespaceContext for namespace prefix mappings used in XPath expressions + * @see As4ReceiptValidationInInterceptor for where this validator is invoked + */ + +@Slf4j +public final class PeppolAs4ReceiptValidator { + + /** + * Private constructor to prevent instantiation. + * This class is a utility class with only static methods, no instantiation is needed or allowed. + */ + private PeppolAs4ReceiptValidator() { + } + + + /** + * Validates that an AS4 receipt conforms to PEPPOL-specific requirements. + * + *

This is the main entry point for PEPPOL receipt validation. It performs a + * hierarchical series of checks, failing fast on the first violation: + * + *

    + *
  1. NonRepudiationInformation check: Exactly one element must exist
  2. + *
  3. MessagePartNRInformation check: At least one element must exist
  4. + *
  5. Reference format check: Only ds:Reference allowed (no MessagePartIdentifier)
  6. + *
+ * + *

Error Handling: + * All validation failures throw {@link OxalisAs4Exception} with error code EBMS_0302 + * (Invalid Receipt), which indicates the receipt structure doesn't conform to + * the required AS4/PEPPOL profile. On an empty or null receipt, EBMS_0301 (Missing Receipt) is thrown instead. + * + * @param receiptNode the eb:Receipt DOM node to validate (must not be null) + * @throws OxalisAs4Exception if the receipt is null or fails any PEPPOL conformance rule + */ + public static void validateConformance(Node receiptNode) throws OxalisAs4Exception { + if (receiptNode == null) { + throw new OxalisAs4Exception("Receipt not found in the response message", AS4ErrorCode.EBMS_0301); + } + + // Validates in hierarchical order (fail-fast) + checkNonRepudiationInformation(receiptNode); + checkMessagePartNRInformation(receiptNode); + checkMessagePartReferences(receiptNode); + + log.debug("PEPPOL receipt conformance validated successfully."); + } + + + /** + * Since PEPPOL AS4 Receipt MUST contain NonRepudiationInformation element on synchronous transmission responses + * as per PEPPOL AS4 profile > CEF eDelivery Common Profile + * + *

Specification Reference: + * + * CEF eDelivery AS4 - Section 1.14: Reliable Messaging and Non-Repudiation of Receipt + */ + private static void checkNonRepudiationInformation(Node receiptNode) + throws OxalisAs4Exception { + + var nonRepudiationInfoElements = getElements(receiptNode, "//ebbp:NonRepudiationInformation"); + + if (nonRepudiationInfoElements.getLength() == 0) { + throw new OxalisAs4Exception("NonRepudiationInformation element not found", AS4ErrorCode.EBMS_0302); + } + + if (nonRepudiationInfoElements.getLength() > 1) { + throw new OxalisAs4Exception("Multiple NonRepudiationInformation elements found", AS4ErrorCode.EBMS_0302); + } + + } + + + public static void checkMessagePartNRInformation(Node receiptNode) + throws OxalisAs4Exception { + var messagePartNRInfoElements = getElements(receiptNode, "//ebbp:NonRepudiationInformation/ebbp:MessagePartNRInformation"); + + if (messagePartNRInfoElements.getLength() == 0) { + throw new OxalisAs4Exception("MessagePartNRInformation element(s) not found", AS4ErrorCode.EBMS_0302); + } + } + + public static void checkMessagePartReferences(Node receiptNode) throws OxalisAs4Exception { + var messagePartNRInfoElements = getElements(receiptNode, "//ebbp:NonRepudiationInformation/ebbp:MessagePartNRInformation"); + + for (int i = 0; i < messagePartNRInfoElements.getLength(); i++) { + var dsigRefs = getElements(messagePartNRInfoElements.item(i), "ds:Reference"); + var messagePartIdentifiers = getElements(messagePartNRInfoElements.item(i), "ebbp:MessagePartIdentifier"); + + boolean hasDsigRefs = dsigRefs.getLength() > 0; + boolean hasMessagePartIdentifiers = messagePartIdentifiers.getLength() > 0; + + // checks conformance for message part references in AS4 receipts as per + // https://docs.oasis-open.org/ebxml-msg/ebms/v3.0/profiles/AS4-profile/v1.0/os/AS4-profile-v1.0-os.html#__RefHeading__26454_1909778835 [Section 5.1.9] + + // explicit checking for #135 + if (hasDsigRefs && hasMessagePartIdentifiers) { + throw new OxalisAs4Exception("Both ds:Reference and ebbpsig:MessagePartIdentifier elements are not allowed together in Peppol AS4 receipts", AS4ErrorCode.EBMS_0302); + } + + if (!hasDsigRefs) { + throw new OxalisAs4Exception("Digest references are not found in the non-repudiation receipt", AS4ErrorCode.EBMS_0302); + } + } + } + + + private static void checkElementExists(Node context, String xpathExpression, String errorMessage) throws OxalisAs4Exception { + NodeList nodes = getElements(context, xpathExpression); + + if (nodes.getLength() == 0) { + throw new OxalisAs4Exception(errorMessage, AS4ErrorCode.EBMS_0302); + } + } + + private static synchronized NodeList getElements(Node context, String xpathExpression) throws OxalisAs4Exception { + try { + + XPath xPath = XPathFactory.newInstance().newXPath(); + xPath.setNamespaceContext(As4NamespaceContext.getInstance()); + + return (NodeList) xPath.evaluate( + xpathExpression, + context, + XPathConstants.NODESET + ); + } catch (XPathExpressionException e) { + throw new OxalisAs4Exception("XPath evaluation failed: " + xpathExpression, e); + } + } + +} \ No newline at end of file diff --git a/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/util/SchemaValidatorFactory.java b/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/util/SchemaValidatorFactory.java new file mode 100644 index 00000000..1cc46d1a --- /dev/null +++ b/oxalis-ng-extension/oxalis-ng-as4/src/main/java/network/oxalis/ng/as4/util/SchemaValidatorFactory.java @@ -0,0 +1,368 @@ +package network.oxalis.ng.as4.util; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import network.oxalis.ng.api.lang.OxalisResourceException; +import org.apache.cxf.common.xmlschema.LSInputImpl; +import org.w3c.dom.ls.LSInput; +import org.w3c.dom.ls.LSResourceResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +import javax.xml.XMLConstants; +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Factory for creating XML Schema validators for AS4 message validation. + * + *

This factory provides thread-safe, cached Schema and Validator instances for validating + * AS4/ebMS messages against the relevant XSD schemas. It can handle schemas for pre-defined validations contexts or custom sets of schemas.: + *

    + *
  • SOAP 1.2 envelope structure (SOAP 1.2 is mandatory for AS4)
  • + *
  • ebMS v3.0 messaging headers (Oasis AS4 protocol)
  • + *
  • EBBP signals for receipts and errors
  • + *
  • XML Digital Signatures
  • + *
+ * + * @see ValidationContext for pre-defined validation scenarios + * @see DefaultValidationErrorHandler for error collection and reporting + */ +@Slf4j +public class SchemaValidatorFactory { + + // XSD Schema resource paths + private static final String SOAP_ENVELOPE = "w3/soap-envelope.xsd"; + private static final String EBMS_V3 = "ebxml/ebms-header-3_0-200704.xsd"; + private static final String EBBP_SIGNAL = "ebxml/ebbp-signals-2.0.xsd"; + private static final String XML_DSIG = "w3/xmldsig-core-schema.xsd"; + + /** + * Cache for pre-compiled schemas by validation context. + * Thread-safe and prevents redundant schema compilation. + */ + private static final Map schemaCache = new ConcurrentHashMap<>(); + private static final Map customSchemaCache = new ConcurrentHashMap<>(); + + + /** + * Pre-defined validation contexts for common AS4 message types. + * Each context defines which XSD schemas must be compiled together for a message type. + * + *

Available Contexts: + *

    + *
  • SIGNAL_MESSAGE: For AS4 receipts and errors (SOAP + ebMS + XMLDsig)
  • + *
  • USER_MESSAGE: For AS4 user messages carrying business documents (SOAP + ebMS + XMLDsig)
  • + *
  • NRR_RECEIPT: For non-repudiation receipts specifically (ebMS + EBBP + XMLDsig, no SOAP envelope)
  • + *
+ */ + @Getter + public enum ValidationContext { + + SIGNAL_MESSAGE( + SOAP_ENVELOPE, + EBMS_V3, + XML_DSIG + ), // for EBBP signals like PullRequest, ReceiptNotification (ebMS header + SOAP envelope) + + USER_MESSAGE( + SOAP_ENVELOPE, + EBMS_V3, + XML_DSIG + ), // for user messages (ebMS header + SOAP envelope) + + NRR_RECEIPT( + EBMS_V3, + EBBP_SIGNAL, + XML_DSIG + ); // for NonRepudiationInformation in receipts (ebMS header + EBBP signals, no SOAP envelope) + + private final String[] schemaPaths; + + ValidationContext(String... schemaPaths) { + this.schemaPaths = schemaPaths; + } + + } + + // ==================== Public API ==================== + + /** + * Returns a compiled, cached Schema for the specified validation context. + * + *

Schemas are compiled once and cached for performance. This method is thread-safe. + * + * @param context the validation context defining which schemas to combine + * @return compiled Schema instance (cached) + * @throws OxalisResourceException on schema compilation failure + */ + public static Schema getSchema(ValidationContext context) { + return schemaCache.computeIfAbsent(context, SchemaValidatorFactory::getCompiledSchema); + } + + /** + * Returns a compiled, cached Schema for custom schema paths. + * + *

Use this when you need to validate against a custom combination of schemas + * not covered by the pre-defined {@link ValidationContext} options. + * + * @param schemaPaths classpath-relative paths to XSD files + * @return compiled Schema instance (cached with path concatenation as key) + * @throws OxalisResourceException on schema compilation failure + */ + public static Schema getSchema(String... schemaPaths) { + String key = String.join("|", schemaPaths); + return customSchemaCache.computeIfAbsent(key, k -> getCompiledSchema(schemaPaths)); + } + + /** + * Creates a new Validator for the specified validation context. + * + *

Note: Validators are NOT thread-safe and should not be shared between threads. + * Create a new validator for each validation operation. + * + * @param context the validation context defining which schemas to be validated against + * @return new Validator instance + * @throws OxalisResourceException on schema compilation failure + */ + public static Validator getValidator(ValidationContext context) { + Schema schema = getSchema(context); + return schema.newValidator(); + } + + /** + * Creates a new Validator for custom schema paths. + * + * @param schemaPaths classpath-relative paths to XSD files + * @return new Validator instance + * @throws OxalisResourceException on schema compilation failure + */ + public static Validator getValidator(String... schemaPaths) { + Schema schema = getSchema(schemaPaths); + return schema.newValidator(); + } + + public static Validator getReceiptValidator() { + return getValidator(ValidationContext.NRR_RECEIPT); + } + + public static DefaultValidationErrorHandler newDefaultErrorHandler() { + return new DefaultValidationErrorHandler(); + } + + + // private helpers + + private static Schema getCompiledSchema(ValidationContext validationContext) { + return getCompiledSchema(validationContext.getSchemaPaths()); + } + + /** + * Compiles multiple XSD schemas into a single Schema object. + * + *

This method: + *

    + *
  1. Loads all schema files from the classpath into memory
  2. + *
  3. Configures the custom resource resolver for XSD imports
  4. + *
  5. Compiles all schemas together into a single Schema instance
  6. + *
+ * + *

Synchronized to ensure thread-safe schema compilation (SchemaFactory is not thread-safe + * during configuration changes). + * + * @param schemaPaths classpath-relative paths to XSD files + * @return compiled Schema instance + * @throws OxalisResourceException if schema loading or compilation fails + */ + private static synchronized Schema getCompiledSchema(String... schemaPaths) { + try { + + SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + schemaFactory.setResourceResolver(new ClasspathSchemaResolver()); + + Source[] sources = loadSchemaSources(schemaPaths); + + return schemaFactory.newSchema(sources); + + } catch (SAXException e) { + log.error("Error compiling schema: {}", e.getMessage(), e); + throw new OxalisResourceException("Error compiling schema", e); + } + } + + + /** + * Loads schema files from the classpath into an array of StreamSource objects. + * + *

Schemas are fully read into memory to avoid resource leaks and to ensure + * the streams remain available during validation. This is acceptable since + * XSD files are typically small (< 100KB each). + * + * @param schemaPaths classpath-relative paths to XSD files + * @return array of StreamSource objects + * @throws OxalisResourceException on schema loading failure (file not found or read error / all or nothing) + */ + + private static Source[] loadSchemaSources(String... schemaPaths) { + Source[] sources = new Source[schemaPaths.length]; + + for (int i = 0; i < schemaPaths.length; i++) { + String path = schemaPaths[i]; + + try (InputStream is = SchemaValidatorFactory.class.getClassLoader().getResourceAsStream(path)) { + if (is == null) { + throw new OxalisResourceException("Schema resource not found: " + path); + } + + // the schemas are not big and read typically once, so this is fine + byte[] schemaBytes = is.readAllBytes(); + + sources[i] = new StreamSource(new ByteArrayInputStream(schemaBytes), path); + + } catch (IOException e) { + throw new OxalisResourceException("Failed to read schema resource: " + path, e); + } + } + + return sources; + } + + /** + * Custom LSResourceResolver for resolving XSD imports and includes from classpath. + * + *

This resolver handles: + *

    + *
  • Relative path resolution (../ and ./)
  • + *
  • Common XSD location prefixes (/ebxml/, /w3/, /xmlsoap/)
  • + *
  • Fallback to the root classpath if no prefix matches
  • + *
+ * + *

This is necessary because XSD schemas often reference other schemas using + * relative or absolute paths that need to be resolved against the classpath. + */ + private static class ClasspathSchemaResolver implements LSResourceResolver { + @Override + public LSInput resolveResource(String type, String namespaceURI, + String publicId, String systemId, String baseURI) { + InputStream stream = tryLoadSchema(systemId, baseURI); + return stream != null ? new LSInputImpl(publicId, systemId, stream) : null; + } + + /** + * Attempts to load a schema from the classpath using multiple resolution strategies. + * + * @param systemId the schema identifier (filename or path) + * @param baseURI the URI of the schema containing the reference (for relative path resolution) + * @return InputStream to the schema, or null if not found + */ + private InputStream tryLoadSchema(String systemId, String baseURI) { + if (systemId == null) return null; + + // Strategy 1: Resolve relative paths (../ or ./) against the base URI + if (baseURI != null && (systemId.startsWith("../") || systemId.startsWith("./"))) { + String resolved = resolveRelativePath(baseURI, systemId); + InputStream stream = getClass().getResourceAsStream(resolved); + if (stream != null) return stream; + } + + // Strategy 2: Try common classpath prefixes where XSD files are typically located + for (String prefix : new String[]{"", "/ebxml/", "/w3/", "/xmlsoap/"}) { + InputStream stream = getClass().getResourceAsStream(prefix + systemId); + if (stream != null) return stream; + } + + return null; + } + + private String resolveRelativePath(String baseURI, String systemId) { + try { + URI base = new URI(baseURI); + URI resolved = base.resolve(systemId); + + return resolved.getPath(); + } catch (Exception e) { + return systemId; + } + } + } + + + /** + * SAX ErrorHandler that collects validation errors and warnings for reporting . + * + *

This handler accumulates all errors and warnings during validation, allowing + * the caller to inspect them after validation completes. It distinguishes between: + *

    + *
  • Warnings: Non-fatal issues that don't prevent processing
  • + *
  • Errors: Schema violations that make the document invalid
  • + *
  • Fatal Errors: Severe problems that prevent further parsing
  • + *
+ * + */ + @Getter + public static class DefaultValidationErrorHandler implements ErrorHandler { + + private final List errors = new ArrayList<>(); + private final List warnings = new ArrayList<>(); + + + @Override + public void warning(SAXParseException exception) { + warnings.add(exception); + } + + @Override + public void error(SAXParseException exception) { + errors.add(exception); + } + + @Override + public void fatalError(SAXParseException exception) { + errors.add(exception); + } + + public boolean hasErrors() { + return !errors.isEmpty(); + } + + public String getErrorsAsString() { + StringBuilder sb = new StringBuilder(); + for (SAXParseException e : errors) { + sb.append(String.format("Line %d, Column %d: %s%n", e.getLineNumber(), e.getColumnNumber(), e.getMessage())); + } + return sb.toString(); + } + + public String getWarningsAsString() { + StringBuilder sb = new StringBuilder(); + for (SAXParseException e : warnings) { + sb.append(String.format("Line %d, Column %d: %s%n", e.getLineNumber(), e.getColumnNumber(), e.getMessage())); + } + return sb.toString(); + } + + public String getValidationSummary() { + StringBuilder sb = new StringBuilder(); + if (!warnings.isEmpty()) { + sb.append("Warnings:\n").append(getWarningsAsString()); + } + if (!errors.isEmpty()) { + sb.append("Errors:\n").append(getErrorsAsString()); + } + return sb.toString(); + } + } + +} diff --git a/oxalis-ng-extension/oxalis-ng-as4/src/test/java/network/oxalis/ng/as4/SendReceiveTest.java b/oxalis-ng-extension/oxalis-ng-as4/src/test/java/network/oxalis/ng/as4/SendReceiveTest.java index c2bb1616..badb3cbd 100644 --- a/oxalis-ng-extension/oxalis-ng-as4/src/test/java/network/oxalis/ng/as4/SendReceiveTest.java +++ b/oxalis-ng-extension/oxalis-ng-as4/src/test/java/network/oxalis/ng/as4/SendReceiveTest.java @@ -21,6 +21,7 @@ import network.oxalis.ng.as4.api.MessageIdGenerator; import network.oxalis.ng.as4.common.DefaultMessageIdGenerator; import network.oxalis.ng.as4.inbound.As4InboundModule; +import network.oxalis.ng.as4.outbound.As4ReceiptValidationInInterceptor; import network.oxalis.ng.as4.util.PeppolConfiguration; import network.oxalis.ng.commons.guice.GuiceModuleLoader; import network.oxalis.ng.test.jetty.AbstractJettyServerTest; @@ -29,6 +30,7 @@ import org.apache.cxf.BusFactory; import org.apache.cxf.ext.logging.LoggingFeature; import org.apache.cxf.feature.Feature; +import org.apache.cxf.phase.Phase; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.testng.Assert; import org.testng.annotations.BeforeClass; @@ -70,7 +72,7 @@ public SendReceiveTest() throws Exception { @Override public Injector getInjector() { - if(Security.getProvider(BouncyCastleProvider.PROVIDER_NAME)==null){ + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { Security.addProvider(new BouncyCastleProvider()); } return Guice.createInjector( @@ -78,13 +80,18 @@ public Injector getInjector() { Modules.override(new GuiceModuleLoader()).with(new AbstractModule() { @Override protected void configure() { - bind(Key.get(MessageSender.class,Names.named("oxalis-as4"))) + bind(Key.get(MessageSender.class, Names.named("oxalis-as4"))) .to(As4MessageSenderFacade.class); bind(ReceiptPersister.class).toInstance((m, p) -> { }); bind(PayloadPersister.class).toInstance(temporaryFilePersister); bind(InboundService.class).toInstance(temporaryInboundService); bind(MessageIdGenerator.class).toInstance(new DefaultMessageIdGenerator("test.com")); + bind(As4ReceiptValidationInInterceptor.class).toInstance( + new As4ReceiptValidationInInterceptor( + Phase.POST_PROTOCOL, + As4ReceiptValidationInInterceptor.ValidationMode.LOGGING) + ); } }) ); @@ -267,8 +274,8 @@ private static class Data { } static class TemporaryInboundService implements InboundService { - @Override - public void complete(InboundMetadata inboundMetadata) { - } + @Override + public void complete(InboundMetadata inboundMetadata) { + } } } diff --git a/oxalis-ng-extension/oxalis-ng-as4/src/test/java/network/oxalis/ng/as4/util/As4NamespaceContextTest.java b/oxalis-ng-extension/oxalis-ng-as4/src/test/java/network/oxalis/ng/as4/util/As4NamespaceContextTest.java new file mode 100644 index 00000000..8c7015ca --- /dev/null +++ b/oxalis-ng-extension/oxalis-ng-as4/src/test/java/network/oxalis/ng/as4/util/As4NamespaceContextTest.java @@ -0,0 +1,61 @@ +package network.oxalis.ng.as4.util; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import javax.xml.XMLConstants; + +import static org.testng.Assert.*; + +public class As4NamespaceContextTest { + + private final As4NamespaceContext context = As4NamespaceContext.getInstance(); + + @DataProvider(name = "prefixToUriDataProvider") + public Object[][] prefixToUriDataProvider() { + return new Object[][]{ + {"env", "http://www.w3.org/2003/05/soap-envelope"}, + {"eb", "http://docs.oasis-open.org/ebxml-msg/ebms/v3.0/ns/core/200704/"}, + {"ebbp", "http://docs.oasis-open.org/ebxml-bp/ebbp-signals-2.0"}, + {"ds", "http://www.w3.org/2000/09/xmldsig#"}, + {"sbdh", "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader"}, + {"unknown", XMLConstants.NULL_NS_URI} + }; + } + + + @DataProvider(name = "uriToPrefixDataProvider") + public Object[][] uriToPrefixDataProvider() { + return new Object[][]{ + {"http://www.w3.org/2003/05/soap-envelope", "env"}, + {"http://docs.oasis-open.org/ebxml-msg/ebms/v3.0/ns/core/200704/", "eb"}, + {"http://docs.oasis-open.org/ebxml-bp/ebbp-signals-2.0", "ebbp"}, + {"http://www.w3.org/2000/09/xmldsig#", "ds"}, + {"http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", "sbdh"} + }; + } + + + @Test(dataProvider = "prefixToUriDataProvider") + public void getNamespaceURI_validPrefix_returnsNamespaceUri(String prefix, String expectedUri) { + String actualUri = context.getNamespaceURI(prefix); + assertEquals(actualUri, expectedUri, "Namespace URI for prefix '" + prefix + "' did not match expected value."); + } + + @Test(dataProvider = "uriToPrefixDataProvider") + public void getPrefix_validUri_returnsPrefix(String namespaceURI, String expectedPrefix) { + String actualPrefix = context.getPrefix(namespaceURI); + assertEquals(actualPrefix, expectedPrefix, "Prefix for namespace URI '" + namespaceURI + "' did not match expected value."); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void getPrefix_unknownNamespaceUri_throwsIllegalArgumentException() { + String unknownNamespaceUri = "http://unknown.namespace/uri"; + context.getPrefix(unknownNamespaceUri); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void getPrefix_nullNamespaceUri_throwsIllegalArgumentException() { + context.getPrefix(null); + } +} \ No newline at end of file diff --git a/oxalis-ng-extension/oxalis-ng-as4/src/test/java/network/oxalis/ng/as4/util/PeppolAs4ReceiptValidatorTest.java b/oxalis-ng-extension/oxalis-ng-as4/src/test/java/network/oxalis/ng/as4/util/PeppolAs4ReceiptValidatorTest.java new file mode 100644 index 00000000..df81a30f --- /dev/null +++ b/oxalis-ng-extension/oxalis-ng-as4/src/test/java/network/oxalis/ng/as4/util/PeppolAs4ReceiptValidatorTest.java @@ -0,0 +1,176 @@ +package network.oxalis.ng.as4.util; + +import network.oxalis.ng.as4.lang.OxalisAs4Exception; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import static org.testng.Assert.*; + +public class PeppolAs4ReceiptValidatorTest { + + private Node validReceiptNode; + private Node invalidReceiptNode; + + @BeforeClass + public void setUp() throws Exception { + // Load receipts from test resources + validReceiptNode = extractReceiptNode("/ebms/nrr-signal-message.xml"); + invalidReceiptNode = extractReceiptNode("/ebms/nrr-signal-message-invalid-conformance.xml"); + } + + // Positive test case - valid receipt should pass without exceptions + @Test + public void validateConformance_validReceipt_passesValidation() throws OxalisAs4Exception { + // Act & Assert - should not throw + PeppolAs4ReceiptValidator.validateConformance(validReceiptNode); + } + + @Test + public void validateConformance_nullReceiptNode_throwsWithCorrectErrorCode() { + // Act + try { + PeppolAs4ReceiptValidator.validateConformance(null); + fail("Should have thrown OxalisAs4Exception"); + } catch (OxalisAs4Exception e) { + // Assert + assertEquals(e.getErrorCode(), AS4ErrorCode.EBMS_0301, "Should throw with EBMS_0301 error code for missing receipt"); + assertTrue(e.getMessage().contains("Receipt not found"), "Exception message should indicate missing receipt"); + } + } + + // oxalis-as4 #135 + // invalid receipt with both ds:Reference and ebbpsig:MessagePartIdentifier should throw exception with the correct error code + @Test + public void validateConformance_invalidMessagePartNRInformationNode_throwsErrorWithCode() { + // Act + try { + PeppolAs4ReceiptValidator.validateConformance(invalidReceiptNode); + fail("Should have thrown OxalisAs4Exception"); + } catch (OxalisAs4Exception e) { + // Assert + assertEquals(e.getErrorCode(), AS4ErrorCode.EBMS_0302, "Should throw with EBMS_0302 error code for missing NonRepudiationInformation"); + assertTrue(e.getMessage().contains("ds:Reference and ebbpsig:MessagePartIdentifier elements are not allowed together"), "Exception message should indicate the specific conformance issue"); + } + } + + @Test + public void validateConformance_noNonRepudiationInfo_throwsOxalisAs4Exception() throws Exception { + // Arrange + String xml = "" + + ""; + Node receiptNode = parseXmlString(xml).getDocumentElement(); + + // Act + try { + PeppolAs4ReceiptValidator.validateConformance(receiptNode); + fail("Should have thrown OxalisAs4Exception"); + } catch (OxalisAs4Exception e) { + // Assert + assertEquals(e.getErrorCode(), AS4ErrorCode.EBMS_0302); + assertTrue(e.getMessage().contains("NonRepudiationInformation element not found"), "Error message should mention missing NonRepudiationInformation"); + } + } + + + @Test + public void validateConformance_multipleNonRepudiationInfo_throwsOxalisAs4Exception() throws Exception { + // Arrange + String xml = "" + + " " + + " " + + " " + + " " + + " 6Kvu0nc3EzKKbwJs7XGZfCnQlt971OVi1cm6T6HyBTs=" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " wCtCO88xutufHEOdOHmD3746QjkclPHGGfMkPfPECCI=" + + " " + + " " + + " " + + ""; + Node receiptNode = parseXmlString(xml).getDocumentElement(); + + // Act + try { + PeppolAs4ReceiptValidator.validateConformance(receiptNode); + fail("Should have thrown OxalisAs4Exception"); + } catch (OxalisAs4Exception e) { + // Assert + assertEquals(e.getErrorCode(), AS4ErrorCode.EBMS_0302); + assertTrue(e.getMessage().contains("Multiple NonRepudiationInformation elements found"), "Error message should mention multiple NonRepudiationInformation"); + } + } + + @Test + public void validateConformance_noDsigReferences_throwsOxalisAs4Exception() throws Exception { + // Arrange + String xml = "" + + " " + + " " + + " " + + " " + + ""; + Node receiptNode = parseXmlString(xml).getDocumentElement(); + + // Act + try { + PeppolAs4ReceiptValidator.validateConformance(receiptNode); + fail("Should have thrown OxalisAs4Exception"); + } catch (OxalisAs4Exception e) { + // Assert + assertEquals(e.getErrorCode(), AS4ErrorCode.EBMS_0302); + assertTrue(e.getMessage().contains("Digest references are not found"), "Error message should mention missing digest references"); + } + } + + + // helpers + private Document parseXml(InputStream is) throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + DocumentBuilder builder = factory.newDocumentBuilder(); + return builder.parse(is); + } + + private Document parseXmlString(String xml) throws Exception { + return parseXml(new ByteArrayInputStream(xml.getBytes())); + } + + private Node extractReceiptNode(String testResource) throws Exception { + + try (InputStream is = getClass().getResourceAsStream(testResource)) { + assertNotNull(is, "Test resource " + testResource + " should exist"); + Document doc = parseXml(is); + + NodeList receiptList = doc.getElementsByTagNameNS( + "http://docs.oasis-open.org/ebxml-msg/ebms/v3.0/ns/core/200704/", + "Receipt" + ); + + if (receiptList.getLength() > 0) { + return receiptList.item(0); + } + + return doc.getDocumentElement(); + } + + + } + + +} \ No newline at end of file diff --git a/oxalis-ng-extension/oxalis-ng-as4/src/test/java/network/oxalis/ng/as4/util/SchemaValidatorFactoryTest.java b/oxalis-ng-extension/oxalis-ng-as4/src/test/java/network/oxalis/ng/as4/util/SchemaValidatorFactoryTest.java new file mode 100644 index 00000000..cb1ed299 --- /dev/null +++ b/oxalis-ng-extension/oxalis-ng-as4/src/test/java/network/oxalis/ng/as4/util/SchemaValidatorFactoryTest.java @@ -0,0 +1,260 @@ +package network.oxalis.ng.as4.util; + +import network.oxalis.ng.api.lang.OxalisResourceException; +import org.testng.annotations.Test; +import org.xml.sax.SAXException; + +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.Validator; + +import java.io.IOException; +import java.io.InputStream; + +import static org.testng.Assert.*; + +public class SchemaValidatorFactoryTest { + + + // Schema caching tests + + @Test + public void getSchema_sameContextCalledTwice_returnsSameCachedInstance() { + + // Arrange + SchemaValidatorFactory.ValidationContext context = SchemaValidatorFactory.ValidationContext.SIGNAL_MESSAGE; + + // Act + var schema1 = SchemaValidatorFactory.getSchema(context); + var schema2 = SchemaValidatorFactory.getSchema(context); + + // Assert + assertNotNull(schema1); + assertNotNull(schema2); + + assertSame(schema1, schema2); + + } + + @Test + public void getSchema_differentContexts_returnsDifferentInstances() { + + // Arrange + SchemaValidatorFactory.ValidationContext context1 = SchemaValidatorFactory.ValidationContext.SIGNAL_MESSAGE; + SchemaValidatorFactory.ValidationContext context2 = SchemaValidatorFactory.ValidationContext.USER_MESSAGE; + + // Act + var schema1 = SchemaValidatorFactory.getSchema(context1); + var schema2 = SchemaValidatorFactory.getSchema(context2); + + // Assert + assertNotNull(schema1); + assertNotNull(schema2); + + assertNotSame(schema1, schema2); + + } + + @Test + public void getSchema_customSchemaPathsSameKey_returnsSameCachedInstance() { + // Arrange + String[] schemaPaths = { + "ebxml/ebms-header-3_0-200704.xsd", + "w3/xmldsig-core-schema.xsd", + "xmlsoap/envelope.xsd" + }; + + // Act + Schema schema1 = SchemaValidatorFactory.getSchema(schemaPaths); + Schema schema2 = SchemaValidatorFactory.getSchema(schemaPaths); + + // Assert + assertNotNull(schema1, "First schema instance should not be null"); + assertNotNull(schema2, "Second schema instance should not be null"); + assertSame(schema2, schema1, "Same schema paths should return the same cached instance"); + } + + @Test + public void getSchema_customSchemaPathsDifferentKeys_returnsDifferentInstances() { + // Arrange + String[] schemaPaths1 = { + "ebxml/ebms-header-3_0-200704.xsd", + "w3/xmldsig-core-schema.xsd" + }; + + String[] schemaPaths2 = { + "ebxml/ebms-header-3_0-200704.xsd", + "w3/xmldsig-core-schema.xsd", + "ebxml/ebbp-signals-2.0.xsd" // Different - has additional schema + }; + + // Act + Schema schema1 = SchemaValidatorFactory.getSchema(schemaPaths1); + Schema schema2 = SchemaValidatorFactory.getSchema(schemaPaths2); + + // Assert + assertNotNull(schema1, "First schema instance should not be null"); + assertNotNull(schema2, "Second schema instance should not be null"); + assertNotSame(schema2, schema1, "Different schema paths should return different instances"); + } + + + // negative tests for invalid schema paths + + @Test(expectedExceptions = OxalisResourceException.class) + public void getSchema_nonExistentSchemaPath_throwsResourceException() { + // Arrange + String[] schemaPaths = { + "nonexistent/path/schema.xsd" + }; + + // Act + SchemaValidatorFactory.getSchema(schemaPaths); + } + + @Test(expectedExceptions = OxalisResourceException.class) + public void getSchema_wrongSchemaPath_throwsResourceException() { + // Arrange + String[] schemaPaths = { + "../../../soap-envelope.xsd", + }; + + // Act + SchemaValidatorFactory.getSchema(schemaPaths); + } + + + // validator tests + @Test + public void getValidator_validContext_returnsValidator() { + // Arrange + SchemaValidatorFactory.ValidationContext context = SchemaValidatorFactory.ValidationContext.SIGNAL_MESSAGE; + + // Act + var validator = SchemaValidatorFactory.getValidator(context); + + // Assert + assertNotNull(validator, "Validator should not be null for valid context"); + } + + @Test + public void getValidator_multipleCallsSameContext_returnsDifferentValidatorInstances() { + // Arrange + SchemaValidatorFactory.ValidationContext context = SchemaValidatorFactory.ValidationContext.USER_MESSAGE; + + // Act + var validator1 = SchemaValidatorFactory.getValidator(context); + var validator2 = SchemaValidatorFactory.getValidator(context); + + // Assert + assertNotNull(validator1, "First validator instance should not be null"); + assertNotNull(validator2, "Second validator instance should not be null"); + assertNotSame(validator1, validator2, "Multiple calls should return different validator instances"); + } + + // positive test for user message validation + @Test + public void validateUserMessage_successfulValidation() throws IOException, SAXException { + // Arrange + Validator validator = SchemaValidatorFactory.getValidator(SchemaValidatorFactory.ValidationContext.USER_MESSAGE); + SchemaValidatorFactory.DefaultValidationErrorHandler errorHandler = SchemaValidatorFactory.newDefaultErrorHandler(); + validator.setErrorHandler(errorHandler); + + // Act + InputStream is = getClass().getResourceAsStream("/ebms/user-message.xml"); + + try (is) { + assertNotNull(is, "Test file user-message.xml should exist in test resources"); + + StreamSource source = new StreamSource(is); + source.setSystemId("ebms/user-message.xml"); + + validator.validate(source); + + // Assert + assertFalse(errorHandler.hasErrors(), + "User message should be valid. Errors: " + errorHandler.getErrorsAsString()); + + } + } + + // negative test for an invalid user message + @Test + public void validateUserMessage_invalidMessage_hasValidationErrors() throws IOException, SAXException { + + Validator validator = SchemaValidatorFactory.getValidator(SchemaValidatorFactory.ValidationContext.USER_MESSAGE); + SchemaValidatorFactory.DefaultValidationErrorHandler errorHandler = SchemaValidatorFactory.newDefaultErrorHandler(); + validator.setErrorHandler(errorHandler); + + // Act + InputStream is = getClass().getResourceAsStream("/ebms/user-message-invalid.xml"); + + try (is) { + assertNotNull(is, "Test file user-message.xml should exist in test resources"); + StreamSource source = new StreamSource(is); + source.setSystemId("ebms/user-message-invalid.xml"); + + validator.validate(source); + + // Assert + assertTrue(errorHandler.hasErrors(), "Invalid user message should have validation errors"); + String errors = errorHandler.getErrorsAsString(); + + System.out.println("Validation errors for invalid user message:\n" + errors); + } + } + + // positive test for the receipt message + @Test + public void validateReceiptMessage_successfulValidation() throws IOException, SAXException { + // Arrange + Validator validator = SchemaValidatorFactory.getValidator(SchemaValidatorFactory.ValidationContext.NRR_RECEIPT); + SchemaValidatorFactory.DefaultValidationErrorHandler errorHandler = SchemaValidatorFactory.newDefaultErrorHandler(); + validator.setErrorHandler(errorHandler); + + // Act + InputStream is = getClass().getResourceAsStream("/ebms/nrr-signal-message.xml"); + + try (is) { + assertNotNull(is, "Test file nrr-signal-message.xml should exist in test resources"); + + StreamSource source = new StreamSource(is); + source.setSystemId("ebms/nrr-signal-message.xml"); + + validator.validate(source); + + // Assert + assertFalse(errorHandler.hasErrors(), + "Signal message should be valid. Errors: " + errorHandler.getErrorsAsString()); + + } + } + + + // negative test for an invalid signal message + @Test + public void validateReceiptMessage_invalidMessage_hasValidationErrors() throws IOException, SAXException { + Validator validator = SchemaValidatorFactory.getValidator(SchemaValidatorFactory.ValidationContext.NRR_RECEIPT); + SchemaValidatorFactory.DefaultValidationErrorHandler errorHandler = SchemaValidatorFactory.newDefaultErrorHandler(); + validator.setErrorHandler(errorHandler); + + // Act + InputStream is = getClass().getResourceAsStream("/ebms/nrr-signal-message-invalid-schema.xml"); + + try (is) { + assertNotNull(is, "Test file nrr-signal-message-invalid-schema.xml should exist in test resources"); + + StreamSource source = new StreamSource(is); + source.setSystemId("ebms/nrr-signal-message-invalid-schema.xml"); + + validator.validate(source); + + // Assert + assertTrue(errorHandler.hasErrors(), "Invalid signal message should have validation errors"); + String errors = errorHandler.getErrorsAsString(); + + System.out.println("Validation errors for invalid signal message:\n" + errors); + } + } + +} \ No newline at end of file diff --git a/oxalis-ng-extension/oxalis-ng-as4/src/test/resources/ebms/nrr-signal-message-invalid-conformance.xml b/oxalis-ng-extension/oxalis-ng-as4/src/test/resources/ebms/nrr-signal-message-invalid-conformance.xml new file mode 100644 index 00000000..71809fa6 --- /dev/null +++ b/oxalis-ng-extension/oxalis-ng-as4/src/test/resources/ebms/nrr-signal-message-invalid-conformance.xml @@ -0,0 +1,47 @@ + + + + 2026-02-06T12:00:28.426+01:00 + d3a83fa2-955e-4f12-89b5-d80d614c07f0@test + f2ee7d49-c8a8-4b78-98e3-49d7db6657f4@test + + + + + + + + + + 6Kvu0nc3EzKKbwJs7XGZfCnQlt971OVi1cm6T6HyBTs= + + + eb:Messaging + + + + + + + + wCtCO88xutufHEOdOHmD3746QjkclPHGGfMkPfPECCI= + + + + + + + + + QnS8vRed/EehxjndcqLwnZHPIZ3aTxMmZbuNdrpG3BM= + + + + + + diff --git a/oxalis-ng-extension/oxalis-ng-as4/src/test/resources/ebms/nrr-signal-message-invalid-schema.xml b/oxalis-ng-extension/oxalis-ng-as4/src/test/resources/ebms/nrr-signal-message-invalid-schema.xml new file mode 100644 index 00000000..7b35d02f --- /dev/null +++ b/oxalis-ng-extension/oxalis-ng-as4/src/test/resources/ebms/nrr-signal-message-invalid-schema.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + 6Kvu0nc3EzKKbwJs7XGZfCnQlt971OVi1cm6T6HyBTs= + + + + + + + + + wCtCO88xutufHEOdOHmD3746QjkclPHGGfMkPfPECCI= + + + + + + + + + QnS8vRed/EehxjndcqLwnZHPIZ3aTxMmZbuNdrpG3BM= + + + + + + diff --git a/oxalis-ng-extension/oxalis-ng-as4/src/test/resources/ebms/nrr-signal-message.xml b/oxalis-ng-extension/oxalis-ng-as4/src/test/resources/ebms/nrr-signal-message.xml new file mode 100644 index 00000000..a455cd38 --- /dev/null +++ b/oxalis-ng-extension/oxalis-ng-as4/src/test/resources/ebms/nrr-signal-message.xml @@ -0,0 +1,45 @@ + + + + 2026-02-06T12:00:28.426+01:00 + d3a83fa2-955e-4f12-89b5-d80d614c07f0@test + f2ee7d49-c8a8-4b78-98e3-49d7db6657f4@test + + + + + + + + + + 6Kvu0nc3EzKKbwJs7XGZfCnQlt971OVi1cm6T6HyBTs= + + + + + + + + + wCtCO88xutufHEOdOHmD3746QjkclPHGGfMkPfPECCI= + + + + + + + + + QnS8vRed/EehxjndcqLwnZHPIZ3aTxMmZbuNdrpG3BM= + + + + + + diff --git a/oxalis-ng-extension/oxalis-ng-as4/src/test/resources/ebms/user-message-invalid.xml b/oxalis-ng-extension/oxalis-ng-as4/src/test/resources/ebms/user-message-invalid.xml new file mode 100644 index 00000000..68933471 --- /dev/null +++ b/oxalis-ng-extension/oxalis-ng-as4/src/test/resources/ebms/user-message-invalid.xml @@ -0,0 +1,35 @@ + + + + 2026-02-06T12:00:27.937+01:00 + f2ee7d49-c8a8-4b78-98e3-49d7db6657f4@test + + + + + + + urn:fdc:peppol.eu:2017:agreements:tia:ap_provider + urn:www.cenbii.eu:profile:bii04:ver1.0 + + busdox-docid-qns::urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:www.cenbii.eu:transaction:biicoretrdm010:ver1.0:#urn:www.peppol.eu:bis:peppol4a:ver1.0::2.0 + + + + + 0007:5567125082 + 0007:4455454480 + + + + + application/gzip + application/xml + + + + + \ No newline at end of file diff --git a/oxalis-ng-extension/oxalis-ng-as4/src/test/resources/ebms/user-message.xml b/oxalis-ng-extension/oxalis-ng-as4/src/test/resources/ebms/user-message.xml new file mode 100644 index 00000000..a2679978 --- /dev/null +++ b/oxalis-ng-extension/oxalis-ng-as4/src/test/resources/ebms/user-message.xml @@ -0,0 +1,41 @@ + + + + 2026-02-06T12:00:27.937+01:00 + f2ee7d49-c8a8-4b78-98e3-49d7db6657f4@workstation-main + + + + ASAB44444 + http://docs.oasis-open.org/ebxml-msg/ebms/v3.0/ns/core/200704/initiator + + + ASAB44444 + http://docs.oasis-open.org/ebxml-msg/ebms/v3.0/ns/core/200704/responder + + + + urn:fdc:peppol.eu:2017:agreements:tia:ap_provider + urn:www.cenbii.eu:profile:bii04:ver1.0 + + busdox-docid-qns::urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:www.cenbii.eu:transaction:biicoretrdm010:ver1.0:#urn:www.peppol.eu:bis:peppol4a:ver1.0::2.0 + + 45a62685-bd67-4d22-9e82-6b3e5126e75e@workstation-main + + + 0007:5567125082 + 0007:4455454480 + + + + + application/gzip + application/xml + + + + + \ No newline at end of file diff --git a/oxalis-ng-extension/oxalis-ng-as4/src/test/resources/sample_response.xml b/oxalis-ng-extension/oxalis-ng-as4/src/test/resources/sample_response.xml new file mode 100644 index 00000000..19b6d42d --- /dev/null +++ b/oxalis-ng-extension/oxalis-ng-as4/src/test/resources/sample_response.xml @@ -0,0 +1,99 @@ + + + + + + 2026-01-30T06:51:48.560+01:00 + 80512224-9eb8-463c-af0b-7e368bb7183a@workstation-main + 0afe05c2-8ded-4e1a-87e8-e9a8e6b6646c@workstation-main + + + + + + + + + + hSPXipsZ6qW+9/giVbcxx9iSOo0ChsYSKUQqa+yClHY= + + + + + + + + + tR8tUobKXHdS1TUMd3LkoEOfxpb4ulevUYDfnDvXDRQ= + + + + + + + + + 81jBwoe6fXqzM4Kx2izpiE753a5o8gwK/9x0PRS7ntM= + + + + + + + + + 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 + + + + + + + + + + + + + 42GgHCrJg9EijwtVB/UbSjVAD39fR6e2mUbzFLB9rzM= + + + + + + + kJ10TR07VXD0+ERCRrrWc9Iuaq4jiT/KAR2eqADiGNI= + + + + u7kWbZNUVx4qv4W7C8P9IpfAlVuKnPlel/eo1zzpJlZuBiMtqycPcDUgw8d/UlYrfCgRt6pbdwBJUbFOm2+0N1ynRb1jpCQhbmNA2grr2U8DqO1ueT+sxNhes4bYMaYpvuF6uKC9X3UhTo9jylTdiJYufStJqqmP90BXJgPKx18+PnWRFTbsXHOC2miMyPKbRC6NZCLVRa1WzxYCzSbv1VMkcM1vZcX/VpW1l9Ew/gCr7tf56IQ3jSdyiLiUF9ERfVD6zvBktgJnNxncC6fpOrEHrYLFbdz6Ogua74ZUwRSoxLIXZ0FIiBrzCSW+HMCjEltRsTHeGNa8087PdBEpQQ== + + + + + + + + + + + \ No newline at end of file diff --git a/oxalis-ng-outbound/src/main/resources/reference.conf b/oxalis-ng-outbound/src/main/resources/reference.conf index 7b544472..2fb887c4 100644 --- a/oxalis-ng-outbound/src/main/resources/reference.conf +++ b/oxalis-ng-outbound/src/main/resources/reference.conf @@ -1,4 +1,6 @@ oxalis.module.outbound.lookup.class = network.oxalis.ng.outbound.lookup.LookupModule oxalis.module.outbound.transmission.class = network.oxalis.ng.outbound.transmission.TransmissionModule -mode.default.oxalis.lookup.service = cached \ No newline at end of file +oxalis.as4.receipt.validation = logging // options: strict, logging, none + +mode.default.oxalis.lookup.service = cached