You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hive.apache.org by ai...@apache.org on 2017/01/09 20:39:32 UTC

hive git commit: HIVE-15076: Improve scalability of LDAP authentication provider group filter (Illya Yalovyy, reviewed by Aihua Xu, Naveen Gangam)

Repository: hive
Updated Branches:
  refs/heads/master 6fdf49ac2 -> 01e691c5c


HIVE-15076: Improve scalability of LDAP authentication provider group filter (Illya Yalovyy, reviewed by Aihua Xu, Naveen Gangam)


Project: http://git-wip-us.apache.org/repos/asf/hive/repo
Commit: http://git-wip-us.apache.org/repos/asf/hive/commit/01e691c5
Tree: http://git-wip-us.apache.org/repos/asf/hive/tree/01e691c5
Diff: http://git-wip-us.apache.org/repos/asf/hive/diff/01e691c5

Branch: refs/heads/master
Commit: 01e691c5c545523f1ba88cafe68b2460ad0a4ab0
Parents: 6fdf49a
Author: Aihua Xu <ai...@apache.org>
Authored: Mon Jan 9 15:38:23 2017 -0500
Committer: Aihua Xu <ai...@apache.org>
Committed: Mon Jan 9 15:38:23 2017 -0500

----------------------------------------------------------------------
 .../org/apache/hadoop/hive/conf/HiveConf.java   |  11 +-
 .../hive/service/auth/ldap/DirSearch.java       |  17 +++
 .../service/auth/ldap/GroupFilterFactory.java   |  76 +++++++++-
 .../hive/service/auth/ldap/LdapSearch.java      |  17 +++
 .../apache/hive/service/auth/ldap/Query.java    |  11 ++
 .../hive/service/auth/ldap/QueryFactory.java    |  42 +++++-
 .../auth/TestLdapAtnProviderWithMiniDS.java     | 147 ++++++++++++++++++-
 .../TestLdapAuthenticationProviderImpl.java     |  74 +++++++++-
 .../auth/ldap/LdapAuthenticationTestCase.java   |  10 +-
 .../hive/service/auth/ldap/TestGroupFilter.java |  68 ++++++++-
 .../hive/service/auth/ldap/TestLdapSearch.java  |  84 +++++++++++
 .../service/auth/ldap/TestQueryFactory.java     |  24 +++
 .../src/test/resources/ldap/ad.example.com.ldif | 133 +++++++++++++++++
 .../test/resources/ldap/microsoft.schema.ldif   |  45 ++++++
 14 files changed, 736 insertions(+), 23 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/hive/blob/01e691c5/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
----------------------------------------------------------------------
diff --git a/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java b/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
index 47db0c0..16f6c1c 100644
--- a/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
+++ b/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
@@ -320,7 +320,8 @@ public class HiveConf extends Configuration {
   }
 
   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
@@ -2519,8 +2520,14 @@ public class HiveConf extends Configuration {
         "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."),

