You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hive.apache.org by ct...@apache.org on 2016/09/23 19:50:49 UTC

[1/2] hive git commit: HIVE-14713: LDAP Authentication Provider should be covered with unit tests (Illya Yalovyy, reviewed by Chaoyu Tang, Szehon Ho)

Repository: hive
Updated Branches:
  refs/heads/master 421d97a8d -> 990927e3d


http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/service/src/java/org/apache/hive/service/auth/ldap/UserSearchFilterFactory.java
----------------------------------------------------------------------
diff --git a/service/src/java/org/apache/hive/service/auth/ldap/UserSearchFilterFactory.java b/service/src/java/org/apache/hive/service/auth/ldap/UserSearchFilterFactory.java
new file mode 100644
index 0000000..3218875
--- /dev/null
+++ b/service/src/java/org/apache/hive/service/auth/ldap/UserSearchFilterFactory.java
@@ -0,0 +1,65 @@
+/**
+ * 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 java.util.Collection;
+import javax.naming.NamingException;
+import javax.security.sasl.AuthenticationException;
+import org.apache.hadoop.hive.conf.HiveConf;
+
+/**
+ * A factory for a {@link Filter} that check whether provided user could be found in the directory.
+ * <br>
+ * The produced filter object filters out all users that are not found in the directory.
+ */
+public final class UserSearchFilterFactory implements FilterFactory {
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Filter getInstance(HiveConf conf) {
+    Collection<String> groupFilter = conf.getStringCollection(
+        HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER.varname);
+    Collection<String> userFilter = conf.getStringCollection(
+        HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER.varname);
+
+    if (groupFilter.isEmpty() && userFilter.isEmpty()) {
+      return null;
+    }
+
+    return new UserSearchFilter();
+  }
+
+  private static final class UserSearchFilter implements Filter {
+    @Override
+    public void apply(DirSearch client, String user) throws AuthenticationException {
+      try {
+        String userDn = client.findUserDn(user);
+
+        // 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 failed");
+        }
+      } catch (NamingException e) {
+        throw new AuthenticationException("LDAP Authentication failed for user", e);
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/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 089a059..23a048a 100644
--- a/service/src/test/org/apache/hive/service/auth/TestLdapAtnProviderWithMiniDS.java
+++ b/service/src/test/org/apache/hive/service/auth/TestLdapAtnProviderWithMiniDS.java
@@ -225,7 +225,6 @@ public class TestLdapAtnProviderWithMiniDS extends AbstractLdapTestUnit {
     hiveConf = new HiveConf();
 
     ldapProvider = new LdapAuthenticationProviderImpl(hiveConf);
-    ldapProvider.init(hiveConf);
   }
 
   @AfterClass
@@ -259,7 +258,7 @@ public class TestLdapAtnProviderWithMiniDS extends AbstractLdapTestUnit {
       }
     }
 
-    ldapProvider.init(hiveConf);
+    ldapProvider = new LdapAuthenticationProviderImpl(hiveConf);
   }
 
   @Test

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/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 f276906..4fad755 100644
--- a/service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java
+++ b/service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java
@@ -15,51 +15,260 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.hive.service.auth;
 
+import java.io.IOException;
+import java.util.Arrays;
+import javax.naming.NamingException;
 import javax.security.sasl.AuthenticationException;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-
-import junit.framework.TestCase;
 import org.apache.hadoop.hive.conf.HiveConf;
+import org.apache.hive.service.auth.ldap.DirSearch;
+import org.apache.hive.service.auth.ldap.DirSearchFactory;
+import org.apache.hive.service.auth.ldap.LdapSearchFactory;
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TestLdapAuthenticationProviderImpl {
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
 
-public class TestLdapAuthenticationProviderImpl extends TestCase {
+  public HiveConf conf;
+  public LdapAuthenticationProviderImpl auth;
 
-  private static HiveConf hiveConf;
-  private static byte[] hiveConfBackup;
+  @Mock
+  public DirSearchFactory factory;
 
-  @Override
-  public void setUp() throws Exception {
-      hiveConf = new HiveConf();
-      ByteArrayOutputStream baos = new ByteArrayOutputStream();
-      hiveConf.writeXml(baos);
-      baos.close();
-      hiveConfBackup = baos.toByteArray();
-      hiveConf.set("hive.server2.authentication.ldap.url", "localhost");
-      FileOutputStream fos = new FileOutputStream(new File(hiveConf.getHiveSiteLocation().toURI()));
-      hiveConf.writeXml(fos);
-      fos.close();
+  @Mock
+  public DirSearch search;
+
+  @Before
+  public void setup() throws AuthenticationException {
+    conf = new HiveConf();
+    conf.set("hive.root.logger", "DEBUG,console");
+    conf.set("hive.server2.authentication.ldap.url", "localhost");
+    when(factory.getInstance(any(HiveConf.class), anyString(), anyString())).thenReturn(search);
   }
 
-  public void testLdapEmptyPassword() {
-    LdapAuthenticationProviderImpl ldapImpl = new LdapAuthenticationProviderImpl(hiveConf);
-    try {
-      ldapImpl.Authenticate("user", "");
-      assertFalse(true);
-    } catch (AuthenticationException e) {
-      assertTrue(e.getMessage(), e.getMessage().contains("a null or blank password has been provided"));
-    }
+  @Test
+  public void authenticateGivenBlankPassword() throws Exception {
+    auth = new LdapAuthenticationProviderImpl(conf, new LdapSearchFactory());
+    expectAuthenticationExceptionForInvalidPassword();
+    auth.Authenticate("user", "");
+  }
+
+  @Test
+  public void authenticateGivenStringWithNullCharacterForPassword() throws Exception {
+    auth = new LdapAuthenticationProviderImpl(conf, new LdapSearchFactory());
+    expectAuthenticationExceptionForInvalidPassword();
+    auth.Authenticate("user", "\0");
+  }
+
+  @Test
+  public void authenticateGivenNullForPassword() throws Exception {
+    auth = new LdapAuthenticationProviderImpl(conf, new LdapSearchFactory());
+    expectAuthenticationExceptionForInvalidPassword();
+    auth.Authenticate("user", null);
+  }
+
+  @Test
+  public void testAuthenticateNoUserOrGroupFilter() throws NamingException, AuthenticationException, IOException {
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN,
+        "cn=%s,ou=Users,dc=mycorp,dc=com:cn=%s,ou=PowerUsers,dc=mycorp,dc=com");
+
+    DirSearchFactory factory = mock(DirSearchFactory.class);
+
+    when(search.findUserDn("user1")).thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com");
+
+    when(factory.getInstance(conf, "cn=user1,ou=PowerUsers,dc=mycorp,dc=com", "Blah")).thenReturn(search);
+    when(factory.getInstance(conf, "cn=user1,ou=Users,dc=mycorp,dc=com", "Blah")).thenThrow(AuthenticationException.class);
+
+    auth = new LdapAuthenticationProviderImpl(conf, factory);
+    auth.Authenticate("user1", "Blah");
+
+    verify(factory, times(2)).getInstance(isA(HiveConf.class), anyString(), eq("Blah"));
+    verify(search, atLeastOnce()).close();
   }
 
-  @Override
-  public void tearDown() throws Exception {
-    if(hiveConf != null && hiveConfBackup != null) {
-      FileOutputStream fos = new FileOutputStream(new File(hiveConf.getHiveSiteLocation().toURI()));
-      fos.write(hiveConfBackup);
-      fos.close();
+  @Test
+  public void testAuthenticateWhenUserFilterPasses() throws NamingException, AuthenticationException, IOException {
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER,
+        "user1,user2");
+
+    when(search.findUserDn("user1")).thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com");
+    when(search.findUserDn("user2")).thenReturn("cn=user2,ou=PowerUsers,dc=mycorp,dc=com");
+
+    authenticateUserAndCheckSearchIsClosed("user1");
+    authenticateUserAndCheckSearchIsClosed("user2");
+  }
+
+  @Test
+  public void testAuthenticateWhenLoginWithDomainAndUserFilterPasses() throws NamingException, AuthenticationException, IOException {
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER,
+        "user1");
+
+    when(search.findUserDn("user1")).thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com");
+
+    authenticateUserAndCheckSearchIsClosed("user1@mydomain.com");
+  }
+
+  @Test
+  public void testAuthenticateWhenLoginWithDnAndUserFilterPasses() throws NamingException, AuthenticationException, IOException {
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER,
+        "user1");
+
+    when(search.findUserDn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com")).thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com");
+
+    authenticateUserAndCheckSearchIsClosed("cn=user1,ou=PowerUsers,dc=mycorp,dc=com");
+  }
+
+  @Test
+  public void testAuthenticateWhenUserSearchFails() throws NamingException, AuthenticationException, IOException {
+    thrown.expect(AuthenticationException.class);
+
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER, "user1,user2");
+
+    when(search.findUserDn("user1")).thenReturn(null);
+
+    authenticateUserAndCheckSearchIsClosed("user1");
+  }
+
+  @Test
+  public void testAuthenticateWhenUserFilterFails() throws NamingException, AuthenticationException, IOException {
+    thrown.expect(AuthenticationException.class);
+
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER, "user1,user2");
+
+    when(search.findUserDn("user3")).thenReturn("cn=user3,ou=PowerUsers,dc=mycorp,dc=com");
+
+    authenticateUserAndCheckSearchIsClosed("user3");
+  }
+
+  @Test
+  public void testAuthenticateWhenGroupFilterPasses() 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");
+    when(search.findUserDn("user2")).thenReturn("cn=user2,ou=PowerUsers,dc=mycorp,dc=com");
+
+    when(search.findGroupsForUser("cn=user1,ou=PowerUsers,dc=mycorp,dc=com"))
+        .thenReturn(Arrays.asList(
+            "cn=testGroup,ou=Groups,dc=mycorp,dc=com",
+            "cn=group1,ou=Groups,dc=mycorp,dc=com"));
+    when(search.findGroupsForUser("cn=user2,ou=PowerUsers,dc=mycorp,dc=com"))
+        .thenReturn(Arrays.asList(
+            "cn=testGroup,ou=Groups,dc=mycorp,dc=com",
+            "cn=group2,ou=Groups,dc=mycorp,dc=com"));
+
+    authenticateUserAndCheckSearchIsClosed("user1");
+    authenticateUserAndCheckSearchIsClosed("user2");
+  }
+
+  @Test
+  public void testAuthenticateWhenUserAndGroupFiltersPass() 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");
+
+    when(search.findUserDn("user1")).thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com");
+    when(search.findUserDn("user2")).thenReturn("cn=user2,ou=PowerUsers,dc=mycorp,dc=com");
+
+    when(search.findGroupsForUser("cn=user1,ou=PowerUsers,dc=mycorp,dc=com"))
+        .thenReturn(Arrays.asList(
+            "cn=testGroup,ou=Groups,dc=mycorp,dc=com",
+            "cn=group1,ou=Groups,dc=mycorp,dc=com"));
+    when(search.findGroupsForUser("cn=user2,ou=PowerUsers,dc=mycorp,dc=com"))
+        .thenReturn(Arrays.asList(
+            "cn=testGroup,ou=Groups,dc=mycorp,dc=com",
+            "cn=group2,ou=Groups,dc=mycorp,dc=com"));
+
+    authenticateUserAndCheckSearchIsClosed("user1");
+    authenticateUserAndCheckSearchIsClosed("user2");
+  }
+
+  @Test
+  public void testAuthenticateWhenUserFilterPassesAndGroupFilterFails()
+      throws NamingException, AuthenticationException, IOException {
+    thrown.expect(AuthenticationException.class);
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "group1,group2");
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER, "user1,user2");
+
+    when(search.findUserDn("user1")).thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com");
+
+    when(search.findGroupsForUser("cn=user1,ou=PowerUsers,dc=mycorp,dc=com"))
+        .thenReturn(Arrays.asList(
+            "cn=testGroup,ou=Groups,dc=mycorp,dc=com",
+            "cn=OtherGroup,ou=Groups,dc=mycorp,dc=com"));
+
+    authenticateUserAndCheckSearchIsClosed("user1");
+  }
+
+  @Test
+  public void testAuthenticateWhenUserFilterFailsAndGroupFilterPasses()
+      throws NamingException, AuthenticationException, IOException {
+    thrown.expect(AuthenticationException.class);
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "group3");
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER, "user1,user2");
+
+    when(search.findUserDn("user3")).thenReturn("cn=user3,ou=PowerUsers,dc=mycorp,dc=com");
+
+    when(search.findGroupsForUser("cn=user3,ou=PowerUsers,dc=mycorp,dc=com"))
+        .thenReturn(Arrays.asList(
+            "cn=testGroup,ou=Groups,dc=mycorp,dc=com",
+            "cn=group3,ou=Groups,dc=mycorp,dc=com"));
+
+    authenticateUserAndCheckSearchIsClosed("user3");
+  }
+
+  @Test
+  public void testAuthenticateWhenCustomQueryFilterPasses() throws NamingException, AuthenticationException, IOException {
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BASEDN, "dc=mycorp,dc=com");
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_CUSTOMLDAPQUERY,
+        "(&(objectClass=person)(|(memberOf=CN=Domain Admins,CN=Users,DC=apache,DC=org)(memberOf=CN=Administrators,CN=Builtin,DC=apache,DC=org)))");
+
+    when(search.executeCustomQuery(anyString())).thenReturn(Arrays.asList(
+        "cn=user1,ou=PowerUsers,dc=mycorp,dc=com",
+        "cn=user2,ou=PowerUsers,dc=mycorp,dc=com"));
+
+    authenticateUserAndCheckSearchIsClosed("user1");
+  }
+
+  @Test
+  public void testAuthenticateWhenCustomQueryFilterFailsAndUserFilterPasses() throws NamingException, AuthenticationException, IOException {
+    thrown.expect(AuthenticationException.class);
+
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BASEDN, "dc=mycorp,dc=com");
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_CUSTOMLDAPQUERY,
+        "(&(objectClass=person)(|(memberOf=CN=Domain Admins,CN=Users,DC=apache,DC=org)(memberOf=CN=Administrators,CN=Builtin,DC=apache,DC=org)))");
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER, "user3");
+
+    when(search.findUserDn("user3")).thenReturn("cn=user3,ou=PowerUsers,dc=mycorp,dc=com");
+    when(search.executeCustomQuery(anyString())).thenReturn(Arrays.asList(
+        "cn=user1,ou=PowerUsers,dc=mycorp,dc=com",
+        "cn=user2,ou=PowerUsers,dc=mycorp,dc=com"));
+
+    authenticateUserAndCheckSearchIsClosed("user3");
+  }
+
+  private void expectAuthenticationExceptionForInvalidPassword() {
+    thrown.expect(AuthenticationException.class);
+    thrown.expectMessage("a null or blank password has been provided");
+  }
+
+  private void authenticateUserAndCheckSearchIsClosed(String user) throws IOException {
+    auth = new LdapAuthenticationProviderImpl(conf, factory);
+    try {
+      auth.Authenticate(user, "password doesn't matter");
+    } finally {
+      verify(search, atLeastOnce()).close();
     }
   }
 }

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/service/src/test/org/apache/hive/service/auth/ldap/Credentials.java
----------------------------------------------------------------------
diff --git a/service/src/test/org/apache/hive/service/auth/ldap/Credentials.java b/service/src/test/org/apache/hive/service/auth/ldap/Credentials.java
new file mode 100644
index 0000000..ce22b8e
--- /dev/null
+++ b/service/src/test/org/apache/hive/service/auth/ldap/Credentials.java
@@ -0,0 +1,41 @@
+/**
+ * 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;
+
+public final class Credentials {
+
+  private final String user;
+  private final String password;
+
+  private Credentials(String user, String password) {
+    this.user = user;
+    this.password = password;
+  }
+
+  public static Credentials of(String user, String password) {
+    return new Credentials(user, password);
+  }
+
+  public String getUser() {
+    return user;
+  }
+
+  public String getPassword() {
+    return password;
+  }
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/service/src/test/org/apache/hive/service/auth/ldap/LdapTestUtils.java
----------------------------------------------------------------------
diff --git a/service/src/test/org/apache/hive/service/auth/ldap/LdapTestUtils.java b/service/src/test/org/apache/hive/service/auth/ldap/LdapTestUtils.java
new file mode 100644
index 0000000..d4e034f
--- /dev/null
+++ b/service/src/test/org/apache/hive/service/auth/ldap/LdapTestUtils.java
@@ -0,0 +1,126 @@
+/**
+ * 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 java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.directory.SearchResult;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import org.mockito.stubbing.OngoingStubbing;
+
+public final class LdapTestUtils {
+
+  private LdapTestUtils() {
+  }
+
+  public static NamingEnumeration<SearchResult> mockEmptyNamingEnumeration() throws NamingException {
+    return mockNamingEnumeration(new SearchResult[0]);
+  }
+
+  public static NamingEnumeration<SearchResult> mockNamingEnumeration(String... dns) throws NamingException {
+    return mockNamingEnumeration(mockSearchResults(dns).toArray(new SearchResult[0]));
+  }
+
+  public static NamingEnumeration<SearchResult> mockNamingEnumeration(SearchResult... searchResults) throws NamingException {
+    NamingEnumeration<SearchResult> ne =
+        (NamingEnumeration<SearchResult>) mock(NamingEnumeration.class);
+    mockHasMoreMethod(ne, searchResults.length);
+    if (searchResults.length > 0) {
+      List<SearchResult> mockedResults = Arrays.asList(searchResults);
+      mockNextMethod(ne, mockedResults);
+    }
+    return ne;
+  }
+
+  public static void mockHasMoreMethod(NamingEnumeration<SearchResult> ne, int length) throws NamingException {
+    OngoingStubbing<Boolean> hasMoreStub = when(ne.hasMore());
+    for (int i = 0; i < length; i++) {
+      hasMoreStub = hasMoreStub.thenReturn(true);
+    }
+    hasMoreStub.thenReturn(false);
+  }
+
+  public static void mockNextMethod(NamingEnumeration<SearchResult> ne, List<SearchResult> searchResults) throws NamingException {
+    OngoingStubbing<SearchResult> nextStub = when(ne.next());
+    for (SearchResult searchResult : searchResults) {
+      nextStub = nextStub.thenReturn(searchResult);
+    }
+  }
+
+  public static List<SearchResult> mockSearchResults(String[] dns) {
+    List<SearchResult> list = new ArrayList<>();
+    for (String dn : dns) {
+      list.add(mockSearchResult(dn, null));
+    }
+    return list;
+  }
+
+  public static SearchResult mockSearchResult(String dn, Attributes attributes) {
+    SearchResult searchResult = mock(SearchResult.class);
+    when(searchResult.getNameInNamespace()).thenReturn(dn);
+    when(searchResult.getAttributes()).thenReturn(attributes);
+    return searchResult;
+  }
+
+  public static Attributes mockEmptyAttributes() throws NamingException {
+    return mockAttributes();
+  }
+
+  public static Attributes mockAttributes(String name, String value) throws NamingException {
+    return mockAttributes(new NameValues(name, value));
+  }
+
+  public static Attributes mockAttributes(String name1, String value1, String name2, String value2) throws NamingException {
+    if (name1.equals(name2)) {
+      return mockAttributes(new NameValues(name1, value1, value2));
+    } else {
+      return mockAttributes(new NameValues(name1, value1), new NameValues(name2, value2));
+    }
+  }
+
+  private static Attributes mockAttributes(NameValues... namedValues) throws NamingException {
+    Attributes attributes =  new BasicAttributes();
+    for (NameValues namedValue : namedValues) {
+      Attribute attr = new BasicAttribute(namedValue.name);
+      for (String value : namedValue.values) {
+        attr.add(value);
+      }
+      attributes.put(attr);
+    }
+    return attributes;
+  }
+
+  private static final class NameValues {
+    final String name;
+    final String[] values;
+
+    public NameValues(String name, String... values) {
+      this.name = name;
+      this.values = values;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/service/src/test/org/apache/hive/service/auth/ldap/TestChainFilter.java
----------------------------------------------------------------------
diff --git a/service/src/test/org/apache/hive/service/auth/ldap/TestChainFilter.java b/service/src/test/org/apache/hive/service/auth/ldap/TestChainFilter.java
new file mode 100644
index 0000000..9caa233
--- /dev/null
+++ b/service/src/test/org/apache/hive/service/auth/ldap/TestChainFilter.java
@@ -0,0 +1,103 @@
+/**
+ * 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 java.io.IOException;
+import javax.naming.NamingException;
+import javax.security.sasl.AuthenticationException;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import org.junit.Before;
+import org.mockito.Mock;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TestChainFilter {
+
+  private FilterFactory factory;
+  private HiveConf conf;
+
+  @Mock
+  public Filter filter1;
+
+  @Mock
+  public Filter filter2;
+
+  @Mock
+  public Filter filter3;
+
+  @Mock
+  public FilterFactory factory1;
+
+  @Mock
+  public FilterFactory factory2;
+
+  @Mock
+  public FilterFactory factory3;
+
+  @Mock
+  private DirSearch search;
+
+  @Before
+  public void setup() {
+    conf = new HiveConf();
+    factory = new ChainFilterFactory(factory1, factory2, factory3);
+  }
+
+  @Test
+  public void testFactoryAllNull() {
+    assertNull(factory.getInstance(conf));
+  }
+
+  @Test
+  public void testFactoryAllEmpty() {
+    FilterFactory emptyFactory = new ChainFilterFactory();
+    assertNull(emptyFactory.getInstance(conf));
+  }
+
+  @Test
+  public void testFactory() throws AuthenticationException {
+    when(factory1.getInstance(any(HiveConf.class))).thenReturn(filter1);
+    when(factory2.getInstance(any(HiveConf.class))).thenReturn(filter2);
+    when(factory3.getInstance(any(HiveConf.class))).thenReturn(filter3);
+
+    Filter filter = factory.getInstance(conf);
+
+    filter.apply(search, "User");
+    verify(filter1, times(1)).apply(search, "User");
+    verify(filter2, times(1)).apply(search, "User");
+    verify(filter3, times(1)).apply(search, "User");
+  }
+
+  @Test(expected = AuthenticationException.class)
+  public void testApplyNegative() throws AuthenticationException, NamingException, IOException {
+    doThrow(AuthenticationException.class).when(filter3).apply((DirSearch) anyObject(), anyString());
+
+    when(factory1.getInstance(any(HiveConf.class))).thenReturn(filter1);
+    when(factory3.getInstance(any(HiveConf.class))).thenReturn(filter3);
+
+    Filter filter = factory.getInstance(conf);
+
+    filter.apply(search, "User");
+  }
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/service/src/test/org/apache/hive/service/auth/ldap/TestCustomQueryFilter.java
----------------------------------------------------------------------
diff --git a/service/src/test/org/apache/hive/service/auth/ldap/TestCustomQueryFilter.java b/service/src/test/org/apache/hive/service/auth/ldap/TestCustomQueryFilter.java
new file mode 100644
index 0000000..fd4b898
--- /dev/null
+++ b/service/src/test/org/apache/hive/service/auth/ldap/TestCustomQueryFilter.java
@@ -0,0 +1,85 @@
+/**
+ * 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 java.io.IOException;
+import java.util.Arrays;
+import javax.naming.NamingException;
+import javax.security.sasl.AuthenticationException;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import org.junit.Before;
+import org.mockito.Mock;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TestCustomQueryFilter {
+
+  private static final String USER2_DN = "uid=user2,ou=People,dc=example,dc=com";
+  private static final String USER1_DN = "uid=user1,ou=People,dc=example,dc=com";
+  private static final String CUSTOM_QUERY = "(&(objectClass=person)(|(uid=user1)(uid=user2)))";
+
+  private FilterFactory factory;
+  private HiveConf conf;
+
+  @Mock
+  private DirSearch search;
+
+  @Before
+  public void setup() {
+    conf = new HiveConf();
+    conf.set("hive.root.logger", "DEBUG,console");
+    factory = new CustomQueryFilterFactory();
+  }
+
+  @Test
+  public void testFactory() {
+    conf.unset(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_CUSTOMLDAPQUERY.varname);
+    assertNull(factory.getInstance(conf));
+
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_CUSTOMLDAPQUERY, CUSTOM_QUERY);
+    assertNotNull(factory.getInstance(conf));
+  }
+
+  @Test
+  public void testApplyPositive() throws AuthenticationException, NamingException, IOException {
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_CUSTOMLDAPQUERY, CUSTOM_QUERY);
+
+    when(search.executeCustomQuery(eq(CUSTOM_QUERY))).thenReturn(Arrays.asList(USER1_DN, USER2_DN));
+
+    Filter filter = factory.getInstance(conf);
+    filter.apply(search, "user1");
+    filter.apply(search, "user2");
+  }
+
+
+  @Test(expected = AuthenticationException.class)
+  public void testApplyNegative() throws AuthenticationException, NamingException, IOException {
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_CUSTOMLDAPQUERY, CUSTOM_QUERY);
+
+    when(search.executeCustomQuery(eq(CUSTOM_QUERY))).thenReturn(Arrays.asList(USER1_DN, USER2_DN));
+
+    Filter filter = factory.getInstance(conf);
+    filter.apply(search, "user3");
+  }
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/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
new file mode 100644
index 0000000..0cc2ead
--- /dev/null
+++ b/service/src/test/org/apache/hive/service/auth/ldap/TestGroupFilter.java
@@ -0,0 +1,101 @@
+/**
+ * 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 java.io.IOException;
+import java.util.Arrays;
+import javax.naming.NamingException;
+import javax.security.sasl.AuthenticationException;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import org.junit.Before;
+import org.mockito.Mock;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TestGroupFilter {
+
+  private FilterFactory factory;
+  private HiveConf conf;
+
+  @Mock
+  private DirSearch search;
+
+  @Before
+  public void setup() {
+    conf = new HiveConf();
+    conf.set("hive.root.logger", "DEBUG,console");
+    factory = new GroupFilterFactory();
+  }
+
+  @Test
+  public void testFactory() {
+    conf.unset(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER.varname);
+    assertNull(factory.getInstance(conf));
+
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "G1");
+    assertNotNull(factory.getInstance(conf));
+  }
+
+  @Test
+  public void testApplyPositive() throws AuthenticationException, NamingException, IOException {
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "HiveUsers");
+
+    when(search.findUserDn(eq("user1")))
+        .thenReturn("cn=user1,ou=People,dc=example,dc=com");
+    when(search.findUserDn(eq("cn=user2,dc=example,dc=com")))
+        .thenReturn("cn=user2,ou=People,dc=example,dc=com");
+    when(search.findUserDn(eq("user3@mydomain.com")))
+        .thenReturn("cn=user3,ou=People,dc=example,dc=com");
+
+    when(search.findGroupsForUser(eq("cn=user1,ou=People,dc=example,dc=com")))
+        .thenReturn(Arrays.asList(
+            "cn=SuperUsers,ou=Groups,dc=example,dc=com",
+            "cn=Office1,ou=Groups,dc=example,dc=com",
+            "cn=HiveUsers,ou=Groups,dc=example,dc=com",
+            "cn=G1,ou=Groups,dc=example,dc=com"));
+    when(search.findGroupsForUser(eq("cn=user2,ou=People,dc=example,dc=com")))
+        .thenReturn(Arrays.asList(
+            "cn=HiveUsers,ou=Groups,dc=example,dc=com"));
+    when(search.findGroupsForUser(eq("cn=user3,ou=People,dc=example,dc=com")))
+        .thenReturn(Arrays.asList(
+            "cn=HiveUsers,ou=Groups,dc=example,dc=com",
+            "cn=G1,ou=Groups,dc=example,dc=com",
+            "cn=G2,ou=Groups,dc=example,dc=com"));
+
+    Filter filter = factory.getInstance(conf);
+    filter.apply(search, "user1");
+    filter.apply(search, "cn=user2,dc=example,dc=com");
+    filter.apply(search, "user3@mydomain.com");
+  }
+
+  @Test(expected = AuthenticationException.class)
+  public void testApplyNegative() 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"));
+
+    Filter filter = factory.getInstance(conf);
+    filter.apply(search, "user1");
+  }
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/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
new file mode 100644
index 0000000..499b624
--- /dev/null
+++ b/service/src/test/org/apache/hive/service/auth/ldap/TestLdapSearch.java
@@ -0,0 +1,209 @@
+/**
+ * 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 java.util.Arrays;
+import java.util.Collections;
+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.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+import static org.apache.hive.service.auth.ldap.LdapTestUtils.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TestLdapSearch {
+
+  @Mock
+  private DirContext ctx;
+
+  private HiveConf conf;
+  private LdapSearch search;
+
+  @Before
+  public void setup() {
+    conf = new HiveConf();
+  }
+
+  @Test
+  public void testClose() throws NamingException {
+    search = new LdapSearch(conf, ctx);
+    search.close();
+    verify(ctx, atLeastOnce()).close();
+  }
+
+  @Test
+  public void testFindUserDnWhenUserDnPositive() throws NamingException {
+    NamingEnumeration<SearchResult> searchResult = mockNamingEnumeration("CN=User1,OU=org1,DC=foo,DC=bar");
+    when(ctx.search(anyString(), anyString(), any(SearchControls.class)))
+        .thenReturn(searchResult)
+        .thenThrow(NamingException.class);
+    search = new LdapSearch(conf, ctx);
+    String expected = "CN=User1,OU=org1,DC=foo,DC=bar";
+    String actual = search.findUserDn("CN=User1,OU=org1");
+    assertEquals(expected, actual);
+  }
+
+  @Test
+  public void testFindUserDnWhenUserDnNegativeDuplicates() throws NamingException {
+    NamingEnumeration<SearchResult> searchResult = mockNamingEnumeration(
+            "CN=User1,OU=org1,DC=foo,DC=bar",
+            "CN=User1,OU=org2,DC=foo,DC=bar");
+    when(ctx.search(anyString(), anyString(), any(SearchControls.class))).thenReturn(searchResult);
+    search = new LdapSearch(conf, ctx);
+    assertNull(search.findUserDn("CN=User1,DC=foo,DC=bar"));
+  }
+
+  @Test
+  public void testFindUserDnWhenUserDnNegativeNone() throws NamingException {
+    NamingEnumeration<SearchResult> searchResult = mockEmptyNamingEnumeration();
+    when(ctx.search(anyString(), anyString(), any(SearchControls.class))).thenReturn(searchResult);
+    search = new LdapSearch(conf, ctx);
+    assertNull(search.findUserDn("CN=User1,DC=foo,DC=bar"));
+  }
+
+  @Test
+  public void testFindUserDnWhenUserPatternFoundBySecondPattern() throws NamingException {
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN,
+        "CN=%s,OU=org1,DC=foo,DC=bar:CN=%s,OU=org2,DC=foo,DC=bar");
+    NamingEnumeration<SearchResult> emptyResult = mockEmptyNamingEnumeration();
+    NamingEnumeration<SearchResult> validResult = mockNamingEnumeration("CN=User1,OU=org2,DC=foo,DC=bar");
+    when(ctx.search(anyString(), anyString(), any(SearchControls.class)))
+        .thenReturn(emptyResult)
+        .thenReturn(validResult);
+    search = new LdapSearch(conf, ctx);
+    String expected = "CN=User1,OU=org2,DC=foo,DC=bar";
+    String actual = search.findUserDn("User1");
+    assertEquals(expected, actual);
+    verify(ctx).search(eq("OU=org1,DC=foo,DC=bar"), contains("CN=User1"), any(SearchControls.class));
+    verify(ctx).search(eq("OU=org2,DC=foo,DC=bar"), contains("CN=User1"), any(SearchControls.class));
+  }
+
+  @Test
+  public void testFindUserDnWhenUserPatternFoundByFirstPattern() throws NamingException {
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN,
+        "CN=%s,OU=org1,DC=foo,DC=bar:CN=%s,OU=org2,DC=foo,DC=bar");
+    NamingEnumeration<SearchResult> emptyResult = mockEmptyNamingEnumeration();
+    NamingEnumeration<SearchResult> validResult = mockNamingEnumeration("CN=User1,OU=org2,DC=foo,DC=bar");
+    when(ctx.search(anyString(), anyString(), any(SearchControls.class)))
+        .thenReturn(validResult)
+        .thenReturn(emptyResult);
+    search = new LdapSearch(conf, ctx);
+    String expected = "CN=User1,OU=org2,DC=foo,DC=bar";
+    String actual = search.findUserDn("User1");
+    assertEquals(expected, actual);
+    verify(ctx).search(eq("OU=org1,DC=foo,DC=bar"), contains("CN=User1"), any(SearchControls.class));
+  }
+
+  @Test
+  public void testFindUserDnWhenUserPatternFoundByUniqueIdentifier() throws NamingException {
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN,
+        "CN=%s,OU=org1,DC=foo,DC=bar");
+    NamingEnumeration<SearchResult> validResult = mockNamingEnumeration("CN=User1,OU=org1,DC=foo,DC=bar");
+    when(ctx.search(anyString(), anyString(), any(SearchControls.class)))
+        .thenReturn(null)
+        .thenReturn(validResult);
+    search = new LdapSearch(conf, ctx);
+    String expected = "CN=User1,OU=org1,DC=foo,DC=bar";
+    String actual = search.findUserDn("User1");
+    assertEquals(expected, actual);
+    verify(ctx).search(eq("OU=org1,DC=foo,DC=bar"), contains("CN=User1"), any(SearchControls.class));
+    verify(ctx).search(eq("OU=org1,DC=foo,DC=bar"), contains("uid=User1"), any(SearchControls.class));
+  }
+
+  @Test
+  public void testFindUserDnWhenUserPatternFoundByUniqueIdentifierNegativeNone() throws NamingException {
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN,
+        "CN=%s,OU=org1,DC=foo,DC=bar");
+    when(ctx.search(anyString(), anyString(), any(SearchControls.class)))
+        .thenReturn(null)
+        .thenReturn(null);
+    search = new LdapSearch(conf, ctx);
+    assertNull(search.findUserDn("User1"));
+  }
+
+  @Test
+  public void testFindUserDnWhenUserPatternFoundByUniqueIdentifierNegativeMany() throws NamingException {
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN,
+        "CN=%s,OU=org1,DC=foo,DC=bar");
+    NamingEnumeration<SearchResult> manyResult = mockNamingEnumeration(
+        "CN=User1,OU=org1,DC=foo,DC=bar",
+        "CN=User12,OU=org1,DC=foo,DC=bar");
+    when(ctx.search(anyString(), anyString(), any(SearchControls.class)))
+        .thenReturn(null)
+        .thenReturn(manyResult);
+    search = new LdapSearch(conf, ctx);
+    assertNull(search.findUserDn("User1"));
+  }
+
+  @Test
+  public void testFindGroupsForUser() throws NamingException {
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPDNPATTERN,
+        "CN=%s,OU=org1,DC=foo,DC=bar");
+
+    NamingEnumeration<SearchResult> groupsResult = mockNamingEnumeration("CN=Group1,OU=org1,DC=foo,DC=bar");
+    when(ctx.search(eq("OU=org1,DC=foo,DC=bar"), contains("User1"), any(SearchControls.class)))
+        .thenReturn(groupsResult);
+
+    search = new LdapSearch(conf, ctx);
+
+    List<String> expected = Arrays.asList("CN=Group1,OU=org1,DC=foo,DC=bar");
+    List<String> actual = search.findGroupsForUser("CN=User1,OU=org1,DC=foo,DC=bar");
+    assertEquals(expected, actual);
+  }
+
+  @Test
+  public void testExecuteCustomQuery() throws NamingException {
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BASEDN, "dc=example,dc=com");
+
+    NamingEnumeration<SearchResult> customQueryResult = mockNamingEnumeration(
+        mockSearchResult(
+            "uid=group1,ou=Groups,dc=example,dc=com",
+            mockAttributes("member", "uid=user1,ou=People,dc=example,dc=com")),
+        mockSearchResult(
+            "uid=group2,ou=Groups,dc=example,dc=com",
+            mockAttributes("member", "uid=user2,ou=People,dc=example,dc=com"))
+        );
+
+    when(ctx.search(eq("dc=example,dc=com"), anyString(), any(SearchControls.class)))
+        .thenReturn(customQueryResult);
+
+    search = new LdapSearch(conf, ctx);
+
+    List<String> expected = Arrays.asList(
+        "uid=group1,ou=Groups,dc=example,dc=com",
+        "uid=user1,ou=People,dc=example,dc=com",
+        "uid=group2,ou=Groups,dc=example,dc=com",
+        "uid=user2,ou=People,dc=example,dc=com");
+    List<String> actual = search.executeCustomQuery("(&(objectClass=groupOfNames)(|(cn=group1)(cn=group2)))");
+    Collections.sort(expected);
+    Collections.sort(actual);
+    assertEquals(expected, actual);
+  }
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/service/src/test/org/apache/hive/service/auth/ldap/TestLdapUtils.java
----------------------------------------------------------------------
diff --git a/service/src/test/org/apache/hive/service/auth/ldap/TestLdapUtils.java b/service/src/test/org/apache/hive/service/auth/ldap/TestLdapUtils.java
new file mode 100644
index 0000000..661aff4
--- /dev/null
+++ b/service/src/test/org/apache/hive/service/auth/ldap/TestLdapUtils.java
@@ -0,0 +1,103 @@
+/**
+ * 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 java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class TestLdapUtils {
+
+  @Test
+  public void testCreateCandidatePrincipalsForUserDn() {
+    HiveConf conf = new HiveConf();
+    String userDn = "cn=user1,ou=CORP,dc=mycompany,dc=com";
+    List<String> expected = Arrays.asList(userDn);
+    List<String> actual = LdapUtils.createCandidatePrincipals(conf, userDn);
+    assertEquals(expected, actual);
+  }
+
+  @Test
+  public void testCreateCandidatePrincipalsForUserWithDomain() {
+    HiveConf conf = new HiveConf();
+    String userWithDomain = "user1@mycompany.com";
+    List<String> expected = Arrays.asList(userWithDomain);
+    List<String> actual = LdapUtils.createCandidatePrincipals(conf, userWithDomain);
+    assertEquals(expected, actual);
+  }
+
+  @Test
+  public void testCreateCandidatePrincipalsLdapDomain() {
+    HiveConf conf = new HiveConf();
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_DOMAIN, "mycompany.com");
+    List<String> expected = Arrays.asList("user1@mycompany.com");
+    List<String> actual = LdapUtils.createCandidatePrincipals(conf, "user1");
+    assertEquals(expected, actual);
+  }
+
+  @Test
+  public void testCreateCandidatePrincipalsUserPatternsDefaultBaseDn() {
+    HiveConf conf = new HiveConf();
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GUIDKEY, "sAMAccountName");
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BASEDN, "dc=mycompany,dc=com");
+    List<String> expected = Arrays.asList("sAMAccountName=user1,dc=mycompany,dc=com");
+    List<String> actual = LdapUtils.createCandidatePrincipals(conf, "user1");
+    assertEquals(expected, actual);
+  }
+
+  @Test
+  public void testCreateCandidatePrincipals() {
+    HiveConf conf = new HiveConf();
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BASEDN, "dc=mycompany,dc=com");
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN,
+        "cn=%s,ou=CORP1,dc=mycompany,dc=com:cn=%s,ou=CORP2,dc=mycompany,dc=com");
+    List<String> expected = Arrays.asList(
+        "cn=user1,ou=CORP1,dc=mycompany,dc=com",
+        "cn=user1,ou=CORP2,dc=mycompany,dc=com");
+    List<String> actual = LdapUtils.createCandidatePrincipals(conf, "user1");
+    Collections.sort(expected);
+    Collections.sort(actual);
+    assertEquals(expected, actual);
+  }
+
+  @Test
+  public void testExtractFirstRdn() {
+    String dn = "cn=user1,ou=CORP1,dc=mycompany,dc=com";
+    String expected = "cn=user1";
+    String actual = LdapUtils.extractFirstRdn(dn);
+    assertEquals(expected, actual);
+  }
+
+  @Test
+  public void testExtractBaseDn() {
+    String dn = "cn=user1,ou=CORP1,dc=mycompany,dc=com";
+    String expected = "ou=CORP1,dc=mycompany,dc=com";
+    String actual = LdapUtils.extractBaseDn(dn);
+    assertEquals(expected, actual);
+  }
+
+  @Test
+  public void testExtractBaseDnNegative() {
+    String dn = "cn=user1";
+    assertNull(LdapUtils.extractBaseDn(dn));
+  }
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/service/src/test/org/apache/hive/service/auth/ldap/TestQuery.java
----------------------------------------------------------------------
diff --git a/service/src/test/org/apache/hive/service/auth/ldap/TestQuery.java b/service/src/test/org/apache/hive/service/auth/ldap/TestQuery.java
new file mode 100644
index 0000000..1f4bb1a
--- /dev/null
+++ b/service/src/test/org/apache/hive/service/auth/ldap/TestQuery.java
@@ -0,0 +1,59 @@
+/**
+ * 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 org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class TestQuery {
+
+  @Test
+  public void testQueryBuilderFilter() {
+    Query q = Query.builder()
+        .filter("test <uid_attr>=<value> query")
+        .map("uid_attr", "uid")
+        .map("value", "Hello!")
+        .build();
+    assertEquals("test uid=Hello! query", q.getFilter());
+    assertEquals(0, q.getControls().getCountLimit());
+  }
+
+  @Test
+  public void testQueryBuilderLimit() {
+    Query q = Query.builder()
+        .filter("<key1>,<key2>")
+        .map("key1", "value1")
+        .map("key2", "value2")
+        .limit(8)
+        .build();
+    assertEquals("value1,value2", q.getFilter());
+    assertEquals(8, q.getControls().getCountLimit());
+  }
+
+  @Test
+  public void testQueryBuilderReturningAttributes() {
+    Query q = Query.builder()
+        .filter("(query)")
+        .returnAttribute("attr1")
+        .returnAttribute("attr2")
+        .build();
+    assertEquals("(query)", q.getFilter());
+    assertArrayEquals(new String[] {"attr1", "attr2"}, q.getControls().getReturningAttributes());
+  }
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/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
new file mode 100644
index 0000000..3054e33
--- /dev/null
+++ b/service/src/test/org/apache/hive/service/auth/ldap/TestQueryFactory.java
@@ -0,0 +1,79 @@
+/**
+ * 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 org.apache.hadoop.hive.conf.HiveConf;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class TestQueryFactory {
+
+  private QueryFactory queries;
+  private HiveConf conf;
+
+  @Before
+  public void setup() {
+    conf = new HiveConf();
+    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");
+    queries = new QueryFactory(conf);
+  }
+
+  @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);
+  }
+
+  @Test
+  public void testFindUserDnByRdn() {
+    Query q = queries.findUserDnByRdn("cn=User1");
+    String expected = "(&(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))(cn=User1))";
+    String actual = q.getFilter();
+    assertEquals(expected, actual);
+  }
+
+  @Test
+  public void testFindDnByPattern() {
+    Query q = queries.findDnByPattern("cn=User1");
+    String expected = "(cn=User1)";
+    String actual = q.getFilter();
+    assertEquals(expected, actual);
+  }
+
+  @Test
+  public void testFindUserDnByName() {
+    Query q = queries.findUserDnByName("unique_user_id");
+    String expected = "(&(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))(|(uid=unique_user_id)(sAMAccountName=unique_user_id)))";
+    String actual = q.getFilter();
+    assertEquals(expected, actual);
+  }
+
+  @Test
+  public void testFindGroupsForUser() {
+    Query q = queries.findGroupsForUser("user_name", "user_Dn");
+    String expected = "(&(objectClass=superGroups)(|(member=user_Dn)(member=user_name)))";
+    String actual = q.getFilter();
+    assertEquals(expected, actual);
+  }
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/service/src/test/org/apache/hive/service/auth/ldap/TestSearchResultHandler.java
----------------------------------------------------------------------
diff --git a/service/src/test/org/apache/hive/service/auth/ldap/TestSearchResultHandler.java b/service/src/test/org/apache/hive/service/auth/ldap/TestSearchResultHandler.java
new file mode 100644
index 0000000..2615680
--- /dev/null
+++ b/service/src/test/org/apache/hive/service/auth/ldap/TestSearchResultHandler.java
@@ -0,0 +1,222 @@
+/**
+ * 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 java.util.AbstractCollection;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.SearchResult;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+import static org.apache.hive.service.auth.ldap.LdapTestUtils.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TestSearchResultHandler {
+
+  SearchResultHandler handler;
+
+  @Test
+  public void testHandle() throws NamingException {
+    MockResultCollection resultCollection = MockResultCollection.create()
+        .addSearchResultWithDns("1")
+        .addSearchResultWithDns("2", "3");
+    handler = new SearchResultHandler(resultCollection);
+    List<String> expected = Arrays.asList("1", "2");
+    final List<String> actual = new ArrayList<>();
+    handler.handle(new SearchResultHandler.RecordProcessor() {
+      @Override
+      public boolean process(SearchResult record) throws NamingException {
+        actual.add(record.getNameInNamespace());
+        return actual.size() < 2;
+      }
+    });
+    assertEquals(expected, actual);
+    assertAllNamingEnumerationsClosed(resultCollection);
+  }
+
+  @Test
+  public void testGetAllLdapNamesNoRecords() throws NamingException {
+    MockResultCollection resultCollection = MockResultCollection.create()
+        .addEmptySearchResult();
+    handler = new SearchResultHandler(resultCollection);
+    List<String> actual = handler.getAllLdapNames();
+    assertEquals("Resultset size", 0, actual.size());
+    assertAllNamingEnumerationsClosed(resultCollection);
+  }
+
+  @Test
+  public void testGetAllLdapNamesWithExceptionInNamingEnumerationClose() throws NamingException {
+    MockResultCollection resultCollection = MockResultCollection.create()
+        .addSearchResultWithDns("1")
+        .addSearchResultWithDns("2");
+    doThrow(NamingException.class).when(resultCollection.iterator().next()).close();
+    handler = new SearchResultHandler(resultCollection);
+    List<String> actual = handler.getAllLdapNames();
+    assertEquals("Resultset size", 2, actual.size());
+    assertAllNamingEnumerationsClosed(resultCollection);
+  }
+
+  @Test
+  public void testGetAllLdapNames() throws NamingException {
+    String objectDn1 = "cn=a1,dc=b,dc=c";
+    String objectDn2 = "cn=a2,dc=b,dc=c";
+    String objectDn3 = "cn=a3,dc=b,dc=c";
+    MockResultCollection resultCollection = MockResultCollection.create()
+        .addSearchResultWithDns(objectDn1)
+        .addSearchResultWithDns(objectDn2, objectDn3);
+    handler = new SearchResultHandler(resultCollection);
+    List<String> expected = Arrays.asList(objectDn1, objectDn2, objectDn3);
+    Collections.sort(expected);
+    List<String> actual = handler.getAllLdapNames();
+    Collections.sort(actual);
+    assertEquals(expected, actual);
+    assertAllNamingEnumerationsClosed(resultCollection);
+  }
+
+  @Test
+  public void testGetAllLdapNamesAndAttributes() throws NamingException {
+    SearchResult searchResult1 = mockSearchResult("cn=a1,dc=b,dc=c",
+        mockAttributes("attr1", "attr1value1"));
+    SearchResult searchResult2 = mockSearchResult("cn=a2,dc=b,dc=c",
+        mockAttributes("attr1", "attr1value2", "attr2", "attr2value1"));
+    SearchResult searchResult3 = mockSearchResult("cn=a3,dc=b,dc=c",
+        mockAttributes("attr1", "attr1value3", "attr1", "attr1value4"));
+    SearchResult searchResult4 = mockSearchResult("cn=a4,dc=b,dc=c",
+        mockEmptyAttributes());
+
+    MockResultCollection resultCollection = MockResultCollection.create()
+        .addSearchResults(searchResult1)
+        .addSearchResults(searchResult2, searchResult3)
+        .addSearchResults(searchResult4);
+
+    handler = new SearchResultHandler(resultCollection);
+    List<String> expected = Arrays.asList(
+        "cn=a1,dc=b,dc=c", "attr1value1",
+        "cn=a2,dc=b,dc=c", "attr1value2", "attr2value1",
+        "cn=a3,dc=b,dc=c", "attr1value3", "attr1value4",
+        "cn=a4,dc=b,dc=c");
+    Collections.sort(expected);
+    List<String> actual = handler.getAllLdapNamesAndAttributes();
+    Collections.sort(actual);
+    assertEquals(expected, actual);
+    assertAllNamingEnumerationsClosed(resultCollection);
+  }
+
+  @Test
+  public void testHasSingleResultNoRecords() throws NamingException {
+    MockResultCollection resultCollection = MockResultCollection.create()
+        .addEmptySearchResult();
+    handler = new SearchResultHandler(resultCollection);
+    assertFalse(handler.hasSingleResult());
+    assertAllNamingEnumerationsClosed(resultCollection);
+  }
+
+  @Test
+  public void testHasSingleResult() throws NamingException {
+    MockResultCollection resultCollection = MockResultCollection.create()
+        .addSearchResultWithDns("1");
+    handler = new SearchResultHandler(resultCollection);
+    assertTrue(handler.hasSingleResult());
+    assertAllNamingEnumerationsClosed(resultCollection);
+  }
+
+  @Test
+  public void testHasSingleResultManyRecords() throws NamingException {
+    MockResultCollection resultCollection = MockResultCollection.create()
+        .addSearchResultWithDns("1")
+        .addSearchResultWithDns("2");
+    handler = new SearchResultHandler(resultCollection);
+    assertFalse(handler.hasSingleResult());
+    assertAllNamingEnumerationsClosed(resultCollection);
+  }
+
+  @Test(expected = NamingException.class)
+  public void testGetSingleLdapNameNoRecords() throws NamingException {
+    MockResultCollection resultCollection = MockResultCollection.create()
+        .addEmptySearchResult();
+    handler = new SearchResultHandler(resultCollection);
+    try {
+      handler.getSingleLdapName();
+    } finally {
+      assertAllNamingEnumerationsClosed(resultCollection);
+    }
+  }
+
+  @Test
+  public void testGetSingleLdapName() throws NamingException {
+    String objectDn = "cn=a,dc=b,dc=c";
+    MockResultCollection resultCollection = MockResultCollection.create()
+        .addEmptySearchResult()
+        .addSearchResultWithDns(objectDn);
+
+    handler = new SearchResultHandler(resultCollection);
+    String expected = objectDn;
+    String actual = handler.getSingleLdapName();
+    assertEquals(expected, actual);
+    assertAllNamingEnumerationsClosed(resultCollection);
+  }
+
+  private void assertAllNamingEnumerationsClosed(MockResultCollection resultCollection) throws NamingException {
+    for (NamingEnumeration<SearchResult> namingEnumeration : resultCollection) {
+      verify(namingEnumeration, atLeastOnce()).close();
+    }
+  }
+
+  private static final class MockResultCollection extends AbstractCollection<NamingEnumeration<SearchResult>> {
+
+    List<NamingEnumeration<SearchResult>> results = new ArrayList<>();
+
+    static MockResultCollection create() {
+      return new MockResultCollection();
+    }
+
+    MockResultCollection addSearchResultWithDns(String... dns) throws NamingException {
+      results.add(mockNamingEnumeration(dns));
+      return this;
+    }
+
+    MockResultCollection addSearchResults(SearchResult... dns) throws NamingException {
+      results.add(mockNamingEnumeration(dns));
+      return this;
+    }
+
+    MockResultCollection addEmptySearchResult() throws NamingException {
+      addSearchResults();
+      return this;
+    }
+
+    @Override
+    public Iterator<NamingEnumeration<SearchResult>> iterator() {
+      return results.iterator();
+    }
+
+    @Override
+    public int size() {
+      return results.size();
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/service/src/test/org/apache/hive/service/auth/ldap/TestUserFilter.java
----------------------------------------------------------------------
diff --git a/service/src/test/org/apache/hive/service/auth/ldap/TestUserFilter.java b/service/src/test/org/apache/hive/service/auth/ldap/TestUserFilter.java
new file mode 100644
index 0000000..f941c9c
--- /dev/null
+++ b/service/src/test/org/apache/hive/service/auth/ldap/TestUserFilter.java
@@ -0,0 +1,75 @@
+/**
+ * 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 java.io.IOException;
+import javax.naming.NamingException;
+import javax.security.sasl.AuthenticationException;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import org.junit.Before;
+import org.mockito.Mock;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TestUserFilter {
+
+  private FilterFactory factory;
+  private HiveConf conf;
+
+  @Mock
+  private DirSearch search;
+
+  @Before
+  public void setup() {
+    conf = new HiveConf();
+    factory = new UserFilterFactory();
+  }
+
+  @Test
+  public void testFactory() {
+    conf.unset(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER.varname);
+    assertNull(factory.getInstance(conf));
+
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER, "User1");
+    assertNotNull(factory.getInstance(conf));
+  }
+
+  @Test
+  public void testApplyPositive() throws AuthenticationException, NamingException, IOException {
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER, "User1,User2,uSeR3");
+
+    Filter filter = factory.getInstance(conf);
+    filter.apply(search, "User1");
+    filter.apply(search, "uid=user2,ou=People,dc=example,dc=com");
+    filter.apply(search, "User3@mydomain.com");
+  }
+
+  @Test(expected = AuthenticationException.class)
+  public void testApplyNegative() throws AuthenticationException, NamingException, IOException {
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER, "User1,User2");
+
+    Filter filter = factory.getInstance(conf);
+    filter.apply(search, "User3");
+  }
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/service/src/test/org/apache/hive/service/auth/ldap/TestUserSearchFilter.java
----------------------------------------------------------------------
diff --git a/service/src/test/org/apache/hive/service/auth/ldap/TestUserSearchFilter.java b/service/src/test/org/apache/hive/service/auth/ldap/TestUserSearchFilter.java
new file mode 100644
index 0000000..0f2b509
--- /dev/null
+++ b/service/src/test/org/apache/hive/service/auth/ldap/TestUserSearchFilter.java
@@ -0,0 +1,94 @@
+/**
+ * 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 java.io.IOException;
+import javax.naming.NamingException;
+import javax.security.sasl.AuthenticationException;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TestUserSearchFilter {
+
+  private FilterFactory factory;
+  private HiveConf conf;
+
+  @Mock
+  private DirSearch search;
+
+  @Before
+  public void setup() {
+    conf = new HiveConf();
+    factory = new UserSearchFilterFactory();
+  }
+
+  @Test
+  public void testFactoryWhenNoGroupOrUserFilters() {
+    assertNull(factory.getInstance(conf));
+  }
+
+  @Test
+  public void testFactoryWhenGroupFilter() {
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "Grp1,Grp2");
+    assertNotNull(factory.getInstance(conf));
+  }
+
+  @Test
+  public void testFactoryWhenUserFilter() {
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER, "User1,User2");
+    assertNotNull(factory.getInstance(conf));
+  }
+
+  @Test
+  public void testApplyPositive() throws AuthenticationException, NamingException, IOException {
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER, "User1");
+    Filter filter = factory.getInstance(conf);
+
+    when(search.findUserDn(anyString())).thenReturn("cn=User1,ou=People,dc=example,dc=com");
+
+    filter.apply(search, "User1");
+  }
+
+  @Test(expected = AuthenticationException.class)
+  public void testApplyWhenNamingException() throws AuthenticationException, NamingException, IOException {
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER, "User1");
+    Filter filter = factory.getInstance(conf);
+
+    when(search.findUserDn(anyString())).thenThrow(NamingException.class);
+
+    filter.apply(search, "User3");
+  }
+
+  @Test(expected = AuthenticationException.class)
+  public void testApplyWhenNotFound() throws AuthenticationException, NamingException, IOException {
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER, "User1");
+    Filter filter = factory.getInstance(conf);
+
+    when(search.findUserDn(anyString())).thenReturn(null);
+
+    filter.apply(search, "User3");
+  }
+}


[2/2] hive git commit: HIVE-14713: LDAP Authentication Provider should be covered with unit tests (Illya Yalovyy, reviewed by Chaoyu Tang, Szehon Ho)

Posted by ct...@apache.org.
HIVE-14713: LDAP Authentication Provider should be covered with unit tests (Illya Yalovyy, reviewed by Chaoyu Tang, Szehon Ho)


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

Branch: refs/heads/master
Commit: 990927e3dcddcc7c82a16437d55d9f7ea9a1a447
Parents: 421d97a
Author: ctang <ct...@cloudera.com>
Authored: Fri Sep 23 15:50:32 2016 -0400
Committer: ctang <ct...@cloudera.com>
Committed: Fri Sep 23 15:50:32 2016 -0400

----------------------------------------------------------------------
 service/pom.xml                                 |   7 +
 .../auth/LdapAuthenticationProviderImpl.java    | 657 ++-----------------
 .../service/auth/ldap/ChainFilterFactory.java   |  78 +++
 .../auth/ldap/CustomQueryFilterFactory.java     |  84 +++
 .../hive/service/auth/ldap/DirSearch.java       |  52 ++
 .../service/auth/ldap/DirSearchFactory.java     |  37 ++
 .../apache/hive/service/auth/ldap/Filter.java   |  36 +
 .../hive/service/auth/ldap/FilterFactory.java   |  33 +
 .../service/auth/ldap/GroupFilterFactory.java   |  90 +++
 .../hive/service/auth/ldap/LdapSearch.java      | 155 +++++
 .../service/auth/ldap/LdapSearchFactory.java    |  64 ++
 .../hive/service/auth/ldap/LdapUtils.java       | 228 +++++++
 .../apache/hive/service/auth/ldap/Query.java    | 154 +++++
 .../hive/service/auth/ldap/QueryFactory.java    | 135 ++++
 .../service/auth/ldap/SearchResultHandler.java  | 163 +++++
 .../service/auth/ldap/UserFilterFactory.java    |  75 +++
 .../auth/ldap/UserSearchFilterFactory.java      |  65 ++
 .../auth/TestLdapAtnProviderWithMiniDS.java     |   3 +-
 .../TestLdapAuthenticationProviderImpl.java     | 277 +++++++-
 .../hive/service/auth/ldap/Credentials.java     |  41 ++
 .../hive/service/auth/ldap/LdapTestUtils.java   | 126 ++++
 .../hive/service/auth/ldap/TestChainFilter.java | 103 +++
 .../auth/ldap/TestCustomQueryFilter.java        |  85 +++
 .../hive/service/auth/ldap/TestGroupFilter.java | 101 +++
 .../hive/service/auth/ldap/TestLdapSearch.java  | 209 ++++++
 .../hive/service/auth/ldap/TestLdapUtils.java   | 103 +++
 .../hive/service/auth/ldap/TestQuery.java       |  59 ++
 .../service/auth/ldap/TestQueryFactory.java     |  79 +++
 .../auth/ldap/TestSearchResultHandler.java      | 222 +++++++
 .../hive/service/auth/ldap/TestUserFilter.java  |  75 +++
 .../service/auth/ldap/TestUserSearchFilter.java |  94 +++
 31 files changed, 3062 insertions(+), 628 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/service/pom.xml
----------------------------------------------------------------------
diff --git a/service/pom.xml b/service/pom.xml
index ecea719..9306739 100644
--- a/service/pom.xml
+++ b/service/pom.xml
@@ -164,6 +164,13 @@
     </dependency>
 
     <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-all</artifactId>
+      <version>${mockito-all.version}</version>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
       <groupId>org.apache.directory.client.ldap</groupId>
       <artifactId>ldap-client-api</artifactId>
       <version>${apache-directory-clientapi.version}</version>

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/service/src/java/org/apache/hive/service/auth/LdapAuthenticationProviderImpl.java
----------------------------------------------------------------------
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 efd5393..c21da28 100644
--- a/service/src/java/org/apache/hive/service/auth/LdapAuthenticationProviderImpl.java
+++ b/service/src/java/org/apache/hive/service/auth/LdapAuthenticationProviderImpl.java
@@ -17,633 +17,106 @@
  */
 package org.apache.hive.service.auth;
 
