Skip to content

Commit 00810cc

Browse files
authored
Merge pull request #53067 from geoand/#53030
Fix path templating issue for overlapping paths
2 parents b097d96 + bfbd644 commit 00810cc

File tree

3 files changed

+69
-19
lines changed

3 files changed

+69
-19
lines changed

extensions/resteasy-reactive/rest/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/observability/ObservabilityIntegrationRecorder.java

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.jboss.resteasy.reactive.common.util.PathHelper;
99
import org.jboss.resteasy.reactive.server.core.Deployment;
1010
import org.jboss.resteasy.reactive.server.handlers.ClassRoutingHandler;
11+
import org.jboss.resteasy.reactive.server.handlers.RestInitialHandler;
1112
import org.jboss.resteasy.reactive.server.mapping.RequestMapper;
1213

1314
import io.quarkus.runtime.RuntimeValue;
@@ -54,24 +55,37 @@ private boolean shouldHandle(RoutingContext event) {
5455
public static void setTemplatePath(RoutingContext rc, Deployment deployment) {
5556
// do what RestInitialHandler does
5657
var initMappers = new RequestMapper<>(deployment.getClassMappers());
57-
var requestMatch = initMappers.map(getPathWithoutPrefix(rc, deployment));
58-
if (requestMatch == null) {
59-
return;
58+
var path = getPathWithoutPrefix(rc, deployment);
59+
var requestMatch = initMappers.map(path);
60+
61+
// try each class-level match until we find one whose method-level mapper also matches
62+
// this mirrors what ClassRoutingHandler does with restartWithNextInitialMatch()
63+
while (requestMatch != null) {
64+
var templatePath = tryMatchTemplatePath(rc, requestMatch);
65+
if (templatePath != null) {
66+
if (templatePath.endsWith("/")) {
67+
templatePath = templatePath.substring(0, templatePath.length() - 1);
68+
}
69+
setUrlPathTemplate(rc, templatePath);
70+
return;
71+
}
72+
requestMatch = initMappers.continueMatching(path, requestMatch);
6073
}
74+
}
75+
76+
private static String tryMatchTemplatePath(RoutingContext rc,
77+
RequestMapper.RequestMatch<RestInitialHandler.InitialMatch> requestMatch) {
6178
var remaining = requestMatch.remaining.isEmpty() ? "/" : requestMatch.remaining;
6279

6380
var serverRestHandlers = requestMatch.value.handlers;
6481
if (serverRestHandlers == null || serverRestHandlers.length < 1) {
65-
// nothing we can do
66-
return;
82+
return null;
6783
}
6884
var firstHandler = serverRestHandlers[0];
69-
if (!(firstHandler instanceof ClassRoutingHandler)) {
70-
// nothing we can do
71-
return;
85+
if (!(firstHandler instanceof ClassRoutingHandler classRoutingHandler)) {
86+
return null;
7287
}
7388

74-
var classRoutingHandler = (ClassRoutingHandler) firstHandler;
7589
var mappers = classRoutingHandler.getMappers();
7690

7791
var requestMethod = rc.request().method().name();
@@ -86,8 +100,7 @@ public static void setTemplatePath(RoutingContext rc, Deployment deployment) {
86100
mapper = mappers.get(null);
87101
}
88102
if (mapper == null) {
89-
// can't match the path
90-
return;
103+
return null;
91104
}
92105
}
93106
var target = mapper.map(remaining);
@@ -100,17 +113,11 @@ public static void setTemplatePath(RoutingContext rc, Deployment deployment) {
100113
}
101114

102115
if (target == null) {
103-
// can't match the path
104-
return;
116+
return null;
105117
}
106118
}
107119

108-
var templatePath = requestMatch.template.template + target.template.template;
109-
if (templatePath.endsWith("/")) {
110-
templatePath = templatePath.substring(0, templatePath.length() - 1);
111-
}
112-
113-
setUrlPathTemplate(rc, templatePath);
120+
return requestMatch.template.template + target.template.template;
114121
}
115122

116123
private static String getPathWithoutPrefix(RoutingContext rc, Deployment deployment) {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.quarkus.it.micrometer.security;
2+
3+
import jakarta.ws.rs.GET;
4+
import jakarta.ws.rs.Path;
5+
import jakarta.ws.rs.PathParam;
6+
import jakarta.ws.rs.Produces;
7+
import jakarta.ws.rs.core.MediaType;
8+
9+
import io.smallrye.mutiny.Uni;
10+
11+
@Path("/secured/{message}")
12+
public class SecuredResourceOverlapping {
13+
14+
@GET
15+
@Path("/details")
16+
@Produces(MediaType.TEXT_PLAIN)
17+
public Uni<String> details(@PathParam("message") String message) {
18+
return Uni.createFrom().item("details of " + message);
19+
}
20+
}

integration-tests/micrometer-security/src/test/java/io/quarkus/it/micrometer/security/SecuredResourceTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,27 @@ void testMetricsForUnauthorizedRequest() {
2929
);
3030
}
3131

32+
@Test
33+
void testMetricsForUnauthorizedRequestWithOverlappingPaths() {
34+
// When two controllers have overlapping paths (e.g. /secured/{message} and /secured/{message}/details),
35+
// the URI template should still be resolved correctly for unauthorized requests.
36+
// See https://github.com/quarkusio/quarkus/issues/53030
37+
when().get("/secured/foo")
38+
.then()
39+
.statusCode(403);
40+
41+
when().get("/secured/foo/details")
42+
.then()
43+
.statusCode(403);
44+
45+
when().get("/q/metrics")
46+
.then()
47+
.statusCode(200)
48+
.body(
49+
allOf(
50+
not(containsString("/secured/foo")),
51+
containsString("/secured/{message}"),
52+
containsString("/secured/{message}/details")));
53+
}
54+
3255
}

0 commit comments

Comments
 (0)