This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch backport/28274-to-camel-4.18.x in repository https://gitbox.apache.org/repos/asf/camel.git
commit 72e60f36dc5c49be5ffe7ff3cfe8802b0077467a Author: Guillaume Nodet <[email protected]> AuthorDate: Fri Oct 23 11:06:20 2026 +0000 [backport camel-4.18.x] CAMEL-25918: camel-as2 - consumers sharing a port use their own asynchronous MDN settings --- .../org/apache/camel/catalog/components/as2.json | 8 +- .../component/as2/api/AS2ServerConnection.java | 92 ++++++++--- .../AS2ConsumerConfigurationMdnSettingsTest.java | 91 +++++++++++ .../org/apache/camel/component/as2/as2.json | 8 +- .../src/main/docs/as2-component.adoc | 53 +++++++ .../camel/component/as2/AS2Configuration.java | 13 +- .../apache/camel/component/as2/AS2Consumer.java | 28 ++-- .../AS2ServerTwoConsumerAsyncMdnSettingsTest.java | 170 +++++++++++++++++++++ .../endpoint/dsl/AS2EndpointBuilderFactory.java | 42 +++-- 9 files changed, 452 insertions(+), 53 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/as2.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/as2.json index ffc9649e3ce4..fbed9eddfe02 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/as2.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/as2.json @@ -72,9 +72,9 @@ "encryptingAlgorithm": { "index": 36, "kind": "parameter", "displayName": "Encrypting Algorithm", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "org.apache.camel.component.as2.api.AS2EncryptionAlgorithm", "enum": [ "AES128_CBC", "AES192_CBC", "AES256_CBC", "AES128_CCM", "AES192_CCM", "AES256_CCM", "AES128_GCM", "AES192_GCM", "AES256_GCM", "CAMELLIA128_CBC", "CAMELLIA192_CBC", "CAMELLIA256_CBC", "CAST5_CBC", "DES_CBC", "DES_EDE3_CBC", "GOST28 [...] "encryptingCertificateChain": { "index": 37, "kind": "parameter", "displayName": "Encrypting Certificate Chain", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "java.security.cert.Certificate[]", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The chain of certificates used to encrypt EDI message." }, "hostnameVerifier": { "index": 38, "kind": "parameter", "displayName": "Hostname Verifier", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "Set hostname verifier for SSL session." }, - "mdnAccessToken": { "index": 39, "kind": "parameter", "displayName": "Mdn Access Token", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The access token that is used by the server when it sends an async MDN." }, - "mdnPassword": { "index": 40, "kind": "parameter", "displayName": "Mdn Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The password that is used by the server for basic authentication when it sends an async MDN." }, - "mdnUserName": { "index": 41, "kind": "parameter", "displayName": "Mdn User Name", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The user-name that is used by the server for basic authentication when it sends an async MDN. If options for [...] + "mdnAccessToken": { "index": 39, "kind": "parameter", "displayName": "Mdn Access Token", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The access token that is used by the server when it sends an async MDN. The option applies to the consu [...] + "mdnPassword": { "index": 40, "kind": "parameter", "displayName": "Mdn Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The password that is used by the server for basic authentication when it sends an async MDN. The option applie [...] + "mdnUserName": { "index": 41, "kind": "parameter", "displayName": "Mdn User Name", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The user-name that is used by the server for basic authentication when it sends an async MDN. If options for [...] "password": { "index": 42, "kind": "parameter", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The password that is used by the client for basic authentication." }, "signedReceiptMicAlgorithms": { "index": 43, "kind": "parameter", "displayName": "Signed Receipt Mic Algorithms", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The list of algorithms, in order of preference, requested to generate a messa [...] "signingAlgorithm": { "index": 44, "kind": "parameter", "displayName": "Signing Algorithm", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "org.apache.camel.component.as2.api.AS2SignatureAlgorithm", "enum": [ "SHA3_224WITHRSA", "SHA3_256WITHRSA", "SHA3_384withRSA", "SHA3_512WITHRSA", "MD5WITHRSA", "SHA1WITHRSA", "MD2WITHRSA", "SHA224WITHRSA", "SHA256WITHRSA", "SHA384WITHRSA", "SHA512WITHRSA", "RIPEMD128WITHRSA", "RIPEMD160WITHRSA", "RIPEMD256 [...] @@ -82,7 +82,7 @@ "signingPrivateKey": { "index": 46, "kind": "parameter", "displayName": "Signing Private Key", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "java.security.PrivateKey", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The key used to sign the EDI message." }, "sslContext": { "index": 47, "kind": "parameter", "displayName": "Ssl Context", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "javax.net.ssl.SSLContext", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "Set SSL context for connection to remote server." }, "userName": { "index": 48, "kind": "parameter", "displayName": "User Name", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The user-name that is used by the client for basic authentication. If options for basic authentication and bearer au [...] - "validateSigningCertificateChain": { "index": 49, "kind": "parameter", "displayName": "Validate Signing Certificate Chain", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "java.security.cert.Certificate[]", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "Certificates to validate the message's signature ag [...] + "validateSigningCertificateChain": { "index": 49, "kind": "parameter", "displayName": "Validate Signing Certificate Chain", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "java.security.cert.Certificate[]", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "Certificates to validate the message's signature ag [...] }, "apis": { "client": { "consumerOnly": false, "producerOnly": true, "description": "Sends EDI Messages over HTTP", "methods": { "send": { "description": "Send ediMessage to trading partner", "signatures": [ "org.apache.hc.core5.http.protocol.HttpCoreContext send(Object ediMessage, String requestUri, String subject, String from, String as2From, String as2To, org.apache.camel.component.as2.api.AS2MessageStructure as2MessageStructure, String ediMessageContentType, String ediMessageCharset, String [...] diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ServerConnection.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ServerConnection.java index 895cf21af273..2c20b45119ed 100644 --- a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ServerConnection.java +++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ServerConnection.java @@ -87,10 +87,7 @@ public class AS2ServerConnection { private final String as2Version; private final String originServer; private final String serverFqdn; - private final String userName; - private final String password; - private final String accessToken; - private final String asyncMdnAllowedHosts; + /** * Stores the configuration for each consumer endpoint path (e.g., "/consumerA"). Uses LinkedHashMap to preserve @@ -127,8 +124,10 @@ public class AS2ServerConnection { private static final ThreadLocal<ThreadLocalConfigWrapper> CURRENT_CONSUMER_CONFIG = new ThreadLocal<>(); /** - * Configuration data holding all necessary security material (signing keys/certs and decryption keys/certs) for a - * single AS2 consumer endpoint. This immutable object is looked up per request URI. + * Configuration data holding all necessary security material (signing keys/certs and decryption keys/certs) and the + * asynchronous MDN settings (the credentials attached to an asynchronous MDN and the hosts it may be delivered to) + * for a single AS2 consumer endpoint. This immutable object is looked up per request URI, so the consumers sharing + * a server connection each use their own values. */ public static class AS2ConsumerConfiguration { private final Certificate[] signingCertificateChain; @@ -136,18 +135,49 @@ public class AS2ServerConnection { private final PrivateKey decryptingPrivateKey; private final Certificate[] validateSigningCertificateChain; private final AS2SignatureAlgorithm signingAlgorithm; - + private final String mdnUserName; + private final String mdnPassword; + private final String mdnAccessToken; + private final String asyncMdnAllowedHosts; + + /** + * Creates a configuration without asynchronous MDN settings: an asynchronous MDN for a request matched by it is + * delivered without credentials, to the delivery address the sender named. + */ public AS2ConsumerConfiguration( AS2SignatureAlgorithm signingAlgorithm, Certificate[] signingCertificateChain, PrivateKey signingPrivateKey, PrivateKey decryptingPrivateKey, Certificate[] validateSigningCertificateChain) { + this(signingAlgorithm, signingCertificateChain, signingPrivateKey, decryptingPrivateKey, + validateSigningCertificateChain, null, null, null, null); + } + + /** + * Creates a configuration with its own asynchronous MDN settings: an asynchronous MDN for a request matched by + * it is delivered with these credentials (a user name and password, or an access token), and only to a delivery + * address in {@code asyncMdnAllowedHosts} when that list is set. + */ + public AS2ConsumerConfiguration( + AS2SignatureAlgorithm signingAlgorithm, + Certificate[] signingCertificateChain, + PrivateKey signingPrivateKey, + PrivateKey decryptingPrivateKey, + Certificate[] validateSigningCertificateChain, + String mdnUserName, + String mdnPassword, + String mdnAccessToken, + String asyncMdnAllowedHosts) { this.signingAlgorithm = signingAlgorithm; this.signingCertificateChain = signingCertificateChain; this.signingPrivateKey = signingPrivateKey; this.decryptingPrivateKey = decryptingPrivateKey; this.validateSigningCertificateChain = validateSigningCertificateChain; + this.mdnUserName = mdnUserName; + this.mdnPassword = mdnPassword; + this.mdnAccessToken = mdnAccessToken; + this.asyncMdnAllowedHosts = asyncMdnAllowedHosts; } // Getters @@ -170,6 +200,22 @@ public class AS2ServerConnection { public PrivateKey getDecryptingPrivateKey() { return decryptingPrivateKey; } + + public String getMdnUserName() { + return mdnUserName; + } + + public String getMdnPassword() { + return mdnPassword; + } + + public String getMdnAccessToken() { + return mdnAccessToken; + } + + public String getAsyncMdnAllowedHosts() { + return asyncMdnAllowedHosts; + } } /** @@ -495,10 +541,10 @@ public class AS2ServerConnection { AS2ServerConnection.this.serverFqdn, config.getSigningCertificateChain(), config.getSigningPrivateKey(), - AS2ServerConnection.this.userName, - AS2ServerConnection.this.password, - AS2ServerConnection.this.accessToken, - AS2ServerConnection.this.asyncMdnAllowedHosts); + config.getMdnUserName(), + config.getMdnPassword(), + config.getMdnAccessToken(), + config.getAsyncMdnAllowedHosts()); HttpRequest request = coreContext.getAttribute(HttpCoreContext.HTTP_REQUEST, HttpRequest.class); AS2SignedDataGenerator gen = ResponseMDN.createSigningGenerator( @@ -579,24 +625,27 @@ public class AS2ServerConnection { String accessToken, String asyncMdnAllowedHosts) throws IOException { - this.asyncMdnAllowedHosts = asyncMdnAllowedHosts; + this.as2Version = ObjectHelper.notNull(as2Version, "as2Version"); this.originServer = ObjectHelper.notNull(originServer, "userAgent"); this.serverFqdn = ObjectHelper.notNull(serverFqdn, "serverFqdn"); final Integer parserServerPortNumber = ObjectHelper.notNull(serverPortNumber, "serverPortNumber"); - this.userName = userName; - this.password = password; - this.accessToken = accessToken; // Create and register a default consumer configuration for the root path ('/'). - // This ensures that all incoming requests have a fallback configuration for decryption - // and MDN signing, even if they don't match a specific Camel route path. + // This ensures that all incoming requests have a fallback configuration for decryption, + // MDN signing and asynchronous MDN delivery, even if they don't match a specific Camel route path. + // The MDN settings given to this constructor belong to that fallback only: a consumer registers + // its own configuration, with its own MDN settings, for its own request URI pattern. AS2ServerConnection.AS2ConsumerConfiguration consumerConfig = new AS2ServerConnection.AS2ConsumerConfiguration( signingAlgorithm, signingCertificateChain, signingPrivateKey, decryptingPrivateKey, - validateSigningCertificateChain); + validateSigningCertificateChain, + userName, + password, + accessToken, + asyncMdnAllowedHosts); registerConsumerConfiguration("/", consumerConfig); listenerService = new RequestListenerService( @@ -628,6 +677,13 @@ public class AS2ServerConnection { .orElse(null); } + /** + * Registers the configuration used for the requests matching a consumer's request URI pattern: its signing and + * decrypting material and its asynchronous MDN settings. Each consumer registers its own configuration for its own + * pattern, also when the pattern is the root path ({@code /}) for which the constructor registered a fallback: the + * fallback and the configurations of the other consumers sharing this connection are not its own. Registering a + * pattern again replaces the configuration registered for it. + */ public void registerConsumerConfiguration(String path, AS2ConsumerConfiguration config) { consumerConfigurations.put(path, config); } diff --git a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2ConsumerConfigurationMdnSettingsTest.java b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2ConsumerConfigurationMdnSettingsTest.java new file mode 100644 index 000000000000..1a297f6a10b3 --- /dev/null +++ b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2ConsumerConfigurationMdnSettingsTest.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.as2.api; + +import org.apache.camel.component.as2.api.AS2ServerConnection.AS2ConsumerConfiguration; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * The asynchronous MDN settings (the credentials attached to an asynchronous MDN and the hosts it may be delivered to) + * are part of the per-path {@link AS2ConsumerConfiguration}, so the consumers sharing a server connection each keep + * their own. The fallback configuration of the root path carries the values given to the connection's constructor. + */ +class AS2ConsumerConfigurationMdnSettingsTest { + + @Test + void configurationCarriesTheMdnSettings() { + AS2ConsumerConfiguration config = new AS2ConsumerConfiguration( + AS2SignatureAlgorithm.SHA256WITHRSA, null, null, null, null, + "user", "password", "token", "receipts.example.com"); + + assertEquals(AS2SignatureAlgorithm.SHA256WITHRSA, config.getSigningAlgorithm()); + assertEquals("user", config.getMdnUserName()); + assertEquals("password", config.getMdnPassword()); + assertEquals("token", config.getMdnAccessToken()); + assertEquals("receipts.example.com", config.getAsyncMdnAllowedHosts()); + } + + @Test + void configurationWithoutMdnSettingsHasNone() { + AS2ConsumerConfiguration config = new AS2ConsumerConfiguration( + AS2SignatureAlgorithm.SHA256WITHRSA, null, null, null, null); + + assertNull(config.getMdnUserName()); + assertNull(config.getMdnPassword()); + assertNull(config.getMdnAccessToken()); + assertNull(config.getAsyncMdnAllowedHosts()); + } + + @Test + void rootPathFallbackCarriesTheConnectionSettings() throws Exception { + AS2ServerConnection connection = new AS2ServerConnection( + "1.1", "AS2ConsumerConfigurationMdnSettingsTest Server", "server.example.com", 0, + AS2SignatureAlgorithm.SHA256WITHRSA, null, null, null, "TBD", null, null, + "user", "password", "token", "receipts.example.com"); + try { + AS2ConsumerConfiguration fallback = connection.getConfigurationForPath("/").orElseThrow(); + assertEquals("user", fallback.getMdnUserName()); + assertEquals("password", fallback.getMdnPassword()); + assertEquals("token", fallback.getMdnAccessToken()); + assertEquals("receipts.example.com", fallback.getAsyncMdnAllowedHosts()); + + // a consumer registered for another path keeps its own settings, not the fallback's + connection.registerConsumerConfiguration("/partner", new AS2ConsumerConfiguration( + AS2SignatureAlgorithm.SHA256WITHRSA, null, null, null, null, + null, null, "partner-token", "partner.example.com")); + AS2ConsumerConfiguration partner = connection.getConfigurationForPath("/partner").orElseThrow(); + assertNull(partner.getMdnUserName()); + assertNull(partner.getMdnPassword()); + assertEquals("partner-token", partner.getMdnAccessToken()); + assertEquals("partner.example.com", partner.getAsyncMdnAllowedHosts()); + assertEquals("user", connection.getConfigurationForPath("/").orElseThrow().getMdnUserName()); + + // registering the root path again replaces the fallback + connection.registerConsumerConfiguration("/", new AS2ConsumerConfiguration( + AS2SignatureAlgorithm.SHA256WITHRSA, null, null, null, null)); + AS2ConsumerConfiguration replaced = connection.getConfigurationForPath("/").orElseThrow(); + assertNull(replaced.getMdnUserName()); + assertNull(replaced.getMdnAccessToken()); + assertNull(replaced.getAsyncMdnAllowedHosts()); + } finally { + connection.close(); + } + } +} diff --git a/components/camel-as2/camel-as2-component/src/generated/resources/META-INF/org/apache/camel/component/as2/as2.json b/components/camel-as2/camel-as2-component/src/generated/resources/META-INF/org/apache/camel/component/as2/as2.json index ffc9649e3ce4..fbed9eddfe02 100644 --- a/components/camel-as2/camel-as2-component/src/generated/resources/META-INF/org/apache/camel/component/as2/as2.json +++ b/components/camel-as2/camel-as2-component/src/generated/resources/META-INF/org/apache/camel/component/as2/as2.json @@ -72,9 +72,9 @@ "encryptingAlgorithm": { "index": 36, "kind": "parameter", "displayName": "Encrypting Algorithm", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "org.apache.camel.component.as2.api.AS2EncryptionAlgorithm", "enum": [ "AES128_CBC", "AES192_CBC", "AES256_CBC", "AES128_CCM", "AES192_CCM", "AES256_CCM", "AES128_GCM", "AES192_GCM", "AES256_GCM", "CAMELLIA128_CBC", "CAMELLIA192_CBC", "CAMELLIA256_CBC", "CAST5_CBC", "DES_CBC", "DES_EDE3_CBC", "GOST28 [...] "encryptingCertificateChain": { "index": 37, "kind": "parameter", "displayName": "Encrypting Certificate Chain", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "java.security.cert.Certificate[]", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The chain of certificates used to encrypt EDI message." }, "hostnameVerifier": { "index": 38, "kind": "parameter", "displayName": "Hostname Verifier", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "Set hostname verifier for SSL session." }, - "mdnAccessToken": { "index": 39, "kind": "parameter", "displayName": "Mdn Access Token", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The access token that is used by the server when it sends an async MDN." }, - "mdnPassword": { "index": 40, "kind": "parameter", "displayName": "Mdn Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The password that is used by the server for basic authentication when it sends an async MDN." }, - "mdnUserName": { "index": 41, "kind": "parameter", "displayName": "Mdn User Name", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The user-name that is used by the server for basic authentication when it sends an async MDN. If options for [...] + "mdnAccessToken": { "index": 39, "kind": "parameter", "displayName": "Mdn Access Token", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The access token that is used by the server when it sends an async MDN. The option applies to the consu [...] + "mdnPassword": { "index": 40, "kind": "parameter", "displayName": "Mdn Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The password that is used by the server for basic authentication when it sends an async MDN. The option applie [...] + "mdnUserName": { "index": 41, "kind": "parameter", "displayName": "Mdn User Name", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The user-name that is used by the server for basic authentication when it sends an async MDN. If options for [...] "password": { "index": 42, "kind": "parameter", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The password that is used by the client for basic authentication." }, "signedReceiptMicAlgorithms": { "index": 43, "kind": "parameter", "displayName": "Signed Receipt Mic Algorithms", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The list of algorithms, in order of preference, requested to generate a messa [...] "signingAlgorithm": { "index": 44, "kind": "parameter", "displayName": "Signing Algorithm", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "org.apache.camel.component.as2.api.AS2SignatureAlgorithm", "enum": [ "SHA3_224WITHRSA", "SHA3_256WITHRSA", "SHA3_384withRSA", "SHA3_512WITHRSA", "MD5WITHRSA", "SHA1WITHRSA", "MD2WITHRSA", "SHA224WITHRSA", "SHA256WITHRSA", "SHA384WITHRSA", "SHA512WITHRSA", "RIPEMD128WITHRSA", "RIPEMD160WITHRSA", "RIPEMD256 [...] @@ -82,7 +82,7 @@ "signingPrivateKey": { "index": 46, "kind": "parameter", "displayName": "Signing Private Key", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "java.security.PrivateKey", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The key used to sign the EDI message." }, "sslContext": { "index": 47, "kind": "parameter", "displayName": "Ssl Context", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "javax.net.ssl.SSLContext", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "Set SSL context for connection to remote server." }, "userName": { "index": 48, "kind": "parameter", "displayName": "User Name", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The user-name that is used by the client for basic authentication. If options for basic authentication and bearer au [...] - "validateSigningCertificateChain": { "index": 49, "kind": "parameter", "displayName": "Validate Signing Certificate Chain", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "java.security.cert.Certificate[]", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "Certificates to validate the message's signature ag [...] + "validateSigningCertificateChain": { "index": 49, "kind": "parameter", "displayName": "Validate Signing Certificate Chain", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "java.security.cert.Certificate[]", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "Certificates to validate the message's signature ag [...] }, "apis": { "client": { "consumerOnly": false, "producerOnly": true, "description": "Sends EDI Messages over HTTP", "methods": { "send": { "description": "Send ediMessage to trading partner", "signatures": [ "org.apache.hc.core5.http.protocol.HttpCoreContext send(Object ediMessage, String requestUri, String subject, String from, String as2From, String as2To, org.apache.camel.component.as2.api.AS2MessageStructure as2MessageStructure, String ediMessageContentType, String ediMessageCharset, String [...] diff --git a/components/camel-as2/camel-as2-component/src/main/docs/as2-component.adoc b/components/camel-as2/camel-as2-component/src/main/docs/as2-component.adoc index 59fb7a7510bc..5f920de19ae5 100644 --- a/components/camel-as2/camel-as2-component/src/main/docs/as2-component.adoc +++ b/components/camel-as2/camel-as2-component/src/main/docs/as2-component.adoc @@ -50,3 +50,56 @@ include::partial$component-endpoint-headers.adoc[] include::spring-boot:partial$starter.adoc[] + +== Usage + +An `as2://server/listen` consumer receives AS2 messages on `serverPortNumber`, for the request URIs that match +`requestUriPattern`. An `as2://client/send` producer sends the message body (`inBody: ediMessage`) to +`targetHostname` and `targetPortNumber`. The following routes send a plain EDIFACT message from one to the other: + +[source,yaml] +---- +- route: + from: + uri: as2://server/listen + parameters: + serverPortNumber: 8888 + requestUriPattern: /receiver + steps: + - log: + message: "Received EDI message: ${body}" +- route: + from: + uri: timer:send + parameters: + repeatCount: 1 + steps: + - setBody: + expression: + constant: + expression: "UNB+UNOA:1+SENDER+RECEIVER+260101:1200+1'UNZ+1+1'" + - to: + uri: as2://client/send + parameters: + inBody: ediMessage + targetHostname: localhost + targetPortNumber: 8888 + requestUri: /receiver + as2From: sender + as2To: receiver + from: [email protected] + subject: Order + as2MessageStructure: PLAIN + ediMessageContentType: application/edifact +---- + +A `SIGNED`, `ENCRYPTED` or `SIGNED_ENCRYPTED` message structure also needs the keys and certificates of the signing +and encrypting options. + +Several `as2://server/listen` consumers can share a `serverPortNumber`, each with its own `requestUriPattern`. Each +consumer applies its own keys and certificates and its own asynchronous MDN settings (`mdnUserName`, `mdnPassword`, +`mdnAccessToken` and `asyncMdnAllowedHosts`) to the messages received on its own pattern. + +The settings of the shared server connection still come from the first endpoint created on the port: +`sslContextParameters` and `sslContext` (used for the TLS listener and for the https delivery of asynchronous MDNs), +`mdnMessageTemplate`, `server`, `serverFqdn` and `as2Version`. They are per port, not per consumer.) diff --git a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Configuration.java b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Configuration.java index e03d3d8a38f2..acf459b7feb3 100644 --- a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Configuration.java +++ b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Configuration.java @@ -628,7 +628,9 @@ public class AS2Configuration { /** * The user-name that is used by the server for basic authentication when it sends an async MDN. If options for - * basic authentication and bearer authentication are both set then basic authentication takes precedence. + * basic authentication and bearer authentication are both set then basic authentication takes precedence. The + * option applies to the consumer endpoint it is set on: consumers sharing a serverPortNumber each send their async + * MDNs with their own values. */ public void setMdnUserName(String mdnUserName) { this.mdnUserName = mdnUserName; @@ -639,7 +641,8 @@ public class AS2Configuration { } /** - * The password that is used by the server for basic authentication when it sends an async MDN. + * The password that is used by the server for basic authentication when it sends an async MDN. The option applies + * to the consumer endpoint it is set on, like mdnUserName. */ public void setMdnPassword(String mdnPassword) { this.mdnPassword = mdnPassword; @@ -650,7 +653,8 @@ public class AS2Configuration { } /** - * The access token that is used by the server when it sends an async MDN. + * The access token that is used by the server when it sends an async MDN. The option applies to the consumer + * endpoint it is set on, like mdnUserName. */ public void setMdnAccessToken(String accessToken) { this.mdnAccessToken = accessToken; @@ -667,6 +671,9 @@ public class AS2Configuration { * When set, an asynchronous MDN whose delivery address names a host outside the list is refused, and the configured * mdnUserName/mdnPassword/mdnAccessToken are attached only to a host on the list. When not set, the MDN is still * delivered to the sender-supplied address but no credentials are attached. + * + * The option applies to the consumer endpoint it is set on: consumers sharing a serverPortNumber each apply their + * own list. */ public void setAsyncMdnAllowedHosts(String asyncMdnAllowedHosts) { this.asyncMdnAllowedHosts = asyncMdnAllowedHosts; diff --git a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Consumer.java b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Consumer.java index 553b636cfc6f..8d8c43edd542 100644 --- a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Consumer.java +++ b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Consumer.java @@ -93,18 +93,22 @@ public class AS2Consumer extends AbstractApiConsumer<AS2ApiName, AS2Configuratio String uri = properties.computeIfAbsent("requestUriPattern", param -> "/").toString(); - // Check if the configuration for this specific URI path has already been registered - // (e.g., by the default "/" fallback or another consumer). - // If not, create and register it now using the endpoint's configured keys/certs. - if (as2ServerConnection.getConfigurationForPath(uri).isEmpty()) { - AS2ServerConnection.AS2ConsumerConfiguration consumerConfig = new AS2ServerConnection.AS2ConsumerConfiguration( - getEndpoint().getSigningAlgorithm(), - getEndpoint().getSigningCertificateChain(), - getEndpoint().getSigningPrivateKey(), - getEndpoint().getDecryptingPrivateKey(), - getEndpoint().getValidateSigningCertificateChain()); - as2ServerConnection.registerConsumerConfiguration(uri, consumerConfig); - } + // Register this consumer's own configuration for its URI pattern: the endpoint's keys/certs and its + // asynchronous MDN settings. The server connection is shared by every consumer on the port and was + // built from the first endpoint created on it, so the "/" fallback and the configurations of the + // other consumers are not this consumer's; a consumer whose pattern is "/" replaces the fallback. + AS2Configuration configuration = getEndpoint().getConfiguration(); + AS2ServerConnection.AS2ConsumerConfiguration consumerConfig = new AS2ServerConnection.AS2ConsumerConfiguration( + getEndpoint().getSigningAlgorithm(), + getEndpoint().getSigningCertificateChain(), + getEndpoint().getSigningPrivateKey(), + getEndpoint().getDecryptingPrivateKey(), + getEndpoint().getValidateSigningCertificateChain(), + configuration.getMdnUserName(), + configuration.getMdnPassword(), + configuration.getMdnAccessToken(), + configuration.getAsyncMdnAllowedHosts()); + as2ServerConnection.registerConsumerConfiguration(uri, consumerConfig); as2ServerConnection.listen(uri, this); } diff --git a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerTwoConsumerAsyncMdnSettingsTest.java b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerTwoConsumerAsyncMdnSettingsTest.java new file mode 100644 index 000000000000..2420baafa10c --- /dev/null +++ b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerTwoConsumerAsyncMdnSettingsTest.java @@ -0,0 +1,170 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.as2; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.as2.api.AS2MediaType; +import org.apache.camel.component.as2.api.AS2MessageStructure; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.AvailablePortFinder; +import org.apache.hc.core5.http.protocol.HttpCoreContext; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * Several {@code as2://server/listen} consumers share one {@code serverPortNumber}, each with its own + * {@code requestUriPattern}. Each of them delivers the asynchronous MDNs for the messages received on its own pattern + * with its own {@code mdnUserName}, {@code mdnPassword}, {@code mdnAccessToken} and {@code asyncMdnAllowedHosts}, not + * with the values of the consumer that created the shared server connection. This also holds for a consumer on the root + * pattern, for which the connection registers a fallback configuration from the endpoint that created it. + */ +class AS2ServerTwoConsumerAsyncMdnSettingsTest extends AS2ServerSecTestBase { + + private static final String FIRST_PATTERN = "/receiver-one"; + private static final String SECOND_PATTERN = "/receiver-two"; + private static final String THIRD_PATTERN = "/receiver-three"; + private static final String ROOT_PATTERN = "/"; + private static final String FIRST_USER_NAME = "first-user"; + private static final String FIRST_PASSWORD = "first-password"; + private static final String SECOND_ACCESS_TOKEN = "second-token"; + private static final String ROOT_ACCESS_TOKEN = "root-token"; + private static final String RECEIPT_PATH = "/receipts"; + + // one receipt endpoint per host name, so that each delivery address resolves to the endpoint bound to it + @RegisterExtension + AvailablePortFinder.Port localhostReceiptPort = AvailablePortFinder.find(); + @RegisterExtension + AvailablePortFinder.Port loopbackReceiptPort = AvailablePortFinder.find(); + + @Test + void asyncMdnForTheSecondConsumerUsesItsOwnSettings() throws Exception { + String authorization = authorizationOfTheReceiptFor(SECOND_PATTERN, loopbackReceiptAddress()); + + assertEquals("Bearer " + SECOND_ACCESS_TOKEN, authorization); + } + + @Test + void asyncMdnForTheSecondConsumerAppliesItsOwnAllowedHosts() throws Exception { + // localhost is on the first consumer's list only, so the second consumer's MDN is not delivered to it + MockEndpoint receipts = getMockEndpoint("mock:receipts"); + receipts.expectedMessageCount(0); + + HttpCoreContext context = sendTo(SECOND_PATTERN, localhostReceiptAddress()); + verifyOkResponse(context); + + receipts.assertIsSatisfied(2000); + } + + @Test + void asyncMdnForAConsumerWithoutSettingsCarriesNoAuthorization() throws Exception { + assertNull(authorizationOfTheReceiptFor(THIRD_PATTERN, localhostReceiptAddress())); + } + + @Test + void asyncMdnForTheConsumerOnTheRootPatternUsesItsOwnSettings() throws Exception { + String authorization = authorizationOfTheReceiptFor(ROOT_PATTERN, localhostReceiptAddress()); + + assertEquals("Bearer " + ROOT_ACCESS_TOKEN, authorization); + } + + @Test + void asyncMdnForTheFirstConsumerIsUnchanged() throws Exception { + String authorization = authorizationOfTheReceiptFor(FIRST_PATTERN, localhostReceiptAddress()); + + String encoded = Base64.getEncoder() + .encodeToString((FIRST_USER_NAME + ":" + FIRST_PASSWORD).getBytes(StandardCharsets.UTF_8)); + assertEquals("Basic " + encoded, authorization); + } + + @Test + void asyncMdnAfterTheFirstConsumerStopsUsesTheRemainingConsumerSettings() throws Exception { + context.getRouteController().stopRoute("first"); + + String authorization = authorizationOfTheReceiptFor(SECOND_PATTERN, loopbackReceiptAddress()); + + assertEquals("Bearer " + SECOND_ACCESS_TOKEN, authorization); + } + + private String authorizationOfTheReceiptFor(String requestUri, String receiptDeliveryAddress) throws Exception { + MockEndpoint receipts = getMockEndpoint("mock:receipts"); + receipts.expectedMessageCount(1); + + HttpCoreContext context = sendTo(requestUri, receiptDeliveryAddress); + verifyOkResponse(context); + + receipts.assertIsSatisfied(); + return receipts.getExchanges().get(0).getIn().getHeader("Authorization", String.class); + } + + private HttpCoreContext sendTo(String requestUri, String receiptDeliveryAddress) throws Exception { + return clientConnection().send(EDI_MESSAGE, requestUri, SUBJECT, FROM, AS2_NAME, AS2_NAME, + AS2MessageStructure.PLAIN, AS2MediaType.APPLICATION_EDIFACT, null, null, + null, null, null, null, + DISPOSITION_NOTIFICATION_TO, null, null, null, + null, receiptDeliveryAddress, null, null, null); + } + + private String localhostReceiptAddress() { + return "http://localhost:" + localhostReceiptPort.getPort() + RECEIPT_PATH; + } + + private String loopbackReceiptAddress() { + return "http://127.0.0.1:" + loopbackReceiptPort.getPort() + RECEIPT_PATH; + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + public void configure() { + // the first consumer creates the server connection shared by every consumer on the port + from("as2://server/listen?requestUriPattern=" + FIRST_PATTERN + + "&mdnUserName=" + FIRST_USER_NAME + "&mdnPassword=" + FIRST_PASSWORD + + "&asyncMdnAllowedHosts=localhost,127.0.0.1") + .routeId("first") + .to("mock:first"); + + from("as2://server/listen?requestUriPattern=" + SECOND_PATTERN + + "&mdnAccessToken=" + SECOND_ACCESS_TOKEN + "&asyncMdnAllowedHosts=127.0.0.1") + .routeId("second") + .to("mock:second"); + + // no asynchronous MDN settings at all + from("as2://server/listen?requestUriPattern=" + THIRD_PATTERN) + .routeId("third") + .to("mock:third"); + + // the root pattern, on the connection another consumer created + from("as2://server/listen?requestUriPattern=" + ROOT_PATTERN + + "&mdnAccessToken=" + ROOT_ACCESS_TOKEN + "&asyncMdnAllowedHosts=localhost") + .routeId("root") + .to("mock:root"); + + // where the asynchronous MDNs are delivered to + from("jetty:http://localhost:" + localhostReceiptPort.getPort() + RECEIPT_PATH) + .to("mock:receipts"); + from("jetty:http://127.0.0.1:" + loopbackReceiptPort.getPort() + RECEIPT_PATH) + .to("mock:receipts"); + } + }; + } +} diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AS2EndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AS2EndpointBuilderFactory.java index efd829f154eb..c9336a11f5e6 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AS2EndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AS2EndpointBuilderFactory.java @@ -630,7 +630,9 @@ public interface AS2EndpointBuilderFactory { * the list is refused, and the configured * mdnUserName/mdnPassword/mdnAccessToken are attached only to a host on * the list. When not set, the MDN is still delivered to the - * sender-supplied address but no credentials are attached. + * sender-supplied address but no credentials are attached. The option + * applies to the consumer endpoint it is set on: consumers sharing a + * serverPortNumber each apply their own list. * * The option is a: <code>java.lang.String</code> type. * @@ -762,7 +764,8 @@ public interface AS2EndpointBuilderFactory { } /** * The access token that is used by the server when it sends an async - * MDN. + * MDN. The option applies to the consumer endpoint it is set on, like + * mdnUserName. * * The option is a: <code>java.lang.String</code> type. * @@ -777,7 +780,8 @@ public interface AS2EndpointBuilderFactory { } /** * The password that is used by the server for basic authentication when - * it sends an async MDN. + * it sends an async MDN. The option applies to the consumer endpoint it + * is set on, like mdnUserName. * * The option is a: <code>java.lang.String</code> type. * @@ -794,7 +798,9 @@ public interface AS2EndpointBuilderFactory { * The user-name that is used by the server for basic authentication * when it sends an async MDN. If options for basic authentication and * bearer authentication are both set then basic authentication takes - * precedence. + * precedence. The option applies to the consumer endpoint it is set on: + * consumers sharing a serverPortNumber each send their async MDNs with + * their own values. * * The option is a: <code>java.lang.String</code> type. * @@ -1682,7 +1688,9 @@ public interface AS2EndpointBuilderFactory { * the list is refused, and the configured * mdnUserName/mdnPassword/mdnAccessToken are attached only to a host on * the list. When not set, the MDN is still delivered to the - * sender-supplied address but no credentials are attached. + * sender-supplied address but no credentials are attached. The option + * applies to the consumer endpoint it is set on: consumers sharing a + * serverPortNumber each apply their own list. * * The option is a: <code>java.lang.String</code> type. * @@ -1814,7 +1822,8 @@ public interface AS2EndpointBuilderFactory { } /** * The access token that is used by the server when it sends an async - * MDN. + * MDN. The option applies to the consumer endpoint it is set on, like + * mdnUserName. * * The option is a: <code>java.lang.String</code> type. * @@ -1829,7 +1838,8 @@ public interface AS2EndpointBuilderFactory { } /** * The password that is used by the server for basic authentication when - * it sends an async MDN. + * it sends an async MDN. The option applies to the consumer endpoint it + * is set on, like mdnUserName. * * The option is a: <code>java.lang.String</code> type. * @@ -1846,7 +1856,9 @@ public interface AS2EndpointBuilderFactory { * The user-name that is used by the server for basic authentication * when it sends an async MDN. If options for basic authentication and * bearer authentication are both set then basic authentication takes - * precedence. + * precedence. The option applies to the consumer endpoint it is set on: + * consumers sharing a serverPortNumber each send their async MDNs with + * their own values. * * The option is a: <code>java.lang.String</code> type. * @@ -2715,7 +2727,9 @@ public interface AS2EndpointBuilderFactory { * the list is refused, and the configured * mdnUserName/mdnPassword/mdnAccessToken are attached only to a host on * the list. When not set, the MDN is still delivered to the - * sender-supplied address but no credentials are attached. + * sender-supplied address but no credentials are attached. The option + * applies to the consumer endpoint it is set on: consumers sharing a + * serverPortNumber each apply their own list. * * The option is a: <code>java.lang.String</code> type. * @@ -2847,7 +2861,8 @@ public interface AS2EndpointBuilderFactory { } /** * The access token that is used by the server when it sends an async - * MDN. + * MDN. The option applies to the consumer endpoint it is set on, like + * mdnUserName. * * The option is a: <code>java.lang.String</code> type. * @@ -2862,7 +2877,8 @@ public interface AS2EndpointBuilderFactory { } /** * The password that is used by the server for basic authentication when - * it sends an async MDN. + * it sends an async MDN. The option applies to the consumer endpoint it + * is set on, like mdnUserName. * * The option is a: <code>java.lang.String</code> type. * @@ -2879,7 +2895,9 @@ public interface AS2EndpointBuilderFactory { * The user-name that is used by the server for basic authentication * when it sends an async MDN. If options for basic authentication and * bearer authentication are both set then basic authentication takes - * precedence. + * precedence. The option applies to the consumer endpoint it is set on: + * consumers sharing a serverPortNumber each send their async MDNs with + * their own values. * * The option is a: <code>java.lang.String</code> type. *
