This is an automated email from the ASF dual-hosted git repository.
oscerd pushed a commit to branch camel-4.22.x
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-4.22.x by this push:
new af7421ff5f74 [backport camel-4.22.x] CAMEL-24417: camel-as2 - validate
the asynchronous MDN delivery address and scope the MDN credentials (#25763)
af7421ff5f74 is described below
commit af7421ff5f7456e252dad14e3279c7809cdef208
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Aug 27 06:40:48 2026 +0200
[backport camel-4.22.x] CAMEL-24417: camel-as2 - validate the asynchronous
MDN delivery address and scope the MDN credentials (#25763)
CAMEL-24417: camel-as2 - validate the asynchronous MDN delivery address and
scope the MDN credentials
The delivery address for an asynchronous MDN comes from the
Receipt-Delivery-Option
header of the received AS2 message, so it is chosen by the sender. The
server passed it
straight to a socket and attached the configured MDN credentials to
whatever host it
named, with no scheme check.
A new asyncMdnAllowedHosts option names the hosts an asynchronous MDN may
be delivered
to. Credentials are attached only for a host on the list, and an address
naming a host
outside it is refused. With the option unset the MDN is still delivered, as
before, but
no credentials are attached.
Independently of the option the scheme must now be http. https is refused
rather than
attempted: this class delivers over a plain socket and has no TLS, so an
https address
wrote the request - including the Authorization header - in cleartext to
the TLS port.
IPv6 hosts are unbracketed before the allow-list match.
Public API stays source and binary compatible; the previous constructors
delegate. Real
TLS delivery via SSLContextParameters is tracked as CAMEL-24474.
Closes #25637
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
.../org/apache/camel/catalog/components/as2.json | 33 +++----
.../as2/api/AS2AsynchronousMDNManager.java | 84 ++++++++++++++++-
.../component/as2/api/AS2ServerConnection.java | 26 +++++-
...2AsynchronousMDNManagerDeliveryAddressTest.java | 102 +++++++++++++++++++++
...rverManagerEndpointConfigurationConfigurer.java | 7 ++
...ientManagerEndpointConfigurationConfigurer.java | 7 ++
.../component/as2/AS2ConfigurationConfigurer.java | 7 ++
.../camel/component/as2/AS2EndpointConfigurer.java | 7 ++
.../camel/component/as2/AS2EndpointUriFactory.java | 3 +-
...rverManagerEndpointConfigurationConfigurer.java | 7 ++
.../org/apache/camel/component/as2/as2.json | 33 +++----
.../camel/component/as2/AS2Configuration.java | 18 ++++
.../as2/internal/AS2ConnectionHelper.java | 3 +-
.../as2/AS2AsyncMdnBasicAuthHeaderTest.java | 5 +-
.../as2/AS2AsyncMdnTokenAuthHeaderTest.java | 5 +-
.../as2/AS2ServerBasicAuthHeaderTest.java | 3 +
.../as2/AS2ServerBearerAuthHeaderTest.java | 3 +
.../endpoint/dsl/AS2EndpointBuilderFactory.java | 63 +++++++++++++
18 files changed, 377 insertions(+), 39 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 bef8bcfaed86..7fe783876211 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,22 +68,23 @@
"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. W [...]
"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 produ [...]
"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." },
- "decryptingPrivateKey": { "index": 35, "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": 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, "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": 40, "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 [...]
- "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, "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 asyn [...]
- "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, "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": 43, "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 cann [...]
- "signedReceiptMicAlgorithms": { "index": 44, "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": 45, "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 [...]
- "signingCertificateChain": { "index": 46, "kind": "parameter",
"displayName": "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": "The chain of certificates used to sign EDI
message." },
- "signingPrivateKey": { "index": 47, "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": 48, "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": 49, "kind": "parameter", "displayName": "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 client for
basic authentication. If options for basic authen [...]
- "validateSigningCertificateChain": { "index": 50, "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 [...]
+ "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 [...]
+ "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",
"GOST28 [...]
+ "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 [...]
+ "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 asyn [...]
+ "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 cann [...]
+ "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 messa [...]
+ "signingAlgorithm": { "index": 46, "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 [...]
+ "signingCertificateChain": { "index": 47, "kind": "parameter",
"displayName": "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": "The chain of certificates used to sign EDI
message." },
+ "signingPrivateKey": { "index": 48, "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": 49, "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": 50, "kind": "parameter", "displayName": "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 client for
basic authentication. If options for basic authen [...]
+ "validateSigningCertificateChain": { "index": 51, "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/AS2AsynchronousMDNManager.java
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2AsynchronousMDNManager.java
index 184dcff6b7ac..8cf1c9a30dc6 100644
---
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2AsynchronousMDNManager.java
+++
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2AsynchronousMDNManager.java
@@ -21,6 +21,7 @@ import java.net.Socket;
import java.net.URI;
import java.security.PrivateKey;
import java.security.cert.Certificate;
+import java.util.Locale;
import org.apache.camel.component.as2.api.entity.MultipartMimeEntity;
import org.apache.camel.component.as2.api.protocol.RequestAsynchronousMDN;
@@ -46,9 +47,13 @@ import org.apache.hc.core5.http.protocol.RequestContent;
import org.apache.hc.core5.http.protocol.RequestDate;
import org.apache.hc.core5.http.protocol.RequestTargetHost;
import org.apache.hc.core5.http.protocol.RequestUserAgent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class AS2AsynchronousMDNManager {
+ private static final Logger LOG =
LoggerFactory.getLogger(AS2AsynchronousMDNManager.class);
+
//
// AS2 HTTP Context Attribute Keys
//
@@ -98,7 +103,15 @@ public class AS2AsynchronousMDNManager {
private String userName;
private String password;
private String accessToken;
+ private String allowedHosts;
+ /**
+ * @deprecated use
+ * {@link #AS2AsynchronousMDNManager(String, String, String,
Certificate[], PrivateKey, String, String, String, String)}
+ * which also takes the allowed delivery hosts. This
constructor delivers the MDN without attaching the
+ * configured credentials, because no allow-list is supplied.
+ */
+ @Deprecated
public AS2AsynchronousMDNManager(String as2Version,
String userAgent,
String senderFQDN,
@@ -107,6 +120,20 @@ public class AS2AsynchronousMDNManager {
String userName,
String password,
String accessToken) {
+ this(as2Version, userAgent, senderFQDN, signingCertificateChain,
signingPrivateKey, userName, password,
+ accessToken, null);
+ }
+
+ public AS2AsynchronousMDNManager(String as2Version,
+ String userAgent,
+ String senderFQDN,
+ Certificate[] signingCertificateChain,
+ PrivateKey signingPrivateKey,
+ String userName,
+ String password,
+ String accessToken,
+ String allowedHosts) {
+ this.allowedHosts = allowedHosts;
this.signingCertificateChain = signingCertificateChain;
this.signingPrivateKey = signingPrivateKey;
this.userName = userName;
@@ -130,7 +157,29 @@ public class AS2AsynchronousMDNManager {
ObjectHelper.notNull(contentType, "contentType");
ObjectHelper.notNull(recipientDeliveryAddress,
"recipientDeliveryAddress");
+ // The delivery address is chosen by the sender of the AS2 message
(the Receipt-Delivery-Option
+ // header), so it is untrusted input that selects an outbound
destination.
URI uri = URI.create(recipientDeliveryAddress);
+ String scheme = uri.getScheme() == null ? null :
uri.getScheme().toLowerCase(Locale.US);
+ // Only http. This class delivers over a plain Socket and has no TLS
of any kind, so accepting https
+ // would mean writing the request - including the Authorization header
- in cleartext to the TLS port.
+ // https delivery has never worked here for that reason, so refusing
it removes nothing that functioned.
+ if (!"http".equals(scheme)) {
+ throw new HttpException(
+ "Refusing to deliver the asynchronous MDN: the delivery
address must use http."
+ + " TLS delivery of asynchronous MDNs is
not supported");
+ }
+ String host = normalizeHost(uri.getHost());
+ if (host == null) {
+ throw new HttpException("Refusing to deliver the asynchronous MDN:
the delivery address has no host");
+ }
+ int port = uri.getPort() != -1 ? uri.getPort() : 80;
+
+ boolean hostIsAllowed = isAllowedHost(host);
+ if (allowedHosts != null && !allowedHosts.isBlank() && !hostIsAllowed)
{
+ throw new HttpException(
+ "Refusing to deliver the asynchronous MDN: the delivery
address host is not in asyncMdnAllowedHosts");
+ }
int buffSize = 8 * 1024;
@@ -138,7 +187,7 @@ public class AS2AsynchronousMDNManager {
HttpConnectionFactory<ManagedHttpClientConnection> connFactory
=
ManagedHttpClientConnectionFactory.builder().http1Config(h1Config).build();
- try (HttpClientConnection httpConnection =
connFactory.createConnection(new Socket(uri.getHost(), uri.getPort()))) {
+ try (HttpClientConnection httpConnection =
connFactory.createConnection(new Socket(host, port))) {
// Add Context attributes
HttpCoreContext httpContext = HttpCoreContext.create();
@@ -146,7 +195,15 @@ public class AS2AsynchronousMDNManager {
ClassicHttpRequest request = new BasicClassicHttpRequest("POST",
uri);
request.setHeader(AS2Header.CONTENT_TYPE, contentType);
- AS2HeaderUtils.addAuthorizationHeader(request, userName, password,
accessToken);
+ // Credentials are only attached to a host the operator has
vouched for. Without an allow-list the
+ // destination is entirely sender-chosen, so the MDN is still
delivered but without them.
+ if (hostIsAllowed) {
+ AS2HeaderUtils.addAuthorizationHeader(request, userName,
password, accessToken);
+ } else if (userName != null || accessToken != null) {
+ LOG.warn("Asynchronous MDN credentials not sent to
sender-supplied host {}:"
+ + " set asyncMdnAllowedHosts to authorise it",
+ host);
+ }
httpContext.setRequest(request);
multipartMimeEntity.setMainBody(true);
EntityUtils.setMessageEntity(request, multipartMimeEntity);
@@ -166,6 +223,29 @@ public class AS2AsynchronousMDNManager {
}
}
+ /**
+ * {@link URI#getHost()} returns an IPv6 literal in its bracketed form
({@code [::1]}), which would never match an
+ * allow-list entry written the way an operator writes it. Compare the
address itself.
+ */
+ private static String normalizeHost(String host) {
+ if (host != null && host.length() > 1 && host.charAt(0) == '[' &&
host.charAt(host.length() - 1) == ']') {
+ return host.substring(1, host.length() - 1);
+ }
+ return host;
+ }
+
+ private boolean isAllowedHost(String host) {
+ if (allowedHosts == null || allowedHosts.isBlank()) {
+ return false;
+ }
+ for (String allowed : allowedHosts.split(",")) {
+ if (allowed.trim().equalsIgnoreCase(host)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private HttpResponse send(HttpClientConnection httpConnection,
ClassicHttpRequest request, HttpCoreContext httpContext)
throws HttpException, IOException {
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 e99112afc581..e3c5349a6e97 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
@@ -92,6 +92,7 @@ public class AS2ServerConnection {
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
@@ -530,7 +531,8 @@ public class AS2ServerConnection {
config.getSigningPrivateKey(),
AS2ServerConnection.this.userName,
AS2ServerConnection.this.password,
- AS2ServerConnection.this.accessToken);
+ AS2ServerConnection.this.accessToken,
+ AS2ServerConnection.this.asyncMdnAllowedHosts);
HttpRequest request = coreContext.getRequest();
AS2SignedDataGenerator gen =
ResponseMDN.createSigningGenerator(
@@ -589,6 +591,28 @@ public class AS2ServerConnection {
String password,
String accessToken)
throws IOException {
+ this(as2Version, originServer, serverFqdn, serverPortNumber,
signingAlgorithm, signingCertificateChain,
+ signingPrivateKey, decryptingPrivateKey, mdnMessageTemplate,
validateSigningCertificateChain, sslContext,
+ userName, password, accessToken, null);
+ }
+
+ public AS2ServerConnection(String as2Version,
+ String originServer,
+ String serverFqdn,
+ Integer serverPortNumber,
+ AS2SignatureAlgorithm signingAlgorithm,
+ Certificate[] signingCertificateChain,
+ PrivateKey signingPrivateKey,
+ PrivateKey decryptingPrivateKey,
+ String mdnMessageTemplate,
+ Certificate[] validateSigningCertificateChain,
+ SSLContext sslContext,
+ String userName,
+ String password,
+ 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");
diff --git
a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2AsynchronousMDNManagerDeliveryAddressTest.java
b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2AsynchronousMDNManagerDeliveryAddressTest.java
new file mode 100644
index 000000000000..09ea7b6cca42
--- /dev/null
+++
b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2AsynchronousMDNManagerDeliveryAddressTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.entity.MultipartMimeEntity;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpException;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The delivery address for an asynchronous MDN comes from the {@code
Receipt-Delivery-Option} header of the received
+ * AS2 message, so it is chosen by the sender. It selects an outbound
destination and, before this was constrained, the
+ * configured MDN credentials went with it.
+ */
+class AS2AsynchronousMDNManagerDeliveryAddressTest {
+
+ private static final String ALLOWED = "partner.example";
+
+ /**
+ * https in particular: this class delivers over a plain {@link
java.net.Socket} and has no TLS, so accepting an
+ * https address would write the request - including the Authorization
header - in cleartext to the TLS port. Before
+ * the delivery address was validated at all, an https address without an
explicit port failed closed on
+ * {@code new Socket(host, -1)}; resolving it to 443 instead would have
been a regression.
+ */
+ @Test
+ void aSchemeOtherThanHttpIsRefused() {
+ for (String address : new String[] {
+ "https://partner.example/receipts",
"https://partner.example:443/receipts",
+ "file:///etc/passwd", "ftp://partner.example/x",
"gopher://partner.example:70/x", "//partner.example/x" }) {
+ HttpException e = assertThrows(HttpException.class, () ->
deliver(address, ALLOWED),
+ "expected " + address + " to be refused");
+ assertTrue(e.getMessage().contains("must use http") ||
e.getMessage().contains("no host"),
+ "unexpected message for " + address + ": " +
e.getMessage());
+ }
+ }
+
+ @Test
+ void aHostOutsideTheAllowListIsRefused() {
+ HttpException e = assertThrows(HttpException.class,
+ () -> deliver("http://attacker.example/receipts", ALLOWED));
+ assertTrue(e.getMessage().contains("asyncMdnAllowedHosts"),
"unexpected message: " + e.getMessage());
+ }
+
+ /**
+ * With no allow-list the MDN is still delivered, so this gets past the
address checks and fails on the connection
+ * instead - which is what tells us the address itself was accepted.
+ */
+ @Test
+ void withNoAllowListTheAddressIsStillAccepted() {
+ Exception e = assertThrows(Exception.class,
+ () -> deliver("http://localhost:1/receipts", null));
+ assertTrue(!(e instanceof HttpException) ||
!e.getMessage().contains("asyncMdnAllowedHosts"),
+ "the address must not be refused when no allow-list is
configured: " + e.getMessage());
+ }
+
+ @Test
+ void anIpv6AllowListEntryIsMatchedWithoutItsBrackets() {
+ // URI.getHost() yields "[::1]", but an operator writes "::1" in the
allow-list
+ Exception e = assertThrows(Exception.class, () ->
deliver("http://[::1]:1/receipts", "::1"));
+ assertTrue(!(e instanceof HttpException) ||
!e.getMessage().contains("asyncMdnAllowedHosts"),
+ "an IPv6 literal should match its unbracketed allow-list
entry: " + e.getMessage());
+ }
+
+ private static void deliver(String deliveryAddress, String allowedHosts)
throws Exception {
+ AS2AsynchronousMDNManager manager = new AS2AsynchronousMDNManager(
+ "1.1", "Camel", "sender.example.com", null, null, "user",
"password", null, allowedHosts);
+ manager.send(new TestEntity(), AS2MimeType.MULTIPART_REPORT,
deliveryAddress);
+ }
+
+ /**
+ * The manager null-checks its entity before it looks at the delivery
address, so the address checks need a real
+ * one. Its content is irrelevant here - none of these cases reach the
point of writing it.
+ */
+ private static final class TestEntity extends MultipartMimeEntity {
+
+ private TestEntity() {
+ super(ContentType.create(AS2MimeType.MULTIPART_REPORT), "7bit");
+ }
+
+ @Override
+ public void close() {
+ // nothing to release
+ }
+ }
+}
diff --git
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2AsyncMDNServerManagerEndpointConfigurationConfigurer.java
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2AsyncMDNServerManagerEndpointConfigurationConfigurer.java
index 1d9d953996b3..beb0ce63526f 100644
---
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2AsyncMDNServerManagerEndpointConfigurationConfigurer.java
+++
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2AsyncMDNServerManagerEndpointConfigurationConfigurer.java
@@ -28,6 +28,7 @@ public class
AS2AsyncMDNServerManagerEndpointConfigurationConfigurer extends org
map.put("As2MessageStructure",
org.apache.camel.component.as2.api.AS2MessageStructure.class);
map.put("As2To", java.lang.String.class);
map.put("As2Version", java.lang.String.class);
+ map.put("AsyncMdnAllowedHosts", java.lang.String.class);
map.put("AsyncMdnPortNumber", java.lang.Integer.class);
map.put("AttachedFileName", java.lang.String.class);
map.put("ClientFqdn", java.lang.String.class);
@@ -89,6 +90,8 @@ public class
AS2AsyncMDNServerManagerEndpointConfigurationConfigurer extends org
case "as2To": target.setAs2To(property(camelContext,
java.lang.String.class, value)); return true;
case "as2version":
case "as2Version": target.setAs2Version(property(camelContext,
java.lang.String.class, value)); return true;
+ case "asyncmdnallowedhosts":
+ case "asyncMdnAllowedHosts":
target.setAsyncMdnAllowedHosts(property(camelContext, java.lang.String.class,
value)); return true;
case "asyncmdnportnumber":
case "asyncMdnPortNumber":
target.setAsyncMdnPortNumber(property(camelContext, java.lang.Integer.class,
value)); return true;
case "attachedfilename":
@@ -193,6 +196,8 @@ public class
AS2AsyncMDNServerManagerEndpointConfigurationConfigurer extends org
case "as2To": return java.lang.String.class;
case "as2version":
case "as2Version": return java.lang.String.class;
+ case "asyncmdnallowedhosts":
+ case "asyncMdnAllowedHosts": return java.lang.String.class;
case "asyncmdnportnumber":
case "asyncMdnPortNumber": return java.lang.Integer.class;
case "attachedfilename":
@@ -293,6 +298,8 @@ public class
AS2AsyncMDNServerManagerEndpointConfigurationConfigurer extends org
case "as2To": return target.getAs2To();
case "as2version":
case "as2Version": return target.getAs2Version();
+ case "asyncmdnallowedhosts":
+ case "asyncMdnAllowedHosts": return target.getAsyncMdnAllowedHosts();
case "asyncmdnportnumber":
case "asyncMdnPortNumber": return target.getAsyncMdnPortNumber();
case "attachedfilename":
diff --git
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ClientManagerEndpointConfigurationConfigurer.java
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ClientManagerEndpointConfigurationConfigurer.java
index cc25efd702dd..96a5158d8e56 100644
---
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ClientManagerEndpointConfigurationConfigurer.java
+++
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ClientManagerEndpointConfigurationConfigurer.java
@@ -28,6 +28,7 @@ public class AS2ClientManagerEndpointConfigurationConfigurer
extends org.apache.
map.put("As2MessageStructure",
org.apache.camel.component.as2.api.AS2MessageStructure.class);
map.put("As2To", java.lang.String.class);
map.put("As2Version", java.lang.String.class);
+ map.put("AsyncMdnAllowedHosts", java.lang.String.class);
map.put("AsyncMdnPortNumber", java.lang.Integer.class);
map.put("AttachedFileName", java.lang.String.class);
map.put("ClientFqdn", java.lang.String.class);
@@ -90,6 +91,8 @@ public class AS2ClientManagerEndpointConfigurationConfigurer
extends org.apache.
case "as2To": target.setAs2To(property(camelContext,
java.lang.String.class, value)); return true;
case "as2version":
case "as2Version": target.setAs2Version(property(camelContext,
java.lang.String.class, value)); return true;
+ case "asyncmdnallowedhosts":
+ case "asyncMdnAllowedHosts":
target.setAsyncMdnAllowedHosts(property(camelContext, java.lang.String.class,
value)); return true;
case "asyncmdnportnumber":
case "asyncMdnPortNumber":
target.setAsyncMdnPortNumber(property(camelContext, java.lang.Integer.class,
value)); return true;
case "attachedfilename":
@@ -196,6 +199,8 @@ public class
AS2ClientManagerEndpointConfigurationConfigurer extends org.apache.
case "as2To": return java.lang.String.class;
case "as2version":
case "as2Version": return java.lang.String.class;
+ case "asyncmdnallowedhosts":
+ case "asyncMdnAllowedHosts": return java.lang.String.class;
case "asyncmdnportnumber":
case "asyncMdnPortNumber": return java.lang.Integer.class;
case "attachedfilename":
@@ -298,6 +303,8 @@ public class
AS2ClientManagerEndpointConfigurationConfigurer extends org.apache.
case "as2To": return target.getAs2To();
case "as2version":
case "as2Version": return target.getAs2Version();
+ case "asyncmdnallowedhosts":
+ case "asyncMdnAllowedHosts": return target.getAsyncMdnAllowedHosts();
case "asyncmdnportnumber":
case "asyncMdnPortNumber": return target.getAsyncMdnPortNumber();
case "attachedfilename":
diff --git
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ConfigurationConfigurer.java
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ConfigurationConfigurer.java
index aded86eb73de..0af682c05337 100644
---
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ConfigurationConfigurer.java
+++
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ConfigurationConfigurer.java
@@ -28,6 +28,7 @@ public class AS2ConfigurationConfigurer extends
org.apache.camel.support.compone
map.put("As2MessageStructure",
org.apache.camel.component.as2.api.AS2MessageStructure.class);
map.put("As2To", java.lang.String.class);
map.put("As2Version", java.lang.String.class);
+ map.put("AsyncMdnAllowedHosts", java.lang.String.class);
map.put("AsyncMdnPortNumber", java.lang.Integer.class);
map.put("AttachedFileName", java.lang.String.class);
map.put("ClientFqdn", java.lang.String.class);
@@ -88,6 +89,8 @@ public class AS2ConfigurationConfigurer extends
org.apache.camel.support.compone
case "as2To": target.setAs2To(property(camelContext,
java.lang.String.class, value)); return true;
case "as2version":
case "as2Version": target.setAs2Version(property(camelContext,
java.lang.String.class, value)); return true;
+ case "asyncmdnallowedhosts":
+ case "asyncMdnAllowedHosts":
target.setAsyncMdnAllowedHosts(property(camelContext, java.lang.String.class,
value)); return true;
case "asyncmdnportnumber":
case "asyncMdnPortNumber":
target.setAsyncMdnPortNumber(property(camelContext, java.lang.Integer.class,
value)); return true;
case "attachedfilename":
@@ -190,6 +193,8 @@ public class AS2ConfigurationConfigurer extends
org.apache.camel.support.compone
case "as2To": return java.lang.String.class;
case "as2version":
case "as2Version": return java.lang.String.class;
+ case "asyncmdnallowedhosts":
+ case "asyncMdnAllowedHosts": return java.lang.String.class;
case "asyncmdnportnumber":
case "asyncMdnPortNumber": return java.lang.Integer.class;
case "attachedfilename":
@@ -288,6 +293,8 @@ public class AS2ConfigurationConfigurer extends
org.apache.camel.support.compone
case "as2To": return target.getAs2To();
case "as2version":
case "as2Version": return target.getAs2Version();
+ case "asyncmdnallowedhosts":
+ case "asyncMdnAllowedHosts": return target.getAsyncMdnAllowedHosts();
case "asyncmdnportnumber":
case "asyncMdnPortNumber": return target.getAsyncMdnPortNumber();
case "attachedfilename":
diff --git
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointConfigurer.java
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointConfigurer.java
index 91eb0bb01448..69a06454c0db 100644
---
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointConfigurer.java
+++
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointConfigurer.java
@@ -57,6 +57,7 @@ public class AS2EndpointConfigurer extends
PropertyConfigurerSupport implements
map.put("ExpectContinue", boolean.class);
map.put("LazyStartProducer", boolean.class);
map.put("AccessToken", java.lang.String.class);
+ map.put("AsyncMdnAllowedHosts", java.lang.String.class);
map.put("DecryptingPrivateKey", java.security.PrivateKey.class);
map.put("EncryptingAlgorithm",
org.apache.camel.component.as2.api.AS2EncryptionAlgorithm.class);
map.put("EncryptingCertificateChain",
java.security.cert.Certificate[].class);
@@ -90,6 +91,8 @@ public class AS2EndpointConfigurer extends
PropertyConfigurerSupport implements
case "as2To":
target.getConfiguration().setAs2To(property(camelContext,
java.lang.String.class, value)); return true;
case "as2version":
case "as2Version":
target.getConfiguration().setAs2Version(property(camelContext,
java.lang.String.class, value)); return true;
+ case "asyncmdnallowedhosts":
+ case "asyncMdnAllowedHosts":
target.getConfiguration().setAsyncMdnAllowedHosts(property(camelContext,
java.lang.String.class, value)); return true;
case "asyncmdnportnumber":
case "asyncMdnPortNumber":
target.getConfiguration().setAsyncMdnPortNumber(property(camelContext,
java.lang.Integer.class, value)); return true;
case "attachedfilename":
@@ -196,6 +199,8 @@ public class AS2EndpointConfigurer extends
PropertyConfigurerSupport implements
case "as2To": return java.lang.String.class;
case "as2version":
case "as2Version": return java.lang.String.class;
+ case "asyncmdnallowedhosts":
+ case "asyncMdnAllowedHosts": return java.lang.String.class;
case "asyncmdnportnumber":
case "asyncMdnPortNumber": return java.lang.Integer.class;
case "attachedfilename":
@@ -298,6 +303,8 @@ public class AS2EndpointConfigurer extends
PropertyConfigurerSupport implements
case "as2To": return target.getConfiguration().getAs2To();
case "as2version":
case "as2Version": return target.getConfiguration().getAs2Version();
+ case "asyncmdnallowedhosts":
+ case "asyncMdnAllowedHosts": return
target.getConfiguration().getAsyncMdnAllowedHosts();
case "asyncmdnportnumber":
case "asyncMdnPortNumber": return
target.getConfiguration().getAsyncMdnPortNumber();
case "attachedfilename":
diff --git
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointUriFactory.java
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointUriFactory.java
index ae00901c0b9e..76bc94bf23ab 100644
---
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointUriFactory.java
+++
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointUriFactory.java
@@ -24,13 +24,14 @@ public class AS2EndpointUriFactory extends
org.apache.camel.support.component.En
private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES;
private static final Map<String, String> MULTI_VALUE_PREFIXES;
static {
- Set<String> props = new HashSet<>(54);
+ Set<String> props = new HashSet<>(55);
props.add("accessToken");
props.add("apiName");
props.add("as2From");
props.add("as2MessageStructure");
props.add("as2To");
props.add("as2Version");
+ props.add("asyncMdnAllowedHosts");
props.add("asyncMdnPortNumber");
props.add("attachedFileName");
props.add("clientFqdn");
diff --git
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ServerManagerEndpointConfigurationConfigurer.java
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ServerManagerEndpointConfigurationConfigurer.java
index c51a80c7f24f..a3580c9594e2 100644
---
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ServerManagerEndpointConfigurationConfigurer.java
+++
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ServerManagerEndpointConfigurationConfigurer.java
@@ -28,6 +28,7 @@ public class AS2ServerManagerEndpointConfigurationConfigurer
extends org.apache.
map.put("As2MessageStructure",
org.apache.camel.component.as2.api.AS2MessageStructure.class);
map.put("As2To", java.lang.String.class);
map.put("As2Version", java.lang.String.class);
+ map.put("AsyncMdnAllowedHosts", java.lang.String.class);
map.put("AsyncMdnPortNumber", java.lang.Integer.class);
map.put("AttachedFileName", java.lang.String.class);
map.put("ClientFqdn", java.lang.String.class);
@@ -89,6 +90,8 @@ public class AS2ServerManagerEndpointConfigurationConfigurer
extends org.apache.
case "as2To": target.setAs2To(property(camelContext,
java.lang.String.class, value)); return true;
case "as2version":
case "as2Version": target.setAs2Version(property(camelContext,
java.lang.String.class, value)); return true;
+ case "asyncmdnallowedhosts":
+ case "asyncMdnAllowedHosts":
target.setAsyncMdnAllowedHosts(property(camelContext, java.lang.String.class,
value)); return true;
case "asyncmdnportnumber":
case "asyncMdnPortNumber":
target.setAsyncMdnPortNumber(property(camelContext, java.lang.Integer.class,
value)); return true;
case "attachedfilename":
@@ -193,6 +196,8 @@ public class
AS2ServerManagerEndpointConfigurationConfigurer extends org.apache.
case "as2To": return java.lang.String.class;
case "as2version":
case "as2Version": return java.lang.String.class;
+ case "asyncmdnallowedhosts":
+ case "asyncMdnAllowedHosts": return java.lang.String.class;
case "asyncmdnportnumber":
case "asyncMdnPortNumber": return java.lang.Integer.class;
case "attachedfilename":
@@ -293,6 +298,8 @@ public class
AS2ServerManagerEndpointConfigurationConfigurer extends org.apache.
case "as2To": return target.getAs2To();
case "as2version":
case "as2Version": return target.getAs2Version();
+ case "asyncmdnallowedhosts":
+ case "asyncMdnAllowedHosts": return target.getAsyncMdnAllowedHosts();
case "asyncmdnportnumber":
case "asyncMdnPortNumber": return target.getAsyncMdnPortNumber();
case "attachedfilename":
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 bef8bcfaed86..7fe783876211 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,22 +68,23 @@
"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. W [...]
"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 produ [...]
"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." },
- "decryptingPrivateKey": { "index": 35, "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": 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, "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": 40, "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 [...]
- "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, "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 asyn [...]
- "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, "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": 43, "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 cann [...]
- "signedReceiptMicAlgorithms": { "index": 44, "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": 45, "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 [...]
- "signingCertificateChain": { "index": 46, "kind": "parameter",
"displayName": "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": "The chain of certificates used to sign EDI
message." },
- "signingPrivateKey": { "index": 47, "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": 48, "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": 49, "kind": "parameter", "displayName": "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 client for
basic authentication. If options for basic authen [...]
- "validateSigningCertificateChain": { "index": 50, "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 [...]
+ "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 [...]
+ "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",
"GOST28 [...]
+ "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 [...]
+ "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 asyn [...]
+ "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 cann [...]
+ "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 messa [...]
+ "signingAlgorithm": { "index": 46, "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 [...]
+ "signingCertificateChain": { "index": 47, "kind": "parameter",
"displayName": "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": "The chain of certificates used to sign EDI
message." },
+ "signingPrivateKey": { "index": 48, "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": 49, "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": 50, "kind": "parameter", "displayName": "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 client for
basic authentication. If options for basic authen [...]
+ "validateSigningCertificateChain": { "index": 51, "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/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 e3cceb395f7b..cc283631a27c 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
@@ -118,6 +118,8 @@ public class AS2Configuration {
private boolean signatureVerificationRequired;
@UriParam(label = "security")
private SSLContext sslContext;
+ @UriParam(label = "security")
+ private String asyncMdnAllowedHosts;
// If you use localhost-based AS2 server, you don't need to specify a
hostnameVerifier
@UriParam(label = "security")
private HostnameVerifier hostnameVerifier;
@@ -686,4 +688,20 @@ public class AS2Configuration {
public void setMdnAccessToken(String accessToken) {
this.mdnAccessToken = accessToken;
}
+
+ public String getAsyncMdnAllowedHosts() {
+ return asyncMdnAllowedHosts;
+ }
+
+ /**
+ * 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.
+ */
+ 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/internal/AS2ConnectionHelper.java
b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/internal/AS2ConnectionHelper.java
index e22a54588b78..ceacff93c682 100644
---
a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/internal/AS2ConnectionHelper.java
+++
b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/internal/AS2ConnectionHelper.java
@@ -110,7 +110,8 @@ public final class AS2ConnectionHelper {
configuration.getValidateSigningCertificateChain(),
configuration.getSslContext(),
configuration.getMdnUserName(),
configuration.getMdnPassword(),
- configuration.getMdnAccessToken());
+ configuration.getMdnAccessToken(),
+ configuration.getAsyncMdnAllowedHosts());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
diff --git
a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2AsyncMdnBasicAuthHeaderTest.java
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2AsyncMdnBasicAuthHeaderTest.java
index 69faa09e695b..be603853b859 100644
---
a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2AsyncMdnBasicAuthHeaderTest.java
+++
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2AsyncMdnBasicAuthHeaderTest.java
@@ -196,7 +196,10 @@ public class AS2AsyncMdnBasicAuthHeaderTest extends
AbstractAS2ITSupport {
null, null, null,
"TBD", null, null,
// server authorization config
- MDN_USER_NAME, MDN_PASSWORD, MDN_ACCESS_TOKEN);
+ MDN_USER_NAME, MDN_PASSWORD, MDN_ACCESS_TOKEN,
+ // the MDN delivery address comes from the
receiptDeliveryOption header, so the host it names
+ // has to be authorised before the credentials above are
attached to it
+ "localhost");
targetPort = serverConnection.getLocalPort();
serverConnection.listen("/", new
AS2AsyncMDNServerManagerIT.RequestHandler());
}
diff --git
a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2AsyncMdnTokenAuthHeaderTest.java
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2AsyncMdnTokenAuthHeaderTest.java
index 9c8c6da0017e..afc5e3a70ae1 100644
---
a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2AsyncMdnTokenAuthHeaderTest.java
+++
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2AsyncMdnTokenAuthHeaderTest.java
@@ -147,7 +147,10 @@ public class AS2AsyncMdnTokenAuthHeaderTest extends
AbstractAS2ITSupport {
null, null, null,
"TBD", null, null,
// server authorization config
- null, null, MDN_ACCESS_TOKEN);
+ null, null, MDN_ACCESS_TOKEN,
+ // the MDN delivery address comes from the
receiptDeliveryOption header, so the host it names
+ // has to be authorised before the credentials above are
attached to it
+ "localhost");
targetPort = serverConnection.getLocalPort();
serverConnection.listen("/", new
AS2AsyncMDNServerManagerIT.RequestHandler());
}
diff --git
a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerBasicAuthHeaderTest.java
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerBasicAuthHeaderTest.java
index 3c414991de16..f7d18367df56 100644
---
a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerBasicAuthHeaderTest.java
+++
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerBasicAuthHeaderTest.java
@@ -128,5 +128,8 @@ public class AS2ServerBasicAuthHeaderTest extends
AbstractAS2ITSupport {
@Override
protected void customizeConfiguration(AS2Configuration configuration) {
configuration.setServerPortNumber(targetPort.getPort());
+ // The MDN credentials are only attached to a delivery host the
operator has authorised, and the
+ // delivery address here comes from the receiptDeliveryOption header,
so localhost has to be listed.
+ configuration.setAsyncMdnAllowedHosts("localhost");
}
}
diff --git
a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerBearerAuthHeaderTest.java
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerBearerAuthHeaderTest.java
index 9ae4ea3a704e..94df68a32b4a 100644
---
a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerBearerAuthHeaderTest.java
+++
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerBearerAuthHeaderTest.java
@@ -125,5 +125,8 @@ public class AS2ServerBearerAuthHeaderTest extends
AbstractAS2ITSupport {
@Override
protected void customizeConfiguration(AS2Configuration configuration) {
configuration.setServerPortNumber(targetPort.getPort());
+ // The MDN credentials are only attached to a delivery host the
operator has authorised, and the
+ // delivery address here comes from the receiptDeliveryOption header,
so localhost has to be listed.
+ configuration.setAsyncMdnAllowedHosts("localhost");
}
}
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 e34bfad092f6..bdcfdec593df 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
@@ -622,6 +622,27 @@ public interface AS2EndpointBuilderFactory {
doSetProperty("accessToken", accessToken);
return this;
}
+ /**
+ * 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 is a: <code>java.lang.String</code> type.
+ *
+ * Group: security
+ *
+ * @param asyncMdnAllowedHosts the value to set
+ * @return the dsl builder
+ */
+ default AS2EndpointConsumerBuilder asyncMdnAllowedHosts(String
asyncMdnAllowedHosts) {
+ doSetProperty("asyncMdnAllowedHosts", asyncMdnAllowedHosts);
+ return this;
+ }
/**
* The key used to encrypt the EDI message.
*
@@ -1737,6 +1758,27 @@ public interface AS2EndpointBuilderFactory {
doSetProperty("accessToken", accessToken);
return this;
}
+ /**
+ * 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 is a: <code>java.lang.String</code> type.
+ *
+ * Group: security
+ *
+ * @param asyncMdnAllowedHosts the value to set
+ * @return the dsl builder
+ */
+ default AS2EndpointProducerBuilder asyncMdnAllowedHosts(String
asyncMdnAllowedHosts) {
+ doSetProperty("asyncMdnAllowedHosts", asyncMdnAllowedHosts);
+ return this;
+ }
/**
* The key used to encrypt the EDI message.
*
@@ -2793,6 +2835,27 @@ public interface AS2EndpointBuilderFactory {
doSetProperty("accessToken", accessToken);
return this;
}
+ /**
+ * 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 is a: <code>java.lang.String</code> type.
+ *
+ * Group: security
+ *
+ * @param asyncMdnAllowedHosts the value to set
+ * @return the dsl builder
+ */
+ default AS2EndpointBuilder asyncMdnAllowedHosts(String
asyncMdnAllowedHosts) {
+ doSetProperty("asyncMdnAllowedHosts", asyncMdnAllowedHosts);
+ return this;
+ }
/**
* The key used to encrypt the EDI message.
*