This is an automated email from the ASF dual-hosted git repository.

morningman pushed a commit to branch branch-4.0
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.0 by this push:
     new 7957ada1301 branch-4.0:[improvement](fe) Support LDAP default roles 
(#63411) (#64115)
7957ada1301 is described below

commit 7957ada1301cbd32d7c1c2e23a3d01f9af0abd4a
Author: seawinde <[email protected]>
AuthorDate: Thu Jun 18 23:43:14 2026 +0800

    branch-4.0:[improvement](fe) Support LDAP default roles (#63411) (#64115)
    
    pr: #63411
    commitId: 866a2f9186d
    
    Co-authored-by: Raiden <[email protected]>
---
 conf/ldap.conf                                     |   3 +
 .../java/org/apache/doris/common/LdapConfig.java   |   6 +
 .../main/java/org/apache/doris/catalog/Env.java    |   3 +
 .../doris/mysql/authenticate/ldap/LdapManager.java |  30 ++++-
 .../java/org/apache/doris/catalog/EnvTest.java     |  38 ++++++
 .../mysql/authenticate/ldap/LdapManagerTest.java   | 138 ++++++++++++++++++---
 6 files changed, 193 insertions(+), 25 deletions(-)

diff --git a/conf/ldap.conf b/conf/ldap.conf
index 9388ae7ee50..00647819273 100644
--- a/conf/ldap.conf
+++ b/conf/ldap.conf
@@ -31,6 +31,8 @@
 # ldap_user_filter - User lookup filter, the placeholder {login} will be 
replaced by the user supplied login.
 # ldap_group_basedn - Search base for groups.
 # ldap_group_filter - Group lookup filter, the placeholder {login} will be 
replaced by the user supplied login. example : "(&(memberUid={login}))"
+# ldap_default_roles - Comma-separated Doris roles granted to every 
LDAP-authenticated user.
+# Online updates of ldap_default_roles refresh the LDAP user cache 
automatically.
 ## step2: Restart fe, and use root or admin account to log in to doris.
 ## step3: Execute sql statement to set ldap admin password:
 # set ldap_admin_password = 'password';
@@ -41,6 +43,7 @@ ldap_admin_name = cn=admin,dc=domain,dc=com
 ldap_user_basedn = ou=people,dc=domain,dc=com
 ldap_user_filter = (&(uid={login}))
 ldap_group_basedn = ou=group,dc=domain,dc=com
+# ldap_default_roles = ldap_default_role
 
 # ldap_user_cache_timeout_s = 5 * 60;
 
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 be25b5af0a4..82966af525b 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
@@ -72,6 +72,12 @@ public class LdapConfig extends ConfigBase {
     @ConfigBase.ConfField
     public static String ldap_group_filter = "";
 
+    /**
+     * Default Doris roles granted to every LDAP-authenticated user.
+     */
+    @ConfigBase.ConfField(mutable = true)
+    public static String[] ldap_default_roles = {};
+
     /**
      * The user LDAP information cache time.
      * After timeout, the user information will be retrieved from the LDAP 
service again.
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
index a6108e2d1b2..e909823aacb 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
@@ -6594,6 +6594,9 @@ public class Env {
      */
     public void setMutableConfigWithCallback(String key, String value) throws 
ConfigException {
         ConfigBase.setMutableConfig(key, value);
+        if ("ldap_default_roles".equals(key)) {
+            getAuth().getLdapManager().refresh(true, null);
+        }
         if (configtoThreads.get(key) != null) {
             try {
                 // not atomic. maybe delay to aware. but acceptable.
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 da4665eb7d3..bfafb0ac686 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
@@ -37,6 +37,7 @@ import com.google.common.collect.Sets;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -257,12 +258,8 @@ public class LdapManager {
         // get user ldap group. the ldap group name should be the same as the 
doris role name
         List<String> ldapGroups = ldapClient.getGroups(userName);
         Set<Role> roles = Sets.newHashSet();
-        for (String group : ldapGroups) {
-            String qualifiedRole = group;
-            if (Env.getCurrentEnv().getAuth().doesRoleExist(qualifiedRole)) {
-                
roles.add(Env.getCurrentEnv().getAuth().getRoleByName(qualifiedRole));
-            }
-        }
+        addExistingRoles(roles, ldapGroups, false);
+        addExistingRoles(roles, Arrays.asList(LdapConfig.ldap_default_roles), 
true);
         if (LOG.isDebugEnabled()) {
             LOG.debug("get user:{} ldap groups:{} and doris roles:{}", 
userName, ldapGroups, roles);
         }
@@ -273,6 +270,27 @@ public class LdapManager {
         return roles;
     }
 
+    private void addExistingRoles(Set<Role> roles, Iterable<String> roleNames, 
boolean warnIfMissing) {
+        Auth auth = null;
+        for (String roleName : roleNames) {
+            if (Strings.isNullOrEmpty(roleName)) {
+                continue;
+            }
+            String qualifiedRole = roleName.trim();
+            if (Strings.isNullOrEmpty(qualifiedRole)) {
+                continue;
+            }
+            if (auth == null) {
+                auth = Env.getCurrentEnv().getAuth();
+            }
+            if (auth.doesRoleExist(qualifiedRole)) {
+                roles.add(auth.getRoleByName(qualifiedRole));
+            } else if (warnIfMissing) {
+                LOG.warn("LDAP default role {} does not exist in Doris, ignore 
it.", qualifiedRole);
+            }
+        }
+    }
+
     public void refresh(boolean isAll, String fullName) {
         writeLock();
         try {
diff --git a/fe/fe-core/src/test/java/org/apache/doris/catalog/EnvTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/catalog/EnvTest.java
index 436e012d9f7..6e4ab8e9f68 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/catalog/EnvTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/catalog/EnvTest.java
@@ -17,15 +17,20 @@
 
 package org.apache.doris.catalog;
 
+import org.apache.doris.common.ConfigBase;
 import org.apache.doris.common.FeConstants;
+import org.apache.doris.common.LdapConfig;
 import org.apache.doris.common.io.CountingDataOutputStream;
 import org.apache.doris.meta.MetaContext;
+import org.apache.doris.mysql.authenticate.ldap.LdapManager;
+import org.apache.doris.mysql.privilege.Auth;
 import org.apache.doris.persist.meta.MetaHeader;
 
 import mockit.Expectations;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mockito;
 
 import java.io.BufferedInputStream;
 import java.io.DataInputStream;
@@ -34,6 +39,9 @@ import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Random;
 
 public class EnvTest {
@@ -140,4 +148,34 @@ public class EnvTest {
 
         deleteDir(dir);
     }
+
+    @Test
+    public void testSetLdapDefaultRolesConfigRefreshesLdapCache() throws 
Exception {
+        Env env = Mockito.spy(new Env(false));
+        Auth auth = Mockito.mock(Auth.class);
+        LdapManager ldapManager = Mockito.mock(LdapManager.class);
+        Mockito.doReturn(auth).when(env).getAuth();
+        Mockito.when(auth.getLdapManager()).thenReturn(ldapManager);
+
+        Map<String, Field> oldConfFields = ConfigBase.confFields;
+        Field oldLdapDefaultRolesField = 
ConfigBase.ldapConfFields.put("ldap_default_roles",
+                LdapConfig.class.getField("ldap_default_roles"));
+        String[] oldLdapDefaultRoles = LdapConfig.ldap_default_roles;
+        try {
+            ConfigBase.confFields = new HashMap<>();
+
+            env.setMutableConfigWithCallback("ldap_default_roles", 
"role1,role2");
+
+            Assert.assertArrayEquals(new String[] {"role1", "role2"}, 
LdapConfig.ldap_default_roles);
+            Mockito.verify(ldapManager).refresh(true, null);
+        } finally {
+            ConfigBase.confFields = oldConfFields;
+            if (oldLdapDefaultRolesField == null) {
+                ConfigBase.ldapConfFields.remove("ldap_default_roles");
+            } else {
+                ConfigBase.ldapConfFields.put("ldap_default_roles", 
oldLdapDefaultRolesField);
+            }
+            LdapConfig.ldap_default_roles = oldLdapDefaultRoles;
+        }
+    }
 }
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 70361675054..37492c799be 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
@@ -17,52 +17,73 @@
 
 package org.apache.doris.mysql.authenticate.ldap;
 
+import org.apache.doris.catalog.Env;
 import org.apache.doris.common.Config;
+import org.apache.doris.common.LdapConfig;
+import org.apache.doris.common.jmockit.Deencapsulation;
 import org.apache.doris.mysql.authenticate.TestLogAppender;
+import org.apache.doris.mysql.privilege.Auth;
+import org.apache.doris.mysql.privilege.Role;
 
-import mockit.Expectations;
-import mockit.Mocked;
 import org.apache.logging.log4j.Level;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 
 public class LdapManagerTest {
 
     private static final String USER1 = "user1";
     private static final String USER2 = "user2";
+    private static final String LDAP_GROUP_ROLE = "ldap_group_role";
+    private static final String LDAP_DEFAULT_ROLE = "ldap_default_role";
+    private static final String MISSING_LDAP_DEFAULT_ROLE = 
"missing_ldap_default_role";
 
-    @Mocked
-    private LdapClient ldapClient;
+    private LdapClient ldapClient = Mockito.mock(LdapClient.class);
 
     @Before
     public void setUp() {
         Config.authentication_type = "ldap";
+        LdapConfig.ldap_default_roles = new String[0];
     }
 
     private void mockClient(boolean userExist, boolean passwd) {
-        new Expectations() {
-            {
-                ldapClient.doesUserExist(anyString);
-                minTimes = 0;
-                result = userExist;
-
-                ldapClient.checkPassword(anyString, anyString);
-                minTimes = 0;
-                result = passwd;
-
-                ldapClient.getGroups(anyString);
-                minTimes = 0;
-                result = new ArrayList<>();
-            }
-        };
+        mockClient(userExist, passwd, new ArrayList<>());
+    }
+
+    private void mockClient(boolean userExist, boolean passwd, 
ArrayList<String> groups) {
+        
Mockito.when(ldapClient.doesUserExist(Mockito.anyString())).thenReturn(userExist);
+        Mockito.when(ldapClient.checkPassword(Mockito.anyString(), 
Mockito.anyString())).thenReturn(passwd);
+        
Mockito.when(ldapClient.getGroups(Mockito.anyString())).thenReturn(groups);
+    }
+
+    private void mockAuth(MockedStatic<Env> envMockedStatic, Role 
ldapGroupRole, Role ldapDefaultRole) {
+        mockAuth(envMockedStatic, ldapGroupRole, ldapDefaultRole, true);
+    }
+
+    private void mockAuth(MockedStatic<Env> envMockedStatic, Role 
ldapGroupRole, Role ldapDefaultRole,
+            boolean ldapGroupRoleExists) {
+        Env env = Mockito.mock(Env.class);
+        Auth auth = Mockito.mock(Auth.class);
+        envMockedStatic.when(Env::getCurrentEnv).thenReturn(env);
+        Mockito.when(env.getAuth()).thenReturn(auth);
+        
Mockito.when(auth.doesRoleExist(LDAP_GROUP_ROLE)).thenReturn(ldapGroupRoleExists);
+        if (ldapGroupRoleExists) {
+            
Mockito.when(auth.getRoleByName(LDAP_GROUP_ROLE)).thenReturn(ldapGroupRole);
+        }
+        Mockito.when(auth.doesRoleExist(LDAP_DEFAULT_ROLE)).thenReturn(true);
+        
Mockito.when(auth.getRoleByName(LDAP_DEFAULT_ROLE)).thenReturn(ldapDefaultRole);
+        
Mockito.when(auth.doesRoleExist(MISSING_LDAP_DEFAULT_ROLE)).thenReturn(false);
     }
 
     @Test
     public void testGetUserInfo() {
         LdapManager ldapManager = new LdapManager();
+        Deencapsulation.setField(ldapManager, "ldapClient", ldapClient);
         mockClient(true, true);
         LdapUserInfo ldapUserInfo = ldapManager.getUserInfo(USER1);
         Assert.assertNotNull(ldapUserInfo);
@@ -77,6 +98,7 @@ public class LdapManagerTest {
     @Test
     public void testCheckUserPasswd() {
         LdapManager ldapManager = new LdapManager();
+        Deencapsulation.setField(ldapManager, "ldapClient", ldapClient);
         mockClient(true, true);
         Assert.assertTrue(ldapManager.checkUserPasswd(USER1, "123"));
         LdapUserInfo ldapUserInfo = ldapManager.getUserInfo(USER1);
@@ -91,6 +113,7 @@ public class LdapManagerTest {
     @Test
     public void testCheckUserPasswdCachedPasswdMatchLogsInfoWithoutThreshold() 
{
         LdapManager ldapManager = new LdapManager();
+        Deencapsulation.setField(ldapManager, "ldapClient", ldapClient);
         mockClient(true, true);
         Assert.assertTrue(ldapManager.checkUserPasswd(USER1, "123"));
 
@@ -106,6 +129,7 @@ public class LdapManagerTest {
     @Test
     public void testGetUserInfoLogsInfoWithoutThreshold() {
         LdapManager ldapManager = new LdapManager();
+        Deencapsulation.setField(ldapManager, "ldapClient", ldapClient);
         mockClient(true, true);
 
         try (TestLogAppender appender = 
TestLogAppender.attach(LdapManager.class)) {
@@ -116,4 +140,80 @@ public class LdapManagerTest {
                     "LdapManager.getUserInfo slow: user=user1"));
         }
     }
+
+    @Test
+    public void testGetUserInfoWithLdapDefaultRolesWithoutLdapGroups() {
+        LdapManager ldapManager = new LdapManager();
+        Deencapsulation.setField(ldapManager, "ldapClient", ldapClient);
+        LdapConfig.ldap_default_roles = new String[] {LDAP_DEFAULT_ROLE, 
MISSING_LDAP_DEFAULT_ROLE};
+        Role ldapGroupRole = new Role(LDAP_GROUP_ROLE);
+        Role ldapDefaultRole = new Role(LDAP_DEFAULT_ROLE);
+        mockClient(true, true, new ArrayList<>());
+        try (MockedStatic<Env> envMockedStatic = 
Mockito.mockStatic(Env.class)) {
+            mockAuth(envMockedStatic, ldapGroupRole, ldapDefaultRole);
+
+            LdapUserInfo ldapUserInfo = ldapManager.getUserInfo(USER1);
+            Assert.assertNotNull(ldapUserInfo);
+            
Assert.assertFalse(ldapUserInfo.getRoles().contains(ldapGroupRole));
+            
Assert.assertTrue(ldapUserInfo.getRoles().contains(ldapDefaultRole));
+            Assert.assertEquals(2, ldapUserInfo.getRoles().size());
+        }
+    }
+
+    @Test
+    public void testGetUserInfoWithLdapDefaultRolesWhenLdapGroupRoleMissing() {
+        LdapManager ldapManager = new LdapManager();
+        Deencapsulation.setField(ldapManager, "ldapClient", ldapClient);
+        LdapConfig.ldap_default_roles = new String[] {LDAP_DEFAULT_ROLE, 
MISSING_LDAP_DEFAULT_ROLE};
+        Role ldapGroupRole = new Role(LDAP_GROUP_ROLE);
+        Role ldapDefaultRole = new Role(LDAP_DEFAULT_ROLE);
+        mockClient(true, true, new 
ArrayList<>(Arrays.asList(LDAP_GROUP_ROLE)));
+        try (MockedStatic<Env> envMockedStatic = 
Mockito.mockStatic(Env.class)) {
+            mockAuth(envMockedStatic, ldapGroupRole, ldapDefaultRole, false);
+
+            LdapUserInfo ldapUserInfo = ldapManager.getUserInfo(USER1);
+            Assert.assertNotNull(ldapUserInfo);
+            
Assert.assertFalse(ldapUserInfo.getRoles().contains(ldapGroupRole));
+            
Assert.assertTrue(ldapUserInfo.getRoles().contains(ldapDefaultRole));
+            Assert.assertEquals(2, ldapUserInfo.getRoles().size());
+        }
+    }
+
+    @Test
+    public void testGetUserInfoWithBlankLdapDefaultRoles() {
+        LdapManager ldapManager = new LdapManager();
+        Deencapsulation.setField(ldapManager, "ldapClient", ldapClient);
+        LdapConfig.ldap_default_roles = new String[] {null, "", "   ", 
LDAP_DEFAULT_ROLE};
+        Role ldapGroupRole = new Role(LDAP_GROUP_ROLE);
+        Role ldapDefaultRole = new Role(LDAP_DEFAULT_ROLE);
+        mockClient(true, true, new 
ArrayList<>(Arrays.asList(LDAP_GROUP_ROLE)));
+        try (MockedStatic<Env> envMockedStatic = 
Mockito.mockStatic(Env.class)) {
+            mockAuth(envMockedStatic, ldapGroupRole, ldapDefaultRole);
+
+            LdapUserInfo ldapUserInfo = ldapManager.getUserInfo(USER1);
+            Assert.assertNotNull(ldapUserInfo);
+            Assert.assertTrue(ldapUserInfo.getRoles().contains(ldapGroupRole));
+            
Assert.assertTrue(ldapUserInfo.getRoles().contains(ldapDefaultRole));
+            Assert.assertEquals(3, ldapUserInfo.getRoles().size());
+        }
+    }
+
+    @Test
+    public void testGetUserInfoWithLdapDefaultRoles() {
+        LdapManager ldapManager = new LdapManager();
+        Deencapsulation.setField(ldapManager, "ldapClient", ldapClient);
+        LdapConfig.ldap_default_roles = new String[] {LDAP_DEFAULT_ROLE, 
MISSING_LDAP_DEFAULT_ROLE};
+        Role ldapGroupRole = new Role(LDAP_GROUP_ROLE);
+        Role ldapDefaultRole = new Role(LDAP_DEFAULT_ROLE);
+        mockClient(true, true, new 
ArrayList<>(Arrays.asList(LDAP_GROUP_ROLE)));
+        try (MockedStatic<Env> envMockedStatic = 
Mockito.mockStatic(Env.class)) {
+            mockAuth(envMockedStatic, ldapGroupRole, ldapDefaultRole);
+
+            LdapUserInfo ldapUserInfo = ldapManager.getUserInfo(USER1);
+            Assert.assertNotNull(ldapUserInfo);
+            Assert.assertTrue(ldapUserInfo.getRoles().contains(ldapGroupRole));
+            
Assert.assertTrue(ldapUserInfo.getRoles().contains(ldapDefaultRole));
+            Assert.assertEquals(3, ldapUserInfo.getRoles().size());
+        }
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to