This is an automated email from the ASF dual-hosted git repository.

oscerd pushed a commit to branch camel-4.22.x
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/camel-4.22.x by this push:
     new ccd49cc50491 [backport camel-4.22.x] CAMEL-24412: camel-netty-http - 
evaluate the security constraint against the same normalized target as dispatch 
(#25761)
ccd49cc50491 is described below

commit ccd49cc5049113c8be976ecef3d581da112b9918
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Aug 27 06:40:27 2026 +0200

    [backport camel-4.22.x] CAMEL-24412: camel-netty-http - evaluate the 
security constraint against the same normalized target as dispatch (#25761)
    
    CAMEL-24412: camel-netty-http - evaluate the security constraint against 
the same normalized target as dispatch (#25578)
    
    HttpServerChannelHandler.extractTarget() strips the endpoint context-path 
from
    the request target so the security constraint is evaluated relative to the
    endpoint. The strip was guarded by a case-sensitive startsWith:
    
        if (path != null && target.startsWith(path)) {
            // need to match by lower case as we want to ignore case on 
context-path
            path = path.toLowerCase(Locale.US);
            String match = target.toLowerCase(Locale.US);
            if (match.startsWith(path)) {
    
    so the inner case-insensitive comparison could never change the outcome - 
it was
    dead code. A request whose context-path differed only by case was evaluated
    against the unstripped target.
    
    Dispatch does not share that property: 
RestConsumerContextPathMatcher.matchPath()
    compares with equalsIgnoreCase and a lower-cased prefix, so the request 
still
    reaches the route. Authorization and dispatch therefore disagreed about 
which
    endpoint a request belongs to. With matchOnUriPrefix=true and a 
securityConstraint
    whose inclusions are specific sub-paths rather than a catch-all, the 
miscased
    target matched no inclusion, and an unmatched target counts as unrestricted.
    
    The strip now uses the case-insensitive comparison directly.
    
    The added test covers both directions against a constraint with a specific
    /admin/* inclusion: the exact-case path is challenged as before, and the
    differently-cased path - which reaches the route either way - is now 
challenged
    too. Without this fix the second case returns 200 with no challenge.
    
    Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
 .../http/handlers/HttpServerChannelHandler.java    | 14 ++--
 ...HttpBasicAuthConstraintCaseInsensitiveTest.java | 88 ++++++++++++++++++++++
 2 files changed, 94 insertions(+), 8 deletions(-)

diff --git 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerChannelHandler.java
 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerChannelHandler.java
index fedb184b99e4..790ff8cc4903 100644
--- 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerChannelHandler.java
+++ 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerChannelHandler.java
@@ -217,15 +217,13 @@ public class HttpServerChannelHandler extends 
ServerChannelHandler {
     private String extractTarget(URI uri) {
         String target = uri.getPath();
 
-        // strip the starting endpoint path so the target is relative to the 
endpoint uri
+        // strip the starting endpoint path so the target is relative to the 
endpoint uri.
+        // the comparison must ignore case on the context-path, the same way 
consumer dispatch does
+        // (RestConsumerContextPathMatcher), so the security constraint is 
evaluated against the same
+        // normalized target the request is actually routed to
         String path = consumer.getConfiguration().getPath();
-        if (path != null && target.startsWith(path)) {
-            // need to match by lower case as we want to ignore case on 
context-path
-            path = path.toLowerCase(Locale.US);
-            String match = target.toLowerCase(Locale.US);
-            if (match.startsWith(path)) {
-                target = target.substring(path.length());
-            }
+        if (path != null && 
target.toLowerCase(Locale.US).startsWith(path.toLowerCase(Locale.US))) {
+            target = target.substring(path.length());
         }
         return target;
     }
diff --git 
a/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBasicAuthConstraintCaseInsensitiveTest.java
 
b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBasicAuthConstraintCaseInsensitiveTest.java
new file mode 100644
index 000000000000..6dd72458a919
--- /dev/null
+++ 
b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBasicAuthConstraintCaseInsensitiveTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.component.netty.http;
+
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.CamelExecutionException;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+
+import static org.apache.camel.test.junit6.TestSupport.assertIsInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Consumer dispatch matches the context-path case-insensitively, so the 
security constraint has to be evaluated against
+ * the same normalized target. Otherwise a request that differs from the 
configured context-path only by case reaches
+ * the route while skipping the constraint that guards it.
+ */
+public class NettyHttpBasicAuthConstraintCaseInsensitiveTest extends 
BaseNettyTestSupport {
+
+    @Override
+    public void doPreSetup() {
+        System.setProperty("java.security.auth.login.config", 
"src/test/resources/myjaas.config");
+    }
+
+    @Override
+    public void doPostTearDown() {
+        System.clearProperty("java.security.auth.login.config");
+    }
+
+    @BindToRegistry("mySecurityConfig")
+    public NettyHttpSecurityConfiguration loadSecConf() {
+        NettyHttpSecurityConfiguration security = new 
NettyHttpSecurityConfiguration();
+        security.setRealm("karaf");
+        SecurityAuthenticator auth = new JAASSecurityAuthenticator();
+        auth.setName("karaf");
+        security.setSecurityAuthenticator(auth);
+
+        // a specific inclusion, not a catch-all: only /admin/* below the 
endpoint path is restricted
+        SecurityConstraintMapping matcher = new SecurityConstraintMapping();
+        matcher.addInclusion("/admin/*");
+        security.setSecurityConstraint(matcher);
+
+        return security;
+    }
+
+    @Test
+    public void exactCaseContextPathIsChallenged() {
+        CamelExecutionException e = assertThrows(CamelExecutionException.class,
+                () -> 
template.requestBody("netty-http:http://localhost:{{port}}/foo/admin/x";, 
"Hello", String.class));
+        NettyHttpOperationFailedException cause = 
assertIsInstanceOf(NettyHttpOperationFailedException.class, e.getCause());
+        assertEquals(401, cause.getStatusCode());
+    }
+
+    @Test
+    public void differentlyCasedContextPathIsChallengedToo() {
+        // dispatch reaches the route either way, so the constraint must apply 
either way
+        CamelExecutionException e = assertThrows(CamelExecutionException.class,
+                () -> 
template.requestBody("netty-http:http://localhost:{{port}}/Foo/admin/x";, 
"Hello", String.class));
+        NettyHttpOperationFailedException cause = 
assertIsInstanceOf(NettyHttpOperationFailedException.class, e.getCause());
+        assertEquals(401, cause.getStatusCode());
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                
from("netty-http:http://0.0.0.0:{{port}}/foo?matchOnUriPrefix=true&securityConfiguration=#mySecurityConfig";)
+                        .transform().constant("Bye World");
+            }
+        };
+    }
+}

Reply via email to