Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions conf/ldap.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
Original file line number Diff line number Diff line change
Expand Up @@ -6594,6 +6594,9 @@ public void replayDropGlobalFunction(FunctionSearchDesc functionSearchDesc) {
*/
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
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;
Expand Down Expand Up @@ -257,12 +258,8 @@ private Set<Role> getLdapGroupsRoles(String userName) throws DdlException {
// 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);
}
Expand All @@ -273,6 +270,27 @@ private Set<Role> getLdapGroupsRoles(String userName) throws DdlException {
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 {
Expand Down
38 changes: 38 additions & 0 deletions fe/fe-core/src/test/java/org/apache/doris/catalog/EnvTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -34,6 +39,9 @@
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 {
Expand Down Expand Up @@ -140,4 +148,34 @@ public void testSaveLoadHeader() throws Exception {

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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -77,6 +98,7 @@ public void testGetUserInfo() {
@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);
Expand All @@ -91,6 +113,7 @@ public void testCheckUserPasswd() {
@Test
public void testCheckUserPasswdCachedPasswdMatchLogsInfoWithoutThreshold() {
LdapManager ldapManager = new LdapManager();
Deencapsulation.setField(ldapManager, "ldapClient", ldapClient);
mockClient(true, true);
Assert.assertTrue(ldapManager.checkUserPasswd(USER1, "123"));

Expand All @@ -106,6 +129,7 @@ public void testCheckUserPasswdCachedPasswdMatchLogsInfoWithoutThreshold() {
@Test
public void testGetUserInfoLogsInfoWithoutThreshold() {
LdapManager ldapManager = new LdapManager();
Deencapsulation.setField(ldapManager, "ldapClient", ldapClient);
mockClient(true, true);

try (TestLogAppender appender = TestLogAppender.attach(LdapManager.class)) {
Expand All @@ -116,4 +140,80 @@ public void testGetUserInfoLogsInfoWithoutThreshold() {
"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());
}
}
}
Loading