-import java.util.ArrayList;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.ListIterator;
-
-import javax.naming.Context;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.InitialDirContext;
-import javax.naming.directory.SearchControls;
-import javax.naming.directory.SearchResult;
 import javax.security.sasl.AuthenticationException;
 
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import java.util.Iterator;
+import java.util.List;
+import org.apache.commons.lang.StringUtils;
 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.LdapUtils;
+import org.apache.hive.service.auth.ldap.UserFilterFactory;
+import org.apache.hive.service.auth.ldap.UserSearchFilterFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 public class LdapAuthenticationProviderImpl implements PasswdAuthenticationProvider {
 
-  private static final Logger LOG     = LoggerFactory.getLogger(LdapAuthenticationProviderImpl.class);
+  private static final Logger LOG = LoggerFactory.getLogger(LdapAuthenticationProviderImpl.class);
 
-  private String ldapURL;
-  private String baseDN;
-  private String ldapDomain;
-  private static List<String> groupBases;
-  private static List<String> userBases;
-  private static List<String> userFilter;
-  private static List<String> groupFilter;
-  private String customQuery;
-  private static String guid_attr;
-  private static String groupMembership_attr;
-  private static String groupClass_attr;
-
-  LdapAuthenticationProviderImpl(HiveConf conf) {
-    init(conf);
-  }
-
-  protected void init(HiveConf conf) {
-    ldapURL     = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_URL);
-    baseDN      = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BASEDN);
-    ldapDomain  = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_DOMAIN);
-    customQuery = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_CUSTOMLDAPQUERY);
-    guid_attr   = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GUIDKEY);
-    groupBases  = new ArrayList<String>();
-    userBases   = new ArrayList<String>();
-    userFilter  = new ArrayList<String>();
-    groupFilter = new ArrayList<String>();
-
-    String groupDNPatterns = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPDNPATTERN);
-    String groupFilterVal  = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER);
-    String userDNPatterns  = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN);
-    String userFilterVal   = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER);
-    groupMembership_attr   = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPMEMBERSHIP_KEY);
-    groupClass_attr        = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPCLASS_KEY);
-
-    // parse COLON delimited root DNs for users/groups that may or may not be under BaseDN.
-    // Expect the root DNs be fully qualified including the baseDN
-    if (groupDNPatterns != null && groupDNPatterns.trim().length() > 0) {
-      String[] groupTokens = groupDNPatterns.split(":");
-      for (int i = 0; i < groupTokens.length; i++) {
-        if (groupTokens[i].contains(",") && groupTokens[i].contains("=")) {
-          groupBases.add(groupTokens[i]);
-        } else {
-          LOG.warn("Unexpected format for " + HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPDNPATTERN
-                       + "..ignoring " + groupTokens[i]);
-        }
-      }
-    } else if (baseDN != null) {
-      groupBases.add(guid_attr + "=%s," + baseDN);
-    }
+  private static final List<FilterFactory> FILTER_FACTORIES = ImmutableList.<FilterFactory>of(
+      new CustomQueryFilterFactory(),
+      new ChainFilterFactory(new UserSearchFilterFactory(), new UserFilterFactory(),
+          new GroupFilterFactory())
+  );
 