http://git-wip-us.apache.org/repos/asf/hive/blob/01e691c5/service/src/java/org/apache/hive/service/auth/ldap/DirSearch.java
----------------------------------------------------------------------
diff --git a/service/src/java/org/apache/hive/service/auth/ldap/DirSearch.java b/service/src/java/org/apache/hive/service/auth/ldap/DirSearch.java
index 33b6088..f7020bc 100644
--- a/service/src/java/org/apache/hive/service/auth/ldap/DirSearch.java
+++ b/service/src/java/org/apache/hive/service/auth/ldap/DirSearch.java
@@ -35,6 +35,23 @@ public interface DirSearch extends Closeable {
   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

http://git-wip-us.apache.org/repos/asf/hive/blob/01e691c5/service/src/java/org/apache/hive/service/auth/ldap/GroupFilterFactory.java
----------------------------------------------------------------------
diff --git a/service/src/java/org/apache/hive/service/auth/ldap/GroupFilterFactory.java b/service/src/java/org/apache/hive/service/auth/ldap/GroupFilterFactory.java
index 152c4b2..e0f4518 100644
--- a/service/src/java/org/apache/hive/service/auth/ldap/GroupFilterFactory.java
+++ b/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 final class GroupFilterFactory implements FilterFactory {
       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<String> groupFilter = new HashSet<>();
 
-    GroupFilter(Collection<String> groupFilter) {
+    GroupMembershipKeyFilter(Collection<String> 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<String> memberOf = null;
 
@@ -78,6 +87,8 @@ public final class GroupFilterFactory implements FilterFactory {
       for (String groupDn : memberOf) {
         String shortName = LdapUtils.getShortName(groupDn);
         if (groupFilter.contains(shortName)) {
+          LOG.debug("GroupMembershipKeyFilter passes: user '{}' is a member of '{}' group",
+              user, groupDn);
           LOG.info("Authentication succeeded based on group membership");
           return;
         }
@@ -87,4 +98,59 @@ public final class GroupFilterFactory implements FilterFactory {
           + "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<String> groupFilter;
+
+    UserMembershipKeyFilter(Collection<String> groupFilter) {
+      this.groupFilter = groupFilter;
+    }
+
+    @Override
+    public void apply(DirSearch ldap, String user) throws AuthenticationException {
+      LOG.info("Authenticating user '{}' using {}", user,
+          UserMembershipKeyFilter.class.getSimpleName());
+
+      List<String> groupDns = new ArrayList<>();
+      for (String groupId : groupFilter) {
+        try {
+          String groupDn = ldap.findGroupDn(groupId);
+          groupDns.add(groupDn);
+        } catch (NamingException e) {
+          LOG.warn("Cannot find DN for group", 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);
+            LOG.info("Authentication succeeded based on user membership");
+            return;
+          }
+        } catch (NamingException e) {
+          LOG.warn("Cannot match user and group", 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));
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/hive/blob/01e691c5/service/src/java/org/apache/hive/service/auth/ldap/LdapSearch.java
----------------------------------------------------------------------
diff --git a/service/src/java/org/apache/hive/service/auth/ldap/LdapSearch.java b/service/src/java/org/apache/hive/service/auth/ldap/LdapSearch.java
index 65076ea..be512c0 100644
--- a/service/src/java/org/apache/hive/service/auth/ldap/LdapSearch.java
+++ b/service/src/java/org/apache/hive/service/auth/ldap/LdapSearch.java
@@ -121,6 +121,23 @@ public final class LdapSearch implements DirSearch {
    * {@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<String> findGroupsForUser(String userDn) throws NamingException {
     String userName = LdapUtils.extractUserName(userDn);
     return execute(groupBases, queries.findGroupsForUser(userName, userDn)).getAllLdapNames();

http://git-wip-us.apache.org/repos/asf/hive/blob/01e691c5/service/src/java/org/apache/hive/service/auth/ldap/Query.java
----------------------------------------------------------------------
diff --git a/service/src/java/org/apache/hive/service/auth/ldap/Query.java b/service/src/java/org/apache/hive/service/auth/ldap/Query.java
index b8bf938..194f8aa 100644
--- a/service/src/java/org/apache/hive/service/auth/ldap/Query.java
+++ b/service/src/java/org/apache/hive/service/auth/ldap/Query.java
@@ -103,6 +103,17 @@ public final class Query {
     }
 
     /**
+     * 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

http://git-wip-us.apache.org/repos/asf/hive/blob/01e691c5/service/src/java/org/apache/hive/service/auth/ldap/QueryFactory.java
----------------------------------------------------------------------
diff --git a/service/src/java/org/apache/hive/service/auth/ldap/QueryFactory.java b/service/src/java/org/apache/hive/service/auth/ldap/QueryFactory.java
index e9172d3..6ce01c0 100644
--- a/service/src/java/org/apache/hive/service/auth/ldap/QueryFactory.java
+++ b/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 final class QueryFactory {
     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 final class QueryFactory {
    */
   public Query findUserDnByRdn(String userRdn) {
     return Query.builder()
-        .filter("(&(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))"
+        .filter("(&(|<classes:{ class |(objectClass=<class>)}>)"
             + "(<userRdn>))")
         .limit(2)
+        .map("classes", USER_OBJECT_CLASSES)
         .map("userRdn", userRdn)
         .build();
   }
@@ -93,8 +100,9 @@ public final class QueryFactory {
    */
   public Query findUserDnByName(String userName) {
     return Query.builder()
-        .filter("(&(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))"
+        .filter("(&(|<classes:{ class |(objectClass=<class>)}>)"
             + "(|(uid=<userName>)(sAMAccountName=<userName>)))")
+        .map("classes", USER_OBJECT_CLASSES)
         .map("userName", userName)
         .limit(2)
         .build();
@@ -118,6 +126,34 @@ public final class QueryFactory {
   }
 
   /**
+   * 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("(&(|<classes:{ class |(objectClass=<class>)}>)" +
+            "(<userMembershipAttr>=<groupDn>)(<guidAttr>=<userId>))")
+        .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.
    * <br>
    * This query is configured to return a group membership attribute as part of the search result.

http://git-wip-us.apache.org/repos/asf/hive/blob/01e691c5/service/src/test/org/apache/hive/service/auth/TestLdapAtnProviderWithMiniDS.java
----------------------------------------------------------------------
diff --git a/service/src/test/org/apache/hive/service/auth/TestLdapAtnProviderWithMiniDS.java b/service/src/test/org/apache/hive/service/auth/TestLdapAtnProviderWithMiniDS.java
index cd62935..d6d67a5 100644
--- a/service/src/test/org/apache/hive/service/auth/TestLdapAtnProviderWithMiniDS.java
+++ b/service/src/test/org/apache/hive/service/auth/TestLdapAtnProviderWithMiniDS.java
@@ -67,7 +67,11 @@ import static org.junit.Assert.assertTrue;
   )
 })
 
-@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 @@ public class TestLdapAtnProviderWithMiniDS extends AbstractLdapTestUnit {
   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 @@ public class TestLdapAtnProviderWithMiniDS extends AbstractLdapTestUnit {
       .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 class TestLdapAtnProviderWithMiniDS extends AbstractLdapTestUnit {
         .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 class TestLdapAtnProviderWithMiniDS extends AbstractLdapTestUnit {
         .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());
+  }
 }

http://git-wip-us.apache.org/repos/asf/hive/blob/01e691c5/service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java
----------------------------------------------------------------------
diff --git a/service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java b/service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java
index 4fad755..6fd218c 100644
--- a/service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java
+++ b/service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java
@@ -154,7 +154,7 @@ public class TestLdapAuthenticationProviderImpl {
   }
 
   @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 class TestLdapAuthenticationProviderImpl {
   }
 
   @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 class TestLdapAuthenticationProviderImpl {
   }
 
   @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 class TestLdapAuthenticationProviderImpl {
   }
 
   @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 class TestLdapAuthenticationProviderImpl {
     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");

http://git-wip-us.apache.org/repos/asf/hive/blob/01e691c5/service/src/test/org/apache/hive/service/auth/ldap/LdapAuthenticationTestCase.java
----------------------------------------------------------------------
diff --git a/service/src/test/org/apache/hive/service/auth/ldap/LdapAuthenticationTestCase.java b/service/src/test/org/apache/hive/service/auth/ldap/LdapAuthenticationTestCase.java
index acde8c1..f4ad6ed 100644
--- a/service/src/test/org/apache/hive/service/auth/ldap/LdapAuthenticationTestCase.java
+++ b/service/src/test/org/apache/hive/service/auth/ldap/LdapAuthenticationTestCase.java
@@ -113,8 +113,14 @@ public final class LdapAuthenticationTestCase {
       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) {

http://git-wip-us.apache.org/repos/asf/hive/blob/01e691c5/service/src/test/org/apache/hive/service/auth/ldap/TestGroupFilter.java
----------------------------------------------------------------------
diff --git a/service/src/test/org/apache/hive/service/auth/ldap/TestGroupFilter.java b/service/src/test/org/apache/hive/service/auth/ldap/TestGroupFilter.java
index 0cc2ead..d5da76c 100644
--- a/service/src/test/org/apache/hive/service/auth/ldap/TestGroupFilter.java
+++ b/service/src/test/org/apache/hive/service/auth/ldap/TestGroupFilter.java
@@ -29,6 +29,7 @@ import org.mockito.runners.MockitoJUnitRunner;
 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 class TestGroupFilter {
   }
 
   @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 class TestGroupFilter {
   }
 
   @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 class TestGroupFilter {
     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");
+  }
 }

http://git-wip-us.apache.org/repos/asf/hive/blob/01e691c5/service/src/test/org/apache/hive/service/auth/ldap/TestLdapSearch.java
----------------------------------------------------------------------
diff --git a/service/src/test/org/apache/hive/service/auth/ldap/TestLdapSearch.java b/service/src/test/org/apache/hive/service/auth/ldap/TestLdapSearch.java
index 499b624..7c7b393 100644
--- a/service/src/test/org/apache/hive/service/auth/ldap/TestLdapSearch.java
+++ b/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 @@ public class TestLdapSearch {
   @Before
   public void setup() {
     conf = new HiveConf();
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERMEMBERSHIP_KEY, "memberOf");
   }
 
   @Test
@@ -206,4 +208,86 @@ public class TestLdapSearch {
     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<SearchResult> 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<SearchResult> 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<SearchResult> 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<SearchResult> 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<SearchResult> validResult = LdapTestUtils.mockNamingEnumeration("CN=User1");
+    NamingEnumeration<SearchResult> 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<SearchResult> validResult = LdapTestUtils.mockNamingEnumeration("CN=User1");
+    NamingEnumeration<SearchResult> 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"));
+  }
 }

http://git-wip-us.apache.org/repos/asf/hive/blob/01e691c5/service/src/test/org/apache/hive/service/auth/ldap/TestQueryFactory.java
----------------------------------------------------------------------
diff --git a/service/src/test/org/apache/hive/service/auth/ldap/TestQueryFactory.java b/service/src/test/org/apache/hive/service/auth/ldap/TestQueryFactory.java
index 3054e33..582ca35 100644
--- a/service/src/test/org/apache/hive/service/auth/ldap/TestQueryFactory.java
+++ b/service/src/test/org/apache/hive/service/auth/ldap/TestQueryFactory.java
@@ -34,6 +34,7 @@ public class TestQueryFactory {
     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 class TestQueryFactory {
     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);
+  }
 }

http://git-wip-us.apache.org/repos/asf/hive/blob/01e691c5/service/src/test/resources/ldap/ad.example.com.ldif
----------------------------------------------------------------------
diff --git a/service/src/test/resources/ldap/ad.example.com.ldif b/service/src/test/resources/ldap/ad.example.com.ldif
new file mode 100644
index 0000000..6e0f20f
--- /dev/null
+++ b/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

http://git-wip-us.apache.org/repos/asf/hive/blob/01e691c5/service/src/test/resources/ldap/microsoft.schema.ldif
----------------------------------------------------------------------
diff --git a/service/src/test/resources/ldap/microsoft.schema.ldif b/service/src/test/resources/ldap/microsoft.schema.ldif
new file mode 100644
index 0000000..d9a6d94
--- /dev/null
+++ b/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