HADOOP-14465. LdapGroupsMapping - support user and group search base. Contributed by Shwetha G S and Mingliang Liu
This commit is contained in:
parent
5578af8603
commit
a2121cb0d9
|
@ -130,6 +130,19 @@ public class LdapGroupsMapping
|
|||
public static final String BASE_DN_KEY = LDAP_CONFIG_PREFIX + ".base";
|
||||
public static final String BASE_DN_DEFAULT = "";
|
||||
|
||||
/*
|
||||
* Base DN used in user search.
|
||||
*/
|
||||
public static final String USER_BASE_DN_KEY =
|
||||
LDAP_CONFIG_PREFIX + ".userbase";
|
||||
|
||||
/*
|
||||
* Base DN used in group search.
|
||||
*/
|
||||
public static final String GROUP_BASE_DN_KEY =
|
||||
LDAP_CONFIG_PREFIX + ".groupbase";
|
||||
|
||||
|
||||
/*
|
||||
* Any additional filters to apply when searching for users
|
||||
*/
|
||||
|
@ -200,7 +213,7 @@ public class LdapGroupsMapping
|
|||
|
||||
private static final Log LOG = LogFactory.getLog(LdapGroupsMapping.class);
|
||||
|
||||
private static final SearchControls SEARCH_CONTROLS = new SearchControls();
|
||||
static final SearchControls SEARCH_CONTROLS = new SearchControls();
|
||||
static {
|
||||
SEARCH_CONTROLS.setSearchScope(SearchControls.SUBTREE_SCOPE);
|
||||
}
|
||||
|
@ -214,7 +227,8 @@ public class LdapGroupsMapping
|
|||
private String keystorePass;
|
||||
private String bindUser;
|
||||
private String bindPassword;
|
||||
private String baseDN;
|
||||
private String userbaseDN;
|
||||
private String groupbaseDN;
|
||||
private String groupSearchFilter;
|
||||
private String userSearchFilter;
|
||||
private String memberOfAttr;
|
||||
|
@ -315,7 +329,7 @@ public class LdapGroupsMapping
|
|||
uidNumber = uidAttribute.get().toString();
|
||||
}
|
||||
if (uidNumber != null && gidNumber != null) {
|
||||
return c.search(baseDN,
|
||||
return c.search(groupbaseDN,
|
||||
"(&"+ groupSearchFilter + "(|(" + posixGidAttr + "={0})" +
|
||||
"(" + groupMemberAttr + "={1})))",
|
||||
new Object[] {gidNumber, uidNumber},
|
||||
|
@ -350,7 +364,7 @@ public class LdapGroupsMapping
|
|||
} else {
|
||||
String userDn = result.getNameInNamespace();
|
||||
groupResults =
|
||||
c.search(baseDN,
|
||||
c.search(groupbaseDN,
|
||||
"(&" + groupSearchFilter + "(" + groupMemberAttr + "={0}))",
|
||||
new Object[]{userDn},
|
||||
SEARCH_CONTROLS);
|
||||
|
@ -391,7 +405,7 @@ public class LdapGroupsMapping
|
|||
DirContext c = getDirContext();
|
||||
|
||||
// Search for the user. We'll only ever need to look at the first result
|
||||
NamingEnumeration<SearchResult> results = c.search(baseDN,
|
||||
NamingEnumeration<SearchResult> results = c.search(userbaseDN,
|
||||
userSearchFilter, new Object[]{user}, SEARCH_CONTROLS);
|
||||
// return empty list if the user can not be found.
|
||||
if (!results.hasMoreElements()) {
|
||||
|
@ -489,7 +503,7 @@ public class LdapGroupsMapping
|
|||
filter.append("))");
|
||||
LOG.debug("Ldap group query string: " + filter.toString());
|
||||
NamingEnumeration<SearchResult> groupResults =
|
||||
context.search(baseDN,
|
||||
context.search(groupbaseDN,
|
||||
filter.toString(),
|
||||
SEARCH_CONTROLS);
|
||||
while (groupResults.hasMoreElements()) {
|
||||
|
@ -575,7 +589,20 @@ public class LdapGroupsMapping
|
|||
conf.get(BIND_PASSWORD_FILE_KEY, BIND_PASSWORD_FILE_DEFAULT));
|
||||
}
|
||||
|
||||
baseDN = conf.get(BASE_DN_KEY, BASE_DN_DEFAULT);
|
||||
String baseDN = conf.getTrimmed(BASE_DN_KEY, BASE_DN_DEFAULT);
|
||||
|
||||
//User search base which defaults to base dn.
|
||||
userbaseDN = conf.getTrimmed(USER_BASE_DN_KEY, baseDN);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Usersearch baseDN: " + userbaseDN);
|
||||
}
|
||||
|
||||
//Group search base which defaults to base dn.
|
||||
groupbaseDN = conf.getTrimmed(GROUP_BASE_DN_KEY, baseDN);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Groupsearch baseDN: " + userbaseDN);
|
||||
}
|
||||
|
||||
groupSearchFilter =
|
||||
conf.get(GROUP_SEARCH_FILTER_KEY, GROUP_SEARCH_FILTER_DEFAULT);
|
||||
userSearchFilter =
|
||||
|
|
|
@ -346,6 +346,26 @@
|
|||
</description>
|
||||
</property>
|
||||
|
||||
<property>
|
||||
<name>hadoop.security.group.mapping.ldap.userbase</name>
|
||||
<value></value>
|
||||
<description>
|
||||
The search base for the LDAP connection for user search query. This is a
|
||||
distinguished name, and its the root of the LDAP directory for users.
|
||||
If not set, hadoop.security.group.mapping.ldap.base is used.
|
||||
</description>
|
||||
</property>
|
||||
|
||||
<property>
|
||||
<name>hadoop.security.group.mapping.ldap.groupbase</name>
|
||||
<value></value>
|
||||
<description>
|
||||
The search base for the LDAP connection for group search . This is a
|
||||
distinguished name, and its the root of the LDAP directory for groups.
|
||||
If not set, hadoop.security.group.mapping.ldap.base is used.
|
||||
</description>
|
||||
</property>
|
||||
|
||||
<property>
|
||||
<name>hadoop.security.group.mapping.ldap.search.filter.user</name>
|
||||
<value>(&(objectClass=user)(sAMAccountName={0}))</value>
|
||||
|
|
|
@ -76,6 +76,7 @@ This provider supports LDAP with simple password authentication using JNDI API.
|
|||
`hadoop.security.group.mapping.ldap.url` must be set. This refers to the URL of the LDAP server for resolving user groups.
|
||||
|
||||
`hadoop.security.group.mapping.ldap.base` configures the search base for the LDAP connection. This is a distinguished name, and will typically be the root of the LDAP directory.
|
||||
Get groups for a given username first looks up the user and then looks up the groups for the user result. If the directory setup has different user and group search bases, use `hadoop.security.group.mapping.ldap.userbase` and `hadoop.security.group.mapping.ldap.groupbase` configs.
|
||||
|
||||
If the LDAP server does not support anonymous binds,
|
||||
set the distinguished name of the user to bind in `hadoop.security.group.mapping.ldap.bind.user`.
|
||||
|
|
|
@ -80,10 +80,12 @@ public class TestLdapGroupsMapping extends TestLdapGroupsMappingBase {
|
|||
private static final byte[] AUTHENTICATE_SUCCESS_MSG =
|
||||
{48, 12, 2, 1, 1, 97, 7, 10, 1, 0, 4, 0, 4, 0};
|
||||
|
||||
private final String userDN = "CN=some_user,DC=test,DC=com";
|
||||
|
||||
@Before
|
||||
public void setupMocks() throws NamingException {
|
||||
when(getUserSearchResult().getNameInNamespace()).
|
||||
thenReturn("CN=some_user,DC=test,DC=com");
|
||||
thenReturn(userDN);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -96,6 +98,66 @@ public class TestLdapGroupsMapping extends TestLdapGroupsMappingBase {
|
|||
doTestGetGroups(Arrays.asList(getTestGroups()), 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetGroupsWithDifferentBaseDNs() throws Exception {
|
||||
Configuration conf = new Configuration();
|
||||
// Set this, so we don't throw an exception
|
||||
conf.set(LdapGroupsMapping.LDAP_URL_KEY, "ldap://test");
|
||||
String userBaseDN = "ou=Users,dc=xxx,dc=com ";
|
||||
String groupBaseDN = " ou=Groups,dc=xxx,dc=com";
|
||||
conf.set(LdapGroupsMapping.USER_BASE_DN_KEY, userBaseDN);
|
||||
conf.set(LdapGroupsMapping.GROUP_BASE_DN_KEY, groupBaseDN);
|
||||
|
||||
doTestGetGroupsWithBaseDN(conf, userBaseDN.trim(), groupBaseDN.trim());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetGroupsWithDefaultBaseDN() throws Exception {
|
||||
Configuration conf = new Configuration();
|
||||
// Set this, so we don't throw an exception
|
||||
conf.set(LdapGroupsMapping.LDAP_URL_KEY, "ldap://test");
|
||||
String baseDN = " dc=xxx,dc=com ";
|
||||
conf.set(LdapGroupsMapping.BASE_DN_KEY, baseDN);
|
||||
doTestGetGroupsWithBaseDN(conf, baseDN.trim(), baseDN.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to do the LDAP getGroups operation using given user base DN
|
||||
* and group base DN.
|
||||
* @param conf The created configuration
|
||||
* @param userBaseDN user base DN
|
||||
* @param groupBaseDN group base DN
|
||||
* @throws NamingException if error happens when getting groups
|
||||
*/
|
||||
private void doTestGetGroupsWithBaseDN(Configuration conf, String userBaseDN,
|
||||
String groupBaseDN) throws NamingException {
|
||||
final LdapGroupsMapping groupsMapping = getGroupsMapping();
|
||||
groupsMapping.setConf(conf);
|
||||
|
||||
final String userName = "some_user";
|
||||
|
||||
// The search functionality of the mock context is reused, so we will
|
||||
// return the user NamingEnumeration first, and then the group
|
||||
when(getContext().search(anyString(), anyString(), any(Object[].class),
|
||||
any(SearchControls.class)))
|
||||
.thenReturn(getUserNames(), getGroupNames());
|
||||
|
||||
List<String> groups = groupsMapping.getGroups(userName);
|
||||
Assert.assertEquals(Arrays.asList(getTestGroups()), groups);
|
||||
|
||||
// We should have searched for the username and groups with default base dn
|
||||
verify(getContext(), times(1)).search(userBaseDN,
|
||||
LdapGroupsMapping.USER_SEARCH_FILTER_DEFAULT,
|
||||
new Object[]{userName},
|
||||
LdapGroupsMapping.SEARCH_CONTROLS);
|
||||
|
||||
verify(getContext(), times(1)).search(groupBaseDN,
|
||||
"(&" + LdapGroupsMapping.GROUP_SEARCH_FILTER_DEFAULT + "(" +
|
||||
LdapGroupsMapping.GROUP_MEMBERSHIP_ATTR_DEFAULT + "={0}))",
|
||||
new Object[]{userDN},
|
||||
LdapGroupsMapping.SEARCH_CONTROLS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetGroupsWithHierarchy() throws IOException, NamingException {
|
||||
// The search functionality of the mock context is reused, so we will
|
||||
|
|
Loading…
Reference in New Issue