-    if (groupFilterVal != null && groupFilterVal.trim().length() > 0) {
-      String[] groups = groupFilterVal.split(",");
-      for (int i = 0; i < groups.length; i++) {
-        if (LOG.isDebugEnabled()) {
-          LOG.debug("Filtered group: " + groups[i]);
-        }
-        groupFilter.add(groups[i]);
-      }
-    }
+  private final HiveConf conf;
+  private final Filter filter;
+  private final DirSearchFactory searchFactory;
 
-    if (userDNPatterns != null && userDNPatterns.trim().length() > 0) {
-      String[] userTokens = userDNPatterns.split(":");
-      for (int i = 0; i < userTokens.length; i++) {
-        if (userTokens[i].contains(",") && userTokens[i].contains("=")) {
-          userBases.add(userTokens[i]);
-        } else {
-          LOG.warn("Unexpected format for " + HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN
-                       + "..ignoring " + userTokens[i]);
-        }
-      }
-    } else if (baseDN != null) {
-      userBases.add(guid_attr + "=%s," + baseDN);
-    }
+  public LdapAuthenticationProviderImpl(HiveConf conf) {
+    this(conf, new LdapSearchFactory());
+  }
 
-    if (userFilterVal != null && userFilterVal.trim().length() > 0) {
-      String[] users = userFilterVal.split(",");
-      for (int i = 0; i < users.length; i++) {
-        if (LOG.isDebugEnabled()) {
-          LOG.debug("Filtered user: " + users[i]);
-        }
-        userFilter.add(users[i]);
-      }
-    }
+  @VisibleForTesting
+  LdapAuthenticationProviderImpl(HiveConf conf, DirSearchFactory searchFactory) {
+    this.conf = conf;
+    this.searchFactory = searchFactory;
+    filter = resolveFilter(conf);
   }
 
   @Override
   public void Authenticate(String user, String password) throws AuthenticationException {
-
-    Hashtable<String, Object> env = new Hashtable<String, Object>();
-    env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
-    env.put(Context.PROVIDER_URL, ldapURL);
-
-    // If the domain is available in the config, then append it unless domain is
-    // already part of the username. LDAP providers like Active Directory use a
-    // fully qualified user name like foo@bar.com.
-    if (!hasDomain(user) && ldapDomain != null) {
-      user  = user + "@" + ldapDomain;
-    }
-
-    if (password == null || password.isEmpty() || password.getBytes()[0] == 0) {
-      throw new AuthenticationException("Error validating LDAP user:" +
-          " a null or blank password has been provided");
-    }
-
-    env.put(Context.SECURITY_AUTHENTICATION, "simple");
-    env.put(Context.SECURITY_CREDENTIALS, password);
-
-    // setup the security principal
-    String bindDN   = null;
-    DirContext ctx  = null;
-    String userDN   = null;
-    String userName = null;
-    Exception ex    = null;
-
-    if (!isDN(user) && !hasDomain(user) && userBases.size() > 0) {
-      ListIterator<String> listIter = userBases.listIterator();
-      while (listIter.hasNext()) {
-        try {
-          bindDN = listIter.next().replaceAll("%s", user);
-          env.put(Context.SECURITY_PRINCIPAL, bindDN);
-          LOG.debug("Connecting using DN " + bindDN + " at url " + ldapURL);
-          ctx = new InitialDirContext(env);
-          break;
-        } catch (NamingException e) {
-          ex = e;
-        }
-      }
-    } else {
-      env.put(Context.SECURITY_PRINCIPAL, user);
-      LOG.debug("Connecting using principal " + user + " at url " + ldapURL);
-      try {
-        ctx = new InitialDirContext(env);
-      } catch (NamingException e) {
-        ex = e;
-      }
-    }
-
-    if (ctx == null) {
-      LOG.debug("Could not connect to the LDAP Server:Authentication failed for " + user);
-      throw new AuthenticationException("LDAP Authentication failed for user", ex);
-    }
-
-    LOG.debug("Connected using principal=" + user + " at url=" + ldapURL);
+    DirSearch search = null;
     try {
-      if (isDN(user) || hasDomain(user)) {
-        userName = extractName(user);
-      } else {
-        userName = user;
-      }
-
-      // if a custom LDAP query is specified, it takes precedence over other configuration properties.
-      // if the user being authenticated is part of the resultset from the custom query, it succeeds.
-      if (customQuery != null) {
-        List<String> resultList = executeLDAPQuery(ctx, customQuery, baseDN);
-        if (resultList != null) {
-          for (String matchedDN : resultList) {
-            LOG.info("<queried user=" + matchedDN.split(",",2)[0].split("=",2)[1] + ",user=" + user + ">");
-            if (matchedDN.split(",",2)[0].split("=",2)[1].equalsIgnoreCase(user) ||
-                matchedDN.equalsIgnoreCase(user)) {
-              LOG.info("Authentication succeeded based on result set from LDAP query");
-              return;
-            }
-          }
-        }
-        LOG.info("Authentication failed based on result set from custom LDAP query");
-        throw new AuthenticationException("Authentication failed: LDAP query " +
-            "from property returned no data");
-      } else if (userBases.size() > 0) {
-        if (isDN(user)) {
-          userDN = findUserDNByDN(ctx, user);
-        } else {
-          if (userDN == null) {
-            userDN = findUserDNByPattern(ctx, userName);
-          }
-
-          if (userDN == null) {
-            userDN = findUserDNByName(ctx, userName);
-          }
-        }
-
-        // 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 failed");
-        }
-
-        // This section checks if the user satisfies the specified user filter.
-        if (userFilter.size() > 0) {
-          LOG.info("Authenticating user " + user + " using user filter");
-
-          if (userDN != null) {
-            LOG.info("User filter partially satisfied");
-          }
-
-          boolean success = false;
-          for (String filteredUser : userFilter) {
-            if (filteredUser.equalsIgnoreCase(userName)) {
-              LOG.debug("User filter entirely satisfied");
-              success = true;
-              break;
-            }
-          }
-
-          if (!success) {
-            LOG.info("Authentication failed based on user membership");
-            throw new AuthenticationException("Authentication failed: User not a member " +
-                "of specified list");
-          }
-        }
-
-        // This section checks if the user satisfies the specified user filter.
-        if (groupFilter.size() > 0) {
-          LOG.debug("Authenticating user " + user + " using group membership");
-          List<String> userGroups = getGroupsForUser(ctx, userDN);
-          if (LOG.isDebugEnabled()) {
-            LOG.debug("User member of :");
-            prettyPrint(userGroups);
-          }
-
-          if (userGroups != null) {
-            for (String elem : userGroups) {
-              String shortName = ((elem.split(","))[0].split("="))[1];
-              if (groupFilter.contains(shortName)) {
-                LOG.info("Authentication succeeded based on group membership");
-                return;
-              }
-            }
-          }
-
-          LOG.debug("Authentication failed: User is not a member of configured groups");
-          throw new AuthenticationException("Authentication failed: User not a member of " +
-              "listed groups");
-        }
-        LOG.info("Authentication succeeded using ldap user search");
-        return;
-      }
-      // Ideally we should not be here. Indicates partially configured LDAP Service.
-      // We allow it for now for backward compatibility.
-      LOG.info("Simple password authentication succeeded");
-    } catch (NamingException e) {
-      throw new AuthenticationException("LDAP Authentication failed for user", e);
+      search = createDirSearch(user, password);
+      applyFilter(search, user);
     } finally {
-      try {
-        if (ctx != null) {
-          ctx.close();
-        }
-      } catch(Exception e) {
-        LOG.warn("Exception when closing LDAP context:" + e.getMessage());
-      }
+      ServiceUtils.cleanup(LOG, search);
     }
   }
 
