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.

Reply via email to