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

morrySnow pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 6e900dd2310 [improvement](fe) Support LDAP default roles (#63411)
6e900dd2310 is described below

commit 6e900dd2310b2f726b66bc7d6d0e63f572dd3e9d
Author: Raiden <[email protected]>
AuthorDate: Tue Jun 2 11:32:14 2026 +0800

    [improvement](fe) Support LDAP default roles (#63411)
    
    ### What problem does this PR solve?
    
    Problem Summary:
    
    LDAP temporary users could only receive roles mapped from LDAP groups
    and the built-in information_schema-only role. This PR adds
    `ldap_default_roles` so every LDAP-authenticated user can receive
    configured Doris roles while still keeping LDAP group roles.
    
    ### Release note
    
    Support configuring default Doris roles for LDAP-authenticated users
    through `ldap_default_roles`.
---
 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     |  37 +++++++
 .../mysql/authenticate/ldap/LdapManagerTest.java   | 111 ++++++++++++++++++++-
 6 files changed, 183 insertions(+), 7 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 53b6a67cdb5..b13666a2a74 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
@@ -6790,6 +6790,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 f279647f2a5..2e1e1a26ecb 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
@@ -36,6 +36,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;
@@ -256,12 +257,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);
         }
@@ -272,6 +269,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 69a1898a9f1..fe3f4aebadd 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,9 +17,13 @@
 
 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 org.junit.After;
@@ -36,6 +40,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 {
@@ -146,4 +153,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 c3c08e606bd..64fffd2c71d 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,32 +17,65 @@
 
 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.privilege.Auth;
+import org.apache.doris.mysql.privilege.Role;
 
 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";
 
     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) {
+        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(new 
ArrayList<>());
+        
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
@@ -74,4 +107,80 @@ public class LdapManagerTest {
         mockClient(true, false);
         Assert.assertFalse(ldapManager.checkUserPasswd(USER2, "123"));
     }
+
+    @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