This is an automated email from the ASF dual-hosted git repository. jiangtian pushed a commit to branch fix_login_query_leak in repository https://gitbox.apache.org/repos/asf/iotdb.git
commit 019f9440e53a6df1a2e5aa905d5debabf0b01ba8 Author: Tian Jiang <[email protected]> AuthorDate: Wed Aug 13 16:35:21 2025 +0800 Fix query leak during login & fix inconsistent expiration hints --- .../org/apache/iotdb/db/it/IoTDBLoginAndOutIT.java | 81 ++++++++++++++++++++++ .../iotdb/db/protocol/session/SessionManager.java | 14 +++- .../sql/ast/RelationalAuthorStatement.java | 9 ++- .../plan/statement/sys/AuthorStatement.java | 9 ++- .../apache/iotdb/db/utils/DataNodeAuthUtils.java | 17 ++++- 5 files changed, 120 insertions(+), 10 deletions(-) diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBLoginAndOutIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBLoginAndOutIT.java new file mode 100644 index 00000000000..e636d20dfc3 --- /dev/null +++ b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBLoginAndOutIT.java @@ -0,0 +1,81 @@ +/* + * 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.it; + +import org.apache.iotdb.it.env.EnvFactory; +import org.apache.iotdb.it.framework.IoTDBTestRunner; +import org.apache.iotdb.itbase.category.ClusterIT; +import org.apache.iotdb.itbase.category.LocalStandaloneIT; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; + +import static org.junit.Assert.assertEquals; + +@RunWith(IoTDBTestRunner.class) +@Category({LocalStandaloneIT.class, ClusterIT.class}) +public class IoTDBLoginAndOutIT { + + @BeforeClass + public static void setUp() throws Exception { + // use small page + EnvFactory.getEnv().initClusterEnvironment(); + } + + @AfterClass + public static void tearDown() throws Exception { + EnvFactory.getEnv().cleanClusterEnvironment(); + } + + @Test + public void testRepeatedlyLoginAndOut() throws Exception { + int attempts = 100; + for (int i = 0; i < attempts; i++) { + try (Connection ignored = EnvFactory.getEnv().getConnection()) { + // do nothing + } + } + try (Connection connection = EnvFactory.getEnv().getConnection(); + Statement statement = connection.createStatement()) { + int rowCount = 0; + ResultSet resultSet = statement.executeQuery("SHOW QUERIES"); + int columnCount = resultSet.getMetaData().getColumnCount(); + for (int i = 0; i < columnCount; i++) { + System.out.printf("%s, ", resultSet.getMetaData().getColumnName(i + 1)); + } + System.out.println(); + while (resultSet.next()) { + for (int i = 0; i < columnCount; i++) { + System.out.printf("%s, ", resultSet.getString(i + 1)); + } + System.out.println(); + rowCount++; + } + assertEquals(1, rowCount); + } + } +} 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 69008d34b75..6bc63023894 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 @@ -152,12 +152,14 @@ public class SessionManager implements SessionManagerMBean { lastDataQueryReq.setPaths( Collections.singletonList( SystemConstant.PREFIX_PASSWORD_HISTORY + ".`_" + username + "`.password")); + + long queryId = -1; try { Statement statement = StatementGenerator.createStatement(lastDataQueryReq); SessionInfo sessionInfo = new SessionInfo(0, AuthorityChecker.SUPER_USER, ZoneId.systemDefault()); - long queryId = requestQueryId(); + queryId = requestQueryId(); ExecutionResult result = Coordinator.getInstance() .executeForTreeModel( @@ -211,6 +213,10 @@ public class SessionManager implements SessionManagerMBean { "Internal server error " + ", please log in later or disable password expiration.", TSStatusCode.INTERNAL_SERVER_ERROR.getStatusCode()); } + } finally { + if (queryId != -1) { + Coordinator.getInstance().cleanupQueryExecution(queryId); + } } } @@ -256,7 +262,9 @@ public class SessionManager implements SessionManagerMBean { LOGGER.info( "No password history for user {}, using the current time to create a new one", username); - TSStatus tsStatus = DataNodeAuthUtils.recordPassword(username, password, null); + long currentTime = CommonDateTimeUtils.currentTime(); + TSStatus tsStatus = + DataNodeAuthUtils.recordPassword(username, password, null, currentTime); if (tsStatus.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) { openSessionResp .sessionId(-1) @@ -265,7 +273,7 @@ public class SessionManager implements SessionManagerMBean { return openSessionResp; } timeToExpire = - System.currentTimeMillis() + CommonDateTimeUtils.convertIoTDBTimeToMillis(currentTime) + CommonDescriptor.getInstance().getConfig().getPasswordExpirationDays() * 1000 * 86400; 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 74454e0eaf3..4e2b4597672 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 @@ -20,6 +20,7 @@ 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.commons.utils.CommonDateTimeUtils; 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; @@ -265,7 +266,9 @@ public class RelationalAuthorStatement extends Statement { } private TSStatus onCreateUserSuccess() { - TSStatus tsStatus = DataNodeAuthUtils.recordPassword(userName, password, null); + TSStatus tsStatus = + DataNodeAuthUtils.recordPassword( + userName, password, null, CommonDateTimeUtils.currentTime()); try { RpcUtils.verifySuccess(tsStatus); } catch (StatementExecutionException e) { @@ -275,7 +278,9 @@ public class RelationalAuthorStatement extends Statement { } private TSStatus onUpdateUserSuccess() { - TSStatus tsStatus = DataNodeAuthUtils.recordPassword(userName, password, oldPassword); + TSStatus tsStatus = + DataNodeAuthUtils.recordPassword( + userName, password, oldPassword, CommonDateTimeUtils.currentTime()); try { RpcUtils.verifySuccess(tsStatus); } catch (StatementExecutionException e) { 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 fc0481fa656..1cc2b3d499c 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 @@ -22,6 +22,7 @@ package org.apache.iotdb.db.queryengine.plan.statement.sys; import org.apache.iotdb.common.rpc.thrift.TSStatus; import org.apache.iotdb.commons.auth.entity.PrivilegeType; import org.apache.iotdb.commons.path.PartialPath; +import org.apache.iotdb.commons.utils.CommonDateTimeUtils; import org.apache.iotdb.db.auth.AuthorityChecker; import org.apache.iotdb.db.queryengine.plan.analyze.QueryType; import org.apache.iotdb.db.queryengine.plan.statement.AuthorType; @@ -364,7 +365,9 @@ public class AuthorStatement extends Statement implements IConfigStatement { } private TSStatus onCreateUserSuccess() { - TSStatus tsStatus = DataNodeAuthUtils.recordPassword(userName, password, null); + TSStatus tsStatus = + DataNodeAuthUtils.recordPassword( + userName, password, null, CommonDateTimeUtils.currentTime()); try { RpcUtils.verifySuccess(tsStatus); } catch (StatementExecutionException e) { @@ -374,7 +377,9 @@ public class AuthorStatement extends Statement implements IConfigStatement { } private TSStatus onUpdateUserSuccess() { - TSStatus tsStatus = DataNodeAuthUtils.recordPassword(userName, newPassword, password); + TSStatus tsStatus = + DataNodeAuthUtils.recordPassword( + userName, newPassword, password, CommonDateTimeUtils.currentTime()); try { RpcUtils.verifySuccess(tsStatus); } catch (StatementExecutionException e) { 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 3526c2619ff..180fb3e3e68 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 @@ -65,6 +65,7 @@ public class DataNodeAuthUtils { */ public static long getPasswordChangeTimeMillis(String username, String password) { + long queryId = -1; try { Statement statement = StatementGenerator.createStatement( @@ -79,7 +80,7 @@ public class DataNodeAuthUtils { SessionInfo sessionInfo = new SessionInfo(0, AuthorityChecker.SUPER_USER, ZoneId.systemDefault()); - long queryId = SessionManager.getInstance().requestQueryId(); + queryId = SessionManager.getInstance().requestQueryId(); ExecutionResult result = Coordinator.getInstance() .executeForTreeModel( @@ -108,6 +109,10 @@ public class DataNodeAuthUtils { } } catch (IoTDBException e) { LOGGER.warn("Cannot generate query for checking password reuse interval", e); + } finally { + if (queryId != -1) { + Coordinator.getInstance().cleanupQueryExecution(queryId); + } } return -1; } @@ -139,7 +144,8 @@ public class DataNodeAuthUtils { currentTimeMillis); } - public static TSStatus recordPassword(String username, String password, String oldPassword) { + public static TSStatus recordPassword( + String username, String password, String oldPassword, long timeToRecord) { InsertRowStatement insertRowStatement = new InsertRowStatement(); try { insertRowStatement.setDevicePath( @@ -160,11 +166,12 @@ public class DataNodeAuthUtils { + " because the path will be illegal"); } + long queryId = -1; try { SessionInfo sessionInfo = new SessionInfo(0, AuthorityChecker.SUPER_USER, ZoneId.systemDefault()); - long queryId = SessionManager.getInstance().requestQueryId(); + queryId = SessionManager.getInstance().requestQueryId(); ExecutionResult result = Coordinator.getInstance() .executeForTreeModel( @@ -182,6 +189,10 @@ public class DataNodeAuthUtils { LOGGER.error("Cannot create password history for {} because {}", username, e.getMessage()); return new TSStatus(TSStatusCode.INTERNAL_SERVER_ERROR.getStatusCode()) .setMessage("The server is not ready for login, please check the server log for details"); + } finally { + if (queryId != -1) { + Coordinator.getInstance().cleanupQueryExecution(queryId); + } } } }
