Skip to content

Commit 89b1d0b

Browse files
Merge pull request #48770 from phillip-kruger/devui-relative-importmap
Dev UI: Allow a context root url via config
2 parents b26d319 + 0c69aaf commit 89b1d0b

File tree

7 files changed

+129
-56
lines changed

7 files changed

+129
-56
lines changed

extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/BuildTimeContentProcessor.java

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,12 @@ public class BuildTimeContentProcessor {
9898
* This will be merged into the final importmap
9999
*/
100100
@BuildStep(onlyIf = IsLocalDevelopment.class)
101-
InternalImportMapBuildItem createKnownInternalImportMap(NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem) {
101+
InternalImportMapBuildItem createKnownInternalImportMap(NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
102+
DevUIConfig config) {
102103

103-
String contextRoot = nonApplicationRootPathBuildItem.getNonApplicationRootPath() + EndpointsProcessor.DEV_UI + SLASH;
104+
String devUIContext = config.contextRoot().orElse("");
105+
String contextRoot = devUIContext + nonApplicationRootPathBuildItem.getNonApplicationRootPath()
106+
+ EndpointsProcessor.DEV_UI + SLASH;
104107

105108
InternalImportMapBuildItem internalImportMapBuildItem = new InternalImportMapBuildItem();
106109

@@ -292,13 +295,15 @@ private Map<String, Object> getBuildTimeDataForCard(CurateOutcomeBuildItem curat
292295
* @param internalImportMapProducer
293296
*/
294297
@BuildStep(onlyIf = IsLocalDevelopment.class)
295-
void createBuildTimeConstJsTemplate(CurateOutcomeBuildItem curateOutcomeBuildItem,
298+
void createBuildTimeConstJsTemplate(DevUIConfig config,
299+
CurateOutcomeBuildItem curateOutcomeBuildItem,
296300
NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
297301
List<BuildTimeConstBuildItem> buildTimeConstBuildItems,
298302
BuildProducer<QuteTemplateBuildItem> quteTemplateProducer,
299303
BuildProducer<InternalImportMapBuildItem> internalImportMapProducer) {
300304

301-
String contextRoot = nonApplicationRootPathBuildItem.getNonApplicationRootPath() + EndpointsProcessor.DEV_UI + SLASH;
305+
String contextRoot = config.contextRoot().orElse("") + nonApplicationRootPathBuildItem.getNonApplicationRootPath()
306+
+ EndpointsProcessor.DEV_UI + SLASH;
302307

303308
QuteTemplateBuildItem quteTemplateBuildItem = new QuteTemplateBuildItem(
304309
QuteTemplateBuildItem.DEV_UI);
@@ -361,7 +366,7 @@ void gatherMvnpmJars(BuildProducer<MvnpmBuildItem> mvnpmProducer, CurateOutcomeB
361366
* @return The QuteTemplate Build item that will create the end result
362367
*/
363368
@BuildStep(onlyIf = IsLocalDevelopment.class)
364-
QuteTemplateBuildItem createIndexHtmlTemplate(
369+
QuteTemplateBuildItem createIndexHtmlTemplate(DevUIConfig config,
365370
MvnpmBuildItem mvnpmBuildItem,
366371
ThemeVarsBuildItem themeVarsBuildItem,
367372
NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
@@ -376,7 +381,10 @@ QuteTemplateBuildItem createIndexHtmlTemplate(
376381
aggregator.addMappings(importMap);
377382
}
378383

379-
Imports imports = aggregator.aggregate(nonApplicationRootPathBuildItem.getNonApplicationRootPath(), false);
384+
String devUIContext = config.contextRoot().orElse("");
385+
386+
Imports imports = aggregator.aggregate(devUIContext + nonApplicationRootPathBuildItem.getNonApplicationRootPath(),
387+
false);
380388
Map<String, String> currentImportMap = imports.getImports();
381389
Map<String, String> relocationMap = relocationImportMapBuildItem.getRelocationMap();
382390
for (Map.Entry<String, String> relocation : relocationMap.entrySet()) {
@@ -397,7 +405,7 @@ QuteTemplateBuildItem createIndexHtmlTemplate(
397405

398406
String themeVars = themeVarsBuildItem.getTemplateValue();
399407
String nonApplicationRoot = nonApplicationRootPathBuildItem.getNonApplicationRootPath();
400-
String contextRoot = nonApplicationRoot + EndpointsProcessor.DEV_UI + SLASH;
408+
String contextRoot = devUIContext + nonApplicationRoot + EndpointsProcessor.DEV_UI + SLASH;
401409

402410
Map<String, Object> data = Map.of(
403411
"nonApplicationRoot", nonApplicationRoot,
@@ -588,9 +596,11 @@ private void addApplicationInfoBuildTimeData(BuildTimeConstBuildItem internalBui
588596
applicationInfo.put("groupId", groupId);
589597
String artifactId = appArtifact.getArtifactId();
590598
applicationInfo.put("artifactId", artifactId);
591-
// Add version info
599+
592600
String contextRoot = nonApplicationRootPathBuildItem.getNonApplicationRootPath() + EndpointsProcessor.DEV_UI + SLASH;
593601
applicationInfo.put("contextRoot", contextRoot);
602+
603+
// Add version info
594604
applicationInfo.put("quarkusVersion", Version.getVersion());
595605
applicationInfo.put("applicationName", config.getOptionalValue("quarkus.application.name", String.class).orElse(""));
596606
applicationInfo.put("applicationVersion",

extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/DevUIConfig.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ public interface DevUIConfig {
4242
*/
4343
Optional<List<String>> hosts();
4444

45+
/**
46+
* Set a context root for dev-ui. This is useful for remote environments or online IDEs
47+
*
48+
* @return The dev-ui context root
49+
*/
50+
Optional<String> contextRoot();
51+
4552
/**
4653
* Workspace configuration.
4754
*/

extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/DevUIProcessor.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@
5555
import io.quarkus.devui.deployment.extension.Codestart;
5656
import io.quarkus.devui.deployment.extension.Extension;
5757
import io.quarkus.devui.deployment.jsonrpc.DevUIDatabindCodec;
58-
import io.quarkus.devui.runtime.DevUICORSFilter;
5958
import io.quarkus.devui.runtime.DevUIRecorder;
6059
import io.quarkus.devui.runtime.VertxRouteInfoService;
6160
import io.quarkus.devui.runtime.comms.JsonRpcRouter;
@@ -165,7 +164,7 @@ void registerDevUiHandlers(
165164
if (devUIConfig.cors().enabled()) {
166165
routeProducer.produce(nonApplicationRootPathBuildItem.routeBuilder()
167166
.orderedRoute(DEVUI + SLASH_ALL, -1 * FilterBuildItem.CORS)
168-
.handler(new DevUICORSFilter())
167+
.handler(recorder.createDevUICorsFilter(devUIConfig.hosts().orElse(null)))
169168
.build());
170169
}
171170

extensions/vertx-http/runtime-dev/src/main/java/io/quarkus/devui/runtime/DevUICORSFilter.java

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package io.quarkus.devui.runtime;
22

33
import java.time.Duration;
4+
import java.util.ArrayList;
45
import java.util.List;
56
import java.util.Optional;
7+
import java.util.regex.Matcher;
8+
import java.util.regex.Pattern;
69

710
import org.eclipse.microprofile.config.ConfigProvider;
811
import org.jboss.logging.Logger;
@@ -22,23 +25,38 @@ public class DevUICORSFilter implements Handler<RoutingContext> {
2225
private static final String HTTPS_PORT_CONFIG_PROP = "quarkus.http.ssl-port";
2326
private static final String LOCAL_HOST = "localhost";
2427
private static final String LOCAL_HOST_IP = "127.0.0.1";
25-
private static final String HTTP_LOCAL_HOST = "http://" + LOCAL_HOST;
26-
private static final String HTTPS_LOCAL_HOST = "https://" + LOCAL_HOST;
27-
private static final String HTTP_LOCAL_HOST_IP = "http://" + LOCAL_HOST_IP;
28-
private static final String HTTPS_LOCAL_HOST_IP = "https://" + LOCAL_HOST_IP;
28+
private static final String HTTP = "http://";
29+
private static final String HTTPS = "https://";
30+
private static final String HTTP_LOCAL_HOST = HTTP + LOCAL_HOST;
31+
private static final String HTTPS_LOCAL_HOST = HTTPS + LOCAL_HOST;
32+
private static final String HTTP_LOCAL_HOST_IP = HTTP + LOCAL_HOST_IP;
33+
private static final String HTTPS_LOCAL_HOST_IP = HTTPS + LOCAL_HOST_IP;
2934
private static final String CHROME_EXTENSION = "chrome-extension://";
3035

31-
public DevUICORSFilter() {
36+
private final List<String> hosts;
37+
private final List<Pattern> hostsPatterns;
38+
39+
public DevUICORSFilter(List<String> hosts) {
40+
this.hosts = hosts;
41+
this.hostsPatterns = DevUIFilterHelper.detectPatterns(this.hosts);
3242
}
3343

34-
private static CORSFilter corsFilter() {
44+
private static CORSFilter corsFilter(String allowedHost) {
3545
int httpPort = ConfigProvider.getConfig().getValue(HTTP_PORT_CONFIG_PROP, int.class);
3646
int httpsPort = ConfigProvider.getConfig().getValue(HTTPS_PORT_CONFIG_PROP, int.class);
3747
CORSConfig config = new CORSConfig() {
3848
@Override
3949
public Optional<List<String>> origins() {
40-
return Optional.of(List.of(HTTP_LOCAL_HOST + ":" + httpPort, HTTP_LOCAL_HOST_IP + ":" + httpPort,
41-
HTTPS_LOCAL_HOST + ":" + httpsPort, HTTPS_LOCAL_HOST_IP + ":" + httpsPort));
50+
List<String> validOrigins = new ArrayList<>();
51+
validOrigins.add(HTTP_LOCAL_HOST + ":" + httpPort);
52+
validOrigins.add(HTTPS_LOCAL_HOST + ":" + httpsPort);
53+
validOrigins.add(HTTP_LOCAL_HOST_IP + ":" + httpPort);
54+
validOrigins.add(HTTPS_LOCAL_HOST_IP + ":" + httpsPort);
55+
56+
if (allowedHost != null) {
57+
validOrigins.add(allowedHost);
58+
}
59+
return Optional.of(validOrigins);
4260
}
4361

4462
@Override
@@ -74,20 +92,47 @@ public void handle(RoutingContext event) {
7492
HttpServerRequest request = event.request();
7593
HttpServerResponse response = event.response();
7694
String origin = request.getHeader(HttpHeaders.ORIGIN);
77-
if (origin == null) {
78-
corsFilter().handle(event);
95+
if (origin == null || isLocalHost(origin)) {
96+
corsFilter(null).handle(event);
97+
} else if (isConfiguredHost(origin) || isConfiguredHostPattern(origin)) {
98+
corsFilter(origin).handle(event);
7999
} else {
80-
if (origin.startsWith(HTTP_LOCAL_HOST) || origin.startsWith(HTTPS_LOCAL_HOST)
81-
|| origin.startsWith(HTTP_LOCAL_HOST_IP) || origin.startsWith(HTTPS_LOCAL_HOST_IP)) {
82-
corsFilter().handle(event);
83-
} else {
84-
if (!origin.startsWith(CHROME_EXTENSION)) {
85-
LOG.errorf("Only localhost origin is allowed, but Origin header value is: %s", origin);
100+
if (!origin.startsWith(CHROME_EXTENSION)) {
101+
LOG.errorf("Only localhost origin is allowed, but Origin header value is: %s", origin);
102+
}
103+
response.setStatusCode(403);
104+
response.setStatusMessage("CORS Rejected - Invalid origin");
105+
response.end();
106+
}
107+
}
108+
109+
private boolean isLocalHost(String origin) {
110+
return origin.startsWith(HTTP_LOCAL_HOST) || origin.startsWith(HTTPS_LOCAL_HOST)
111+
|| origin.startsWith(HTTP_LOCAL_HOST_IP) || origin.startsWith(HTTPS_LOCAL_HOST_IP);
112+
}
113+
114+
private boolean isConfiguredHost(String origin) {
115+
if (this.hosts != null) {
116+
for (String configuredHost : this.hosts) {
117+
if (origin.startsWith(HTTP + configuredHost) ||
118+
origin.startsWith(HTTPS + configuredHost)) {
119+
return true;
120+
}
121+
}
122+
}
123+
return false;
124+
}
125+
126+
private boolean isConfiguredHostPattern(String origin) {
127+
if (this.hostsPatterns != null && !this.hostsPatterns.isEmpty()) {
128+
// Regex
129+
for (Pattern pat : this.hostsPatterns) {
130+
Matcher matcher = pat.matcher(origin);
131+
if (matcher.matches()) {
132+
return true;
86133
}
87-
response.setStatusCode(403);
88-
response.setStatusMessage("CORS Rejected - Invalid origin");
89-
response.end();
90134
}
91135
}
136+
return false;
92137
}
93138
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.quarkus.devui.runtime;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import java.util.regex.Pattern;
6+
import java.util.regex.PatternSyntaxException;
7+
8+
public class DevUIFilterHelper {
9+
10+
public static List<Pattern> detectPatterns(List<String> hosts) {
11+
if (hosts != null && !hosts.isEmpty()) {
12+
List<Pattern> pat = new ArrayList<>();
13+
for (String h : hosts) {
14+
Pattern p = toPattern(h);
15+
if (p != null) {
16+
pat.add(p);
17+
}
18+
}
19+
if (!pat.isEmpty()) {
20+
return pat;
21+
}
22+
}
23+
return null;
24+
}
25+
26+
private static Pattern toPattern(String regex) {
27+
try {
28+
return Pattern.compile(regex);
29+
} catch (PatternSyntaxException e) {
30+
return null;
31+
}
32+
}
33+
34+
}

extensions/vertx-http/runtime-dev/src/main/java/io/quarkus/devui/runtime/DevUIRecorder.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ public Handler<RoutingContext> createLocalHostOnlyFilter(List<String> hosts) {
136136
return new LocalHostOnlyFilter(hosts);
137137
}
138138

139+
public Handler<RoutingContext> createDevUICorsFilter(List<String> hosts) {
140+
return new DevUICORSFilter(hosts);
141+
}
142+
139143
private static final class DeleteDirectoryRunnable implements Runnable {
140144

141145
private final Path directory;

extensions/vertx-http/runtime-dev/src/main/java/io/quarkus/devui/runtime/LocalHostOnlyFilter.java

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@
44
import java.net.URI;
55
import java.net.URISyntaxException;
66
import java.net.URL;
7-
import java.util.ArrayList;
87
import java.util.List;
98
import java.util.regex.Matcher;
109
import java.util.regex.Pattern;
11-
import java.util.regex.PatternSyntaxException;
1210

1311
import org.jboss.logging.Logger;
1412

@@ -27,7 +25,7 @@ public class LocalHostOnlyFilter implements Handler<RoutingContext> {
2725

2826
public LocalHostOnlyFilter(List<String> hosts) {
2927
this.hosts = hosts;
30-
this.hostsPatterns = detectPatterns();
28+
this.hostsPatterns = DevUIFilterHelper.detectPatterns(this.hosts);
3129
}
3230

3331
@Override
@@ -68,28 +66,4 @@ private boolean hostIsValid(RoutingContext event) {
6866
}
6967
return false;
7068
}
71-
72-
private List<Pattern> detectPatterns() {
73-
if (this.hosts != null && !this.hosts.isEmpty()) {
74-
List<Pattern> pat = new ArrayList<>();
75-
for (String h : this.hosts) {
76-
Pattern p = toPattern(h);
77-
if (p != null) {
78-
pat.add(p);
79-
}
80-
}
81-
if (!pat.isEmpty()) {
82-
return pat;
83-
}
84-
}
85-
return null;
86-
}
87-
88-
private Pattern toPattern(String regex) {
89-
try {
90-
return Pattern.compile(regex);
91-
} catch (PatternSyntaxException e) {
92-
return null;
93-
}
94-
}
9569
}

0 commit comments

Comments
 (0)