You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kyuubi.apache.org by fe...@apache.org on 2022/08/18 12:29:21 UTC

[incubator-kyuubi] branch master updated: [KYUUBI #3020] kyuubi ldap add new config property kyuubi.authentication.ldap.bindpw and kyuubi.authentication.ldap.attrs

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

feiwang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-kyuubi.git


The following commit(s) were added to refs/heads/master by this push:
     new 5d88c7b20 [KYUUBI #3020] kyuubi ldap add new config property kyuubi.authentication.ldap.bindpw and kyuubi.authentication.ldap.attrs
5d88c7b20 is described below

commit 5d88c7b207b97ea2f11b43811f47b2619d04f14e
Author: pengqli <pe...@cisco.com>
AuthorDate: Thu Aug 18 20:29:12 2022 +0800

    [KYUUBI #3020] kyuubi ldap add new config property kyuubi.authentication.ldap.bindpw and kyuubi.authentication.ldap.attrs
    
    ### _Why are the changes needed?_
    
    Among the kyuubi ldap configuration parameters, there is currently "kyuubi.authentication.ldap.base.dn=", but there is no place corresponding to the CN password setting. similar to hive
    "hive.server2.authentication.ldap.binddn",
    "hive.server2.authentication.ldap.bindpw".
    
    ### _How was this patch tested?_
    - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible
    
    - [x] Add screenshots for manual tests if appropriate
    
    - [ ] [Run test](https://kyuubi.apache.org/docs/latest/develop_tools/testing.html#running-tests) locally before make a pull request
    
    Closes #3213 from dev-lpq/branch-ldap-1.6.
    
    Closes #3020
    
    31817cc6 [pengqli] upgrade ldap to support ldap before 1.6.0
    31094030 [pengqli] upgrade ldap to support ldap before 1.6.0
    f62e4c96 [pengqli] upgrade ldap to support ldap before 1.6.0
    e03e036e [pengqli] add AUTHENTICATION_LDAP_GUIDKEY deprecated
    6f680dfc [pengqli] add ldap config AUTHENTICATION_LDAP_BINDDN
    19f8e6ce [pengqli] add ldap config AUTHENTICATION_LDAP_BINDDN
    07df56b8 [pengqli]  upgrade LdapAuthenticationProviderImpl
    1a48f418 [pengqli] upgrade settings.md
    8e9b5721 [pengqli] upgrade ldap ldapBindpw
    5793a811 [pengqli] upgrade ldap ldapBindpw
    822d4c30 [pengqli] upgrade docs/deployment/settings.md
    c2b20418 [pengqli] upgrade ldap WithLdapServer UTs
    5d7a301c [pengqli] add AUTHENTICATION_LDAP_PASSWORD into serverOnlyConfEntries
    10b42b1b [pengqli] upgrade AuthenticationProviderFactorySuite UTs
    490bfae8 [pengqli] kyuubi ldap add new config property kyuubi.authentication.ldap.bindnpw and kyuubi.authentication.ldap.attrs
    5e071252 [pengqli] upgrafe config kyuubi.authentication.ldap.attrs
    4e771ec6 [pengqli] kyuubi ldap add new config property kyuubi.authentication.ldap.bindnpw and kyuubi.authentication.ldap.attrs
    
    Authored-by: pengqli <pe...@cisco.com>
    Signed-off-by: Fei Wang <fw...@ebay.com>
---
 docs/deployment/settings.md                        |  5 +-
 .../org/apache/kyuubi/config/KyuubiConf.scala      | 38 ++++++++-
 .../LdapAuthenticationProviderImpl.scala           | 93 +++++++++++++++++-----
 .../AuthenticationProviderFactorySuite.scala       | 29 ++++++-
 .../LdapAuthenticationProviderImplSuite.scala      | 80 ++++++++++++++++++-
 .../service/authentication/WithLdapServer.scala    | 53 +++++++++++-
 .../org/apache/kyuubi/RestClientTestHelper.scala   |  4 +
 .../KyuubiOperationKerberosAndPlainAuthSuite.scala |  4 +
 ...rationThriftHttpKerberosAndPlainAuthSuite.scala |  4 +
 .../server/rest/client/BatchRestApiSuite.scala     |  2 +-
 10 files changed, 279 insertions(+), 33 deletions(-)

diff --git a/docs/deployment/settings.md b/docs/deployment/settings.md
index 9dc974787..e0a499bdd 100644
--- a/docs/deployment/settings.md
+++ b/docs/deployment/settings.md
@@ -143,9 +143,12 @@ kyuubi.authentication.jdbc.password|&lt;undefined&gt;|Database password for JDBC
 kyuubi.authentication.jdbc.query|&lt;undefined&gt;|Query SQL template with placeholders for JDBC Authentication Provider to execute. Authentication passes if at least one row fetched in the result set.Available placeholders are: <ul><li>`${username}`</li><li>`${password}`</li></ul>eg.: query sql `SELECT 1 FROM auth_table WHERE user=${username} AND passwd=MD5(CONCAT(salt,${password}));` will be prepared as: `SELECT 1 FROM auth_table WHERE user=? AND passwd=MD5(CONCAT(salt,?));` with value [...]
 kyuubi.authentication.jdbc.url|&lt;undefined&gt;|JDBC URL for JDBC Authentication Provider.|string|1.6.0
 kyuubi.authentication.jdbc.username|&lt;undefined&gt;|Database username for JDBC Authentication Provider.|string|1.6.0
+kyuubi.authentication.ldap.attrs|mail|Specifies part of the search as an attribute returned by LDAP. For example: mail,name.|seq|1.6.0
 kyuubi.authentication.ldap.base.dn|&lt;undefined&gt;|LDAP base DN.|string|1.0.0
+kyuubi.authentication.ldap.binddn|&lt;undefined&gt;|The user with which to bind to the LDAP server, and search for the full domain name of the user being authenticated. For example: uid=admin,cn=Directory Manager,ou=users,dc=example,dc=com|string|1.6.0
+kyuubi.authentication.ldap.bindpw|&lt;undefined&gt;|The password for the bind user, to be used to search for the full name of the user being authenticated.|string|1.6.0
 kyuubi.authentication.ldap.domain|&lt;undefined&gt;|LDAP domain.|string|1.0.0
-kyuubi.authentication.ldap.guidKey|uid|LDAP attribute name whose values are unique in this LDAP server.For example:uid or cn.|string|1.2.0
+kyuubi.authentication.ldap.guidKey|uid|(deprecated)LDAP attribute name whose values are unique in this LDAP server.For example:uid or cn.|string|1.2.0
 kyuubi.authentication.ldap.url|&lt;undefined&gt;|SPACE character separated LDAP connection URL(s).|string|1.0.0
 kyuubi.authentication.sasl.qop|auth|Sasl QOP enable higher levels of protection for Kyuubi communication with clients.<ul> <li>auth - authentication only (default)</li> <li>auth-int - authentication plus integrity protection</li> <li>auth-conf - authentication plus integrity and confidentiality protection. This is applicable only if Kyuubi is configured to use Kerberos authentication.</li> </ul>|string|1.0.0
 
diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala
index 7b72f4d2d..844302cc9 100644
--- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala
+++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala
@@ -639,14 +639,41 @@ object KyuubiConf {
       .stringConf
       .createOptional
 
+  @deprecated(s"using kyuubi.authentication.ldap.binddn instead", "1.6.0")
   val AUTHENTICATION_LDAP_GUIDKEY: ConfigEntry[String] =
     buildConf("kyuubi.authentication.ldap.guidKey")
-      .doc("LDAP attribute name whose values are unique in this LDAP server." +
+      .doc("(deprecated)LDAP attribute name whose values are unique in this LDAP server." +
         "For example:uid or cn.")
       .version("1.2.0")
       .stringConf
       .createWithDefault("uid")
 
+  val AUTHENTICATION_LDAP_BINDDN: OptionalConfigEntry[String] =
+    buildConf("kyuubi.authentication.ldap.binddn")
+      .doc("The user with which to bind to the LDAP server, and search for the full domain name " +
+        "of the user being authenticated." +
+        " For example: uid=admin,cn=Directory Manager,ou=users,dc=example,dc=com")
+      .version("1.6.0")
+      .stringConf
+      .createOptional
+
+  val AUTHENTICATION_LDAP_PASSWORD: OptionalConfigEntry[String] =
+    buildConf("kyuubi.authentication.ldap.bindpw")
+      .doc("The password for the bind user," +
+        " to be used to search for the full name of the user being authenticated.")
+      .version("1.6.0")
+      .stringConf
+      .createOptional
+
+  val AUTHENTICATION_LDAP_ATTRIBUTES: ConfigEntry[Seq[String]] =
+    buildConf("kyuubi.authentication.ldap.attrs")
+      .doc("Specifies part of the search as an attribute returned by LDAP." +
+        " For example: mail,name.")
+      .version("1.6.0")
+      .stringConf
+      .toSequence()
+      .createWithDefault(Seq("mail"))
+
   val AUTHENTICATION_JDBC_DRIVER: OptionalConfigEntry[String] =
     buildConf("kyuubi.authentication.jdbc.driver.class")
       .doc("Driver class name for JDBC Authentication Provider.")
@@ -1923,7 +1950,8 @@ object KyuubiConf {
     SERVER_LIMIT_CONNECTIONS_PER_IPADDRESS,
     SERVER_LIMIT_CONNECTIONS_PER_USER_IPADDRESS,
     SERVER_LIMIT_CONNECTIONS_PER_USER,
-    SESSION_LOCAL_DIR_ALLOW_LIST)
+    SESSION_LOCAL_DIR_ALLOW_LIST,
+    AUTHENTICATION_LDAP_PASSWORD)
 
   /**
    * Holds information about keys that have been deprecated.
@@ -1992,7 +2020,11 @@ object KyuubiConf {
       DeprecatedConfig(
         "kyuubi.ha.zookeeper.acl.enabled",
         "1.3.2",
-        "Use kyuubi.ha.zookeeper.auth.type and kyuubi.ha.zookeeper.engine.auth.type instead"))
+        "Use kyuubi.ha.zookeeper.auth.type and kyuubi.ha.zookeeper.engine.auth.type instead"),
+      DeprecatedConfig(
+        AUTHENTICATION_LDAP_GUIDKEY.key,
+        "1.6.0",
+        s"using ${AUTHENTICATION_LDAP_BINDDN} instead"))
     Map(configs.map { cfg => cfg.key -> cfg }: _*)
   }
 
diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/LdapAuthenticationProviderImpl.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/LdapAuthenticationProviderImpl.scala
index b5e08def5..9bd80cdd7 100644
--- a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/LdapAuthenticationProviderImpl.scala
+++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/LdapAuthenticationProviderImpl.scala
@@ -17,8 +17,9 @@
 
 package org.apache.kyuubi.service.authentication
 
-import javax.naming.{Context, NamingException}
-import javax.naming.directory.InitialDirContext
+import javax.naming.{Context, NamingEnumeration, NamingException}
+import javax.naming.directory.{InitialDirContext, SearchControls, SearchResult}
+import javax.naming.ldap.InitialLdapContext
 import javax.security.sasl.AuthenticationException
 
 import org.apache.commons.lang3.StringUtils
@@ -58,29 +59,79 @@ class LdapAuthenticationProviderImpl(conf: KyuubiConf) extends PasswdAuthenticat
     conf.get(AUTHENTICATION_LDAP_URL).foreach(env.put(Context.PROVIDER_URL, _))
 
     val domain = conf.get(AUTHENTICATION_LDAP_DOMAIN)
-    val u =
-      if (!hasDomain(user) && domain.nonEmpty) {
-        user + "@" + domain.get
+    val mail = if (!hasDomain(user) && domain.nonEmpty) (user + "@" + domain.get) else user
+    var bindDn = conf.get(AUTHENTICATION_LDAP_BINDDN).getOrElse("")
+    val guidKey = conf.get(AUTHENTICATION_LDAP_GUIDKEY)
+
+    if ("".equals(bindDn)) {
+      bindDn = conf.get(AUTHENTICATION_LDAP_BASEDN) match {
+        case Some(dn) => guidKey + "=" + mail + "," + dn
+        case _ => mail
+      }
+      env.put(Context.SECURITY_PRINCIPAL, bindDn)
+      env.put(Context.SECURITY_CREDENTIALS, password)
+      try {
+        val ctx = new InitialDirContext(env)
+        ctx.close()
+      } catch {
+        case e: NamingException =>
+          throw new AuthenticationException(
+            s"Error validating LDAP user: $user," +
+              s" bindDn: $bindDn.",
+            e)
+      }
+    } else {
+      val baseDn = conf.get(AUTHENTICATION_LDAP_BASEDN).getOrElse("")
+      val bindPw = conf.get(AUTHENTICATION_LDAP_PASSWORD).getOrElse("")
+      val attrs = conf.get(AUTHENTICATION_LDAP_ATTRIBUTES).toArray
+      env.put(Context.SECURITY_PRINCIPAL, bindDn)
+      env.put(Context.SECURITY_CREDENTIALS, bindPw)
+      var nameEnuResults: NamingEnumeration[SearchResult] = null
+      try {
+        val ctx = new InitialLdapContext(env, null)
+        val sc = new SearchControls
+        sc.setReturningAttributes(attrs)
+        sc.setSearchScope(SearchControls.SUBTREE_SCOPE)
+        val searchFilter = String.format("(%s=%s)", "mail", mail)
+        nameEnuResults = ctx.search(baseDn, searchFilter, sc)
+      } catch {
+        case e: NamingException =>
+          throw new AuthenticationException(
+            s"LDAP InitialLdapContext failed, Error validating LDAP user: $user," +
+              s" bindDn: $bindDn.",
+            e)
+      }
+      if (nameEnuResults != null && nameEnuResults.hasMore) {
+        try {
+          val searchResult = nameEnuResults.next
+          val attrs = searchResult.getAttributes.getAll
+          if (!attrs.hasMore) {
+            throw new AuthenticationException(
+              s"LDAP attributes are empty, please check config " +
+                s"AUTHENTICATION_LDAP_ATTRIBUTES.key, Error validating LDAP user: $user," +
+                s" bindDn: $bindDn.")
+          }
+          while (attrs.hasMore) {
+            attrs.next
+            env.put(Context.SECURITY_PRINCIPAL, searchResult.getNameInNamespace)
+            env.put(Context.SECURITY_CREDENTIALS, password)
+            val ctx = new InitialDirContext(env)
+            ctx.close()
+          }
+        } catch {
+          case e: NamingException =>
+            throw new AuthenticationException(
+              s"LDAP InitialDirContext failed, Error validating LDAP user: $user," +
+                s" bindDn: $bindDn.",
+              e)
+        }
       } else {
-        user
+        throw new AuthenticationException(
+          s"LDAP InitialLdapContext search results are empty, Error validating LDAP user: $user," +
+            s" bindDn: $bindDn.")
       }
-
-    val guidKey = conf.get(AUTHENTICATION_LDAP_GUIDKEY)
-    val bindDn = conf.get(AUTHENTICATION_LDAP_BASEDN) match {
-      case Some(dn) => guidKey + "=" + u + "," + dn
-      case _ => u
     }
 
-    env.put(Context.SECURITY_PRINCIPAL, bindDn)
-    env.put(Context.SECURITY_CREDENTIALS, password)
-
-    try {
-      val ctx = new InitialDirContext(env)
-      ctx.close()
-    } catch {
-      case e: NamingException =>
-        throw new AuthenticationException(s"Error validating LDAP user: $bindDn", e)
-    }
   }
 
   private def hasDomain(userName: String): Boolean = ServiceUtils.indexOfDomainMatch(userName) > 0
diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/AuthenticationProviderFactorySuite.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/AuthenticationProviderFactorySuite.scala
index 0bd29ac56..4e829cf55 100644
--- a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/AuthenticationProviderFactorySuite.scala
+++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/AuthenticationProviderFactorySuite.scala
@@ -21,18 +21,41 @@ import javax.security.sasl.AuthenticationException
 
 import org.apache.kyuubi.{KyuubiFunSuite, Utils}
 import org.apache.kyuubi.config.KyuubiConf
+import org.apache.kyuubi.config.KyuubiConf._
 
 class AuthenticationProviderFactorySuite extends KyuubiFunSuite {
 
   import AuthenticationProviderFactory._
 
-  test("get auth provider") {
-    val conf = KyuubiConf()
+  private val conf = new KyuubiConf()
+
+  override def beforeAll(): Unit = {
+    super.beforeAll()
+  }
+
+  test("get auth provider before 1.6.0") {
+    conf.set(AUTHENTICATION_LDAP_BASEDN, "test")
+    val p1 = getAuthenticationProvider(AuthMethods.withName("NONE"), conf)
+    p1.authenticate(Utils.currentUser, "")
+    val p2 = getAuthenticationProvider(AuthMethods.withName("LDAP"), conf)
+    val e1 = intercept[AuthenticationException](p2.authenticate("test", "test"))
+    assert(e1.getMessage.contains("Error validating LDAP user"))
+    val e2 = intercept[AuthenticationException](
+      AuthenticationProviderFactory.getAuthenticationProvider(null, conf))
+    assert(e2.getMessage === "Not a valid authentication method")
+  }
+
+  test("get auth provider on since 1.6.0") {
+    conf.set(AUTHENTICATION_LDAP_BASEDN, "test")
+    conf.set(AUTHENTICATION_LDAP_BINDDN, "test")
+    conf.set(AUTHENTICATION_LDAP_PASSWORD, "test")
+    conf.set(AUTHENTICATION_LDAP_DOMAIN, "test")
+    conf.set(AUTHENTICATION_LDAP_ATTRIBUTES, Seq("test"))
     val p1 = getAuthenticationProvider(AuthMethods.withName("NONE"), conf)
     p1.authenticate(Utils.currentUser, "")
     val p2 = getAuthenticationProvider(AuthMethods.withName("LDAP"), conf)
     val e1 = intercept[AuthenticationException](p2.authenticate("test", "test"))
-    assert(e1.getMessage.contains("Error validating LDAP user:"))
+    assert(e1.getMessage.contains("Error validating LDAP user"))
     val e2 = intercept[AuthenticationException](
       AuthenticationProviderFactory.getAuthenticationProvider(null, conf))
     assert(e2.getMessage === "Not a valid authentication method")
diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/LdapAuthenticationProviderImplSuite.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/LdapAuthenticationProviderImplSuite.scala
index 639411628..97adf51eb 100644
--- a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/LdapAuthenticationProviderImplSuite.scala
+++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/LdapAuthenticationProviderImplSuite.scala
@@ -42,14 +42,14 @@ class LdapAuthenticationProviderImplSuite extends WithLdapServer {
     assert(ldapServer.getListenPort > 0)
   }
 
-  test("authenticate tests") {
+  test("Kyubbi ldap authenticate tests before 1.6.0") {
     val providerImpl = new LdapAuthenticationProviderImpl(conf)
     val e1 = intercept[AuthenticationException](providerImpl.authenticate("", ""))
     assert(e1.getMessage.contains("user is null"))
     val e2 = intercept[AuthenticationException](providerImpl.authenticate("kyuubi", ""))
     assert(e2.getMessage.contains("password is null"))
 
-    val user = "uid=kentyao,ou=users"
+    val user = "uid=kentyao,dc=example,dc=com"
     providerImpl.authenticate(user, "kentyao")
     val e3 = intercept[AuthenticationException](
       providerImpl.authenticate(user, "kent"))
@@ -68,6 +68,7 @@ class LdapAuthenticationProviderImplSuite extends WithLdapServer {
     val providerImpl3 = new LdapAuthenticationProviderImpl(conf)
     val e5 = intercept[AuthenticationException](
       providerImpl3.authenticate("kentyao", "kentyao"))
+    conf.set(AUTHENTICATION_LDAP_URL, ldapUrl)
 
     assert(e5.getMessage.contains(user))
     assert(e5.getCause.isInstanceOf[CommunicationException])
@@ -76,4 +77,79 @@ class LdapAuthenticationProviderImplSuite extends WithLdapServer {
     val providerImpl4 = new LdapAuthenticationProviderImpl(conf)
     intercept[AuthenticationException](providerImpl4.authenticate("kentyao", "kentyao"))
   }
+
+  test("Kyubbi ldap authenticate tests since 1.6.0") {
+    conf.set(AUTHENTICATION_LDAP_BASEDN, ldapBaseDn)
+    conf.set(AUTHENTICATION_LDAP_ATTRIBUTES, ldapAttrs)
+    conf.set(AUTHENTICATION_LDAP_BINDDN, ldapBinddn)
+    conf.set(AUTHENTICATION_LDAP_PASSWORD, ldapBindpw)
+    conf.set(AUTHENTICATION_LDAP_DOMAIN, ldapDomain)
+    val providerImpl = new LdapAuthenticationProviderImpl(conf)
+    val e1 = intercept[AuthenticationException](providerImpl.authenticate("", ""))
+    assert(e1.getMessage.contains("user is null"))
+    val e2 = intercept[AuthenticationException](providerImpl.authenticate("kyuubi", ""))
+    assert(e2.getMessage.contains("password is null"))
+
+    providerImpl.authenticate(ldapUser, ldapUserPasswd)
+
+    conf.set(AUTHENTICATION_LDAP_BASEDN, "dc=com")
+    val providerImpl1 = new LdapAuthenticationProviderImpl(conf)
+    val e3 = intercept[AuthenticationException](
+      providerImpl1.authenticate(ldapUser, ldapUserPasswd))
+    assert(e3.getMessage contains (ldapUser))
+    assert(e3.getMessage contains "Error validating LDAP user")
+    assert(e3.getCause.isInstanceOf[javax.naming.NameNotFoundException])
+    conf.set(AUTHENTICATION_LDAP_BASEDN, ldapBaseDn)
+
+    conf.set(
+      AUTHENTICATION_LDAP_BINDDN,
+      s"uid=admin,cn=Directory Manager,ou=users,dc=example,dc=com")
+    conf.set(AUTHENTICATION_LDAP_PASSWORD, "adminPasswordTest")
+    val providerImpl2 = new LdapAuthenticationProviderImpl(conf)
+    val e4 = intercept[AuthenticationException](
+      providerImpl2.authenticate(ldapUser, ldapUserPasswd))
+    assert(e4.getMessage contains (ldapUser))
+    assert(e4.getMessage contains "Error validating LDAP user")
+    assert(e4.getCause.isInstanceOf[javax.naming.AuthenticationException])
+    conf.set(AUTHENTICATION_LDAP_PASSWORD, ldapBindpw)
+
+    val providerImpl3 = new LdapAuthenticationProviderImpl(conf)
+    val e5 = intercept[AuthenticationException](
+      providerImpl3.authenticate(ldapUser, "kent"))
+    assert(e5.getMessage contains (ldapUser))
+    assert(e5.getMessage contains "Error validating LDAP user")
+    assert(e5.getCause.isInstanceOf[javax.naming.AuthenticationException])
+
+    val providerImpl4 = new LdapAuthenticationProviderImpl(conf)
+    val e6 = intercept[AuthenticationException](
+      providerImpl4.authenticate("kent", ldapUserPasswd))
+    assert(e6.getMessage contains ("kent"))
+    assert(e6.getMessage contains "Error validating LDAP user")
+
+    conf.set(AUTHENTICATION_LDAP_DOMAIN, "kyuubi.com")
+    val providerImpl5 = new LdapAuthenticationProviderImpl(conf)
+    val e7 =
+      intercept[AuthenticationException](providerImpl5.authenticate(ldapUser, ldapUserPasswd))
+    assert(e7.getMessage contains "Error validating LDAP user")
+    conf.set(AUTHENTICATION_LDAP_DOMAIN, "example")
+
+    conf.set(AUTHENTICATION_LDAP_ATTRIBUTES, Seq("cn"))
+    val providerImpl6 = new LdapAuthenticationProviderImpl(conf)
+    providerImpl6.authenticate(ldapUser, ldapUserPasswd)
+    conf.set(AUTHENTICATION_LDAP_ATTRIBUTES, Seq("mail"))
+
+    conf.set(AUTHENTICATION_LDAP_ATTRIBUTES, Seq("mobile"))
+    val providerImpl7 = new LdapAuthenticationProviderImpl(conf)
+    val e8 =
+      intercept[AuthenticationException](providerImpl7.authenticate(ldapUser, ldapUserPasswd))
+    assert(e8.getMessage contains "Error validating LDAP user")
+    conf.set(AUTHENTICATION_LDAP_ATTRIBUTES, Seq("mail"))
+
+    conf.unset(AUTHENTICATION_LDAP_URL)
+    val providerImpl8 = new LdapAuthenticationProviderImpl(conf)
+    val e9 = intercept[AuthenticationException](
+      providerImpl8.authenticate(ldapUser, ldapUserPasswd))
+    assert(e9.getMessage.contains(ldapUser))
+    assert(e9.getCause.isInstanceOf[CommunicationException])
+  }
 }
diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/WithLdapServer.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/WithLdapServer.scala
index 0bb38684e..814364470 100644
--- a/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/WithLdapServer.scala
+++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/service/authentication/WithLdapServer.scala
@@ -23,17 +23,23 @@ import org.apache.kyuubi.{KyuubiFunSuite, Utils}
 
 trait WithLdapServer extends KyuubiFunSuite {
   protected var ldapServer: InMemoryDirectoryServer = _
-  protected val ldapBaseDn = "ou=users"
+  protected val ldapBaseDn = "dc=example,dc=com"
   protected val ldapUser = Utils.currentUser
   protected val ldapUserPasswd = "ldapPassword"
+  protected val ldapBinddn = "uid=admin,cn=Directory Manager,ou=users,dc=example,dc=com"
+  protected val ldapBindpw = "adminPassword"
+  protected val ldapBaseUserDn = "ou=users,dc=example,dc=com"
+  protected val ldapDomain = "example"
+  protected val ldapAttrs = Seq("mail")
 
   protected def ldapUrl = s"ldap://localhost:${ldapServer.getListenPort}"
 
   override def beforeAll(): Unit = {
     val config = new InMemoryDirectoryServerConfig(ldapBaseDn)
-    config.addAdditionalBindCredentials(s"uid=$ldapUser,ou=users", ldapUserPasswd)
+    config.addAdditionalBindCredentials(ldapBinddn, ldapBindpw)
     ldapServer = new InMemoryDirectoryServer(config)
     ldapServer.startListening()
+    addLdapUser(ldapServer, ldapBaseDn, ldapBaseUserDn, ldapDomain, ldapUser, ldapUserPasswd)
     super.beforeAll()
   }
 
@@ -41,4 +47,47 @@ trait WithLdapServer extends KyuubiFunSuite {
     ldapServer.close()
     super.afterAll()
   }
+
+  def addLdapUser(
+      ldapServer: InMemoryDirectoryServer,
+      ldapBaseDn: String,
+      ldapBaseUserDn: String,
+      ldapDomain: String,
+      ldapUser: String,
+      ldapUserPasswd: String): Unit = {
+    ldapServer.add(
+      s"dn: $ldapBaseDn",
+      "objectClass: domain",
+      "objectClass: top",
+      "dc: example")
+    ldapServer.add(
+      s"dn: $ldapBaseUserDn",
+      "objectClass: top",
+      "objectClass: organizationalUnit",
+      "ou: users")
+    ldapServer.add(
+      s"dn: cn=$ldapUser,$ldapBaseUserDn",
+      s"cn: $ldapUser",
+      s"sn: $ldapUser",
+      s"userPassword: $ldapUserPasswd",
+      "objectClass: person")
+    ldapServer.add(
+      s"dn: uid=$ldapUser,cn=$ldapUser,$ldapBaseUserDn",
+      s"uid: $ldapUser",
+      s"mail: $ldapUser@$ldapDomain",
+      s"cn: $ldapUser",
+      s"sn: $ldapUser",
+      s"userPassword: $ldapUserPasswd",
+      "objectClass: person",
+      "objectClass: inetOrgPerson")
+    ldapServer.add(
+      s"dn: uid=$ldapUser,$ldapBaseDn",
+      s"uid: $ldapUser",
+      s"mail: $ldapUser@$ldapDomain",
+      s"cn: $ldapUser",
+      s"sn: $ldapUser",
+      s"userPassword: $ldapUserPasswd",
+      "objectClass: person",
+      "objectClass: inetOrgPerson")
+  }
 }
diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/RestClientTestHelper.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/RestClientTestHelper.scala
index d15993797..f6c3c22f5 100644
--- a/kyuubi-server/src/test/scala/org/apache/kyuubi/RestClientTestHelper.scala
+++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/RestClientTestHelper.scala
@@ -56,6 +56,10 @@ trait RestClientTestHelper extends RestFrontendTestHelper with KerberizedTestHel
       .set(
         KyuubiConf.AUTHENTICATION_CUSTOM_CLASS,
         classOf[UserDefineAuthenticationProviderImpl].getCanonicalName)
+      .set(KyuubiConf.AUTHENTICATION_LDAP_BINDDN, ldapBinddn)
+      .set(KyuubiConf.AUTHENTICATION_LDAP_PASSWORD, ldapBindpw)
+      .set(KyuubiConf.AUTHENTICATION_LDAP_DOMAIN, ldapDomain)
+      .set(KyuubiConf.AUTHENTICATION_LDAP_ATTRIBUTES, ldapAttrs)
   }
 
 }
diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationKerberosAndPlainAuthSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationKerberosAndPlainAuthSuite.scala
index 4e9c5dd78..4bcf09c5c 100644
--- a/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationKerberosAndPlainAuthSuite.scala
+++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationKerberosAndPlainAuthSuite.scala
@@ -67,6 +67,10 @@ class KyuubiOperationKerberosAndPlainAuthSuite extends WithKyuubiServer with Ker
       .set(
         KyuubiConf.AUTHENTICATION_CUSTOM_CLASS,
         classOf[UserDefineAuthenticationProviderImpl].getCanonicalName)
+      .set(KyuubiConf.AUTHENTICATION_LDAP_BINDDN, ldapBinddn)
+      .set(KyuubiConf.AUTHENTICATION_LDAP_PASSWORD, ldapBindpw)
+      .set(KyuubiConf.AUTHENTICATION_LDAP_DOMAIN, ldapDomain)
+      .set(KyuubiConf.AUTHENTICATION_LDAP_ATTRIBUTES, ldapAttrs)
   }
 
   test("test with KERBEROS TGT cache authentication") {
diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/thrift/http/KyuubiOperationThriftHttpKerberosAndPlainAuthSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/thrift/http/KyuubiOperationThriftHttpKerberosAndPlainAuthSuite.scala
index e0f32e843..ec9dc4070 100644
--- a/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/thrift/http/KyuubiOperationThriftHttpKerberosAndPlainAuthSuite.scala
+++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/thrift/http/KyuubiOperationThriftHttpKerberosAndPlainAuthSuite.scala
@@ -56,6 +56,10 @@ class KyuubiOperationThriftHttpKerberosAndPlainAuthSuite
         classOf[UserDefineAuthenticationProviderImpl].getCanonicalName)
       .set(KyuubiConf.SERVER_SPNEGO_KEYTAB, testKeytab)
       .set(KyuubiConf.SERVER_SPNEGO_PRINCIPAL, testSpnegoPrincipal)
+      .set(KyuubiConf.AUTHENTICATION_LDAP_BINDDN, ldapBinddn)
+      .set(KyuubiConf.AUTHENTICATION_LDAP_PASSWORD, ldapBindpw)
+      .set(KyuubiConf.AUTHENTICATION_LDAP_DOMAIN, ldapDomain)
+      .set(KyuubiConf.AUTHENTICATION_LDAP_ATTRIBUTES, ldapAttrs)
   }
 
   override protected def getJdbcUrl: String =
diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/rest/client/BatchRestApiSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/rest/client/BatchRestApiSuite.scala
index d144d61be..4148223ea 100644
--- a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/rest/client/BatchRestApiSuite.scala
+++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/rest/client/BatchRestApiSuite.scala
@@ -80,7 +80,7 @@ class BatchRestApiSuite extends RestClientTestHelper with BatchTestHelper {
       // get batch by id
       batchRestApi.getBatchById("1")
     }
-    assert(e.getCause.toString.contains(s"Error validating LDAP user: uid=${customUser}"))
+    assert(e.getCause.toString.contains(s"Error validating LDAP user: ${customUser}"))
 
     basicKyuubiRestClient.close()