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

Reply via email to