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());
+  }
+}

Reply via email to