This is an automated email from the ASF dual-hosted git repository. jiangtian pushed a commit to branch strong_password in repository https://gitbox.apache.org/repos/asf/iotdb.git
commit 9812ddddbcfb3194a329b528afb2f7e260244f3d Author: Tian Jiang <[email protected]> AuthorDate: Mon Jun 9 10:27:37 2025 +0800 Add enforce_strong_password --- .../iotdb/it/env/remote/env/RemoteServerEnv.java | 2 +- .../org/apache/iotdb/db/it/auth/IoTDBAuthIT.java | 228 ++++++++++++++++++++- .../java/org/apache/iotdb/rpc/TSStatusCode.java | 1 + .../apache/iotdb/session/util/SessionUtils.java | 5 +- .../client/async/CnToDnAsyncRequestType.java | 3 + .../CnToDnInternalServiceAsyncRequestManager.java | 5 + .../confignode/conf/ConfigNodeDescriptor.java | 25 +++ .../iotdb/confignode/manager/ConfigManager.java | 19 +- .../confignode/manager/PermissionManager.java | 4 + .../iotdb/confignode/persistence/AuthorInfo.java | 18 +- .../thrift/ConfigNodeRPCServiceProcessor.java | 5 + .../confignode/persistence/AuthorInfoTest.java | 2 +- .../persistence/CNPhysicalPlanGeneratorTest.java | 2 +- .../iotdb/db/auth/ClusterAuthorityFetcher.java | 37 ++++ .../apache/iotdb/db/auth/IAuthorityFetcher.java | 3 + .../org/apache/iotdb/db/conf/IoTDBDescriptor.java | 18 ++ .../iotdb/db/protocol/client/ConfigNodeClient.java | 6 + .../iotdb/db/protocol/session/SessionManager.java | 81 ++++++++ .../impl/DataNodeInternalRPCServiceImpl.java | 22 ++ .../execution/config/TableConfigTaskVisitor.java | 16 ++ .../execution/config/TreeConfigTaskVisitor.java | 16 ++ .../sql/ast/RelationalAuthorStatement.java | 47 +++++ .../plan/statement/sys/AuthorStatement.java | 37 ++++ .../apache/iotdb/db/utils/DataNodeAuthUtils.java | 172 ++++++++++++++++ .../conf/iotdb-system.properties.template | 22 ++ .../iotdb/commons/auth/user/BasicUserManager.java | 17 +- .../apache/iotdb/commons/conf/CommonConfig.java | 29 +++ .../iotdb/commons/conf/CommonDescriptor.java | 17 ++ .../pipe/config/constant/SystemConstant.java | 2 + .../org/apache/iotdb/commons/utils/AuthUtils.java | 45 +++- .../src/main/thrift/confignode.thrift | 2 + .../src/main/thrift/datanode.thrift | 4 + 32 files changed, 892 insertions(+), 20 deletions(-) diff --git a/integration-test/src/main/java/org/apache/iotdb/it/env/remote/env/RemoteServerEnv.java b/integration-test/src/main/java/org/apache/iotdb/it/env/remote/env/RemoteServerEnv.java index c2308cfe103..35a45f95acc 100644 --- a/integration-test/src/main/java/org/apache/iotdb/it/env/remote/env/RemoteServerEnv.java +++ b/integration-test/src/main/java/org/apache/iotdb/it/env/remote/env/RemoteServerEnv.java @@ -134,7 +134,7 @@ public class RemoteServerEnv implements BaseEnv { connection = DriverManager.getConnection( Config.IOTDB_URL_PREFIX + ip_addr + ":" + port, - BaseEnv.constructProperties(this.user, this.password, sqlDialect)); + BaseEnv.constructProperties(username, password, sqlDialect)); } catch (ClassNotFoundException e) { e.printStackTrace(); throw new AssertionError(); diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/auth/IoTDBAuthIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/auth/IoTDBAuthIT.java index f88c47bbe2e..ae80d070201 100644 --- a/integration-test/src/test/java/org/apache/iotdb/db/it/auth/IoTDBAuthIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/db/it/auth/IoTDBAuthIT.java @@ -21,6 +21,7 @@ package org.apache.iotdb.db.it.auth; import org.apache.iotdb.commons.auth.entity.PrivilegeType; import org.apache.iotdb.commons.schema.column.ColumnHeaderConstant; +import org.apache.iotdb.commons.utils.AuthUtils; import org.apache.iotdb.db.it.utils.TestUtils; import org.apache.iotdb.it.env.EnvFactory; import org.apache.iotdb.it.framework.IoTDBTestRunner; @@ -60,6 +61,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** This is an example for integration test. */ +@SuppressWarnings("EmptyTryBlock") @RunWith(IoTDBTestRunner.class) @Category({LocalStandaloneIT.class, ClusterIT.class}) public class IoTDBAuthIT { @@ -454,7 +456,7 @@ public class IoTDBAuthIT { userStmt.execute("CREATE TIMESERIES root.a.c WITH DATATYPE=INT32,ENCODING=PLAIN"); userStmt.execute("INSERT INTO root.a(timestamp,b,c) VALUES (1,100,1000)"); // userStmt.execute("DELETE FROM root.a.b WHERE TIME <= 1000000000"); - ResultSet resultSet = userStmt.executeQuery("SELECT * FROM root.**"); + ResultSet resultSet = userStmt.executeQuery("SELECT * FROM root.a"); validateResultSet(resultSet, "1,100,1000,\n"); resultSet.close(); @@ -1206,7 +1208,7 @@ public class IoTDBAuthIT { adminStmt.execute("insert into root.sg.d2(time,s1,s2) values(1,1,1)"); adminStmt.execute( "insert into root.sg.aligned_template(time,temperature,status) values(1,20,true)"); - try (ResultSet resultSet = adminStmt.executeQuery("select * from root.**;")) { + try (ResultSet resultSet = adminStmt.executeQuery("select * from root.sg.**;")) { Set<String> standards = new HashSet<>( Arrays.asList( @@ -1423,4 +1425,226 @@ public class IoTDBAuthIT { }; resultSetEqualTest("show current_user", expectedHeader, retArray, "tempuser", "temppw"); } + + @Test + public void testStrongPassword() throws SQLException { + try (Connection connection = EnvFactory.getEnv().getConnection(); + Statement statement = connection.createStatement()) { + statement.execute("SET CONFIGURATION 'enforce_strong_password'='true'"); + } catch (SQLException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + + try (Connection connection = EnvFactory.getEnv().getConnection(); + Statement statement = connection.createStatement()) { + statement.execute("create user userA 'NO_LOWER_CASE_123'"); + fail(); + } catch (SQLException e) { + assertEquals( + "820: Invalid password, must contain at least one lowercase letter.", e.getMessage()); + } + try (Connection connection = EnvFactory.getEnv().getConnection(); + Statement statement = connection.createStatement()) { + statement.execute("alter user root set password 'NO_LOWER_CASE_123'"); + fail(); + } catch (SQLException e) { + assertEquals( + "820: Invalid password, must contain at least one lowercase letter.", e.getMessage()); + } + + try (Connection connection = EnvFactory.getEnv().getConnection(); + Statement statement = connection.createStatement()) { + statement.execute("create user userA 'no_upper_case_123'"); + fail(); + } catch (SQLException e) { + assertEquals( + "820: Invalid password, must contain at least one uppercase letter.", e.getMessage()); + } + try (Connection connection = EnvFactory.getEnv().getConnection(); + Statement statement = connection.createStatement()) { + statement.execute("alter user root set password 'no_upper_case_123'"); + fail(); + } catch (SQLException e) { + assertEquals( + "820: Invalid password, must contain at least one uppercase letter.", e.getMessage()); + } + + try (Connection connection = EnvFactory.getEnv().getConnection(); + Statement statement = connection.createStatement()) { + statement.execute("create user userA 'no_DIGIT'"); + fail(); + } catch (SQLException e) { + assertEquals("820: Invalid password, must contain at least one digit.", e.getMessage()); + } + try (Connection connection = EnvFactory.getEnv().getConnection(); + Statement statement = connection.createStatement()) { + statement.execute("alter user root set password 'no_DIGIT'"); + fail(); + } catch (SQLException e) { + assertEquals("820: Invalid password, must contain at least one digit.", e.getMessage()); + } + + try (Connection connection = EnvFactory.getEnv().getConnection(); + Statement statement = connection.createStatement()) { + statement.execute("create user userA 'noSpecial123'"); + fail(); + } catch (SQLException e) { + assertEquals( + "820: Invalid password, must contain at least one special character.", e.getMessage()); + } + try (Connection connection = EnvFactory.getEnv().getConnection(); + Statement statement = connection.createStatement()) { + statement.execute("alter user root set password 'noSpecial123'"); + fail(); + } catch (SQLException e) { + assertEquals( + "820: Invalid password, must contain at least one special character.", e.getMessage()); + } + + try (Connection connection = EnvFactory.getEnv().getConnection(); + Statement statement = connection.createStatement()) { + statement.execute("create user userA '1234'"); + fail(); + } catch (SQLException e) { + assertEquals( + "820: Invalid password, must contain at least one lowercase letter, one uppercase letter, one special character.", + e.getMessage()); + } + try (Connection connection = EnvFactory.getEnv().getConnection(); + Statement statement = connection.createStatement()) { + statement.execute("alter user root set password '1234'"); + fail(); + } catch (SQLException e) { + assertEquals( + "820: Invalid password, must contain at least one lowercase letter, one uppercase letter, one special character.", + e.getMessage()); + } + + try (Connection connection = EnvFactory.getEnv().getConnection(); + Statement statement = connection.createStatement()) { + statement.execute("create user userA 'userA'"); + fail(); + } catch (SQLException e) { + assertEquals("820: Password cannot be the same as user name", e.getMessage()); + } + try (Connection connection = EnvFactory.getEnv().getConnection(); + Statement statement = connection.createStatement()) { + statement.execute("alter user root set password 'root'"); + fail(); + } catch (SQLException e) { + assertEquals("820: Password cannot be the same as user name", e.getMessage()); + } + + try (Connection connection = EnvFactory.getEnv().getConnection(); + Statement statement = connection.createStatement()) { + statement.execute("create user userA 'strongPassword#1234'"); + + statement.execute("SET CONFIGURATION 'enforce_strong_password'='false'"); + + statement.execute("create user userB '1234'"); + statement.execute("alter user root set password 'root'"); + } + } + + @Test + public void testPasswordHistory() { + try (Connection connection = EnvFactory.getEnv().getConnection(); + Statement statement = connection.createStatement()) { + testPasswordHistoryCreate(statement); + testPasswordHistoryAlter(statement); + } catch (SQLException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + public void testPasswordHistoryCreate(Statement statement) throws SQLException { + statement.execute("create user userA '123456'"); + + try (ResultSet resultSet = + statement.executeQuery( + "select last password from root.__system.password_history.`userA`")) { + if (!resultSet.next()) { + fail("Password history not found"); + } + assertEquals(AuthUtils.encryptPassword("123456"), resultSet.getString("Value")); + } + } + + public void testPasswordHistoryAlter(Statement statement) throws SQLException { + statement.execute("alter user userA set password '654321'"); + + try (ResultSet resultSet = + statement.executeQuery( + "select last password from root.__system.password_history.`userA`")) { + if (!resultSet.next()) { + fail("Password history not found"); + } + assertEquals(AuthUtils.encryptPassword("654321"), resultSet.getString("Value")); + } + } + + @Test + public void testPasswordExpiration() throws SQLException { + try (Connection connection = EnvFactory.getEnv().getConnection(); + Statement statement = connection.createStatement()) { + statement.execute("SET CONFIGURATION 'password_expiration_seconds'='1'"); + statement.execute("CREATE USER userA 'password0001'"); + } + + long startTime = System.currentTimeMillis(); + boolean loginFailed = false; + while (System.currentTimeMillis() - startTime < 60000) { + try (Connection ignored = EnvFactory.getEnv().getConnection("userA", "password0001")) { + + } catch (SQLException e) { + assertEquals("820: Password has expired, please change to a new one", e.getMessage()); + loginFailed = true; + break; + } + } + + if (!loginFailed) { + fail("Password did not expire"); + } + + try (Connection connection = EnvFactory.getEnv().getConnection(); + Statement statement = connection.createStatement()) { + statement.execute("SET CONFIGURATION 'password_expiration_seconds'='0'"); + statement.execute("ALTER USER userA SET PASSWORD 'password0002'"); + } + + try (Connection ignored = EnvFactory.getEnv().getConnection("userA", "password0002")) {} + } + + @Test + public void testPasswordReuseInterval() throws SQLException { + try (Connection connection = EnvFactory.getEnv().getConnection(); + Statement statement = connection.createStatement()) { + statement.execute("SET CONFIGURATION 'password_reuse_interval_seconds'='5'"); + + statement.execute("CREATE USER userA 'password0001'"); + + long startTime = System.currentTimeMillis(); + statement.execute("ALTER USER userA SET PASSWORD 'password0002'"); + + boolean changeSuccess = false; + while (System.currentTimeMillis() - startTime < 60000) { + try { + statement.execute("ALTER USER userA SET PASSWORD 'password0001'"); + changeSuccess = true; + break; + } catch (SQLException e) { + assertTrue(e.getMessage().startsWith("701: The password has been used at")); + } + } + + if (!changeSuccess) { + fail("Password is not successfully changed"); + } + long currentTimeMillis = System.currentTimeMillis(); + assertTrue(currentTimeMillis - startTime > 5000); + } + } } diff --git a/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java b/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java index 3f7eb8f6507..ffbf77704a3 100644 --- a/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java +++ b/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java @@ -168,6 +168,7 @@ public enum TSStatusCode { NOT_HAS_PRIVILEGE_GRANTOPT(817), AUTH_OPERATE_EXCEPTION(818), OPTIMIZER_TIMEOUT(819), + ILLEGAL_PASSWORD(820), // Partition Error MIGRATE_REGION_ERROR(900), diff --git a/iotdb-client/session/src/main/java/org/apache/iotdb/session/util/SessionUtils.java b/iotdb-client/session/src/main/java/org/apache/iotdb/session/util/SessionUtils.java index 8534b808754..cde07acd834 100644 --- a/iotdb-client/session/src/main/java/org/apache/iotdb/session/util/SessionUtils.java +++ b/iotdb-client/session/src/main/java/org/apache/iotdb/session/util/SessionUtils.java @@ -152,7 +152,7 @@ public class SessionUtils { return buffer; } - private static int calculateLength(List<TSDataType> types, List<Object> values) + public static int calculateLength(List<TSDataType> types, List<? extends Object> values) throws IoTDBConnectionException { int res = 0; for (int i = 0; i < types.size(); i++) { @@ -204,7 +204,8 @@ public class SessionUtils { * @param buffer buffer to insert * @throws IoTDBConnectionException */ - private static void putValues(List<TSDataType> types, List<Object> values, ByteBuffer buffer) + public static void putValues( + List<TSDataType> types, List<? extends Object> values, ByteBuffer buffer) throws IoTDBConnectionException { for (int i = 0; i < values.size(); i++) { if (values.get(i) == null) { diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnAsyncRequestType.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnAsyncRequestType.java index d138e333098..badad8a1fe7 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnAsyncRequestType.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnAsyncRequestType.java @@ -123,4 +123,7 @@ public enum CnToDnAsyncRequestType { DELETE_DATA_FOR_TABLE_DEVICE, DELETE_TABLE_DEVICE_IN_BLACK_LIST, DETECT_TREE_DEVICE_VIEW_FIELD_TYPE, + + // audit log and event write-back + INSERT_RECORD } diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java index 3b7a5c4bfb7..f683b9d8e42 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java @@ -99,6 +99,7 @@ import org.apache.iotdb.mpp.rpc.thrift.TTableDeviceInvalidateCacheReq; import org.apache.iotdb.mpp.rpc.thrift.TUpdateTableReq; import org.apache.iotdb.mpp.rpc.thrift.TUpdateTemplateReq; import org.apache.iotdb.mpp.rpc.thrift.TUpdateTriggerLocationReq; +import org.apache.iotdb.service.rpc.thrift.TSInsertRecordReq; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -391,6 +392,10 @@ public class CnToDnInternalServiceAsyncRequestManager CnToDnAsyncRequestType.TEST_CONNECTION, (req, client, handler) -> client.testConnectionEmptyRPC((DataNodeTSStatusRPCHandler) handler)); + actionMapBuilder.put( + CnToDnAsyncRequestType.INSERT_RECORD, + (req, client, handler) -> + client.insertRecord((TSInsertRecordReq) req, (DataNodeTSStatusRPCHandler) handler)); actionMapBuilder.put( CnToDnAsyncRequestType.UPDATE_TABLE, (req, client, handler) -> diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/conf/ConfigNodeDescriptor.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/conf/ConfigNodeDescriptor.java index ed8c4bb5f26..aabcd4f536c 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/conf/ConfigNodeDescriptor.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/conf/ConfigNodeDescriptor.java @@ -755,6 +755,31 @@ public class ConfigNodeDescriptor { public void loadHotModifiedProps(TrimProperties properties) { Optional.ofNullable(properties.getProperty(IoTDBConstant.CLUSTER_NAME)) .ifPresent(conf::setClusterName); + + commonDescriptor + .getConfig() + .setEnforceStrongPassword( + Boolean.parseBoolean( + properties.getProperty( + "enforce_strong_password", + String.valueOf(commonDescriptor.getConfig().isEnforceStrongPassword())))); + + commonDescriptor + .getConfig() + .setPasswordExpirationSeconds( + Long.parseLong( + properties.getProperty( + "password_expiration_seconds", + String.valueOf(commonDescriptor.getConfig().getPasswordExpirationSeconds())))); + + commonDescriptor + .getConfig() + .setPasswordReuseIntervalSeconds( + Long.parseLong( + properties.getProperty( + "password_reuse_interval_seconds", + String.valueOf( + commonDescriptor.getConfig().getPasswordReuseIntervalSeconds())))); } public static ConfigNodeDescriptor getInstance() { diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java index cbd67795774..c5cdc734297 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java @@ -359,7 +359,7 @@ public class ConfigManager implements IManager { NodeInfo nodeInfo = new NodeInfo(); ClusterSchemaInfo clusterSchemaInfo = new ClusterSchemaInfo(); PartitionInfo partitionInfo = new PartitionInfo(); - AuthorInfo authorInfo = new AuthorInfo(); + AuthorInfo authorInfo = new AuthorInfo(this); ProcedureInfo procedureInfo = new ProcedureInfo(this); UDFInfo udfInfo = new UDFInfo(); TriggerInfo triggerInfo = new TriggerInfo(); @@ -1371,6 +1371,23 @@ public class ConfigManager implements IManager { return resp; } + public TPermissionInfoResp getUser(String userName) { + TSStatus status = confirmLeader(); + TPermissionInfoResp resp = new TPermissionInfoResp(); + if (status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) { + try { + resp = permissionManager.getUser(userName); + } catch (AuthException e) { + status.setCode(e.getCode().getStatusCode()).setMessage(e.getMessage()); + resp.setStatus(status); + return resp; + } + } else { + resp.setStatus(status); + } + return resp; + } + @Override public TConfigNodeRegisterResp registerConfigNode(TConfigNodeRegisterReq req) { final int ERROR_STATUS_NODE_ID = -1; diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/PermissionManager.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/PermissionManager.java index adbb41cf9ea..46549c67449 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/PermissionManager.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/PermissionManager.java @@ -137,4 +137,8 @@ public class PermissionManager { throws AuthException { return authorInfo.checkRoleOfUser(username, rolename); } + + public TPermissionInfoResp getUser(String username) throws AuthException { + return authorInfo.getUser(username); + } } diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/AuthorInfo.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/AuthorInfo.java index f18f0f44459..bafb17ce87e 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/AuthorInfo.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/AuthorInfo.java @@ -45,6 +45,7 @@ import org.apache.iotdb.confignode.consensus.request.write.auth.AuthorPlan; import org.apache.iotdb.confignode.consensus.request.write.auth.AuthorRelationalPlan; import org.apache.iotdb.confignode.consensus.request.write.auth.AuthorTreePlan; import org.apache.iotdb.confignode.consensus.response.auth.PermissionInfoResp; +import org.apache.iotdb.confignode.manager.ConfigManager; import org.apache.iotdb.confignode.rpc.thrift.TAuthizedPatternTreeResp; import org.apache.iotdb.confignode.rpc.thrift.TPermissionInfoResp; import org.apache.iotdb.confignode.rpc.thrift.TRoleResp; @@ -77,11 +78,12 @@ public class AuthorInfo implements SnapshotProcessor { private static final String NO_USER_MSG = "No such user : "; private IAuthorizer authorizer; + private ConfigManager configManager; - public AuthorInfo() { + public AuthorInfo(ConfigManager configManager) { try { authorizer = BasicAuthorizer.getInstance(); - + this.configManager = configManager; } catch (AuthException e) { LOGGER.error("get user or role permissionInfo failed because ", e); } @@ -651,6 +653,18 @@ public class AuthorInfo implements SnapshotProcessor { return result; } + public TPermissionInfoResp getUser(String username) throws AuthException { + TPermissionInfoResp result; + User user = authorizer.getUser(username); + if (user == null) { + throw new AuthException( + TSStatusCode.USER_NOT_EXIST, String.format("No such user : %s", username)); + } + result = getUserPermissionInfo(username, ModelType.ALL); + result.setStatus(RpcUtils.getStatus(TSStatusCode.SUCCESS_STATUS)); + return result; + } + @Override public boolean processTakeSnapshot(File snapshotDir) throws TException, IOException { return authorizer.processTakeSnapshot(snapshotDir); diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/service/thrift/ConfigNodeRPCServiceProcessor.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/service/thrift/ConfigNodeRPCServiceProcessor.java index af8e7e593b4..0dec6b99ab8 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/service/thrift/ConfigNodeRPCServiceProcessor.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/service/thrift/ConfigNodeRPCServiceProcessor.java @@ -757,6 +757,11 @@ public class ConfigNodeRPCServiceProcessor implements IConfigNodeRPCService.Ifac return configManager.checkRoleOfUser(req.getUserName(), req.getRoleName()); } + @Override + public TPermissionInfoResp getUser(String userName) { + return configManager.getUser(userName); + } + @Override public TConfigNodeRegisterResp registerConfigNode(TConfigNodeRegisterReq req) { TConfigNodeRegisterResp resp = configManager.registerConfigNode(req); diff --git a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/AuthorInfoTest.java b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/AuthorInfoTest.java index 8766109ac8c..8d8cb837b20 100644 --- a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/AuthorInfoTest.java +++ b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/AuthorInfoTest.java @@ -67,7 +67,7 @@ public class AuthorInfoTest { @BeforeClass public static void setup() { - authorInfo = new AuthorInfo(); + authorInfo = new AuthorInfo(null); if (!snapshotDir.exists()) { snapshotDir.mkdirs(); } diff --git a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/CNPhysicalPlanGeneratorTest.java b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/CNPhysicalPlanGeneratorTest.java index b782b866847..58703cfb3f9 100644 --- a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/CNPhysicalPlanGeneratorTest.java +++ b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/CNPhysicalPlanGeneratorTest.java @@ -76,7 +76,7 @@ public class CNPhysicalPlanGeneratorTest { private static final String TEMPLATE_INFO_FILE_NAME = "template_info.bin"; private static void setupAuthorInfo() { - authorInfo = new AuthorInfo(); + authorInfo = new AuthorInfo(null); if (!snapshotDir.exists()) { snapshotDir.mkdir(); } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/ClusterAuthorityFetcher.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/ClusterAuthorityFetcher.java index 23b0efcd1e2..ef3bb7a9746 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/ClusterAuthorityFetcher.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/ClusterAuthorityFetcher.java @@ -379,6 +379,7 @@ public class ClusterAuthorityFetcher implements IAuthorityFetcher { if (TSStatusCode.SUCCESS_STATUS.getStatusCode() != tsStatus.getCode()) { future.setException(new IoTDBException(tsStatus.message, tsStatus.code)); } else { + onOperatePermissionSuccess(plan); future.set(new ConfigTaskResult(TSStatusCode.SUCCESS_STATUS)); } } catch (AuthException e) { @@ -390,6 +391,16 @@ public class ClusterAuthorityFetcher implements IAuthorityFetcher { return future; } + private void onOperatePermissionSuccess(Object plan) { + if (plan instanceof RelationalAuthorStatement) { + RelationalAuthorStatement stmt = (RelationalAuthorStatement) plan; + stmt.onSuccess(); + } else if (plan instanceof AuthorStatement) { + AuthorStatement stmt = (AuthorStatement) plan; + stmt.onSuccess(); + } + } + @Override public SettableFuture<ConfigTaskResult> operatePermission(AuthorStatement authorStatement) { return operatePermissionInternal(authorStatement, false); @@ -511,6 +522,32 @@ public class ClusterAuthorityFetcher implements IAuthorityFetcher { } } + public User getUser(String userName) { + checkCacheAvailable(); + User user = iAuthorCache.getUserCache(userName); + if (user != null) { + return user; + } else { + TPermissionInfoResp permissionInfoResp = null; + try (ConfigNodeClient configNodeClient = + CONFIG_NODE_CLIENT_MANAGER.borrowClient(ConfigNodeInfo.CONFIG_REGION_ID)) { + // Send request to some API server + permissionInfoResp = configNodeClient.getUser(userName); + } catch (ClientManagerException | TException e) { + LOGGER.error(CONNECTERROR); + } + user = cacheUser(permissionInfoResp); + if (permissionInfoResp != null + && permissionInfoResp.getStatus().getCode() + == TSStatusCode.SUCCESS_STATUS.getStatusCode()) { + if (acceptCache) { + iAuthorCache.putUserCache(userName, user); + } + } + } + return user; + } + @Override public boolean checkRole(String userName, String roleName) { checkCacheAvailable(); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/IAuthorityFetcher.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/IAuthorityFetcher.java index c9f2970ad46..6b93c211fd6 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/IAuthorityFetcher.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/IAuthorityFetcher.java @@ -22,6 +22,7 @@ package org.apache.iotdb.db.auth; import org.apache.iotdb.common.rpc.thrift.TSStatus; import org.apache.iotdb.commons.auth.AuthException; import org.apache.iotdb.commons.auth.entity.PrivilegeType; +import org.apache.iotdb.commons.auth.entity.User; import org.apache.iotdb.commons.path.PartialPath; import org.apache.iotdb.commons.path.PathPatternTree; import org.apache.iotdb.db.queryengine.plan.execution.config.ConfigTaskResult; @@ -79,4 +80,6 @@ public interface IAuthorityFetcher { IAuthorCache getAuthorCache(); void refreshToken(); + + User getUser(String username); } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java index dfd854f724c..d04a6916e35 100755 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java @@ -2040,6 +2040,24 @@ public class IoTDBDescriptor { ConfigurationFileUtils.getConfigurationDefaultValue( "timestamp_precision_check_enabled")))); + commonDescriptor + .getConfig() + .setPasswordExpirationSeconds( + Long.parseLong( + properties.getProperty( + "password_expiration_seconds", + String.valueOf( + commonDescriptor.getConfig().getPasswordExpirationSeconds())))); + + commonDescriptor + .getConfig() + .setPasswordReuseIntervalSeconds( + Long.parseLong( + properties.getProperty( + "password_reuse_interval_seconds", + String.valueOf( + commonDescriptor.getConfig().getPasswordReuseIntervalSeconds())))); + conf.setEnablePartialInsert( Boolean.parseBoolean( Optional.ofNullable( diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/client/ConfigNodeClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/client/ConfigNodeClient.java index 9d0dcd22f91..fee3a116c4d 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/client/ConfigNodeClient.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/client/ConfigNodeClient.java @@ -701,6 +701,12 @@ public class ConfigNodeClient implements IConfigNodeRPCService.Iface, ThriftClie () -> client.login(req), resp -> !updateConfigNodeLeader(resp.status)); } + @Override + public TPermissionInfoResp getUser(String userName) throws TException { + return executeRemoteCallWithRetry( + () -> client.getUser(userName), resp -> !updateConfigNodeLeader(resp.status)); + } + @Override public TPermissionInfoResp checkUserPrivileges(TCheckUserPrivilegesReq req) throws TException { return executeRemoteCallWithRetry( diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/SessionManager.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/SessionManager.java index 764d5e9fb25..420158cd3cf 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/SessionManager.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/SessionManager.java @@ -20,18 +20,29 @@ package org.apache.iotdb.db.protocol.session; import org.apache.iotdb.common.rpc.thrift.TSStatus; +import org.apache.iotdb.commons.conf.CommonDescriptor; import org.apache.iotdb.commons.conf.IoTDBConstant; +import org.apache.iotdb.commons.exception.IoTDBException; +import org.apache.iotdb.commons.pipe.config.constant.SystemConstant; import org.apache.iotdb.commons.service.JMXService; import org.apache.iotdb.commons.service.ServiceType; import org.apache.iotdb.commons.service.metric.MetricService; import org.apache.iotdb.commons.service.metric.enums.Metric; import org.apache.iotdb.commons.service.metric.enums.Tag; +import org.apache.iotdb.commons.utils.AuthUtils; import org.apache.iotdb.db.audit.AuditLogger; import org.apache.iotdb.db.auth.AuthorityChecker; import org.apache.iotdb.db.conf.IoTDBDescriptor; import org.apache.iotdb.db.protocol.basic.BasicOpenSessionResp; import org.apache.iotdb.db.protocol.thrift.OperationType; import org.apache.iotdb.db.queryengine.common.SessionInfo; +import org.apache.iotdb.db.queryengine.plan.Coordinator; +import org.apache.iotdb.db.queryengine.plan.analyze.ClusterPartitionFetcher; +import org.apache.iotdb.db.queryengine.plan.analyze.schema.ClusterSchemaFetcher; +import org.apache.iotdb.db.queryengine.plan.execution.ExecutionResult; +import org.apache.iotdb.db.queryengine.plan.execution.IQueryExecution; +import org.apache.iotdb.db.queryengine.plan.parser.StatementGenerator; +import org.apache.iotdb.db.queryengine.plan.statement.Statement; import org.apache.iotdb.db.queryengine.plan.statement.StatementType; import org.apache.iotdb.db.queryengine.plan.statement.sys.AuthorStatement; import org.apache.iotdb.db.storageengine.dataregion.read.control.QueryResourceManager; @@ -41,15 +52,19 @@ import org.apache.iotdb.rpc.RpcUtils; import org.apache.iotdb.rpc.TSStatusCode; import org.apache.iotdb.service.rpc.thrift.TSConnectionInfo; import org.apache.iotdb.service.rpc.thrift.TSConnectionInfoResp; +import org.apache.iotdb.service.rpc.thrift.TSLastDataQueryReq; import org.apache.iotdb.service.rpc.thrift.TSProtocolVersion; import org.apache.commons.lang3.StringUtils; +import org.apache.tsfile.read.common.block.TsBlock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.ZoneId; +import java.util.Collections; import java.util.Comparator; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.TimeZone; import java.util.concurrent.ConcurrentHashMap; @@ -114,6 +129,63 @@ public class SessionManager implements SessionManagerMBean { IClientSession.SqlDialect.TREE); } + private TSStatus checkPasswordExpiration(String username, String password) { + // check password expiration + long passwordExpirationSeconds = + CommonDescriptor.getInstance().getConfig().getPasswordExpirationSeconds(); + if (passwordExpirationSeconds <= 0 + || username.equals(CommonDescriptor.getInstance().getConfig().getAdminName())) { + return null; + } + + TSLastDataQueryReq lastDataQueryReq = new TSLastDataQueryReq(); + lastDataQueryReq.setSessionId(0); + lastDataQueryReq.setPaths( + Collections.singletonList( + SystemConstant.PREFIX_PASSWORD_HISTORY + ".`" + username + "`.password")); + try { + Statement statement = StatementGenerator.createStatement(lastDataQueryReq); + SessionInfo sessionInfo = + new SessionInfo(0, AuthorityChecker.SUPER_USER, ZoneId.systemDefault()); + + long queryId = requestQueryId(); + ExecutionResult result = + Coordinator.getInstance() + .executeForTreeModel( + statement, + queryId, + sessionInfo, + "", + ClusterPartitionFetcher.getInstance(), + ClusterSchemaFetcher.getInstance()); + if (result.status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) { + LOGGER.warn("Fail to check password expiration: {}", result.status); + return null; + } + + IQueryExecution queryExecution = Coordinator.getInstance().getQueryExecution(queryId); + Optional<TsBlock> batchResult = queryExecution.getBatchResult(); + if (batchResult.isPresent()) { + TsBlock tsBlock = batchResult.get(); + if (tsBlock.getPositionCount() <= 0) { + // no password history, may have upgraded from an older version + return null; + } + long lastPasswordTime = tsBlock.getTimeByIndex(0); + // columns of last query: [timeseriesName, value, dataType] + String oldPassword = tsBlock.getColumn(1).getBinary(0).toString(); + if (oldPassword.equals(AuthUtils.encryptPassword(password)) + && System.currentTimeMillis() - lastPasswordTime > passwordExpirationSeconds * 1000) { + return new TSStatus(TSStatusCode.ILLEGAL_PASSWORD.getStatusCode()) + .setMessage("Password has expired, please change to a new one"); + } + } + } catch (IoTDBException e) { + LOGGER.warn("Cannot generate query for checking password expiration", e); + } + return null; + } + public BasicOpenSessionResp login( IClientSession session, String username, @@ -125,6 +197,15 @@ public class SessionManager implements SessionManagerMBean { TSStatus loginStatus; BasicOpenSessionResp openSessionResp = new BasicOpenSessionResp(); + loginStatus = checkPasswordExpiration(username, password); + if (loginStatus != null) { + openSessionResp + .sessionId(-1) + .setCode(loginStatus.getCode()) + .setMessage(loginStatus.getMessage()); + return openSessionResp; + } + loginStatus = AuthorityChecker.checkUser(username, password); if (loginStatus.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) { // check the version compatibility diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java index 368b3b027d1..69818f707c9 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java @@ -84,6 +84,7 @@ import org.apache.iotdb.db.conf.IoTDBDescriptor; import org.apache.iotdb.db.consensus.DataRegionConsensusImpl; import org.apache.iotdb.db.consensus.SchemaRegionConsensusImpl; import org.apache.iotdb.db.exception.StorageEngineException; +import org.apache.iotdb.db.exception.query.QueryProcessException; import org.apache.iotdb.db.pipe.agent.PipeDataNodeAgent; import org.apache.iotdb.db.protocol.client.ConfigNodeInfo; import org.apache.iotdb.db.protocol.client.cn.DnToCnInternalServiceAsyncRequestManager; @@ -98,6 +99,7 @@ import org.apache.iotdb.db.protocol.session.InternalClientSession; import org.apache.iotdb.db.protocol.session.SessionManager; import org.apache.iotdb.db.protocol.thrift.OperationType; import org.apache.iotdb.db.queryengine.common.FragmentInstanceId; +import org.apache.iotdb.db.queryengine.common.SessionInfo; import org.apache.iotdb.db.queryengine.execution.executor.RegionExecutionResult; import org.apache.iotdb.db.queryengine.execution.executor.RegionReadExecutor; import org.apache.iotdb.db.queryengine.execution.executor.RegionWriteExecutor; @@ -156,6 +158,7 @@ import org.apache.iotdb.db.queryengine.plan.relational.planner.node.schema.Table import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.DeleteDevice; import org.apache.iotdb.db.queryengine.plan.scheduler.load.LoadTsFileScheduler; import org.apache.iotdb.db.queryengine.plan.statement.component.WhereCondition; +import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertRowStatement; import org.apache.iotdb.db.queryengine.plan.statement.crud.QueryStatement; import org.apache.iotdb.db.queryengine.plan.udf.UDFManagementService; import org.apache.iotdb.db.schemaengine.SchemaEngine; @@ -283,6 +286,7 @@ import org.apache.iotdb.mpp.rpc.thrift.TUpdateTriggerLocationReq; import org.apache.iotdb.rpc.RpcUtils; import org.apache.iotdb.rpc.TSStatusCode; import org.apache.iotdb.rpc.subscription.exception.SubscriptionException; +import org.apache.iotdb.service.rpc.thrift.TSInsertRecordReq; import org.apache.iotdb.trigger.api.enums.FailureStrategy; import org.apache.iotdb.trigger.api.enums.TriggerEvent; @@ -330,6 +334,7 @@ import java.util.stream.Stream; import static org.apache.iotdb.commons.client.request.TestConnectionUtils.testConnectionsImpl; import static org.apache.iotdb.commons.conf.IoTDBConstant.MULTI_LEVEL_PATH_WILDCARD; import static org.apache.iotdb.db.service.RegionMigrateService.REGION_MIGRATE_PROCESS; +import static org.apache.iotdb.db.utils.ErrorHandlingUtils.onIoTDBException; import static org.apache.iotdb.db.utils.ErrorHandlingUtils.onQueryException; public class DataNodeInternalRPCServiceImpl implements IDataNodeRPCService.Iface { @@ -2827,6 +2832,23 @@ public class DataNodeInternalRPCServiceImpl implements IDataNodeRPCService.Iface return status; } + @Override + public TSStatus insertRecord(TSInsertRecordReq req) throws TException { + try { + InsertRowStatement statement = StatementGenerator.createStatement(req); + SessionInfo sessionInfo = + new SessionInfo(0, AuthorityChecker.SUPER_USER, ZoneId.systemDefault()); + + long queryId = SESSION_MANAGER.requestQueryId(); + ExecutionResult result = + COORDINATOR.executeForTreeModel( + statement, queryId, sessionInfo, "", partitionFetcher, schemaFetcher); + return result.status; + } catch (IllegalPathException | QueryProcessException e) { + return onIoTDBException(e, OperationType.INSERT_RECORD, e.getErrorCode()); + } + } + public void handleClientExit() { // Do nothing } 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 a169096341f..8155643951e 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 @@ -20,6 +20,7 @@ package org.apache.iotdb.db.queryengine.plan.execution.config; import org.apache.iotdb.common.rpc.thrift.Model; +import org.apache.iotdb.commons.auth.entity.User; import org.apache.iotdb.commons.exception.IllegalPathException; import org.apache.iotdb.commons.exception.auth.AccessDeniedException; import org.apache.iotdb.commons.executable.ExecutableManager; @@ -34,6 +35,7 @@ import org.apache.iotdb.commons.schema.table.column.TimeColumnSchema; import org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory; import org.apache.iotdb.commons.schema.table.column.TsTableColumnSchema; import org.apache.iotdb.confignode.rpc.thrift.TDatabaseSchema; +import org.apache.iotdb.db.auth.AuthorityChecker; import org.apache.iotdb.db.conf.IoTDBConfig; import org.apache.iotdb.db.exception.sql.SemanticException; import org.apache.iotdb.db.protocol.session.IClientSession; @@ -192,6 +194,7 @@ import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.StopRepairData; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Use; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ViewFieldDefinition; import org.apache.iotdb.db.queryengine.plan.relational.sql.rewrite.StatementRewrite; +import org.apache.iotdb.db.queryengine.plan.relational.type.AuthorRType; import org.apache.iotdb.db.queryengine.plan.relational.type.TypeNotFoundException; import org.apache.iotdb.db.queryengine.plan.statement.metadata.DatabaseSchemaStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.RemoveConfigNodeStatement; @@ -204,6 +207,7 @@ import org.apache.iotdb.db.queryengine.plan.statement.sys.SetConfigurationStatem import org.apache.iotdb.db.queryengine.plan.statement.sys.SetSystemStatusStatement; import org.apache.iotdb.db.queryengine.plan.statement.sys.StartRepairDataStatement; import org.apache.iotdb.db.queryengine.plan.statement.sys.StopRepairDataStatement; +import org.apache.iotdb.db.utils.DataNodeAuthUtils; import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters; import org.apache.tsfile.common.conf.TSFileConfig; @@ -1281,9 +1285,21 @@ public class TableConfigTaskVisitor extends AstVisitor<IConfigTask, MPPQueryCont accessControl.checkUserCanRunRelationalAuthorStatement( context.getSession().getUserName(), node); context.setQueryType(node.getQueryType()); + if (node.getAuthorType() == AuthorRType.UPDATE_USER) { + visitUpdateUser(node); + } return new RelationalAuthorizerTask(node); } + private void visitUpdateUser(RelationalAuthorStatement node) { + User user = AuthorityChecker.getAuthorityFetcher().getUser(node.getUserName()); + if (user == null) { + throw new SemanticException("User " + node.getUserName() + " not found"); + } + node.setOldPassword(user.getPassword()); + DataNodeAuthUtils.verifyPasswordReuse(node.getUserName(), node.getPassword()); + } + @Override protected IConfigTask visitKillQuery(KillQuery node, MPPQueryContext context) { context.setQueryType(QueryType.WRITE); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TreeConfigTaskVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TreeConfigTaskVisitor.java index 282839ec560..4b2e0c7bcdf 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TreeConfigTaskVisitor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TreeConfigTaskVisitor.java @@ -20,9 +20,11 @@ package org.apache.iotdb.db.queryengine.plan.execution.config; import org.apache.iotdb.common.rpc.thrift.Model; +import org.apache.iotdb.commons.auth.entity.User; import org.apache.iotdb.commons.executable.ExecutableManager; import org.apache.iotdb.commons.path.PartialPath; import org.apache.iotdb.commons.pipe.config.constant.SystemConstant; +import org.apache.iotdb.db.auth.AuthorityChecker; import org.apache.iotdb.db.exception.sql.SemanticException; import org.apache.iotdb.db.queryengine.common.MPPQueryContext; import org.apache.iotdb.db.queryengine.plan.execution.config.metadata.CountDatabaseTask; @@ -108,6 +110,7 @@ import org.apache.iotdb.db.queryengine.plan.execution.config.sys.subscription.Dr import org.apache.iotdb.db.queryengine.plan.execution.config.sys.subscription.DropTopicTask; import org.apache.iotdb.db.queryengine.plan.execution.config.sys.subscription.ShowSubscriptionsTask; import org.apache.iotdb.db.queryengine.plan.execution.config.sys.subscription.ShowTopicsTask; +import org.apache.iotdb.db.queryengine.plan.statement.AuthorType; import org.apache.iotdb.db.queryengine.plan.statement.Statement; import org.apache.iotdb.db.queryengine.plan.statement.StatementNode; import org.apache.iotdb.db.queryengine.plan.statement.StatementVisitor; @@ -193,6 +196,7 @@ import org.apache.iotdb.db.queryengine.plan.statement.sys.quota.SetSpaceQuotaSta import org.apache.iotdb.db.queryengine.plan.statement.sys.quota.SetThrottleQuotaStatement; import org.apache.iotdb.db.queryengine.plan.statement.sys.quota.ShowSpaceQuotaStatement; import org.apache.iotdb.db.queryengine.plan.statement.sys.quota.ShowThrottleQuotaStatement; +import org.apache.iotdb.db.utils.DataNodeAuthUtils; import org.apache.tsfile.exception.NotImplementedException; @@ -292,9 +296,21 @@ public class TreeConfigTaskVisitor extends StatementVisitor<IConfigTask, MPPQuer @Override public IConfigTask visitAuthor(AuthorStatement statement, MPPQueryContext context) { + if (statement.getAuthorType() == AuthorType.UPDATE_USER) { + visitUpdateUser(statement); + } return new AuthorizerTask(statement); } + private void visitUpdateUser(AuthorStatement statement) { + User user = AuthorityChecker.getAuthorityFetcher().getUser(statement.getUserName()); + if (user == null) { + throw new SemanticException("User " + statement.getUserName() + " not found"); + } + statement.setPassWord(user.getPassword()); + DataNodeAuthUtils.verifyPasswordReuse(statement.getUserName(), statement.getNewPassword()); + } + @Override public IConfigTask visitMerge(MergeStatement mergeStatement, MPPQueryContext context) { return new MergeTask(mergeStatement); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/RelationalAuthorStatement.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/RelationalAuthorStatement.java index 71c1962f2ea..74454e0eaf3 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/RelationalAuthorStatement.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/RelationalAuthorStatement.java @@ -18,9 +18,13 @@ */ package org.apache.iotdb.db.queryengine.plan.relational.sql.ast; +import org.apache.iotdb.common.rpc.thrift.TSStatus; import org.apache.iotdb.commons.auth.entity.PrivilegeType; import org.apache.iotdb.db.queryengine.plan.analyze.QueryType; import org.apache.iotdb.db.queryengine.plan.relational.type.AuthorRType; +import org.apache.iotdb.db.utils.DataNodeAuthUtils; +import org.apache.iotdb.rpc.RpcUtils; +import org.apache.iotdb.rpc.StatementExecutionException; import com.google.common.collect.ImmutableList; @@ -40,6 +44,7 @@ public class RelationalAuthorStatement extends Statement { private String roleName; private String password; + private String oldPassword; private Set<PrivilegeType> privilegeType; @@ -244,4 +249,46 @@ public class RelationalAuthorStatement extends Statement { + ", grantOption:" + grantOption; } + + /** + * Post-process when the statement is successfully executed. + * + * @return null if the post-process succeeds, a status otherwise. + */ + public TSStatus onSuccess() { + if (authorType == AuthorRType.CREATE_USER) { + return onCreateUserSuccess(); + } else if (authorType == AuthorRType.UPDATE_USER) { + return onUpdateUserSuccess(); + } + return null; + } + + private TSStatus onCreateUserSuccess() { + TSStatus tsStatus = DataNodeAuthUtils.recordPassword(userName, password, null); + try { + RpcUtils.verifySuccess(tsStatus); + } catch (StatementExecutionException e) { + return new TSStatus(e.getStatusCode()).setMessage(e.getMessage()); + } + return null; + } + + private TSStatus onUpdateUserSuccess() { + TSStatus tsStatus = DataNodeAuthUtils.recordPassword(userName, password, oldPassword); + try { + RpcUtils.verifySuccess(tsStatus); + } catch (StatementExecutionException e) { + return new TSStatus(e.getStatusCode()).setMessage(e.getMessage()); + } + return null; + } + + public String getOldPassword() { + return oldPassword; + } + + public void setOldPassword(String oldPassword) { + this.oldPassword = oldPassword; + } } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/sys/AuthorStatement.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/sys/AuthorStatement.java index 0c905c45d18..fc0481fa656 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/sys/AuthorStatement.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/sys/AuthorStatement.java @@ -29,6 +29,9 @@ import org.apache.iotdb.db.queryengine.plan.statement.IConfigStatement; import org.apache.iotdb.db.queryengine.plan.statement.Statement; import org.apache.iotdb.db.queryengine.plan.statement.StatementType; import org.apache.iotdb.db.queryengine.plan.statement.StatementVisitor; +import org.apache.iotdb.db.utils.DataNodeAuthUtils; +import org.apache.iotdb.rpc.RpcUtils; +import org.apache.iotdb.rpc.StatementExecutionException; import org.apache.iotdb.rpc.TSStatusCode; import java.util.Collections; @@ -345,4 +348,38 @@ public class AuthorStatement extends Statement implements IConfigStatement { throw new IllegalArgumentException("Unknown authorType: " + authorType); } } + + /** + * Post-process when the statement is successfully executed. + * + * @return null if the post-process succeeds, a status otherwise. + */ + public TSStatus onSuccess() { + if (authorType == AuthorType.CREATE_USER) { + return onCreateUserSuccess(); + } else if (authorType == AuthorType.UPDATE_USER) { + return onUpdateUserSuccess(); + } + return null; + } + + private TSStatus onCreateUserSuccess() { + TSStatus tsStatus = DataNodeAuthUtils.recordPassword(userName, password, null); + try { + RpcUtils.verifySuccess(tsStatus); + } catch (StatementExecutionException e) { + return new TSStatus(e.getStatusCode()).setMessage(e.getMessage()); + } + return null; + } + + private TSStatus onUpdateUserSuccess() { + TSStatus tsStatus = DataNodeAuthUtils.recordPassword(userName, newPassword, password); + try { + RpcUtils.verifySuccess(tsStatus); + } catch (StatementExecutionException e) { + return new TSStatus(e.getStatusCode()).setMessage(e.getMessage()); + } + return null; + } } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/DataNodeAuthUtils.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/DataNodeAuthUtils.java new file mode 100644 index 00000000000..d60ed5c9e27 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/DataNodeAuthUtils.java @@ -0,0 +1,172 @@ +/* + * 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.db.utils; + +import org.apache.iotdb.common.rpc.thrift.TSStatus; +import org.apache.iotdb.commons.conf.CommonDescriptor; +import org.apache.iotdb.commons.exception.IllegalPathException; +import org.apache.iotdb.commons.exception.IoTDBException; +import org.apache.iotdb.commons.path.PartialPath; +import org.apache.iotdb.commons.pipe.config.constant.SystemConstant; +import org.apache.iotdb.commons.utils.AuthUtils; +import org.apache.iotdb.db.auth.AuthorityChecker; +import org.apache.iotdb.db.exception.sql.SemanticException; +import org.apache.iotdb.db.protocol.session.SessionManager; +import org.apache.iotdb.db.queryengine.common.SessionInfo; +import org.apache.iotdb.db.queryengine.plan.Coordinator; +import org.apache.iotdb.db.queryengine.plan.analyze.ClusterPartitionFetcher; +import org.apache.iotdb.db.queryengine.plan.analyze.schema.ClusterSchemaFetcher; +import org.apache.iotdb.db.queryengine.plan.execution.ExecutionResult; +import org.apache.iotdb.db.queryengine.plan.execution.IQueryExecution; +import org.apache.iotdb.db.queryengine.plan.parser.StatementGenerator; +import org.apache.iotdb.db.queryengine.plan.statement.Statement; +import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertRowStatement; +import org.apache.iotdb.rpc.TSStatusCode; + +import org.apache.tsfile.enums.TSDataType; +import org.apache.tsfile.read.common.block.TsBlock; +import org.apache.tsfile.utils.Binary; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.charset.StandardCharsets; +import java.time.ZoneId; +import java.util.Date; +import java.util.Optional; + +public class DataNodeAuthUtils { + + private DataNodeAuthUtils() {} + + private static final Logger LOGGER = LoggerFactory.getLogger(DataNodeAuthUtils.class); + + /** + * @return the timestamp when the password of the user is lastly changed from the given one to a + * new one, or -1 if the password has not been used. + */ + public static long getPasswordChangeTime(String username, String password) { + + try { + Statement statement = + StatementGenerator.createStatement( + "SELECT password from " + + SystemConstant.PREFIX_PASSWORD_HISTORY + + ".`" + + username + + "` where oldPassword='" + + AuthUtils.encryptPassword(password) + + "'", + ZoneId.systemDefault()); + SessionInfo sessionInfo = + new SessionInfo(0, AuthorityChecker.SUPER_USER, ZoneId.systemDefault()); + + long queryId = SessionManager.getInstance().requestQueryId(); + ExecutionResult result = + Coordinator.getInstance() + .executeForTreeModel( + statement, + queryId, + sessionInfo, + "", + ClusterPartitionFetcher.getInstance(), + ClusterSchemaFetcher.getInstance()); + if (result.status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) { + LOGGER.warn("Fail to get password change time: {}", result.status); + return -1; + } + + IQueryExecution queryExecution = Coordinator.getInstance().getQueryExecution(queryId); + TsBlock lastTsBlock = null; + Optional<TsBlock> batchResult = queryExecution.getBatchResult(); + while (batchResult.isPresent()) { + if (!batchResult.get().isEmpty()) { + lastTsBlock = batchResult.get(); + } + batchResult = queryExecution.getBatchResult(); + } + if (lastTsBlock != null) { + if (lastTsBlock.getPositionCount() <= 0) { + // no password history, may have upgraded from an older version + return -1; + } + return lastTsBlock.getTimeByIndex(lastTsBlock.getPositionCount() - 1); + } + } catch (IoTDBException e) { + LOGGER.warn("Cannot generate query for checking password expiration", e); + } + return -1; + } + + public static void verifyPasswordReuse(String username, String password) { + long passwordReuseIntervalSeconds = + CommonDescriptor.getInstance().getConfig().getPasswordReuseIntervalSeconds(); + if (password == null || passwordReuseIntervalSeconds <= 0) { + return; + } + + long passwordChangeTime = DataNodeAuthUtils.getPasswordChangeTime(username, password); + long currentTimeMillis = System.currentTimeMillis(); + long elapsedTime = currentTimeMillis - passwordChangeTime; + if (elapsedTime <= passwordReuseIntervalSeconds * 1000) { + throw new SemanticException( + String.format( + "The password has been used at %s and it cannot be reused before %s", + new Date(passwordChangeTime), + new Date(passwordChangeTime + passwordReuseIntervalSeconds))); + } + LOGGER.info( + "It has been {}ms, since the password was changed {} -> {}", + elapsedTime, + passwordChangeTime, + currentTimeMillis); + } + + public static TSStatus recordPassword(String username, String password, String oldPassword) { + InsertRowStatement insertRowStatement = new InsertRowStatement(); + try { + insertRowStatement.setDevicePath( + new PartialPath(SystemConstant.PREFIX_PASSWORD_HISTORY + ".`" + username + "`")); + insertRowStatement.setTime(System.currentTimeMillis()); + insertRowStatement.setMeasurements(new String[] {"password", "oldPassword"}); + insertRowStatement.setValues( + new Object[] { + new Binary(AuthUtils.encryptPassword(password), StandardCharsets.UTF_8), + oldPassword == null ? null : new Binary(oldPassword, StandardCharsets.UTF_8) + }); + insertRowStatement.setDataTypes(new TSDataType[] {TSDataType.STRING, TSDataType.STRING}); + } catch (IllegalPathException ignored) { + } + + SessionInfo sessionInfo = + new SessionInfo(0, AuthorityChecker.SUPER_USER, ZoneId.systemDefault()); + + long queryId = SessionManager.getInstance().requestQueryId(); + ExecutionResult result = + Coordinator.getInstance() + .executeForTreeModel( + insertRowStatement, + queryId, + sessionInfo, + "", + ClusterPartitionFetcher.getInstance(), + ClusterSchemaFetcher.getInstance()); + return result.status; + } +} diff --git a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template index 48c97ad52d1..f7245ab9360 100644 --- a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template +++ b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template @@ -1670,6 +1670,28 @@ author_cache_expire_time=30 # If you want to allow all URIs, you can specify it as .* trusted_uri_pattern=file:.* +# When enabled, passwords of new users or new passwords of existing users must follow the following: +# 1. contain at least one lower case letter; +# 2. contain at least one upper case letter; +# 3. contain at least one digit; +# 4. contain at least one special character (another character except the above three). +# Datatype: boolean +# effectiveMode: hot_reload +enforce_strong_password=false + +# When > 0, the password of a user will expire after last modification/creation in the giving +# seconds, and the user can no longer login unless the user changes the password. +# Notice: if the cluster have upgraded from an old version, the will not take effective +# Datatype: int +# effectiveMode: hot_reload +password_expiration_seconds=0 + +# When > 0, the old password of a user cannot be reused within the seconds specified by this item, +# after a successful password change. +# Datatype: int +# effectiveMode: hot_reload +password_reuse_interval_seconds=0 + #################### ### UDF Configuration #################### diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/user/BasicUserManager.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/user/BasicUserManager.java index bc2c283a363..4e041e02253 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/user/BasicUserManager.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/user/BasicUserManager.java @@ -107,7 +107,12 @@ public abstract class BasicUserManager extends BasicRoleManager { public boolean createUser( String username, String password, boolean validCheck, boolean enableEncrypt) throws AuthException { - if (validCheck) { + if (validCheck && !CommonDescriptor.getInstance().getConfig().getAdminName().equals(username)) { + if (username.equals(password) + && CommonDescriptor.getInstance().getConfig().isEnforceStrongPassword()) { + throw new AuthException( + TSStatusCode.ILLEGAL_PASSWORD, "Password cannot be the same as user name"); + } AuthUtils.validateUsername(username); if (enableEncrypt) { AuthUtils.validatePassword(password); @@ -129,12 +134,12 @@ public abstract class BasicUserManager extends BasicRoleManager { } public boolean updateUserPassword(String username, String newPassword) throws AuthException { - try { - AuthUtils.validatePassword(newPassword); - } catch (AuthException e) { - LOGGER.debug("An illegal password detected ", e); - return false; + if (CommonDescriptor.getInstance().getConfig().isEnforceStrongPassword() + && username.equals(newPassword)) { + throw new AuthException( + TSStatusCode.ILLEGAL_PASSWORD, "Password cannot be the same as user name"); } + AuthUtils.validatePassword(newPassword); lock.writeLock(username); try { diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonConfig.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonConfig.java index 3004ace2e30..12e2b9df0dd 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonConfig.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonConfig.java @@ -398,6 +398,11 @@ public class CommonConfig { private volatile Pattern trustedUriPattern = Pattern.compile("file:.*"); + private boolean enforceStrongPassword = false; + private long passwordExpirationSeconds = 0; + // an old password cannot be reused within the given interval if > 0. + private long passwordReuseIntervalSeconds = 0; + CommonConfig() { // Empty constructor } @@ -2473,4 +2478,28 @@ public class CommonConfig { public void setTrustedUriPattern(Pattern trustedUriPattern) { this.trustedUriPattern = trustedUriPattern; } + + public boolean isEnforceStrongPassword() { + return enforceStrongPassword; + } + + public void setEnforceStrongPassword(boolean enforceStrongPassword) { + this.enforceStrongPassword = enforceStrongPassword; + } + + public long getPasswordExpirationSeconds() { + return passwordExpirationSeconds; + } + + public void setPasswordExpirationSeconds(long passwordExpirationSeconds) { + this.passwordExpirationSeconds = passwordExpirationSeconds; + } + + public long getPasswordReuseIntervalSeconds() { + return passwordReuseIntervalSeconds; + } + + public void setPasswordReuseIntervalSeconds(long passwordReuseIntervalSeconds) { + this.passwordReuseIntervalSeconds = passwordReuseIntervalSeconds; + } } diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonDescriptor.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonDescriptor.java index ab9348f9de3..a6049865605 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonDescriptor.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonDescriptor.java @@ -251,6 +251,23 @@ public class CommonDescriptor { "cluster_device_limit_threshold", String.valueOf(config.getDeviceLimitThreshold())))); + config.setEnforceStrongPassword( + Boolean.parseBoolean( + properties.getProperty( + "enforce_strong_password", String.valueOf(config.isEnforceStrongPassword())))); + + config.setPasswordExpirationSeconds( + Long.parseLong( + properties.getProperty( + "password_expiration_seconds", + String.valueOf(config.getPasswordExpirationSeconds())))); + + config.setPasswordReuseIntervalSeconds( + Long.parseLong( + properties.getProperty( + "password_reuse_interval_seconds", + String.valueOf(config.getPasswordReuseIntervalSeconds())))); + loadRetryProperties(properties); loadBinaryAllocatorProps(properties); } diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/SystemConstant.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/SystemConstant.java index 105390eab9c..175dee723dc 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/SystemConstant.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/SystemConstant.java @@ -29,6 +29,8 @@ import java.util.Set; public class SystemConstant { public static final String SYSTEM_PREFIX_KEY = "__system"; + public static final String PREFIX_PASSWORD_HISTORY = + "root." + SYSTEM_PREFIX_KEY + ".password_history"; public static final String RESTART_KEY = "__system.restart"; public static final boolean RESTART_DEFAULT_VALUE = false; diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/AuthUtils.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/AuthUtils.java index 64e1420e081..86bcd1a3afd 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/AuthUtils.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/AuthUtils.java @@ -69,6 +69,43 @@ public class AuthUtils { */ public static void validatePassword(String password) throws AuthException { validateNameOrPassword(password); + if (CommonDescriptor.getInstance().getConfig().isEnforceStrongPassword()) { + boolean hasLowerCase = false; + boolean hasUpperCase = false; + boolean hasDigit = false; + boolean hasSpecialChar = false; + for (int i = 0; i < password.length(); i++) { + char c = password.charAt(i); + if (Character.isLowerCase(c)) { + hasLowerCase = true; + } else if (Character.isUpperCase(c)) { + hasUpperCase = true; + } else if (Character.isDigit(c)) { + hasDigit = true; + } else { + hasSpecialChar = true; + } + } + + if (!hasLowerCase || !hasUpperCase || !hasDigit || !hasSpecialChar) { + StringBuilder builder = new StringBuilder("Invalid password, must contain at least"); + if (!hasLowerCase) { + builder.append(" one lowercase letter,"); + } + if (!hasUpperCase) { + builder.append(" one uppercase letter,"); + } + if (!hasDigit) { + builder.append(" one digit,"); + } + if (!hasSpecialChar) { + builder.append(" one special character,"); + } + builder.deleteCharAt(builder.length() - 1); + builder.append("."); + throw new AuthException(TSStatusCode.ILLEGAL_PASSWORD, builder.toString()); + } + } } /** @@ -108,18 +145,18 @@ public class AuthUtils { int length = str.length(); if (length < MIN_LENGTH) { throw new AuthException( - TSStatusCode.ILLEGAL_PARAMETER, + TSStatusCode.ILLEGAL_PASSWORD, "The length of name or password must be greater than or equal to " + MIN_LENGTH); } else if (length > MAX_LENGTH) { throw new AuthException( - TSStatusCode.ILLEGAL_PARAMETER, + TSStatusCode.ILLEGAL_PASSWORD, "The length of name or password must be less than or equal to " + MAX_LENGTH); } else if (str.contains(" ")) { throw new AuthException( - TSStatusCode.ILLEGAL_PARAMETER, "The name or password cannot contain spaces"); + TSStatusCode.ILLEGAL_PASSWORD, "The name or password cannot contain spaces"); } else if (!str.matches(REX_PATTERN)) { throw new AuthException( - TSStatusCode.ILLEGAL_PARAMETER, + TSStatusCode.ILLEGAL_PASSWORD, "The name or password can only contain letters, numbers or !@#$%^*()_+-="); } } diff --git a/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift b/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift index 3f4c42d058a..593fc87b68d 100644 --- a/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift +++ b/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift @@ -1512,6 +1512,8 @@ service IConfigNodeRPCService { TPermissionInfoResp checkRoleOfUser(TAuthorizerReq req) + TPermissionInfoResp getUser(string userName); + // ====================================================== diff --git a/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift b/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift index 70456a952f3..21b6388ff2e 100644 --- a/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift +++ b/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift @@ -17,6 +17,7 @@ * under the License. */ include "common.thrift" +include "client.thrift" namespace java org.apache.iotdb.mpp.rpc.thrift namespace py iotdb.thrift.datanode @@ -1207,6 +1208,9 @@ service IDataNodeRPCService { /** Empty rpc, only for connection test */ common.TSStatus testConnectionEmptyRPC() + + /** to write audit log or other events as time series **/ + common.TSStatus insertRecord(1:client.TSInsertRecordReq req); } service MPPDataExchangeService {
