Skip to content

Commit 6e900dd

Browse files
authored
[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`.
1 parent b640914 commit 6e900dd

6 files changed

Lines changed: 183 additions & 7 deletions

File tree

conf/ldap.conf

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
# ldap_user_filter - User lookup filter, the placeholder {login} will be replaced by the user supplied login.
3232
# ldap_group_basedn - Search base for groups.
3333
# ldap_group_filter - Group lookup filter, the placeholder {login} will be replaced by the user supplied login. example : "(&(memberUid={login}))"
34+
# ldap_default_roles - Comma-separated Doris roles granted to every LDAP-authenticated user.
35+
# Online updates of ldap_default_roles refresh the LDAP user cache automatically.
3436
## step2: Restart fe, and use root or admin account to log in to doris.
3537
## step3: Execute sql statement to set ldap admin password:
3638
# set ldap_admin_password = 'password';
@@ -41,6 +43,7 @@ ldap_admin_name = cn=admin,dc=domain,dc=com
4143
ldap_user_basedn = ou=people,dc=domain,dc=com
4244
ldap_user_filter = (&(uid={login}))
4345
ldap_group_basedn = ou=group,dc=domain,dc=com
46+
# ldap_default_roles = ldap_default_role
4447

4548
# ldap_user_cache_timeout_s = 5 * 60;
4649

fe/fe-common/src/main/java/org/apache/doris/common/LdapConfig.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ public class LdapConfig extends ConfigBase {
7272
@ConfigBase.ConfField
7373
public static String ldap_group_filter = "";
7474

75+
/**
76+
* Default Doris roles granted to every LDAP-authenticated user.
77+
*/
78+
@ConfigBase.ConfField(mutable = true)
79+
public static String[] ldap_default_roles = {};
80+
7581
/**
7682
* The user LDAP information cache time.
7783
* After timeout, the user information will be retrieved from the LDAP service again.

fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6790,6 +6790,9 @@ public void replayDropGlobalFunction(FunctionSearchDesc functionSearchDesc) {
67906790
*/
67916791
public void setMutableConfigWithCallback(String key, String value) throws ConfigException {
67926792
ConfigBase.setMutableConfig(key, value);
6793+
if ("ldap_default_roles".equals(key)) {
6794+
getAuth().getLdapManager().refresh(true, null);
6795+
}
67936796
if (configtoThreads.get(key) != null) {
67946797
try {
67956798
// not atomic. maybe delay to aware. but acceptable.

fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapManager.java

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.apache.logging.log4j.LogManager;
3737
import org.apache.logging.log4j.Logger;
3838

39+
import java.util.Arrays;
3940
import java.util.Collections;
4041
import java.util.List;
4142
import java.util.Map;
@@ -256,12 +257,8 @@ private Set<Role> getLdapGroupsRoles(String userName) throws DdlException {
256257
// get user ldap group. the ldap group name should be the same as the doris role name
257258
List<String> ldapGroups = ldapClient.getGroups(userName);
258259
Set<Role> roles = Sets.newHashSet();
259-
for (String group : ldapGroups) {
260-
String qualifiedRole = group;
261-
if (Env.getCurrentEnv().getAuth().doesRoleExist(qualifiedRole)) {
262-
roles.add(Env.getCurrentEnv().getAuth().getRoleByName(qualifiedRole));
263-
}
264-
}
260+
addExistingRoles(roles, ldapGroups, false);
261+
addExistingRoles(roles, Arrays.asList(LdapConfig.ldap_default_roles), true);
265262
if (LOG.isDebugEnabled()) {
266263
LOG.debug("get user:{} ldap groups:{} and doris roles:{}", userName, ldapGroups, roles);
267264
}
@@ -272,6 +269,27 @@ private Set<Role> getLdapGroupsRoles(String userName) throws DdlException {
272269
return roles;
273270
}
274271

272+
private void addExistingRoles(Set<Role> roles, Iterable<String> roleNames, boolean warnIfMissing) {
273+
Auth auth = null;
274+
for (String roleName : roleNames) {
275+
if (Strings.isNullOrEmpty(roleName)) {
276+
continue;
277+
}
278+
String qualifiedRole = roleName.trim();
279+
if (Strings.isNullOrEmpty(qualifiedRole)) {
280+
continue;
281+
}
282+
if (auth == null) {
283+
auth = Env.getCurrentEnv().getAuth();
284+
}
285+
if (auth.doesRoleExist(qualifiedRole)) {
286+
roles.add(auth.getRoleByName(qualifiedRole));
287+
} else if (warnIfMissing) {
288+
LOG.warn("LDAP default role {} does not exist in Doris, ignore it.", qualifiedRole);
289+
}
290+
}
291+
}
292+
275293
public void refresh(boolean isAll, String fullName) {
276294
writeLock();
277295
try {

fe/fe-core/src/test/java/org/apache/doris/catalog/EnvTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@
1717

1818
package org.apache.doris.catalog;
1919

20+
import org.apache.doris.common.ConfigBase;
2021
import org.apache.doris.common.FeConstants;
22+
import org.apache.doris.common.LdapConfig;
2123
import org.apache.doris.common.io.CountingDataOutputStream;
2224
import org.apache.doris.meta.MetaContext;
25+
import org.apache.doris.mysql.authenticate.ldap.LdapManager;
26+
import org.apache.doris.mysql.privilege.Auth;
2327
import org.apache.doris.persist.meta.MetaHeader;
2428

2529
import org.junit.After;
@@ -36,6 +40,9 @@
3640
import java.io.FileOutputStream;
3741
import java.io.FileWriter;
3842
import java.io.IOException;
43+
import java.lang.reflect.Field;
44+
import java.util.HashMap;
45+
import java.util.Map;
3946
import java.util.Random;
4047

4148
public class EnvTest {
@@ -146,4 +153,34 @@ public void testSaveLoadHeader() throws Exception {
146153

147154
deleteDir(dir);
148155
}
156+
157+
@Test
158+
public void testSetLdapDefaultRolesConfigRefreshesLdapCache() throws Exception {
159+
Env env = Mockito.spy(new Env(false));
160+
Auth auth = Mockito.mock(Auth.class);
161+
LdapManager ldapManager = Mockito.mock(LdapManager.class);
162+
Mockito.doReturn(auth).when(env).getAuth();
163+
Mockito.when(auth.getLdapManager()).thenReturn(ldapManager);
164+
165+
Map<String, Field> oldConfFields = ConfigBase.confFields;
166+
Field oldLdapDefaultRolesField = ConfigBase.ldapConfFields.put("ldap_default_roles",
167+
LdapConfig.class.getField("ldap_default_roles"));
168+
String[] oldLdapDefaultRoles = LdapConfig.ldap_default_roles;
169+
try {
170+
ConfigBase.confFields = new HashMap<>();
171+
172+
env.setMutableConfigWithCallback("ldap_default_roles", "role1,role2");
173+
174+
Assert.assertArrayEquals(new String[] {"role1", "role2"}, LdapConfig.ldap_default_roles);
175+
Mockito.verify(ldapManager).refresh(true, null);
176+
} finally {
177+
ConfigBase.confFields = oldConfFields;
178+
if (oldLdapDefaultRolesField == null) {
179+
ConfigBase.ldapConfFields.remove("ldap_default_roles");
180+
} else {
181+
ConfigBase.ldapConfFields.put("ldap_default_roles", oldLdapDefaultRolesField);
182+
}
183+
LdapConfig.ldap_default_roles = oldLdapDefaultRoles;
184+
}
185+
}
149186
}

fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapManagerTest.java

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,32 +17,65 @@
1717

1818
package org.apache.doris.mysql.authenticate.ldap;
1919

20+
import org.apache.doris.catalog.Env;
2021
import org.apache.doris.common.Config;
22+
import org.apache.doris.common.LdapConfig;
2123
import org.apache.doris.common.jmockit.Deencapsulation;
24+
import org.apache.doris.mysql.privilege.Auth;
25+
import org.apache.doris.mysql.privilege.Role;
2226

2327
import org.junit.Assert;
2428
import org.junit.Before;
2529
import org.junit.Test;
30+
import org.mockito.MockedStatic;
2631
import org.mockito.Mockito;
2732

2833
import java.util.ArrayList;
34+
import java.util.Arrays;
2935

3036
public class LdapManagerTest {
3137

3238
private static final String USER1 = "user1";
3339
private static final String USER2 = "user2";
40+
private static final String LDAP_GROUP_ROLE = "ldap_group_role";
41+
private static final String LDAP_DEFAULT_ROLE = "ldap_default_role";
42+
private static final String MISSING_LDAP_DEFAULT_ROLE = "missing_ldap_default_role";
3443

3544
private LdapClient ldapClient = Mockito.mock(LdapClient.class);
3645

3746
@Before
3847
public void setUp() {
3948
Config.authentication_type = "ldap";
49+
LdapConfig.ldap_default_roles = new String[0];
4050
}
4151

4252
private void mockClient(boolean userExist, boolean passwd) {
53+
mockClient(userExist, passwd, new ArrayList<>());
54+
}
55+
56+
private void mockClient(boolean userExist, boolean passwd, ArrayList<String> groups) {
4357
Mockito.when(ldapClient.doesUserExist(Mockito.anyString())).thenReturn(userExist);
4458
Mockito.when(ldapClient.checkPassword(Mockito.anyString(), Mockito.anyString())).thenReturn(passwd);
45-
Mockito.when(ldapClient.getGroups(Mockito.anyString())).thenReturn(new ArrayList<>());
59+
Mockito.when(ldapClient.getGroups(Mockito.anyString())).thenReturn(groups);
60+
}
61+
62+
private void mockAuth(MockedStatic<Env> envMockedStatic, Role ldapGroupRole, Role ldapDefaultRole) {
63+
mockAuth(envMockedStatic, ldapGroupRole, ldapDefaultRole, true);
64+
}
65+
66+
private void mockAuth(MockedStatic<Env> envMockedStatic, Role ldapGroupRole, Role ldapDefaultRole,
67+
boolean ldapGroupRoleExists) {
68+
Env env = Mockito.mock(Env.class);
69+
Auth auth = Mockito.mock(Auth.class);
70+
envMockedStatic.when(Env::getCurrentEnv).thenReturn(env);
71+
Mockito.when(env.getAuth()).thenReturn(auth);
72+
Mockito.when(auth.doesRoleExist(LDAP_GROUP_ROLE)).thenReturn(ldapGroupRoleExists);
73+
if (ldapGroupRoleExists) {
74+
Mockito.when(auth.getRoleByName(LDAP_GROUP_ROLE)).thenReturn(ldapGroupRole);
75+
}
76+
Mockito.when(auth.doesRoleExist(LDAP_DEFAULT_ROLE)).thenReturn(true);
77+
Mockito.when(auth.getRoleByName(LDAP_DEFAULT_ROLE)).thenReturn(ldapDefaultRole);
78+
Mockito.when(auth.doesRoleExist(MISSING_LDAP_DEFAULT_ROLE)).thenReturn(false);
4679
}
4780

4881
@Test
@@ -74,4 +107,80 @@ public void testCheckUserPasswd() {
74107
mockClient(true, false);
75108
Assert.assertFalse(ldapManager.checkUserPasswd(USER2, "123"));
76109
}
110+
111+
@Test
112+
public void testGetUserInfoWithLdapDefaultRolesWithoutLdapGroups() {
113+
LdapManager ldapManager = new LdapManager();
114+
Deencapsulation.setField(ldapManager, "ldapClient", ldapClient);
115+
LdapConfig.ldap_default_roles = new String[] {LDAP_DEFAULT_ROLE, MISSING_LDAP_DEFAULT_ROLE};
116+
Role ldapGroupRole = new Role(LDAP_GROUP_ROLE);
117+
Role ldapDefaultRole = new Role(LDAP_DEFAULT_ROLE);
118+
mockClient(true, true, new ArrayList<>());
119+
try (MockedStatic<Env> envMockedStatic = Mockito.mockStatic(Env.class)) {
120+
mockAuth(envMockedStatic, ldapGroupRole, ldapDefaultRole);
121+
122+
LdapUserInfo ldapUserInfo = ldapManager.getUserInfo(USER1);
123+
Assert.assertNotNull(ldapUserInfo);
124+
Assert.assertFalse(ldapUserInfo.getRoles().contains(ldapGroupRole));
125+
Assert.assertTrue(ldapUserInfo.getRoles().contains(ldapDefaultRole));
126+
Assert.assertEquals(2, ldapUserInfo.getRoles().size());
127+
}
128+
}
129+
130+
@Test
131+
public void testGetUserInfoWithLdapDefaultRolesWhenLdapGroupRoleMissing() {
132+
LdapManager ldapManager = new LdapManager();
133+
Deencapsulation.setField(ldapManager, "ldapClient", ldapClient);
134+
LdapConfig.ldap_default_roles = new String[] {LDAP_DEFAULT_ROLE, MISSING_LDAP_DEFAULT_ROLE};
135+
Role ldapGroupRole = new Role(LDAP_GROUP_ROLE);
136+
Role ldapDefaultRole = new Role(LDAP_DEFAULT_ROLE);
137+
mockClient(true, true, new ArrayList<>(Arrays.asList(LDAP_GROUP_ROLE)));
138+
try (MockedStatic<Env> envMockedStatic = Mockito.mockStatic(Env.class)) {
139+
mockAuth(envMockedStatic, ldapGroupRole, ldapDefaultRole, false);
140+
141+
LdapUserInfo ldapUserInfo = ldapManager.getUserInfo(USER1);
142+
Assert.assertNotNull(ldapUserInfo);
143+
Assert.assertFalse(ldapUserInfo.getRoles().contains(ldapGroupRole));
144+
Assert.assertTrue(ldapUserInfo.getRoles().contains(ldapDefaultRole));
145+
Assert.assertEquals(2, ldapUserInfo.getRoles().size());
146+
}
147+
}
148+
149+
@Test
150+
public void testGetUserInfoWithBlankLdapDefaultRoles() {
151+
LdapManager ldapManager = new LdapManager();
152+
Deencapsulation.setField(ldapManager, "ldapClient", ldapClient);
153+
LdapConfig.ldap_default_roles = new String[] {null, "", " ", LDAP_DEFAULT_ROLE};
154+
Role ldapGroupRole = new Role(LDAP_GROUP_ROLE);
155+
Role ldapDefaultRole = new Role(LDAP_DEFAULT_ROLE);
156+
mockClient(true, true, new ArrayList<>(Arrays.asList(LDAP_GROUP_ROLE)));
157+
try (MockedStatic<Env> envMockedStatic = Mockito.mockStatic(Env.class)) {
158+
mockAuth(envMockedStatic, ldapGroupRole, ldapDefaultRole);
159+
160+
LdapUserInfo ldapUserInfo = ldapManager.getUserInfo(USER1);
161+
Assert.assertNotNull(ldapUserInfo);
162+
Assert.assertTrue(ldapUserInfo.getRoles().contains(ldapGroupRole));
163+
Assert.assertTrue(ldapUserInfo.getRoles().contains(ldapDefaultRole));
164+
Assert.assertEquals(3, ldapUserInfo.getRoles().size());
165+
}
166+
}
167+
168+
@Test
169+
public void testGetUserInfoWithLdapDefaultRoles() {
170+
LdapManager ldapManager = new LdapManager();
171+
Deencapsulation.setField(ldapManager, "ldapClient", ldapClient);
172+
LdapConfig.ldap_default_roles = new String[] {LDAP_DEFAULT_ROLE, MISSING_LDAP_DEFAULT_ROLE};
173+
Role ldapGroupRole = new Role(LDAP_GROUP_ROLE);
174+
Role ldapDefaultRole = new Role(LDAP_DEFAULT_ROLE);
175+
mockClient(true, true, new ArrayList<>(Arrays.asList(LDAP_GROUP_ROLE)));
176+
try (MockedStatic<Env> envMockedStatic = Mockito.mockStatic(Env.class)) {
177+
mockAuth(envMockedStatic, ldapGroupRole, ldapDefaultRole);
178+
179+
LdapUserInfo ldapUserInfo = ldapManager.getUserInfo(USER1);
180+
Assert.assertNotNull(ldapUserInfo);
181+
Assert.assertTrue(ldapUserInfo.getRoles().contains(ldapGroupRole));
182+
Assert.assertTrue(ldapUserInfo.getRoles().contains(ldapDefaultRole));
183+
Assert.assertEquals(3, ldapUserInfo.getRoles().size());
184+
}
185+
}
77186
}

0 commit comments

Comments
 (0)