diff --git a/.gitignore b/.gitignore
index 29815f67aaf..7c941e49b50 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,4 +34,5 @@ hs_err_pid*
spark-warehouse/
metastore_db
derby.log
+ldap
diff --git a/kyuubi-server/src/main/scala/org/apache/spark/KyuubiConf.scala b/kyuubi-server/src/main/scala/org/apache/spark/KyuubiConf.scala
index d8bfcd0a2e5..0b70c706dec 100644
--- a/kyuubi-server/src/main/scala/org/apache/spark/KyuubiConf.scala
+++ b/kyuubi-server/src/main/scala/org/apache/spark/KyuubiConf.scala
@@ -300,7 +300,8 @@ object KyuubiConf {
KyuubiConfigBuilder("spark.kyuubi.authentication")
.doc("Client authentication types." +
" NONE: no authentication check." +
- " KERBEROS: Kerberos/GSSAPI authentication")
+ " KERBEROS: Kerberos/GSSAPI authentication." +
+ " LDAP: Lightweight Directory Access Protocol authentication.")
.stringConf
.createWithDefault("NONE")
@@ -314,6 +315,24 @@ object KyuubiConf {
.stringConf
.createWithDefault("auth")
+ val AUTHENTICATION_LDAP_URL: ConfigEntry[String] =
+ KyuubiConfigBuilder("spark.kyuubi.authentication.ldap.url")
+ .doc("SPACE character separated LDAP connection URL(s).")
+ .stringConf
+ .createWithDefault("")
+
+ val AUTHENTICATION_LDAP_BASEDN: ConfigEntry[String] =
+ KyuubiConfigBuilder("spark.kyuubi.authentication.ldap.baseDN")
+ .doc("LDAP base DN.")
+ .stringConf
+ .createWithDefault("")
+
+ val AUTHENTICATION_LDAP_DOMAIN: ConfigEntry[String] =
+ KyuubiConfigBuilder("spark.kyuubi.authentication.ldap.Domain")
+ .doc("LDAP base DN.")
+ .stringConf
+ .createWithDefault("")
+
/////////////////////////////////////////////////////////////////////////////////////////////////
// Authorization //
/////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/AuthMethods.scala b/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/AuthMethods.scala
index 8f6ff3ff92a..b9ba5dd7a94 100644
--- a/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/AuthMethods.scala
+++ b/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/AuthMethods.scala
@@ -19,18 +19,15 @@ package yaooqinn.kyuubi.auth
import javax.security.sasl.AuthenticationException
-trait AuthMethods {
- def authMethodStr: String = null
-}
+object AuthMethods extends Enumeration {
-object AuthMethods {
+ type AuthMethods = Value
- case object NONE extends AuthMethods {
- override val authMethodStr = "NONE"
- }
+ val NONE, LDAP = Value
def getValidAuthMethod(authMethodStr: String): AuthMethods = authMethodStr match {
- case NONE.authMethodStr => NONE
+ case "NONE" => NONE
+ case "LDAP" => LDAP
case _ => throw new AuthenticationException("Not a valid authentication method")
}
}
diff --git a/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/AuthType.scala b/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/AuthType.scala
index 8a584b9c522..20bac080dba 100644
--- a/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/AuthType.scala
+++ b/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/AuthType.scala
@@ -19,30 +19,17 @@ package yaooqinn.kyuubi.auth
import yaooqinn.kyuubi.service.ServiceException
-abstract class AuthType {
- def name: String = ""
- override def toString: String = name
+object AuthType extends Enumeration {
-}
-
-object AuthType {
-
- case object NOSASL extends AuthType {
- override val name: String = "NOSASL"
- }
+ type AuthType = Value
- case object NONE extends AuthType {
- override val name: String = "NONE"
- }
-
- case object KERBEROS extends AuthType {
- override val name: String = "KERBEROS"
- }
+ val NOSASL, NONE, LDAP, KERBEROS = Value
def toAuthType(authTypeStr: String): AuthType = authTypeStr.toUpperCase match {
- case NOSASL.name => NOSASL
- case NONE.name => NONE
- case KERBEROS.name => KERBEROS
+ case "NOSASL" => NOSASL
+ case "NONE" => NONE
+ case "LDAP" => LDAP
+ case "KERBEROS" => KERBEROS
case other => throw new ServiceException("Unsupported authentication method: " + other)
}
}
diff --git a/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/AuthenticationProviderFactory.scala b/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/AuthenticationProviderFactory.scala
index 917c2e66c28..35697eb226c 100644
--- a/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/AuthenticationProviderFactory.scala
+++ b/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/AuthenticationProviderFactory.scala
@@ -18,14 +18,20 @@ package yaooqinn.kyuubi.auth
import javax.security.sasl.AuthenticationException
+import org.apache.spark.SparkConf
+
+import yaooqinn.kyuubi.auth.AuthMethods.AuthMethods
+
/**
* This class helps select a [[PasswdAuthenticationProvider]] for a given [[AuthMethods]]
*/
object AuthenticationProviderFactory {
@throws[AuthenticationException]
- def getAuthenticationProvider(method: AuthMethods): PasswdAuthenticationProvider = method match {
+ def getAuthenticationProvider(
+ method: AuthMethods,
+ conf: SparkConf): PasswdAuthenticationProvider = method match {
case AuthMethods.NONE => new AnonymousAuthenticationProviderImpl
+ case AuthMethods.LDAP => new LdapAuthenticationProviderImpl(conf)
case _ => throw new AuthenticationException("Not a valid authentication method")
-
}
}
diff --git a/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/KyuubiAuthFactory.scala b/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/KyuubiAuthFactory.scala
index 4a7d79d9961..4e942078f47 100644
--- a/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/KyuubiAuthFactory.scala
+++ b/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/KyuubiAuthFactory.scala
@@ -65,7 +65,7 @@ class KyuubiAuthFactory(conf: SparkConf) extends Logging {
throw new TTransportException("Failed to start token manager", e)
}
Some(server)
- case AuthType.NONE | AuthType.NOSASL => None
+ case AuthType.NONE | AuthType.NOSASL | AuthType.LDAP => None
case other => throw new ServiceException("Unsupported authentication method: " + other)
}
@@ -86,7 +86,7 @@ class KyuubiAuthFactory(conf: SparkConf) extends Logging {
}
case _ => authMethod match {
case AuthType.NOSASL => new TTransportFactory
- case _ => PlainSaslHelper.getTransportFactory(authMethod.name)
+ case _ => PlainSaslHelper.getTransportFactory(authMethod.toString, conf)
}
}
diff --git a/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/LdapAuthenticationProviderImpl.scala b/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/LdapAuthenticationProviderImpl.scala
new file mode 100644
index 00000000000..f96634142d0
--- /dev/null
+++ b/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/LdapAuthenticationProviderImpl.scala
@@ -0,0 +1,87 @@
+/*
+ * 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 yaooqinn.kyuubi.auth
+
+import java.util.Hashtable
+import javax.naming.{Context, NamingException}
+import javax.naming.directory.InitialDirContext
+import javax.security.sasl.AuthenticationException
+
+import org.apache.commons.lang3.StringUtils
+import org.apache.spark.{KyuubiConf, SparkConf}
+
+import yaooqinn.kyuubi.service.ServiceUtils
+
+class LdapAuthenticationProviderImpl(conf: SparkConf) extends PasswdAuthenticationProvider {
+
+ import KyuubiConf._
+
+ /**
+ * The authenticate method is called by the Kyuubi Server authentication layer
+ * to authenticate users for their requests.
+ * If a user is to be granted, return nothing/throw nothing.
+ * When a user is to be disallowed, throw an appropriate [[AuthenticationException]].
+ *
+ * @param user The username received over the connection request
+ * @param password The password received over the connection request
+ *
+ * @throws AuthenticationException When a user is found to be invalid by the implementation
+ */
+ override def authenticate(user: String, password: String): Unit = {
+ if (StringUtils.isBlank(user)) {
+ throw new AuthenticationException(s"Error validating LDAP user, user is null" +
+ s" or contains blank space")
+ }
+
+ if (StringUtils.isBlank(password)) {
+ throw new AuthenticationException(s"Error validating LDAP user, password is null" +
+ s" or contains blank space")
+ }
+
+ val env = new Hashtable[String, Any]()
+ env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory")
+ env.put(Context.SECURITY_AUTHENTICATION, "simple")
+
+ conf.getOption(AUTHENTICATION_LDAP_URL).foreach(env.put(Context.PROVIDER_URL, _))
+
+ val domain = conf.get(AUTHENTICATION_LDAP_DOMAIN, "")
+ val u = if (!hasDomain(user) && StringUtils.isNotBlank(domain)) {
+ user + "@" + domain
+ } else {
+ user
+ }
+
+ val bindDn = conf.getOption(AUTHENTICATION_LDAP_BASEDN) match {
+ case Some(dn) => "uid=" + 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-server/src/main/scala/yaooqinn/kyuubi/auth/PlainSaslHelper.scala b/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/PlainSaslHelper.scala
index adb9de7ee81..f37db78ef6b 100644
--- a/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/PlainSaslHelper.scala
+++ b/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/PlainSaslHelper.scala
@@ -25,9 +25,11 @@ import javax.security.sasl.{AuthenticationException, AuthorizeCallback}
import scala.collection.JavaConverters._
import org.apache.hive.service.cli.thrift.TCLIService.Iface
+import org.apache.spark.SparkConf
import org.apache.thrift.{TProcessor, TProcessorFactory}
import org.apache.thrift.transport.{TSaslServerTransport, TTransport, TTransportFactory}
+import yaooqinn.kyuubi.auth.AuthMethods.AuthMethods
import yaooqinn.kyuubi.auth.PlainSaslServer.SaslPlainProvider
object PlainSaslHelper {
@@ -41,10 +43,10 @@ object PlainSaslHelper {
}
@throws[LoginException]
- def getTransportFactory(authTypeStr: String): TTransportFactory = {
+ def getTransportFactory(authTypeStr: String, conf: SparkConf): TTransportFactory = {
val saslFactory = new TSaslServerTransport.Factory()
try {
- val handler = new PlainServerCallbackHandler(authTypeStr)
+ val handler = new PlainServerCallbackHandler(authTypeStr, conf)
val props = Map.empty[String, String]
saslFactory.addServerDefinition("PLAIN", authTypeStr, null, props.asJava, handler)
} catch {
@@ -54,10 +56,11 @@ object PlainSaslHelper {
saslFactory
}
- private class PlainServerCallbackHandler private(authMethod: AuthMethods)
+ private class PlainServerCallbackHandler private(authMethod: AuthMethods, conf: SparkConf)
extends CallbackHandler {
@throws[AuthenticationException]
- def this(authMethodStr: String) = this(AuthMethods.getValidAuthMethod(authMethodStr))
+ def this(authMethodStr: String, conf: SparkConf) =
+ this(AuthMethods.getValidAuthMethod(authMethodStr), conf)
@throws[IOException]
@throws[UnsupportedCallbackException]
@@ -75,7 +78,7 @@ object PlainSaslHelper {
case _ => throw new UnsupportedCallbackException(callback)
}
}
- val provider = AuthenticationProviderFactory.getAuthenticationProvider(authMethod)
+ val provider = AuthenticationProviderFactory.getAuthenticationProvider(authMethod, conf)
provider.authenticate(username, password)
if (ac != null) ac.setAuthorized(true)
}
diff --git a/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/PlainSaslServer.scala b/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/PlainSaslServer.scala
index ecef8814ef4..917c20b8e17 100644
--- a/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/PlainSaslServer.scala
+++ b/kyuubi-server/src/main/scala/yaooqinn/kyuubi/auth/PlainSaslServer.scala
@@ -22,6 +22,8 @@ import java.util.{ArrayDeque => JDeque, Map => JMap}
import javax.security.auth.callback._
import javax.security.sasl.{AuthorizeCallback, SaslException, SaslServer, SaslServerFactory}
+import yaooqinn.kyuubi.auth.AuthMethods.AuthMethods
+
object PlainSaslServer {
val PLAIN_METHOD = "PLAIN"
@@ -70,7 +72,7 @@ class PlainSaslServer(handler: CallbackHandler, authMethod: AuthMethods) extends
case 0 =>
tokenList.addLast(messageToken.toString)
messageToken.setLength(0)
- case b: Byte => messageToken.append(b)
+ case b: Byte => messageToken.append(b.toChar)
}
tokenList.addLast(messageToken.toString)
// validate response
diff --git a/kyuubi-server/src/main/scala/yaooqinn/kyuubi/server/FrontendService.scala b/kyuubi-server/src/main/scala/yaooqinn/kyuubi/server/FrontendService.scala
index bed6ff6019f..2341620a6c7 100644
--- a/kyuubi-server/src/main/scala/yaooqinn/kyuubi/server/FrontendService.scala
+++ b/kyuubi-server/src/main/scala/yaooqinn/kyuubi/server/FrontendService.scala
@@ -151,7 +151,7 @@ private[kyuubi] class FrontendService private(name: String, beService: BackendSe
def getServerIPAddress: InetAddress = serverIPAddress
private def isKerberosAuthMode: Boolean = {
- conf.get(KyuubiConf.AUTHENTICATION_METHOD).equalsIgnoreCase(AuthType.KERBEROS.name)
+ conf.get(KyuubiConf.AUTHENTICATION_METHOD).equalsIgnoreCase(AuthType.KERBEROS.toString)
}
private def getUserName(req: TOpenSessionReq): String = {
diff --git a/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/AuthMethodsSuite.scala b/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/AuthMethodsSuite.scala
index c63511f2223..9a2ce20eee0 100644
--- a/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/AuthMethodsSuite.scala
+++ b/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/AuthMethodsSuite.scala
@@ -21,14 +21,17 @@ import javax.security.sasl.AuthenticationException
import org.apache.spark.SparkFunSuite
+import yaooqinn.kyuubi.auth.AuthMethods._
+
class AuthMethodsSuite extends SparkFunSuite {
- test("testGetValidAuthMethod") {
- assert(new AuthMethods{}.authMethodStr === null)
- assert(AuthMethods.getValidAuthMethod("NONE") === AuthMethods.NONE)
- assert(AuthMethods.getValidAuthMethod(AuthMethods.NONE.authMethodStr) === AuthMethods.NONE)
+ test("get valid auth method") {
+ assert(getValidAuthMethod("NONE") === NONE)
+ assert(getValidAuthMethod(NONE.toString) === NONE)
+ assert(getValidAuthMethod("LDAP") === LDAP)
+ assert(getValidAuthMethod(LDAP.toString) === LDAP)
- assert(AuthMethods.NONE.authMethodStr === "NONE")
- intercept[AuthenticationException](AuthMethods.getValidAuthMethod("ELSE"))
+ assert(NONE.toString === "NONE")
+ intercept[AuthenticationException](getValidAuthMethod("ELSE"))
}
}
diff --git a/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/AuthTypeSuite.scala b/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/AuthTypeSuite.scala
index 9d5797ca685..a3f16ae1900 100644
--- a/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/AuthTypeSuite.scala
+++ b/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/AuthTypeSuite.scala
@@ -19,27 +19,24 @@ package yaooqinn.kyuubi.auth
import org.apache.spark.SparkFunSuite
+import yaooqinn.kyuubi.auth.AuthType._
import yaooqinn.kyuubi.service.ServiceException
class AuthTypeSuite extends SparkFunSuite {
- test("test name") {
- assert(new AuthType {}.name === "")
- assert(AuthType.NONE.name === "NONE")
- assert(AuthType.KERBEROS.name === "KERBEROS")
- assert(AuthType.NOSASL.name === "NOSASL")
- }
-
test("to auth type") {
- assert(AuthType.toAuthType("NONE") === AuthType.NONE)
- assert(AuthType.toAuthType(AuthType.NONE.name) === AuthType.NONE)
+ assert(toAuthType("NOSASL") === NOSASL)
+ assert(toAuthType(NOSASL.toString) === NOSASL)
+
+ assert(toAuthType("NONE") === NONE)
+ assert(toAuthType(NONE.toString) === NONE)
- assert(AuthType.toAuthType("KERBEROS") === AuthType.KERBEROS)
- assert(AuthType.toAuthType(AuthType.KERBEROS.name) === AuthType.KERBEROS)
+ assert(toAuthType("LDAP") === LDAP)
+ assert(toAuthType(LDAP.toString) === LDAP)
- assert(AuthType.toAuthType("NOSASL") === AuthType.NOSASL)
- assert(AuthType.toAuthType(AuthType.NOSASL.name) === AuthType.NOSASL)
+ assert(toAuthType("KERBEROS") === KERBEROS)
+ assert(toAuthType(KERBEROS.toString) === KERBEROS)
- intercept[ServiceException](AuthType.toAuthType("ELSE"))
+ intercept[ServiceException](toAuthType("ELSE"))
}
}
diff --git a/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/AuthenticationProviderFactorySuite.scala b/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/AuthenticationProviderFactorySuite.scala
index 681902a4a82..fae185b3888 100644
--- a/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/AuthenticationProviderFactorySuite.scala
+++ b/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/AuthenticationProviderFactorySuite.scala
@@ -19,17 +19,22 @@ package yaooqinn.kyuubi.auth
import javax.security.sasl.AuthenticationException
-import org.apache.spark.SparkFunSuite
+import org.apache.spark.{SparkConf, SparkFunSuite}
class AuthenticationProviderFactorySuite extends SparkFunSuite {
test("testGetAuthenticationProvider") {
- AuthenticationProviderFactory
- .getAuthenticationProvider(AuthMethods.NONE).authenticate("test", "test")
- intercept[AuthenticationException](AuthenticationProviderFactory
- .getAuthenticationProvider(new AuthMethods {
- override def authMethodStr: String = "TEST"
- }))
+ val conf = new SparkConf()
+ val anonymous = AuthenticationProviderFactory.getAuthenticationProvider(AuthMethods.NONE, conf)
+ anonymous.authenticate("test", "test")
+
+ val ldap = AuthenticationProviderFactory.getAuthenticationProvider(AuthMethods.LDAP, conf)
+ val exception = intercept[AuthenticationException](ldap.authenticate("test", "test"))
+ assert(exception.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-server/src/test/scala/yaooqinn/kyuubi/auth/KyuubiAuthFactorySuite.scala b/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/KyuubiAuthFactorySuite.scala
index f9c4b2c8198..b128c171dc4 100644
--- a/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/KyuubiAuthFactorySuite.scala
+++ b/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/KyuubiAuthFactorySuite.scala
@@ -25,7 +25,6 @@ import org.apache.hadoop.security.authorize.AuthorizationException
import org.apache.spark.{KyuubiConf, KyuubiSparkUtil, SparkConf, SparkFunSuite}
import yaooqinn.kyuubi.KyuubiSQLException
-import yaooqinn.kyuubi.server.KyuubiServer
import yaooqinn.kyuubi.service.ServiceException
import yaooqinn.kyuubi.utils.ReflectUtils
@@ -81,6 +80,13 @@ class KyuubiAuthFactorySuite extends SparkFunSuite {
assert(e.getMessage === "Unsupported authentication method: OTHER")
}
+ test("AuthType LDAP") {
+ val conf = new SparkConf(true).set(KyuubiConf.AUTHENTICATION_METHOD.key, "LDAP")
+ KyuubiSparkUtil.setupCommonConfig(conf)
+ val authFactory = new KyuubiAuthFactory(conf)
+ assert(authFactory.getIpAddress.isEmpty)
+ }
+
test("AuthType KERBEROS without keytab/principal") {
val conf = new SparkConf(true).set(KyuubiConf.AUTHENTICATION_METHOD.key, "KERBEROS")
KyuubiSparkUtil.setupCommonConfig(conf)
diff --git a/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/LdapAuthenticationProviderImplSuite.scala b/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/LdapAuthenticationProviderImplSuite.scala
new file mode 100644
index 00000000000..b86fd64960a
--- /dev/null
+++ b/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/LdapAuthenticationProviderImplSuite.scala
@@ -0,0 +1,187 @@
+/*
+ * 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 yaooqinn.kyuubi.auth
+
+import java.io.File
+import java.util.{Collections, Hashtable}
+import javax.naming.{CommunicationException, Context}
+import javax.naming.directory.{BasicAttribute, BasicAttributes, InitialDirContext}
+import javax.security.sasl.AuthenticationException
+
+import scala.collection.mutable.ArrayBuffer
+
+import org.apache.directory.api.ldap.model.name.Dn
+import org.apache.directory.api.ldap.schemaextractor.impl.DefaultSchemaLdifExtractor
+import org.apache.directory.api.ldap.schemaloader.LdifSchemaLoader
+import org.apache.directory.api.ldap.schemamanager.impl.DefaultSchemaManager
+import org.apache.directory.server.constants.ServerDNConstants
+import org.apache.directory.server.core.DefaultDirectoryService
+import org.apache.directory.server.core.api.{CacheService, InstanceLayout}
+import org.apache.directory.server.core.api.partition.Partition
+import org.apache.directory.server.core.api.schema.SchemaPartition
+import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmPartition
+import org.apache.directory.server.core.partition.ldif.LdifPartition
+import org.apache.directory.server.ldap.LdapServer
+import org.apache.directory.server.protocol.shared.transport.TcpTransport
+import org.apache.mina.util.AvailablePortFinder
+import org.apache.spark.{KyuubiConf, KyuubiSparkUtil, SparkConf, SparkFunSuite}
+
+class LdapAuthenticationProviderImplSuite extends SparkFunSuite {
+
+ import KyuubiConf._
+
+ private val servicePort = AvailablePortFinder.getNextAvailable(9000)
+ val service = new DefaultDirectoryService
+ private val workingDIr = KyuubiSparkUtil.createTempDir()
+ val ldapServer: LdapServer = new LdapServer()
+
+ val partitions = ArrayBuffer[Partition]()
+
+ val conf = new SparkConf()
+
+ override def beforeAll(): Unit = {
+ service.setInstanceLayout(new InstanceLayout(workingDIr))
+ val cachedService = new CacheService()
+ cachedService.initialize(service.getInstanceLayout)
+ service.setCacheService(cachedService)
+
+ val schemaPartitionDir = new File(service.getInstanceLayout.getPartitionsDirectory, "schema")
+ if (!schemaPartitionDir.exists()) {
+ val extractor = new DefaultSchemaLdifExtractor(
+ service.getInstanceLayout.getPartitionsDirectory)
+ extractor.extractOrCopy()
+ }
+
+ val loader = new LdifSchemaLoader(schemaPartitionDir)
+ val schemaManager = new DefaultSchemaManager(loader)
+ schemaManager.loadAllEnabled()
+ service.setSchemaManager(schemaManager)
+
+ val schemaLDifPartition = new LdifPartition(schemaManager)
+ schemaLDifPartition.setPartitionPath(schemaPartitionDir.toURI)
+ val schemaPartition = new SchemaPartition(schemaManager)
+ schemaPartition.setWrappedPartition(schemaLDifPartition)
+ service.setSchemaPartition(schemaPartition)
+
+ val jdbmPartition = new JdbmPartition(service.getSchemaManager)
+ jdbmPartition.setId("kyuubi")
+ jdbmPartition.setPartitionPath(
+ new File(service.getInstanceLayout.getPartitionsDirectory, jdbmPartition.getId).toURI)
+ jdbmPartition.setSuffixDn(new Dn(ServerDNConstants.SYSTEM_DN))
+ jdbmPartition.setSchemaManager(service.getSchemaManager)
+ service.setSystemPartition(jdbmPartition)
+
+ service.getChangeLog.setEnabled(false)
+
+ service.setDenormalizeOpAttrsEnabled(true)
+
+ service.startup()
+ ldapServer.setDirectoryService(service)
+ ldapServer.setTransports(new TcpTransport(servicePort))
+ ldapServer.start()
+
+ conf.set(AUTHENTICATION_LDAP_URL, "ldap://localhost:" + ldapServer.getPort)
+
+ addPartition("kentyao", "uid=kentyao,ou=users")
+ addUser()
+
+ super.beforeAll()
+ }
+
+ private def addUser(): Unit = {
+ val env = new Hashtable[String, Any]()
+ env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory")
+ env.put(Context.SECURITY_AUTHENTICATION, "simple")
+ env.put(Context.PROVIDER_URL, conf.get(AUTHENTICATION_LDAP_URL))
+ env.put(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system")
+ env.put(Context.SECURITY_CREDENTIALS, "secret")
+
+ val container = new BasicAttributes()
+ val objClasses = new BasicAttribute("objectClass")
+ objClasses.add("inetOrgPerson")
+ val cn = new BasicAttribute("cn", "kentyao")
+ val o = new BasicAttribute("o", "kyuubi")
+ val sn = new BasicAttribute("sn", "yao")
+ val uid = new BasicAttribute("uid", "kentyao")
+ val password = new BasicAttribute("userpassword", "kentyao")
+ container.put(objClasses)
+ container.put(cn)
+ container.put(uid)
+ container.put(o)
+ container.put(sn)
+ container.put(password)
+ val context = new InitialDirContext(env)
+ context.createSubcontext("uid=kentyao,ou=users", container)
+ }
+
+ private def addPartition(id: String, dn: String): Partition = {
+ val partition = new JdbmPartition(service.getSchemaManager)
+ partition.setId(id)
+ partition.setPartitionPath(
+ new File(service.getInstanceLayout.getPartitionsDirectory, partition.getId).toURI)
+ partition.setSuffixDn(new Dn(dn))
+ service.addPartition(partition)
+ partitions += partition
+ partition
+ }
+
+ override def afterAll(): Unit = {
+ if (ldapServer.isStarted) {
+ ldapServer.stop()
+ }
+ super.afterAll()
+ }
+
+ test("ldap server is started") {
+ assert(ldapServer.isStarted)
+ assert(ldapServer.getPort > 0)
+ }
+
+ test("authenticate tests") {
+ 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"
+ providerImpl.authenticate(user, "kentyao")
+ val e3 = intercept[AuthenticationException](
+ providerImpl.authenticate(user, "kent"))
+ assert(e3.getMessage.contains(user))
+ assert(e3.getCause.isInstanceOf[javax.naming.AuthenticationException])
+
+ val dn = "ou=users"
+ conf.set(AUTHENTICATION_LDAP_BASEDN, dn)
+ val providerImpl2 = new LdapAuthenticationProviderImpl(conf)
+ providerImpl2.authenticate("kentyao", "kentyao")
+
+ val e4 = intercept[AuthenticationException](
+ providerImpl.authenticate("kentyao", "kent"))
+ assert(e4.getMessage.contains(user))
+
+ conf.remove(AUTHENTICATION_LDAP_URL)
+ val providerImpl3 = new LdapAuthenticationProviderImpl(conf)
+ val e5 = intercept[AuthenticationException](
+ providerImpl3.authenticate("kentyao", "kentyao"))
+
+ assert(e5.getMessage.contains(user))
+ assert(e5.getCause.isInstanceOf[CommunicationException])
+ }
+
+}
diff --git a/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/PlainSaslHelperSuite.scala b/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/PlainSaslHelperSuite.scala
index 087c66a8eee..265628e57e5 100644
--- a/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/PlainSaslHelperSuite.scala
+++ b/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/PlainSaslHelperSuite.scala
@@ -38,10 +38,10 @@ class PlainSaslHelperSuite extends SparkFunSuite {
val tSocket = new TSocket("0.0.0.0", 0)
val tProcessor = tProcessorFactory.getProcessor(tSocket)
assert(tProcessor.isInstanceOf[TSetIpAddressProcessor[_]])
- intercept[LoginException](PlainSaslHelper.getTransportFactory("KERBEROS"))
- intercept[LoginException](PlainSaslHelper.getTransportFactory("NOSASL"))
- intercept[LoginException](PlainSaslHelper.getTransportFactory("ELSE"))
- val tTransportFactory = PlainSaslHelper.getTransportFactory("NONE")
+ intercept[LoginException](PlainSaslHelper.getTransportFactory("KERBEROS", conf))
+ intercept[LoginException](PlainSaslHelper.getTransportFactory("NOSASL", conf))
+ intercept[LoginException](PlainSaslHelper.getTransportFactory("ELSE", conf))
+ val tTransportFactory = PlainSaslHelper.getTransportFactory("NONE", conf)
assert(tTransportFactory.isInstanceOf[TSaslServerTransport.Factory])
Security.getProviders.exists(_.isInstanceOf[SaslPlainProvider])
}
diff --git a/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/PlainSaslServerSuite.scala b/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/PlainSaslServerSuite.scala
index e36c680620b..409a3d4e6de 100644
--- a/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/PlainSaslServerSuite.scala
+++ b/kyuubi-server/src/test/scala/yaooqinn/kyuubi/auth/PlainSaslServerSuite.scala
@@ -64,10 +64,10 @@ class PlainSaslServerSuite extends SparkFunSuite {
val e3 = intercept[SaslException](server.evaluateResponse(res3.map(_.toByte)))
assert(e3.getMessage === "Error validating the login")
assert(e3.getCause.getMessage === "No password name provided")
- val res4 = Array(1, 0, 1)
+ val res4 = Array(97, 0, 99)
server.evaluateResponse(res4.map(_.toByte))
assert(server.isComplete)
- assert(server.getAuthorizationID === "1")
+ assert(server.getAuthorizationID === "a")
intercept[UnsupportedOperationException](server.wrap(res4.map(_.toByte), 0, 3))
intercept[UnsupportedOperationException](server.unwrap(res4.map(_.toByte), 0, 3))
assert(server.getNegotiatedProperty("name") === null)
diff --git a/pom.xml b/pom.xml
index 2b14acee831..4b985fb991a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -291,14 +291,14 @@
test
- org.apache.directory.api
- api-all
+ org.apache.directory.server
+ *
- org.apache.directory.jdbm
- apacheds-jdbm1
+ org.apache.directory.api
+ api-all
-
+
org.apache.directory.server