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 b348e9105ce7b..989fb9a25f74f 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 @@ -68,14 +68,14 @@ "expectContinue": { "index": 32, "kind": "parameter", "displayName": "Expect Continue", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "Controls whether the Expect: 100-Continue header is included in outbound AS2 messages. When enabled, the client sends the headers first and waits for a 100 Continue response from the server before sending the message body. This can improve efficiency with compatible partners but may cause 3-second delays with servers that don't support the protocol. Default is false for backward compatibility." }, "lazyStartProducer": { "index": 33, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." }, "accessToken": { "index": 34, "kind": "parameter", "displayName": "Access Token", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The access token that is used by the client for bearer authentication." }, - "asyncMdnAllowedHosts": { "index": 35, "kind": "parameter", "displayName": "Async Mdn Allowed Hosts", "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": "Comma separated list of host names an asynchronous MDN may be delivered to. The delivery address is chosen by the sender of the AS2 message, so this is the list of hosts the operator vouches for. 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." }, + "asyncMdnAllowedHosts": { "index": 35, "kind": "parameter", "displayName": "Async Mdn Allowed Hosts", "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": "Comma separated list of host names an asynchronous MDN may be delivered to. The delivery address is chosen by the sender of the AS2 message, so this is the list of hosts the operator vouches for. 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." }, "decryptingPrivateKey": { "index": 36, "kind": "parameter", "displayName": "Decrypting 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 encrypt the EDI message." }, "encryptingAlgorithm": { "index": 37, "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", "GOST28147_GCFB", "IDEA_CBC", "RC2_CBC", "RC4", "SEED_CBC" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The algorithm used to encrypt EDI message." }, "encryptingCertificateChain": { "index": 38, "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": 39, "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": 40, "kind": "parameter", "displayName": "Mdn Access Token", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "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": 41, "kind": "parameter", "displayName": "Mdn Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "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": 42, "kind": "parameter", "displayName": "Mdn User Name", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "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 basic authentication and bearer authentication are both set then basic authentication takes precedence." }, + "mdnAccessToken": { "index": 40, "kind": "parameter", "displayName": "Mdn Access Token", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "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 consumer endpoint it is set on, like mdnUserName." }, + "mdnPassword": { "index": 41, "kind": "parameter", "displayName": "Mdn Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "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 applies to the consumer endpoint it is set on, like mdnUserName." }, + "mdnUserName": { "index": 42, "kind": "parameter", "displayName": "Mdn User Name", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "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 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." }, "password": { "index": 43, "kind": "parameter", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The password that is used by the client for basic authentication." }, "signatureVerificationRequired": { "index": 44, "kind": "parameter", "displayName": "Signature Verification Required", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "Whether to reject an inbound signed AS2 message that cannot be verified because no validateSigningCertificateChain is configured (server only). When false (default), such a message is delivered after logging a warning, preserving the previous behaviour. When true, the message is rejected instead of being delivered without verifying its signature. Has no effect when validateSigningCertificateChain is set (signatures are always validated then) or for unsigned messages." }, "signedReceiptMicAlgorithms": { "index": 45, "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 message integrity check (MIC) returned in message disposition notification (MDN). Multiple algorithms can be separated by comma." }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/as2-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/as2-component.adoc index c5aabf3e6aecd..8b3988308f017 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/as2-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/as2-component.adoc @@ -99,3 +99,11 @@ An `as2://server/listen` consumer receives AS2 messages on `serverPortNumber`, f 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-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 1270448dc5ee2..57459d9279b22 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 @@ -89,10 +89,6 @@ 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; private final SSLContext sslContext; /** @@ -131,8 +127,10 @@ private static class ThreadLocalConfigWrapper { = ContextValue.newThreadLocal("AS2ConsumerConfig"); /** - * 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; @@ -140,18 +138,49 @@ public static class AS2ConsumerConfiguration { 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 @@ -174,6 +203,22 @@ public PrivateKey getSigningPrivateKey() { 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; + } } /** @@ -549,16 +594,18 @@ private void handleAsynchronousMDN(HttpCoreContext coreContext) throws HttpExcep DispositionNotificationMultipartReportEntity multipartReportEntity = coreContext.getAttribute( AS2AsynchronousMDNManager.ASYNCHRONOUS_MDN, DispositionNotificationMultipartReportEntity.class); + // The MDN settings come from the matched path's configuration, like the signing material: the + // consumers sharing this connection each deliver their asynchronous MDNs with their own values. AS2AsynchronousMDNManager asynchronousMDNManager = new AS2AsynchronousMDNManager( AS2ServerConnection.this.as2Version, AS2ServerConnection.this.originServer, 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(), AS2ServerConnection.this.sslContext); HttpRequest request = coreContext.getRequest(); @@ -625,25 +672,27 @@ public AS2ServerConnection(String as2Version, String accessToken, String asyncMdnAllowedHosts) throws IOException { - this.asyncMdnAllowedHosts = asyncMdnAllowedHosts; this.sslContext = sslContext; 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( @@ -675,6 +724,13 @@ public PrivateKey getDecryptingPrivateKey() { .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 0000000000000..1a297f6a10b3a --- /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 b348e9105ce7b..989fb9a25f74f 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 @@ -68,14 +68,14 @@ "expectContinue": { "index": 32, "kind": "parameter", "displayName": "Expect Continue", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "Controls whether the Expect: 100-Continue header is included in outbound AS2 messages. When enabled, the client sends the headers first and waits for a 100 Continue response from the server before sending the message body. This can improve efficiency with compatible partners but may cause 3-second delays with servers that don't support the protocol. Default is false for backward compatibility." }, "lazyStartProducer": { "index": 33, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." }, "accessToken": { "index": 34, "kind": "parameter", "displayName": "Access Token", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The access token that is used by the client for bearer authentication." }, - "asyncMdnAllowedHosts": { "index": 35, "kind": "parameter", "displayName": "Async Mdn Allowed Hosts", "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": "Comma separated list of host names an asynchronous MDN may be delivered to. The delivery address is chosen by the sender of the AS2 message, so this is the list of hosts the operator vouches for. 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." }, + "asyncMdnAllowedHosts": { "index": 35, "kind": "parameter", "displayName": "Async Mdn Allowed Hosts", "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": "Comma separated list of host names an asynchronous MDN may be delivered to. The delivery address is chosen by the sender of the AS2 message, so this is the list of hosts the operator vouches for. 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." }, "decryptingPrivateKey": { "index": 36, "kind": "parameter", "displayName": "Decrypting 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 encrypt the EDI message." }, "encryptingAlgorithm": { "index": 37, "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", "GOST28147_GCFB", "IDEA_CBC", "RC2_CBC", "RC4", "SEED_CBC" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The algorithm used to encrypt EDI message." }, "encryptingCertificateChain": { "index": 38, "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": 39, "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": 40, "kind": "parameter", "displayName": "Mdn Access Token", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "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": 41, "kind": "parameter", "displayName": "Mdn Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "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": 42, "kind": "parameter", "displayName": "Mdn User Name", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "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 basic authentication and bearer authentication are both set then basic authentication takes precedence." }, + "mdnAccessToken": { "index": 40, "kind": "parameter", "displayName": "Mdn Access Token", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "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 consumer endpoint it is set on, like mdnUserName." }, + "mdnPassword": { "index": 41, "kind": "parameter", "displayName": "Mdn Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "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 applies to the consumer endpoint it is set on, like mdnUserName." }, + "mdnUserName": { "index": 42, "kind": "parameter", "displayName": "Mdn User Name", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "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 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." }, "password": { "index": 43, "kind": "parameter", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The password that is used by the client for basic authentication." }, "signatureVerificationRequired": { "index": 44, "kind": "parameter", "displayName": "Signature Verification Required", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "Whether to reject an inbound signed AS2 message that cannot be verified because no validateSigningCertificateChain is configured (server only). When false (default), such a message is delivered after logging a warning, preserving the previous behaviour. When true, the message is rejected instead of being delivered without verifying its signature. Has no effect when validateSigningCertificateChain is set (signatures are always validated then) or for unsigned messages." }, "signedReceiptMicAlgorithms": { "index": 45, "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 message integrity check (MIC) returned in message disposition notification (MDN). Multiple algorithms can be separated by comma." }, 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 c5aabf3e6aecd..8b3988308f017 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 @@ -99,3 +99,11 @@ An `as2://server/listen` consumer receives AS2 messages on `serverPortNumber`, f 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 cc283631a27cc..3bc6aec167045 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 @@ -661,7 +661,9 @@ public String getMdnUserName() { /** * 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; @@ -672,7 +674,8 @@ public String getMdnPassword() { } /** - * 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; @@ -683,7 +686,8 @@ public String getMdnAccessToken() { } /** - * 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; @@ -700,6 +704,9 @@ public String getAsyncMdnAllowedHosts() { * 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 9446fc6135e16..9a2e8854dba46 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 @@ protected void doStart() throws Exception { 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 0000000000000..2420baafa10c5 --- /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/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc index d22ffd4ca0b3f..cdb7147589808 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc @@ -2206,6 +2206,21 @@ With `strict=false`, unmarshalling no longer returns `null` or a partial result recoveries of the TOON specification apply, such as accepting an array whose length differs from the declared length; other malformed TOON fails with `IllegalArgumentException`, as in strict mode. +=== camel-as2 - consumers sharing a port use their own asynchronous MDN settings + +`as2://server/listen` consumers that share a `serverPortNumber` now deliver asynchronous MDNs with their own +`mdnUserName`, `mdnPassword`, `mdnAccessToken` and `asyncMdnAllowedHosts`, selected by the matched `requestUriPattern` +in the same way as the signing material. Previously the values of the first consumer configured on the port were used +for every consumer on it, and the values set on the other consumers were ignored. A consumer that relied on inheriting +them now has to set them itself. + +This applies to `asyncMdnAllowedHosts` as well. A consumer without its own list used to inherit the first consumer's +list, so asynchronous MDNs to other hosts were refused; it now delivers to the address the sender gives, without +credentials. A deployment that counted on the inherited list as a restriction has to set it on each consumer. + +A consumer whose `requestUriPattern` is `/` now also uses its own keys, certificates and asynchronous MDN settings when +another consumer created the shared server connection; before, it used the first consumer's. + === camel-ftp, camel-sftp, camel-ftps, camel-mina-sftp - sendNoop also applies to the consumer The `sendNoop` option now also applies to the check a consumer makes before each poll, not only to the pre-write check 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 bdcfdec593dfb..97e5a5dc260e4 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 @@ default AS2EndpointConsumerBuilder accessToken(String accessToken) { * 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: java.lang.String type. * @@ -762,7 +764,8 @@ default AS2EndpointConsumerBuilder hostnameVerifier(String hostnameVerifier) { } /** * 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: java.lang.String type. * @@ -777,7 +780,8 @@ default AS2EndpointConsumerBuilder mdnAccessToken(String mdnAccessToken) { } /** * 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: java.lang.String type. * @@ -794,7 +798,9 @@ default AS2EndpointConsumerBuilder mdnPassword(String mdnPassword) { * 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: java.lang.String type. * @@ -1766,7 +1772,9 @@ default AS2EndpointProducerBuilder accessToken(String accessToken) { * 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: java.lang.String type. * @@ -1898,7 +1906,8 @@ default AS2EndpointProducerBuilder hostnameVerifier(String hostnameVerifier) { } /** * 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: java.lang.String type. * @@ -1913,7 +1922,8 @@ default AS2EndpointProducerBuilder mdnAccessToken(String mdnAccessToken) { } /** * 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: java.lang.String type. * @@ -1930,7 +1940,9 @@ default AS2EndpointProducerBuilder mdnPassword(String mdnPassword) { * 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: java.lang.String type. * @@ -2843,7 +2855,9 @@ default AS2EndpointBuilder accessToken(String accessToken) { * 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: java.lang.String type. * @@ -2975,7 +2989,8 @@ default AS2EndpointBuilder hostnameVerifier(String hostnameVerifier) { } /** * 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: java.lang.String type. * @@ -2990,7 +3005,8 @@ default AS2EndpointBuilder mdnAccessToken(String mdnAccessToken) { } /** * 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: java.lang.String type. * @@ -3007,7 +3023,9 @@ default AS2EndpointBuilder mdnPassword(String mdnPassword) { * 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: java.lang.String type. *