This is an automated email from the ASF dual-hosted git repository. luigidemasi pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit 2ec45e1f23c19ec695b3fd15240971a90f9d1c5b Author: Luigi De Masi <[email protected]> AuthorDate: Thu Jun 11 17:01:16 2026 +0200 CAMEL-23723: Make oauthProfile a first-class rest-openapi endpoint option Declares oauthProfile as a @UriParam on RestOpenApiEndpoint and injects it explicitly into the parameters passed to the consumer delegate factory, instead of relying on lenient pass-through. Reverts the lenientProperties annotation addition; runtime leniency via isLenientProperties() is unchanged. Co-authored-by: Claude Opus 4.6 <[email protected]> --- .../camel/catalog/components/rest-openapi.json | 5 +++-- .../rest/openapi/RestOpenApiEndpointConfigurer.java | 6 ++++++ .../rest/openapi/RestOpenApiEndpointUriFactory.java | 5 +++-- .../camel/component/rest/openapi/rest-openapi.json | 5 +++-- .../src/main/docs/rest-openapi-component.adoc | 4 ++-- .../component/rest/openapi/RestOpenApiEndpoint.java | 21 +++++++++++++++++++-- .../rest/openapi/RestOpenApiEndpointV3Test.java | 3 +++ .../ROOT/pages/camel-4x-upgrade-guide-4_21.adoc | 8 ++++---- .../modules/ROOT/pages/rest-dsl-openapi.adoc | 6 +++--- .../dsl/RestOpenApiEndpointBuilderFactory.java | 17 +++++++++++++++++ 10 files changed, 63 insertions(+), 17 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/rest-openapi.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/rest-openapi.json index 95d974acb41a..40b618315912 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/rest-openapi.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/rest-openapi.json @@ -19,7 +19,7 @@ "api": false, "consumerOnly": false, "producerOnly": false, - "lenientProperties": true, + "lenientProperties": false, "browsable": false, "remote": true }, @@ -64,6 +64,7 @@ "produces": { "index": 15, "kind": "parameter", "displayName": "Produces", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "What payload type this component is producing. For example application\/json according to the RFC7231. This equates to the value of Content-Type HTTP header. If set overrides any value present in the OpenApi specification. Overr [...] "requestValidationEnabled": { "index": 16, "kind": "parameter", "displayName": "Request Validation Enabled", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Enable validation of requests against the configured OpenAPI specification" }, "componentName": { "index": 17, "kind": "parameter", "displayName": "Component Name", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of the Camel component that will perform the requests. The component must be present in Camel registry and it must implement RestProducerFactory service provider interface. If not set CLASSPAT [...] - "lazyStartProducer": { "index": 18, "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": 18, "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 [...] + "oauthProfile": { "index": 19, "kind": "parameter", "displayName": "OAuth Profile", "group": "security", "label": "consumer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "OAuth profile name passed to the HTTP consumer delegate for validating incoming Authorization: Bearer tokens. The selected consumer component must support the oauthProfile option; delegates that ignore unknown [...] } } diff --git a/components/camel-rest-openapi/src/generated/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpointConfigurer.java b/components/camel-rest-openapi/src/generated/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpointConfigurer.java index 5fa644a83f82..27c86cc5c395 100644 --- a/components/camel-rest-openapi/src/generated/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpointConfigurer.java +++ b/components/camel-rest-openapi/src/generated/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpointConfigurer.java @@ -49,6 +49,8 @@ public class RestOpenApiEndpointConfigurer extends PropertyConfigurerSupport imp case "missingOperation": target.setMissingOperation(property(camelContext, java.lang.String.class, value)); return true; case "mockincludepattern": case "mockIncludePattern": target.setMockIncludePattern(property(camelContext, java.lang.String.class, value)); return true; + case "oauthprofile": + case "oauthProfile": target.setOauthProfile(property(camelContext, java.lang.String.class, value)); return true; case "produces": target.setProduces(property(camelContext, java.lang.String.class, value)); return true; case "requestvalidationenabled": case "requestValidationEnabled": target.setRequestValidationEnabled(property(camelContext, boolean.class, value)); return true; @@ -87,6 +89,8 @@ public class RestOpenApiEndpointConfigurer extends PropertyConfigurerSupport imp case "missingOperation": return java.lang.String.class; case "mockincludepattern": case "mockIncludePattern": return java.lang.String.class; + case "oauthprofile": + case "oauthProfile": return java.lang.String.class; case "produces": return java.lang.String.class; case "requestvalidationenabled": case "requestValidationEnabled": return boolean.class; @@ -126,6 +130,8 @@ public class RestOpenApiEndpointConfigurer extends PropertyConfigurerSupport imp case "missingOperation": return target.getMissingOperation(); case "mockincludepattern": case "mockIncludePattern": return target.getMockIncludePattern(); + case "oauthprofile": + case "oauthProfile": return target.getOauthProfile(); case "produces": return target.getProduces(); case "requestvalidationenabled": case "requestValidationEnabled": return target.isRequestValidationEnabled(); diff --git a/components/camel-rest-openapi/src/generated/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpointUriFactory.java b/components/camel-rest-openapi/src/generated/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpointUriFactory.java index b168baf6feef..f2285b934b61 100644 --- a/components/camel-rest-openapi/src/generated/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpointUriFactory.java +++ b/components/camel-rest-openapi/src/generated/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpointUriFactory.java @@ -24,7 +24,7 @@ public class RestOpenApiEndpointUriFactory extends org.apache.camel.support.comp private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES; private static final Map<String, String> MULTI_VALUE_PREFIXES; static { - Set<String> props = new HashSet<>(19); + Set<String> props = new HashSet<>(20); props.add("apiContextPath"); props.add("basePath"); props.add("bridgeErrorHandler"); @@ -39,6 +39,7 @@ public class RestOpenApiEndpointUriFactory extends org.apache.camel.support.comp props.add("lazyStartProducer"); props.add("missingOperation"); props.add("mockIncludePattern"); + props.add("oauthProfile"); props.add("operationId"); props.add("produces"); props.add("requestValidationEnabled"); @@ -90,7 +91,7 @@ public class RestOpenApiEndpointUriFactory extends org.apache.camel.support.comp @Override public boolean isLenientProperties() { - return true; + return false; } } diff --git a/components/camel-rest-openapi/src/generated/resources/META-INF/org/apache/camel/component/rest/openapi/rest-openapi.json b/components/camel-rest-openapi/src/generated/resources/META-INF/org/apache/camel/component/rest/openapi/rest-openapi.json index 95d974acb41a..40b618315912 100644 --- a/components/camel-rest-openapi/src/generated/resources/META-INF/org/apache/camel/component/rest/openapi/rest-openapi.json +++ b/components/camel-rest-openapi/src/generated/resources/META-INF/org/apache/camel/component/rest/openapi/rest-openapi.json @@ -19,7 +19,7 @@ "api": false, "consumerOnly": false, "producerOnly": false, - "lenientProperties": true, + "lenientProperties": false, "browsable": false, "remote": true }, @@ -64,6 +64,7 @@ "produces": { "index": 15, "kind": "parameter", "displayName": "Produces", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "What payload type this component is producing. For example application\/json according to the RFC7231. This equates to the value of Content-Type HTTP header. If set overrides any value present in the OpenApi specification. Overr [...] "requestValidationEnabled": { "index": 16, "kind": "parameter", "displayName": "Request Validation Enabled", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Enable validation of requests against the configured OpenAPI specification" }, "componentName": { "index": 17, "kind": "parameter", "displayName": "Component Name", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of the Camel component that will perform the requests. The component must be present in Camel registry and it must implement RestProducerFactory service provider interface. If not set CLASSPAT [...] - "lazyStartProducer": { "index": 18, "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": 18, "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 [...] + "oauthProfile": { "index": 19, "kind": "parameter", "displayName": "OAuth Profile", "group": "security", "label": "consumer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "OAuth profile name passed to the HTTP consumer delegate for validating incoming Authorization: Bearer tokens. The selected consumer component must support the oauthProfile option; delegates that ignore unknown [...] } } diff --git a/components/camel-rest-openapi/src/main/docs/rest-openapi-component.adoc b/components/camel-rest-openapi/src/main/docs/rest-openapi-component.adoc index 27eee299b6bc..2988682af8e0 100644 --- a/components/camel-rest-openapi/src/main/docs/rest-openapi-component.adoc +++ b/components/camel-rest-openapi/src/main/docs/rest-openapi-component.adoc @@ -138,8 +138,8 @@ xref:components:others:oauth.adoc[camel-oauth] or from the runtime integration. OpenAPI `securitySchemes` and operation security requirements are not converted into `oauthProfile` configuration; select the OAuth profile explicitly with the REST endpoint property or direct endpoint URI option. -Because `rest-openapi` endpoint URIs are lenient, pass-through options are accepted by -`rest-openapi` and forwarded to the selected delegate. If the delegate ignores an unknown option, +The `oauthProfile` option is a first-class `rest-openapi` endpoint option that is forwarded to the +selected delegate, which is responsible for enforcing it. If the delegate ignores the option, the endpoint can start without the expected protection, so verify that the selected consumer component supports `oauthProfile`. diff --git a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java index 7b36960a6443..039826116e64 100644 --- a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java +++ b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java @@ -90,8 +90,7 @@ import static org.apache.camel.util.StringHelper.before; * To call and expose REST services using OpenAPI specification as contract. */ @UriEndpoint(firstVersion = "3.1.0", scheme = "rest-openapi", title = "REST OpenApi", - syntax = "rest-openapi:specificationUri#operationId", category = { Category.REST, Category.API }, - lenientProperties = true) + syntax = "rest-openapi:specificationUri#operationId", category = { Category.REST, Category.API }) public final class RestOpenApiEndpoint extends DefaultEndpoint { private static final Logger LOG = LoggerFactory.getLogger(RestOpenApiEndpoint.class); @@ -177,6 +176,12 @@ public final class RestOpenApiEndpoint extends DefaultEndpoint { private String mockIncludePattern; @UriParam(label = "consumer", description = "Sets the context-path to use for servicing the OpenAPI specification") private String apiContextPath; + @UriParam(label = "consumer,security", displayName = "OAuth Profile", + description = "OAuth profile name passed to the HTTP consumer delegate for validating incoming" + + " Authorization: Bearer tokens. The selected consumer component must support the" + + " oauthProfile option; delegates that ignore unknown options will start without" + + " endpoint protection.") + private String oauthProfile; public RestOpenApiEndpoint() { // help tooling instantiate endpoint @@ -324,6 +329,10 @@ public final class RestOpenApiEndpoint extends DefaultEndpoint { if (factory != null) { RestConfiguration config = CamelContextHelper.getRestConfiguration(getCamelContext(), cname); Map<String, Object> copy = new HashMap<>(parameters); // defensive copy of the parameters + // pass oauthProfile to the delegate consumer, which is responsible for enforcing it + if (isNotEmpty(oauthProfile)) { + copy.put("oauthProfile", oauthProfile); + } // avoid duplicate context-path if (basePath.equals(config.getContextPath())) { basePath = ""; @@ -504,6 +513,14 @@ public final class RestOpenApiEndpoint extends DefaultEndpoint { this.apiContextPath = apiContextPath; } + public String getOauthProfile() { + return oauthProfile; + } + + public void setOauthProfile(String oauthProfile) { + this.oauthProfile = oauthProfile; + } + public String getBindingPackageScan() { return bindingPackageScan; } diff --git a/components/camel-rest-openapi/src/test/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpointV3Test.java b/components/camel-rest-openapi/src/test/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpointV3Test.java index 1f58e9853109..c15f84e5c6b5 100644 --- a/components/camel-rest-openapi/src/test/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpointV3Test.java +++ b/components/camel-rest-openapi/src/test/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpointV3Test.java @@ -115,6 +115,9 @@ public class RestOpenApiEndpointV3Test { endpoint.createConsumer(exchange -> { }); + // oauthProfile is a first-class endpoint option bound to the endpoint and injected into + // the parameters passed to the delegate consumer factory + assertThat(endpoint.getOauthProfile()).isEqualTo("myprofile"); assertThat(delegate.parameters).containsEntry("oauthProfile", "myprofile"); } finally { camelContext.stop(); diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc index 7ae5f745a352..fe9c4f4d676c 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc @@ -876,10 +876,10 @@ Rest DSL and contract-first OpenAPI routes pass the option to the selected HTTP Direct contract-first OpenAPI consumer routes can also pass the option on the endpoint URI, for example `rest-openapi:petstore-v3.json?consumerComponentName=platform-http&oauthProfile=myprofile`. OpenAPI `securitySchemes` are not converted into `oauthProfile` automatically. -Because `rest-openapi` endpoint URIs are lenient, pass-through options such as `oauthProfile` are -accepted by the selected consumer delegate but may not appear as first-class `rest-openapi` options in -component metadata or tooling. Verify that the selected delegate supports the pass-through option; -delegates that ignore unknown options can otherwise start without the expected endpoint protection. +The `oauthProfile` option is a first-class `rest-openapi` endpoint option that is forwarded to the +selected consumer delegate, which is responsible for enforcing it. Verify that the selected delegate +supports `oauthProfile`; delegates that ignore the option can otherwise start without the expected +endpoint protection. For netty-http consumers, `oauthProfile` requires `usingExecutorService=true` and `sync=true`, which are the defaults, so blocking validation does not run on a Netty event-loop thread and rejection responses can be returned to the client. Routes that combine `oauthProfile` diff --git a/docs/user-manual/modules/ROOT/pages/rest-dsl-openapi.adoc b/docs/user-manual/modules/ROOT/pages/rest-dsl-openapi.adoc index 573a0d310d3f..ff6ed3055fba 100644 --- a/docs/user-manual/modules/ROOT/pages/rest-dsl-openapi.adoc +++ b/docs/user-manual/modules/ROOT/pages/rest-dsl-openapi.adoc @@ -65,9 +65,9 @@ support it. Direct `rest-openapi` consumer endpoint URIs can also pass the optio for example `rest-openapi:petstore-v3.json?consumerComponentName=platform-http&oauthProfile=myprofile`. Consumer endpoint URIs identify the OpenAPI specification and do not include an `#operationId` fragment. -Because `rest-openapi` endpoint URIs are lenient, delegate options may be accepted by -`rest-openapi` even when the selected delegate ignores them. Verify that the selected consumer -component supports `oauthProfile`. +The `oauthProfile` option is forwarded to the selected delegate, which is responsible for +enforcing it. Verify that the selected consumer component supports `oauthProfile`; a delegate +that ignores the option starts without the expected protection. == Contract first diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/RestOpenApiEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/RestOpenApiEndpointBuilderFactory.java index bf2adee3f17a..0322ea108e4c 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/RestOpenApiEndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/RestOpenApiEndpointBuilderFactory.java @@ -175,6 +175,23 @@ public interface RestOpenApiEndpointBuilderFactory { doSetProperty("missingOperation", missingOperation); return this; } + /** + * OAuth profile name passed to the HTTP consumer delegate for + * validating incoming Authorization: Bearer tokens. The selected + * consumer component must support the oauthProfile option; delegates + * that ignore unknown options will start without endpoint protection. + * + * The option is a: <code>java.lang.String</code> type. + * + * Group: security + * + * @param oauthProfile the value to set + * @return the dsl builder + */ + default RestOpenApiEndpointConsumerBuilder oauthProfile(String oauthProfile) { + doSetProperty("oauthProfile", oauthProfile); + return this; + } } /**
