This is an automated email from the ASF dual-hosted git repository.

jackietien pushed a commit to branch MaintainAuth
in repository https://gitbox.apache.org/repos/asf/iotdb.git

commit f69aa9833395c5d52a76d51422a4a7cd7a60d48c
Author: JackieTien97 <[email protected]>
AuthorDate: Fri Feb 7 19:47:01 2025 +0800

    Support authentication for maintain statement
---
 .../org/apache/iotdb/db/it/utils/TestUtils.java    |  34 ++++
 .../it/query/recent/IoTDBMaintainAuthIT.java       | 215 +++++++++++++++++++++
 .../it/query/recent/IoTDBQueryAuthIT.java          |   3 +-
 .../plan/execution/config/ConfigExecution.java     |   4 +-
 .../execution/config/TableConfigTaskVisitor.java   |   1 +
 .../config/executor/ClusterConfigTaskExecutor.java |  17 +-
 6 files changed, 267 insertions(+), 7 deletions(-)

diff --git 
a/integration-test/src/test/java/org/apache/iotdb/db/it/utils/TestUtils.java 
b/integration-test/src/test/java/org/apache/iotdb/db/it/utils/TestUtils.java
index 15de78c0e3d..330a2475915 100644
--- a/integration-test/src/test/java/org/apache/iotdb/db/it/utils/TestUtils.java
+++ b/integration-test/src/test/java/org/apache/iotdb/db/it/utils/TestUtils.java
@@ -268,6 +268,40 @@ public class TestUtils {
     }
   }
 
