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 37ade6028080657e2402791cb8950e16f9828464
Author: Luigi De Masi <[email protected]>
AuthorDate: Thu Jun 11 17:13:34 2026 +0200

    CAMEL-23723: Fail closed when the rest-openapi delegate does not enforce 
oauthProfile
    
    Adds RestOpenApiConsumerFactory.supportsOAuthProfile(), defaulting to false,
    so rest-openapi fails at startup instead of creating an unprotected consumer
    when oauthProfile is set against a delegate that does not enforce it.
    PlatformHttpComponent declares support. Factories that enforce the option
    must override the method to return true.
    
    Co-authored-by: Claude Opus 4.6 <[email protected]>
---
 .../platform/http/PlatformHttpComponent.java       |  6 +++
 .../src/main/docs/rest-openapi-component.adoc      |  8 ++--
 .../rest/openapi/RestOpenApiEndpoint.java          |  8 ++++
 .../rest/openapi/RestOpenApiEndpointV3Test.java    | 49 ++++++++++++++++++++++
 .../vertx/http/VertxHttpTransferExceptionTest.java |  2 -
 .../camel/spi/RestOpenApiConsumerFactory.java      | 12 ++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_21.adoc    |  8 ++--
 .../modules/ROOT/pages/rest-dsl-openapi.adoc       |  5 ++-
 8 files changed, 88 insertions(+), 10 deletions(-)

diff --git 
a/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/PlatformHttpComponent.java
 
b/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/PlatformHttpComponent.java
index 2ac0a76b8afb..df2bdabc8030 100644
--- 
a/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/PlatformHttpComponent.java
+++ 
b/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/PlatformHttpComponent.java
@@ -127,6 +127,12 @@ public class PlatformHttpComponent extends 
HeaderFilterStrategyComponent
                 parameters, true, false);
     }
 
+    @Override
+    public boolean supportsOAuthProfile() {
+        // the oauthProfile endpoint option is enforced by the platform-http 
consumer
+        return true;
+    }
+
     /**
      * Adds a known http endpoint managed by this component.
      */
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 2988682af8e0..b09f86f2d0d8 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
@@ -139,9 +139,11 @@ OpenAPI `securitySchemes` and operation security 
requirements are not converted
 configuration; select the OAuth profile explicitly with the REST endpoint 
