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
