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");
+ }
+ };
+ }
+}