This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 1d5d3e983c3a CAMEL-24576: dynamic-router - gate control message
predicate
1d5d3e983c3a is described below
commit 1d5d3e983c3a3fb64b6bfe238d614726191ee977
Author: Andrea Cosentino <[email protected]>
AuthorDate: Tue Sep 1 16:51:05 2026 +0200
CAMEL-24576: dynamic-router - gate control message predicate
Adds allowPredicateFromMessage on the dynamic-router-control endpoint,
default false. Previously a control message could freely supply a
predicate/expressionLanguage that got compiled into the channel's rule
base -- untrusted input driving expression evaluation, unlike every
other Camel opt-in-gated template/script source (allowTemplateFromHeader).
When the gate is closed, control messages fall back to the endpoint's
configured predicate instead of being rejected. Also fixes
DynamicRouterRecipientListHelper to wire allowedSchemes into the
RecipientList it builds, and makes subscribeFromHeaders fall back to
endpoint configuration for subscription parameters the message omits.
This changes a default: deployments relying on control messages
supplying their own predicate must now set allowPredicateFromMessage=true
or switch to predicateBean. Documented in the 4.23 upgrade guide.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Closes #25992
---
.../catalog/components/dynamic-router-control.json | 3 +-
.../camel/catalog/components/dynamic-router.json | 3 +-
.../catalog/docs/dynamic-router-component.adoc | 19 +++-
.../docs/dynamic-router-control-component.adoc | 31 +++++
.../DynamicRouterControlEndpointConfigurer.java | 6 +
.../DynamicRouterControlEndpointUriFactory.java | 3 +-
.../routing/DynamicRouterEndpointConfigurer.java | 6 +
.../routing/DynamicRouterEndpointUriFactory.java | 3 +-
.../control/dynamic-router-control.json | 3 +-
.../dynamicrouter/routing/dynamic-router.json | 3 +-
.../src/main/docs/dynamic-router-component.adoc | 19 +++-
.../docs/dynamic-router-control-component.adoc | 31 +++++
...ynamicRouterControlChannelSendDynamicAware.java | 9 ++
.../control/DynamicRouterControlConfiguration.java | 33 ++++++
.../control/DynamicRouterControlConstants.java | 16 +++
.../control/DynamicRouterControlProducer.java | 70 +++++++++---
.../routing/DynamicRouterConfiguration.java | 28 +++++
.../routing/DynamicRouterRecipientListHelper.java | 1 +
...icRouterControlChannelSendDynamicAwareTest.java | 11 ++
.../control/DynamicRouterControlProducerTest.java | 126 +++++++++++++++++++++
.../DynamicRouterBasicSynchronousIT.java | 8 +-
.../DynamicRouterDefaultAggregationIT.java | 2 +-
.../DynamicRouterSendDynamicAwareIT.java | 3 +-
.../DynamicRouterRecipientListHelperTest.java | 2 +
.../java/org/apache/camel/util/SecurityUtils.java | 1 +
.../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc | 33 ++++++
...DynamicRouterControlEndpointBuilderFactory.java | 42 +++++++
.../dsl/DynamicRouterEndpointBuilderFactory.java | 17 +++
28 files changed, 498 insertions(+), 34 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dynamic-router-control.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dynamic-router-control.json
index 00e34e99babf..a27bcb45d41a 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dynamic-router-control.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dynamic-router-control.json
@@ -46,6 +46,7 @@
"predicateBean": { "index": 5, "kind": "parameter", "displayName":
"Predicate Bean", "group": "control", "label": "control", "required": false,
"type": "object", "javaType": "org.apache.camel.Predicate", "deprecated":
false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
"configurationField": "configuration", "description": "A Predicate instance in
the registry." },
"priority": { "index": 6, "kind": "parameter", "displayName": "Priority",
"group": "control", "label": "control", "required": false, "type": "integer",
"javaType": "java.lang.Integer", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
"configurationField": "configuration", "description": "The subscription
priority." },
"subscribeChannel": { "index": 7, "kind": "parameter", "displayName":
"Subscribe Channel", "group": "control", "label": "control", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
"configurationField": "configuration", "description": "The channel to
subscribe to" },
- "subscriptionId": { "index": 8, "kind": "parameter", "displayName":
"Subscription Id", "group": "control", "label": "control", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
"configurationField": "configuration", "description": "The subscription ID; if
unspecified, one will be assigned and returned." }
+ "subscriptionId": { "index": 8, "kind": "parameter", "displayName":
"Subscription Id", "group": "control", "label": "control", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
"configurationField": "configuration", "description": "The subscription ID; if
unspecified, one will be assigned and returned." },
+ "allowPredicateFromMessage": { "index": 9, "kind": "parameter",
"displayName": "Allow Predicate From Message", "group": "security", "label":
"control,security", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"security": "insecure:dev", "insecureValue": "true", "defaultValue": false,
"configurationClass":
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
"configurationField": "c [...]
}
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dynamic-router.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dynamic-router.json
index c360b9c180ed..2a056e382f8d 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dynamic-router.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dynamic-router.json
@@ -48,6 +48,7 @@
"synchronous": { "index": 17, "kind": "parameter", "displayName":
"Synchronous", "group": "common", "label": "common", "required": false, "type":
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.dynamicrouter.routing.DynamicRouterConfiguration",
"configurationField": "configuration", "description": "Sets whether synchronous
processing should be strictly used. When enabl [...]
"timeout": { "index": 18, "kind": "parameter", "displayName": "Timeout",
"group": "common", "label": "common", "required": false, "type": "integer",
"javaType": "long", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": -1, "configurationClass":
"org.apache.camel.component.dynamicrouter.routing.DynamicRouterConfiguration",
"configurationField": "configuration", "description": "Sets a total timeout
specified in millis, when using parallel processing. If the Dyna [...]
"warnDroppedMessage": { "index": 19, "kind": "parameter", "displayName":
"Warn Dropped Message", "group": "common", "label": "common", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false,
"configurationClass":
"org.apache.camel.component.dynamicrouter.routing.DynamicRouterConfiguration",
"configurationField": "configuration", "description": "Flag to log a warning if
no predicates match for an exc [...]
- "lazyStartProducer": { "index": 20, "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 [...]
+ "lazyStartProducer": { "index": 20, "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 [...]
+ "allowedSchemes": { "index": 21, "kind": "parameter", "displayName":
"Allowed Schemes", "group": "security", "label": "advanced,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.dynamicrouter.routing.DynamicRouterConfiguration",
"configurationField": "configuration", "description": "Sets an optional
comma-separated allow-list of component schemes t [...]
}
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dynamic-router-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dynamic-router-component.adoc
index 826097176c52..1aa7f2b2d187 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dynamic-router-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dynamic-router-component.adoc
@@ -318,7 +318,18 @@ producerTemplate.sendBody("kafka://subscriptions",
mapper.writeValueAsString(con
----
In another module, additional routing will serve as a bridge to get the
message from Kafka to the control channel of the
-Dynamic Router:
+Dynamic Router. The subscriptions above carry their predicate in the control
message, so the bridge has to opt in with
+`allowPredicateFromMessage=true`.
+
+[WARNING]
+====
+`allowPredicateFromMessage` lets whoever puts a message on the bridged
transport choose both the expression language and
+the expression that the Dynamic Router compiles and evaluates, which means it
decides what runs inside the Camel
+process. Only enable it when the transport that feeds the control channel is
trusted. When it is not, have the
+participants subscribe with `predicateBean`, which selects a `Predicate` that
the route author bound in the registry,
+and leave the flag at its default of `false`. See
+xref:dynamic-router-control-component.adoc[the control component
documentation] for the alternatives.
+====
.Bridge from Kafka to the Dynamic Router control channel
@@ -330,7 +341,7 @@ Java::
----
from("kafka:subscriptions")
.unmarshal().json(DynamicRouterControlMessage.class)
- .to("dynamic-router-control:subscribe");
+ .to("dynamic-router-control:subscribe?allowPredicateFromMessage=true");
----
XML::
@@ -342,7 +353,7 @@ XML::
<unmarshal>
<json library="Jackson"
unmarshalType="org.apache.camel.component.dynamicrouter.control.DynamicRouterControlMessage"/>
</unmarshal>
- <to uri="dynamic-router-control:subscribe"/>
+ <to uri="dynamic-router-control:subscribe?allowPredicateFromMessage=true"/>
</route>
----
@@ -360,6 +371,8 @@ YAML::
unmarshalType:
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlMessage
- to:
uri: dynamic-router-control:subscribe
+ parameters:
+ allowPredicateFromMessage: true
----
====
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dynamic-router-control-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dynamic-router-control-component.adoc
index 6f7ee90aae83..ec0eeb535aa8 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dynamic-router-control-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dynamic-router-control-component.adoc
@@ -30,6 +30,37 @@ include::partial$component-endpoint-options.adoc[]
include::partial$component-endpoint-headers.adoc[]
// component options: END
+== Supplying the subscription predicate
+
+A subscription needs a predicate that decides which exchanges the participant
receives. There are three ways to supply
+one, and they differ in who chooses it:
+
+* `predicateBean` -- the name of a `Predicate` bound in the registry. The
route author decides which predicates exist,
+and the control message only selects one of them by name.
+* `predicate` and `expressionLanguage` as control endpoint URI parameters --
the route author writes the expression, and
+every subscription made through that endpoint uses it.
+* `predicate` and `expressionLanguage` carried in the control message, in its
body or its headers -- the sender of the
+control message chooses both the expression language and the expression.
+
+The third form is disabled by default. The predicate is compiled and then
evaluated against every exchange on the
+channel, so letting the control message choose both the language and the
expression means the sender of that message
+decides what runs inside the Camel process. Set
`allowPredicateFromMessage=true` on the control endpoint to enable it,
+and only do so when control messages can only come from a trusted source:
+
+[source,java]
+----
+from("kafka:subscriptions")
+ .unmarshal().json(DynamicRouterControlMessage.class)
+ .to("dynamic-router-control:subscribe?allowPredicateFromMessage=true");
+----
+
+When `allowPredicateFromMessage` is `false`, which is the default, a control
message that supplies a `predicate` or an
+`expressionLanguage` is rejected with an `IllegalArgumentException`. Prefer
`predicateBean`, or configure the predicate
+on the endpoint, when control messages arrive from outside the application.
+
+Subscription parameters that the control message does not carry fall back to
the values configured on the control
+endpoint, so a participant can send only the parameters that identify it and
let the endpoint supply the rest.
+
== Subscribing
Subscribing can be achieved by using query parameters in the control endpoint
URI, or by sending a
diff --git
a/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlEndpointConfigurer.java
b/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlEndpointConfigurer.java
index 65978aadf7f5..6eaaebe11bff 100644
---
a/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlEndpointConfigurer.java
+++
b/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlEndpointConfigurer.java
@@ -23,6 +23,8 @@ public class DynamicRouterControlEndpointConfigurer extends
PropertyConfigurerSu
public boolean configure(CamelContext camelContext, Object obj, String
name, Object value, boolean ignoreCase) {
DynamicRouterControlEndpoint target = (DynamicRouterControlEndpoint)
obj;
switch (ignoreCase ? name.toLowerCase() : name) {
+ case "allowpredicatefrommessage":
+ case "allowPredicateFromMessage":
target.getConfiguration().setAllowPredicateFromMessage(property(camelContext,
boolean.class, value)); return true;
case "destinationuri":
case "destinationUri":
target.getConfiguration().setDestinationUri(property(camelContext,
java.lang.String.class, value)); return true;
case "expressionlanguage":
@@ -44,6 +46,8 @@ public class DynamicRouterControlEndpointConfigurer extends
PropertyConfigurerSu
@Override
public Class<?> getOptionType(String name, boolean ignoreCase) {
switch (ignoreCase ? name.toLowerCase() : name) {
+ case "allowpredicatefrommessage":
+ case "allowPredicateFromMessage": return boolean.class;
case "destinationuri":
case "destinationUri": return java.lang.String.class;
case "expressionlanguage":
@@ -66,6 +70,8 @@ public class DynamicRouterControlEndpointConfigurer extends
PropertyConfigurerSu
public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
DynamicRouterControlEndpoint target = (DynamicRouterControlEndpoint)
obj;
switch (ignoreCase ? name.toLowerCase() : name) {
+ case "allowpredicatefrommessage":
+ case "allowPredicateFromMessage": return
target.getConfiguration().isAllowPredicateFromMessage();
case "destinationuri":
case "destinationUri": return
target.getConfiguration().getDestinationUri();
case "expressionlanguage":
diff --git
a/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlEndpointUriFactory.java
b/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlEndpointUriFactory.java
index 98313723392b..42772d430286 100644
---
a/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlEndpointUriFactory.java
+++
b/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlEndpointUriFactory.java
@@ -24,7 +24,8 @@ public class DynamicRouterControlEndpointUriFactory extends
org.apache.camel.sup
private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES;
private static final Map<String, String> MULTI_VALUE_PREFIXES;
static {
- Set<String> props = new HashSet<>(9);
+ Set<String> props = new HashSet<>(10);
+ props.add("allowPredicateFromMessage");
props.add("controlAction");
props.add("destinationUri");
props.add("expressionLanguage");
diff --git
a/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterEndpointConfigurer.java
b/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterEndpointConfigurer.java
index d1a144748b73..968bbd8dd52d 100644
---
a/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterEndpointConfigurer.java
+++
b/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterEndpointConfigurer.java
@@ -31,6 +31,8 @@ public class DynamicRouterEndpointConfigurer extends
PropertyConfigurerSupport i
case "aggregationStrategyMethodAllowNull":
target.getConfiguration().setAggregationStrategyMethodAllowNull(property(camelContext,
boolean.class, value)); return true;
case "aggregationstrategymethodname":
case "aggregationStrategyMethodName":
target.getConfiguration().setAggregationStrategyMethodName(property(camelContext,
java.lang.String.class, value)); return true;
+ case "allowedschemes":
+ case "allowedSchemes":
target.getConfiguration().setAllowedSchemes(property(camelContext,
java.lang.String.class, value)); return true;
case "cachesize":
case "cacheSize":
target.getConfiguration().setCacheSize(property(camelContext, int.class,
value)); return true;
case "executorservice":
@@ -75,6 +77,8 @@ public class DynamicRouterEndpointConfigurer extends
PropertyConfigurerSupport i
case "aggregationStrategyMethodAllowNull": return boolean.class;
case "aggregationstrategymethodname":
case "aggregationStrategyMethodName": return java.lang.String.class;
+ case "allowedschemes":
+ case "allowedSchemes": return java.lang.String.class;
case "cachesize":
case "cacheSize": return int.class;
case "executorservice":
@@ -120,6 +124,8 @@ public class DynamicRouterEndpointConfigurer extends
PropertyConfigurerSupport i
case "aggregationStrategyMethodAllowNull": return
target.getConfiguration().isAggregationStrategyMethodAllowNull();
case "aggregationstrategymethodname":
case "aggregationStrategyMethodName": return
target.getConfiguration().getAggregationStrategyMethodName();
+ case "allowedschemes":
+ case "allowedSchemes": return
target.getConfiguration().getAllowedSchemes();
case "cachesize":
case "cacheSize": return target.getConfiguration().getCacheSize();
case "executorservice":
diff --git
a/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterEndpointUriFactory.java
b/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterEndpointUriFactory.java
index a8542b69adce..908eea5e9efd 100644
---
a/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterEndpointUriFactory.java
+++
b/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterEndpointUriFactory.java
@@ -24,11 +24,12 @@ public class DynamicRouterEndpointUriFactory extends
org.apache.camel.support.co
private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES;
private static final Map<String, String> MULTI_VALUE_PREFIXES;
static {
- Set<String> props = new HashSet<>(21);
+ Set<String> props = new HashSet<>(22);
props.add("aggregationStrategy");
props.add("aggregationStrategyBean");
props.add("aggregationStrategyMethodAllowNull");
props.add("aggregationStrategyMethodName");
+ props.add("allowedSchemes");
props.add("cacheSize");
props.add("channel");
props.add("executorService");
diff --git
a/components/camel-dynamic-router/src/generated/resources/META-INF/org/apache/camel/component/dynamicrouter/control/dynamic-router-control.json
b/components/camel-dynamic-router/src/generated/resources/META-INF/org/apache/camel/component/dynamicrouter/control/dynamic-router-control.json
index 00e34e99babf..a27bcb45d41a 100644
---
a/components/camel-dynamic-router/src/generated/resources/META-INF/org/apache/camel/component/dynamicrouter/control/dynamic-router-control.json
+++
b/components/camel-dynamic-router/src/generated/resources/META-INF/org/apache/camel/component/dynamicrouter/control/dynamic-router-control.json
@@ -46,6 +46,7 @@
"predicateBean": { "index": 5, "kind": "parameter", "displayName":
"Predicate Bean", "group": "control", "label": "control", "required": false,
"type": "object", "javaType": "org.apache.camel.Predicate", "deprecated":
false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
"configurationField": "configuration", "description": "A Predicate instance in
the registry." },
"priority": { "index": 6, "kind": "parameter", "displayName": "Priority",
"group": "control", "label": "control", "required": false, "type": "integer",
"javaType": "java.lang.Integer", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
"configurationField": "configuration", "description": "The subscription
priority." },
"subscribeChannel": { "index": 7, "kind": "parameter", "displayName":
"Subscribe Channel", "group": "control", "label": "control", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
"configurationField": "configuration", "description": "The channel to
subscribe to" },
- "subscriptionId": { "index": 8, "kind": "parameter", "displayName":
"Subscription Id", "group": "control", "label": "control", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
"configurationField": "configuration", "description": "The subscription ID; if
unspecified, one will be assigned and returned." }
+ "subscriptionId": { "index": 8, "kind": "parameter", "displayName":
"Subscription Id", "group": "control", "label": "control", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
"configurationField": "configuration", "description": "The subscription ID; if
unspecified, one will be assigned and returned." },
+ "allowPredicateFromMessage": { "index": 9, "kind": "parameter",
"displayName": "Allow Predicate From Message", "group": "security", "label":
"control,security", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"security": "insecure:dev", "insecureValue": "true", "defaultValue": false,
"configurationClass":
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
"configurationField": "c [...]
}
}
diff --git
a/components/camel-dynamic-router/src/generated/resources/META-INF/org/apache/camel/component/dynamicrouter/routing/dynamic-router.json
b/components/camel-dynamic-router/src/generated/resources/META-INF/org/apache/camel/component/dynamicrouter/routing/dynamic-router.json
index c360b9c180ed..2a056e382f8d 100644
---
a/components/camel-dynamic-router/src/generated/resources/META-INF/org/apache/camel/component/dynamicrouter/routing/dynamic-router.json
+++
b/components/camel-dynamic-router/src/generated/resources/META-INF/org/apache/camel/component/dynamicrouter/routing/dynamic-router.json
@@ -48,6 +48,7 @@
"synchronous": { "index": 17, "kind": "parameter", "displayName":
"Synchronous", "group": "common", "label": "common", "required": false, "type":
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.dynamicrouter.routing.DynamicRouterConfiguration",
"configurationField": "configuration", "description": "Sets whether synchronous
processing should be strictly used. When enabl [...]
"timeout": { "index": 18, "kind": "parameter", "displayName": "Timeout",
"group": "common", "label": "common", "required": false, "type": "integer",
"javaType": "long", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": -1, "configurationClass":
"org.apache.camel.component.dynamicrouter.routing.DynamicRouterConfiguration",
"configurationField": "configuration", "description": "Sets a total timeout
specified in millis, when using parallel processing. If the Dyna [...]
"warnDroppedMessage": { "index": 19, "kind": "parameter", "displayName":
"Warn Dropped Message", "group": "common", "label": "common", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false,
"configurationClass":
"org.apache.camel.component.dynamicrouter.routing.DynamicRouterConfiguration",
"configurationField": "configuration", "description": "Flag to log a warning if
no predicates match for an exc [...]
- "lazyStartProducer": { "index": 20, "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 [...]
+ "lazyStartProducer": { "index": 20, "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 [...]
+ "allowedSchemes": { "index": 21, "kind": "parameter", "displayName":
"Allowed Schemes", "group": "security", "label": "advanced,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.dynamicrouter.routing.DynamicRouterConfiguration",
"configurationField": "configuration", "description": "Sets an optional
comma-separated allow-list of component schemes t [...]
}
}
diff --git
a/components/camel-dynamic-router/src/main/docs/dynamic-router-component.adoc
b/components/camel-dynamic-router/src/main/docs/dynamic-router-component.adoc
index 826097176c52..1aa7f2b2d187 100644
---
a/components/camel-dynamic-router/src/main/docs/dynamic-router-component.adoc
+++
b/components/camel-dynamic-router/src/main/docs/dynamic-router-component.adoc
@@ -318,7 +318,18 @@ producerTemplate.sendBody("kafka://subscriptions",
mapper.writeValueAsString(con
----
In another module, additional routing will serve as a bridge to get the
message from Kafka to the control channel of the
-Dynamic Router:
+Dynamic Router. The subscriptions above carry their predicate in the control
message, so the bridge has to opt in with
+`allowPredicateFromMessage=true`.
+
+[WARNING]
+====
+`allowPredicateFromMessage` lets whoever puts a message on the bridged
transport choose both the expression language and
+the expression that the Dynamic Router compiles and evaluates, which means it
decides what runs inside the Camel
+process. Only enable it when the transport that feeds the control channel is
trusted. When it is not, have the
+participants subscribe with `predicateBean`, which selects a `Predicate` that
the route author bound in the registry,
+and leave the flag at its default of `false`. See
+xref:dynamic-router-control-component.adoc[the control component
documentation] for the alternatives.
+====
.Bridge from Kafka to the Dynamic Router control channel
@@ -330,7 +341,7 @@ Java::
----
from("kafka:subscriptions")
.unmarshal().json(DynamicRouterControlMessage.class)
- .to("dynamic-router-control:subscribe");
+ .to("dynamic-router-control:subscribe?allowPredicateFromMessage=true");
----
XML::
@@ -342,7 +353,7 @@ XML::
<unmarshal>
<json library="Jackson"
unmarshalType="org.apache.camel.component.dynamicrouter.control.DynamicRouterControlMessage"/>
</unmarshal>
- <to uri="dynamic-router-control:subscribe"/>
+ <to uri="dynamic-router-control:subscribe?allowPredicateFromMessage=true"/>
</route>
----
@@ -360,6 +371,8 @@ YAML::
unmarshalType:
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlMessage
- to:
uri: dynamic-router-control:subscribe
+ parameters:
+ allowPredicateFromMessage: true
----
====
diff --git
a/components/camel-dynamic-router/src/main/docs/dynamic-router-control-component.adoc
b/components/camel-dynamic-router/src/main/docs/dynamic-router-control-component.adoc
index 6f7ee90aae83..ec0eeb535aa8 100644
---
a/components/camel-dynamic-router/src/main/docs/dynamic-router-control-component.adoc
+++
b/components/camel-dynamic-router/src/main/docs/dynamic-router-control-component.adoc
@@ -30,6 +30,37 @@ include::partial$component-endpoint-options.adoc[]
include::partial$component-endpoint-headers.adoc[]
// component options: END
+== Supplying the subscription predicate
+
+A subscription needs a predicate that decides which exchanges the participant
receives. There are three ways to supply
+one, and they differ in who chooses it:
+
+* `predicateBean` -- the name of a `Predicate` bound in the registry. The
route author decides which predicates exist,
+and the control message only selects one of them by name.
+* `predicate` and `expressionLanguage` as control endpoint URI parameters --
the route author writes the expression, and
+every subscription made through that endpoint uses it.
+* `predicate` and `expressionLanguage` carried in the control message, in its
body or its headers -- the sender of the
+control message chooses both the expression language and the expression.
+
+The third form is disabled by default. The predicate is compiled and then
evaluated against every exchange on the
+channel, so letting the control message choose both the language and the
expression means the sender of that message
+decides what runs inside the Camel process. Set
`allowPredicateFromMessage=true` on the control endpoint to enable it,
+and only do so when control messages can only come from a trusted source:
+
+[source,java]
+----
+from("kafka:subscriptions")
+ .unmarshal().json(DynamicRouterControlMessage.class)
+ .to("dynamic-router-control:subscribe?allowPredicateFromMessage=true");
+----
+
+When `allowPredicateFromMessage` is `false`, which is the default, a control
message that supplies a `predicate` or an
+`expressionLanguage` is rejected with an `IllegalArgumentException`. Prefer
`predicateBean`, or configure the predicate
+on the endpoint, when control messages arrive from outside the application.
+
+Subscription parameters that the control message does not carry fall back to
the values configured on the control
+endpoint, so a participant can send only the parameters that identify it and
let the endpoint supply the rest.
+
== Subscribing
Subscribing can be achieved by using query parameters in the control endpoint
URI, or by sending a
diff --git
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlChannelSendDynamicAware.java
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlChannelSendDynamicAware.java
index 13662241b271..3e5d0a79731d 100644
---
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlChannelSendDynamicAware.java
+++
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlChannelSendDynamicAware.java
@@ -28,6 +28,7 @@ import org.apache.camel.spi.annotations.SendDynamic;
import org.apache.camel.support.component.SendDynamicAwareSupport;
import org.apache.camel.util.URISupport;
+import static
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.ALLOW_PREDICATE_FROM_MESSAGE_PROPERTY;
import static
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.COMPONENT_SCHEME_CONTROL;
import static
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.CONTROL_ACTION_PROPERTY;
@@ -95,6 +96,14 @@ public class DynamicRouterControlChannelSendDynamicAware
extends SendDynamicAwar
String uri = entry.getUri();
if (DynamicRouterControlConstants.SHOULD_OPTIMIZE.test(uri)) {
optimizedUri = URISupport.stripQuery(uri);
+ // The subscription parameters are carried as headers, but
"allowPredicateFromMessage" decides whether a
+ // control message may supply the predicate at all, so it has to
stay on the endpoint, where only the
+ // route author can set it. Stripping it here would make the
endpoint fall back to the default of false.
+ Object allowPredicateFromMessage =
entry.getProperties().get(ALLOW_PREDICATE_FROM_MESSAGE_PROPERTY);
+ if (allowPredicateFromMessage != null) {
+ optimizedUri = optimizedUri + "?" +
ALLOW_PREDICATE_FROM_MESSAGE_PROPERTY + "="
+ + allowPredicateFromMessage;
+ }
}
return optimizedUri;
}
diff --git
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlConfiguration.java
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlConfiguration.java
index 27a02fbe1684..875820f8d7be 100644
---
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlConfiguration.java
+++
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlConfiguration.java
@@ -80,6 +80,19 @@ public class DynamicRouterControlConfiguration {
@UriParam(label = "control", defaultValue = "simple", description = "The
subscription predicate language.")
private String expressionLanguage = "simple";
+ /**
+ * Whether the subscription predicate, and the language used to compile
it, may be taken from the incoming control
+ * message.
+ */
+ @UriParam(label = "control,security", defaultValue = "false", security =
"insecure:dev", insecureValue = "true",
+ description = "Whether the subscription predicate, and the
language used to compile it, may be taken"
+ + " from the incoming control message. When
disabled, the predicate and expressionLanguage"
+ + " configured on this endpoint are used instead.
Enabling this lets the sender of a"
+ + " control message choose both the expression
language and the expression that the"
+ + " Dynamic Router compiles into a live predicate,
so only enable it when control messages"
+ + " come from a trusted source.")
+ private boolean allowPredicateFromMessage;
+
/**
* The control action (subscribe or unsubscribe).
*
@@ -237,6 +250,26 @@ public class DynamicRouterControlConfiguration {
this.expressionLanguage = expressionLanguage;
}
+ /**
+ * Whether the subscription predicate, and the language used to compile
it, may be taken from the incoming control
+ * message.
+ *
+ * @return true if the control message may supply the predicate
+ */
+ public boolean isAllowPredicateFromMessage() {
+ return allowPredicateFromMessage;
+ }
+
+ /**
+ * Whether the subscription predicate, and the language used to compile
it, may be taken from the incoming control
+ * message.
+ *
+ * @param allowPredicateFromMessage true if the control message may supply
the predicate
+ */
+ public void setAllowPredicateFromMessage(final boolean
allowPredicateFromMessage) {
+ this.allowPredicateFromMessage = allowPredicateFromMessage;
+ }
+
public DynamicRouterControlConfiguration() {
// Default constructor is empty because we need an instance, and
// then things can be populated through mutator methods.
diff --git
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlConstants.java
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlConstants.java
index c5516973453d..196cb80f8174 100644
---
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlConstants.java
+++
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlConstants.java
@@ -124,6 +124,15 @@ public final class DynamicRouterControlConstants {
*/
public static final String ERROR_NO_PREDICATE_BEAN_FOUND = "Predicate bean
could not be found";
+ /**
+ * Error when a control message supplies a predicate expression, or the
language to compile it with, but the control
+ * endpoint does not allow those to be taken from a control message.
+ */
+ public static final String ERROR_PREDICATE_FROM_MESSAGE_NOT_ALLOWED
+ = "The subscription predicate expression, and the language used to
compile it, cannot be taken from a "
+ + "control message. Configure 'predicate' and
'expressionLanguage' on the dynamic-router-control "
+ + "endpoint, or set 'allowPredicateFromMessage=true' to let the
control message supply them.";
+
/**
* The configuration property for the control channel action.
*/
@@ -164,6 +173,13 @@ public final class DynamicRouterControlConstants {
*/
public static final String EXPRESSION_LANGUAGE_PROPERTY =
"expressionLanguage";
+ /**
+ * The configuration property that controls whether the predicate may be
taken from a control message. Unlike the
+ * other properties, this one is deliberately absent from {@link
#URI_PARAMS_TO_HEADER_NAMES}: it must stay on the
+ * endpoint, where only the route author can set it, and must never be
settable from a message.
+ */
+ public static final String ALLOW_PREDICATE_FROM_MESSAGE_PROPERTY =
"allowPredicateFromMessage";
+
/**
* Header name for the control action.
*/
diff --git
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlProducer.java
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlProducer.java
index 178cd0903390..658969e6af3d 100644
---
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlProducer.java
+++
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlProducer.java
@@ -42,6 +42,7 @@ import static
org.apache.camel.component.dynamicrouter.control.DynamicRouterCont
import static
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.CONTROL_SUBSCRIBE_CHANNEL;
import static
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.CONTROL_SUBSCRIPTION_ID;
import static
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.ERROR_NO_PREDICATE_BEAN_FOUND;
+import static
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.ERROR_PREDICATE_FROM_MESSAGE_NOT_ALLOWED;
import static
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.SIMPLE_LANGUAGE;
/**
@@ -74,29 +75,57 @@ public class DynamicRouterControlProducer extends
HeaderSelectorProducer {
this.configuration = configuration;
}
+ /**
+ * Returns the value to use for a predicate parameter that a control
message may try to supply. Unless the endpoint
+ * explicitly allows the predicate to come from the message, a
message-supplied value is rejected, and the value
+ * configured on the endpoint is used instead. Without this, the sender of
a control message would choose both the
+ * expression language and the expression that the Dynamic Router compiles
into a live predicate.
+ *
+ * @param allowFromMessage whether the endpoint allows the predicate to
come from the control message
+ * @param messageValue the value found in the control message, if any
+ * @param configValue the value configured on the endpoint
+ * @return the value to use
+ */
+ static String predicateValueToUse(
+ final boolean allowFromMessage, final String messageValue, final
String configValue) {
+ if (allowFromMessage) {
+ return messageValue;
+ }
+ if (ObjectHelper.isNotEmpty(messageValue)) {
+ throw new
IllegalArgumentException(ERROR_PREDICATE_FROM_MESSAGE_NOT_ALLOWED);
+ }
+ return configValue;
+ }
+
/**
* Create a filter from parameters in the message body.
*
* @param dynamicRouterControlService the {@link
DynamicRouterControlService}
+ * @param configuration the configuration for the Dynamic
Router Control
* @param message the message, where the body
contains a control message
* @param update whether to update an existing
filter (true) or add a new one (false)
* @return the ID of the added filter
*/
static String subscribeFromMessage(
final DynamicRouterControlService dynamicRouterControlService,
+ final DynamicRouterControlConfiguration configuration,
final Message message, final boolean update) {
DynamicRouterControlMessage messageBody =
message.getBody(DynamicRouterControlMessage.class);
String subscriptionId = messageBody.getSubscriptionId();
String subscribeChannel = messageBody.getSubscribeChannel();
String destinationUri = messageBody.getDestinationUri();
String priority = String.valueOf(messageBody.getPriority());
- String predicate = messageBody.getPredicate();
String predicateBean = messageBody.getPredicateBean();
- String expressionLanguage = messageBody.getExpressionLanguage();
if (ObjectHelper.isNotEmpty(predicateBean)) {
return
dynamicRouterControlService.subscribeWithPredicateBean(subscribeChannel,
subscriptionId,
destinationUri, Integer.parseInt(priority), predicateBean,
update);
- } else if (ObjectHelper.isNotEmpty(predicate) &&
ObjectHelper.isNotEmpty(expressionLanguage)) {
+ }
+ boolean allowFromMessage = configuration.isAllowPredicateFromMessage();
+ String predicate = predicateValueToUse(allowFromMessage,
messageBody.getPredicate(),
+ configuration.getPredicate());
+ String expressionLanguage = predicateValueToUse(allowFromMessage,
messageBody.getExpressionLanguage(),
+ configuration.getExpressionLanguage());
+ if (ObjectHelper.isNotEmpty(predicate) &&
ObjectHelper.isNotEmpty(expressionLanguage)) {
return
dynamicRouterControlService.subscribeWithPredicateExpression(subscribeChannel,
subscriptionId,
destinationUri, Integer.parseInt(priority), predicate,
expressionLanguage, update);
} else {
@@ -105,29 +134,38 @@ public class DynamicRouterControlProducer extends
HeaderSelectorProducer {
}
/**
- * Create a filter from parameters in message headers.
+ * Create a filter from parameters in message headers, falling back to the
values configured on the endpoint for any
+ * parameter that the message does not carry.
*
* @param dynamicRouterControlService the {@link
DynamicRouterControlService}
+ * @param configuration the configuration for the Dynamic
Router Control
* @param message the message, where the headers
contain subscription params
* @param update whether to update an existing
filter (true) or add a new one (false)
* @return the ID of the added filter
*/
static String subscribeFromHeaders(
final DynamicRouterControlService dynamicRouterControlService,
+ final DynamicRouterControlConfiguration configuration,
final Message message, final boolean update) {
Map<String, Object> headers = message.getHeaders();
- String subscriptionId = (String) headers.get(CONTROL_SUBSCRIPTION_ID);
- String subscribeChannel = (String)
headers.get(CONTROL_SUBSCRIBE_CHANNEL);
- String destinationUri = (String) headers.get(CONTROL_DESTINATION_URI);
- String priority = String.valueOf(headers.get(CONTROL_PRIORITY));
- String predicate = (String) headers.get(CONTROL_PREDICATE);
+ String subscriptionId = (String)
headers.getOrDefault(CONTROL_SUBSCRIPTION_ID,
configuration.getSubscriptionId());
+ String subscribeChannel
+ = (String) headers.getOrDefault(CONTROL_SUBSCRIBE_CHANNEL,
configuration.getSubscribeChannel());
+ String destinationUri = (String)
headers.getOrDefault(CONTROL_DESTINATION_URI,
configuration.getDestinationUri());
+ String priority =
String.valueOf(headers.getOrDefault(CONTROL_PRIORITY,
configuration.getPriority()));
String predicateBean = (String) headers.get(CONTROL_PREDICATE_BEAN);
- String expressionLanguage = Optional.ofNullable((String)
headers.get(CONTROL_EXPRESSION_LANGUAGE))
- .orElse(SIMPLE_LANGUAGE);
if (ObjectHelper.isNotEmpty(predicateBean)) {
return
dynamicRouterControlService.subscribeWithPredicateBean(subscribeChannel,
subscriptionId,
destinationUri, Integer.parseInt(priority), predicateBean,
update);
- } else if (ObjectHelper.isNotEmpty(predicate) &&
ObjectHelper.isNotEmpty(expressionLanguage)) {
+ }
+ boolean allowFromMessage = configuration.isAllowPredicateFromMessage();
+ String predicate = predicateValueToUse(allowFromMessage, (String)
headers.get(CONTROL_PREDICATE),
+ configuration.getPredicate());
+ String expressionLanguage = Optional
+ .ofNullable(predicateValueToUse(allowFromMessage, (String)
headers.get(CONTROL_EXPRESSION_LANGUAGE),
+ configuration.getExpressionLanguage()))
+ .orElse(SIMPLE_LANGUAGE);
+ if (ObjectHelper.isNotEmpty(predicate) &&
ObjectHelper.isNotEmpty(expressionLanguage)) {
return
dynamicRouterControlService.subscribeWithPredicateExpression(subscribeChannel,
subscriptionId,
destinationUri, Integer.parseInt(priority), predicate,
expressionLanguage, update);
} else {
@@ -147,9 +185,9 @@ public class DynamicRouterControlProducer extends
HeaderSelectorProducer {
public void performSubscribe(final Message message, AsyncCallback
callback) {
String filterId;
if (message.getBody() instanceof DynamicRouterControlMessage) {
- filterId = subscribeFromMessage(dynamicRouterControlService,
message, false);
+ filterId = subscribeFromMessage(dynamicRouterControlService,
configuration, message, false);
} else {
- filterId = subscribeFromHeaders(dynamicRouterControlService,
message, false);
+ filterId = subscribeFromHeaders(dynamicRouterControlService,
configuration, message, false);
}
message.setBody(filterId);
callback.done(false);
@@ -190,9 +228,9 @@ public class DynamicRouterControlProducer extends
HeaderSelectorProducer {
public void performUpdate(final Message message, AsyncCallback callback) {
String filterId;
if (message.getBody() instanceof DynamicRouterControlMessage) {
- filterId = subscribeFromMessage(dynamicRouterControlService,
message, true);
+ filterId = subscribeFromMessage(dynamicRouterControlService,
configuration, message, true);
} else {
- filterId = subscribeFromHeaders(dynamicRouterControlService,
message, true);
+ filterId = subscribeFromHeaders(dynamicRouterControlService,
configuration, message, true);
}
message.setBody(filterId);
callback.done(false);
diff --git
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterConfiguration.java
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterConfiguration.java
index e8d67b232cb8..a2b0bd203d29 100644
---
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterConfiguration.java
+++
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterConfiguration.java
@@ -103,6 +103,16 @@ public class DynamicRouterConfiguration {
@UriParam(label = "common", defaultValue = "false")
private boolean ignoreInvalidEndpoints;
+ /**
+ * An optional comma-separated allow-list of component schemes that a
subscription destination may resolve to.
+ */
+ @UriParam(label = "advanced,security",
+ description = "Sets an optional comma-separated allow-list of
component schemes that a subscription"
+ + " destination may resolve to (e.g. http,https).
When set, a subscription whose"
+ + " destination URI uses a scheme that is not in
the list is rejected. By default (unset)"
+ + " any scheme is allowed.")
+ private String allowedSchemes;
+
/**
* If enabled, then Camel will process replies out-of-order (e.g., in the
order they come back). If disabled, Camel
* will process replies in the same order as defined by the multicast.
@@ -315,6 +325,24 @@ public class DynamicRouterConfiguration {
this.ignoreInvalidEndpoints = ignoreInvalidEndpoints;
}
+ /**
+ * An optional comma-separated allow-list of component schemes that a
subscription destination may resolve to.
+ *
+ * @return the allowed schemes, or null if any scheme is allowed
+ */
+ public String getAllowedSchemes() {
+ return allowedSchemes;
+ }
+
+ /**
+ * An optional comma-separated allow-list of component schemes that a
subscription destination may resolve to.
+ *
+ * @param allowedSchemes the allowed schemes
+ */
+ public void setAllowedSchemes(String allowedSchemes) {
+ this.allowedSchemes = allowedSchemes;
+ }
+
/**
* Gets whether parallel processing is enabled.
*
diff --git
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelper.java
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelper.java
index 414a16814e07..31bb6e2de5a1 100644
---
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelper.java
+++
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelper.java
@@ -124,6 +124,7 @@ public final class DynamicRouterRecipientListHelper {
recipientList.setShareUnitOfWork(cfg.isShareUnitOfWork());
recipientList.setStopOnException(cfg.isStopOnException());
recipientList.setIgnoreInvalidEndpoints(cfg.isIgnoreInvalidEndpoints());
+ recipientList.setAllowedSchemes(cfg.getAllowedSchemes());
recipientList.setCacheSize(cfg.getCacheSize());
if (cfg.getOnPrepare() != null) {
recipientList.setOnPrepare(mandatoryLookup(camelContext,
cfg.getOnPrepare(), Processor.class));
diff --git
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlChannelSendDynamicAwareTest.java
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlChannelSendDynamicAwareTest.java
index 634d942297b5..deb7e48b812d 100644
---
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlChannelSendDynamicAwareTest.java
+++
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlChannelSendDynamicAwareTest.java
@@ -68,6 +68,17 @@ class DynamicRouterControlChannelSendDynamicAwareTest {
}
}
+ @Test
+ void resolveStaticUriRetainsAllowPredicateFromMessage() throws Exception {
+ String originalUri =
"dynamic-router-control:subscribe?subscriptionId=testSub1&allowPredicateFromMessage=true";
+ String uri =
"dynamic-router-control://subscribe?subscriptionId=testSub1&allowPredicateFromMessage=true";
+ try (DynamicRouterControlChannelSendDynamicAware testSubject = new
DynamicRouterControlChannelSendDynamicAware()) {
+ SendDynamicAware.DynamicAwareEntry entry =
testSubject.prepare(exchange, uri, originalUri);
+ String result = testSubject.resolveStaticUri(exchange, entry);
+
assertEquals("dynamic-router-control://subscribe?allowPredicateFromMessage=true",
result);
+ }
+ }
+
@Test
void resolveStaticUriShouldNotOptimize() throws Exception {
String originalUri =
"dynamic-router-ctrl:subscribe?subscriptionId=testSub1";
diff --git
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlProducerTest.java
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlProducerTest.java
index f86a48d55575..fad05c24c670 100644
---
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlProducerTest.java
+++
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlProducerTest.java
@@ -48,6 +48,7 @@ import static
org.apache.camel.component.dynamicrouter.control.DynamicRouterCont
import static
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.CONTROL_PRIORITY;
import static
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.CONTROL_SUBSCRIBE_CHANNEL;
import static
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.CONTROL_SUBSCRIPTION_ID;
+import static
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.ERROR_PREDICATE_FROM_MESSAGE_NOT_ALLOWED;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.when;
@@ -89,6 +90,7 @@ class DynamicRouterControlProducerTest {
@Test
void performSubscribeAction() {
+ when(configuration.isAllowPredicateFromMessage()).thenReturn(true);
String subscribeChannel = "testChannel";
Map<String, Object> headers = Map.of(
CONTROL_ACTION_HEADER, CONTROL_ACTION_SUBSCRIBE,
@@ -107,6 +109,7 @@ class DynamicRouterControlProducerTest {
@Test
void performSubscribeActionWithEmptyExpressionLanguage() {
+ when(configuration.isAllowPredicateFromMessage()).thenReturn(true);
String subscribeChannel = "testChannel";
Map<String, Object> headers = Map.of(
CONTROL_ACTION_HEADER, CONTROL_ACTION_SUBSCRIBE,
@@ -155,6 +158,7 @@ class DynamicRouterControlProducerTest {
@Test
void performUpdateAction() {
+ when(configuration.isAllowPredicateFromMessage()).thenReturn(true);
// First, perform initial subscription
String subscribeChannel = "testChannel";
Map<String, Object> headers = Map.of(
@@ -229,6 +233,7 @@ class DynamicRouterControlProducerTest {
@Test
void performSubscribeActionWithMessageInBody() {
+ when(configuration.isAllowPredicateFromMessage()).thenReturn(true);
String subscribeChannel = "testChannel";
DynamicRouterControlMessage subMsg =
DynamicRouterControlMessage.Builder.newBuilder()
.subscribeChannel(subscribeChannel)
@@ -249,6 +254,7 @@ class DynamicRouterControlProducerTest {
@Test
void performSubscribeActionWithMessageInBodyWithEmptyExpressionLanguage() {
+ when(configuration.isAllowPredicateFromMessage()).thenReturn(true);
String subscribeChannel = "testChannel";
DynamicRouterControlMessage subMsg =
DynamicRouterControlMessage.Builder.newBuilder()
.subscribeChannel(subscribeChannel)
@@ -266,6 +272,7 @@ class DynamicRouterControlProducerTest {
@Test
void performSubscribeActionWithMessageInBodyWithEmptyExpression() {
+ when(configuration.isAllowPredicateFromMessage()).thenReturn(true);
String subscribeChannel = "testChannel";
DynamicRouterControlMessage subMsg =
DynamicRouterControlMessage.Builder.newBuilder()
.subscribeChannel(subscribeChannel)
@@ -302,6 +309,7 @@ class DynamicRouterControlProducerTest {
@Test
void performUpdateActionWithMessageInBody() {
+ when(configuration.isAllowPredicateFromMessage()).thenReturn(true);
String subscribeChannel = "testChannel";
DynamicRouterControlMessage subMsg =
DynamicRouterControlMessage.Builder.newBuilder()
.subscribeChannel(subscribeChannel)
@@ -320,6 +328,124 @@ class DynamicRouterControlProducerTest {
subscribeChannel, "testId", "mock://test", 10, "true",
"simple", true);
}
+ @Test
+ void performSubscribeActionRejectsPredicateFromHeadersByDefault() {
+ Map<String, Object> headers = Map.of(
+ CONTROL_ACTION_HEADER, CONTROL_ACTION_SUBSCRIBE,
+ CONTROL_SUBSCRIBE_CHANNEL, "testChannel",
+ CONTROL_SUBSCRIPTION_ID, "testId",
+ CONTROL_DESTINATION_URI, "mock://test",
+ CONTROL_PREDICATE, "true",
+ CONTROL_EXPRESSION_LANGUAGE, "simple",
+ CONTROL_PRIORITY, 10);
+ when(message.getHeaders()).thenReturn(headers);
+ Exception ex = assertThrows(IllegalArgumentException.class, () ->
producer.performSubscribe(message, callback));
+ assertEquals(ERROR_PREDICATE_FROM_MESSAGE_NOT_ALLOWED,
ex.getMessage());
+ Mockito.verifyNoInteractions(controlService);
+ }
+
+ @Test
+ void performSubscribeActionRejectsExpressionLanguageFromHeadersByDefault()
{
+ Map<String, Object> headers = Map.of(
+ CONTROL_ACTION_HEADER, CONTROL_ACTION_SUBSCRIBE,
+ CONTROL_SUBSCRIBE_CHANNEL, "testChannel",
+ CONTROL_SUBSCRIPTION_ID, "testId",
+ CONTROL_DESTINATION_URI, "mock://test",
+ CONTROL_EXPRESSION_LANGUAGE, "simple",
+ CONTROL_PRIORITY, 10);
+ when(message.getHeaders()).thenReturn(headers);
+ Exception ex = assertThrows(IllegalArgumentException.class, () ->
producer.performSubscribe(message, callback));
+ assertEquals(ERROR_PREDICATE_FROM_MESSAGE_NOT_ALLOWED,
ex.getMessage());
+ Mockito.verifyNoInteractions(controlService);
+ }
+
+ @Test
+ void performSubscribeActionRejectsPredicateFromMessageBodyByDefault() {
+ DynamicRouterControlMessage subMsg =
DynamicRouterControlMessage.Builder.newBuilder()
+ .subscribeChannel("testChannel")
+ .subscriptionId("testId")
+ .destinationUri("mock://test")
+ .priority(10)
+ .predicate("true")
+ .expressionLanguage("simple")
+ .build();
+ when(message.getBody()).thenReturn(subMsg);
+
when(message.getBody(DynamicRouterControlMessage.class)).thenReturn(subMsg);
+ Exception ex = assertThrows(IllegalArgumentException.class, () ->
producer.performSubscribe(message, callback));
+ assertEquals(ERROR_PREDICATE_FROM_MESSAGE_NOT_ALLOWED,
ex.getMessage());
+ Mockito.verifyNoInteractions(controlService);
+ }
+
+ @Test
+ void performUpdateActionRejectsPredicateFromMessageBodyByDefault() {
+ DynamicRouterControlMessage subMsg =
DynamicRouterControlMessage.Builder.newBuilder()
+ .subscribeChannel("testChannel")
+ .subscriptionId("testId")
+ .destinationUri("mock://test")
+ .priority(10)
+ .predicate("true")
+ .expressionLanguage("simple")
+ .build();
+ when(message.getBody()).thenReturn(subMsg);
+
when(message.getBody(DynamicRouterControlMessage.class)).thenReturn(subMsg);
+ Exception ex = assertThrows(IllegalArgumentException.class, () ->
producer.performUpdate(message, callback));
+ assertEquals(ERROR_PREDICATE_FROM_MESSAGE_NOT_ALLOWED,
ex.getMessage());
+ Mockito.verifyNoInteractions(controlService);
+ }
+
+ @Test
+ void performSubscribeActionUsesEndpointPredicateWhenMessageSuppliesNone() {
+ String subscribeChannel = "testChannel";
+ Map<String, Object> headers = Map.of(
+ CONTROL_ACTION_HEADER, CONTROL_ACTION_SUBSCRIBE,
+ CONTROL_SUBSCRIBE_CHANNEL, subscribeChannel,
+ CONTROL_SUBSCRIPTION_ID, "testId",
+ CONTROL_DESTINATION_URI, "mock://test",
+ CONTROL_PRIORITY, 10);
+ when(message.getHeaders()).thenReturn(headers);
+ when(configuration.getPredicate()).thenReturn("true");
+ when(configuration.getExpressionLanguage()).thenReturn("simple");
+ Mockito.doNothing().when(callback).done(false);
+ producer.performSubscribe(message, callback);
+ Mockito.verify(controlService,
Mockito.times(1)).subscribeWithPredicateExpression(
+ subscribeChannel, "testId", "mock://test", 10, "true",
"simple", false);
+ }
+
+ @Test
+ void
performSubscribeActionFallsBackToEndpointParametersWhenHeadersAreAbsent() {
+ Map<String, Object> headers = Map.of(CONTROL_ACTION_HEADER,
CONTROL_ACTION_SUBSCRIBE);
+ when(message.getHeaders()).thenReturn(headers);
+ when(configuration.getSubscribeChannel()).thenReturn("testChannel");
+ when(configuration.getSubscriptionId()).thenReturn("testId");
+ when(configuration.getDestinationUri()).thenReturn("mock://test");
+ when(configuration.getPriority()).thenReturn(10);
+ when(configuration.getPredicate()).thenReturn("true");
+ when(configuration.getExpressionLanguage()).thenReturn("simple");
+ Mockito.doNothing().when(callback).done(false);
+ producer.performSubscribe(message, callback);
+ Mockito.verify(controlService,
Mockito.times(1)).subscribeWithPredicateExpression(
+ "testChannel", "testId", "mock://test", 10, "true", "simple",
false);
+ }
+
+ @Test
+ void performSubscribeActionWithPredicateBeanIsUnaffectedByTheGate() {
+ String subscribeChannel = "testChannel";
+ Map<String, Object> headers = Map.of(
+ CONTROL_ACTION_HEADER, CONTROL_ACTION_SUBSCRIBE,
+ CONTROL_SUBSCRIBE_CHANNEL, subscribeChannel,
+ CONTROL_SUBSCRIPTION_ID, "testId",
+ CONTROL_DESTINATION_URI, "mock://test",
+ CONTROL_PREDICATE, "true",
+ CONTROL_EXPRESSION_LANGUAGE, "simple",
+ CONTROL_PRIORITY, 10,
+ CONTROL_PREDICATE_BEAN, "testPredicate");
+ when(message.getHeaders()).thenReturn(headers);
+ Mockito.doNothing().when(callback).done(false);
+ producer.performSubscribe(message, callback);
+ Mockito.verify(controlService,
Mockito.times(1)).subscribeWithPredicateBean(
+ subscribeChannel, "testId", "mock://test", 10,
"testPredicate", false);
+ }
+
@Test
void testPerformListAction() {
String subscribeChannel = "testChannel";
diff --git
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterBasicSynchronousIT.java
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterBasicSynchronousIT.java
index da957ccdc46c..8b21c21b624d 100644
---
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterBasicSynchronousIT.java
+++
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterBasicSynchronousIT.java
@@ -176,7 +176,7 @@ public class DynamicRouterBasicSynchronousIT {
.expressionLanguage("spel")
.build();
- template.sendBody("dynamic-router-control:subscribe", controlMessage);
+
template.sendBody("dynamic-router-control:subscribe?allowPredicateFromMessage=true",
controlMessage);
// Trigger events to subscribers
template.sendBodyAndHeader("direct:start", "testMessage", "test",
"testValue");
@@ -239,7 +239,7 @@ public class DynamicRouterBasicSynchronousIT {
.predicate("#{headers.test == 'testValue1'}")
.expressionLanguage("spel")
.build();
- template.sendBody("dynamic-router-control:subscribe", controlMessage1);
+
template.sendBody("dynamic-router-control:subscribe?allowPredicateFromMessage=true",
controlMessage1);
DynamicRouterControlMessage controlMessage2 =
DynamicRouterControlMessage.Builder.newBuilder()
.subscribeChannel("test")
@@ -249,7 +249,7 @@ public class DynamicRouterBasicSynchronousIT {
.predicate("#{headers.test == 'testValue2'}")
.expressionLanguage("spel")
.build();
- template.sendBody("dynamic-router-control:subscribe", controlMessage2);
+
template.sendBody("dynamic-router-control:subscribe?allowPredicateFromMessage=true",
controlMessage2);
DynamicRouterControlMessage controlMessage3 =
DynamicRouterControlMessage.Builder.newBuilder()
.subscribeChannel("test")
@@ -259,7 +259,7 @@ public class DynamicRouterBasicSynchronousIT {
.predicate("#{headers.test == 'testValue3'}")
.expressionLanguage("spel")
.build();
- template.sendBody("dynamic-router-control:subscribe", controlMessage3);
+
template.sendBody("dynamic-router-control:subscribe?allowPredicateFromMessage=true",
controlMessage3);
String filtersJson = template.requestBodyAndHeader("direct:list", "",
"subscribeChannel", "test", String.class);
Assertions.assertEquals(
diff --git
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterDefaultAggregationIT.java
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterDefaultAggregationIT.java
index bdf67dfc9ff5..47e08e1fed60 100644
---
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterDefaultAggregationIT.java
+++
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterDefaultAggregationIT.java
@@ -116,6 +116,6 @@ public class DynamicRouterDefaultAggregationIT {
.predicate("${body} != null")
.expressionLanguage("simple")
.build();
- template.sendBody("dynamic-router-control:subscribe", controlMessage);
+
template.sendBody("dynamic-router-control:subscribe?allowPredicateFromMessage=true",
controlMessage);
}
}
diff --git
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterSendDynamicAwareIT.java
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterSendDynamicAwareIT.java
index dfcc58352080..aae5c2e1d694 100644
---
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterSendDynamicAwareIT.java
+++
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterSendDynamicAwareIT.java
@@ -62,7 +62,8 @@ public class DynamicRouterSendDynamicAwareIT {
"&subscriptionId=${header.subscriptionId}" +
"&destinationUri=${header.destinationUri}" +
"&priority=${header.priority}" +
- "&predicate=${header.predicate}");
+ "&predicate=${header.predicate}" +
+ "&allowPredicateFromMessage=true");
}
});
}
diff --git
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelperTest.java
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelperTest.java
index 7f9171d5f797..ea765b79e3f9 100644
---
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelperTest.java
+++
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelperTest.java
@@ -160,9 +160,11 @@ class DynamicRouterRecipientListHelperTest {
when(mockConfig.isStopOnException()).thenReturn(true);
when(mockConfig.isIgnoreInvalidEndpoints()).thenReturn(true);
when(mockConfig.getCacheSize()).thenReturn(10);
+ when(mockConfig.getAllowedSchemes()).thenReturn("direct,mock");
// Invoke the method under test
DynamicRouterRecipientListHelper.setPropertiesForRecipientList(recipientList,
camelContext, mockConfig);
// Verify results
+ verify(recipientList, times(1)).setAllowedSchemes("direct,mock");
verify(recipientList, times(1)).setParallelProcessing(true);
verify(recipientList, times(1)).setParallelAggregate(true);
verify(recipientList, times(1)).setSynchronous(true);
diff --git
a/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java
b/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java
index 11b71a339f68..035b5e1892ac 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java
@@ -61,6 +61,7 @@ public final class SecurityUtils {
map.put("allowfilepathsource", new SecurityOption(INSECURE_DEV,
"true"));
map.put("allowjavaserializedobject", new
SecurityOption(INSECURE_SERIALIZATION, "true"));
map.put("allowlocalwebhookurls", new SecurityOption(INSECURE_DEV,
"true"));
+ map.put("allowpredicatefrommessage", new SecurityOption(INSECURE_DEV,
"true"));
map.put("allowserializedheaders", new
SecurityOption(INSECURE_SERIALIZATION, "true"));
map.put("allowurlbody", new SecurityOption(INSECURE_DEV, "true"));
map.put("allowurlsource", new SecurityOption(INSECURE_DEV, "true"));
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 1c3b57e5338a..d50c50fdbb48 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -39,6 +39,39 @@ Avro IPC handshake classes:
Do not use `*`, as it disables Avro's class-loading protection.
+=== camel-dynamic-router
+
+The `dynamic-router-control` endpoint no longer takes the subscription
`predicate`, or the
+`expressionLanguage` used to compile it, from the incoming control message. A
control message that
+supplies either is now rejected with an `IllegalArgumentException`.
+
+The predicate is compiled and then evaluated against every exchange on the
channel, so letting the
+control message choose both the language and the expression means the sender
of that message decides
+what runs inside the Camel process. Only enable this when control messages can
only come from a
+trusted source:
+
+[source,java]
+----
+from("kafka:subscriptions")
+ .unmarshal().json(DynamicRouterControlMessage.class)
+ .to("dynamic-router-control:subscribe?allowPredicateFromMessage=true");
+----
+
+The option is annotated `security = "insecure:dev"`, so with
`camel.main.profile = prod` the default
+policy for that category is `fail`, and an endpoint that sets
`allowPredicateFromMessage=true` will
+not start unless you relax `camel.security.insecureDevPolicy`.
+
+Two alternatives avoid the flag entirely. A control message may still name a
`predicateBean`, which
+selects a `Predicate` that the route author bound in the registry; that path
is unchanged. The
+control endpoint may also carry `predicate` and `expressionLanguage` as URI
parameters, in which case
+every subscription made through that endpoint uses the route author's
expression. Subscription
+parameters that the control message does not carry now fall back to the values
configured on the
+endpoint.
+
+The `dynamic-router` endpoint gained an `allowedSchemes` option, an optional
comma-separated
+allow-list of component schemes that a subscription destination may resolve
to. It is unset by
+default, which allows any scheme, matching the previous behaviour.
+
=== camel-exec
`allowControlHeaders` is now annotated `security = "insecure:dev"`.
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DynamicRouterControlEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DynamicRouterControlEndpointBuilderFactory.java
index 0e45af063a02..aab0f8b357fb 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DynamicRouterControlEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DynamicRouterControlEndpointBuilderFactory.java
@@ -176,6 +176,48 @@ public interface
DynamicRouterControlEndpointBuilderFactory {
doSetProperty("subscriptionId", subscriptionId);
return this;
}
+ /**
+ * Whether the subscription predicate, and the language used to compile
+ * it, may be taken from the incoming control message. When disabled,
+ * the predicate and expressionLanguage configured on this endpoint are
+ * used instead. Enabling this lets the sender of a control message
+ * choose both the expression language and the expression that the
+ * Dynamic Router compiles into a live predicate, so only enable it
when
+ * control messages come from a trusted source.
+ *
+ * The option is a: <code>boolean</code> type.
+ *
+ * Default: false
+ * Group: security
+ *
+ * @param allowPredicateFromMessage the value to set
+ * @return the dsl builder
+ */
+ default DynamicRouterControlEndpointBuilder
allowPredicateFromMessage(boolean allowPredicateFromMessage) {
+ doSetProperty("allowPredicateFromMessage",
allowPredicateFromMessage);
+ return this;
+ }
+ /**
+ * Whether the subscription predicate, and the language used to compile
+ * it, may be taken from the incoming control message. When disabled,
+ * the predicate and expressionLanguage configured on this endpoint are
+ * used instead. Enabling this lets the sender of a control message
+ * choose both the expression language and the expression that the
+ * Dynamic Router compiles into a live predicate, so only enable it
when
+ * control messages come from a trusted source.
+ *
+ * The option will be converted to a <code>boolean</code> type.
+ *
+ * Default: false
+ * Group: security
+ *
+ * @param allowPredicateFromMessage the value to set
+ * @return the dsl builder
+ */
+ default DynamicRouterControlEndpointBuilder
allowPredicateFromMessage(String allowPredicateFromMessage) {
+ doSetProperty("allowPredicateFromMessage",
allowPredicateFromMessage);
+ return this;
+ }
}
/**
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DynamicRouterEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DynamicRouterEndpointBuilderFactory.java
index 090629ff004e..a1969f993a80 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DynamicRouterEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DynamicRouterEndpointBuilderFactory.java
@@ -701,6 +701,23 @@ public interface DynamicRouterEndpointBuilderFactory {
doSetProperty("lazyStartProducer", lazyStartProducer);
return this;
}
+ /**
+ * Sets an optional comma-separated allow-list of component schemes
that
+ * a subscription destination may resolve to (e.g. http,https). When
+ * set, a subscription whose destination URI uses a scheme that is not
+ * in the list is rejected. By default (unset) any scheme is allowed.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: security
+ *
+ * @param allowedSchemes the value to set
+ * @return the dsl builder
+ */
+ default AdvancedDynamicRouterEndpointBuilder allowedSchemes(String
allowedSchemes) {
+ doSetProperty("allowedSchemes", allowedSchemes);
+ return this;
+ }
}
public interface DynamicRouterBuilders {