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);
