You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hive.apache.org by gs...@apache.org on 2023/05/12 17:14:40 UTC

[hive] branch master updated: HIVE-27311: Support generic search bind filter auth with LDAP (#4284) (Naveen Gangam, Reviewed by Henri Biestro, Sai Hemanth G, Zhihua Deng)

This is an automated email from the ASF dual-hosted git repository.

gsaihemanth pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hive.git


The following commit(s) were added to refs/heads/master by this push:
     new dd3fd775406 HIVE-27311: Support generic search bind filter auth with LDAP (#4284) (Naveen Gangam, Reviewed by Henri Biestro, Sai Hemanth G, Zhihua Deng)
dd3fd775406 is described below

commit dd3fd775406c2d621c42ef1adce2fa0f3b9acd39
Author: Naveen Gangam <ng...@cloudera.com>
AuthorDate: Fri May 12 13:14:27 2023 -0400

    HIVE-27311: Support generic search bind filter auth with LDAP (#4284) (Naveen Gangam, Reviewed by Henri Biestro, Sai Hemanth G, Zhihua Deng)
---
 .../java/org/apache/hadoop/hive/conf/HiveConf.java |  10 ++
 .../auth/LdapAuthenticationProviderImpl.java       |   4 +-
 .../apache/hive/service/auth/ldap/DirSearch.java   |  21 ++++
 .../apache/hive/service/auth/ldap/LdapSearch.java  |  32 ++++++
 .../hive/service/auth/ldap/QueryFactory.java       |  32 ++++++
 .../auth/ldap/UserGroupSearchFilterFactory.java    | 105 ++++++++++++++++++++
 .../auth/TestLdapAtnProviderWithMiniDS.java        |  62 ++++++++++++
 .../auth/ldap/LdapAuthenticationTestCase.java      |  15 +++
 .../MetaStoreLdapAuthenticationProviderImpl.java   |   2 +
 .../hadoop/hive/metastore/conf/MetastoreConf.java  |  13 +++
 .../hadoop/hive/metastore/ldap/DirSearch.java      |  21 ++++
 .../hadoop/hive/metastore/ldap/LdapSearch.java     |  31 ++++++
 .../hadoop/hive/metastore/ldap/QueryFactory.java   |  32 ++++++
 .../ldap/UserGroupSearchFilterFactory.java         | 108 +++++++++++++++++++++
 .../metastore/TestLdapAtnProviderWithMiniDS.java   |  63 ++++++++++++
 .../metastore/ldap/LdapAuthenticationTestCase.java |  15 +++
 16 files changed, 565 insertions(+), 1 deletion(-)

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 a1bae8b0d34..676b2fe417e 100644
--- a/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
+++ b/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
@@ -4252,6 +4252,16 @@ public class HiveConf extends Configuration {
         "For example: (&(objectClass=group)(objectClass=top)(instanceType=4)(cn=Domain*)) \n" +
         "(&(objectClass=person)(|(sAMAccountName=admin)(|(memberOf=CN=Domain Admins,CN=Users,DC=domain,DC=com)" +
         "(memberOf=CN=Administrators,CN=Builtin,DC=domain,DC=com))))"),
+    HIVE_SERVER2_PLAIN_LDAP_USERSEARCHFILTER("hive.server2.authentication.ldap.userSearchFilter", null,
+        "User search filter to be used with baseDN to search for users\n" +
+            "For example: (&(uid={0})(objectClass=person))"),
+    HIVE_SERVER2_PLAIN_LDAP_GROUPBASEDN("hive.server2.authentication.ldap.groupBaseDN", null,
+        "BaseDN for Group Search. This is used in conjunction with hive.server2.authentication.ldap.baseDN\n" +
+            "and \n" +
+            "request, succeeds if the group is part of the resultset."),
+    HIVE_SERVER2_PLAIN_LDAP_GROUPSEARCHFILTER("hive.server2.authentication.ldap.groupSearchFilter", null,
+        "Group search filter to be used with baseDN, userSearchFilter, groupBaseDN to search for users in groups\n" +
+            "For example: (&(|(memberUid={0})(memberUid={1}))(objectClass=posixGroup))\n"),
     HIVE_SERVER2_PLAIN_LDAP_BIND_USER("hive.server2.authentication.ldap.binddn", null,
         "The user with which to bind to the LDAP server, and search for the full domain name " +
         "of the user being authenticated.\n" +
diff --git a/service/src/java/org/apache/hive/service/auth/LdapAuthenticationProviderImpl.java b/service/src/java/org/apache/hive/service/auth/LdapAuthenticationProviderImpl.java
index 0f318fb5d85..31351b1da4a 100644
--- a/service/src/java/org/apache/hive/service/auth/LdapAuthenticationProviderImpl.java
+++ b/service/src/java/org/apache/hive/service/auth/LdapAuthenticationProviderImpl.java
@@ -29,14 +29,15 @@ import org.apache.hadoop.hive.conf.HiveConf;
 import org.apache.hive.service.ServiceUtils;
 import org.apache.hive.service.auth.ldap.ChainFilterFactory;
 import org.apache.hive.service.auth.ldap.CustomQueryFilterFactory;
-import org.apache.hive.service.auth.ldap.LdapSearchFactory;
 import org.apache.hive.service.auth.ldap.Filter;
 import org.apache.hive.service.auth.ldap.DirSearch;
 import org.apache.hive.service.auth.ldap.DirSearchFactory;
 import org.apache.hive.service.auth.ldap.FilterFactory;
 import org.apache.hive.service.auth.ldap.GroupFilterFactory;
+import org.apache.hive.service.auth.ldap.LdapSearchFactory;
 import org.apache.hive.service.auth.ldap.LdapUtils;
 import org.apache.hive.service.auth.ldap.UserFilterFactory;
+import org.apache.hive.service.auth.ldap.UserGroupSearchFilterFactory;
 import org.apache.hive.service.auth.ldap.UserSearchFilterFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -46,6 +47,7 @@ public class LdapAuthenticationProviderImpl implements PasswdAuthenticationProvi
   private static final Logger LOG = LoggerFactory.getLogger(LdapAuthenticationProviderImpl.class);
 
   private static final List<FilterFactory> FILTER_FACTORIES = ImmutableList.<FilterFactory>of(
+      new UserGroupSearchFilterFactory(),
       new CustomQueryFilterFactory(),
       new ChainFilterFactory(new UserSearchFilterFactory(), new UserFilterFactory(),
           new GroupFilterFactory())
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 da8cc66b7e6..3a79b473808 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
@@ -34,6 +34,16 @@ public interface DirSearch extends Closeable {
    */
   String findUserDn(String user) throws NamingException;
 
+  /**
+   * Finds user's distinguished name.
+   * @param user username
+   * @param userSearchFilter Generic LDAP Search filter for ex: (&amp;(uid={0})(objectClass=person))
+   * @param baseDn LDAP BaseDN for user searches for ex: dc=apache,dc=org
+   * @return DN for the specific user if exists, null otherwise
+   * @throws NamingException
+   */
+  String findUserDn(String user, String userSearchFilter, String baseDn) throws NamingException;
+
   /**
    * Finds group's distinguished name.
    * @param group group name or unique identifier
@@ -66,4 +76,15 @@ public interface DirSearch extends Closeable {
    * @throws NamingException
    */
   List<String> executeCustomQuery(String query) throws NamingException;
+
+  /**
+   * Executes an arbitrary query.
+   * @param user user RDN or username. This will be substituted for {0} in group search
+   * @param userDn userDn DN for the username. This will be substituted for {1} in group search
+   * @param filter filter is the group filter query ex: (&amp;(memberUid={0})(&amp;(CN=group1)(objectClass=posixGroup)))
+   * @param groupBaseDn BaseDN for group searches. ex: "ou=groups,dc=apache,dc=org"
+   * @return list of names that match the group filter aka groups that the user belongs to, if any.
+   * @throws NamingException
+   */
+  List<String> executeUserAndGroupFilterQuery(String user, String userDn, String filter, String groupBaseDn) throws NamingException;
 }
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 9fe4a7cd51d..2b5ca940cfc 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
@@ -24,6 +24,7 @@ import java.util.List;
 import javax.naming.NamingEnumeration;
 import javax.naming.NamingException;
 import javax.naming.directory.DirContext;
+import javax.naming.directory.SearchControls;
 import javax.naming.directory.SearchResult;
 import org.apache.hadoop.hive.conf.HiveConf;
 import org.slf4j.Logger;
@@ -104,6 +105,28 @@ public final class LdapSearch implements DirSearch {
     }
   }
 
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String findUserDn(String user, final String userSearchFilter, final String baseDn) throws NamingException {
+    List<String> allLdapNames;
+    String userRdn = user;
+    if (LdapUtils.isDn(user)) {
+      userRdn = LdapUtils.extractUserName(user);
+    }
+    Query query = queries.findUserDnBySearch(userRdn, userSearchFilter);
+    allLdapNames = execute(Collections.singletonList(baseDn), query).getAllLdapNames();
+
+    if (allLdapNames.size() == 1) {
+      return allLdapNames.get(0);
+    } else {
+      LOG.info("Expected exactly one userSearchFilter result for the userSearchFilter: {}, but got {}. Returning null",
+          userSearchFilter, allLdapNames.size());
+      return null;
+    }
+  }
+
   private List<String> findDnByPattern(List<String> patterns, String name) throws NamingException {
     for (String pattern : patterns) {
       String baseDnFromPattern = LdapUtils.extractBaseDn(pattern);
@@ -152,6 +175,15 @@ public final class LdapSearch implements DirSearch {
         .getAllLdapNamesAndAttributes();
   }
 
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public List<String> executeUserAndGroupFilterQuery(String user, String userDn, String groupSearchFilter, String groupBaseDn) throws NamingException {
+    Query query = queries.findDnByUserAndGroupSearch(user, userDn, groupSearchFilter);
+    return execute(Collections.singletonList(groupBaseDn), query).getAllLdapNames();
+  }
+
   private SearchResultHandler execute(Collection<String> baseDns, Query query) {
     List<NamingEnumeration<SearchResult>> searchResults = new ArrayList<>();
     LOG.debug("Executing a query: '{}' with base DNs {}.", query.getFilter(), baseDns);
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 e06f1129da0..6bcbf92fe57 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
@@ -108,6 +108,38 @@ final class QueryFactory {
         .build();
   }
 
+  /**
+   * Returns a query for finding user DN based on user RDN.
+   * @param user user RDN that will replace {0} in the search filter
+   * @param userSearchFilter search query that user filter conditions.
+   * @return an instance of {@link Query}
+   */
+  public Query findUserDnBySearch(String user, String userSearchFilter) {
+    if (userSearchFilter != null) {
+      userSearchFilter = userSearchFilter.replaceAll("\\{0\\}", user);
+    }
+    return Query.builder()
+        .filter(userSearchFilter)
+        .build();
+  }
+
+  /**
+   * Returns a query for finding matches based on user and group filter query.
+   * @param user user RDN to replace {0}
+   * @param userDn user DN to replace {1}}
+   * @param groupSearchFilter search query that includes any group filter conditions.
+   * @return an instance of {@link Query}
+   */
+  public Query findDnByUserAndGroupSearch(String user, String userDn, String groupSearchFilter) {
+    if (groupSearchFilter != null) {
+      groupSearchFilter = groupSearchFilter.replaceAll("\\{0\\}", userDn);
+      groupSearchFilter = groupSearchFilter.replaceAll("\\{1\\}", user);
+    }
+    return Query.builder()
+        .filter(groupSearchFilter)
+        .build();
+  }
+
   /**
    * Returns a query for finding groups to which the user belongs.
    * @param userName username
diff --git a/service/src/java/org/apache/hive/service/auth/ldap/UserGroupSearchFilterFactory.java b/service/src/java/org/apache/hive/service/auth/ldap/UserGroupSearchFilterFactory.java
new file mode 100644
index 00000000000..30a9bc64874
--- /dev/null
+++ b/service/src/java/org/apache/hive/service/auth/ldap/UserGroupSearchFilterFactory.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hive.service.auth.ldap;
+
+import com.google.common.base.Strings;
+
+import java.util.List;
+import javax.naming.NamingException;
+import javax.security.sasl.AuthenticationException;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A factory for a {@link Filter} based on user and group search filters.
+ * <br>
+ * The produced filter object filters out all users that are not found in the search result
+ * of the query provided in Hive configuration.
+ * Atleast one search criteria is REQUIRED.
+ * Configuration could have Usersearch filter or Groupsearch filter or both.
+ * @see HiveConf.ConfVars#HIVE_SERVER2_PLAIN_LDAP_USERSEARCHFILTER
+ * @see HiveConf.ConfVars#HIVE_SERVER2_PLAIN_LDAP_BASEDN
+ * @see HiveConf.ConfVars#HIVE_SERVER2_PLAIN_LDAP_GROUPSEARCHFILTER
+ * @see HiveConf.ConfVars#HIVE_SERVER2_PLAIN_LDAP_GROUPBASEDN
+ */
+public class UserGroupSearchFilterFactory implements FilterFactory {
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Filter getInstance(HiveConf conf) {
+    String userSearchFilter = conf.get(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERSEARCHFILTER.varname);
+    String userSearchBaseDN = conf.get(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BASEDN.varname);
+    String groupSearchFilter = conf.get(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPSEARCHFILTER.varname);
+    String groupSearchBaseDN = conf.get(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPBASEDN.varname);
+
+    // Both UserSearch and GroupSearch cannot be null or empty.
+    if (Strings.isNullOrEmpty(userSearchFilter) &&
+        (Strings.isNullOrEmpty(groupSearchFilter) && Strings.isNullOrEmpty(groupSearchBaseDN))) {
+      return null;
+    }
+    return new UserGroupSearchFilter(userSearchFilter, userSearchBaseDN, groupSearchFilter, groupSearchBaseDN);
+  }
+
+  private static final class UserGroupSearchFilter implements Filter {
+
+    private static final Logger LOG = LoggerFactory.getLogger(UserGroupSearchFilter.class);
+
+    private final String userSearchFilter;
+    private final String userBaseDN;
+    private final String groupSearchFilter;
+    private final String groupBaseDN;
+
+    UserGroupSearchFilter(String userSearchFilter, String userBaseDN, String groupSearchFilter, String groupBaseDN) {
+      this.userSearchFilter = userSearchFilter;
+      this.userBaseDN = userBaseDN;
+      this.groupSearchFilter = groupSearchFilter;
+      this.groupBaseDN = groupBaseDN;
+    }
+
+    @Override public void apply(DirSearch client, String user) throws AuthenticationException {
+      String userDn = null;
+      List<String> resultList;
+      try {
+        if (!Strings.isNullOrEmpty(userSearchFilter) && !Strings.isNullOrEmpty(userBaseDN)) {
+          userDn = client.findUserDn(user, userSearchFilter, userBaseDN);
+
+          // This should not be null because we were allowed to bind with this username
+          // safe check in case we were able to bind anonymously.
+          if (userDn == null) {
+            throw new AuthenticationException("Authentication failed: User search found no matching user");
+          }
+        }
+
+        if (!Strings.isNullOrEmpty(groupSearchFilter) && !Strings.isNullOrEmpty(groupBaseDN)) {
+          resultList = client.executeUserAndGroupFilterQuery(user, userDn, groupSearchFilter, groupBaseDN);
+            if (resultList != null && resultList.size() > 0) {
+              return;
+            }
+        } else if (userDn != null) { // succeed based on user search filter only
+          return;
+        }
+        throw new AuthenticationException("Authentication failed: User search does not satisfy filter condition");
+      } catch (NamingException e) {
+        throw new AuthenticationException("LDAP Authentication failed for user", e);
+      }
+    }
+  }
+}
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 edf39128728..d72cd5604d7 100644
--- a/service/src/test/org/apache/hive/service/auth/TestLdapAtnProviderWithMiniDS.java
+++ b/service/src/test/org/apache/hive/service/auth/TestLdapAtnProviderWithMiniDS.java
@@ -682,4 +682,66 @@ public class TestLdapAtnProviderWithMiniDS extends AbstractLdapTestUnit {
     testCase.assertAuthenticateFails(ENGINEER_1.credentialsWithId());
     testCase.assertAuthenticateFails(MANAGER_1.credentialsWithDn());
   }
+
+  @Test
+  public void testUserSearchFilterPositive() throws Exception {
+    testCase = defaultBuilder()
+        .baseDN("ou=People,dc=example,dc=com")
+        .userSearchFilter("(&(|(uid={0})(sAMAccountName={0}))(objectClass=person))")
+        .guidKey("uid")
+        .build();
+
+    testCase.assertAuthenticatePasses(USER1.credentialsWithId());
+    testCase.assertAuthenticatePasses(USER2.credentialsWithId());
+
+    testCase = defaultBuilder()
+        .baseDN("ou=Engineering,dc=ad,dc=example,dc=com")
+        .userSearchFilter("(&(|(uid={0})(sAMAccountName={0}))(objectClass=person))")
+        .guidKey("sAMAccountName")
+        .build();
+
+    testCase.assertAuthenticatePasses(ENGINEER_1.credentialsWithId());
+    testCase.assertAuthenticatePasses(ENGINEER_1.credentialsWithDn());
+  }
+
+  @Test
+  public void testUserSearchFilterNegative() throws Exception {
+    testCase = defaultBuilder()
+        .baseDN("ou=Engineering,dc=ad,dc=example,dc=com")
+        .userSearchFilter("(&(sAMAccountName={0})(objectClass=person)")
+        .guidKey("uid")
+        .build();
+
+    testCase.assertAuthenticateFails(USER1.credentialsWithId());
+    testCase.assertAuthenticateFails(USER3.credentialsWithId());
+  }
+
+  @Test
+  public void testUserAndGroupSearchFilterPositive() throws Exception {
+    testCase = defaultBuilder()
+        .baseDN("ou=People,dc=example,dc=com")
+        .userSearchFilter("(&(|(uid={0})(sAMAccountName={0}))(objectClass=person))")
+        .guidKey("uid")
+        .groupBaseDN("ou=Groups,dc=example,dc=com")
+        .groupSearchFilter("(&(|(member={0})(member={1}))(&(|(cn=group1)(cn=group2))(objectClass=groupOfNames)))")
+        .build();
+
+    testCase.assertAuthenticatePasses(USER1.credentialsWithId());
+    testCase.assertAuthenticatePasses(USER2.credentialsWithId());
+  }
+
+  @Test
+  public void testUserAndGroupSearchFilterNegative() throws Exception {
+    testCase = defaultBuilder()
+        .baseDN("ou=People,dc=example,dc=com")
+        .userSearchFilter("(&(|(uid={0})(sAMAccountName={0}))(objectClass=person)")
+        .guidKey("uid")
+        .groupBaseDN("ou=Groups,dc=example,dc=com")
+        .groupSearchFilter("(&(|(member={0})(member={1}))(&(cn=group1)(objectClass=groupOfNames)))")
+        .build();
+
+    testCase.assertAuthenticateFails(USER2.credentialsWithId());
+    testCase.assertAuthenticateFails(USER3.credentialsWithId());
+    testCase.assertAuthenticateFails(ENGINEER_1.credentialsWithId());
+  }
 }
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 9693e77edc4..40b99f47b90 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
@@ -123,6 +123,21 @@ public final class LdapAuthenticationTestCase {
           userMembershipKey);
     }
 
+    public Builder userSearchFilter(String userSearchFilter) {
+      return setVarOnce(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERSEARCHFILTER,
+          userSearchFilter);
+    }
+
+    public Builder groupSearchFilter(String groupSearchFilter) {
+      return setVarOnce(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPSEARCHFILTER,
+          groupSearchFilter);
+    }
+
+    public Builder groupBaseDN(String groupBaseDN) {
+      return setVarOnce(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPBASEDN,
+          groupBaseDN);
+    }
+
     private Builder setVarOnce(HiveConf.ConfVars confVar, String value) {
       Preconditions.checkState(!overrides.containsKey(confVar),
           "Property %s has been set already", confVar);
diff --git a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/MetaStoreLdapAuthenticationProviderImpl.java b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/MetaStoreLdapAuthenticationProviderImpl.java
index 51f0d1a8b3a..ba433e250f7 100644
--- a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/MetaStoreLdapAuthenticationProviderImpl.java
+++ b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/MetaStoreLdapAuthenticationProviderImpl.java
@@ -39,6 +39,7 @@ import org.apache.hadoop.hive.metastore.ldap.FilterFactory;
 import org.apache.hadoop.hive.metastore.ldap.GroupFilterFactory;
 import org.apache.hadoop.hive.metastore.ldap.LdapUtils;
 import org.apache.hadoop.hive.metastore.ldap.UserFilterFactory;
+import org.apache.hadoop.hive.metastore.ldap.UserGroupSearchFilterFactory;
 import org.apache.hadoop.hive.metastore.ldap.UserSearchFilterFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -51,6 +52,7 @@ public class MetaStoreLdapAuthenticationProviderImpl implements MetaStorePasswdA
           LoggerFactory.getLogger(MetaStoreLdapAuthenticationProviderImpl.class);
 
   private static final List<FilterFactory> FILTER_FACTORIES = ImmutableList.<FilterFactory>of(
+      new UserGroupSearchFilterFactory(),
       new CustomQueryFilterFactory(),
       new ChainFilterFactory(new UserSearchFilterFactory(), new UserFilterFactory(),
           new GroupFilterFactory())
diff --git a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/conf/MetastoreConf.java b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/conf/MetastoreConf.java
index a61df6c2672..29c4b5774be 100644
--- a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/conf/MetastoreConf.java
+++ b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/conf/MetastoreConf.java
@@ -985,6 +985,19 @@ public class MetastoreConf {
             "For example: (&(objectClass=group)(objectClass=top)(instanceType=4)(cn=Domain*)) \n" +
             "(&(objectClass=person)(|(sAMAccountName=admin)(|(memberOf=CN=Domain Admins,CN=Users,DC=domain,DC=com)" +
             "(memberOf=CN=Administrators,CN=Builtin,DC=domain,DC=com))))"),
+    METASTORE_PLAIN_LDAP_USERSEARCHFILTER("metastore.authentication.ldap.userSearchFilter",
+        "hive.metastore.authentication.ldap.userSearchFilter", "",
+        "User search filter to be used with baseDN to search for users\n" +
+            "For example: (&(uid={0})(objectClass=person))"),
+    METASTORE_PLAIN_LDAP_GROUPBASEDN("metastore.authentication.ldap.groupBaseDN",
+        "hive.metastore.authentication.ldap.groupBaseDN", "",
+        "BaseDN for Group Search. This is used in conjunction with metastore.authentication.ldap.baseDN\n" +
+            "and \n" +
+            "request, succeeds if the group is part of the resultset."),
+    METASTORE_PLAIN_LDAP_GROUPSEARCHFILTER("metastore.authentication.ldap.groupSearchFilter",
+        "hive.metastore.authentication.ldap.groupSearchFilter", "",
+        "Group search filter to be used with baseDN, userSearchFilter, groupBaseDN to search for users in groups\n" +
+            "For example: (&(|(memberUid={0})(memberUid={1}))(objectClass=posixGroup))\n"),
     METASTORE_PLAIN_LDAP_BIND_USER("metastore.authentication.ldap.binddn",
             "hive.metastore.authentication.ldap.binddn", "",
 "The user with which to bind to the LDAP server, and search for the full domain name " +
diff --git a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/DirSearch.java b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/DirSearch.java
index 2b8238bc77e..0b8efe3bdc4 100644
--- a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/DirSearch.java
+++ b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/DirSearch.java
@@ -34,6 +34,16 @@ public interface DirSearch extends Closeable {
    */
   String findUserDn(String user) throws NamingException;
 
+  /**
+   * Finds user's distinguished name.
+   * @param user username
+   * @param userSearchFilter Generic LDAP Search filter for ex: (&amp;(uid={0})(objectClass=person))
+   * @param baseDn LDAP BaseDN for user searches for ex: dc=apache,dc=org
+   * @return DN for the specific user if exists, null otherwise
+   * @throws NamingException
+   */
+  String findUserDn(String user, String userSearchFilter, String baseDn) throws NamingException;
+
   /**
    * Finds group's distinguished name.
    * @param group group name or unique identifier
@@ -66,4 +76,15 @@ public interface DirSearch extends Closeable {
    * @throws NamingException
    */
   List<String> executeCustomQuery(String query) throws NamingException;
+
+  /**
+   * Executes an arbitrary query.
+   * @param user user RDN or username. This will be substituted for {0} in group search
+   * @param userDn userDn DN for the username. This will be substituted for {1} in group search
+   * @param filter filter is the group filter query ex: (&amp;(memberUid={0})(&amp;(CN=group1)(objectClass=posixGroup)))
+   * @param groupBaseDn BaseDN for group searches. ex: "ou=groups,dc=apache,dc=org"
+   * @return list of names that match the group filter aka groups that the user belongs to, if any.
+   * @throws NamingException
+   */
+  List<String> executeUserAndGroupFilterQuery(String user, String userDn, String filter, String groupBaseDn) throws NamingException;
 }
diff --git a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/LdapSearch.java b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/LdapSearch.java
index b82036cd8ae..775127457ef 100644
--- a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/LdapSearch.java
+++ b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/LdapSearch.java
@@ -106,6 +106,28 @@ public final class LdapSearch implements DirSearch {
     }
   }
 
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String findUserDn(String user, final String userSearchFilter, final String baseDn) throws NamingException {
+    List<String> allLdapNames;
+    String userRdn = user;
+    if (LdapUtils.isDn(user)) {
+      userRdn = LdapUtils.extractUserName(user);
+    }
+    Query query = queries.findUserDnBySearch(userRdn, userSearchFilter);
+    allLdapNames = execute(Collections.singletonList(baseDn), query).getAllLdapNames();
+
+    if (allLdapNames.size() == 1) {
+      return allLdapNames.get(0);
+    } else {
+      LOG.info("Expected exactly one userSearchFilter result for the userSearchFilter: {}, but got {}. Returning null",
+          userSearchFilter, allLdapNames.size());
+      return null;
+    }
+  }
+
   private List<String> findDnByPattern(List<String> patterns, String name) throws NamingException {
     for (String pattern : patterns) {
       String baseDnFromPattern = LdapUtils.extractBaseDn(pattern);
@@ -154,6 +176,15 @@ public final class LdapSearch implements DirSearch {
         .getAllLdapNamesAndAttributes();
   }
 
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public List<String> executeUserAndGroupFilterQuery(String user, String userDn, String groupSearchFilter, String groupBaseDn) throws NamingException {
+    Query query = queries.findDnByUserAndGroupSearch(user, userDn, groupSearchFilter);
+    return execute(Collections.singletonList(groupBaseDn), query).getAllLdapNames();
+  }
+
   private SearchResultHandler execute(Collection<String> baseDns, Query query) {
     List<NamingEnumeration<SearchResult>> searchResults = new ArrayList<>();
     LOG.debug("Executing a query: '{}' with base DNs {}.", query.getFilter(), baseDns);
diff --git a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/QueryFactory.java b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/QueryFactory.java
index 7ba2e4c281e..74b3457d256 100644
--- a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/QueryFactory.java
+++ b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/QueryFactory.java
@@ -110,6 +110,38 @@ final class QueryFactory {
         .build();
   }
 
+  /**
+   * Returns a query for finding user DN based on user RDN.
+   * @param user user RDN that will replace {0} in the search filter
+   * @param userSearchFilter search query that user filter conditions.
+   * @return an instance of {@link Query}
+   */
+  public Query findUserDnBySearch(String user, String userSearchFilter) {
+    if (userSearchFilter != null) {
+      userSearchFilter = userSearchFilter.replaceAll("\\{0\\}", user);
+    }
+    return Query.builder()
+        .filter(userSearchFilter)
+        .build();
+  }
+
+  /**
+   * Returns a query for finding matches based on user and group filter query.
+   * @param user user RDN to replace {0}
+   * @param userDn user DN to replace {1}}
+   * @param groupSearchFilter search query that includes any group filter conditions.
+   * @return an instance of {@link Query}
+   */
+  public Query findDnByUserAndGroupSearch(String user, String userDn, String groupSearchFilter) {
+    if (groupSearchFilter != null) {
+      groupSearchFilter = groupSearchFilter.replaceAll("\\{0\\}", userDn);
+      groupSearchFilter = groupSearchFilter.replaceAll("\\{1\\}", user);
+    }
+    return Query.builder()
+        .filter(groupSearchFilter)
+        .build();
+  }
+
   /**
    * Returns a query for finding groups to which the user belongs.
    * @param userName username
diff --git a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/UserGroupSearchFilterFactory.java b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/UserGroupSearchFilterFactory.java
new file mode 100644
index 00000000000..ee12a57ed5d
--- /dev/null
+++ b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/UserGroupSearchFilterFactory.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hive.metastore.ldap;
+
+import com.google.common.base.Strings;
+
+import java.util.List;
+import javax.naming.NamingException;
+import javax.security.sasl.AuthenticationException;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hive.metastore.conf.MetastoreConf;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A factory for a {@link Filter} based on user and group search filters.
+ * <br>
+ * The produced filter object filters out all users that are not found in the search result
+ * of the query provided in Metastore configuration.
+ * Atleast one search criteria is REQUIRED.
+ * Configuration could have Usersearch filter or Groupsearch filter or both.
+ * @see MetastoreConf.ConfVars#METASTORE_PLAIN_LDAP_USERSEARCHFILTER
+ * @see MetastoreConf.ConfVars#METASTORE_PLAIN_LDAP_BASEDN
+ * @see MetastoreConf.ConfVars#METASTORE_PLAIN_LDAP_GROUPSEARCHFILTER
+ * @see MetastoreConf.ConfVars#METASTORE_PLAIN_LDAP_GROUPBASEDN
+ */
+public class UserGroupSearchFilterFactory implements FilterFactory {
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Filter getInstance(Configuration conf) {
+    String userSearchFilter = conf.get(MetastoreConf.ConfVars.METASTORE_PLAIN_LDAP_USERSEARCHFILTER.getVarname());
+    String userSearchBaseDN = conf.get(MetastoreConf.ConfVars.METASTORE_PLAIN_LDAP_BASEDN.getVarname());
+    String groupSearchFilter = conf.get(MetastoreConf.ConfVars.METASTORE_PLAIN_LDAP_GROUPSEARCHFILTER.getVarname());
+    String groupSearchBaseDN = conf.get(MetastoreConf.ConfVars.METASTORE_PLAIN_LDAP_GROUPBASEDN.getVarname());
+
+    // Both UserSearch and GroupSearch cannot be null or empty.
+    if (Strings.isNullOrEmpty(userSearchFilter) &&
+        (Strings.isNullOrEmpty(groupSearchFilter) && Strings.isNullOrEmpty(groupSearchBaseDN))) {
+      return null;
+    }
+    return new UserGroupSearchFilter(userSearchFilter, userSearchBaseDN, groupSearchFilter, groupSearchBaseDN);
+  }
+
+  private static final class UserGroupSearchFilter implements Filter {
+
+    private static final Logger LOG = LoggerFactory.getLogger(UserGroupSearchFilter.class);
+
+    private final String userSearchFilter;
+    private final String userBaseDN;
+    private final String groupSearchFilter;
+    private final String groupBaseDN;
+
+    UserGroupSearchFilter(String userSearchFilter, String userBaseDN, String groupSearchFilter, String groupBaseDN) {
+      this.userSearchFilter = userSearchFilter;
+      this.userBaseDN = userBaseDN;
+      this.groupSearchFilter = groupSearchFilter;
+      this.groupBaseDN = groupBaseDN;
+    }
+
+    @Override public void apply(DirSearch client, String user) throws AuthenticationException {
+      String userDn = null;
+      List<String> resultList;
+      try {
+        if (!Strings.isNullOrEmpty(userSearchFilter) && !Strings.isNullOrEmpty(userBaseDN)) {
+          userDn = client.findUserDn(user, userSearchFilter, userBaseDN);
+
+          // This should not be null because we were allowed to bind with this username
+          // safe check in case we were able to bind anonymously.
+          if (userDn == null) {
+            throw new AuthenticationException("Authentication failed: User search found no matching user");
+          }
+        }
+
+        if (!Strings.isNullOrEmpty(groupSearchFilter) && !Strings.isNullOrEmpty(groupBaseDN)) {
+          resultList = client.executeUserAndGroupFilterQuery(user, userDn, groupSearchFilter, groupBaseDN);
+            if (resultList != null && resultList.size() > 0) {
+              return;
+            }
+        } else if (userDn != null) { // succeed based on user search filter only
+          return;
+        }
+        throw new AuthenticationException("Authentication failed: User search does not satisfy filter condition");
+      } catch (NamingException e) {
+        throw new AuthenticationException("LDAP Authentication failed for user", e);
+      }
+    }
+  }
+}
+
diff --git a/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/TestLdapAtnProviderWithMiniDS.java b/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/TestLdapAtnProviderWithMiniDS.java
index 075e895e23e..816ec4a3717 100644
--- a/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/TestLdapAtnProviderWithMiniDS.java
+++ b/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/TestLdapAtnProviderWithMiniDS.java
@@ -689,4 +689,67 @@ public class TestLdapAtnProviderWithMiniDS extends AbstractLdapTestUnit {
     testCase.assertAuthenticateFails(ENGINEER_1.credentialsWithId());
     testCase.assertAuthenticateFails(MANAGER_1.credentialsWithDn());
   }
+
+  @Test
+  public void testUserSearchFilterPositive() throws Exception {
+    testCase = defaultBuilder()
+        .baseDN("ou=People,dc=example,dc=com")
+        .userSearchFilter("(&(|(uid={0})(sAMAccountName={0}))(objectClass=person))")
+        .guidKey("uid")
+        .build();
+
+    testCase.assertAuthenticatePasses(USER1.credentialsWithId());
+    testCase.assertAuthenticatePasses(USER2.credentialsWithId());
+
+    testCase = defaultBuilder()
+        .baseDN("ou=Engineering,dc=ad,dc=example,dc=com")
+        .userSearchFilter("(&(|(uid={0})(sAMAccountName={0}))(objectClass=person))")
+        .guidKey("sAMAccountName")
+        .build();
+
+    testCase.assertAuthenticatePasses(ENGINEER_1.credentialsWithId());
+    testCase.assertAuthenticatePasses(ENGINEER_1.credentialsWithDn());
+  }
+
+  @Test
+  public void testUserSearchFilterNegative() throws Exception {
+    testCase = defaultBuilder()
+        .baseDN("ou=Engineering,dc=ad,dc=example,dc=com")
+        .userSearchFilter("(&(sAMAccountName={0})(objectClass=person)")
+        .guidKey("uid")
+        .build();
+
+    testCase.assertAuthenticateFails(USER1.credentialsWithId());
+    testCase.assertAuthenticateFails(USER3.credentialsWithId());
+  }
+
+  @Test
+  public void testUserAndGroupSearchFilterPositive() throws Exception {
+    testCase = defaultBuilder()
+        .baseDN("ou=People,dc=example,dc=com")
+        .userSearchFilter("(&(|(uid={0})(sAMAccountName={0}))(objectClass=person))")
+        .guidKey("uid")
+        .groupBaseDN("ou=Groups,dc=example,dc=com")
+        .groupSearchFilter("(&(|(member={0})(member={1}))(&(|(cn=group1)(cn=group2))(objectClass=groupOfNames)))")
+        .build();
+
+    testCase.assertAuthenticatePasses(USER1.credentialsWithId());
+    testCase.assertAuthenticatePasses(USER2.credentialsWithId());
+  }
+
+  @Test
+  public void testUserAndGroupSearchFilterNegative() throws Exception {
+    testCase = defaultBuilder()
+        .baseDN("ou=People,dc=example,dc=com")
+        .userSearchFilter("(&(|(uid={0})(sAMAccountName={0}))(objectClass=person)")
+        .guidKey("uid")
+        .groupBaseDN("ou=Groups,dc=example,dc=com")
+        .groupSearchFilter("(&(|(member={0})(member={1}))(&(cn=group1)(objectClass=groupOfNames)))")
+        .build();
+
+    testCase.assertAuthenticateFails(USER2.credentialsWithId());
+    testCase.assertAuthenticateFails(USER3.credentialsWithId());
+    testCase.assertAuthenticateFails(ENGINEER_1.credentialsWithId());
+  }
+
 }
diff --git a/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/ldap/LdapAuthenticationTestCase.java b/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/ldap/LdapAuthenticationTestCase.java
index 1a404a83401..4429840eae6 100644
--- a/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/ldap/LdapAuthenticationTestCase.java
+++ b/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/ldap/LdapAuthenticationTestCase.java
@@ -125,6 +125,21 @@ public final class LdapAuthenticationTestCase {
           userMembershipKey);
     }
 
+    public Builder userSearchFilter(String userSearchFilter) {
+      return setVarOnce(MetastoreConf.ConfVars.METASTORE_PLAIN_LDAP_USERSEARCHFILTER,
+          userSearchFilter);
+    }
+
+    public Builder groupSearchFilter(String groupSearchFilter) {
+      return setVarOnce(MetastoreConf.ConfVars.METASTORE_PLAIN_LDAP_GROUPSEARCHFILTER,
+          groupSearchFilter);
+    }
+
+    public Builder groupBaseDN(String groupBaseDN) {
+      return setVarOnce(MetastoreConf.ConfVars.METASTORE_PLAIN_LDAP_GROUPBASEDN,
+          groupBaseDN);
+    }
+
     private Builder setVarOnce(MetastoreConf.ConfVars confVar, String value) {
       Preconditions.checkState(!overrides.containsKey(confVar),
           "Property %s has been set already", confVar);