property or direct endpoint
 URI 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`.
+selected delegate, which is responsible for enforcing it. The route fails at 
startup when the
+resolved `RestOpenApiConsumerFactory` does not declare that its consumers 
enforce `oauthProfile`,
+so a misconfigured delegate cannot start without the expected protection. 
Custom factories that
+enforce the option must override 
`RestOpenApiConsumerFactory.supportsOAuthProfile()` to return
+`true`.
 
 == Request validation
 
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 039826116e64..11f5b98a5fd4 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
@@ -327,6 +327,14 @@ public final class RestOpenApiEndpoint extends 
DefaultEndpoint {
         }
 
         if (factory != null) {
+            // fail closed: never start an unprotected consumer when 
oauthProfile is configured but the
+            // delegate factory does not declare that its consumers enforce it
+            if (isNotEmpty(oauthProfile) && !factory.supportsOAuthProfile()) {
+                throw new IllegalArgumentException(
+                        "The oauthProfile option is not supported by the 
resolved RestOpenApiConsumerFactory ("
+                                                   + 
factory.getClass().getName()
+                                                   + "); select a consumer 
component that enforces oauthProfile");
+            }
             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
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 c15f84e5c6b5..24ed458fb49f 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
@@ -124,6 +124,33 @@ public class RestOpenApiEndpointV3Test {
         }
     }
 
+    @Test
+    public void oauthProfileFailsClosedWhenDelegateDoesNotSupportIt() throws 
Exception {
+        final CamelContext camelContext = new DefaultCamelContext();
+        final RestOpenApiComponent component = new 
RestOpenApiComponent(camelContext);
+        String specificationUri = 
Objects.requireNonNull(RestOpenApiEndpointV3Test.class.getResource("/openapi-v3.json"))
+                .toString();
+        camelContext.addComponent("rest-openapi", component);
+        camelContext.addComponent("no-oauth-http", new 
NoOAuthRestOpenApiConsumerFactory());
+        camelContext.start();
+
+        try {
+            RestOpenApiEndpoint endpoint = camelContext.getEndpoint(
+                    "rest-openapi:" + specificationUri + 
"?consumerComponentName=no-oauth-http&oauthProfile=myprofile",
+                    RestOpenApiEndpoint.class);
+
+            IllegalArgumentException exception = 
assertThrows(IllegalArgumentException.class,
+                    () -> endpoint.createConsumer(exchange -> {
+                    }));
+
+            assertThat(exception.getMessage())
+                    .contains("The oauthProfile option is not supported by the 
resolved RestOpenApiConsumerFactory")
+                    
.contains(NoOAuthRestOpenApiConsumerFactory.class.getName());
+        } finally {
+            camelContext.stop();
+        }
+    }
+
     @Test
     public void unknownEndpointPropertyIsTolerated() throws Exception {
         final CamelContext camelContext = new DefaultCamelContext();
@@ -516,6 +543,28 @@ public class RestOpenApiEndpointV3Test {
             this.parameters = new HashMap<>(parameters);
             return mock(Consumer.class);
         }
+
+        @Override
+        public boolean supportsOAuthProfile() {
+            return true;
+        }
+    }
+
+    private static final class NoOAuthRestOpenApiConsumerFactory extends 
DefaultComponent
+            implements RestOpenApiConsumerFactory {
+
+        @Override
+        protected Endpoint createEndpoint(String uri, String remaining, 
Map<String, Object> parameters) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Consumer createConsumer(
+                CamelContext camelContext, Processor processor, String 
contextPath, RestConfiguration configuration,
+                Map<String, Object> parameters) {
+            return mock(Consumer.class);
+        }
+        // does not override supportsOAuthProfile: the default is false
     }
 
 }
diff --git 
a/components/camel-vertx/camel-vertx-http/src/test/java/org/apache/camel/component/vertx/http/VertxHttpTransferExceptionTest.java
 
b/components/camel-vertx/camel-vertx-http/src/test/java/org/apache/camel/component/vertx/http/VertxHttpTransferExceptionTest.java
index 1d3e2b267e33..42dd4b0c60ee 100644
--- 
a/components/camel-vertx/camel-vertx-http/src/test/java/org/apache/camel/component/vertx/http/VertxHttpTransferExceptionTest.java
+++ 
b/components/camel-vertx/camel-vertx-http/src/test/java/org/apache/camel/component/vertx/http/VertxHttpTransferExceptionTest.java
@@ -22,8 +22,6 @@ import org.apache.camel.builder.RouteBuilder;
 import org.assertj.core.api.Assertions;
 import org.junit.jupiter.api.Test;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class VertxHttpTransferExceptionTest extends VertxHttpTestSupport {
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/RestOpenApiConsumerFactory.java
 
b/core/camel-api/src/main/java/org/apache/camel/spi/RestOpenApiConsumerFactory.java
index 292392705bc1..807cfa37077a 100644
--- 
a/core/camel-api/src/main/java/org/apache/camel/spi/RestOpenApiConsumerFactory.java
+++ 
b/core/camel-api/src/main/java/org/apache/camel/spi/RestOpenApiConsumerFactory.java
@@ -50,4 +50,16 @@ public interface RestOpenApiConsumerFactory {
             CamelContext camelContext, Processor processor, String contextPath,
             RestConfiguration configuration, Map<String, Object> parameters)
             throws Exception;
+
+    /**
+     * Whether consumers created by this factory enforce the {@code 
oauthProfile} option for validating incoming
+     * {@code Authorization: Bearer} tokens. Factories that do not enforce the 
option must return {@code false}, so
+     * callers such as the rest-openapi endpoint can fail fast at startup 
instead of starting an unprotected consumer.
+     *
+     * @return true when consumers created by this factory enforce the {@code 
oauthProfile} option
+     * @since  4.21
+     */
+    default boolean supportsOAuthProfile() {
+        return false;
+    }
 }
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 fe9c4f4d676c..60ba22b5cf99 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
@@ -877,9 +877,11 @@ Direct contract-first OpenAPI consumer routes can also 
pass the option on the en
 
`rest-openapi:petstore-v3.json?consumerComponentName=platform-http&oauthProfile=myprofile`.
 OpenAPI `securitySchemes` are not converted into `oauthProfile` automatically.
 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.
+selected consumer delegate, which is responsible for enforcing it. The route 
fails at startup when
+the resolved `RestOpenApiConsumerFactory` does not declare that its consumers 
enforce `oauthProfile`
+via the new `RestOpenApiConsumerFactory.supportsOAuthProfile()` method, which 
defaults to `false`.
+Custom factories that enforce the option must override it to return `true`; 
the built-in
+`platform-http` delegate declares support.
 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 ff6ed3055fba..9983bcaa769f 100644
--- a/docs/user-manual/modules/ROOT/pages/rest-dsl-openapi.adoc
+++ b/docs/user-manual/modules/ROOT/pages/rest-dsl-openapi.adoc
@@ -66,8 +66,9 @@ for example 
`rest-openapi:petstore-v3.json?consumerComponentName=platform-http&o
 Consumer endpoint URIs identify the OpenAPI specification and do not include 
an `#operationId`
 fragment.
 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.
+enforcing it. The route fails at startup when the resolved 
`RestOpenApiConsumerFactory` does not
+declare that its consumers enforce `oauthProfile`, so a misconfigured delegate 
cannot start
+without the expected protection.
 
 == Contract first
 

Reply via email to