Skip to content

Commit

Permalink
Merge pull request #33659 from phillip-kruger/dev-ui-cors
Browse files Browse the repository at this point in the history
Add DevConsoleCORSFilter to Dev UI JsonRPC WebSocket
  • Loading branch information
phillip-kruger authored May 31, 2023
2 parents 51c7d6d + 8b795d6 commit eceba88
Show file tree
Hide file tree
Showing 7 changed files with 275 additions and 18 deletions.
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;
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

0 comments on commit eceba88

Please sign in to comment.