diff --git common/src/java/org/apache/hadoop/hive/conf/HiveConf.java common/src/java/org/apache/hadoop/hive/conf/HiveConf.java index 5ea9751..8a29eb1 100644 --- common/src/java/org/apache/hadoop/hive/conf/HiveConf.java +++ common/src/java/org/apache/hadoop/hive/conf/HiveConf.java @@ -317,7 +317,8 @@ private static URL checkConfigFile(File f) { } public static final String HIVE_LLAP_DAEMON_SERVICE_PRINCIPAL_NAME = "hive.llap.daemon.service.principal"; - + public static final String HIVE_SERVER2_AUTHENTICATION_LDAP_USERMEMBERSHIPKEY_NAME = + "hive.server2.authentication.ldap.userMembershipKey"; /** * dbVars are the parameters can be set per database. If these @@ -2418,8 +2419,14 @@ private static void populateLlapDaemonVarsSet(Set llapDaemonVarsSetLocal "LDAP attribute name whose values are unique in this LDAP server.\n" + "For example: uid or CN."), HIVE_SERVER2_PLAIN_LDAP_GROUPMEMBERSHIP_KEY("hive.server2.authentication.ldap.groupMembershipKey", "member", - "LDAP attribute name on the user entry that references a group, the user belongs to.\n" + + "LDAP attribute name on the group object that contains the list of distinguished names\n" + + "for the user, group, and contact objects that are members of the group.\n" + "For example: member, uniqueMember or memberUid"), + HIVE_SERVER2_PLAIN_LDAP_USERMEMBERSHIP_KEY(HIVE_SERVER2_AUTHENTICATION_LDAP_USERMEMBERSHIPKEY_NAME, null, + "LDAP attribute name on the user object that contains groups of which the user is\n" + + "a direct member, except for the primary group, which is represented by the\n" + + "primaryGroupId.\n" + + "For example: memberOf"), HIVE_SERVER2_PLAIN_LDAP_GROUPCLASS_KEY("hive.server2.authentication.ldap.groupClassKey", "groupOfNames", "LDAP attribute name on the group entry that is to be used in LDAP group searches.\n" + "For example: group, groupOfNames or groupOfUniqueNames."), diff --git service/src/java/org/apache/hive/service/auth/ldap/DirSearch.java service/src/java/org/apache/hive/service/auth/ldap/DirSearch.java index 33b6088..f7020bc 100644 --- service/src/java/org/apache/hive/service/auth/ldap/DirSearch.java +++ service/src/java/org/apache/hive/service/auth/ldap/DirSearch.java @@ -35,6 +35,23 @@ String findUserDn(String user) throws NamingException; /** + * Finds group's distinguished name. + * @param group group name or unique identifier + * @return DN for the specified group name + * @throws NamingException + */ + String findGroupDn(String group) throws NamingException; + + /** + * Verifies that specified user is a member of specified group. + * @param user user id or distinguished name + * @param groupDn group's DN + * @return {@code true} if the user is a member of the group, {@code false} - otherwise. + * @throws NamingException + */ + boolean isUserMemberOfGroup(String user, String groupDn) throws NamingException; + + /** * Finds groups that contain the specified user. * @param userDn user's distinguished name * @return list of groups diff --git service/src/java/org/apache/hive/service/auth/ldap/GroupFilterFactory.java service/src/java/org/apache/hive/service/auth/ldap/GroupFilterFactory.java index 152c4b2..2adade2 100644 --- service/src/java/org/apache/hive/service/auth/ldap/GroupFilterFactory.java +++ service/src/java/org/apache/hive/service/auth/ldap/GroupFilterFactory.java @@ -17,6 +17,9 @@ */ package org.apache.hive.service.auth.ldap; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -48,22 +51,28 @@ public Filter getInstance(HiveConf conf) { return null; } - return new GroupFilter(groupFilter); + if (conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERMEMBERSHIP_KEY) == null) { + return new GroupMembershipKeyFilter(groupFilter); + } else { + return new UserMembershipKeyFilter(groupFilter); + } } - private static final class GroupFilter implements Filter { + @VisibleForTesting + static final class GroupMembershipKeyFilter implements Filter { - private static final Logger LOG = LoggerFactory.getLogger(GroupFilter.class); + private static final Logger LOG = LoggerFactory.getLogger(GroupMembershipKeyFilter.class); private final Set groupFilter = new HashSet<>(); - GroupFilter(Collection groupFilter) { + GroupMembershipKeyFilter(Collection groupFilter) { this.groupFilter.addAll(groupFilter); } @Override public void apply(DirSearch ldap, String user) throws AuthenticationException { - LOG.info("Authenticating user '{}' using group membership", user); + LOG.info("Authenticating user '{}' using {}", user, + GroupMembershipKeyFilter.class.getSimpleName()); List memberOf = null; @@ -78,7 +87,8 @@ public void apply(DirSearch ldap, String user) throws AuthenticationException { for (String groupDn : memberOf) { String shortName = LdapUtils.getShortName(groupDn); if (groupFilter.contains(shortName)) { - LOG.info("Authentication succeeded based on group membership"); + LOG.debug("GroupMembershipKeyFilter passes: user '{}' is a member of '{}' group", + user, groupDn); return; } } @@ -87,4 +97,56 @@ public void apply(DirSearch ldap, String user) throws AuthenticationException { + "User not a member of specified list"); } } + + @VisibleForTesting + static final class UserMembershipKeyFilter implements Filter { + + private static final Logger LOG = LoggerFactory.getLogger(UserMembershipKeyFilter.class); + + private final Collection groupFilter; + + UserMembershipKeyFilter(Collection groupFilter) { + this.groupFilter = groupFilter; + } + + @Override + public void apply(DirSearch ldap, String user) throws AuthenticationException { + LOG.info("Authenticating user '{}' using {}", user, + UserMembershipKeyFilter.class.getSimpleName()); + + List groupDns = new ArrayList<>(); + for (String groupId : groupFilter) { + try { + String groupDn = ldap.findGroupDn(groupId); + groupDns.add(groupDn); + } catch (NamingException e) { + LOG.debug("Cannot find DN for group " + groupId, e); + } + } + + if (groupDns.isEmpty()) { + String msg = String.format("No DN(s) has been found for any of group(s): %s", + Joiner.on(',').join(groupFilter)); + LOG.debug(msg); + throw new AuthenticationException("No DN(s) has been found for any of specified group(s)"); + } + + for (String groupDn : groupDns) { + try { + if (ldap.isUserMemberOfGroup(user, groupDn)) { + LOG.debug("UserMembershipKeyFilter passes: user '{}' is a member of '{}' group", + user, groupDn); + return; + } + } catch (NamingException e) { + if (LOG.isDebugEnabled()) { + String msg = String.format("Cannot match user '%s' and group '%s'", user, groupDn); + LOG.debug(msg, e); + } + } + } + throw new AuthenticationException(String.format( + "Authentication failed: User '%s' is not a member of listed groups", user)); + } + } } diff --git service/src/java/org/apache/hive/service/auth/ldap/LdapSearch.java service/src/java/org/apache/hive/service/auth/ldap/LdapSearch.java index 65076ea..be512c0 100644 --- service/src/java/org/apache/hive/service/auth/ldap/LdapSearch.java +++ service/src/java/org/apache/hive/service/auth/ldap/LdapSearch.java @@ -121,6 +121,23 @@ public String findUserDn(String user) throws NamingException { * {@inheritDoc} */ @Override + public String findGroupDn(String group) throws NamingException { + return execute(groupBases, queries.findGroupDnById(group)).getSingleLdapName(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isUserMemberOfGroup(String user, String groupDn) throws NamingException { + String userId = LdapUtils.extractUserName(user); + return execute(userBases, queries.isUserMemberOfGroup(userId, groupDn)).hasSingleResult(); + } + + /** + * {@inheritDoc} + */ + @Override public List findGroupsForUser(String userDn) throws NamingException { String userName = LdapUtils.extractUserName(userDn); return execute(groupBases, queries.findGroupsForUser(userName, userDn)).getAllLdapNames(); diff --git service/src/java/org/apache/hive/service/auth/ldap/Query.java service/src/java/org/apache/hive/service/auth/ldap/Query.java index b8bf938..194f8aa 100644 --- service/src/java/org/apache/hive/service/auth/ldap/Query.java +++ service/src/java/org/apache/hive/service/auth/ldap/Query.java @@ -103,6 +103,17 @@ public QueryBuilder map(String key, String value) { } /** + * Sets mapping between names in the search filter template and actual values. + * @param key marker in the search filter template. + * @param values array of values + * @return the current instance of the builder + */ + public QueryBuilder map(String key, String[] values) { + filterTemplate.add(key, values); + return this; + } + + /** * Sets attribute that should be returned in results for the query. * @param attributeName attribute name * @return the current instance of the builder diff --git service/src/java/org/apache/hive/service/auth/ldap/QueryFactory.java service/src/java/org/apache/hive/service/auth/ldap/QueryFactory.java index e9172d3..6ce01c0 100644 --- service/src/java/org/apache/hive/service/auth/ldap/QueryFactory.java +++ service/src/java/org/apache/hive/service/auth/ldap/QueryFactory.java @@ -17,17 +17,21 @@ */ package org.apache.hive.service.auth.ldap; +import com.google.common.base.Preconditions; import com.google.common.base.Strings; import org.apache.hadoop.hive.conf.HiveConf; /** * A factory for common types of directory service search queries. */ -public final class QueryFactory { +final class QueryFactory { + + private static final String[] USER_OBJECT_CLASSES = {"person", "user", "inetOrgPerson"}; private final String guidAttr; private final String groupClassAttr; private final String groupMembershipAttr; + private final String userMembershipAttr; /** * Constructs the factory based on provided Hive configuration. @@ -38,6 +42,8 @@ public QueryFactory(HiveConf conf) { groupClassAttr = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPCLASS_KEY); groupMembershipAttr = conf.getVar( HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPMEMBERSHIP_KEY); + userMembershipAttr = conf.getVar( + HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERMEMBERSHIP_KEY); } /** @@ -62,9 +68,10 @@ public Query findGroupDnById(String groupId) { */ public Query findUserDnByRdn(String userRdn) { return Query.builder() - .filter("(&(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))" + .filter("(&(|)}>)" + "())") .limit(2) + .map("classes", USER_OBJECT_CLASSES) .map("userRdn", userRdn) .build(); } @@ -93,8 +100,9 @@ public Query findDnByPattern(String rdn) { */ public Query findUserDnByName(String userName) { return Query.builder() - .filter("(&(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))" + .filter("(&(|)}>)" + "(|(uid=)(sAMAccountName=)))") + .map("classes", USER_OBJECT_CLASSES) .map("userName", userName) .limit(2) .build(); @@ -118,6 +126,34 @@ public Query findGroupsForUser(String userName, String userDn) { } /** + * Returns a query for checking whether specified user is a member of specified group. + * + * The query requires {@value HiveConf#HIVE_SERVER2_AUTHENTICATION_LDAP_USERMEMBERSHIPKEY_NAME} + * Hive configuration property to be set. + * + * @param userId user unique identifier + * @param groupDn group DN + * @return an instance of {@link Query} + * @see HiveConf.ConfVars#HIVE_SERVER2_PLAIN_LDAP_USERMEMBERSHIP_KEY + * @throws NullPointerException when + * {@value HiveConf#HIVE_SERVER2_AUTHENTICATION_LDAP_USERMEMBERSHIPKEY_NAME} is not set. + */ + public Query isUserMemberOfGroup(String userId, String groupDn) { + Preconditions.checkState(!Strings.isNullOrEmpty(userMembershipAttr), + "hive.server2.authentication.ldap.userMembershipKey is not configured."); + return Query.builder() + .filter("(&(|)}>)" + + "(=)(=))") + .map("classes", USER_OBJECT_CLASSES) + .map("guidAttr", guidAttr) + .map("userMembershipAttr", userMembershipAttr) + .map("userId", userId) + .map("groupDn", groupDn) + .limit(2) + .build(); + } + + /** * Returns a query object created for the custom filter. *
* This query is configured to return a group membership attribute as part of the search result. diff --git service/src/test/org/apache/hive/service/auth/TestLdapAtnProviderWithMiniDS.java service/src/test/org/apache/hive/service/auth/TestLdapAtnProviderWithMiniDS.java index cd62935..d6d67a5 100644 --- service/src/test/org/apache/hive/service/auth/TestLdapAtnProviderWithMiniDS.java +++ service/src/test/org/apache/hive/service/auth/TestLdapAtnProviderWithMiniDS.java @@ -67,7 +67,11 @@ ) }) -@ApplyLdifFiles("ldap/example.com.ldif") +@ApplyLdifFiles({ + "ldap/example.com.ldif", + "ldap/microsoft.schema.ldif", + "ldap/ad.example.com.ldif" +}) public class TestLdapAtnProviderWithMiniDS extends AbstractLdapTestUnit { private static final String GROUP1_NAME = "group1"; @@ -75,6 +79,12 @@ private static final String GROUP3_NAME = "group3"; private static final String GROUP4_NAME = "group4"; + private static final String GROUP_ADMINS_NAME = "admins"; + private static final String GROUP_TEAM1_NAME = "team1"; + private static final String GROUP_TEAM2_NAME = "team2"; + private static final String GROUP_RESOURCE1_NAME = "resource1"; + private static final String GROUP_RESOURCE2_NAME = "resource2"; + private static final User USER1 = User.builder() .id("user1") .useIdForPassword() @@ -99,6 +109,36 @@ .dn("cn=user4,ou=People,dc=example,dc=com") .build(); + private static final User ENGINEER_1 = User.builder() + .id("engineer1") + .dn("sAMAccountName=engineer1,ou=Engineering,dc=ad,dc=example,dc=com") + .password("engineer1-password") + .build(); + + private static final User ENGINEER_2 = User.builder() + .id("engineer2") + .dn("sAMAccountName=engineer2,ou=Engineering,dc=ad,dc=example,dc=com") + .password("engineer2-password") + .build(); + + private static final User MANAGER_1 = User.builder() + .id("manager1") + .dn("sAMAccountName=manager1,ou=Management,dc=ad,dc=example,dc=com") + .password("manager1-password") + .build(); + + private static final User MANAGER_2 = User.builder() + .id("manager2") + .dn("sAMAccountName=manager2,ou=Management,dc=ad,dc=example,dc=com") + .password("manager2-password") + .build(); + + private static final User ADMIN_1 = User.builder() + .id("admin1") + .dn("sAMAccountName=admin1,ou=Administration,dc=ad,dc=example,dc=com") + .password("admin1-password") + .build(); + private LdapAuthenticationTestCase testCase; private LdapAuthenticationTestCase.Builder defaultBuilder() { @@ -481,7 +521,7 @@ public void testCustomQueryWithGroupsPositive() { .userDNPatterns( "cn=%s,ou=People,dc=example,dc=com", "uid=%s,ou=People,dc=example,dc=com") - .groupMembership("uniqueMember") + .groupMembershipKey("uniqueMember") .customQuery( String.format("(&(objectClass=groupOfUniqueNames)(cn=%s))", GROUP4_NAME)) @@ -528,11 +568,112 @@ public void testGroupFilterPositiveWithCustomAttributes() { .groupDNPatterns("cn=%s,ou=Groups,dc=example,dc=com") .groupFilters(GROUP4_NAME) .guidKey("cn") - .groupMembership("uniqueMember") + .groupMembershipKey("uniqueMember") .groupClassKey("groupOfUniqueNames") .build(); testCase.assertAuthenticatePasses(USER4.credentialsWithId()); testCase.assertAuthenticatePasses(USER4.credentialsWithDn()); } + + @Test + public void testDirectUserMembershipGroupFilterPositive() { + testCase = defaultBuilder() + .userDNPatterns( + "sAMAccountName=%s,ou=Engineering,dc=ad,dc=example,dc=com", + "sAMAccountName=%s,ou=Management,dc=ad,dc=example,dc=com") + .groupDNPatterns( + "sAMAccountName=%s,ou=Teams,dc=ad,dc=example,dc=com", + "sAMAccountName=%s,ou=Resources,dc=ad,dc=example,dc=com") + .groupFilters( + GROUP_TEAM1_NAME, + GROUP_TEAM2_NAME, + GROUP_RESOURCE1_NAME, + GROUP_RESOURCE2_NAME) + .guidKey("sAMAccountName") + .userMembershipKey("memberOf") + .build(); + + testCase.assertAuthenticatePasses(ENGINEER_1.credentialsWithId()); + testCase.assertAuthenticatePasses(ENGINEER_2.credentialsWithId()); + testCase.assertAuthenticatePasses(MANAGER_1.credentialsWithId()); + testCase.assertAuthenticatePasses(MANAGER_2.credentialsWithId()); + } + + @Test + public void testDirectUserMembershipGroupFilterNegative() { + testCase = defaultBuilder() + .userDNPatterns( + "sAMAccountName=%s,ou=Engineering,dc=ad,dc=example,dc=com", + "sAMAccountName=%s,ou=Management,dc=ad,dc=example,dc=com") + .groupDNPatterns("cn=%s,ou=Teams,dc=ad,dc=example,dc=com") + .groupFilters(GROUP_TEAM1_NAME) + .guidKey("sAMAccountName") + .userMembershipKey("memberOf") + .build(); + + testCase.assertAuthenticateFails(ENGINEER_2.credentialsWithId()); + testCase.assertAuthenticateFails(MANAGER_2.credentialsWithId()); + } + + @Test + public void testDirectUserMembershipGroupFilterNegativeWithoutUserBases() throws Exception { + testCase = defaultBuilder() + .groupDNPatterns("cn=%s,ou=Teams,dc=ad,dc=example,dc=com") + .groupFilters(GROUP_TEAM1_NAME) + .guidKey("sAMAccountName") + .userMembershipKey("memberOf") + .build(); + + testCase.assertAuthenticateFails(ENGINEER_1.credentialsWithId()); + testCase.assertAuthenticateFails(ENGINEER_2.credentialsWithId()); + testCase.assertAuthenticateFails(MANAGER_1.credentialsWithId()); + testCase.assertAuthenticateFails(MANAGER_2.credentialsWithId()); + } + + @Test + public void testDirectUserMembershipGroupFilterWithDNCredentials() throws Exception { + testCase = defaultBuilder() + .userDNPatterns("sAMAccountName=%s,ou=Engineering,dc=ad,dc=example,dc=com") + .groupDNPatterns("cn=%s,ou=Teams,dc=ad,dc=example,dc=com") + .groupFilters(GROUP_TEAM1_NAME) + .guidKey("sAMAccountName") + .userMembershipKey("memberOf") + .build(); + + testCase.assertAuthenticatePasses(ENGINEER_1.credentialsWithDn()); + testCase.assertAuthenticateFails(MANAGER_1.credentialsWithDn()); + } + + @Test + public void testDirectUserMembershipGroupFilterWithDifferentGroupClassKey() throws Exception { + testCase = defaultBuilder() + .userDNPatterns("sAMAccountName=%s,ou=Administration,dc=ad,dc=example,dc=com") + .groupDNPatterns("cn=%s,ou=Administration,dc=ad,dc=example,dc=com") + .groupFilters(GROUP_ADMINS_NAME) + .guidKey("sAMAccountName") + .userMembershipKey("memberOf") + .groupClassKey("groupOfUniqueNames") + .build(); + + testCase.assertAuthenticatePasses(ADMIN_1.credentialsWithId()); + testCase.assertAuthenticateFails(ENGINEER_1.credentialsWithId()); + testCase.assertAuthenticateFails(MANAGER_1.credentialsWithDn()); + } + + @Test + public void testDirectUserMembershipGroupFilterNegativeWithWrongGroupClassKey() throws Exception { + testCase = defaultBuilder() + .userDNPatterns("sAMAccountName=%s,ou=Administration,dc=ad,dc=example,dc=com") + .groupDNPatterns("cn=%s,ou=Administration,dc=ad,dc=example,dc=com") + .groupFilters(GROUP_ADMINS_NAME) + .guidKey("sAMAccountName") + .userMembershipKey("memberOf") + .groupClassKey("wrongClass") + .build(); + + testCase.assertAuthenticateFails(ADMIN_1.credentialsWithId()); + testCase.assertAuthenticateFails(ENGINEER_1.credentialsWithId()); + testCase.assertAuthenticateFails(MANAGER_1.credentialsWithDn()); + } } diff --git service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java index 4fad755..6fd218c 100644 --- service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java +++ service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java @@ -154,7 +154,7 @@ public void testAuthenticateWhenUserFilterFails() throws NamingException, Authen } @Test - public void testAuthenticateWhenGroupFilterPasses() throws NamingException, AuthenticationException, IOException { + public void testAuthenticateWhenGroupMembershipKeyFilterPasses() throws NamingException, AuthenticationException, IOException { conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "group1,group2"); when(search.findUserDn("user1")).thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com"); @@ -174,7 +174,7 @@ public void testAuthenticateWhenGroupFilterPasses() throws NamingException, Auth } @Test - public void testAuthenticateWhenUserAndGroupFiltersPass() throws NamingException, AuthenticationException, IOException { + public void testAuthenticateWhenUserAndGroupMembershipKeyFiltersPass() throws NamingException, AuthenticationException, IOException { conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "group1,group2"); conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER, "user1,user2"); @@ -195,7 +195,7 @@ public void testAuthenticateWhenUserAndGroupFiltersPass() throws NamingException } @Test - public void testAuthenticateWhenUserFilterPassesAndGroupFilterFails() + public void testAuthenticateWhenUserFilterPassesAndGroupMembershipKeyFilterFails() throws NamingException, AuthenticationException, IOException { thrown.expect(AuthenticationException.class); conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "group1,group2"); @@ -212,7 +212,7 @@ public void testAuthenticateWhenUserFilterPassesAndGroupFilterFails() } @Test - public void testAuthenticateWhenUserFilterFailsAndGroupFilterPasses() + public void testAuthenticateWhenUserFilterFailsAndGroupMembershipKeyFilterPasses() throws NamingException, AuthenticationException, IOException { thrown.expect(AuthenticationException.class); conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "group3"); @@ -258,6 +258,72 @@ public void testAuthenticateWhenCustomQueryFilterFailsAndUserFilterPasses() thro authenticateUserAndCheckSearchIsClosed("user3"); } + @Test + public void testAuthenticateWhenUserMembershipKeyFilterPasses() throws NamingException, AuthenticationException, IOException { + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "HIVE-USERS"); + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BASEDN, "dc=mycorp,dc=com"); + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERMEMBERSHIP_KEY, "memberOf"); + + when(search.findUserDn("user1")).thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com"); + + String groupDn = "cn=HIVE-USERS,ou=Groups,dc=mycorp,dc=com"; + when(search.findGroupDn("HIVE-USERS")).thenReturn(groupDn); + when(search.isUserMemberOfGroup("user1", groupDn)).thenReturn(true); + + auth = new LdapAuthenticationProviderImpl(conf, factory); + auth.Authenticate("user1", "Blah"); + + verify(factory, times(1)).getInstance(isA(HiveConf.class), anyString(), eq("Blah")); + verify(search, times(1)).findGroupDn(anyString()); + verify(search, times(1)).isUserMemberOfGroup(anyString(), anyString()); + verify(search, atLeastOnce()).close(); + } + + @Test + public void testAuthenticateWhenUserMembershipKeyFilterFails() throws NamingException, AuthenticationException, IOException { + thrown.expect(AuthenticationException.class); + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "HIVE-USERS"); + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BASEDN, "dc=mycorp,dc=com"); + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERMEMBERSHIP_KEY, "memberOf"); + + when(search.findUserDn("user1")).thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com"); + + String groupDn = "cn=HIVE-USERS,ou=Groups,dc=mycorp,dc=com"; + when(search.findGroupDn("HIVE-USERS")).thenReturn(groupDn); + when(search.isUserMemberOfGroup("user1", groupDn)).thenReturn(false); + + auth = new LdapAuthenticationProviderImpl(conf, factory); + auth.Authenticate("user1", "Blah"); + } + + @Test + public void testAuthenticateWhenUserMembershipKeyFilter2x2PatternsPasses() throws NamingException, AuthenticationException, IOException { + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "HIVE-USERS1,HIVE-USERS2"); + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPDNPATTERN, + "cn=%s,ou=Groups,ou=branch1,dc=mycorp,dc=com"); + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN, + "cn=%s,ou=Userss,ou=branch1,dc=mycorp,dc=com"); + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERMEMBERSHIP_KEY, "memberOf"); + + when(search.findUserDn("user1")).thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com"); + + when(search.findGroupDn("HIVE-USERS1")) + .thenReturn("cn=HIVE-USERS1,ou=Groups,ou=branch1,dc=mycorp,dc=com"); + when(search.findGroupDn("HIVE-USERS2")) + .thenReturn("cn=HIVE-USERS2,ou=Groups,ou=branch1,dc=mycorp,dc=com"); + + when(search.isUserMemberOfGroup("user1", "cn=HIVE-USERS1,ou=Groups,ou=branch1,dc=mycorp,dc=com")).thenThrow(NamingException.class); + when(search.isUserMemberOfGroup("user1", "cn=HIVE-USERS2,ou=Groups,ou=branch1,dc=mycorp,dc=com")).thenReturn(true); + + auth = new LdapAuthenticationProviderImpl(conf, factory); + auth.Authenticate("user1", "Blah"); + + verify(factory, times(1)).getInstance(isA(HiveConf.class), anyString(), eq("Blah")); + verify(search, times(2)).findGroupDn(anyString()); + verify(search, times(2)).isUserMemberOfGroup(anyString(), anyString()); + verify(search, atLeastOnce()).close(); + } + private void expectAuthenticationExceptionForInvalidPassword() { thrown.expect(AuthenticationException.class); thrown.expectMessage("a null or blank password has been provided"); diff --git service/src/test/org/apache/hive/service/auth/ldap/LdapAuthenticationTestCase.java service/src/test/org/apache/hive/service/auth/ldap/LdapAuthenticationTestCase.java index acde8c1..f4ad6ed 100644 --- service/src/test/org/apache/hive/service/auth/ldap/LdapAuthenticationTestCase.java +++ service/src/test/org/apache/hive/service/auth/ldap/LdapAuthenticationTestCase.java @@ -113,8 +113,14 @@ public Builder customQuery(String customQuery) { return setVarOnce(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_CUSTOMLDAPQUERY, customQuery); } - public Builder groupMembership(String groupMembership) { - return setVarOnce(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPMEMBERSHIP_KEY, groupMembership); + public Builder groupMembershipKey(String groupMembershipKey) { + return setVarOnce(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPMEMBERSHIP_KEY, + groupMembershipKey); + } + + public Builder userMembershipKey(String userMembershipKey) { + return setVarOnce(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERMEMBERSHIP_KEY, + userMembershipKey); } private Builder setVarOnce(HiveConf.ConfVars confVar, String value) { diff --git service/src/test/org/apache/hive/service/auth/ldap/TestGroupFilter.java service/src/test/org/apache/hive/service/auth/ldap/TestGroupFilter.java index 0cc2ead..d5da76c 100644 --- service/src/test/org/apache/hive/service/auth/ldap/TestGroupFilter.java +++ service/src/test/org/apache/hive/service/auth/ldap/TestGroupFilter.java @@ -29,6 +29,7 @@ import org.junit.Before; import org.mockito.Mock; +import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import static org.mockito.Mockito.*; @@ -49,16 +50,31 @@ public void setup() { } @Test - public void testFactory() { + public void testGetInstanceWhenGroupFilterIsEmpty() { conf.unset(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER.varname); assertNull(factory.getInstance(conf)); + } + @Test + public void testGetInstanceOfGroupMembershipKeyFilter() { conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "G1"); - assertNotNull(factory.getInstance(conf)); + Filter instance = factory.getInstance(conf); + assertNotNull(instance); + assertThat(instance, instanceOf(GroupFilterFactory.GroupMembershipKeyFilter.class)); } @Test - public void testApplyPositive() throws AuthenticationException, NamingException, IOException { + public void testGetInstanceOfUserMembershipKeyFilter() { + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "G1"); + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERMEMBERSHIP_KEY, "memberof"); + Filter instance = factory.getInstance(conf); + assertNotNull(instance); + assertThat(instance, instanceOf(GroupFilterFactory.UserMembershipKeyFilter.class)); + } + + @Test + public void testGroupMembershipKeyFilterApplyPositive() + throws AuthenticationException, NamingException, IOException { conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "HiveUsers"); when(search.findUserDn(eq("user1"))) @@ -90,7 +106,8 @@ public void testApplyPositive() throws AuthenticationException, NamingException, } @Test(expected = AuthenticationException.class) - public void testApplyNegative() throws AuthenticationException, NamingException, IOException { + public void testGroupMembershipKeyFilterApplyNegative() + throws AuthenticationException, NamingException, IOException { conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "HiveUsers"); when(search.findGroupsForUser(eq("user1"))).thenReturn(Arrays.asList("SuperUsers", "Office1", "G1", "G2")); @@ -98,4 +115,47 @@ public void testApplyNegative() throws AuthenticationException, NamingException, Filter filter = factory.getInstance(conf); filter.apply(search, "user1"); } + + @Test + public void testUserMembershipKeyFilterApplyPositiveWithUserId() + throws AuthenticationException, NamingException, IOException { + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERMEMBERSHIP_KEY, "memberOf"); + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "Group1,Group2"); + + when(search.findGroupDn("Group1")).thenReturn("cn=Group1,dc=a,dc=b"); + when(search.findGroupDn("Group2")).thenReturn("cn=Group2,dc=a,dc=b"); + + when(search.isUserMemberOfGroup("User1", "cn=Group2,dc=a,dc=b")).thenReturn(true); + + Filter filter = factory.getInstance(conf); + filter.apply(search, "User1"); + } + + @Test + public void testUserMembershipKeyFilterApplyPositiveWithUserDn() + throws AuthenticationException, NamingException, IOException { + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERMEMBERSHIP_KEY, "memberOf"); + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "Group1,Group2"); + + when(search.findGroupDn("Group1")).thenReturn("cn=Group1,dc=a,dc=b"); + when(search.findGroupDn("Group2")).thenReturn("cn=Group2,dc=a,dc=b"); + + when(search.isUserMemberOfGroup("cn=User1,dc=a,dc=b", "cn=Group2,dc=a,dc=b")).thenReturn(true); + + Filter filter = factory.getInstance(conf); + filter.apply(search, "cn=User1,dc=a,dc=b"); + } + + @Test(expected = AuthenticationException.class) + public void testUserMembershipKeyFilterApplyNegative() + throws AuthenticationException, NamingException, IOException { + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERMEMBERSHIP_KEY, "memberOf"); + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "Group1,Group2"); + + when(search.findGroupDn("Group1")).thenReturn("cn=Group1,dc=a,dc=b"); + when(search.findGroupDn("Group2")).thenReturn("cn=Group2,dc=a,dc=b"); + + Filter filter = factory.getInstance(conf); + filter.apply(search, "User1"); + } } diff --git service/src/test/org/apache/hive/service/auth/ldap/TestLdapSearch.java service/src/test/org/apache/hive/service/auth/ldap/TestLdapSearch.java index 499b624..7c7b393 100644 --- service/src/test/org/apache/hive/service/auth/ldap/TestLdapSearch.java +++ service/src/test/org/apache/hive/service/auth/ldap/TestLdapSearch.java @@ -17,6 +17,7 @@ */ package org.apache.hive.service.auth.ldap; +import com.google.common.base.Joiner; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -49,6 +50,7 @@ @Before public void setup() { conf = new HiveConf(); + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERMEMBERSHIP_KEY, "memberOf"); } @Test @@ -206,4 +208,86 @@ public void testExecuteCustomQuery() throws NamingException { Collections.sort(actual); assertEquals(expected, actual); } + + @Test + public void testFindGroupDnPositive() throws NamingException { + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPDNPATTERN, + "CN=%s,OU=org1,DC=foo,DC=bar"); + String groupDn = "CN=Group1"; + NamingEnumeration result = mockNamingEnumeration(groupDn); + when(ctx.search(anyString(), anyString(), any(SearchControls.class))).thenReturn(result); + search = new LdapSearch(conf, ctx); + String expected = groupDn; + String actual = search.findGroupDn("grp1"); + assertEquals(expected, actual); + } + + @Test(expected = NamingException.class) + public void testFindGroupDNNoResults() throws NamingException { + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPDNPATTERN, + "CN=%s,OU=org1,DC=foo,DC=bar"); + NamingEnumeration result = mockEmptyNamingEnumeration(); + when(ctx.search(anyString(), anyString(), any(SearchControls.class))).thenReturn(result); + search = new LdapSearch(conf, ctx); + search.findGroupDn("anyGroup"); + } + + @Test(expected = NamingException.class) + public void testFindGroupDNTooManyResults() throws NamingException { + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPDNPATTERN, + "CN=%s,OU=org1,DC=foo,DC=bar"); + NamingEnumeration result = + LdapTestUtils.mockNamingEnumeration("Result1", "Result2", "Result3"); + when(ctx.search(anyString(), anyString(), any(SearchControls.class))).thenReturn(result); + search = new LdapSearch(conf, ctx); + search.findGroupDn("anyGroup"); + } + + @Test + public void testFindGroupDNWhenExceptionInSearch() throws NamingException { + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPDNPATTERN, + Joiner.on(":").join( + "CN=%s,OU=org1,DC=foo,DC=bar", + "CN=%s,OU=org2,DC=foo,DC=bar" + ) + ); + NamingEnumeration result = LdapTestUtils.mockNamingEnumeration("CN=Group1"); + when(ctx.search(anyString(), anyString(), any(SearchControls.class))) + .thenReturn(result) + .thenThrow(NamingException.class); + search = new LdapSearch(conf, ctx); + String expected = "CN=Group1"; + String actual = search.findGroupDn("grp1"); + assertEquals(expected, actual); + } + + @Test + public void testIsUserMemberOfGroupWhenUserId() throws NamingException { + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN, + "CN=%s,OU=org1,DC=foo,DC=bar"); + NamingEnumeration validResult = LdapTestUtils.mockNamingEnumeration("CN=User1"); + NamingEnumeration emptyResult = LdapTestUtils.mockEmptyNamingEnumeration(); + when(ctx.search(anyString(), contains("(uid=usr1)"), any(SearchControls.class))) + .thenReturn(validResult); + when(ctx.search(anyString(), contains("(uid=usr2)"), any(SearchControls.class))) + .thenReturn(emptyResult); + search = new LdapSearch(conf, ctx); + assertTrue(search.isUserMemberOfGroup("usr1", "grp1")); + assertFalse(search.isUserMemberOfGroup("usr2", "grp2")); + } + + @Test + public void testIsUserMemberOfGroupWhenUserDn() throws NamingException { + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN, + "CN=%s,OU=org1,DC=foo,DC=bar"); + NamingEnumeration validResult = LdapTestUtils.mockNamingEnumeration("CN=User1"); + NamingEnumeration emptyResult = LdapTestUtils.mockEmptyNamingEnumeration(); + when(ctx.search(anyString(), contains("(uid=User1)"), any(SearchControls.class))) + .thenReturn(validResult); + when(ctx.search(anyString(), contains("(uid=User2)"), any(SearchControls.class))) + .thenReturn(emptyResult); + search = new LdapSearch(conf, ctx); + assertTrue(search.isUserMemberOfGroup("CN=User1,OU=org1,DC=foo,DC=bar", "grp1")); + assertFalse(search.isUserMemberOfGroup("CN=User2,OU=org1,DC=foo,DC=bar", "grp2")); + } } diff --git service/src/test/org/apache/hive/service/auth/ldap/TestQueryFactory.java service/src/test/org/apache/hive/service/auth/ldap/TestQueryFactory.java index 3054e33..582ca35 100644 --- service/src/test/org/apache/hive/service/auth/ldap/TestQueryFactory.java +++ service/src/test/org/apache/hive/service/auth/ldap/TestQueryFactory.java @@ -34,6 +34,7 @@ public void setup() { conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GUIDKEY, "guid"); conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPCLASS_KEY, "superGroups"); conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPMEMBERSHIP_KEY, "member"); + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERMEMBERSHIP_KEY, "partOf"); queries = new QueryFactory(conf); } @@ -76,4 +77,27 @@ public void testFindGroupsForUser() { String actual = q.getFilter(); assertEquals(expected, actual); } + + @Test + public void testIsUserMemberOfGroup() { + Query q = queries.isUserMemberOfGroup("unique_user", "cn=MyGroup,ou=Groups,dc=mycompany,dc=com"); + String expected = "(&(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))" + + "(partOf=cn=MyGroup,ou=Groups,dc=mycompany,dc=com)(guid=unique_user))"; + String actual = q.getFilter(); + assertEquals(expected, actual); + } + + @Test(expected = IllegalStateException.class) + public void testIsUserMemberOfGroupWhenMisconfigured() { + QueryFactory misconfiguredQueryFactory = new QueryFactory(new HiveConf()); + misconfiguredQueryFactory.isUserMemberOfGroup("user", "cn=MyGroup"); + } + + @Test + public void testFindGroupDNByID() { + Query q = queries.findGroupDnById("unique_group_id"); + String expected = "(&(objectClass=superGroups)(guid=unique_group_id))"; + String actual = q.getFilter(); + assertEquals(expected, actual); + } } diff --git service/src/test/resources/ldap/ad.example.com.ldif service/src/test/resources/ldap/ad.example.com.ldif new file mode 100644 index 0000000..6e0f20f --- /dev/null +++ service/src/test/resources/ldap/ad.example.com.ldif @@ -0,0 +1,133 @@ +dn: dc=ad,dc=example,dc=com +dc: ad +objectClass: top +objectClass: domain + +dn: ou=Engineering,dc=ad,dc=example,dc=com +objectClass: top +objectClass: organizationalUnit +ou: Engineering + +dn: ou=Management,dc=ad,dc=example,dc=com +objectClass: top +objectClass: organizationalUnit +ou: Management + +dn: ou=Administration,dc=ad,dc=example,dc=com +objectClass: top +objectClass: organizationalUnit +ou: Administration + +dn: ou=Teams,dc=ad,dc=example,dc=com +objectClass: top +objectClass: organizationalUnit +ou: Teams + +dn: ou=Resources,dc=ad,dc=example,dc=com +objectClass: top +objectClass: organizationalUnit +ou: Resources + +dn: cn=Team 1,ou=Teams,dc=ad,dc=example,dc=com +objectClass: top +objectClass: groupOfNames +objectClass: microsoftSecurityPrincipal +sAMAccountName: team1 +cn: Team 1 +member: sAMAccountName=engineer1,ou=Engineering,dc=ad,dc=example,dc=com +member: sAMAccountName=manager1,ou=Management,dc=ad,dc=example,dc=com + +dn: cn=Team 2,ou=Teams,dc=ad,dc=example,dc=com +objectClass: top +objectClass: groupOfNames +objectClass: microsoftSecurityPrincipal +sAMAccountName: team2 +cn: Team 2 +member: sAMAccountName=engineer2,ou=Engineering,dc=ad,dc=example,dc=com +member: sAMAccountName=manager2,ou=Management,dc=ad,dc=example,dc=com + +dn: cn=Resource 1,ou=Resources,dc=ad,dc=example,dc=com +objectClass: top +objectClass: groupOfNames +objectClass: microsoftSecurityPrincipal +sAMAccountName: resource1 +cn: Resource 1 +member: sAMAccountName=engineer1,ou=Engineering,dc=ad,dc=example,dc=com + +dn: cn=Resource 2,ou=Resources,dc=ad,dc=example,dc=com +objectClass: top +objectClass: groupOfNames +objectClass: microsoftSecurityPrincipal +sAMAccountName: resource2 +cn: Resource 2 +member: sAMAccountName=engineer2,ou=Engineering,dc=ad,dc=example,dc=com + +dn: cn=Admins,ou=Administration,dc=ad,dc=example,dc=com +objectClass: top +objectClass: groupOfUniqueNames +objectClass: microsoftSecurityPrincipal +sAMAccountName: admins +cn: Admins +uniqueMember: sAMAccountName=admin1,ou=Administration,dc=ad,dc=example,dc=com + +dn: sAMAccountName=engineer1,ou=Engineering,dc=ad,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson +objectClass: microsoftSecurityPrincipal +sAMAccountName: engineer1 +cn: Engineer 1 +sn: Surname 1 +userPassword: engineer1-password +memberOf: cn=Team 1,ou=Teams,dc=ad,dc=example,dc=com +memberOf: cn=Resource 1,ou=Resources,dc=ad,dc=example,dc=com + +dn: sAMAccountName=engineer2,ou=Engineering,dc=ad,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson +objectClass: microsoftSecurityPrincipal +sAMAccountName: engineer2 +cn: Engineer 2 +sn: Surname 2 +userPassword: engineer2-password +memberOf: cn=Team 2,ou=Teams,dc=ad,dc=example,dc=com +memberOf: cn=Resource 2,ou=Resources,dc=ad,dc=example,dc=com + +dn: sAMAccountName=manager1,ou=Management,dc=ad,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson +objectClass: microsoftSecurityPrincipal +sAMAccountName: manager1 +cn: Manager 1 +sn: Surname 1 +userPassword: manager1-password +memberOf: cn=Team 1,ou=Teams,dc=ad,dc=example,dc=com + +dn: sAMAccountName=manager2,ou=Management,dc=ad,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson +objectClass: microsoftSecurityPrincipal +sAMAccountName: manager2 +cn: Manager 2 +sn: Surname 2 +userPassword: manager2-password +memberOf: cn=Team 2,ou=Teams,dc=ad,dc=example,dc=com + +dn: sAMAccountName=admin1,ou=Administration,dc=ad,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson +objectClass: microsoftSecurityPrincipal +sAMAccountName: admin1 +cn: Admin 1 +sn: Surname 1 +userPassword: admin1-password +memberOf: cn=Admins,ou=Administration,dc=ad,dc=example,dc=com diff --git service/src/test/resources/ldap/microsoft.schema.ldif service/src/test/resources/ldap/microsoft.schema.ldif new file mode 100644 index 0000000..d9a6d94 --- /dev/null +++ service/src/test/resources/ldap/microsoft.schema.ldif @@ -0,0 +1,45 @@ +dn: cn=microsoft, ou=schema +objectclass: metaSchema +objectclass: top +cn: microsoft + +dn: ou=attributetypes, cn=microsoft, ou=schema +objectclass: organizationalUnit +objectclass: top +ou: attributetypes + +dn: m-oid=1.2.840.113556.1.4.221, ou=attributetypes, cn=microsoft, ou=schema +objectclass: metaAttributeType +objectclass: metaTop +objectclass: top +m-oid: 1.2.840.113556.1.4.221 +m-name: sAMAccountName +m-equality: caseIgnoreMatch +m-syntax: 1.3.6.1.4.1.1466.115.121.1.15 +m-singleValue: TRUE + +dn: m-oid=1.2.840.113556.1.4.222, ou=attributetypes, cn=microsoft, ou=schema +objectclass: metaAttributeType +objectclass: metaTop +objectclass: top +m-oid: 1.2.840.113556.1.4.222 +m-name: memberOf +m-equality: caseIgnoreMatch +m-syntax: 1.3.6.1.4.1.1466.115.121.1.15 +m-singleValue: FALSE + +dn: ou=objectClasses, cn=microsoft, ou=schema +objectclass: organizationalUnit +objectclass: top +ou: objectClasses + +dn: m-oid=1.2.840.113556.1.5.6, ou=objectClasses, cn=microsoft, ou=schema +objectclass: metaObjectClass +objectclass: metaTop +objectclass: top +m-oid: 1.2.840.113556.1.5.6 +m-name: microsoftSecurityPrincipal +m-supObjectClass: top +m-typeObjectClass: AUXILIARY +m-must: sAMAccountName +m-may: memberOf