From 6d0f1dc42a1ee3bea1e85267777025e6bc15c920 Mon Sep 17 00:00:00 2001 From: Ivan Orekhov Date: Mon, 26 Jan 2026 15:02:43 +0300 Subject: [PATCH 1/6] feat: add property to manage usage of empty pass for login --- conf/ldap.conf | 3 +++ .../src/main/java/org/apache/doris/common/LdapConfig.java | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/conf/ldap.conf b/conf/ldap.conf index 9388ae7ee50b1e..15a62c9134f159 100644 --- a/conf/ldap.conf +++ b/conf/ldap.conf @@ -47,6 +47,9 @@ ldap_group_basedn = ou=group,dc=domain,dc=com ## ldap_use_ssl - use secured connection to LDAP server if required (disabled by default). Note: When enabling SSL, ensure ldap_port is set appropriately (typically 636 for LDAPS instead of 389 for LDAP). # ldap_use_ssl = false +## ldap_allow_empty_pass - allow to connect to ldap with empty pass (enabled by default) +# ldap_allow_empty_pass = true + # LDAP pool configuration # https://docs.spring.io/spring-ldap/docs/2.3.3.RELEASE/reference/#pool-configuration # ldap_pool_max_active = 8 diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/LdapConfig.java b/fe/fe-common/src/main/java/org/apache/doris/common/LdapConfig.java index 881840696dcde8..d169d93cbd5382 100644 --- a/fe/fe-common/src/main/java/org/apache/doris/common/LdapConfig.java +++ b/fe/fe-common/src/main/java/org/apache/doris/common/LdapConfig.java @@ -175,4 +175,10 @@ public class LdapConfig extends ConfigBase { public static String getConnectionURL(String hostPortInAccessibleFormat) { return ((LdapConfig.ldap_use_ssl ? "ldaps" : "ldap") + "://" + hostPortInAccessibleFormat); } + + /** + * Flag to enable login with empty pass. + */ + @ConfigBase.ConfField + public static boolean ldap_allow_empty_pass = true; } From 3ee5537a8dcad8a76940496f0c1cb32cdae18ba9 Mon Sep 17 00:00:00 2001 From: Ivan Orekhov Date: Mon, 26 Jan 2026 15:54:10 +0300 Subject: [PATCH 2/6] feat: added logic and test to support allow_empty_pass mode --- .../org/apache/doris/common/ErrorCode.java | 5 +++- .../authenticate/ldap/LdapAuthenticator.java | 10 ++++++- .../apache/doris/mysql/privilege/Auth.java | 7 +++++ .../ldap/LdapAuthenticatorTest.java | 26 +++++++++++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/ErrorCode.java b/fe/fe-common/src/main/java/org/apache/doris/common/ErrorCode.java index a678c2b38f9562..c60088c41fc3ac 100644 --- a/fe/fe-common/src/main/java/org/apache/doris/common/ErrorCode.java +++ b/fe/fe-common/src/main/java/org/apache/doris/common/ErrorCode.java @@ -1233,7 +1233,10 @@ public enum ErrorCode { ERR_NO_CLUSTER_ERROR(5099, new byte[]{'4', '2', '0', '0', '0'}, "No compute group (cloud cluster) selected"), ERR_NOT_CLOUD_MODE(6000, new byte[]{'4', '2', '0', '0', '0'}, - "Command only support in cloud mode."); + "Command only support in cloud mode."), + + ERR_EMPTY_PASSWORD(6001, new byte[]{'4', '2', '0', '0', '0'}, + "Access with empty password is prohibited for user %s because of current mode"); // This is error code private final int code; diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticator.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticator.java index d9e27e4a9fb02f..2fdcb2e6aa9a58 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticator.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticator.java @@ -21,6 +21,7 @@ import org.apache.doris.catalog.Env; import org.apache.doris.common.ErrorCode; import org.apache.doris.common.ErrorReport; +import org.apache.doris.common.LdapConfig; import org.apache.doris.mysql.authenticate.AuthenticateRequest; import org.apache.doris.mysql.authenticate.AuthenticateResponse; import org.apache.doris.mysql.authenticate.Authenticator; @@ -83,7 +84,7 @@ public boolean canDeal(String qualifiedUser) { /** * The LDAP authentication process is as follows: - * step1: Check the LDAP password. + * step1: Check the LDAP password (depending on value of property LdapConfig.ldap_allow_empty_pass login with empty pass can be prohibited). * step2: Get the LDAP groups privileges as a role, saved into ConnectContext. * step3: Set current userIdentity. If the user account does not exist in Doris, login as a temporary user. * Otherwise, login to the Doris account. @@ -95,6 +96,13 @@ private AuthenticateResponse internalAuthenticate(String password, String qualif LOG.debug("user:{}", userName); } + //not allow to login in case when empty password is specified but such mode is disabled by configuration + if (Strings.isNullOrEmpty(password) && !LdapConfig.ldap_allow_empty_pass) { + LOG.info("user:{} is not allowed to login with LDAP with empty password because of ldap_allow_empty_pass is {}", userName, LdapConfig.ldap_allow_empty_pass); + ErrorReport.report(ErrorCode.ERR_EMPTY_PASSWORD, qualifiedUser + "@" + remoteIp); + return AuthenticateResponse.failedResponse; + } + // check user password by ldap server. try { if (!Env.getCurrentEnv().getAuth().getLdapManager().checkUserPasswd(qualifiedUser, password)) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java index 371d4b7c2f7276..204b9d0deff99c 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java @@ -38,6 +38,7 @@ import org.apache.doris.common.ErrorCode; import org.apache.doris.common.ErrorReport; import org.apache.doris.common.FeConstants; +import org.apache.doris.common.LdapConfig; import org.apache.doris.common.Pair; import org.apache.doris.common.PatternMatcherException; import org.apache.doris.common.UserException; @@ -45,6 +46,7 @@ import org.apache.doris.datasource.CatalogIf; import org.apache.doris.datasource.InternalCatalog; import org.apache.doris.mysql.MysqlPassword; +import org.apache.doris.mysql.authenticate.AuthenticateResponse; import org.apache.doris.mysql.authenticate.AuthenticateType; import org.apache.doris.mysql.authenticate.ldap.LdapManager; import org.apache.doris.mysql.authenticate.ldap.LdapUserInfo; @@ -227,6 +229,11 @@ public void checkPlainPassword(String remoteUser, String remoteHost, String remo List currentUser) throws AuthenticationException { // Check the LDAP password when the user exists in the LDAP service. if (ldapManager.doesUserExist(remoteUser)) { + //not allow to login in case when empty password is specified but such mode is disabled by configuration + if (Strings.isNullOrEmpty(remotePasswd) && !LdapConfig.ldap_allow_empty_pass) { + throw new AuthenticationException(ErrorCode.ERR_EMPTY_PASSWORD, remoteUser + "@" + remoteHost); + } + if (!ldapManager.checkUserPasswd(remoteUser, remotePasswd, remoteHost, currentUser)) { throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost, Strings.isNullOrEmpty(remotePasswd) ? "NO" : "YES"); diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java index 99cbcdb5fad643..d7bee2514ba6ce 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java @@ -18,6 +18,7 @@ package org.apache.doris.mysql.authenticate.ldap; import org.apache.doris.analysis.UserIdentity; +import org.apache.doris.common.LdapConfig; import org.apache.doris.mysql.authenticate.AuthenticateRequest; import org.apache.doris.mysql.authenticate.AuthenticateResponse; import org.apache.doris.mysql.authenticate.password.ClearPassword; @@ -27,6 +28,8 @@ import com.google.common.collect.Lists; import mockit.Expectations; import mockit.Mocked; + +import org.junit.After; import org.junit.Assert; import org.junit.Test; @@ -143,4 +146,27 @@ public void testCanDeal() { public void testGetPasswordResolver() { Assert.assertTrue(ldapAuthenticator.getPasswordResolver() instanceof ClearPasswordResolver); } + + @Test + public void testEmptyPassword() throws IOException { + setCheckPassword(true); + setGetUserInDoris(true); + AuthenticateRequest request = new AuthenticateRequest(USER_NAME, new ClearPassword(""), IP); + //running test with non-specified value - ldap_allow_empty_pass should be true + AuthenticateResponse response = ldapAuthenticator.authenticate(request); + Assert.assertTrue(response.isSuccess()); + //running test with specified value - true - ldap_allow_empty_pass is explicitly set to true + LdapConfig.ldap_allow_empty_pass = true; + response = ldapAuthenticator.authenticate(request); + Assert.assertTrue(response.isSuccess()); + //running test with specified value - false - ldap_allow_empty_pass is explicitly set to false + LdapConfig.ldap_allow_empty_pass = false; + response = ldapAuthenticator.authenticate(request); + Assert.assertFalse(response.isSuccess()); + } + + @After + public void tearDown() { + LdapConfig.ldap_allow_empty_pass = true; // restoring default value for other tests + } } From 519d490113a3f596284e363fd1e7fe02e0ddf20d Mon Sep 17 00:00:00 2001 From: Ivan Orekhov Date: Thu, 29 Jan 2026 18:28:29 +0300 Subject: [PATCH 3/6] fix: fixing indentation --- .../src/main/java/org/apache/doris/common/ErrorCode.java | 2 +- .../doris/mysql/authenticate/ldap/LdapAuthenticator.java | 5 +++-- .../src/main/java/org/apache/doris/mysql/privilege/Auth.java | 1 - .../doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java | 3 +-- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/ErrorCode.java b/fe/fe-common/src/main/java/org/apache/doris/common/ErrorCode.java index c60088c41fc3ac..0707a2ccfe7631 100644 --- a/fe/fe-common/src/main/java/org/apache/doris/common/ErrorCode.java +++ b/fe/fe-common/src/main/java/org/apache/doris/common/ErrorCode.java @@ -1236,7 +1236,7 @@ public enum ErrorCode { "Command only support in cloud mode."), ERR_EMPTY_PASSWORD(6001, new byte[]{'4', '2', '0', '0', '0'}, - "Access with empty password is prohibited for user %s because of current mode"); + "Access with empty password is prohibited for user %s because of current mode"); // This is error code private final int code; diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticator.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticator.java index 2fdcb2e6aa9a58..638903490bb784 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticator.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticator.java @@ -84,7 +84,7 @@ public boolean canDeal(String qualifiedUser) { /** * The LDAP authentication process is as follows: - * step1: Check the LDAP password (depending on value of property LdapConfig.ldap_allow_empty_pass login with empty pass can be prohibited). + * step1: Check the LDAP password (if ldap_allow_empty_pass is false login with empty pass is prohibited). * step2: Get the LDAP groups privileges as a role, saved into ConnectContext. * step3: Set current userIdentity. If the user account does not exist in Doris, login as a temporary user. * Otherwise, login to the Doris account. @@ -98,7 +98,8 @@ private AuthenticateResponse internalAuthenticate(String password, String qualif //not allow to login in case when empty password is specified but such mode is disabled by configuration if (Strings.isNullOrEmpty(password) && !LdapConfig.ldap_allow_empty_pass) { - LOG.info("user:{} is not allowed to login with LDAP with empty password because of ldap_allow_empty_pass is {}", userName, LdapConfig.ldap_allow_empty_pass); + LOG.info("user:{} is not allowed to login to LDAP with empty password because ldap_allow_empty_pass:{}", + userName, LdapConfig.ldap_allow_empty_pass); ErrorReport.report(ErrorCode.ERR_EMPTY_PASSWORD, qualifiedUser + "@" + remoteIp); return AuthenticateResponse.failedResponse; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java index 204b9d0deff99c..1f04c5f8f787fb 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java @@ -46,7 +46,6 @@ import org.apache.doris.datasource.CatalogIf; import org.apache.doris.datasource.InternalCatalog; import org.apache.doris.mysql.MysqlPassword; -import org.apache.doris.mysql.authenticate.AuthenticateResponse; import org.apache.doris.mysql.authenticate.AuthenticateType; import org.apache.doris.mysql.authenticate.ldap.LdapManager; import org.apache.doris.mysql.authenticate.ldap.LdapUserInfo; diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java index d7bee2514ba6ce..17dea744b17d73 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java @@ -28,7 +28,6 @@ import com.google.common.collect.Lists; import mockit.Expectations; import mockit.Mocked; - import org.junit.After; import org.junit.Assert; import org.junit.Test; @@ -162,7 +161,7 @@ public void testEmptyPassword() throws IOException { //running test with specified value - false - ldap_allow_empty_pass is explicitly set to false LdapConfig.ldap_allow_empty_pass = false; response = ldapAuthenticator.authenticate(request); - Assert.assertFalse(response.isSuccess()); + Assert.assertFalse(response.isSuccess()); } @After From a8072aff16eba7addef97450bfd0c1181f22c1ba Mon Sep 17 00:00:00 2001 From: Ivan Orekhov Date: Fri, 30 Jan 2026 10:04:53 +0300 Subject: [PATCH 4/6] fix: fix indentation --- .../doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java index 17dea744b17d73..86fd577021df4b 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java @@ -161,7 +161,7 @@ public void testEmptyPassword() throws IOException { //running test with specified value - false - ldap_allow_empty_pass is explicitly set to false LdapConfig.ldap_allow_empty_pass = false; response = ldapAuthenticator.authenticate(request); - Assert.assertFalse(response.isSuccess()); + Assert.assertFalse(response.isSuccess()); } @After From 1d6858dcb8062c5fbb08c633c42514b8f2b0c37a Mon Sep 17 00:00:00 2001 From: Ivan Orekhov Date: Fri, 6 Feb 2026 13:49:43 +0300 Subject: [PATCH 5/6] fix: improve test to restore property before test --- .../mysql/authenticate/ldap/LdapAuthenticatorTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java index 86fd577021df4b..4a1a96c1ddaf8b 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java @@ -29,6 +29,7 @@ import mockit.Expectations; import mockit.Mocked; import org.junit.After; +import org.junit.Before; import org.junit.Assert; import org.junit.Test; @@ -168,4 +169,9 @@ public void testEmptyPassword() throws IOException { public void tearDown() { LdapConfig.ldap_allow_empty_pass = true; // restoring default value for other tests } + + @Before + public void setUp() { + LdapConfig.ldap_allow_empty_pass = true; //restoring default value for other tests + } } From 8fa563330bafb91c2f9f69f841d845f183198ca7 Mon Sep 17 00:00:00 2001 From: Ivan Orekhov Date: Fri, 6 Feb 2026 19:22:43 +0300 Subject: [PATCH 6/6] fix import order --- .../doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java index 4a1a96c1ddaf8b..5a17a3caa80f31 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java @@ -29,8 +29,8 @@ import mockit.Expectations; import mockit.Mocked; import org.junit.After; -import org.junit.Before; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import java.io.IOException;