This is an automated email from the ASF dual-hosted git repository. jiangtian pushed a commit to branch use_userid_for_password_history in repository https://gitbox.apache.org/repos/asf/iotdb.git
commit bf0f798a2438188e144b9336037b2660a2df8002 Author: Tian Jiang <[email protected]> AuthorDate: Thu Oct 9 16:03:56 2025 +0800 Use userId to record password history --- .../org/apache/iotdb/db/it/auth/IoTDBAuthIT.java | 28 +++-- .../protocol/thrift/IoTDBDataNodeReceiver.java | 4 +- .../iotdb/db/protocol/session/SessionManager.java | 114 +----------------- .../execution/config/TableConfigTaskVisitor.java | 2 +- .../execution/config/TreeConfigTaskVisitor.java | 3 +- .../sql/ast/RelationalAuthorStatement.java | 17 ++- .../plan/statement/sys/AuthorStatement.java | 17 ++- .../apache/iotdb/db/utils/DataNodeAuthUtils.java | 128 ++++++++++++++++++--- 8 files changed, 171 insertions(+), 142 deletions(-) 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 3dc96baf232..6b8746bcad3 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 @@ -50,6 +50,7 @@ import java.util.List; import java.util.Set; import java.util.concurrent.Callable; +import static org.apache.iotdb.commons.auth.entity.User.INTERNAL_USER_END_ID; import static org.apache.iotdb.db.audit.DNAuditLogger.PREFIX_PASSWORD_HISTORY; import static org.apache.iotdb.db.it.utils.TestUtils.createUser; import static org.apache.iotdb.db.it.utils.TestUtils.resultSetEqualTest; @@ -1518,9 +1519,12 @@ public class IoTDBAuthIT { public void testPasswordHistoryCreateAndDrop(Statement statement) throws SQLException { statement.execute("create user userA 'abcdef123456'"); + long expectedUserAId = INTERNAL_USER_END_ID + 1; try (ResultSet resultSet = statement.executeQuery( - String.format("select last password from %s.`_userA`", PREFIX_PASSWORD_HISTORY))) { + String.format( + "select last password from %s.`_" + expectedUserAId + "`", + PREFIX_PASSWORD_HISTORY))) { if (!resultSet.next()) { fail("Password history not found"); } @@ -1529,7 +1533,9 @@ public class IoTDBAuthIT { try (ResultSet resultSet = statement.executeQuery( - String.format("select last oldPassword from %s.`_userA`", PREFIX_PASSWORD_HISTORY))) { + String.format( + "select last oldPassword from %s.`_" + expectedUserAId + "`", + PREFIX_PASSWORD_HISTORY))) { if (!resultSet.next()) { fail("Password history not found"); } @@ -1540,13 +1546,17 @@ public class IoTDBAuthIT { try (ResultSet resultSet = statement.executeQuery( - String.format("select last password from %s.`_userA`", PREFIX_PASSWORD_HISTORY))) { + String.format( + "select last password from %s.`_" + expectedUserAId + "`", + PREFIX_PASSWORD_HISTORY))) { assertFalse(resultSet.next()); } try (ResultSet resultSet = statement.executeQuery( - String.format("select last oldPassword from %s.`_userA`", PREFIX_PASSWORD_HISTORY))) { + String.format( + "select last oldPassword from %s.`_" + expectedUserAId + "`", + PREFIX_PASSWORD_HISTORY))) { assertFalse(resultSet.next()); } } @@ -1555,9 +1565,12 @@ public class IoTDBAuthIT { statement.execute("create user userA 'abcdef123456'"); statement.execute("alter user userA set password 'abcdef654321'"); + long expectedUserAId = INTERNAL_USER_END_ID + 2; try (ResultSet resultSet = statement.executeQuery( - String.format("select last password from %s.`_userA`", PREFIX_PASSWORD_HISTORY))) { + String.format( + "select last password from %s.`_" + expectedUserAId + "`", + PREFIX_PASSWORD_HISTORY))) { if (!resultSet.next()) { fail("Password history not found"); } @@ -1567,14 +1580,15 @@ public class IoTDBAuthIT { try (ResultSet resultSet = statement.executeQuery( String.format( - "select oldPassword from %s.`_userA` order by time desc limit 1", + "select oldPassword from %s.`_" + expectedUserAId + "` order by time desc limit 1", PREFIX_PASSWORD_HISTORY))) { if (!resultSet.next()) { fail("Password history not found"); } assertEquals( AuthUtils.encryptPassword("abcdef123456"), - resultSet.getString(String.format("%s._userA.oldPassword", PREFIX_PASSWORD_HISTORY))); + resultSet.getString( + String.format("%s._" + expectedUserAId + ".oldPassword", PREFIX_PASSWORD_HISTORY))); } } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/receiver/protocol/thrift/IoTDBDataNodeReceiver.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/receiver/protocol/thrift/IoTDBDataNodeReceiver.java index 16f463f5f6f..e4093c26081 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/receiver/protocol/thrift/IoTDBDataNodeReceiver.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/receiver/protocol/thrift/IoTDBDataNodeReceiver.java @@ -103,6 +103,7 @@ import org.apache.iotdb.db.storageengine.rescon.disk.FolderManager; import org.apache.iotdb.db.storageengine.rescon.disk.strategy.DirectoryStrategyType; import org.apache.iotdb.db.tools.schema.SRStatementGenerator; import org.apache.iotdb.db.tools.schema.SchemaRegionSnapshotParser; +import org.apache.iotdb.db.utils.DataNodeAuthUtils; import org.apache.iotdb.pipe.api.exception.PipeException; import org.apache.iotdb.rpc.RpcUtils; import org.apache.iotdb.rpc.TSStatusCode; @@ -936,7 +937,8 @@ public class IoTDBDataNodeReceiver extends IoTDBFileReceiver { return RpcUtils.getStatus(openSessionResp.getCode(), openSessionResp.getMessage()); } - Long timeToExpire = SESSION_MANAGER.checkPasswordExpiration(username, password); + long userId = AuthorityChecker.getUserId(username).orElse(-1L); + Long timeToExpire = DataNodeAuthUtils.checkPasswordExpiration(userId, password); if (timeToExpire != null && timeToExpire <= System.currentTimeMillis()) { return RpcUtils.getStatus( TSStatusCode.ILLEGAL_PASSWORD.getStatusCode(), 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 bebc93b7e6b..e1851f62dc6 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 @@ -26,28 +26,18 @@ import org.apache.iotdb.commons.audit.AuditLogOperation; import org.apache.iotdb.commons.audit.UserEntity; import org.apache.iotdb.commons.conf.CommonDescriptor; import org.apache.iotdb.commons.conf.IoTDBConstant; -import org.apache.iotdb.commons.exception.IoTDBRuntimeException; 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.commons.utils.CommonDateTimeUtils; import org.apache.iotdb.db.audit.DNAuditLogger; import org.apache.iotdb.db.auth.AuthorityChecker; import org.apache.iotdb.db.auth.LoginLockManager; -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.storageengine.dataregion.read.control.QueryResourceManager; import org.apache.iotdb.db.utils.DataNodeAuthUtils; import org.apache.iotdb.metrics.utils.MetricLevel; @@ -56,11 +46,9 @@ 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; @@ -68,10 +56,8 @@ import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; -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; @@ -132,99 +118,6 @@ public class SessionManager implements SessionManagerMBean { IClientSession.SqlDialect.TREE); } - /** - * Check if the password for the give user has expired. - * - * @return the timestamp when the password will expire. Long.MAX if the password never expires. - * Null if the password history cannot be found. - */ - public Long checkPasswordExpiration(String username, String password) { - // check password expiration - long passwordExpirationDays = - CommonDescriptor.getInstance().getConfig().getPasswordExpirationDays(); - boolean mayBypassPasswordCheckInException = - CommonDescriptor.getInstance().getConfig().isMayBypassPasswordCheckInException(); - - TSLastDataQueryReq lastDataQueryReq = new TSLastDataQueryReq(); - lastDataQueryReq.setSessionId(0); - lastDataQueryReq.setPaths( - Collections.singletonList( - DNAuditLogger.PREFIX_PASSWORD_HISTORY + ".`_" + username + "`.password")); - - long queryId = -1; - try { - Statement statement = StatementGenerator.createStatement(lastDataQueryReq); - SessionInfo sessionInfo = - new SessionInfo( - 0, - new UserEntity( - AuthorityChecker.INTERNAL_AUDIT_USER_ID, - AuthorityChecker.INTERNAL_AUDIT_USER, - IoTDBDescriptor.getInstance().getConfig().getInternalAddress()), - ZoneId.systemDefault()); - - 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); - throw new IoTDBRuntimeException( - "Cannot query password history because: " - + result - + ", please log in later or disable password expiration.", - result.status.getCode()); - } - - 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 = - CommonDateTimeUtils.convertIoTDBTimeToMillis(tsBlock.getTimeByIndex(0)); - // columns of last query: [timeseriesName, value, dataType] - String oldPassword = tsBlock.getColumn(1).getBinary(0).toString(); - if (oldPassword.equals(AuthUtils.encryptPassword(password))) { - if (lastPasswordTime + passwordExpirationDays * 1000 * 86400 <= lastPasswordTime) { - // overflow or passwordExpirationDays <= 0 - return Long.MAX_VALUE; - } else { - return lastPasswordTime + passwordExpirationDays * 1000 * 86400; - } - } else { - // 1. the password is incorrect, later logIn will fail - // 2. the password history does not record correctly, use the current time to create one - return null; - } - } else { - return null; - } - } catch (Throwable e) { - LOGGER.error("Fail to check password expiration", e); - if (mayBypassPasswordCheckInException) { - return Long.MAX_VALUE; - } else { - throw new IoTDBRuntimeException( - "Internal server error " + ", please log in later or disable password expiration.", - TSStatusCode.INTERNAL_SERVER_ERROR.getStatusCode()); - } - } finally { - if (queryId != -1) { - Coordinator.getInstance().cleanupQueryExecution(queryId); - } - } - } - public BasicOpenSessionResp login( IClientSession session, String username, @@ -235,7 +128,9 @@ public class SessionManager implements SessionManagerMBean { IClientSession.SqlDialect sqlDialect) { BasicOpenSessionResp openSessionResp = new BasicOpenSessionResp(); - Long timeToExpire = checkPasswordExpiration(username, password); + long userId = AuthorityChecker.getUserId(username).orElse(-1L); + + Long timeToExpire = DataNodeAuthUtils.checkPasswordExpiration(userId, password); if (timeToExpire != null && timeToExpire <= System.currentTimeMillis()) { openSessionResp .sessionId(-1) @@ -244,7 +139,6 @@ public class SessionManager implements SessionManagerMBean { return openSessionResp; } - long userId = AuthorityChecker.getUserId(username).orElse(-1L); boolean enableLoginLock = userId != -1; LoginLockManager loginLockManager = LoginLockManager.getInstance(); if (enableLoginLock && loginLockManager.checkLock(userId, session.getClientAddress())) { @@ -281,7 +175,7 @@ public class SessionManager implements SessionManagerMBean { username); long currentTime = CommonDateTimeUtils.currentTime(); TSStatus tsStatus = - DataNodeAuthUtils.recordPasswordHistory(username, password, password, currentTime); + DataNodeAuthUtils.recordPasswordHistory(userId, password, password, currentTime); if (tsStatus.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) { openSessionResp .sessionId(-1) 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 57b4d5bbdae..0475a27c26d 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 @@ -1406,7 +1406,7 @@ public class TableConfigTaskVisitor extends AstVisitor<IConfigTask, MPPQueryCont throw new SemanticException("User " + node.getUserName() + " not found"); } node.setOldPassword(user.getPassword()); - DataNodeAuthUtils.verifyPasswordReuse(node.getUserName(), node.getPassword()); + DataNodeAuthUtils.verifyPasswordReuse(node.getAssociatedUserId(), node.getPassword()); } @Override 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 e01dfa918db..465326ff639 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 @@ -332,7 +332,8 @@ public class TreeConfigTaskVisitor extends StatementVisitor<IConfigTask, MPPQuer throw new SemanticException("User " + statement.getUserName() + " not found"); } statement.setPassWord(user.getPassword()); - DataNodeAuthUtils.verifyPasswordReuse(statement.getUserName(), statement.getNewPassword()); + DataNodeAuthUtils.verifyPasswordReuse( + statement.getAssociatedUsedId(), statement.getNewPassword()); } private void visitRenameUser(AuthorStatement statement) { 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 74e93acaad0..16f38b9c684 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 @@ -57,6 +57,9 @@ public class RelationalAuthorStatement extends Statement { private String newUsername = ""; private String loginAddr; + // the id of userName + private long associatedUserId = -1; + public RelationalAuthorStatement( AuthorRType authorType, String userName, @@ -162,6 +165,9 @@ public class RelationalAuthorStatement extends Statement { public void setUserName(String userName) { this.userName = userName; + if (authorType != AuthorRType.CREATE_USER) { + this.associatedUserId = AuthorityChecker.getUserId(userName).orElse(-1L); + } } public void setRoleName(String roleName) { @@ -301,11 +307,12 @@ public class RelationalAuthorStatement extends Statement { } private TSStatus onCreateUserSuccess() { + associatedUserId = AuthorityChecker.getUserId(userName).orElse(-1L); // the old password is expected to be encrypted during updates, so we also encrypt it here to // keep consistency TSStatus tsStatus = DataNodeAuthUtils.recordPasswordHistory( - userName, + associatedUserId, password, AuthUtils.encryptPassword(password), CommonDateTimeUtils.currentTime()); @@ -320,7 +327,7 @@ public class RelationalAuthorStatement extends Statement { private TSStatus onUpdateUserSuccess() { TSStatus tsStatus = DataNodeAuthUtils.recordPasswordHistory( - userName, password, oldPassword, CommonDateTimeUtils.currentTime()); + associatedUserId, password, oldPassword, CommonDateTimeUtils.currentTime()); try { RpcUtils.verifySuccess(tsStatus); } catch (StatementExecutionException e) { @@ -330,7 +337,7 @@ public class RelationalAuthorStatement extends Statement { } private TSStatus onDropUserSuccess() { - TSStatus tsStatus = DataNodeAuthUtils.deletePasswordHistory(userName); + TSStatus tsStatus = DataNodeAuthUtils.deletePasswordHistory(associatedUserId); try { RpcUtils.verifySuccess(tsStatus); } catch (StatementExecutionException e) { @@ -435,4 +442,8 @@ public class RelationalAuthorStatement extends Statement { } return RpcUtils.SUCCESS_STATUS; } + + public long getAssociatedUserId() { + return associatedUserId; + } } 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 afbef6f7900..a972ddea232 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 @@ -51,6 +51,9 @@ public class AuthorStatement extends Statement implements IConfigStatement { private String newUsername = ""; private String loginAddr; + // the id of userName + private long associatedUsedId = -1; + /** * Constructor with AuthorType. * @@ -137,6 +140,9 @@ public class AuthorStatement extends Statement implements IConfigStatement { public void setUserName(String userName) { this.userName = userName; + if (authorType != AuthorType.CREATE_USER) { + this.associatedUsedId = AuthorityChecker.getUserId(userName).orElse(-1L); + } } public String getRoleName() { @@ -269,11 +275,12 @@ public class AuthorStatement extends Statement implements IConfigStatement { } private TSStatus onCreateUserSuccess() { + associatedUsedId = AuthorityChecker.getUserId(userName).orElse(-1L); // the old password is expected to be encrypted during updates, so we also encrypt it here to // keep consistency TSStatus tsStatus = DataNodeAuthUtils.recordPasswordHistory( - userName, + associatedUsedId, password, AuthUtils.encryptPassword(password), CommonDateTimeUtils.currentTime()); @@ -288,7 +295,7 @@ public class AuthorStatement extends Statement implements IConfigStatement { private TSStatus onUpdateUserSuccess() { TSStatus tsStatus = DataNodeAuthUtils.recordPasswordHistory( - userName, newPassword, password, CommonDateTimeUtils.currentTime()); + associatedUsedId, newPassword, password, CommonDateTimeUtils.currentTime()); try { RpcUtils.verifySuccess(tsStatus); } catch (StatementExecutionException e) { @@ -298,7 +305,7 @@ public class AuthorStatement extends Statement implements IConfigStatement { } private TSStatus onDropUserSuccess() { - TSStatus tsStatus = DataNodeAuthUtils.deletePasswordHistory(userName); + TSStatus tsStatus = DataNodeAuthUtils.deletePasswordHistory(associatedUsedId); try { RpcUtils.verifySuccess(tsStatus); } catch (StatementExecutionException e) { @@ -337,4 +344,8 @@ public class AuthorStatement extends Statement implements IConfigStatement { } return RpcUtils.SUCCESS_STATUS; } + + public long getAssociatedUsedId() { + return associatedUsedId; + } } 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 index 39af6bf24bb..3a96f72f033 100644 --- 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 @@ -24,6 +24,7 @@ import org.apache.iotdb.commons.audit.UserEntity; 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.exception.IoTDBRuntimeException; import org.apache.iotdb.commons.path.PartialPath; import org.apache.iotdb.commons.utils.AuthUtils; import org.apache.iotdb.commons.utils.CommonDateTimeUtils; @@ -44,6 +45,7 @@ import org.apache.iotdb.db.queryengine.plan.statement.Statement; import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertRowStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.DeleteTimeSeriesStatement; import org.apache.iotdb.rpc.TSStatusCode; +import org.apache.iotdb.service.rpc.thrift.TSLastDataQueryReq; import org.apache.tsfile.enums.TSDataType; import org.apache.tsfile.read.common.block.TsBlock; @@ -54,6 +56,7 @@ import org.slf4j.LoggerFactory; import java.nio.charset.StandardCharsets; import java.time.ZoneId; import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.Optional; @@ -67,7 +70,7 @@ public class DataNodeAuthUtils { * @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 changed. */ - public static long getPasswordChangeTimeMillis(String username, String password) { + public static long getPasswordChangeTimeMillis(long userId, String password) { long queryId = -1; try { @@ -76,7 +79,7 @@ public class DataNodeAuthUtils { "SELECT password from " + DNAuditLogger.PREFIX_PASSWORD_HISTORY + ".`_" - + username + + userId + "` where oldPassword='" + AuthUtils.encryptPassword(password) + "' order by time desc limit 1", @@ -128,14 +131,14 @@ public class DataNodeAuthUtils { return -1; } - public static void verifyPasswordReuse(String username, String password) { + public static void verifyPasswordReuse(long userId, String password) { long passwordReuseIntervalDays = CommonDescriptor.getInstance().getConfig().getPasswordReuseIntervalDays(); if (password == null || passwordReuseIntervalDays <= 0) { return; } - long passwordChangeTime = DataNodeAuthUtils.getPasswordChangeTimeMillis(username, password); + long passwordChangeTime = DataNodeAuthUtils.getPasswordChangeTimeMillis(userId, password); long currentTimeMillis = System.currentTimeMillis(); long elapsedTime = currentTimeMillis - passwordChangeTime; long reuseIntervalMillis = @@ -156,11 +159,11 @@ public class DataNodeAuthUtils { } public static TSStatus recordPasswordHistory( - String username, String password, String oldPassword, long timeToRecord) { + long userId, String password, String oldPassword, long timeToRecord) { InsertRowStatement insertRowStatement = new InsertRowStatement(); try { insertRowStatement.setDevicePath( - new PartialPath(DNAuditLogger.PREFIX_PASSWORD_HISTORY + ".`_" + username + "`")); + new PartialPath(DNAuditLogger.PREFIX_PASSWORD_HISTORY + ".`_" + userId + "`")); insertRowStatement.setTime(timeToRecord); insertRowStatement.setMeasurements(new String[] {"password", "oldPassword"}); insertRowStatement.setValues( @@ -172,9 +175,7 @@ public class DataNodeAuthUtils { } catch (IllegalPathException ignored) { return new TSStatus(TSStatusCode.INTERNAL_SERVER_ERROR.getStatusCode()) .setMessage( - "Cannot create password history for " - + username - + " because the path will be illegal"); + "Cannot create password history for " + userId + " because the path will be illegal"); } long queryId = -1; @@ -203,7 +204,7 @@ public class DataNodeAuthUtils { if (CommonDescriptor.getInstance().getConfig().isMayBypassPasswordCheckInException()) { return StatusUtils.OK; } - LOGGER.error("Cannot create password history for {} because {}", username, e.getMessage()); + LOGGER.error("Cannot create password history for {}", userId, e); return new TSStatus(TSStatusCode.INTERNAL_SERVER_ERROR.getStatusCode()) .setMessage("The server is not ready for login, please check the server log for details"); } finally { @@ -213,11 +214,11 @@ public class DataNodeAuthUtils { } } - public static TSStatus deletePasswordHistory(String username) { + public static TSStatus deletePasswordHistory(long userId) { DeleteTimeSeriesStatement deleteTimeSeriesStatement = new DeleteTimeSeriesStatement(); try { PartialPath devicePath = - new PartialPath(DNAuditLogger.PREFIX_PASSWORD_HISTORY + ".`_" + username + "`"); + new PartialPath(DNAuditLogger.PREFIX_PASSWORD_HISTORY + ".`_" + userId + "`"); deleteTimeSeriesStatement.setPathPatternList( Arrays.asList( devicePath.concatAsMeasurementPath("password"), @@ -225,9 +226,7 @@ public class DataNodeAuthUtils { } catch (IllegalPathException ignored) { return new TSStatus(TSStatusCode.INTERNAL_SERVER_ERROR.getStatusCode()) .setMessage( - "Cannot delete password history for " - + username - + " because the path will be illegal"); + "Cannot delete password history for " + userId + " because the path will be illegal"); } long queryId = -1; @@ -256,7 +255,7 @@ public class DataNodeAuthUtils { if (CommonDescriptor.getInstance().getConfig().isMayBypassPasswordCheckInException()) { return StatusUtils.OK; } - LOGGER.error("Cannot delete password history for {} because {}", username, e.getMessage()); + LOGGER.error("Cannot delete password history for {}", userId, e); return new TSStatus(TSStatusCode.INTERNAL_SERVER_ERROR.getStatusCode()) .setMessage( "The server is not ready for this operation, please check the server log for details"); @@ -266,4 +265,101 @@ public class DataNodeAuthUtils { } } } + + /** + * Check if the password for the give user has expired. + * + * @return the timestamp when the password will expire. Long.MAX if the password never expires. + * Null if the password history cannot be found. + */ + public static Long checkPasswordExpiration(long userId, String password) { + if (userId == -1) { + return null; + } + + // check password expiration + long passwordExpirationDays = + CommonDescriptor.getInstance().getConfig().getPasswordExpirationDays(); + boolean mayBypassPasswordCheckInException = + CommonDescriptor.getInstance().getConfig().isMayBypassPasswordCheckInException(); + + TSLastDataQueryReq lastDataQueryReq = new TSLastDataQueryReq(); + lastDataQueryReq.setSessionId(0); + lastDataQueryReq.setPaths( + Collections.singletonList( + DNAuditLogger.PREFIX_PASSWORD_HISTORY + ".`_" + userId + "`.password")); + + long queryId = -1; + try { + Statement statement = StatementGenerator.createStatement(lastDataQueryReq); + SessionInfo sessionInfo = + new SessionInfo( + 0, + new UserEntity( + AuthorityChecker.INTERNAL_AUDIT_USER_ID, + AuthorityChecker.INTERNAL_AUDIT_USER, + IoTDBDescriptor.getInstance().getConfig().getInternalAddress()), + ZoneId.systemDefault()); + + 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 check password expiration: {}", result.status); + throw new IoTDBRuntimeException( + "Cannot query password history because: " + + result + + ", please log in later or disable password expiration.", + result.status.getCode()); + } + + 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 = + CommonDateTimeUtils.convertIoTDBTimeToMillis(tsBlock.getTimeByIndex(0)); + // columns of last query: [timeseriesName, value, dataType] + String oldPassword = tsBlock.getColumn(1).getBinary(0).toString(); + if (oldPassword.equals(AuthUtils.encryptPassword(password))) { + if (lastPasswordTime + passwordExpirationDays * 1000 * 86400 <= lastPasswordTime) { + // overflow or passwordExpirationDays <= 0 + return Long.MAX_VALUE; + } else { + return lastPasswordTime + passwordExpirationDays * 1000 * 86400; + } + } else { + // 1. the password is incorrect, later logIn will fail + // 2. the password history does not record correctly, use the current time to create one + return null; + } + } else { + return null; + } + } catch (Throwable e) { + LOGGER.error("Fail to check password expiration", e); + if (mayBypassPasswordCheckInException) { + return Long.MAX_VALUE; + } else { + throw new IoTDBRuntimeException( + "Internal server error " + ", please log in later or disable password expiration.", + TSStatusCode.INTERNAL_SERVER_ERROR.getStatusCode()); + } + } finally { + if (queryId != -1) { + Coordinator.getInstance().cleanupQueryExecution(queryId); + } + } + } }