+  public static void tableExecuteTest(String sql, String userName, String 
password) {
+    try (Connection connection =
+        EnvFactory.getEnv().getConnection(userName, password, 
BaseEnv.TABLE_SQL_DIALECT)) {
+      connection.setClientInfo("time_zone", "+00:00");
+      try (Statement statement = connection.createStatement()) {
+        statement.execute(sql);
+      }
+    } catch (SQLException e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  public static void tableQueryNoVerifyResultTest(
+      String sql, String[] expectedHeader, String userName, String password, 
String database) {
+    try (Connection connection =
+        EnvFactory.getEnv().getConnection(userName, password, 
BaseEnv.TABLE_SQL_DIALECT)) {
+      connection.setClientInfo("time_zone", "+00:00");
+      try (Statement statement = connection.createStatement()) {
+        statement.execute("use " + database);
+        try (ResultSet resultSet = statement.executeQuery(sql)) {
+          ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
+          for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
+            assertEquals(expectedHeader[i - 1], 
resultSetMetaData.getColumnName(i));
+          }
+          assertEquals(expectedHeader.length, 
resultSetMetaData.getColumnCount());
+        }
+      }
+    } catch (SQLException e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
   public static void tableResultSetEqualTest(
       String sql,
       String[] expectedHeader,
diff --git 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBMaintainAuthIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBMaintainAuthIT.java
new file mode 100644
index 00000000000..df340a46cd0
--- /dev/null
+++ 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBMaintainAuthIT.java
@@ -0,0 +1,215 @@
+/*
+ * 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.iotdb.relational.it.query.recent;
+
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.it.framework.IoTDBTestRunner;
+import org.apache.iotdb.itbase.category.TableClusterIT;
+import org.apache.iotdb.itbase.category.TableLocalStandaloneIT;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import static org.apache.iotdb.db.it.utils.TestUtils.prepareTableData;
+import static org.apache.iotdb.db.it.utils.TestUtils.tableAssertTestFail;
+import static org.apache.iotdb.db.it.utils.TestUtils.tableExecuteTest;
+import static 
org.apache.iotdb.db.it.utils.TestUtils.tableQueryNoVerifyResultTest;
+
+@RunWith(IoTDBTestRunner.class)
+@Category({TableLocalStandaloneIT.class, TableClusterIT.class})
+public class IoTDBMaintainAuthIT {
+  private static final String DATABASE_NAME = "test";
+  private static final String CREATE_USER_FORMAT = "create user %s '%s'";
+  private static final String USER_1 = "user1";
+  private static final String USER_2 = "user2";
+  private static final String PASSWORD = "password";
+
+  private static final String[] createSqls =
+      new String[] {
+        "CREATE DATABASE " + DATABASE_NAME,
+        "USE " + DATABASE_NAME,
+        "CREATE TABLE table1(device_id STRING TAG, s1 INT32 FIELD)",
+        "INSERT INTO table1(time,device_id,s1) values(1, 'd1', 1)",
+        String.format(CREATE_USER_FORMAT, USER_1, PASSWORD),
+        "GRANT MAINTAIN TO USER " + USER_1,
+        "GRANT SELECT ON TABLE table1 TO USER " + USER_1,
+        "GRANT SELECT ON information_schema.queries TO USER " + USER_1,
+        String.format(CREATE_USER_FORMAT, USER_2, PASSWORD)
+      };
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    EnvFactory.getEnv().initClusterEnvironment();
+    prepareTableData(createSqls);
+  }
+
+  @AfterClass
+  public static void tearDown() throws Exception {
+    EnvFactory.getEnv().cleanClusterEnvironment();
+  }
+
+  @Test
+  public void maintainAuthTest() {
+    // case 1: explain
+    // user1 with select on table1
+    String[] expectedHeader = new String[] {"distribution plan"};
+    tableQueryNoVerifyResultTest(
+        "explain select * from table1", expectedHeader, USER_1, PASSWORD, 
DATABASE_NAME);
+    // user2 without select on table1
+    tableAssertTestFail(
+        "explain select * from test.table1",
+        TSStatusCode.NO_PERMISSION.getStatusCode()
+            + ": Access Denied: No permissions for this operation, please add 
privilege SELECT ON test.table1",
+        USER_2,
+        PASSWORD);
+
+    // case 2: explain analyze [verbose]
+    // user1 with select on table1
+    expectedHeader = new String[] {"Explain Analyze"};
+    tableQueryNoVerifyResultTest(
+        "explain analyze select * from table1", expectedHeader, USER_1, 
PASSWORD, DATABASE_NAME);
+    tableQueryNoVerifyResultTest(
+        "explain analyze verbose select * from table1",
+        expectedHeader,
+        USER_1,
+        PASSWORD,
+        DATABASE_NAME);
+    // user2 without select on table1
+    tableAssertTestFail(
+        "explain analyze select * from test.table1",
+        TSStatusCode.NO_PERMISSION.getStatusCode()
+            + ": Access Denied: No permissions for this operation, please add 
privilege SELECT ON test.table1",
+        USER_2,
+        PASSWORD);
+    tableAssertTestFail(
+        "explain analyze verbose select * from test.table1",
+        TSStatusCode.NO_PERMISSION.getStatusCode()
+            + ": Access Denied: No permissions for this operation, please add 
privilege SELECT ON test.table1",
+        USER_2,
+        PASSWORD);
+
+    // case 3: show current_sql_dialect
+    expectedHeader = new String[] {"CurrentSqlDialect"};
+    tableQueryNoVerifyResultTest(
+        "SHOW CURRENT_SQL_DIALECT", expectedHeader, USER_2, PASSWORD, 
DATABASE_NAME);
+
+    // case 4: show current_user
+    expectedHeader = new String[] {"CurrentUser"};
+    tableQueryNoVerifyResultTest(
+        "SHOW CURRENT_USER", expectedHeader, USER_2, PASSWORD, DATABASE_NAME);
+
+    // case 5: show version
+    expectedHeader = new String[] {"Version", "BuildInfo"};
+    tableQueryNoVerifyResultTest("SHOW VERSION", expectedHeader, USER_2, 
PASSWORD, DATABASE_NAME);
+
+    // case 6: show current_timestamp
+    expectedHeader = new String[] {"CurrentTimestamp"};
+    tableQueryNoVerifyResultTest(
+        "SHOW CURRENT_TIMESTAMP", expectedHeader, USER_2, PASSWORD, 
DATABASE_NAME);
+
+    // case 7: show variables
+    expectedHeader = new String[] {"Variable", "Value"};
+    // user1 with MAINTAIN
+    tableQueryNoVerifyResultTest("SHOW VARIABLES", expectedHeader, USER_1, 
PASSWORD, DATABASE_NAME);
+    // user2 without MAINTAIN
+    tableAssertTestFail(
+        "SHOW VARIABLES",
+        TSStatusCode.NO_PERMISSION.getStatusCode()
+            + ": Access Denied: No permissions for this operation, please add 
privilege MAINTAIN",
+        USER_2,
+        PASSWORD);
+
+    // case 8: show cluster_id
+    expectedHeader = new String[] {"ClusterId"};
+    // user1 with MAINTAIN
+    tableQueryNoVerifyResultTest(
+        "SHOW CLUSTER_ID", expectedHeader, USER_1, PASSWORD, DATABASE_NAME);
+    // user2 without MAINTAIN
+    tableAssertTestFail(
+        "SHOW CLUSTER_ID",
+        TSStatusCode.NO_PERMISSION.getStatusCode()
+            + ": Access Denied: No permissions for this operation, please add 
privilege MAINTAIN",
+        USER_2,
+        PASSWORD);
+
+    // case 9: flush
+    // user1 with MAINTAIN
+    tableExecuteTest("FLUSH", USER_1, PASSWORD);
+    // user2 without MAINTAIN
+    tableAssertTestFail(
+        "FLUSH",
+        TSStatusCode.NO_PERMISSION.getStatusCode()
+            + ": Access Denied: No permissions for this operation, please add 
privilege MAINTAIN",
+        USER_2,
+        PASSWORD);
+
+    // case 10: clear cache
+    // user1 with MAINTAIN
+    tableExecuteTest("CLEAR CACHE", USER_1, PASSWORD);
+    // user2 without MAINTAIN
+    tableAssertTestFail(
+        "CLEAR CACHE",
+        TSStatusCode.NO_PERMISSION.getStatusCode()
+            + ": Access Denied: No permissions for this operation, please add 
privilege MAINTAIN",
+        USER_2,
+        PASSWORD);
+
+    // case 11: set configuration
+    // user1 with MAINTAIN
+    tableExecuteTest("SET CONFIGURATION query_timeout_threshold='100000'", 
USER_1, PASSWORD);
+    // user2 without MAINTAIN
+    tableAssertTestFail(
+        "SET CONFIGURATION query_timeout_threshold='100000'",
+        TSStatusCode.NO_PERMISSION.getStatusCode()
+            + ": Access Denied: No permissions for this operation, please add 
privilege MAINTAIN",
+        USER_2,
+        PASSWORD);
+
+    // case 12: show queries
+    // user1 with select on information_schema.queries
+    tableExecuteTest("show queries", USER_1, PASSWORD);
+    // user2 without select on information_schema.queries
+    tableAssertTestFail(
+        "show queries",
+        TSStatusCode.NO_PERMISSION.getStatusCode()
+            + ": Access Denied: No permissions for this operation, please add 
privilege SELECT ON information_schema.queries",
+        USER_2,
+        PASSWORD);
+
+    // case 12: kill query
+    // user1 with MAINTAIN
+    tableAssertTestFail(
+        "kill query '20250206_093300_00001_100'",
+        TSStatusCode.NO_SUCH_QUERY.getStatusCode() + ": No such query",
+        USER_1,
+        PASSWORD);
+    // user2 without MAINTAIN
+    tableAssertTestFail(
+        "kill query '20250206_093300_00001_100'",
+        TSStatusCode.NO_PERMISSION.getStatusCode()
+            + ": Access Denied: No permissions for this operation, please add 
privilege MAINTAIN",
+        USER_2,
+        PASSWORD);
+  }
+}
diff --git 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBQueryAuthIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBQueryAuthIT.java
index 88ac066261f..4557a66e7d1 100644
--- 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBQueryAuthIT.java
+++ 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBQueryAuthIT.java
@@ -81,7 +81,6 @@ public class IoTDBQueryAuthIT {
 
   @BeforeClass
   public static void setUp() throws Exception {
-    
EnvFactory.getEnv().getConfig().getCommonConfig().setEnableCrossSpaceCompaction(false);
     EnvFactory.getEnv().initClusterEnvironment();
     prepareTableData(createSqls);
   }
@@ -92,7 +91,7 @@ public class IoTDBQueryAuthIT {
   }
 
   @Test
-  public void normalFillTest() {
+  public void queryAuthTest() {
     // case 1: user1 with SELECT ON ANY
     String[] expectedHeader1 = new String[] {"time", "device_id", "s1"};
     String[] retArray1 =
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/ConfigExecution.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/ConfigExecution.java
index 92a8a979d65..17c75ee30a8 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/ConfigExecution.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/ConfigExecution.java
@@ -93,7 +93,9 @@ public class ConfigExecution implements IQueryExecution {
                   TSStatusCode.ROLE_NOT_EXIST.getStatusCode(),
                   TSStatusCode.USER_ALREADY_HAS_ROLE.getStatusCode(),
                   TSStatusCode.USER_NOT_HAS_ROLE.getStatusCode(),
-                  TSStatusCode.NOT_HAS_PRIVILEGE_GRANTOPT.getStatusCode())));
+                  TSStatusCode.NOT_HAS_PRIVILEGE_GRANTOPT.getStatusCode(),
+                  TSStatusCode.SEMANTIC_ERROR.getStatusCode(),
+                  TSStatusCode.NO_SUCH_QUERY.getStatusCode())));
 
   private final MPPQueryContext context;
   private final ExecutorService executor;
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java
index 221b0255ea8..be17215ad86 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java
@@ -974,6 +974,7 @@ public class TableConfigTaskVisitor extends 
AstVisitor<IConfigTask, MPPQueryCont
   @Override
   protected IConfigTask visitKillQuery(KillQuery node, MPPQueryContext 
context) {
     context.setQueryType(QueryType.WRITE);
+    
accessControl.checkUserHasMaintainPrivilege(context.getSession().getUserName());
     return new KillQueryTask(node);
   }
 
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/ClusterConfigTaskExecutor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/ClusterConfigTaskExecutor.java
index 2f3ed1a0f2d..ff7e13416a7 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/ClusterConfigTaskExecutor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/ClusterConfigTaskExecutor.java
@@ -150,7 +150,6 @@ import org.apache.iotdb.db.exception.BatchProcessException;
 import org.apache.iotdb.db.exception.StorageEngineException;
 import org.apache.iotdb.db.exception.metadata.PathNotExistException;
 import org.apache.iotdb.db.exception.metadata.SchemaQuotaExceededException;
-import org.apache.iotdb.db.exception.sql.SemanticException;
 import org.apache.iotdb.db.pipe.agent.PipeDataNodeAgent;
 import org.apache.iotdb.db.protocol.client.ConfigNodeClient;
 import org.apache.iotdb.db.protocol.client.ConfigNodeClientManager;
@@ -1344,19 +1343,29 @@ public class ClusterConfigTaskExecutor implements 
IConfigTaskExecutor {
   public SettableFuture<ConfigTaskResult> killQuery(final KillQueryStatement 
killQueryStatement) {
     int dataNodeId = -1;
     String queryId = killQueryStatement.getQueryId();
+    SettableFuture<ConfigTaskResult> future = SettableFuture.create();
     if (!killQueryStatement.isKillAll()) {
       String[] splits = queryId.split("_");
       try {
         // We just judge the input queryId has three '_' and the DataNodeId 
from it is non-negative
         // here
         if (splits.length != 4 || ((dataNodeId = Integer.parseInt(splits[3])) 
< 0)) {
-          throw new SemanticException("Please ensure your input <queryId> is 
correct");
+          future.setException(
+              new IoTDBException(
+                  "Please ensure your input <queryId> is correct",
+                  TSStatusCode.SEMANTIC_ERROR.getStatusCode(),
+                  true));
+          return future;
         }
       } catch (NumberFormatException e) {
-        throw new SemanticException("Please ensure your input <queryId> is 
correct");
+        future.setException(
+            new IoTDBException(
+                "Please ensure your input <queryId> is correct",
+                TSStatusCode.SEMANTIC_ERROR.getStatusCode(),
+                true));
+        return future;
       }
     }
-    SettableFuture<ConfigTaskResult> future = SettableFuture.create();
     try (ConfigNodeClient client =
         
CONFIG_NODE_CLIENT_MANAGER.borrowClient(ConfigNodeInfo.CONFIG_REGION_ID)) {
       final TSStatus executionStatus = client.killQuery(queryId, dataNodeId);

Reply via email to