Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DevConsoleCORSFilter to Dev UI JsonRPC WebSocket #33659

Merged
merged 6 commits into from
May 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.quarkus.vertx.http.deployment.devmode.console;
package io.quarkus.devui.deployment;

import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,13 @@
import io.quarkus.maven.dependency.GACTV;
import io.quarkus.qute.Qute;
import io.quarkus.runtime.util.ClassPathUtils;
import io.quarkus.vertx.http.deployment.FilterBuildItem;
phillip-kruger marked this conversation as resolved.
Show resolved Hide resolved
import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
import io.quarkus.vertx.http.deployment.RouteBuildItem;
import io.quarkus.vertx.http.deployment.webjar.WebJarBuildItem;
import io.quarkus.vertx.http.deployment.webjar.WebJarResourcesFilter;
import io.quarkus.vertx.http.deployment.webjar.WebJarResultsBuildItem;
import io.quarkus.vertx.http.runtime.devmode.DevConsoleCORSFilter;
import io.smallrye.common.annotation.Blocking;
import io.smallrye.common.annotation.NonBlocking;
import io.smallrye.mutiny.Multi;
Expand Down Expand Up @@ -120,6 +122,7 @@ public class DevUIProcessor {
@BuildStep(onlyIf = IsDevelopment.class)
@Record(ExecutionTime.STATIC_INIT)
void registerDevUiHandlers(
DevUIConfig devUIConfig,
MvnpmBuildItem mvnpmBuildItem,
List<DevUIRoutesBuildItem> devUIRoutesBuildItems,
List<StaticContentBuildItem> staticContentBuildItems,
Expand All @@ -133,6 +136,13 @@ void registerDevUiHandlers(
return;
}

if (devUIConfig.cors.enabled) {
routeProducer.produce(nonApplicationRootPathBuildItem.routeBuilder()
.orderedRoute(DEVUI + SLASH_ALL, -1 * FilterBuildItem.CORS)
.handler(new DevConsoleCORSFilter())
.build());
}

// Websocket for JsonRPC comms
routeProducer.produce(
nonApplicationRootPathBuildItem
Expand All @@ -155,8 +165,8 @@ void registerDevUiHandlers(
.route(route)
.handler(uihandler);

if (route.endsWith(DEVUI)) {
builder = builder.displayOnNotFoundPage("Dev UI 2.0");
if (route.endsWith(DEVUI + SLASH)) {
builder = builder.displayOnNotFoundPage("Dev UI (v2)");
routeProducer.produce(builder.build());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,23 @@ public Builder routeFunction(Function<Router, Route> routeFunction) {
}

public Builder routeFunction(String route, Consumer<Route> routeFunction) {
return orderedRoute(route, null, routeFunction);
}

@Override
public Builder route(String route) {
routeFunction(route, null);
return this;
}

@Override
public Builder orderedRoute(String route, Integer order) {
orderedRoute(route, order, null);
return this;
}

@Override
public Builder orderedRoute(String route, Integer order, Consumer<Route> routeFunction) {
if (isManagement && this.buildItem.managementRootPath != null) {
// The logic is slightly different when the management interface is enabled, as we have a single
// router mounted at the root.
Expand All @@ -283,7 +299,7 @@ public Builder routeFunction(String route, Consumer<Route> routeFunction) {
this.path = buildItem.getManagementRootPath() + route;
}
this.routerType = RouteBuildItem.RouteType.ABSOLUTE_ROUTE;
super.routeFunction(this.path, routeFunction);
super.orderedRoute(this.path, order, routeFunction);
return this;
}

Expand All @@ -304,14 +320,7 @@ public Builder routeFunction(String route, Consumer<Route> routeFunction) {
this.path = route;
this.routerType = RouteBuildItem.RouteType.ABSOLUTE_ROUTE;
}

super.routeFunction(this.path, routeFunction);
return this;
}

@Override
public Builder route(String route) {
routeFunction(route, null);
super.orderedRoute(this.path, order, routeFunction);
return this;
}

Expand Down Expand Up @@ -401,6 +410,7 @@ public Builder management() {
super.management();
return this;
}

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import io.quarkus.devconsole.spi.DevConsoleRuntimeTemplateInfoBuildItem;
import io.quarkus.devconsole.spi.DevConsoleTemplateInfoBuildItem;
import io.quarkus.devconsole.spi.DevConsoleWebjarBuildItem;
import io.quarkus.devui.deployment.DevUIConfig;
import io.quarkus.maven.dependency.ArtifactKey;
import io.quarkus.maven.dependency.GACT;
import io.quarkus.netty.runtime.virtual.VirtualChannel;
Expand All @@ -90,6 +91,7 @@
import io.quarkus.runtime.TemplateHtmlBuilder;
import io.quarkus.utilities.OS;
import io.quarkus.vertx.http.deployment.BodyHandlerBuildItem;
import io.quarkus.vertx.http.deployment.FilterBuildItem;
import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem;
import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
import io.quarkus.vertx.http.deployment.RouteBuildItem;
Expand Down Expand Up @@ -477,12 +479,6 @@ public void setupDevConsoleRoutes(
// if the handler is a proxy, then that means it's been produced by a recorder and therefore belongs in the regular runtime Vert.x instance
// otherwise this is handled in the setupDeploymentSideHandling method
if (!i.isDeploymentSide()) {
if (devUIConfig.cors.enabled) {
routeBuildItemBuildProducer.produce(nonApplicationRootPathBuildItem.routeBuilder()
.route("dev-v1/*")
.handler(new DevConsoleCORSFilter())
.build());
}
NonApplicationRootPathBuildItem.Builder builder = nonApplicationRootPathBuildItem.routeBuilder()
.routeFunction(
"dev-v1/" + groupAndArtifact.getKey() + "." + groupAndArtifact.getValue() + "/" + i.getPath(),
Expand All @@ -496,6 +492,13 @@ public void setupDevConsoleRoutes(
}
}

if (devUIConfig.cors.enabled) {
routeBuildItemBuildProducer.produce(nonApplicationRootPathBuildItem.routeBuilder()
.orderedRoute("dev-v1/*", -1 * FilterBuildItem.CORS)
.handler(new DevConsoleCORSFilter())
.build());
}

DevConsoleManager.registerHandler(new DevConsoleHttpHandler());
//must be last so the above routes have precedence
routeBuildItemBuildProducer.produce(nonApplicationRootPathBuildItem.routeBuilder()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package io.quarkus.vertx.http.devui;

import static org.hamcrest.Matchers.emptyOrNullString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusDevModeTest;
import io.restassured.RestAssured;

public class DevUICorsTest {

@RegisterExtension
static final QuarkusDevModeTest config = new QuarkusDevModeTest()
.withEmptyApplication();

@Test
public void testPreflightHttpLocalhostOrigin() {
String origin = "http://localhost:8080";
String methods = "GET,POST";
RestAssured.given()
.header("Origin", origin)
.header("Access-Control-Request-Method", methods)
.when()
.options("q/dev-ui/configuration-form-editor").then()
.statusCode(200)
.header("Access-Control-Allow-Origin", origin)
.header("Access-Control-Allow-Methods", methods)
.body(emptyOrNullString());
}

@Test
public void testPreflightHttpLocalhostIpOrigin() {
String origin = "http://127.0.0.1:8080";
String methods = "GET,POST";
RestAssured.given()
.header("Origin", origin)
.header("Access-Control-Request-Method", methods)
.when()
.options("q/dev-ui/configuration-form-editor").then()
.statusCode(200)
.header("Access-Control-Allow-Origin", origin)
.header("Access-Control-Allow-Methods", methods)
.body(emptyOrNullString());
}

@Test
public void testPreflightHttpsLocalhostOrigin() {
String origin = "https://localhost:8443";
String methods = "GET,POST";
RestAssured.given()
.header("Origin", origin)
.header("Access-Control-Request-Method", methods)
.when()
.options("q/dev-ui/configuration-form-editor").then()
.statusCode(200)
.header("Access-Control-Allow-Origin", origin)
.header("Access-Control-Allow-Methods", methods)
.body(emptyOrNullString());
}

@Test
public void testPreflightHttpsLocalhostIpOrigin() {
String origin = "https://127.0.0.1:8443";
String methods = "GET,POST";
RestAssured.given()
.header("Origin", origin)
.header("Access-Control-Request-Method", methods)
.when()
.options("q/dev-ui/configuration-form-editor").then()
.statusCode(200)
.header("Access-Control-Allow-Origin", origin)
.header("Access-Control-Allow-Methods", methods)
.body(emptyOrNullString());
}

@Test
public void testPreflightNonLocalhostOrigin() {
String methods = "GET,POST";
RestAssured.given()
.header("Origin", "https://quarkus.io/http://localhost")
.header("Access-Control-Request-Method", methods)
.when()
.options("q/dev-ui/configuration-form-editor").then()
.statusCode(403)
.header("Access-Control-Allow-Origin", nullValue())
.header("Access-Control-Allow-Methods", nullValue())
.body(emptyOrNullString());
}

@Test
public void testPreflightBadLocalhostOrigin() {
String methods = "GET,POST";
RestAssured.given()
.header("Origin", "http://localhost:8080/devui")
.header("Access-Control-Request-Method", methods)
.when()
.options("q/dev-ui/configuration-form-editor").then()
.statusCode(403)
.header("Access-Control-Allow-Origin", nullValue())
.body(emptyOrNullString());
}

@Test
public void testPreflightBadLocalhostIpOrigin() {
String methods = "GET,POST";
RestAssured.given()
.header("Origin", "http://127.0.0.1:8080/devui")
.header("Access-Control-Request-Method", methods)
.when()
.options("q/dev-ui/configuration-form-editor").then()
.statusCode(403)
.header("Access-Control-Allow-Origin", nullValue())
.body(emptyOrNullString());
}

@Test
public void testPreflightLocalhostOriginWithoutPort() {
String methods = "GET,POST";
RestAssured.given()
.header("Origin", "http://localhost")
.header("Access-Control-Request-Method", methods)
.when()
.options("q/dev-ui/configuration-form-editor").then()
.statusCode(403)
.header("Access-Control-Allow-Origin", nullValue())
.body(emptyOrNullString());
}

@Test
public void testSimpleRequestHttpLocalhostOrigin() {
String origin = "http://localhost:8080";
RestAssured.given()
.header("Origin", origin)
.when()
.get("q/dev-ui/configuration-form-editor").then()
.statusCode(200)
.header("Access-Control-Allow-Origin", origin)
.header("Access-Control-Allow-Methods", nullValue())
.body(not(emptyOrNullString()));
}

@Test
public void testSimpleRequestHttpLocalhostIpOrigin() {
String origin = "http://127.0.0.1:8080";
RestAssured.given()
.header("Origin", origin)
.when()
.get("q/dev-ui/configuration-form-editor").then()
.statusCode(200)
.header("Access-Control-Allow-Origin", origin)
.header("Access-Control-Allow-Methods", nullValue())
.body(not(emptyOrNullString()));
}

@Test
public void testSimpleRequestHttpsLocalhostOrigin() {
String origin = "https://localhost:8443";
RestAssured.given()
.header("Origin", origin)
.when()
.get("q/dev-ui/configuration-form-editor").then()
.statusCode(200)
.header("Access-Control-Allow-Origin", origin)
.header("Access-Control-Allow-Methods", nullValue())
.body(not(emptyOrNullString()));
}

@Test
public void testSimpleRequestHttpsLocalhostIpOrigin() {
String origin = "https://127.0.0.1:8443";
RestAssured.given()
.header("Origin", origin)
.when()
.get("q/dev-ui/configuration-form-editor").then()
.statusCode(200)
.header("Access-Control-Allow-Origin", origin)
.header("Access-Control-Allow-Methods", nullValue())
.body(not(emptyOrNullString()));
}

@Test
public void testSimpleRequestNonLocalhostOrigin() {
RestAssured.given()
.header("Origin", "https://quarkus.io/http://localhost")
.when()
.get("q/dev-ui/configuration-form-editor").then()
.statusCode(403)
.header("Access-Control-Allow-Origin", nullValue())
.body(emptyOrNullString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.quarkus.vertx.http.devui;

import static org.hamcrest.Matchers.emptyOrNullString;

import java.net.Inet4Address;
import java.net.UnknownHostException;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusDevModeTest;
import io.restassured.RestAssured;

public class DevUIRemoteCorsTest {

@RegisterExtension
static final QuarkusDevModeTest config = new QuarkusDevModeTest()
.setBuildSystemProperty("quarkus.http.host", "0.0.0.0")
.withEmptyApplication();

@Test
public void test() throws UnknownHostException {
String origin = Inet4Address.getLocalHost().toString();
if (origin.contains("/")) {
origin = "http://" + origin.split("/")[1] + ":8080";
}
String methods = "GET,POST";
RestAssured.given()
.header("Origin", origin)
.header("Access-Control-Request-Method", methods)
.when()
.options("q/dev-ui/configuration-form-editor").then()
.statusCode(403)
.body(emptyOrNullString());
}

}
Loading