-  private boolean hasDomain(String userName) {
-    return (ServiceUtils.indexOfDomainMatch(userName) > 0);
-  }
-
-  private static void prettyPrint(List<String> list) {
-    for (String elem : list) {
-      LOG.debug("    " + elem);
+  private DirSearch createDirSearch(String user, String password) throws AuthenticationException {
+    if (StringUtils.isBlank(user)) {
+      throw new AuthenticationException("Error validating LDAP user:"
+          + " a null or blank user name has been provided");
     }
-  }
-
-  private static void prettyPrint(Attributes attrs) {
-    NamingEnumeration<? extends Attribute> set = attrs.getAll();
-    try {
-      NamingEnumeration<?> list = null;
-      while (set.hasMore()) {
-        Attribute attr = set.next();
-        list = attr.getAll();
-        String attrVals = "";
-        while (list.hasMore()) {
-          attrVals += list.next() + "+";
-        }
-        LOG.debug(attr.getID() + ":::" + attrVals);
-      }
-    } catch (Exception e) {
-      System.out.println("Error occurred when reading ldap data:" + e.getMessage());
+    if (StringUtils.isBlank(password) || password.getBytes()[0] == 0) {
+      throw new AuthenticationException("Error validating LDAP user:"
+          + " a null or blank password has been provided");
     }
-  }
-
-  /**
-   * This helper method attempts to find a DN given a unique groupname.
-   * Various LDAP implementations have different keys/properties that store this unique ID.
-   * So the first attempt is to find an entity with objectClass=group && CN=groupName
-   * @param ctx DirContext for the LDAP Connection.
-   * @param baseDN BaseDN for this LDAP directory where the search is to be performed.
-   * @param groupName A unique groupname that is to be located in the LDAP.
-   * @return LDAP DN if the group is found in LDAP, null otherwise.
-   */
-  public static String findGroupDNByName(DirContext ctx, String baseDN, String groupName)
-    throws NamingException {
-    String searchFilter  = "(&(objectClass=" + groupClass_attr + ")(" + guid_attr + "=" + groupName + "))";
-    List<String> results = null;
-
-    results = findDNByName(ctx, baseDN, searchFilter, 2);
-
-    if (results == null) {
-      return null;
-    } else if (results.size() > 1) {
-      //make sure there is not another item available, there should be only 1 match
-      LOG.info("Matched multiple groups for the group: " + groupName + ",returning null");
-      return null;
-    }
-    return results.get(0);
-  }
-
-  /**
-   * This helper method attempts to find an LDAP group entity given a unique name using a
-   * user-defined pattern for GROUPBASE.The list of group bases is defined by the user via property
-   * "hive.server2.authentication.ldap.groupDNPattern" in the hive-site.xml.
-   * Users can use %s where the actual groupname is to be substituted in the LDAP Query.
-   * @param ctx DirContext for the LDAP Connection.
-   * @param groupName A unique groupname that is to be located in the LDAP.
-   * @return LDAP DN of given group if found in the directory, null otherwise.
-   */
-  public static String findGroupDNByPattern(DirContext ctx, String groupName)
-      throws NamingException {
-    return findDNByPattern(ctx, groupName, groupBases);
-  }
-
-  public static String findDNByPattern(DirContext ctx, String name, List<String> nodes)
-      throws NamingException {
-    String searchFilter;
-    String searchBase;
-    SearchResult searchResult = null;
-    NamingEnumeration<SearchResult> results;
-
-    String[] returnAttributes     = new String[0]; // empty set
-    SearchControls searchControls = new SearchControls();
-
-    searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
-    searchControls.setReturningAttributes(returnAttributes);
-
-    for (String node : nodes) {
-      searchFilter = "(" + (node.substring(0,node.indexOf(","))).replaceAll("%s", name) + ")";
-      searchBase   = node.split(",",2)[1];
-      results      = ctx.search(searchBase, searchFilter, searchControls);
-
-      if(results.hasMoreElements()) {
-        searchResult = results.nextElement();
-        //make sure there is not another item available, there should be only 1 match
-        if(results.hasMoreElements()) {
-          LOG.warn("Matched multiple entities for the name: " + name);
-          return null;
+    List<String> principals = LdapUtils.createCandidatePrincipals(conf, user);
+    for (Iterator<String> iterator = principals.iterator(); iterator.hasNext();) {
+      String principal = iterator.next();
+      try {
+        return searchFactory.getInstance(conf, principal, password);
+      } catch (AuthenticationException ex) {
+        if (!iterator.hasNext()) {
+          throw ex;
         }
-        return searchResult.getNameInNamespace();
       }
     }
-    return null;
+    throw new AuthenticationException(
+        String.format("No candidate principals for %s was found.", user));
   }
 
-  /**
-   * This helper method attempts to find a DN given a unique username.
-   * Various LDAP implementations have different keys/properties that store this unique userID.
-   * Active Directory has a "sAMAccountName" that appears reliable,openLDAP uses "uid"
-   * So the first attempt is to find an entity with objectClass=person||user where
-   * (uid||sAMAccountName) matches the given username.
-   * The second attempt is to use CN attribute for wild card matching and then match the
-   * username in the DN.
-   * @param ctx DirContext for the LDAP Connection.
-   * @param baseDN BaseDN for this LDAP directory where the search is to be performed.
-   * @param userName A unique userid that is to be located in the LDAP.
-   * @return LDAP DN if the user is found in LDAP, null otherwise.
-   */
-  public static String findUserDNByName(DirContext ctx, String userName)
-      throws NamingException {
-    if (userBases.size() == 0) {
-      return null;
-    }
-
-    String baseFilter    = "(&(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))";
-    String suffix[]      = new String[] {
-                             "(|(uid=" + userName + ")(sAMAccountName=" + userName + ")))",
-                             "(|(cn=*" + userName + "*)))"
-                           };
-
-    String searchFilter           = null;
-    List<String> results          = null;
-    ListIterator<String> listIter = userBases.listIterator();
-
-    for (int i = 0; i < suffix.length; i++) {
-      searchFilter = baseFilter + suffix[i];
-
-      while (listIter.hasNext()) {
-        results = findDNByName(ctx, listIter.next().split(",",2)[1], searchFilter, 2);
-
-        if(results == null) {
-          continue;
-        }
-
-        if(results != null && results.size() > 1) {
-          //make sure there is not another item available, there should be only 1 match
-          LOG.info("Matched multiple users for the user: " + userName + ",returning null");
-          return null;
-        }
-        return results.get(0);
+  private static Filter resolveFilter(HiveConf conf) {
+    for (FilterFactory filterProvider : FILTER_FACTORIES) {
+      Filter filter = filterProvider.getInstance(conf);
+      if (filter != null) {
+        return filter;
       }
     }
     return null;
   }
 
-  /**
-   * This helper method attempts to find a username given a DN.
-   * Various LDAP implementations have different keys/properties that store this unique userID.
-   * Active Directory has a "sAMAccountName" that appears reliable,openLDAP uses "uid"
-   * So the first attempt is to find an entity with objectClass=person||user where
-   * (uid||sAMAccountName) matches the given username.
-   * The second attempt is to use CN attribute for wild card matching and then match the
-   * username in the DN.
-   * @param ctx DirContext for the LDAP Connection.
-   * @param baseDN BaseDN for this LDAP directory where the search is to be performed.
-   * @param userName A unique userid that is to be located in the LDAP.
-   * @return LDAP DN if the user is found in LDAP, null otherwise.
-   */
-  public static String findUserDNByDN(DirContext ctx, String userDN)
-      throws NamingException {
-    if (!isDN(userDN)) {
-      return null;
-    }
-
-    String baseDN        = extractBaseDN(userDN);
-    List<String> results = null;
-    // we are using the first part of the userDN in the search criteria.
-    // We know the DN is legal as we are able to bind with it, this is to confirm that its a user.
-    String searchFilter  = "(&(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))("
-                             +  userDN.substring(0,userDN.indexOf(",")) + "))";
-
-    results = findDNByName(ctx, baseDN, searchFilter, 2);
-
-    if (results == null) {
-      return null;
-    }
-
-    if(results.size() > 1) {
-      //make sure there is not another item available, there should be only 1 match
-      LOG.info("Matched multiple users for the user: " + userDN + ",returning null");
-      return null;
-    }
-    return results.get(0);
-  }
-
-  public static List<String> findDNByName(DirContext ctx, String baseDN,
-      String searchString, int limit) throws NamingException {
-    SearchResult searchResult     = null;
-    List<String> retValues        = null;
-    String matchedDN              = null;
-    SearchControls searchControls = new SearchControls();
-    String[] returnAttributes     = new String[0]; //empty set
-
-    searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
-    searchControls.setReturningAttributes(returnAttributes);
-    if (limit > 0) {
-      searchControls.setCountLimit(limit); // limit the result set to limit the size of resultset
-    }
-
-    NamingEnumeration<SearchResult> results = ctx.search(baseDN, searchString, searchControls);
-    while(results.hasMoreElements()) {
-      searchResult = results.nextElement();
-      matchedDN    = searchResult.getNameInNamespace();
-
-      if (retValues == null) {
-        retValues = new ArrayList<String>();
-      }
-      retValues.add(matchedDN);
-    }
-    return retValues;
-  }
-
-  /**
-   * This helper method attempts to find a UserDN given a unique username from a
-   * user-defined pattern for USERBASE. The list of user bases is defined by the user
-   * via property "hive.server2.authentication.ldap.userDNPattern" in the hive-site.xml.
-   * Users can use %s where the actual username is to be subsituted in the LDAP Query.
-   * @param ctx DirContext for the LDAP Connection.
-   * @param userName A unique userid that is to be located in the LDAP.
-   * @return LDAP DN of given user if found in the directory, null otherwise.
-   */
-  public static String findUserDNByPattern(DirContext ctx, String userName)
-      throws NamingException {
-    return findDNByPattern(ctx, userName, userBases);
-  }
-
-  /**
-   * This helper method finds all the groups a given user belongs to.
-   * This method relies on the attribute,configurable via HIVE_SERVER2_PLAIN_LDAP_GROUPMEMBERSHIP_KEY,
-   * being set on the user entry that references the group. The returned list ONLY includes direct
-   * groups the user belongs to. Parent groups of these direct groups are NOT included.
-   * @param ctx DirContext for the LDAP Connection.
-   * @param userDN A unique userDN that is to be located in the LDAP.
-   * @return List of Group DNs the user belongs to, emptylist otherwise.
-   */
-  public static List<String> getGroupsForUser(DirContext ctx, String userDN)
-      throws NamingException {
-    List<String> groupList        = new ArrayList<String>();
-    String user                   = extractName(userDN);
-    String searchFilter           = "(&(objectClass=" + groupClass_attr + ")(|(" +
-                                      groupMembership_attr + "=" + userDN + ")(" +
-                                      groupMembership_attr + "=" + user + ")))";
-    SearchControls searchControls = new SearchControls();
-    NamingEnumeration<SearchResult> results = null;
-    SearchResult result = null;
-    String groupBase = null;
-
-    LOG.debug("getGroupsForUser:searchFilter=" + searchFilter);
-    String[] attrIDs = new String[0];
-    searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
-    searchControls.setReturningAttributes(attrIDs);
-
-    ListIterator<String> listIter = groupBases.listIterator();
-    while (listIter.hasNext()) {
-      try {
-        groupBase = listIter.next().split(",", 2)[1];
-        LOG.debug("Searching for groups under " + groupBase);
-        results   = ctx.search(groupBase, searchFilter, searchControls);
-
-        while(results.hasMoreElements()) {
-          result = results.nextElement();
-          LOG.debug("Found Group:" + result.getNameInNamespace());
-          groupList.add(result.getNameInNamespace());
-        }
-      } catch (NamingException e) {
-        LOG.warn("Exception searching for user groups", e);
-      }
-    }
-
-    return groupList;
-  }
-
-  /**
-   * This method helps execute a LDAP query defined by the user via property
-   * "hive.server2.authentication.ldap.customLDAPQuery"
-   * A full LDAP query that LDAP Atn provider uses to execute against LDAP Server.
-   * If this query return a null resultset, the LDAP Provider fails the authentication request.
-   * If the LDAP query returns a list of DNs, a check is performed to confirm one
-   * of the entries is for the user being authenticated.
-   * For example: (&(objectClass=group)(objectClass=top)(instanceType=4)(cn=Domain*))
-   * (&(objectClass=person)(|(sAMAccountName=admin)
-   *                       (|(memberOf=CN=Domain Admins,CN=Users,DC=domain,DC=com)
-   *                         (memberOf=CN=Administrators,CN=Builtin,DC=domain,DC=com))))
-   * @param ctx DirContext to execute this query within.
-   * @param query User-defined LDAP Query string to be used to authenticate users.
-   * @param rootDN BaseDN at which to execute the LDAP query, typically rootDN for the LDAP.
-   * @return List of LDAP DNs returned from executing the LDAP Query.
-   */
-  public static List<String> executeLDAPQuery(DirContext ctx, String query, String rootDN)
-      throws NamingException {
-    if (rootDN == null) {
-      return null;
-    }
-
-    SearchControls searchControls = new SearchControls();
-    List<String> list             = new ArrayList<String>();
-    String[] returnAttributes;
-    if (groupMembership_attr != null) {
-      // retrieve the attributes that are meant to desginate user DNs
-      returnAttributes = new String[] { groupMembership_attr };
-    } else {
-      returnAttributes = new String[0]; //empty set
-    }
-
-    searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
-    searchControls.setReturningAttributes(returnAttributes);
-
-    LOG.info("Using a user specified LDAP query for adjudication:" + query + ",baseDN=" + rootDN);
-    NamingEnumeration<SearchResult> results = ctx.search(rootDN, query, searchControls);
-    SearchResult searchResult = null;
-    while(results.hasMoreElements()) {
-      searchResult = results.nextElement();
-      if (groupMembership_attr != null) {
-        Attribute userAttribute = searchResult.getAttributes().get(groupMembership_attr);
-        if (userAttribute != null) {
-          list.add((String)userAttribute.get());
-          continue;
-        }
+  private void applyFilter(DirSearch client, String user) throws AuthenticationException {
+    if (filter != null) {
+      if (LdapUtils.hasDomain(user)) {
+        filter.apply(client, LdapUtils.extractUserName(user));
+      } else {
+        filter.apply(client, user);
       }
-
-      list.add(searchResult.getNameInNamespace());
-      LOG.debug("LDAPAtn:executeLDAPQuery()::Return set size " + list.get(list.size() - 1));
     }
-    return list;
-  }
-
-  public static boolean isDN(String name) {
-    return (name.indexOf("=") > -1);
-  }
-
-  public static String extractName(String dn) {
-    int domainIdx = ServiceUtils.indexOfDomainMatch(dn);
-    if (domainIdx > 0) {
-      return dn.substring(0, domainIdx);
-    }
-
-    if (dn.indexOf("=") > -1) {
-      return dn.substring(dn.indexOf("=") + 1, dn.indexOf(","));
-    }
-    return dn;
-  }
-
-  public static String extractBaseDN(String dn) {
-    if (dn.indexOf(",") > -1) {
-      return dn.substring(dn.indexOf(",") + 1);
-    }
-    return null;
   }
 }

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/service/src/java/org/apache/hive/service/auth/ldap/ChainFilterFactory.java
----------------------------------------------------------------------
diff --git a/service/src/java/org/apache/hive/service/auth/ldap/ChainFilterFactory.java b/service/src/java/org/apache/hive/service/auth/ldap/ChainFilterFactory.java
new file mode 100644
index 0000000..e6255e8
--- /dev/null
+++ b/service/src/java/org/apache/hive/service/auth/ldap/ChainFilterFactory.java
@@ -0,0 +1,78 @@
+/**
+ * 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.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
+import javax.security.sasl.AuthenticationException;
+import org.apache.hadoop.hive.conf.HiveConf;
+
+/**
+ * A factory that produces a {@link Filter} that is implemented as a chain of other filters.
+ * The chain of filters are created as a result of
+ * {@link #getInstance(org.apache.hadoop.hive.conf.HiveConf) }
+ * method call. The resulting object filters out all users that don't pass <b>all</b>
+ * chained filters. The filters will be applied in the order they are mentioned in the factory
+ * constructor.
+ */
+public class ChainFilterFactory  implements FilterFactory {
+
+  private final List<FilterFactory> chainedFactories;
+
+  /**
+   * Constructs a factory for a chain of filters.
+   *
+   * @param factories The array of factories that will be used to construct a chain of filters.
+   */
+  public ChainFilterFactory(FilterFactory... factories) {
+    this.chainedFactories = ImmutableList.copyOf(factories);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Filter getInstance(HiveConf conf) {
+    List<Filter> filters = new ArrayList<>();
+    for (FilterFactory factory : chainedFactories) {
+      Filter filter = factory.getInstance(conf);
+      if (filter != null) {
+        filters.add(filter);
+      }
+    }
+
+    return filters.isEmpty() ? null : new ChainFilter(ImmutableList.copyOf(filters));
+  }
+
+  private static final class ChainFilter implements Filter {
+
+    private final List<Filter> chainedFilters;
+
+    public ChainFilter(List<Filter> chainedFilters) {
+      this.chainedFilters = chainedFilters;
+    }
+
+    @Override
+    public void apply(DirSearch client, String user) throws AuthenticationException {
+      for (Filter filter : chainedFilters) {
+        filter.apply(client, user);
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/service/src/java/org/apache/hive/service/auth/ldap/CustomQueryFilterFactory.java
----------------------------------------------------------------------
diff --git a/service/src/java/org/apache/hive/service/auth/ldap/CustomQueryFilterFactory.java b/service/src/java/org/apache/hive/service/auth/ldap/CustomQueryFilterFactory.java
new file mode 100644
index 0000000..a0708c3
--- /dev/null
+++ b/service/src/java/org/apache/hive/service/auth/ldap/CustomQueryFilterFactory.java
@@ -0,0 +1,84 @@
+/**
+ * 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 a custom query.
+ * <br>
+ * The produced filter object filters out all users that are not found in the search result
+ * of the query provided in Hive configuration.
+ * @see HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_CUSTOMLDAPQUERY
+ */
+public class CustomQueryFilterFactory implements FilterFactory {
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Filter getInstance(HiveConf conf) {
+    String customQuery = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_CUSTOMLDAPQUERY);
+
+    if (Strings.isNullOrEmpty(customQuery)) {
+      return null;
+    }
+
+    return new CustomQueryFilter(customQuery);
+  }
+
+  private static final class CustomQueryFilter implements Filter {
+
+    private static final Logger LOG = LoggerFactory.getLogger(CustomQueryFilter.class);
+
+    private final String query;
+
+    public CustomQueryFilter(String query) {
+      this.query = query;
+    }
+
+    @Override
+    public void apply(DirSearch client, String user) throws AuthenticationException {
+      List<String> resultList;
+      try {
+        resultList = client.executeCustomQuery(query);
+      } catch (NamingException e) {
+        throw new AuthenticationException("LDAP Authentication failed for user", e);
+      }
+      if (resultList != null) {
+        for (String matchedDn : resultList) {
+          String shortUserName = LdapUtils.getShortName(matchedDn);
+          LOG.info("<queried user=" + shortUserName + ",user=" + user + ">");
+          if (shortUserName.equalsIgnoreCase(user) || matchedDn.equalsIgnoreCase(user)) {
+            LOG.info("Authentication succeeded based on result set from LDAP query");
+            return;
+          }
+        }
+      }
+      LOG.info("Authentication failed based on result set from custom LDAP query");
+      throw new AuthenticationException("Authentication failed: LDAP query "
+          + "from property returned no data");
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/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
new file mode 100644
index 0000000..33b6088
--- /dev/null
+++ b/service/src/java/org/apache/hive/service/auth/ldap/DirSearch.java
@@ -0,0 +1,52 @@
+/**
+ * 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 java.io.Closeable;
+import java.util.List;
+import javax.naming.NamingException;
+
+/**
+ * The object used for executing queries on the Directory Service.
+ */
+public interface DirSearch extends Closeable {
+
+  /**
+   * Finds user's distinguished name.
+   * @param user username
+   * @return DN for the specified username
+   * @throws NamingException
+   */
+  String findUserDn(String user) throws NamingException;
+
+  /**
+   * Finds groups that contain the specified user.
+   * @param userDn user's distinguished name
+   * @return list of groups
+   * @throws NamingException
+   */
+  List<String> findGroupsForUser(String userDn) throws NamingException;
+
+  /**
+   * Executes an arbitrary query.
+   * @param query any query
+   * @return list of names in the namespace
+   * @throws NamingException
+   */
+  List<String> executeCustomQuery(String query) throws NamingException;
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/service/src/java/org/apache/hive/service/auth/ldap/DirSearchFactory.java
----------------------------------------------------------------------
diff --git a/service/src/java/org/apache/hive/service/auth/ldap/DirSearchFactory.java b/service/src/java/org/apache/hive/service/auth/ldap/DirSearchFactory.java
new file mode 100644
index 0000000..e6d9ff9
--- /dev/null
+++ b/service/src/java/org/apache/hive/service/auth/ldap/DirSearchFactory.java
@@ -0,0 +1,37 @@
+/**
+ * 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 javax.security.sasl.AuthenticationException;
+import org.apache.hadoop.hive.conf.HiveConf;
+
+/**
+ * A factory for {@code DirSearch}.
+ */
+public interface DirSearchFactory {
+
+  /**
+   * Returns an instance of {@code DirSearch}.
+   * @param conf Hive configuration
+   * @param user username
+   * @param password user password
+   * @return instance of {@code DirSearch}
+   * @throws AuthenticationException
+   */
+  DirSearch getInstance(HiveConf conf, String user, String password) throws AuthenticationException;
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/service/src/java/org/apache/hive/service/auth/ldap/Filter.java
----------------------------------------------------------------------
diff --git a/service/src/java/org/apache/hive/service/auth/ldap/Filter.java b/service/src/java/org/apache/hive/service/auth/ldap/Filter.java
new file mode 100644
index 0000000..fa72ced
--- /dev/null
+++ b/service/src/java/org/apache/hive/service/auth/ldap/Filter.java
@@ -0,0 +1,36 @@
+/**
+ * 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 javax.security.sasl.AuthenticationException;
+
+/**
+ * The object that filters LDAP users.
+ * <br>
+ * The assumption is that this user was already authenticated by a previous bind operation.
+ */
+public interface Filter {
+
+  /**
+   * Applies this filter to the authenticated user.
+   * @param client LDAP client that will be used for execution of LDAP queries.
+   * @param user username
+   * @throws AuthenticationException
+   */
+  void apply(DirSearch client, String user) throws AuthenticationException;
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/service/src/java/org/apache/hive/service/auth/ldap/FilterFactory.java
----------------------------------------------------------------------
diff --git a/service/src/java/org/apache/hive/service/auth/ldap/FilterFactory.java b/service/src/java/org/apache/hive/service/auth/ldap/FilterFactory.java
new file mode 100644
index 0000000..0708ccd
--- /dev/null
+++ b/service/src/java/org/apache/hive/service/auth/ldap/FilterFactory.java
@@ -0,0 +1,33 @@
+/**
+ * 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 org.apache.hadoop.hive.conf.HiveConf;
+
+/**
+ * Factory for the filter.
+ */
+public interface FilterFactory {
+
+  /**
+   * Returns an instance of the corresponding filter.
+   * @param conf Hive properties used to configure the filter.
+   * @return the filter or {@code null} if this filter doesn't support provided set of properties
+   */
+  Filter getInstance(HiveConf conf);
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/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
new file mode 100644
index 0000000..152c4b2
--- /dev/null
+++ b/service/src/java/org/apache/hive/service/auth/ldap/GroupFilterFactory.java
@@ -0,0 +1,90 @@
+/**
+ * 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 java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+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 a list of allowed groups.
+ * <br>
+ * The produced filter object filters out all users that are not members of at least one of
+ * the groups provided in Hive configuration.
+ * @see HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER
+ */
+public final class GroupFilterFactory implements FilterFactory {
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Filter getInstance(HiveConf conf) {
+    Collection<String> groupFilter = conf.getStringCollection(
+        HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER.varname);
+
+    if (groupFilter.isEmpty()) {
+      return null;
+    }
+
+    return new GroupFilter(groupFilter);
+  }
+
+  private static final class GroupFilter implements Filter {
+
+    private static final Logger LOG = LoggerFactory.getLogger(GroupFilter.class);
+
+    private final Set<String> groupFilter = new HashSet<>();
+
+    GroupFilter(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);
+
+      List<String> memberOf = null;
+
+      try {
+        String userDn = ldap.findUserDn(user);
+        memberOf = ldap.findGroupsForUser(userDn);
+        LOG.debug("User {} member of : {}", userDn, memberOf);
+      } catch (NamingException e) {
+        throw new AuthenticationException("LDAP Authentication failed for user", e);
+      }
+
+      for (String groupDn : memberOf) {
+        String shortName = LdapUtils.getShortName(groupDn);
+        if (groupFilter.contains(shortName)) {
+          LOG.info("Authentication succeeded based on group membership");
+          return;
+        }
+      }
+      LOG.info("Authentication failed based on user membership");
+      throw new AuthenticationException("Authentication failed: "
+          + "User not a member of specified list");
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/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
new file mode 100644
index 0000000..65076ea
--- /dev/null
+++ b/service/src/java/org/apache/hive/service/auth/ldap/LdapSearch.java
@@ -0,0 +1,155 @@
+/**
+ * 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 java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.SearchResult;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implements search for LDAP.
+ */
+public final class LdapSearch implements DirSearch {
+
+  private static final Logger LOG = LoggerFactory.getLogger(LdapSearch.class);
+
+  private final String baseDn;
+  private final List<String> groupBases;
+  private final List<String> userBases;
+  private final List<String> userPatterns;
+
+  private final QueryFactory queries;
+
+  private final DirContext ctx;
+
+  /**
+   * Construct an instance of {@code LdapSearch}.
+   * @param conf Hive configuration
+   * @param ctx Directory service that will be used for the queries.
+   * @throws NamingException
+   */
+  public LdapSearch(HiveConf conf, DirContext ctx) throws NamingException {
+    baseDn = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BASEDN);
+    userPatterns = LdapUtils.parseDnPatterns(conf,
+        HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN);
+    groupBases = LdapUtils.patternsToBaseDns(LdapUtils.parseDnPatterns(conf,
+        HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPDNPATTERN));
+    userBases = LdapUtils.patternsToBaseDns(userPatterns);
+    this.ctx = ctx;
+    queries = new QueryFactory(conf);
+  }
+
+  /**
+   * Closes this search object and releases any system resources associated
+   * with it. If the search object is already closed then invoking this
+   * method has no effect.
+   */
+  @Override
+  public void close() {
+    try {
+      ctx.close();
+    } catch (NamingException e) {
+      LOG.warn("Exception when closing LDAP context:", e);
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String findUserDn(String user) throws NamingException {
+    List<String> allLdapNames;
+    if (LdapUtils.isDn(user)) {
+      String userBaseDn = LdapUtils.extractBaseDn(user);
+      String userRdn = LdapUtils.extractFirstRdn(user);
+      allLdapNames = execute(Collections.singletonList(userBaseDn),
+          queries.findUserDnByRdn(userRdn)).getAllLdapNames();
+    } else {
+      allLdapNames = findDnByPattern(userPatterns, user);
+      if (allLdapNames.isEmpty()) {
+        allLdapNames = execute(userBases, queries.findUserDnByName(user)).getAllLdapNames();
+      }
+    }
+
+    if (allLdapNames.size() == 1) {
+      return allLdapNames.get(0);
+    } else {
+      LOG.info("Expected exactly one user result for the user: {}, but got {}. Returning null",
+          user, allLdapNames.size());
+      LOG.debug("Matched users: {}", allLdapNames);
+      return null;
+    }
+  }
+
+  private List<String> findDnByPattern(List<String> patterns, String name) throws NamingException {
+    for (String pattern : patterns) {
+      String baseDnFromPattern = LdapUtils.extractBaseDn(pattern);
+      String rdn = LdapUtils.extractFirstRdn(pattern).replaceAll("%s", name);
+      List<String> list = execute(Collections.singletonList(baseDnFromPattern),
+          queries.findDnByPattern(rdn)).getAllLdapNames();
+      if (!list.isEmpty()) {
+        return list;
+      }
+    }
+    return Collections.emptyList();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public List<String> findGroupsForUser(String userDn) throws NamingException {
+    String userName = LdapUtils.extractUserName(userDn);
+    return execute(groupBases, queries.findGroupsForUser(userName, userDn)).getAllLdapNames();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public List<String> executeCustomQuery(String query) throws NamingException {
+    return execute(Collections.singletonList(baseDn), queries.customQuery(query))
+        .getAllLdapNamesAndAttributes();
+  }
+
+  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);
+    for (String aBaseDn : baseDns) {
+      try {
+        NamingEnumeration<SearchResult> searchResult = ctx.search(aBaseDn, query.getFilter(),
+            query.getControls());
+        if (searchResult != null) {
+          searchResults.add(searchResult);
+        }
+      } catch (NamingException ex) {
+        LOG.debug("Exception happened for query '" + query.getFilter() +
+            "' with base DN '" + aBaseDn + "'", ex);
+      }
+    }
+    return new SearchResultHandler(searchResults);
+  }
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/service/src/java/org/apache/hive/service/auth/ldap/LdapSearchFactory.java
----------------------------------------------------------------------
diff --git a/service/src/java/org/apache/hive/service/auth/ldap/LdapSearchFactory.java b/service/src/java/org/apache/hive/service/auth/ldap/LdapSearchFactory.java
new file mode 100644
index 0000000..71c3bfe
--- /dev/null
+++ b/service/src/java/org/apache/hive/service/auth/ldap/LdapSearchFactory.java
@@ -0,0 +1,64 @@
+/**
+ * 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 java.util.Hashtable;
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import javax.security.sasl.AuthenticationException;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A factory for LDAP search objects.
+ */
+public final class LdapSearchFactory implements DirSearchFactory {
+
+  private static final Logger LOG = LoggerFactory.getLogger(LdapSearchFactory.class);
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public DirSearch getInstance(HiveConf conf, String principal, String password)
+      throws AuthenticationException {
+    try {
+      DirContext ctx = createDirContext(conf, principal, password);
+      return new LdapSearch(conf, ctx);
+    } catch (NamingException e) {
+      LOG.debug("Could not connect to the LDAP Server:Authentication failed for {}", principal);
+      throw new AuthenticationException("Error validating LDAP user", e);
+    }
+  }
+
+  private static DirContext createDirContext(HiveConf conf, String principal, String password)
+      throws NamingException {
+    Hashtable<String, Object> env = new Hashtable<String, Object>();
+    String ldapUrl = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_URL);
+    env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
+    env.put(Context.PROVIDER_URL, ldapUrl);
+    env.put(Context.SECURITY_AUTHENTICATION, "simple");
+    env.put(Context.SECURITY_CREDENTIALS, password);
+    env.put(Context.SECURITY_PRINCIPAL, principal);
+    LOG.debug("Connecting using principal {} to ldap url {}", principal, ldapUrl);
+    return new InitialDirContext(env);
+  }
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/service/src/java/org/apache/hive/service/auth/ldap/LdapUtils.java
----------------------------------------------------------------------
diff --git a/service/src/java/org/apache/hive/service/auth/ldap/LdapUtils.java b/service/src/java/org/apache/hive/service/auth/ldap/LdapUtils.java
new file mode 100644
index 0000000..df2ba6b
--- /dev/null
+++ b/service/src/java/org/apache/hive/service/auth/ldap/LdapUtils.java
@@ -0,0 +1,228 @@
+/**
+ * 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 java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.apache.commons.lang.StringUtils;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.apache.hive.service.ServiceUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Static utility methods related to LDAP authentication module.
+ */
+public final class LdapUtils {
+
+  private static final Logger LOG = LoggerFactory.getLogger(LdapUtils.class);
+
+  /**
+   * Extracts a base DN from the provided distinguished name.
+   * <br>
+   * <b>Example:</b>
+   * <br>
+   * "ou=CORP,dc=mycompany,dc=com" is the base DN for "cn=user1,ou=CORP,dc=mycompany,dc=com"
+   *
+   * @param dn distinguished name
+   * @return base DN
+   */
+  public static String extractBaseDn(String dn) {
+    final int indexOfFirstDelimiter = dn.indexOf(",");
+    if (indexOfFirstDelimiter > -1) {
+      return dn.substring(indexOfFirstDelimiter + 1);
+    }
+    return null;
+  }
+
+  /**
+   * Extracts the first Relative Distinguished Name (RDN).
+   * <br>
+   * <b>Example:</b>
+   * <br>
+   * For DN "cn=user1,ou=CORP,dc=mycompany,dc=com" this method will return "cn=user1"
+   * @param dn distinguished name
+   * @return first RDN
+   */
+  public static String extractFirstRdn(String dn) {
+    return dn.substring(0, dn.indexOf(","));
+  }
+
+  /**
+   * Extracts username from user DN.
+   * <br>
+   * <b>Examples:</b>
+   * <pre>
+   * LdapUtils.extractUserName("UserName")                        = "UserName"
+   * LdapUtils.extractUserName("UserName@mycorp.com")             = "UserName"
+   * LdapUtils.extractUserName("cn=UserName,dc=mycompany,dc=com") = "UserName"
+   * </pre>
+   * @param userDn
+   * @return
+   */
+  public static String extractUserName(String userDn) {
+    if (!isDn(userDn) && !hasDomain(userDn)) {
+      return userDn;
+    }
+
+    int domainIdx = ServiceUtils.indexOfDomainMatch(userDn);
+    if (domainIdx > 0) {
+      return userDn.substring(0, domainIdx);
+    }
+
+    if (userDn.contains("=")) {
+      return userDn.substring(userDn.indexOf("=") + 1, userDn.indexOf(","));
+    }
+    return userDn;
+  }
+
+  /**
+   * Gets value part of the first attribute in the provided RDN.
+   * <br>
+   * <b>Example:</b>
+   * <br>
+   * For RDN "cn=user1,ou=CORP" this method will return "user1"
+   * @param rdn Relative Distinguished Name
+   * @return value part of the first attribute
+   */
+  public static String getShortName(String rdn) {
+    return ((rdn.split(","))[0].split("="))[1];
+  }
+
+  /**
+   * Check for a domain part in the provided username.
+   * <br>
+   * <b>Example:</b>
+   * <br>
+   * <pre>
+   * LdapUtils.hasDomain("user1@mycorp.com") = true
+   * LdapUtils.hasDomain("user1")            = false
+   * </pre>
+   * @param userName username
+   * @return true if {@code userName} contains {@code @<domain>} part
+   */
+  public static boolean hasDomain(String userName) {
+    return (ServiceUtils.indexOfDomainMatch(userName) > 0);
+  }
+
+  /**
+   * Detects DN names.
+   * <br>
+   * <b>Example:</b>
+   * <br>
+   * <pre>
+   * LdapUtils.isDn("cn=UserName,dc=mycompany,dc=com") = true
+   * LdapUtils.isDn("user1")                           = false
+   * </pre>
+   * @param name name to be checked
+   * @return true if the provided name is a distinguished name
+   */
+  public static boolean isDn(String name) {
+    return name.contains("=");
+  }
+
+  /**
+   * Reads and parses DN patterns from Hive configuration.
+   * <br>
+   * If no patterns are provided in the configuration, then the base DN will be used.
+   * @param conf Hive configuration
+   * @param var variable to be read
+   * @return a list of DN patterns
+   * @see HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BASEDN
+   * @see HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GUIDKEY
+   * @see HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPDNPATTERN
+   * @see HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN
+   */
+  public static List<String> parseDnPatterns(HiveConf conf, HiveConf.ConfVars var) {
+    String patternsString = conf.getVar(var);
+    List<String> result = new ArrayList<>();
+    if (StringUtils.isBlank(patternsString)) {
+      String defaultBaseDn = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BASEDN);
+      String guidAttr = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GUIDKEY);
+      if (StringUtils.isNotBlank(defaultBaseDn)) {
+        result.add(guidAttr + "=%s," + defaultBaseDn);
+      }
+    } else {
+      String[] patterns = patternsString.split(":");
+      for (String pattern : patterns) {
+        if (pattern.contains(",") && pattern.contains("=")) {
+          result.add(pattern);
+        } else {
+          LOG.warn("Unexpected format for " + var + "..ignoring " + pattern);
+        }
+      }
+    }
+    return result;
+  }
+
+  private static String patternToBaseDn(String pattern) {
+    if (pattern.contains("=%s")) {
+      return pattern.split(",", 2)[1];
+    }
+    return pattern;
+  }
+
+  /**
+   * Converts a collection of Distinguished Name patterns to a collection of base DNs.
+   * @param patterns Distinguished Name patterns
+   * @return a list of base DNs
+   * @see HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPDNPATTERN
+   * @see HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN
+   */
+  public static List<String> patternsToBaseDns(Collection<String> patterns) {
+    List<String> result = new ArrayList<>();
+    for (String pattern : patterns) {
+      result.add(patternToBaseDn(pattern));
+    }
+    return result;
+  }
+
+  /**
+   * Creates a list of principals to be used for user authentication.
+   * @param conf Hive configuration
+   * @param user username
+   * @return a list of user's principals
+   */
+  public static List<String> createCandidatePrincipals(HiveConf conf, String user) {
+    if (hasDomain(user) || isDn(user)) {
+      return Collections.singletonList(user);
+    }
+
+    String ldapDomain = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_DOMAIN);
+    if (StringUtils.isNotBlank(ldapDomain)) {
+      return Collections.singletonList(user + "@" + ldapDomain);
+    }
+
+    List<String> userPatterns = parseDnPatterns(conf,
+        HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN);
+    if (userPatterns.isEmpty()) {
+      return Collections.singletonList(user);
+    }
+
+    List<String> candidatePrincipals = new ArrayList<>();
+    for (String userPattern : userPatterns) {
+      candidatePrincipals.add(userPattern.replaceAll("%s", user));
+    }
+    return candidatePrincipals;
+  }
+
+  private LdapUtils() {
+  }
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/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
new file mode 100644
index 0000000..b8bf938
--- /dev/null
+++ b/service/src/java/org/apache/hive/service/auth/ldap/Query.java
@@ -0,0 +1,154 @@
+/**
+ * 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.Preconditions;
+import java.util.ArrayList;
+import java.util.List;
+import javax.naming.directory.SearchControls;
+import org.stringtemplate.v4.ST;
+
+/**
+ * The object that encompasses all components of a Directory Service search query.
+ * <br>
+ * @see LdapSearch
+ */
+public final class Query {
+
+  private final String filter;
+  private final SearchControls controls;
+
+  /**
+   * Constructs an instance of Directory Service search query.
+   * @param filter search filter
+   * @param controls search controls
+   */
+  public Query(String filter, SearchControls controls) {
+    this.filter = filter;
+    this.controls = controls;
+  }
+
+  /**
+   * Returns search filter.
+   * @return search filter
+   */
+  public String getFilter() {
+    return filter;
+  }
+
+  /**
+   * Returns search controls.
+   * @return search controls
+   */
+  public SearchControls getControls() {
+    return controls;
+  }
+
+  /**
+   * Creates Query Builder.
+   * @return query builder.
+   */
+  public static QueryBuilder builder() {
+    return new QueryBuilder();
+  }
+
+  /**
+   * A builder of the {@link Query}.
+   */
+  public static final class QueryBuilder {
+
+    private ST filterTemplate;
+    private final SearchControls controls = new SearchControls();
+    private final List<String> returningAttributes = new ArrayList<>();
+
+    private QueryBuilder() {
+      controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+      controls.setReturningAttributes(new String[0]);
+    }
+
+    /**
+     * Sets search filter template.
+     * @param filterTemplate search filter template
+     * @return the current instance of the builder
+     */
+    public QueryBuilder filter(String filterTemplate) {
+      this.filterTemplate = new ST(filterTemplate);
+      return this;
+    }
+
+    /**
+     * Sets mapping between names in the search filter template and actual values.
+     * @param key marker in the search filter template.
+     * @param value actual value
+     * @return the current instance of the builder
+     */
+    public QueryBuilder map(String key, String value) {
+      filterTemplate.add(key, value);
+      return this;
+    }
+
+    /**
+     * Sets attribute that should be returned in results for the query.
+     * @param attributeName attribute name
+     * @return the current instance of the builder
+     */
+    public QueryBuilder returnAttribute(String attributeName) {
+      returningAttributes.add(attributeName);
+      return this;
+    }
+
+    /**
+     * Sets the maximum number of entries to be returned as a result of the search.
+     * <br>
+     * 0 indicates no limit: all entries will be returned.
+     * @param limit The maximum number of entries that will be returned.
+     * @return the current instance of the builder
+     */
+    public QueryBuilder limit(int limit) {
+      controls.setCountLimit(limit);
+      return this;
+    }
+
+    private void validate() {
+      Preconditions.checkArgument(filterTemplate != null,
+          "filter is required for LDAP search query");
+    }
+
+    private String createFilter() {
+      return filterTemplate.render();
+    }
+
+    private void updateControls() {
+      if (!returningAttributes.isEmpty()) {
+        controls.setReturningAttributes(returningAttributes
+            .toArray(new String[returningAttributes.size()]));
+      }
+    }
+
+    /**
+     * Builds an instance of {@link Query}.
+     * @return configured directory service query
+     */
+    public Query build() {
+      validate();
+      String filter = createFilter();
+      updateControls();
+      return new Query(filter, controls);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/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
new file mode 100644
index 0000000..e9172d3
--- /dev/null
+++ b/service/src/java/org/apache/hive/service/auth/ldap/QueryFactory.java
@@ -0,0 +1,135 @@
+/**
+ * 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 org.apache.hadoop.hive.conf.HiveConf;
+
+/**
+ * A factory for common types of directory service search queries.
+ */
+public final class QueryFactory {
+
+  private final String guidAttr;
+  private final String groupClassAttr;
+  private final String groupMembershipAttr;
+
+  /**
+   * Constructs the factory based on provided Hive configuration.
+   * @param conf Hive configuration
+   */
+  public QueryFactory(HiveConf conf) {
+    guidAttr = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GUIDKEY);
+    groupClassAttr = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPCLASS_KEY);
+    groupMembershipAttr = conf.getVar(
+        HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPMEMBERSHIP_KEY);
+  }
+
+  /**
+   * Returns a query for finding Group DN based on group unique ID.
+   * @param groupId group unique identifier
+   * @return an instance of {@link Query}
+   */
+  public Query findGroupDnById(String groupId) {
+    return Query.builder()
+        .filter("(&(objectClass=<groupClassAttr>)(<guidAttr>=<groupID>))")
+        .map("guidAttr", guidAttr)
+        .map("groupClassAttr", groupClassAttr)
+        .map("groupID", groupId)
+        .limit(2)
+        .build();
+  }
+
+  /**
+   * Returns a query for finding user DN based on user RDN.
+   * @param userRdn user RDN
+   * @return an instance of {@link Query}
+   */
+  public Query findUserDnByRdn(String userRdn) {
+    return Query.builder()
+        .filter("(&(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))"
+            + "(<userRdn>))")
+        .limit(2)
+        .map("userRdn", userRdn)
+        .build();
+  }
+
+  /**
+   * Returns a query for finding user DN based on DN pattern.
+   * <br>
+   * Name of this method was derived from the original implementation of LDAP authentication.
+   * This method should be replaced by {@link QueryFactory#findUserDnByRdn(java.lang.String).
+   *
+   * @param rdn user RDN
+   * @return an instance of {@link Query}
+   */
+  public Query findDnByPattern(String rdn) {
+    return Query.builder()
+        .filter("(<rdn>)")
+        .map("rdn", rdn)
+        .limit(2)
+        .build();
+  }
+
+  /**
+   * Returns a query for finding user DN based on user unique name.
+   * @param userName user unique name (uid or sAMAccountName)
+   * @return an instance of {@link Query}
+   */
+  public Query findUserDnByName(String userName) {
+    return Query.builder()
+        .filter("(&(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))"
+            + "(|(uid=<userName>)(sAMAccountName=<userName>)))")
+        .map("userName", userName)
+        .limit(2)
+        .build();
+  }
+
+  /**
+   * Returns a query for finding groups to which the user belongs.
+   * @param userName username
+   * @param userDn user DN
+   * @return an instance of {@link Query}
+   */
+  public Query findGroupsForUser(String userName, String userDn) {
+    return Query.builder()
+        .filter("(&(objectClass=<groupClassAttr>)(|(<groupMembershipAttr>=<userDn>)"
+            + "(<groupMembershipAttr>=<userName>)))")
+        .map("groupClassAttr", groupClassAttr)
+        .map("groupMembershipAttr", groupMembershipAttr)
+        .map("userName", userName)
+        .map("userDn", userDn)
+        .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.
+   * @param searchFilter custom search filter
+   * @return an instance of {@link Query}
+   */
+  public Query customQuery(String searchFilter) {
+    Query.QueryBuilder builder = Query.builder();
+    builder.filter(searchFilter);
+    if (!Strings.isNullOrEmpty(groupMembershipAttr)) {
+      builder.returnAttribute(groupMembershipAttr);
+    }
+    return builder.build();
+  }
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/service/src/java/org/apache/hive/service/auth/ldap/SearchResultHandler.java
----------------------------------------------------------------------
diff --git a/service/src/java/org/apache/hive/service/auth/ldap/SearchResultHandler.java b/service/src/java/org/apache/hive/service/auth/ldap/SearchResultHandler.java
new file mode 100644
index 0000000..1b391f8
--- /dev/null
+++ b/service/src/java/org/apache/hive/service/auth/ldap/SearchResultHandler.java
@@ -0,0 +1,163 @@
+/**
+ * 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 java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.SearchResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The object that handles Directory Service search results.
+ * In most cases it converts search results into a list of names in the namespace.
+ */
+public final class SearchResultHandler {
+
+  private static final Logger LOG = LoggerFactory.getLogger(SearchResultHandler.class);
+
+  private final Collection<NamingEnumeration<SearchResult>> searchResults;
+
+  /**
+   * Constructs a search result handler object for the provided search results.
+   * @param searchResults directory service search results
+   */
+  public SearchResultHandler(Collection<NamingEnumeration<SearchResult>> searchResults) {
+    this.searchResults = searchResults;
+  }
+
+  /**
+   * Returns all entries from the search result.
+   * @return a list of names in the namespace
+   * @throws NamingException
+   */
+  public List<String> getAllLdapNames() throws NamingException {
+    final List<String> result = new ArrayList<>();
+    handle(new RecordProcessor() {
+      @Override
+      public boolean process(SearchResult record) throws NamingException {
+        result.add(record.getNameInNamespace());
+        return true;
+      }
+    });
+    return result;
+  }
+
+  /**
+   * Checks whether search result contains exactly one entry.
+   * @return true if the search result contains a single entry.
+   * @throws NamingException
+   */
+  public boolean hasSingleResult() throws NamingException {
+    List<String> allResults = getAllLdapNames();
+    return allResults != null && allResults.size() == 1;
+  }
+
+  /**
+   * Returns a single entry from the search result.
+   * Throws {@code NamingException} if the search result doesn't contain exactly one entry.
+   * @return name in the namespace
+   * @throws NamingException
+   */
+  public String getSingleLdapName() throws NamingException {
+    List<String> allLdapNames = getAllLdapNames();
+    if (allLdapNames.size() == 1) {
+      return allLdapNames.get(0);
+    }
+    throw new NamingException("Single result was expected");
+  }
+
+  /**
+   * Returns all entries and all attributes for these entries.
+   * @return a list that includes all entries and all attributes from these entries.
+   * @throws NamingException
+   */
+  public List<String> getAllLdapNamesAndAttributes() throws NamingException {
+    final List<String> result = new ArrayList<>();
+    handle(new RecordProcessor() {
+      @Override
+      public boolean process(SearchResult record) throws NamingException {
+        result.add(record.getNameInNamespace());
+        NamingEnumeration<? extends Attribute> allAttributes = record.getAttributes().getAll();
+        while(allAttributes.hasMore()) {
+          Attribute attribute = allAttributes.next();
+          addAllAttributeValuesToResult(attribute.getAll());
+        }
+        return true;
+      }
+
+      private void addAllAttributeValuesToResult(NamingEnumeration values) throws NamingException {
+        while(values.hasMore()) {
+          result.add(String.valueOf(values.next()));
+        }
+      }
+
+    });
+    return result;
+  }
+
+  /**
+   * Allows for custom processing of the search results.
+   * @param processor {@link RecordProcessor} implementation
+   * @throws NamingException
+   */
+  public void handle(RecordProcessor processor) throws NamingException {
+    try {
+      for (NamingEnumeration<SearchResult> searchResult : searchResults) {
+        while (searchResult.hasMore()) {
+          if (!processor.process(searchResult.next())) {
+            return;
+          }
+        }
+      }
+    } finally {
+      for (NamingEnumeration<SearchResult> searchResult : searchResults) {
+        try {
+          searchResult.close();
+        } catch (NamingException ex) {
+          LOG.warn("Failed to close LDAP search result", ex);
+        }
+      }
+    }
+  }
+
+  /**
+   * An interface used by {@link SearchResultHandler} for processing records of
+   * a {@link SearchResult} on a per-record basis.
+   * <br>
+   * Implementations of this interface perform the actual work of processing each record,
+   * but don't need to worry about exception handling, closing underlying data structures,
+   * and combining results from several search requests.
+   * {@see SearchResultHandler}
+   */
+  public interface RecordProcessor {
+
+    /**
+     * Implementations must implement this method to process each record in {@link SearchResult}.
+     * @param record the {@code SearchResult} to precess
+     * @return {@code true} to continue processing, {@code false} to stop iterating
+     * over search results
+     * @throws NamingException
+     */
+    boolean process(SearchResult record) throws NamingException;
+  }
+}

http://git-wip-us.apache.org/repos/asf/hive/blob/990927e3/service/src/java/org/apache/hive/service/auth/ldap/UserFilterFactory.java
----------------------------------------------------------------------
diff --git a/service/src/java/org/apache/hive/service/auth/ldap/UserFilterFactory.java b/service/src/java/org/apache/hive/service/auth/ldap/UserFilterFactory.java
new file mode 100644
index 0000000..c8a6c88
--- /dev/null
+++ b/service/src/java/org/apache/hive/service/auth/ldap/UserFilterFactory.java
@@ -0,0 +1,75 @@
+/**
+ * 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 java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+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 a list of allowed users.
+ * <br>
+ * The produced filter object filters out all users that are not on the provided in
+ * Hive configuration list.
+ * @see HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER
+ */
+public final class UserFilterFactory implements FilterFactory {
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Filter getInstance(HiveConf conf) {
+    Collection<String> userFilter = conf.getStringCollection(
+        HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER.varname);
+
+    if (userFilter.isEmpty()) {
+      return null;
+    }
+
+    return new UserFilter(userFilter);
+  }
+
+  private static final class UserFilter implements Filter {
+
+    private static final Logger LOG = LoggerFactory.getLogger(UserFilter.class);
+
+    private final Set<String> userFilter = new HashSet<>();
+
+    UserFilter(Collection<String> userFilter) {
+      for (String userFilterItem : userFilter) {
+        this.userFilter.add(userFilterItem.toLowerCase());
+      }
+    }
+
+    @Override
+    public void apply(DirSearch ldap, String user) throws AuthenticationException {
+      LOG.info("Authenticating user '{}' using user filter", user);
+      String userName = LdapUtils.extractUserName(user).toLowerCase();
+      if (!userFilter.contains(userName)) {
+        LOG.info("Authentication failed based on user membership");
+        throw new AuthenticationException("Authentication failed: "
+            + "User not a member of specified list");
+      }
+    }
+  }
+}