From 0b0205528b09a698bf8ffdfb2fb7437a530b8bfd Mon Sep 17 00:00:00 2001 From: Ivan Orekhov Date: Wed, 27 May 2026 11:42:28 +0300 Subject: [PATCH 1/6] feat: disable LDAP empty password authentication --- conf/ldap.conf | 3 + ...apAuthenticationPluginIntegrationTest.java | 50 +++++++- .../org/apache/doris/common/ErrorCode.java | 5 +- .../org/apache/doris/common/LdapConfig.java | 6 + .../authenticate/ldap/LdapAuthenticator.java | 11 +- .../apache/doris/mysql/privilege/Auth.java | 15 ++- .../ldap/LdapAuthenticatorTest.java | 56 +++++++++ ...PlainAuthWithEmptyPasswordAndLdapTest.java | 108 ++++++++++++++++++ 8 files changed, 247 insertions(+), 7 deletions(-) create mode 100644 fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/PlainAuthWithEmptyPasswordAndLdapTest.java diff --git a/conf/ldap.conf b/conf/ldap.conf index 00647819273ce1..8ef56d85e8a806 100644 --- a/conf/ldap.conf +++ b/conf/ldap.conf @@ -50,6 +50,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-authentication/fe-authentication-plugins/fe-authentication-plugin-ldap/src/test/java/org/apache/doris/authentication/plugin/ldap/LdapAuthenticationPluginIntegrationTest.java b/fe/fe-authentication/fe-authentication-plugins/fe-authentication-plugin-ldap/src/test/java/org/apache/doris/authentication/plugin/ldap/LdapAuthenticationPluginIntegrationTest.java index c014836a48a6b5..58642595e9ae44 100644 --- a/fe/fe-authentication/fe-authentication-plugins/fe-authentication-plugin-ldap/src/test/java/org/apache/doris/authentication/plugin/ldap/LdapAuthenticationPluginIntegrationTest.java +++ b/fe/fe-authentication/fe-authentication-plugins/fe-authentication-plugin-ldap/src/test/java/org/apache/doris/authentication/plugin/ldap/LdapAuthenticationPluginIntegrationTest.java @@ -22,6 +22,7 @@ import org.apache.doris.authentication.AuthenticationResult; import org.apache.doris.authentication.CredentialType; import org.apache.doris.authentication.Principal; +import org.apache.doris.common.LdapConfig; import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; @@ -30,6 +31,7 @@ import com.unboundid.ldif.LDIFException; import com.unboundid.ldif.LDIFReader; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -111,8 +113,17 @@ static void stopLdapServer() { } } + @AfterEach + void tearDown() { + //running test with specified value - ldap_allow_empty_pass is be true + LdapConfig.ldap_allow_empty_pass = true; + } + @BeforeEach void setUp() { + //running test with specified value - ldap_allow_empty_pass is be true + LdapConfig.ldap_allow_empty_pass = true; + plugin = new LdapAuthenticationPlugin(); // Create integration configuration pointing to embedded LDAP @@ -220,8 +231,43 @@ void testAuthenticateNonExistentUser() throws Exception { } @Test - @DisplayName("Should reject empty password") - void testAuthenticateEmptyPassword() throws Exception { + @DisplayName("Should reject empty password with default value of ldap_allow_empty_pass") + void testAuthenticateEmptyPasswordAndAllowEmptyPassDefault() throws Exception { + //running test with default value - ldap_allow_empty_pass should be true + LdapConfig.ldap_allow_empty_pass = true; + AuthenticationRequest request = AuthenticationRequest.builder() + .username("alice") + .credentialType(CredentialType.CLEAR_TEXT_PASSWORD) + .credential("".getBytes(StandardCharsets.UTF_8)) + .build(); + + AuthenticationResult result = plugin.authenticate(request, integration); + + Assertions.assertTrue(result.isFailure()); + } + + @Test + @DisplayName("Should reject empty password with true value of ldap_allow_empty_pass") + void testAuthenticateEmptyPasswordAndAllowEmptyPassTrue() throws Exception { + //running test with obvious value - ldap_allow_empty_pass is true + LdapConfig.ldap_allow_empty_pass = true; + AuthenticationRequest request = AuthenticationRequest.builder() + .username("alice") + .credentialType(CredentialType.CLEAR_TEXT_PASSWORD) + .credential("".getBytes(StandardCharsets.UTF_8)) + .build(); + + AuthenticationResult result = plugin.authenticate(request, integration); + + Assertions.assertTrue(result.isFailure()); + } + + @Test + @DisplayName("Should reject empty password with false value of ldap_allow_empty_pass") + void testAuthenticateEmptyPasswordAndAllowEmptyPassFalse() throws Exception { + //running test with obvious value - ldap_allow_empty_pass is false + LdapConfig.ldap_allow_empty_pass = false; + AuthenticationRequest request = AuthenticationRequest.builder() .username("alice") .credentialType(CredentialType.CLEAR_TEXT_PASSWORD) 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 8f5fe32bb302b2..8540f7f03314f5 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 @@ -1235,7 +1235,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 LDAP user '%s'. Set ldap_allow_empty_pass=true to allow."); // This is error code private final int code; 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 82966af525b0d4..5730fc791393cf 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 @@ -210,4 +210,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; } 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 b5502f7bcaffac..005f20c18e49e6 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; @@ -94,7 +95,7 @@ public boolean canDeal(String qualifiedUser) { /** * The LDAP authentication process is as follows: - * step1: Check the LDAP password. + * 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. @@ -106,6 +107,14 @@ 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:{} login rejected: empty LDAP password is prohibited (ldap_allow_empty_pass=false)", + userName); + 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 3d03be862fd7bf..be6792e415c8ac 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; @@ -235,9 +236,17 @@ public boolean shouldSkipPasswordVerificationAfterCertAuth(UserIdentity userIden public void checkPlainPassword(String remoteUser, String remoteHost, String remotePasswd, List currentUser) throws AuthenticationException { // Check the LDAP password when the user exists in the LDAP service. - if (ldapManager.doesUserExist(remoteUser)) { - if (!ldapManager.checkUserPasswd(remoteUser, remotePasswd, remoteHost, currentUser)) { - throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost, + if (getLdapManager().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) { + LOG.info("empty pass branch was activated: for user {}, pass {}, mode {}", + remoteUser, remotePasswd, LdapConfig.ldap_allow_empty_pass); + throw new AuthenticationException(ErrorCode.ERR_EMPTY_PASSWORD, remoteUser + "@" + remoteHost); + } + + if (!getLdapManager().checkUserPasswd(remoteUser, remotePasswd, remoteHost, currentUser)) { + throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, + remoteUser + "@" + remoteHost + " via LDAP", Strings.isNullOrEmpty(remotePasswd) ? "NO" : "YES"); } } else { 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 6045b1ff339189..cf9c44165c8dcb 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 @@ -19,6 +19,7 @@ import org.apache.doris.analysis.UserIdentity; import org.apache.doris.catalog.Env; +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; @@ -54,11 +55,13 @@ public void setUp() { mockedEnvStatic.when(Env::getCurrentEnv).thenReturn(env); Mockito.when(env.getAuth()).thenReturn(auth); Mockito.when(auth.getLdapManager()).thenReturn(ldapManager); + LdapConfig.ldap_allow_empty_pass = true; // restoring default value for other tests } @After public void tearDown() { mockedEnvStatic.close(); + LdapConfig.ldap_allow_empty_pass = true; //restoring default value for other tests } private void setCheckPassword(boolean res) { @@ -135,4 +138,57 @@ public void testCanDeal() { public void testGetPasswordResolver() { Assert.assertTrue(ldapAuthenticator.getPasswordResolver() instanceof ClearPasswordResolver); } + + @Test + public void testEmptyPasswordWithAllowEmptyPassDefault() throws IOException { + setCheckPassword(true); + setGetUserInDoris(true); + //running test with non-specified value - ldap_allow_empty_pass should be true + //test with empty pass - success + AuthenticateRequest request = new AuthenticateRequest("user1.1", new ClearPassword(""), IP); + Assert.assertTrue(LdapConfig.ldap_allow_empty_pass); + AuthenticateResponse response = ldapAuthenticator.authenticate(request); + Assert.assertTrue(response.isSuccess()); + //test with non empty pass - success + request = new AuthenticateRequest("user1.2", new ClearPassword("pass"), IP); + Assert.assertTrue(LdapConfig.ldap_allow_empty_pass); + response = ldapAuthenticator.authenticate(request); + Assert.assertTrue(response.isSuccess()); + } + + @Test + public void testEmptyPasswordWithAllowEmptyPassTrue() throws IOException { + setCheckPassword(true); + setGetUserInDoris(true); + //running test with specified value - ldap_allow_empty_pass is true + LdapConfig.ldap_allow_empty_pass = true; + //test with empty pass - success + AuthenticateRequest request = new AuthenticateRequest("user2.1", new ClearPassword(""), IP); + Assert.assertTrue(LdapConfig.ldap_allow_empty_pass); + AuthenticateResponse response = ldapAuthenticator.authenticate(request); + Assert.assertTrue(response.isSuccess()); + //test with non empty pass - success + request = new AuthenticateRequest("user2.2", new ClearPassword("pass"), IP); + Assert.assertTrue(LdapConfig.ldap_allow_empty_pass); + response = ldapAuthenticator.authenticate(request); + Assert.assertTrue(response.isSuccess()); + } + + @Test + public void testEmptyPasswordWithAllowEmptyPassFalse() throws IOException { + setCheckPassword(true); + setGetUserInDoris(true); + //running test with specified value - ldap_allow_empty_pass is false + LdapConfig.ldap_allow_empty_pass = false; + //test with empty pass - failure + AuthenticateRequest request = new AuthenticateRequest("user3.1", new ClearPassword(""), IP); + Assert.assertFalse(LdapConfig.ldap_allow_empty_pass); + AuthenticateResponse response = ldapAuthenticator.authenticate(request); + Assert.assertFalse(response.isSuccess()); + //test with non empty pass - success + request = new AuthenticateRequest("user3.2", new ClearPassword("pass"), IP); + Assert.assertFalse(LdapConfig.ldap_allow_empty_pass); + response = ldapAuthenticator.authenticate(request); + Assert.assertTrue(response.isSuccess()); + } } diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/PlainAuthWithEmptyPasswordAndLdapTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/PlainAuthWithEmptyPasswordAndLdapTest.java new file mode 100644 index 00000000000000..d9afa3602af1a1 --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/PlainAuthWithEmptyPasswordAndLdapTest.java @@ -0,0 +1,108 @@ +// 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.catalog.Env; +import org.apache.doris.common.AuthenticationException; +import org.apache.doris.common.LdapConfig; +import org.apache.doris.mysql.authenticate.ldap.LdapManager; +import org.apache.doris.utframe.TestWithFeService; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + + +public class PlainAuthWithEmptyPasswordAndLdapTest extends TestWithFeService { + private static final String IP = "192.168.1.1"; + + private LdapManager ldapManager = Mockito.mock(LdapManager.class); + private MockedStatic mockedEnv; + + @Test + public void testPlainPasswordAuthWithAllowEmptyPassDefault() throws Exception { + //running test with non-specified value - ldap_allow_empty_pass should be true + //non empty pass - success + Assertions.assertTrue(LdapConfig.ldap_allow_empty_pass); + Env.getCurrentEnv().getAuth().checkPlainPassword("user1.2", IP, "testPass", null); + //empty pass - success + Assertions.assertTrue(LdapConfig.ldap_allow_empty_pass); + Env.getCurrentEnv().getAuth().checkPlainPassword("user1.1", IP, "", null); + } + + + @Test + public void testPlainPasswordAuthWithAllowEmptyPassTrue() throws Exception { + //running test with specified value - ldap_allow_empty_pass is be true + LdapConfig.ldap_allow_empty_pass = true; + + //non empty pass - success + Assertions.assertTrue(LdapConfig.ldap_allow_empty_pass); + Env.getCurrentEnv().getAuth().checkPlainPassword("user2.2", IP, "testPass", null); + //empty pass - success + Assertions.assertTrue(LdapConfig.ldap_allow_empty_pass); + Env.getCurrentEnv().getAuth().checkPlainPassword("user2.1", IP, "", null); + } + + @Test + public void testPlainPasswordAuthWithAllowEmptyPassFalse() throws Exception { + //running test with specified value - ldap_allow_empty_pass is false + LdapConfig.ldap_allow_empty_pass = false; + + //empty pass - failure + Assertions.assertFalse(LdapConfig.ldap_allow_empty_pass); + Assertions.assertThrows(AuthenticationException.class, () -> { + Env.getCurrentEnv().getAuth().checkPlainPassword("user3.1", IP, "", null); + }); + + //non empty pass - success + Assertions.assertFalse(LdapConfig.ldap_allow_empty_pass); + Env.getCurrentEnv().getAuth().checkPlainPassword("user3.2", IP, "testPass", null); + } + + @AfterEach + public void tearDown() { + System.out.println("4.0 [" + LdapConfig.ldap_allow_empty_pass + "]"); + LdapConfig.ldap_allow_empty_pass = true; // restoring default value for other tests + mockedEnv.close(); + } + + @BeforeEach + public void setUp() { + LdapConfig.ldap_allow_empty_pass = true; // restoring default value for other tests + Auth realAuth = Env.getCurrentEnv().getAuth(); + + mockedEnv = Mockito.mockStatic(Env.class); + Env mockedEnvInstance = Mockito.mock(Env.class); + + Auth authSpy = Mockito.spy(realAuth); + + Mockito.when(ldapManager.doesUserExist(Mockito.anyString())).thenReturn(true); + Mockito.when(ldapManager.checkUserPasswd( + Mockito.anyString(), Mockito.anyString(), + Mockito.anyString(), Mockito.any())).thenReturn(true); + + Mockito.when(authSpy.getLdapManager()).thenReturn(ldapManager); + + Mockito.when(mockedEnvInstance.getAuth()).thenReturn(authSpy); + mockedEnv.when(Env::getCurrentEnv).thenReturn(mockedEnvInstance); + } +} From 8e76ba6087ddab5fa4dac3483a066bd7d8eb093e Mon Sep 17 00:00:00 2001 From: Ivan Orekhov Date: Mon, 8 Jun 2026 18:51:23 +0300 Subject: [PATCH 2/6] covering comments from morningman's review --- .../src/main/java/org/apache/doris/common/ErrorCode.java | 2 +- .../doris/mysql/authenticate/ldap/LdapAuthenticator.java | 2 +- .../src/main/java/org/apache/doris/mysql/privilege/Auth.java | 4 ++-- .../privilege/PlainAuthWithEmptyPasswordAndLdapTest.java | 1 - 4 files changed, 4 insertions(+), 5 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 8540f7f03314f5..0e15ddc0bc7030 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 @@ -1237,7 +1237,7 @@ public enum ErrorCode { ERR_NOT_CLOUD_MODE(6000, new byte[]{'4', '2', '0', '0', '0'}, "Command only support in cloud mode."), - ERR_EMPTY_PASSWORD(6001, new byte[]{'4', '2', '0', '0', '0'}, + ERR_EMPTY_PASSWORD(6001, new byte[]{'2', '8', '0', '0', '0'}, "Access with empty password is prohibited for LDAP user '%s'. Set ldap_allow_empty_pass=true to allow."); // This is error 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 005f20c18e49e6..60080b9cf65350 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 @@ -109,7 +109,7 @@ 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:{} login rejected: empty LDAP password is prohibited (ldap_allow_empty_pass=false)", + LOG.info("User:{} login rejected: empty LDAP password is prohibited (ldap_allow_empty_pass=false)", userName); 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 be6792e415c8ac..83615791de0b0c 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 @@ -239,8 +239,8 @@ public void checkPlainPassword(String remoteUser, String remoteHost, String remo if (getLdapManager().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) { - LOG.info("empty pass branch was activated: for user {}, pass {}, mode {}", - remoteUser, remotePasswd, LdapConfig.ldap_allow_empty_pass); + LOG.info("User {}@{} login rejected: empty LDAP password is prohibited (ldap_allow_empty_pass=false)", + remoteUser, remoteHost); throw new AuthenticationException(ErrorCode.ERR_EMPTY_PASSWORD, remoteUser + "@" + remoteHost); } diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/PlainAuthWithEmptyPasswordAndLdapTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/PlainAuthWithEmptyPasswordAndLdapTest.java index d9afa3602af1a1..12239de24ce7f1 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/PlainAuthWithEmptyPasswordAndLdapTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/PlainAuthWithEmptyPasswordAndLdapTest.java @@ -80,7 +80,6 @@ public void testPlainPasswordAuthWithAllowEmptyPassFalse() throws Exception { @AfterEach public void tearDown() { - System.out.println("4.0 [" + LdapConfig.ldap_allow_empty_pass + "]"); LdapConfig.ldap_allow_empty_pass = true; // restoring default value for other tests mockedEnv.close(); } From 417b8ddd9f0744369ef3ca7d3ee6ae51835de99a Mon Sep 17 00:00:00 2001 From: Ivan Orekhov Date: Mon, 8 Jun 2026 19:06:20 +0300 Subject: [PATCH 3/6] covering comments from copilot's review --- .../fe-authentication-plugin-ldap/pom.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fe/fe-authentication/fe-authentication-plugins/fe-authentication-plugin-ldap/pom.xml b/fe/fe-authentication/fe-authentication-plugins/fe-authentication-plugin-ldap/pom.xml index 5f3522fde66b42..b89797f68c868a 100644 --- a/fe/fe-authentication/fe-authentication-plugins/fe-authentication-plugin-ldap/pom.xml +++ b/fe/fe-authentication/fe-authentication-plugins/fe-authentication-plugin-ldap/pom.xml @@ -71,6 +71,14 @@ under the License. 6.0.7 test + + + + org.apache.doris + fe-common + ${project.version} + test + From 22147fb2d8a9f390859fb3a761f7f645ceb8f41d Mon Sep 17 00:00:00 2001 From: Ivan Orekhov Date: Mon, 15 Jun 2026 16:13:14 +0300 Subject: [PATCH 4/6] review: align error code with mysql --- .../src/main/java/org/apache/doris/common/ErrorCode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0e15ddc0bc7030..c5048532a43374 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 @@ -1237,7 +1237,7 @@ public enum ErrorCode { ERR_NOT_CLOUD_MODE(6000, new byte[]{'4', '2', '0', '0', '0'}, "Command only support in cloud mode."), - ERR_EMPTY_PASSWORD(6001, new byte[]{'2', '8', '0', '0', '0'}, + ERR_EMPTY_PASSWORD(6001, new byte[]{'H', 'Y', '0', '0', '0'}, "Access with empty password is prohibited for LDAP user '%s'. Set ldap_allow_empty_pass=true to allow."); // This is error code From a4a85622acc8c6983ed57ef6536368e15fb453a6 Mon Sep 17 00:00:00 2001 From: Ivan Orekhov Date: Tue, 14 Jul 2026 12:45:10 +0300 Subject: [PATCH 5/6] refactor: move empty-password check to LdapManager.checkUserPasswd --- .../authenticate/ldap/LdapAuthenticator.java | 16 +-- .../mysql/authenticate/ldap/LdapManager.java | 17 +++ .../apache/doris/mysql/privilege/Auth.java | 19 ++-- .../ldap/LdapAuthenticatorTest.java | 56 --------- .../authenticate/ldap/LdapManagerTest.java | 33 ++++++ ...PlainAuthWithEmptyPasswordAndLdapTest.java | 107 ------------------ 6 files changed, 64 insertions(+), 184 deletions(-) delete mode 100644 fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/PlainAuthWithEmptyPasswordAndLdapTest.java 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 60080b9cf65350..213f734a38bfd8 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,7 +21,6 @@ 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; @@ -107,21 +106,18 @@ 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:{} login rejected: empty LDAP password is prohibited (ldap_allow_empty_pass=false)", - userName); - ErrorReport.report(ErrorCode.ERR_EMPTY_PASSWORD, qualifiedUser + "@" + remoteIp); - return AuthenticateResponse.failedResponse; - } - // check user password by ldap server. + // extra check for login with empty LDAP password was added in checkUserPasswd. try { if (!Env.getCurrentEnv().getAuth().getLdapManager().checkUserPasswd(qualifiedUser, password)) { if (LOG.isDebugEnabled()) { LOG.debug("internalAuthenticate: user={}, success=false", userName); } - ErrorReport.report(ErrorCode.ERR_ACCESS_DENIED_ERROR, qualifiedUser, remoteIp, usePasswd); + if (Strings.isNullOrEmpty(password)) { + ErrorReport.report(ErrorCode.ERR_EMPTY_PASSWORD, qualifiedUser + "@" + remoteIp); + } else { + ErrorReport.report(ErrorCode.ERR_ACCESS_DENIED_ERROR, qualifiedUser, remoteIp, usePasswd); + } return AuthenticateResponse.failedResponse; } } catch (Exception e) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapManager.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapManager.java index 2e1e1a26ecbb2b..c75dc02b99d121 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapManager.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapManager.java @@ -122,6 +122,17 @@ public boolean doesUserExist(String fullName) { return exists; } + //not allow to login in case when empty password is specified but such mode is disabled by configuration + public boolean checkLoginWithEmptyPasswordForLdapIsAllowed(String fullName, String passwd) { + if (Strings.isNullOrEmpty(passwd) && !LdapConfig.ldap_allow_empty_pass) { + LOG.info("User:{} login rejected: empty LDAP password is prohibited (ldap_allow_empty_pass=false)", + fullName); + return false; + } else { + return true; + } + } + public boolean checkUserPasswd(String fullName, String passwd) { long start = System.currentTimeMillis(); String userName = fullName; @@ -129,6 +140,12 @@ public boolean checkUserPasswd(String fullName, String passwd) { || Objects.isNull(passwd)) { return false; } + + // extra check for PR 61440 to disable login with empty LDAP password in case when specific property is true + if (!checkLoginWithEmptyPasswordForLdapIsAllowed(fullName, passwd)) { + return false; + } + LdapUserInfo ldapUserInfo = getUserInfo(fullName); if (Objects.isNull(ldapUserInfo) || !ldapUserInfo.isExists()) { long elapsed = System.currentTimeMillis() - start; 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 83615791de0b0c..0f0969f119fd8d 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,7 +38,6 @@ 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; @@ -236,18 +235,16 @@ public boolean shouldSkipPasswordVerificationAfterCertAuth(UserIdentity userIden public void checkPlainPassword(String remoteUser, String remoteHost, String remotePasswd, List currentUser) throws AuthenticationException { // Check the LDAP password when the user exists in the LDAP service. - if (getLdapManager().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) { - LOG.info("User {}@{} login rejected: empty LDAP password is prohibited (ldap_allow_empty_pass=false)", - remoteUser, remoteHost); - throw new AuthenticationException(ErrorCode.ERR_EMPTY_PASSWORD, remoteUser + "@" + remoteHost); - } - - if (!getLdapManager().checkUserPasswd(remoteUser, remotePasswd, remoteHost, currentUser)) { - throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, + if (ldapManager.doesUserExist(remoteUser)) { + if (!ldapManager.checkUserPasswd(remoteUser, remotePasswd, remoteHost, currentUser)) { + if (Strings.isNullOrEmpty(remotePasswd)) { + // extra check for login with empty LDAP password was added in checkUserPasswd + throw new AuthenticationException(ErrorCode.ERR_EMPTY_PASSWORD, remoteUser + "@" + remoteHost); + } else { + throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost + " via LDAP", Strings.isNullOrEmpty(remotePasswd) ? "NO" : "YES"); + } } } else { readLock(); 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 cf9c44165c8dcb..6045b1ff339189 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 @@ -19,7 +19,6 @@ import org.apache.doris.analysis.UserIdentity; import org.apache.doris.catalog.Env; -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; @@ -55,13 +54,11 @@ public void setUp() { mockedEnvStatic.when(Env::getCurrentEnv).thenReturn(env); Mockito.when(env.getAuth()).thenReturn(auth); Mockito.when(auth.getLdapManager()).thenReturn(ldapManager); - LdapConfig.ldap_allow_empty_pass = true; // restoring default value for other tests } @After public void tearDown() { mockedEnvStatic.close(); - LdapConfig.ldap_allow_empty_pass = true; //restoring default value for other tests } private void setCheckPassword(boolean res) { @@ -138,57 +135,4 @@ public void testCanDeal() { public void testGetPasswordResolver() { Assert.assertTrue(ldapAuthenticator.getPasswordResolver() instanceof ClearPasswordResolver); } - - @Test - public void testEmptyPasswordWithAllowEmptyPassDefault() throws IOException { - setCheckPassword(true); - setGetUserInDoris(true); - //running test with non-specified value - ldap_allow_empty_pass should be true - //test with empty pass - success - AuthenticateRequest request = new AuthenticateRequest("user1.1", new ClearPassword(""), IP); - Assert.assertTrue(LdapConfig.ldap_allow_empty_pass); - AuthenticateResponse response = ldapAuthenticator.authenticate(request); - Assert.assertTrue(response.isSuccess()); - //test with non empty pass - success - request = new AuthenticateRequest("user1.2", new ClearPassword("pass"), IP); - Assert.assertTrue(LdapConfig.ldap_allow_empty_pass); - response = ldapAuthenticator.authenticate(request); - Assert.assertTrue(response.isSuccess()); - } - - @Test - public void testEmptyPasswordWithAllowEmptyPassTrue() throws IOException { - setCheckPassword(true); - setGetUserInDoris(true); - //running test with specified value - ldap_allow_empty_pass is true - LdapConfig.ldap_allow_empty_pass = true; - //test with empty pass - success - AuthenticateRequest request = new AuthenticateRequest("user2.1", new ClearPassword(""), IP); - Assert.assertTrue(LdapConfig.ldap_allow_empty_pass); - AuthenticateResponse response = ldapAuthenticator.authenticate(request); - Assert.assertTrue(response.isSuccess()); - //test with non empty pass - success - request = new AuthenticateRequest("user2.2", new ClearPassword("pass"), IP); - Assert.assertTrue(LdapConfig.ldap_allow_empty_pass); - response = ldapAuthenticator.authenticate(request); - Assert.assertTrue(response.isSuccess()); - } - - @Test - public void testEmptyPasswordWithAllowEmptyPassFalse() throws IOException { - setCheckPassword(true); - setGetUserInDoris(true); - //running test with specified value - ldap_allow_empty_pass is false - LdapConfig.ldap_allow_empty_pass = false; - //test with empty pass - failure - AuthenticateRequest request = new AuthenticateRequest("user3.1", new ClearPassword(""), IP); - Assert.assertFalse(LdapConfig.ldap_allow_empty_pass); - AuthenticateResponse response = ldapAuthenticator.authenticate(request); - Assert.assertFalse(response.isSuccess()); - //test with non empty pass - success - request = new AuthenticateRequest("user3.2", new ClearPassword("pass"), IP); - Assert.assertFalse(LdapConfig.ldap_allow_empty_pass); - response = ldapAuthenticator.authenticate(request); - Assert.assertTrue(response.isSuccess()); - } } diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapManagerTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapManagerTest.java index 64fffd2c71dbb9..a414201959838e 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapManagerTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapManagerTest.java @@ -24,6 +24,7 @@ import org.apache.doris.mysql.privilege.Auth; import org.apache.doris.mysql.privilege.Role; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -47,6 +48,12 @@ public class LdapManagerTest { public void setUp() { Config.authentication_type = "ldap"; LdapConfig.ldap_default_roles = new String[0]; + LdapConfig.ldap_allow_empty_pass = true; + } + + @After + public void tearDown() { + LdapConfig.ldap_allow_empty_pass = true; } private void mockClient(boolean userExist, boolean passwd) { @@ -108,6 +115,32 @@ public void testCheckUserPasswd() { Assert.assertFalse(ldapManager.checkUserPasswd(USER2, "123")); } + @Test + public void testUserLoginWithEmptyLDAPPasswordDefault() { + LdapManager ldapManager = new LdapManager(); + Assert.assertTrue(ldapManager.checkLoginWithEmptyPasswordForLdapIsAllowed("username", null)); + Assert.assertTrue(ldapManager.checkLoginWithEmptyPasswordForLdapIsAllowed("username", "")); + Assert.assertTrue(ldapManager.checkLoginWithEmptyPasswordForLdapIsAllowed("username", "password")); + } + + @Test + public void testUserLoginWithEmptyLDAPPasswordEnabled() { + LdapManager ldapManager = new LdapManager(); + LdapConfig.ldap_allow_empty_pass = true; + Assert.assertTrue(ldapManager.checkLoginWithEmptyPasswordForLdapIsAllowed("username", null)); + Assert.assertTrue(ldapManager.checkLoginWithEmptyPasswordForLdapIsAllowed("username", "")); + Assert.assertTrue(ldapManager.checkLoginWithEmptyPasswordForLdapIsAllowed("username", "password")); + } + + @Test + public void testUserLoginWithEmptyLDAPPasswordDisabled() { + LdapManager ldapManager = new LdapManager(); + LdapConfig.ldap_allow_empty_pass = false; + Assert.assertFalse(ldapManager.checkLoginWithEmptyPasswordForLdapIsAllowed("username", null)); + Assert.assertFalse(ldapManager.checkLoginWithEmptyPasswordForLdapIsAllowed("username", "")); + Assert.assertTrue(ldapManager.checkLoginWithEmptyPasswordForLdapIsAllowed("username", "password")); + } + @Test public void testGetUserInfoWithLdapDefaultRolesWithoutLdapGroups() { LdapManager ldapManager = new LdapManager(); diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/PlainAuthWithEmptyPasswordAndLdapTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/PlainAuthWithEmptyPasswordAndLdapTest.java deleted file mode 100644 index 12239de24ce7f1..00000000000000 --- a/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/PlainAuthWithEmptyPasswordAndLdapTest.java +++ /dev/null @@ -1,107 +0,0 @@ -// 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.catalog.Env; -import org.apache.doris.common.AuthenticationException; -import org.apache.doris.common.LdapConfig; -import org.apache.doris.mysql.authenticate.ldap.LdapManager; -import org.apache.doris.utframe.TestWithFeService; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.MockedStatic; -import org.mockito.Mockito; - - -public class PlainAuthWithEmptyPasswordAndLdapTest extends TestWithFeService { - private static final String IP = "192.168.1.1"; - - private LdapManager ldapManager = Mockito.mock(LdapManager.class); - private MockedStatic mockedEnv; - - @Test - public void testPlainPasswordAuthWithAllowEmptyPassDefault() throws Exception { - //running test with non-specified value - ldap_allow_empty_pass should be true - //non empty pass - success - Assertions.assertTrue(LdapConfig.ldap_allow_empty_pass); - Env.getCurrentEnv().getAuth().checkPlainPassword("user1.2", IP, "testPass", null); - //empty pass - success - Assertions.assertTrue(LdapConfig.ldap_allow_empty_pass); - Env.getCurrentEnv().getAuth().checkPlainPassword("user1.1", IP, "", null); - } - - - @Test - public void testPlainPasswordAuthWithAllowEmptyPassTrue() throws Exception { - //running test with specified value - ldap_allow_empty_pass is be true - LdapConfig.ldap_allow_empty_pass = true; - - //non empty pass - success - Assertions.assertTrue(LdapConfig.ldap_allow_empty_pass); - Env.getCurrentEnv().getAuth().checkPlainPassword("user2.2", IP, "testPass", null); - //empty pass - success - Assertions.assertTrue(LdapConfig.ldap_allow_empty_pass); - Env.getCurrentEnv().getAuth().checkPlainPassword("user2.1", IP, "", null); - } - - @Test - public void testPlainPasswordAuthWithAllowEmptyPassFalse() throws Exception { - //running test with specified value - ldap_allow_empty_pass is false - LdapConfig.ldap_allow_empty_pass = false; - - //empty pass - failure - Assertions.assertFalse(LdapConfig.ldap_allow_empty_pass); - Assertions.assertThrows(AuthenticationException.class, () -> { - Env.getCurrentEnv().getAuth().checkPlainPassword("user3.1", IP, "", null); - }); - - //non empty pass - success - Assertions.assertFalse(LdapConfig.ldap_allow_empty_pass); - Env.getCurrentEnv().getAuth().checkPlainPassword("user3.2", IP, "testPass", null); - } - - @AfterEach - public void tearDown() { - LdapConfig.ldap_allow_empty_pass = true; // restoring default value for other tests - mockedEnv.close(); - } - - @BeforeEach - public void setUp() { - LdapConfig.ldap_allow_empty_pass = true; // restoring default value for other tests - Auth realAuth = Env.getCurrentEnv().getAuth(); - - mockedEnv = Mockito.mockStatic(Env.class); - Env mockedEnvInstance = Mockito.mock(Env.class); - - Auth authSpy = Mockito.spy(realAuth); - - Mockito.when(ldapManager.doesUserExist(Mockito.anyString())).thenReturn(true); - Mockito.when(ldapManager.checkUserPasswd( - Mockito.anyString(), Mockito.anyString(), - Mockito.anyString(), Mockito.any())).thenReturn(true); - - Mockito.when(authSpy.getLdapManager()).thenReturn(ldapManager); - - Mockito.when(mockedEnvInstance.getAuth()).thenReturn(authSpy); - mockedEnv.when(Env::getCurrentEnv).thenReturn(mockedEnvInstance); - } -} From 00b57853ff34667292d948c798ae4f5e139ee5de Mon Sep 17 00:00:00 2001 From: Ivan Orekhov Date: Fri, 17 Jul 2026 18:05:04 +0300 Subject: [PATCH 6/6] implement comments from copilot's - return common error instead of new one --- .../mysql/authenticate/ldap/LdapAuthenticator.java | 10 ++++++---- .../java/org/apache/doris/mysql/privilege/Auth.java | 13 ++++++------- git | 0 3 files changed, 12 insertions(+), 11 deletions(-) create mode 100644 git 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 213f734a38bfd8..ee939bc2c185e4 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; @@ -113,11 +114,12 @@ private AuthenticateResponse internalAuthenticate(String password, String qualif if (LOG.isDebugEnabled()) { LOG.debug("internalAuthenticate: user={}, success=false", userName); } - if (Strings.isNullOrEmpty(password)) { - ErrorReport.report(ErrorCode.ERR_EMPTY_PASSWORD, qualifiedUser + "@" + remoteIp); - } else { - ErrorReport.report(ErrorCode.ERR_ACCESS_DENIED_ERROR, qualifiedUser, remoteIp, usePasswd); + // extra log to identify case covered by PR 61440 - login with empty LDAP password is not allowed + if (Strings.isNullOrEmpty(password) && !LdapConfig.ldap_allow_empty_pass) { + LOG.info(ErrorCode.ERR_EMPTY_PASSWORD, username +"@" + remoteIp); } + + ErrorReport.report(ErrorCode.ERR_ACCESS_DENIED_ERROR, qualifiedUser, remoteIp, usePasswd); return AuthenticateResponse.failedResponse; } } catch (Exception e) { 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 0f0969f119fd8d..d45ff92f3fb110 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; @@ -237,14 +238,12 @@ public void checkPlainPassword(String remoteUser, String remoteHost, String remo // Check the LDAP password when the user exists in the LDAP service. if (ldapManager.doesUserExist(remoteUser)) { if (!ldapManager.checkUserPasswd(remoteUser, remotePasswd, remoteHost, currentUser)) { - if (Strings.isNullOrEmpty(remotePasswd)) { - // extra check for login with empty LDAP password was added in checkUserPasswd - throw new AuthenticationException(ErrorCode.ERR_EMPTY_PASSWORD, remoteUser + "@" + remoteHost); - } else { - throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, - remoteUser + "@" + remoteHost + " via LDAP", - Strings.isNullOrEmpty(remotePasswd) ? "NO" : "YES"); + // extra log to identify case covered by PR 61440 - login with empty LDAP password is not allowed + if (Strings.isNullOrEmpty(remotePasswd) && !LdapConfig.ldap_allow_empty_pass) { + LOG.info(ErrorCode.ERR_EMPTY_PASSWORD, remoteUser +"@" + remoteHost); } + throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost, + Strings.isNullOrEmpty(remotePasswd) ? "NO" : "YES"); } } else { readLock(); diff --git a/git b/git new file mode 100644 index 00000000000000..e69de29bb2d1d6