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

sankarh pushed a commit to branch branch-3
in repository https://gitbox.apache.org/repos/asf/hive.git


The following commit(s) were added to refs/heads/branch-3 by this push:
     new edd0d46408f HIVE-27614 : Backport of HIVE-21009: Adding ability for 
user to set bind user (#4594)
edd0d46408f is described below

commit edd0d46408f7f9226e2fad2bc55f4d3b435d69c6
Author: Aman Raj <104416558+amanraj2...@users.noreply.github.com>
AuthorDate: Mon Aug 21 10:42:47 2023 +0530

    HIVE-27614 : Backport of HIVE-21009: Adding ability for user to set bind 
user (#4594)
    
    Signed-off-by: Sankar Hariappan <sank...@apache.org>
    Closes (#4594)
---
 .../java/org/apache/hadoop/hive/conf/HiveConf.java |  10 ++
 service/pom.xml                                    |  11 ++
 .../auth/LdapAuthenticationProviderImpl.java       |  32 +++++-
 .../auth/TestLdapAuthenticationProviderImpl.java   | 113 +++++++++++++++++++++
 service/src/test/resources/creds/test.jceks        | Bin 0 -> 534 bytes
 5 files changed, 164 insertions(+), 2 deletions(-)

diff --git a/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java 
b/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
index f9a47324473..33796a24d19 100644
--- a/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
+++ b/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
@@ -3386,6 +3386,16 @@ public class HiveConf extends Configuration {
         "For example: 
(&(objectClass=group)(objectClass=top)(instanceType=4)(cn=Domain*)) \n" +
         "(&(objectClass=person)(|(sAMAccountName=admin)(|(memberOf=CN=Domain 
Admins,CN=Users,DC=domain,DC=com)" +
         "(memberOf=CN=Administrators,CN=Builtin,DC=domain,DC=com))))"),
+    
HIVE_SERVER2_PLAIN_LDAP_BIND_USER("hive.server2.authentication.ldap.binddn", 
null,
+        "The user with which to bind to the LDAP server, and search for the 
full domain name " +
+        "of the user being authenticated.\n" +
+        "This should be the full domain name of the user, and should have 
search access across all " +
+        "users in the LDAP tree.\n" +
+        "If not specified, then the user being authenticated will be used as 
the bind user.\n" +
+        "For example: CN=bindUser,CN=Users,DC=subdomain,DC=domain,DC=com"),
+    
HIVE_SERVER2_PLAIN_LDAP_BIND_PASSWORD("hive.server2.authentication.ldap.bindpw",
 null,
+        "The password for the bind user, to be used to search for the full 
name of the user being authenticated.\n" +
+        "If the username is specified, this parameter must also be 
specified."),
     
HIVE_SERVER2_CUSTOM_AUTHENTICATION_CLASS("hive.server2.custom.authentication.class",
 null,
         "Custom authentication class. Used when property\n" +
         "'hive.server2.authentication' is set to 'CUSTOM'. Provided class\n" +
diff --git a/service/pom.xml b/service/pom.xml
index e44d1244e52..7f93efe0c04 100644
--- a/service/pom.xml
+++ b/service/pom.xml
@@ -34,6 +34,17 @@
   <dependencies>
     <!-- dependencies are always listed in sorted order by groupId, artifectId 
-->
     <!-- intra-project -->
+    <dependency>
+      <groupId>org.apache.hive</groupId>
+      <artifactId>hive-common</artifactId>
+      <version>${project.version}</version>
+      <exclusions>
+        <exclusion>
+          <groupId>org.eclipse.jetty.aggregate</groupId>
+          <artifactId>jetty-all</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
     <dependency>
       <groupId>org.apache.hive</groupId>
       <artifactId>hive-exec</artifactId>
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 73bbb6bdf8a..0120513b515 100644
--- 
a/service/src/java/org/apache/hive/service/auth/LdapAuthenticationProviderImpl.java
+++ 
b/service/src/java/org/apache/hive/service/auth/LdapAuthenticationProviderImpl.java
@@ -18,9 +18,10 @@
 package org.apache.hive.service.auth;
 
 import javax.security.sasl.AuthenticationException;
-
+import javax.naming.NamingException;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
+import java.io.IOException;
 import java.util.Iterator;
 import java.util.List;
 import org.apache.commons.lang.StringUtils;
@@ -68,9 +69,36 @@ public class LdapAuthenticationProviderImpl implements 
PasswdAuthenticationProvi
   @Override
   public void Authenticate(String user, String password) throws 
AuthenticationException {
     DirSearch search = null;
+    String bindUser = 
this.conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_USER);
+    String bindPassword = null;
+    try {
+      char[] rawPassword = 
this.conf.getPassword(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_PASSWORD.toString());
+      if (rawPassword != null) {
+        bindPassword = new String(rawPassword);
+      }
+    } catch (IOException e) {
+      bindPassword = null;
+    }
+    boolean usedBind = bindUser != null && bindPassword != null;
+    if (!usedBind) {
+      // If no bind user or bind password was specified,
+      // we assume the user we are authenticating has the ability to search
+      // the LDAP tree, so we use it as the "binding" account.
+      // This is the way it worked before bind users were allowed in the LDAP 
authenticator,
+      // so we keep existing systems working.
+      bindUser = user;
+      bindPassword = password;
+    }
     try {
-      search = createDirSearch(user, password);
+      search = createDirSearch(bindUser, bindPassword);
       applyFilter(search, user);
+      if (usedBind) {
+        // If we used the bind user, then we need to authenticate again,
+        // this time using the full user name we got during the bind process.
+        createDirSearch(search.findUserDn(user), password);
+      }
+    } catch (NamingException e) {
+      throw new AuthenticationException("Unable to find the user in the LDAP 
tree. " + e.getMessage());
     } finally {
       ServiceUtils.cleanup(LOG, search);
     }
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 43453fa53f5..0396b749127 100644
--- 
a/service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java
+++ 
b/service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java
@@ -22,6 +22,7 @@ import java.util.Arrays;
 import javax.naming.NamingException;
 import javax.security.sasl.AuthenticationException;
 import org.apache.hadoop.hive.conf.HiveConf;
+import org.apache.hadoop.security.alias.CredentialProviderFactory;
 import org.apache.hive.service.auth.ldap.DirSearch;
 import org.apache.hive.service.auth.ldap.DirSearchFactory;
 import org.apache.hive.service.auth.ldap.LdapSearchFactory;
@@ -324,6 +325,118 @@ public class TestLdapAuthenticationProviderImpl {
     verify(search, atLeastOnce()).close();
   }
 
+  @Test
+  public void testAuthenticateWithBindInCredentialFilePasses() throws 
AuthenticationException, NamingException {
+    String bindUser = "cn=BindUser,ou=Users,ou=branch1,dc=mycorp,dc=com";
+    String bindPass = "testPassword";
+    String authFullUser = "cn=user1,ou=Users,ou=branch1,dc=mycorp,dc=com";
+    String authUser = "user1";
+    String authPass = "Blah";
+    String tmpDir = System.getProperty("build.dir");
+    String credentialsPath = "jceks://file" + tmpDir + 
"/test-classes/creds/test.jceks";
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_USER, bindUser);
+    conf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, 
credentialsPath);
+
+    System.out.println(tmpDir);
+
+    when(search.findUserDn(eq(authUser))).thenReturn(authFullUser);
+
+    auth = new LdapAuthenticationProviderImpl(conf, factory);
+    auth.Authenticate(authUser, authPass);
+
+    verify(factory, times(1)).getInstance(isA(HiveConf.class), eq(bindUser), 
eq(bindPass));
+    verify(factory, times(1)).getInstance(isA(HiveConf.class), 
eq(authFullUser), eq(authPass));
+    verify(search, times(1)).findUserDn(eq(authUser));
+  }
+
+  @Test
+  public void testAuthenticateWithBindInMissingCredentialFilePasses() throws 
AuthenticationException, NamingException {
+    String bindUser = "cn=BindUser,ou=Users,ou=branch1,dc=mycorp,dc=com";
+    String authUser = "user1";
+    String authPass = "Blah";
+    String tmpDir = System.getProperty("build.dir");
+    String credentialsPath = "jceks://file" + tmpDir + 
"/test-classes/creds/nonExistent.jceks";
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_USER, bindUser);
+    conf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, 
credentialsPath);
+
+    auth = new LdapAuthenticationProviderImpl(conf, factory);
+    auth.Authenticate(authUser, authPass);
+
+    verify(factory, times(1)).getInstance(isA(HiveConf.class), eq(authUser), 
eq(authPass));
+  }
+
+  @Test
+  public void testAuthenticateWithBindUserPasses() throws 
AuthenticationException, NamingException {
+    String bindUser = "cn=BindUser,ou=Users,ou=branch1,dc=mycorp,dc=com";
+    String bindPass = "Blah";
+    String authFullUser = "cn=user1,ou=Users,ou=branch1,dc=mycorp,dc=com";
+    String authUser = "user1";
+    String authPass = "Blah2";
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_USER, bindUser);
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_PASSWORD, 
bindPass);
+
+    when(search.findUserDn(eq(authUser))).thenReturn(authFullUser);
+
+    auth = new LdapAuthenticationProviderImpl(conf, factory);
+    auth.Authenticate(authUser, authPass);
+
+    verify(factory, times(1)).getInstance(isA(HiveConf.class), eq(bindUser), 
eq(bindPass));
+    verify(factory, times(1)).getInstance(isA(HiveConf.class), 
eq(authFullUser), eq(authPass));
+    verify(search, times(1)).findUserDn(eq(authUser));
+  }
+
+  @Test
+  public void testAuthenticateWithBindUserFailsOnAuthentication() throws 
AuthenticationException, NamingException {
+    String bindUser = "cn=BindUser,ou=Users,ou=branch1,dc=mycorp,dc=com";
+    String bindPass = "Blah";
+    String authFullUser = "cn=user1,ou=Users,ou=branch1,dc=mycorp,dc=com";
+    String authUser = "user1";
+    String authPass = "Blah2";
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_USER, bindUser);
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_PASSWORD, 
bindPass);
+
+    thrown.expect(AuthenticationException.class);
+    when(factory.getInstance(any(HiveConf.class), eq(authFullUser), 
eq(authPass))).
+      thenThrow(AuthenticationException.class);
+    when(search.findUserDn(eq(authUser))).thenReturn(authFullUser);
+
+    auth = new LdapAuthenticationProviderImpl(conf, factory);
+    auth.Authenticate(authUser, authPass);
+  }
+
+  @Test
+  public void testAuthenticateWithBindUserFailsOnGettingDn() throws 
AuthenticationException, NamingException {
+    String bindUser = "cn=BindUser,ou=Users,ou=branch1,dc=mycorp,dc=com";
+    String bindPass = "Blah";
+    String authFullUser = "cn=user1,ou=Users,ou=branch1,dc=mycorp,dc=com";
+    String authUser = "user1";
+    String authPass = "Blah2";
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_USER, bindUser);
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_PASSWORD, 
bindPass);
+
+    thrown.expect(AuthenticationException.class);
+    when(search.findUserDn(eq(authUser))).thenThrow(NamingException.class);
+
+    auth = new LdapAuthenticationProviderImpl(conf, factory);
+    auth.Authenticate(authUser, authPass);
+  }
+
+  @Test
+  public void testAuthenticateWithBindUserFailsOnBinding() throws 
AuthenticationException {
+    String bindUser = "cn=BindUser,ou=Users,ou=branch1,dc=mycorp,dc=com";
+    String bindPass = "Blah";
+    String authUser = "user1";
+    String authPass = "Blah2";
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_USER, bindUser);
+    conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_PASSWORD, 
bindPass);
+
+    thrown.expect(AuthenticationException.class);
+    when(factory.getInstance(any(HiveConf.class), eq(bindUser), 
eq(bindPass))).thenThrow(AuthenticationException.class);
+
+    auth = new LdapAuthenticationProviderImpl(conf, factory);
+    auth.Authenticate(authUser, authPass);
+  }
+
   private void expectAuthenticationExceptionForInvalidPassword() {
     thrown.expect(AuthenticationException.class);
     thrown.expectMessage("a null or blank password has been provided");
diff --git a/service/src/test/resources/creds/test.jceks 
b/service/src/test/resources/creds/test.jceks
new file mode 100755
index 00000000000..8d58c414192
Binary files /dev/null and b/service/src/test/resources/creds/test.jceks differ

Reply via email to