This is an automated email from the ASF dual-hosted git repository.
oscerd 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 a3a98e4c959f CAMEL-24411: camel-oauth - stop the route when the
processors do not authenticate the request (#25567)
a3a98e4c959f is described below
commit a3a98e4c959f3a04d5e5540b60bf2cc36314e37c
Author: Andrea Cosentino <[email protected]>
AuthorDate: Fri Aug 21 23:07:47 2026 +0200
CAMEL-24411: camel-oauth - stop the route when the processors do not
authenticate the request (#25567)
OAuthBearerTokenProcessor and OAuthCodeFlowProcessor both returned normally
from process() on the paths where they do not authenticate the caller, so
the
remaining steps of the route still ran and overwrote the response the
processor
had just prepared. The component's own test routes have the shape
.process(new OAuthBearerTokenProcessor()).setBody(...)
where that following step executes.
The same shape is present in OAuthCodeFlowCallback, which answered 400 for a
callback without the code parameter and then let the route continue.
There was no setRouteStop, CamelAuthorizationException or RoutePolicy
anywhere
in camel-oauth, so nothing halted the exchange on any of these paths.
This adds reject() and rejectUnauthorized() helpers to
AbstractOAuthProcessor
and uses them at the three denial points:
* OAuthBearerTokenProcessor - a missing Authorization header, or one that
does
not parse as "Bearer <token>", now answers 401 with a WWW-Authenticate:
Bearer
challenge (RFC 6750) instead of 400, and stops the route. A present but
invalid token keeps failing by propagating the exception from
OAuth.authenticate(), as before.
* OAuthCodeFlowProcessor - stops the route after redirecting an
unauthenticated
caller to the identity provider; the 302 is the whole response.
* OAuthCodeFlowCallback - keeps answering 400 for a missing authorization
code
and now stops the route too.
sendRedirect() itself is deliberately left alone, and OAuthLogoutProcessor
is
unchanged: the shipped logout route relies on the step after the redirect
running, so stopping the route inside sendRedirect would break an intended
flow.
Only the denial paths stop; authenticated requests continue through the
rest of
the route exactly as before.
Adds OAuthProcessorFailClosedTest covering the three paths that return
before an
identity provider is contacted, a test-scoped assertj-core the module was
missing, and a 4.23 upgrade-guide entry for the status-code and route-stop
change.
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
components/camel-oauth/pom.xml | 5 ++
.../apache/camel/oauth/AbstractOAuthProcessor.java | 27 +++++++++
.../camel/oauth/OAuthBearerTokenProcessor.java | 8 +--
.../apache/camel/oauth/OAuthCodeFlowCallback.java | 3 +-
.../apache/camel/oauth/OAuthCodeFlowProcessor.java | 3 +
.../camel/oauth/OAuthProcessorFailClosedTest.java | 70 ++++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc | 22 +++++++
7 files changed, 131 insertions(+), 7 deletions(-)
diff --git a/components/camel-oauth/pom.xml b/components/camel-oauth/pom.xml
index 3b6da44c6902..4916362ff38b 100644
--- a/components/camel-oauth/pom.xml
+++ b/components/camel-oauth/pom.xml
@@ -114,6 +114,11 @@
<version>${rest-assured-version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
diff --git
a/components/camel-oauth/src/main/java/org/apache/camel/oauth/AbstractOAuthProcessor.java
b/components/camel-oauth/src/main/java/org/apache/camel/oauth/AbstractOAuthProcessor.java
index e7847205a9ea..8a480c458b00 100644
---
a/components/camel-oauth/src/main/java/org/apache/camel/oauth/AbstractOAuthProcessor.java
+++
b/components/camel-oauth/src/main/java/org/apache/camel/oauth/AbstractOAuthProcessor.java
@@ -64,6 +64,33 @@ public abstract class AbstractOAuthProcessor implements
Processor {
});
}
+ /**
+ * Rejects the current request and stops the route, so that no subsequent
step runs for a request this processor did
+ * not authenticate. Absence of credentials must be rejected at least as
strongly as invalid credentials.
+ *
+ * @param exchange the exchange to reject and stop
+ * @param statusCode the HTTP status code to reply with
+ * @param body the response body
+ */
+ protected void reject(Exchange exchange, int statusCode, String body) {
+ var msg = exchange.getMessage();
+ msg.setHeader(Exchange.HTTP_RESPONSE_CODE, statusCode);
+ msg.setBody(body);
+ exchange.setRouteStop(true);
+ }
+
+ /**
+ * Rejects the current request as unauthenticated with a {@code 401} and a
{@code WWW-Authenticate: Bearer}
+ * challenge, as required by RFC 6750, and stops the route.
+ *
+ * @param exchange the exchange to reject and stop
+ * @param body the response body
+ */
+ protected void rejectUnauthorized(Exchange exchange, String body) {
+ exchange.getMessage().setHeader("WWW-Authenticate", "Bearer");
+ reject(exchange, 401, body);
+ }
+
protected void sendRedirect(Message msg, String redirectUrl) {
log.debug("Redirect to: {}", redirectUrl);
msg.setHeader(Exchange.HTTP_RESPONSE_CODE, 302);
diff --git
a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthBearerTokenProcessor.java
b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthBearerTokenProcessor.java
index 0819e7b37f9d..337a29968946 100644
---
a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthBearerTokenProcessor.java
+++
b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthBearerTokenProcessor.java
@@ -36,16 +36,14 @@ public class OAuthBearerTokenProcessor extends
AbstractOAuthProcessor {
var authHeader = msg.getHeader("Authorization", String.class);
if (authHeader == null) {
log.error("No Authorization header in request");
- msg.setHeader("CamelHttpResponseCode", 400);
- msg.setBody("Authorization header");
+ rejectUnauthorized(exchange, "Authorization header");
return;
}
var toks = authHeader.split(" ");
if (toks.length != 2 || !"Bearer".equals(toks[0])) {
- log.error("Invalid Authorization header: {}", authHeader);
- msg.setHeader("CamelHttpResponseCode", 400);
- msg.setBody("Invalid Authorization header");
+ log.error("Invalid Authorization header");
+ rejectUnauthorized(exchange, "Invalid Authorization header");
return;
}
diff --git
a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowCallback.java
b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowCallback.java
index 64b19141feae..677305dd5cf2 100644
---
a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowCallback.java
+++
b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowCallback.java
@@ -39,8 +39,7 @@ public class OAuthCodeFlowCallback extends
AbstractOAuthProcessor {
var authCode = msg.getHeader("code", String.class);
if (authCode == null) {
log.error("Authorization code is missing in the request");
- msg.setHeader("CamelHttpResponseCode", 400);
- msg.setBody("Authorization code missing");
+ reject(exchange, 400, "Authorization code missing");
return;
}
diff --git
a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowProcessor.java
b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowProcessor.java
index 52b1c9078adb..2de2d8969b28 100644
---
a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowProcessor.java
+++
b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowProcessor.java
@@ -70,6 +70,9 @@ public class OAuthCodeFlowProcessor extends
AbstractOAuthProcessor {
var authRequestUrl = oauth.buildCodeFlowAuthRequestUrl(params);
sendRedirect(msg, authRequestUrl);
+
+ // The caller is not authenticated: the redirect is the whole
response, so the protected route must not run
+ exchange.setRouteStop(true);
}
private String getPostLoginUrl(Message msg) {
diff --git
a/components/camel-oauth/src/test/java/org/apache/camel/oauth/OAuthProcessorFailClosedTest.java
b/components/camel-oauth/src/test/java/org/apache/camel/oauth/OAuthProcessorFailClosedTest.java
new file mode 100644
index 000000000000..72a8d13ac5cf
--- /dev/null
+++
b/components/camel-oauth/src/test/java/org/apache/camel/oauth/OAuthProcessorFailClosedTest.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * A request the processors do not authenticate must stop the route, so that
no subsequent step runs for it. These are
+ * the paths that return before any identity provider is contacted, so they
can be exercised without one.
+ */
+class OAuthProcessorFailClosedTest {
+
+ @Test
+ void missingAuthorizationHeaderStopsTheRoute() throws Exception {
+ try (DefaultCamelContext context = new DefaultCamelContext()) {
+ Exchange exchange = new DefaultExchange(context);
+
+ new OAuthBearerTokenProcessor().process(exchange);
+
+
assertThat(exchange.getMessage().getHeader(Exchange.HTTP_RESPONSE_CODE)).isEqualTo(401);
+
assertThat(exchange.getMessage().getHeader("WWW-Authenticate")).isEqualTo("Bearer");
+ assertThat(exchange.isRouteStop()).isTrue();
+ }
+ }
+
+ @Test
+ void nonBearerAuthorizationHeaderStopsTheRoute() throws Exception {
+ try (DefaultCamelContext context = new DefaultCamelContext()) {
+ Exchange exchange = new DefaultExchange(context);
+ exchange.getMessage().setHeader("Authorization", "Basic
c2NvdHQ6c2VjcmV0");
+
+ new OAuthBearerTokenProcessor().process(exchange);
+
+
assertThat(exchange.getMessage().getHeader(Exchange.HTTP_RESPONSE_CODE)).isEqualTo(401);
+
assertThat(exchange.getMessage().getHeader("WWW-Authenticate")).isEqualTo("Bearer");
+ assertThat(exchange.isRouteStop()).isTrue();
+ }
+ }
+
+ @Test
+ void missingAuthorizationCodeStopsTheRoute() throws Exception {
+ try (DefaultCamelContext context = new DefaultCamelContext()) {
+ Exchange exchange = new DefaultExchange(context);
+
+ new OAuthCodeFlowCallback().process(exchange);
+
+
assertThat(exchange.getMessage().getHeader(Exchange.HTTP_RESPONSE_CODE)).isEqualTo(400);
+ assertThat(exchange.isRouteStop()).isTrue();
+ }
+ }
+}
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 55eb73cbb500..a43c68a08f5d 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
@@ -107,3 +107,25 @@ denies `java.net.**`, and enforces JEP-290 graph-shape
limits). Routes that dese
outside that allow-list must pass an explicit filter pattern to the
two-argument
`ObjectDecoder(ClassResolver, String)` /
`DatagramPacketObjectDecoder(ClassResolver, String)`
constructor (or configure `jdk.serialFilter`) to permit them.
+
+=== camel-oauth
+
+The OAuth processors now stop the route on the paths where they do not
authenticate the caller, so
+that no subsequent step of the route runs for such a request. Previously they
set a response code
+and returned, which left the rest of the route to execute and overwrite the
response the processor
+had just prepared.
+
+What changed:
+
+* `OAuthBearerTokenProcessor` — a request with no `Authorization` header, or
with one that does not
+parse as `Bearer <token>`, is now answered with `401` and a `WWW-Authenticate:
Bearer` challenge
+(RFC 6750) instead of `400`, and the route is stopped. A present-but-invalid
token continues to fail
+by propagating the exception from `OAuth.authenticate()`, as before.
+* `OAuthCodeFlowProcessor` — when the caller has no authenticated session and
is redirected to the
+identity provider, the route is now stopped; the `302` is the whole response.
+* `OAuthCodeFlowCallback` — a callback request without the `code` parameter
still answers `400`, and
+now also stops the route.
+
+Routes that relied on steps after these processors running for unauthenticated
requests must be
+restructured. The authenticated paths are unchanged: a successfully
authenticated request continues
+through the rest of the route exactly as before, and `OAuthLogoutProcessor` is
unchanged.