This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.1 by this push:
new 35629f416f3 branch-4.1: [fix](password) password lock failed after
invalid login #61592 (#61656)
35629f416f3 is described below
commit 35629f416f385191c2ea2587537b07d197bd11b6
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Wed Mar 25 11:43:17 2026 +0800
branch-4.1: [fix](password) password lock failed after invalid login #61592
(#61656)
Cherry-picked from #61592
Co-authored-by: Mingyu Chen (Rayner) <[email protected]>
---
.../doris/mysql/privilege/PasswordPolicy.java | 6 +-
.../doris/mysql/privilege/PasswordPolicyTest.java | 123 +++++++++++++++++++++
.../suites/account_p0/test_alter_user.groovy | 27 +++++
3 files changed, 155 insertions(+), 1 deletion(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PasswordPolicy.java
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PasswordPolicy.java
index de055d702d5..996e2ddec61 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PasswordPolicy.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PasswordPolicy.java
@@ -358,7 +358,11 @@ public class PasswordPolicy {
return false;
}
if (failedLoginCounter.get() >= numFailedLogin) {
- return true;
+ if (isLocked()) {
+ return true;
+ }
+ // Lock has expired, reset counter to allow re-locking on new
failed attempts
+ unlock();
}
if (failedLoginCounter.incrementAndGet() >= numFailedLogin) {
lockTime.set(System.currentTimeMillis());
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/PasswordPolicyTest.java
b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/PasswordPolicyTest.java
new file mode 100644
index 00000000000..942a098182b
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/PasswordPolicyTest.java
@@ -0,0 +1,123 @@
+// 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.doris.mysql.privilege;
+
+import org.apache.doris.mysql.privilege.PasswordPolicy.FailedLoginPolicy;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class PasswordPolicyTest {
+
+ @Test
+ public void testFailedLoginPolicyBasicLock() {
+ FailedLoginPolicy policy = new FailedLoginPolicy();
+ policy.numFailedLogin = 3;
+ policy.passwordLockSeconds = 60;
+
+ // First 2 failures should not lock
+ Assert.assertFalse(policy.onFailedLogin());
+ Assert.assertFalse(policy.onFailedLogin());
+ // 3rd failure should lock
+ Assert.assertTrue(policy.onFailedLogin());
+ Assert.assertTrue(policy.isLocked());
+ }
+
+ @Test
+ public void testFailedLoginPolicyRelockAfterExpiry() {
+ FailedLoginPolicy policy = new FailedLoginPolicy();
+ policy.numFailedLogin = 3;
+ policy.passwordLockSeconds = 5;
+
+ // Trigger first lock
+ Assert.assertFalse(policy.onFailedLogin());
+ Assert.assertFalse(policy.onFailedLogin());
+ Assert.assertTrue(policy.onFailedLogin());
+ Assert.assertTrue(policy.isLocked());
+
+ // Simulate lock expiry by setting lockTime to the past
+ policy.lockTime.set(System.currentTimeMillis() - 6000);
+ Assert.assertFalse(policy.isLocked());
+
+ // Now trigger re-lock: counter should reset and start counting again
+ // 1st failed login after expiry — counter resets from 3 to 0, then
increments to 1
+ Assert.assertFalse(policy.onFailedLogin());
+ Assert.assertFalse(policy.isLocked());
+ // 2nd
+ Assert.assertFalse(policy.onFailedLogin());
+ Assert.assertFalse(policy.isLocked());
+ // 3rd should lock again
+ Assert.assertTrue(policy.onFailedLogin());
+ Assert.assertTrue(policy.isLocked());
+ }
+
+ @Test
+ public void testFailedLoginPolicyStillLockedWhileActive() {
+ FailedLoginPolicy policy = new FailedLoginPolicy();
+ policy.numFailedLogin = 2;
+ policy.passwordLockSeconds = 60;
+
+ // Lock the account
+ Assert.assertFalse(policy.onFailedLogin());
+ Assert.assertTrue(policy.onFailedLogin());
+ Assert.assertTrue(policy.isLocked());
+
+ // While still locked, onFailedLogin should still return true
+ Assert.assertTrue(policy.onFailedLogin());
+ Assert.assertTrue(policy.isLocked());
+ }
+
+ @Test
+ public void testFailedLoginPolicyManualUnlock() {
+ FailedLoginPolicy policy = new FailedLoginPolicy();
+ policy.numFailedLogin = 2;
+ policy.passwordLockSeconds = 60;
+
+ // Lock the account
+ Assert.assertFalse(policy.onFailedLogin());
+ Assert.assertTrue(policy.onFailedLogin());
+ Assert.assertTrue(policy.isLocked());
+
+ // Manual unlock
+ policy.unlock();
+ Assert.assertFalse(policy.isLocked());
+
+ // Should be able to re-lock
+ Assert.assertFalse(policy.onFailedLogin());
+ Assert.assertTrue(policy.onFailedLogin());
+ Assert.assertTrue(policy.isLocked());
+ }
+
+ @Test
+ public void testFailedLoginPolicyDisabled() {
+ FailedLoginPolicy policy = new FailedLoginPolicy();
+ // Both disabled by default (0)
+ Assert.assertFalse(policy.onFailedLogin());
+ Assert.assertFalse(policy.isLocked());
+
+ // Only numFailedLogin set
+ policy.numFailedLogin = 3;
+ policy.passwordLockSeconds = 0;
+ Assert.assertFalse(policy.onFailedLogin());
+
+ // Only passwordLockSeconds set
+ policy.numFailedLogin = 0;
+ policy.passwordLockSeconds = 60;
+ Assert.assertFalse(policy.onFailedLogin());
+ }
+}
diff --git a/regression-test/suites/account_p0/test_alter_user.groovy
b/regression-test/suites/account_p0/test_alter_user.groovy
index c0d0a26fb60..6021c2c1546 100644
--- a/regression-test/suites/account_p0/test_alter_user.groovy
+++ b/regression-test/suites/account_p0/test_alter_user.groovy
@@ -122,6 +122,33 @@ suite("test_alter_user", "account,nonConcurrent") {
sql 'select 1'
}
+ // DORIS-24183: test re-locking after lock expiry
+ // after lock expires, entering wrong passwords again should trigger lock
again
+ try {
+ connect('test_auth_user3', 'wrong', context.config.jdbcUrl) {}
+ assertTrue(false, "should not be able to login")
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains("Access denied for user
'test_auth_user3"), e.getMessage())
+ }
+ try {
+ connect('test_auth_user3', 'wrong', context.config.jdbcUrl) {}
+ assertTrue(false, "should not be able to login")
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains("Access denied for user
'test_auth_user3"), e.getMessage())
+ }
+ // account should be locked again
+ try {
+ connect('test_auth_user3', '12345', context.config.jdbcUrl) {}
+ assertTrue(false, "should not be able to login")
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains("Account is blocked"),
e.getMessage())
+ }
+ // wait for lock to expire again
+ sleep(5000)
+ result1 = connect('test_auth_user3', '12345', context.config.jdbcUrl) {
+ sql 'select 1'
+ }
+
// 4. test password validation
sql """set global validate_password_policy=STRONG"""
test {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]