This is an automated email from the ASF dual-hosted git repository.
zhoujinsong pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/amoro.git
The following commit(s) were added to refs/heads/master by this push:
new 1b094b410 [Hotfix][AMS] Unify authentication handling for dashboard
requests (#4312)
1b094b410 is described below
commit 1b094b4105ee84f77587bbc0f2e602d0471f2b1e
Author: Xu Bai <[email protected]>
AuthorDate: Wed Aug 12 14:53:21 2026 +0800
[Hotfix][AMS] Unify authentication handling for dashboard requests (#4312)
Remove single page token validation and related utility methods
---
.../apache/amoro/server/AmoroServiceContainer.java | 11 +--
.../dashboard/controller/TableController.java | 15 ---
.../amoro/server/dashboard/utils/CommonUtil.java | 72 --------------
.../dashboard/TestDashboardAuthentication.java | 108 +++++++++++++++++++++
4 files changed, 109 insertions(+), 97 deletions(-)
diff --git
a/amoro-ams/src/main/java/org/apache/amoro/server/AmoroServiceContainer.java
b/amoro-ams/src/main/java/org/apache/amoro/server/AmoroServiceContainer.java
index b9eb95f2d..e48961d9e 100644
--- a/amoro-ams/src/main/java/org/apache/amoro/server/AmoroServiceContainer.java
+++ b/amoro-ams/src/main/java/org/apache/amoro/server/AmoroServiceContainer.java
@@ -42,7 +42,6 @@ import org.apache.amoro.server.dashboard.DashboardServer;
import org.apache.amoro.server.dashboard.JavalinJsonMapper;
import org.apache.amoro.server.dashboard.response.ErrorResponse;
import org.apache.amoro.server.dashboard.utils.AmsUtil;
-import org.apache.amoro.server.dashboard.utils.CommonUtil;
import org.apache.amoro.server.ha.HighAvailabilityContainer;
import org.apache.amoro.server.ha.HighAvailabilityContainerFactory;
import org.apache.amoro.server.manager.EventsManager;
@@ -419,15 +418,7 @@ public class AmoroServiceContainer {
}
});
- httpServer.before(
- ctx -> {
- String token = ctx.queryParam("token");
- if (StringUtils.isNotEmpty(token)) {
- CommonUtil.checkSinglePageToken(ctx);
- } else {
- dashboardServer.preHandleRequest(ctx);
- }
- });
+ httpServer.before(dashboardServer::preHandleRequest);
httpServer.exception(
Exception.class,
(e, ctx) -> {
diff --git
a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/controller/TableController.java
b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/controller/TableController.java
index 81d29a425..772e8d0dd 100644
---
a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/controller/TableController.java
+++
b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/controller/TableController.java
@@ -50,7 +50,6 @@ import org.apache.amoro.server.dashboard.model.UpgradeStatus;
import org.apache.amoro.server.dashboard.response.OkResponse;
import org.apache.amoro.server.dashboard.response.PageResult;
import org.apache.amoro.server.dashboard.utils.AmsUtil;
-import org.apache.amoro.server.dashboard.utils.CommonUtil;
import org.apache.amoro.server.optimizing.OptimizingStatus;
import org.apache.amoro.server.persistence.TableRuntimeMeta;
import org.apache.amoro.server.process.TableProcessMeta;
@@ -630,20 +629,6 @@ public class TableController {
ctx.json(OkResponse.of(catalogs));
}
- /**
- * get single page query token.
- *
- * @param ctx - context for handling the request and response
- */
- public void getTableDetailTabToken(Context ctx) {
- String catalog = ctx.pathParam("catalog");
- String db = ctx.pathParam("db");
- String table = ctx.pathParam("table");
-
- String signCal = CommonUtil.generateTablePageToken(catalog, db, table);
- ctx.json(OkResponse.of(signCal));
- }
-
public void getTableTags(Context ctx) {
String catalog = ctx.pathParam("catalog");
String database = ctx.pathParam("db");
diff --git
a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/utils/CommonUtil.java
b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/utils/CommonUtil.java
index 378f60087..5538e4421 100644
---
a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/utils/CommonUtil.java
+++
b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/utils/CommonUtil.java
@@ -18,22 +18,15 @@
package org.apache.amoro.server.dashboard.utils;
-import io.javalin.http.Context;
-import org.apache.amoro.exception.SignatureCheckException;
-import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.telnet.TelnetClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetAddress;
-import java.util.HashMap;
-import java.util.Map;
public class CommonUtil {
private static final Logger LOG = LoggerFactory.getLogger(CommonUtil.class);
- private static final String[] TOKEN_WHITE_LIST = {"/login/current",
"/versionInfo"};
-
/**
* @param addresses support type
127.0.0.1:2181/ddd,host2:2181,host3:2181/service or
* music-hbase64.jd.163.org,music-hbase65.jd.163.org,
@@ -92,69 +85,4 @@ public class CommonUtil {
return false;
}
}
-
- /**
- * Check single page access token.
- *
- * @param ctx The context object containing the request information.
- * @throws SignatureCheckException If the token is invalid or missing.
- */
- public static void checkSinglePageToken(Context ctx) {
- // check if query parameters contain token key
- String token = ctx.queryParam("token");
-
- if (StringUtils.isNotEmpty(token)) {
- // regex extract catalog, db, table
- String url = ctx.req.getRequestURI();
- for (String whiteListUrl : TOKEN_WHITE_LIST) {
- if (url.contains(whiteListUrl)) {
- return;
- }
- }
- String catalog = ctx.queryParam("catalog");
- String db = ctx.queryParam("db");
- String table = ctx.queryParam("table");
- if (StringUtils.isEmpty(catalog) && StringUtils.isEmpty(db) &&
StringUtils.isEmpty(table)) {
- String[] splitResult = url.split("/");
- for (int i = 0; i < splitResult.length; i++) {
- switch (splitResult[i]) {
- case "catalogs":
- catalog = splitResult[i + 1];
- break;
- case "dbs":
- db = splitResult[i + 1];
- break;
- case "tables":
- table = splitResult[i + 1];
- break;
- }
- }
- }
- if (StringUtils.isEmpty(catalog)
- || StringUtils.isEmpty(db)
- || StringUtils.isEmpty(table)
- || !token.equals(generateTablePageToken(catalog, db, table))) {
- throw new SignatureCheckException();
- }
- }
- }
-
- /**
- * Generate the token for single table page access.
- *
- * @param catalog The catalog name.
- * @param db The database name.
- * @param table The table name.
- * @return The generated token.
- */
- public static String generateTablePageToken(String catalog, String db,
String table) {
- Map<String, String> params = new HashMap<>();
- params.put("catalog", catalog);
- params.put("db", db);
- params.put("table", table);
-
- String paramString =
ParamSignatureCalculator.generateParamStringWithValue(params);
- String plainText = String.format("%s%s%s", paramString, paramString,
paramString);
- return ParamSignatureCalculator.getMD5(plainText);
- }
}
diff --git
a/amoro-ams/src/test/java/org/apache/amoro/server/dashboard/TestDashboardAuthentication.java
b/amoro-ams/src/test/java/org/apache/amoro/server/dashboard/TestDashboardAuthentication.java
new file mode 100644
index 000000000..cecd5225e
--- /dev/null
+++
b/amoro-ams/src/test/java/org/apache/amoro/server/dashboard/TestDashboardAuthentication.java
@@ -0,0 +1,108 @@
+/*
+ * 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.amoro.server.dashboard;
+
+import org.apache.amoro.server.AmsEnvironment;
+import org.apache.amoro.shade.jackson2.com.fasterxml.jackson.databind.JsonNode;
+import
org.apache.amoro.shade.jackson2.com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+
+public class TestDashboardAuthentication {
+
+ private static final AmsEnvironment AMS =
AmsEnvironment.getIntegrationInstances();
+ private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+ // MD5 of "catalogxdbxtablex"
+ private static final String FORGED_SINGLE_PAGE_TOKEN =
"b383ed05fbc9d4b7e3888a70ed19398c";
+ private static final String FORGED_TOKEN_QUERY =
+ "token=" + FORGED_SINGLE_PAGE_TOKEN + "&catalog=x&db=x&table=x";
+
+ @BeforeAll
+ public static void beforeAll() throws Exception {
+ AMS.start();
+ }
+
+ @AfterAll
+ public static void afterAll() throws IOException {
+ AMS.stop();
+ }
+
+ @Test
+ public void testForgedSinglePageTokenCannotAccessCatalogApi()
+ throws IOException, InterruptedException {
+ HttpRequest request =
+ HttpRequest.newBuilder(
+ URI.create(AMS.getHttpUrl() + "/api/ams/v1/catalogs?" +
FORGED_TOKEN_QUERY))
+ .GET()
+ .build();
+
+ assertForbidden(
+ HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()),
"Signature check failed");
+ }
+
+ @Test
+ public void testForgedSinglePageTokenCannotAccessTerminalApi()
+ throws IOException, InterruptedException {
+ HttpRequest request =
+ HttpRequest.newBuilder(
+ URI.create(
+ AMS.getHttpUrl()
+ + "/api/ams/v1/terminal/catalogs/"
+ + AmsEnvironment.INTERNAL_ICEBERG_CATALOG
+ + "/execute?"
+ + FORGED_TOKEN_QUERY))
+ .header("Content-Type", "application/json")
+ .POST(HttpRequest.BodyPublishers.ofString("{\"sql\":\"select
1\"}"))
+ .build();
+
+ assertForbidden(
+ HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()),
"Signature check failed");
+ }
+
+ @Test
+ public void testForgedSinglePageTokenCannotBypassWebSessionAuthentication()
+ throws IOException, InterruptedException {
+ HttpRequest request =
+ HttpRequest.newBuilder(
+ URI.create(AMS.getHttpUrl() + "/api/ams/v1/catalogs?" +
FORGED_TOKEN_QUERY))
+ .header("X-Request-Source", "Web")
+ .GET()
+ .build();
+
+ assertForbidden(
+ HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()),
"Please login first");
+ }
+
+ private static void assertForbidden(HttpResponse<String> response, String
expectedMessage)
+ throws IOException {
+ JsonNode responseBody = OBJECT_MAPPER.readTree(response.body());
+ Assertions.assertEquals(403, responseBody.get("code").asInt());
+ Assertions.assertEquals(expectedMessage,
responseBody.get("message").asText());
+ }
+}