[KYUUBI #3222] JDBC Authentication Provider for server
### _Why are the changes needed?_
Add JDBC authentication provider as implementation of PasswdAuthenticationProvider out of box.
Compared to currently support authentication methods like Kerberos and LDAP, JDBC source is a much easy and stable source for practical deployment.
The solution should provide:
easy to use and config jdbc connection details
handy to customize query statements for authentication
Adding JDBC to AuthMethods enum and JDBCPasswdAuthenticationProvider into package org.apache.kyuubi.service.authentication with listed features:
- specify JDBC driver name and load the driver class
- configs of JDBC url, username and password
- select query with placeholders for checking user and password , like `SELECT 1 from user_pass_hash where username = ${user} and password = MD5(${password})`
### _How was this patch tested?_
- [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible
- [ ] Add screenshots for manual tests if appropriate
- [x] [Run test](https://kyuubi.apache.org/docs/latest/develop_tools/testing.html#running-tests) locally before make a pull request
Closes #3235 from bowenliang123/feature-jdbc-auth-provider.
Closes #3222
17403b33 [liangbowen] cleanup docs
d5f43e0e [liangbowen] remove unuseful logs for unrecognized placeholder error
e9af0966 [liangbowen] use clone instead of repeatly generating configs
6fc42bf3 [liangbowen] code styling
a9404fa3 [liangbowen] use {} for intercept
77f5f86b [liangbowen] remove unuseful comment
6765affb [liangbowen] changed to use in-memory derby db for test
543c66cb [liangbowen] prefer scala style string usage
a4fe582f [liangbowen] refactor connection creation on using HikariDataSource in HikariCP. add HikariCP dependencies to kyuubi-common
3a4d5fe9 [liangbowen] update checkConfigs() signature
5a0ac495 [liangbowen] output password length only in checkConfigs
1c956dfb [liangbowen] update KyuubiAuthenticationFactorySuite
4ebe12e2 [liangbowen] add JDBC value to AuthTypes enum
9885f813 [liangbowen] add JDBC condition for getValidPasswordAuthMethod
b9ffac3c [liangbowen] Merge branch 'master' into feature-jdbc-auth-provider
aeb19ce5 [liangbowen] update doc
653bc126 [liangbowen] add more checks for query sql
cdec2066 [liangbowen] more test cases
36729197 [liangbowen] fix ddl statement and remove truncate statement in test
30974d16 [liangbowen] update format
575301ca [liangbowen] update options usage
1dc4187b [liangbowen] update settings.md config doc
cd2c7c2b [liangbowen] update settings.md config doc
15176b2b [liangbowen] fix import orders
46cc1dda [liangbowen] add config docs in docs/deployment/settings.md
7025330b [liangbowen] fix derby startup error in test
df4be568 [liangbowen] update code style
49c18c25 [liangbowen] update
996f7969 [liangbowen] add unit test in JdbcAuthenticationProviderImplSuite
0e7f0ad0 [liangbowen] refactor config and init process.remove unused import.
0dc75fee [liangbowen] implement JDBC Authentication Method
Authored-by: liangbowen <liangbowen@gf.com.cn>
Signed-off-by: Cheng Pan <chengpan@apache.org>
This commit is contained in:
parent
8c2e77465f
commit
a63587b1e3
@ -136,8 +136,13 @@ You can configure the Kyuubi properties in `$KYUUBI_HOME/conf/kyuubi-defaults.co
|
||||
|
||||
Key | Default | Meaning | Type | Since
|
||||
--- | --- | --- | --- | ---
|
||||
kyuubi.authentication|NONE|A comma separated list of client authentication types.<ul> <li>NOSASL: raw transport.</li> <li>NONE: no authentication check.</li> <li>KERBEROS: Kerberos/GSSAPI authentication.</li> <li>CUSTOM: User-defined authentication.</li> <li>LDAP: Lightweight Directory Access Protocol authentication.</li></ul> Note that: For KERBEROS, it is SASL/GSSAPI mechanism, and for NONE, CUSTOM and LDAP, they are all SASL/PLAIN mechanism. If only NOSASL is specified, the authentication will be NOSASL. For SASL authentication, KERBEROS and PLAIN auth type are supported at the same time, and only the first specified PLAIN auth type is valid.|seq|1.0.0
|
||||
kyuubi.authentication|NONE|A comma separated list of client authentication types.<ul> <li>NOSASL: raw transport.</li> <li>NONE: no authentication check.</li> <li>KERBEROS: Kerberos/GSSAPI authentication.</li> <li>CUSTOM: User-defined authentication.</li> <li>JDBC: JDBC query authentication.</li> <li>LDAP: Lightweight Directory Access Protocol authentication.</li></ul> Note that: For KERBEROS, it is SASL/GSSAPI mechanism, and for NONE, CUSTOM and LDAP, they are all SASL/PLAIN mechanism. If only NOSASL is specified, the authentication will be NOSASL. For SASL authentication, KERBEROS and PLAIN auth type are supported at the same time, and only the first specified PLAIN auth type is valid.|seq|1.0.0
|
||||
kyuubi.authentication.custom.class|<undefined>|User-defined authentication implementation of org.apache.kyuubi.service.authentication.PasswdAuthenticationProvider|string|1.3.0
|
||||
kyuubi.authentication.jdbc.driver.class|<undefined>|Driver class name for JDBC Authentication Provider.|string|1.6.0
|
||||
kyuubi.authentication.jdbc.password|<undefined>|Database password for JDBC Authentication Provider.|string|1.6.0
|
||||
kyuubi.authentication.jdbc.query|<undefined>|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 replacement of `username` and `password` in string type.|string|1.6.0
|
||||
kyuubi.authentication.jdbc.url|<undefined>|JDBC URL for JDBC Authentication Provider.|string|1.6.0
|
||||
kyuubi.authentication.jdbc.username|<undefined>|Database username for JDBC Authentication Provider.|string|1.6.0
|
||||
kyuubi.authentication.ldap.base.dn|<undefined>|LDAP base DN.|string|1.0.0
|
||||
kyuubi.authentication.ldap.domain|<undefined>|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
|
||||
|
||||
@ -114,6 +114,11 @@
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.hadoop</groupId>
|
||||
<artifactId>hadoop-minikdc</artifactId>
|
||||
@ -137,6 +142,12 @@
|
||||
<artifactId>failureaccess</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.derby</groupId>
|
||||
<artifactId>derby</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@ -593,7 +593,9 @@ object KyuubiConf {
|
||||
" <li>NONE: no authentication check.</li>" +
|
||||
" <li>KERBEROS: Kerberos/GSSAPI authentication.</li>" +
|
||||
" <li>CUSTOM: User-defined authentication.</li>" +
|
||||
" <li>LDAP: Lightweight Directory Access Protocol authentication.</li></ul>" +
|
||||
" <li>JDBC: JDBC query authentication.</li>" +
|
||||
" <li>LDAP: Lightweight Directory Access Protocol authentication.</li>" +
|
||||
"</ul>" +
|
||||
" Note that: For KERBEROS, it is SASL/GSSAPI mechanism," +
|
||||
" and for NONE, CUSTOM and LDAP, they are all SASL/PLAIN mechanism." +
|
||||
" If only NOSASL is specified, the authentication will be NOSASL." +
|
||||
@ -645,6 +647,51 @@ object KyuubiConf {
|
||||
.stringConf
|
||||
.createWithDefault("uid")
|
||||
|
||||
val AUTHENTICATION_JDBC_DRIVER: OptionalConfigEntry[String] =
|
||||
buildConf("kyuubi.authentication.jdbc.driver.class")
|
||||
.doc("Driver class name for JDBC Authentication Provider.")
|
||||
.version("1.6.0")
|
||||
.stringConf
|
||||
.createOptional
|
||||
|
||||
val AUTHENTICATION_JDBC_URL: OptionalConfigEntry[String] =
|
||||
buildConf("kyuubi.authentication.jdbc.url")
|
||||
.doc("JDBC URL for JDBC Authentication Provider.")
|
||||
.version("1.6.0")
|
||||
.stringConf
|
||||
.createOptional
|
||||
|
||||
val AUTHENTICATION_JDBC_USERNAME: OptionalConfigEntry[String] =
|
||||
buildConf("kyuubi.authentication.jdbc.username")
|
||||
.doc("Database username for JDBC Authentication Provider.")
|
||||
.version("1.6.0")
|
||||
.stringConf
|
||||
.createOptional
|
||||
|
||||
val AUTHENTICATION_JDBC_PASSWORD: OptionalConfigEntry[String] =
|
||||
buildConf("kyuubi.authentication.jdbc.password")
|
||||
.doc("Database password for JDBC Authentication Provider.")
|
||||
.version("1.6.0")
|
||||
.stringConf
|
||||
.createOptional
|
||||
|
||||
val AUTHENTICATION_JDBC_QUERY: OptionalConfigEntry[String] =
|
||||
buildConf("kyuubi.authentication.jdbc.query")
|
||||
.doc("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 replacement of `username` and `password` in string type.")
|
||||
.version("1.6.0")
|
||||
.stringConf
|
||||
.createOptional
|
||||
|
||||
val DELEGATION_KEY_UPDATE_INTERVAL: ConfigEntry[Long] =
|
||||
buildConf("kyuubi.delegation.key.update.interval")
|
||||
.doc("unused yet")
|
||||
|
||||
@ -19,5 +19,5 @@ package org.apache.kyuubi.service.authentication
|
||||
|
||||
object AuthMethods extends Enumeration {
|
||||
type AuthMethod = Value
|
||||
val NONE, LDAP, CUSTOM = Value
|
||||
val NONE, LDAP, JDBC, CUSTOM = Value
|
||||
}
|
||||
|
||||
@ -20,5 +20,5 @@ package org.apache.kyuubi.service.authentication
|
||||
object AuthTypes extends Enumeration {
|
||||
type AuthType = Value
|
||||
|
||||
val NOSASL, NONE, LDAP, KERBEROS, CUSTOM = Value
|
||||
val NOSASL, NONE, LDAP, JDBC, KERBEROS, CUSTOM = Value
|
||||
}
|
||||
|
||||
@ -44,6 +44,7 @@ object AuthenticationProviderFactory {
|
||||
conf: KyuubiConf): PasswdAuthenticationProvider = method match {
|
||||
case AuthMethods.NONE => new AnonymousAuthenticationProviderImpl
|
||||
case AuthMethods.LDAP => new LdapAuthenticationProviderImpl(conf)
|
||||
case AuthMethods.JDBC => new JdbcAuthenticationProviderImpl(conf)
|
||||
case AuthMethods.CUSTOM =>
|
||||
val className = conf.get(KyuubiConf.AUTHENTICATION_CUSTOM_CLASS)
|
||||
if (className.isEmpty) {
|
||||
|
||||
@ -0,0 +1,204 @@
|
||||
/*
|
||||
* 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.kyuubi.service.authentication
|
||||
|
||||
import java.sql.{Connection, PreparedStatement, Statement}
|
||||
import java.util.Properties
|
||||
import javax.security.sasl.AuthenticationException
|
||||
|
||||
import com.zaxxer.hikari.{HikariConfig, HikariDataSource}
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
|
||||
import org.apache.kyuubi.Logging
|
||||
import org.apache.kyuubi.config.KyuubiConf
|
||||
import org.apache.kyuubi.config.KyuubiConf._
|
||||
|
||||
class JdbcAuthenticationProviderImpl(conf: KyuubiConf) extends PasswdAuthenticationProvider
|
||||
with Logging {
|
||||
|
||||
private val driverClass = conf.get(AUTHENTICATION_JDBC_DRIVER)
|
||||
private val jdbcUrl = conf.get(AUTHENTICATION_JDBC_URL)
|
||||
private val jdbcUsername = conf.get(AUTHENTICATION_JDBC_USERNAME)
|
||||
private val jdbcUserPassword = conf.get(AUTHENTICATION_JDBC_PASSWORD)
|
||||
private val authQuerySql = conf.get(AUTHENTICATION_JDBC_QUERY)
|
||||
|
||||
private val SQL_PLACEHOLDER_REGEX = """\$\{.+?}""".r
|
||||
private val USERNAME_SQL_PLACEHOLDER = "${username}"
|
||||
private val PASSWORD_SQL_PLACEHOLDER = "${password}"
|
||||
|
||||
checkJdbcConfigs()
|
||||
|
||||
private[kyuubi] val hikariDataSource = getHikariDataSource
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@throws[AuthenticationException]
|
||||
override def authenticate(user: String, password: String): Unit = {
|
||||
if (StringUtils.isBlank(user)) {
|
||||
throw new AuthenticationException(s"Error validating, user is null" +
|
||||
s" or contains blank space")
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(password)) {
|
||||
throw new AuthenticationException(s"Error validating, password is null" +
|
||||
s" or contains blank space")
|
||||
}
|
||||
|
||||
var connection: Connection = null
|
||||
var queryStatement: PreparedStatement = null
|
||||
|
||||
try {
|
||||
connection = hikariDataSource.getConnection
|
||||
|
||||
queryStatement = getAndPrepareQueryStatement(connection, user, password)
|
||||
|
||||
val resultSet = queryStatement.executeQuery()
|
||||
|
||||
if (resultSet == null || !resultSet.next()) {
|
||||
// auth failed
|
||||
throw new AuthenticationException(s"Password does not match or no such user. user:" +
|
||||
s" $user , password length: ${password.length}")
|
||||
}
|
||||
|
||||
// auth passed
|
||||
|
||||
} catch {
|
||||
case e: AuthenticationException =>
|
||||
throw e
|
||||
case e: Exception =>
|
||||
error("Cannot get user info", e);
|
||||
throw e
|
||||
} finally {
|
||||
closeDbConnection(connection, queryStatement)
|
||||
}
|
||||
}
|
||||
|
||||
private def checkJdbcConfigs(): Unit = {
|
||||
def configLog(config: String, value: String): String = s"JDBCAuthConfig: $config = '$value'"
|
||||
|
||||
debug(configLog("Driver Class", driverClass.orNull))
|
||||
debug(configLog("JDBC URL", jdbcUrl.orNull))
|
||||
debug(configLog("Database username", jdbcUsername.orNull))
|
||||
debug(configLog("Database password length", jdbcUserPassword.getOrElse("").length.toString))
|
||||
debug(configLog("Query SQL", authQuerySql.orNull))
|
||||
|
||||
// Check if JDBC parameters valid
|
||||
if (driverClass.isEmpty) {
|
||||
throw new IllegalArgumentException("JDBC driver class is not configured.")
|
||||
}
|
||||
|
||||
if (jdbcUrl.isEmpty) {
|
||||
throw new IllegalArgumentException("JDBC url is not configured")
|
||||
}
|
||||
|
||||
if (jdbcUsername.isEmpty || jdbcUserPassword.isEmpty) {
|
||||
throw new IllegalArgumentException("JDBC username or password is not configured")
|
||||
}
|
||||
|
||||
// Check Query SQL
|
||||
if (authQuerySql.isEmpty) {
|
||||
throw new IllegalArgumentException("Query SQL is not configured")
|
||||
}
|
||||
val querySqlInLowerCase = authQuerySql.get.trim.toLowerCase
|
||||
if (!querySqlInLowerCase.startsWith("select")) { // allow select query sql only
|
||||
throw new IllegalArgumentException("Query SQL must start with \"SELECT\"");
|
||||
}
|
||||
if (!querySqlInLowerCase.contains("where")) {
|
||||
warn("Query SQL does not contains \"WHERE\" keyword");
|
||||
}
|
||||
if (!querySqlInLowerCase.contains("${username}")) {
|
||||
warn("Query SQL does not contains \"${username}\" placeholder");
|
||||
}
|
||||
}
|
||||
|
||||
private def getPlaceholderList(sql: String): List[String] = {
|
||||
SQL_PLACEHOLDER_REGEX.findAllMatchIn(sql)
|
||||
.map(m => m.matched)
|
||||
.toList
|
||||
}
|
||||
|
||||
private def getAndPrepareQueryStatement(
|
||||
connection: Connection,
|
||||
user: String,
|
||||
password: String): PreparedStatement = {
|
||||
|
||||
val preparedSql: String = {
|
||||
SQL_PLACEHOLDER_REGEX.replaceAllIn(authQuerySql.get, "?")
|
||||
}
|
||||
debug(s"prepared auth query sql: $preparedSql")
|
||||
|
||||
val stmt = connection.prepareStatement(preparedSql)
|
||||
stmt.setMaxRows(1) // minimum result size required for authentication
|
||||
|
||||
// Extract placeholder list and fill parameters to placeholders
|
||||
val placeholderList: List[String] = getPlaceholderList(authQuerySql.get)
|
||||
for (i <- placeholderList.indices) {
|
||||
val param = placeholderList(i) match {
|
||||
case USERNAME_SQL_PLACEHOLDER => user
|
||||
case PASSWORD_SQL_PLACEHOLDER => password
|
||||
case otherPlaceholder =>
|
||||
throw new IllegalArgumentException(
|
||||
s"Unrecognized Placeholder In Query SQL: $otherPlaceholder")
|
||||
}
|
||||
|
||||
stmt.setString(i + 1, param)
|
||||
}
|
||||
|
||||
stmt
|
||||
}
|
||||
|
||||
private def closeDbConnection(connection: Connection, statement: Statement): Unit = {
|
||||
if (statement != null && !statement.isClosed) {
|
||||
try {
|
||||
statement.close()
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
error("Cannot close PreparedStatement to auth database ", e)
|
||||
}
|
||||
}
|
||||
|
||||
if (connection != null && !connection.isClosed) {
|
||||
try {
|
||||
connection.close()
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
error("Cannot close connection to auth database ", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def getHikariDataSource: HikariDataSource = {
|
||||
val datasourceProperties = new Properties()
|
||||
val hikariConfig = new HikariConfig(datasourceProperties)
|
||||
hikariConfig.setDriverClassName(driverClass.orNull)
|
||||
hikariConfig.setJdbcUrl(jdbcUrl.orNull)
|
||||
hikariConfig.setUsername(jdbcUsername.orNull)
|
||||
hikariConfig.setPassword(jdbcUserPassword.orNull)
|
||||
hikariConfig.setPoolName("jdbc-auth-pool")
|
||||
|
||||
new HikariDataSource(hikariConfig)
|
||||
}
|
||||
}
|
||||
@ -140,6 +140,7 @@ class KyuubiAuthenticationFactory(conf: KyuubiConf, isServer: Boolean = true) ex
|
||||
debug(authTypes)
|
||||
if (none) AuthMethods.NONE
|
||||
else if (authTypes.contains(LDAP)) AuthMethods.LDAP
|
||||
else if (authTypes.contains(JDBC)) AuthMethods.JDBC
|
||||
else if (authTypes.contains(CUSTOM)) AuthMethods.CUSTOM
|
||||
else throw new IllegalArgumentException("No valid Password Auth detected")
|
||||
}
|
||||
|
||||
@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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.kyuubi.service.authentication
|
||||
|
||||
import java.sql.{Connection, DriverManager}
|
||||
import java.util.Properties
|
||||
import javax.security.sasl.AuthenticationException
|
||||
|
||||
import org.apache.kyuubi.KyuubiFunSuite
|
||||
import org.apache.kyuubi.config.KyuubiConf
|
||||
import org.apache.kyuubi.config.KyuubiConf._
|
||||
|
||||
class JdbcAuthenticationProviderImplSuite extends KyuubiFunSuite {
|
||||
protected val dbUser: String = "liangbowen"
|
||||
protected val dbPasswd: String = "liangbowen"
|
||||
protected var jdbcUrl: String = _
|
||||
|
||||
protected val authUser: String = "liangtiancheng"
|
||||
protected val authPasswd: String = "liangtiancheng"
|
||||
|
||||
protected var conf = new KyuubiConf()
|
||||
var conn: Connection = _
|
||||
var authDbName: String = "auth_db"
|
||||
|
||||
override def beforeAll(): Unit = {
|
||||
// init db
|
||||
val datasourceProperties = new Properties()
|
||||
datasourceProperties.put("user", dbUser)
|
||||
datasourceProperties.put("password", dbPasswd)
|
||||
|
||||
jdbcUrl = s"jdbc:derby:memory:$authDbName;create=true"
|
||||
conn = DriverManager.getConnection(
|
||||
s"$jdbcUrl;user=$dbUser;password=$dbPasswd",
|
||||
datasourceProperties)
|
||||
|
||||
conn.prepareStatement(s"CREATE SCHEMA $dbUser").execute
|
||||
|
||||
conn.prepareStatement(
|
||||
"""CREATE TABLE user_auth (
|
||||
|username VARCHAR(64) NOT NULL PRIMARY KEY,
|
||||
|passwd VARCHAR(64))""".stripMargin).execute();
|
||||
|
||||
val insertStmt = conn.prepareStatement("INSERT INTO user_auth " +
|
||||
"(username, passwd) VALUES (?,?)")
|
||||
insertStmt.setString(1, authUser)
|
||||
insertStmt.setString(2, authPasswd)
|
||||
insertStmt.execute();
|
||||
|
||||
conf = genJdbcAuthConfigs
|
||||
|
||||
super.beforeAll()
|
||||
}
|
||||
|
||||
override def afterAll(): Unit = {
|
||||
super.afterAll()
|
||||
|
||||
// cleanup db
|
||||
try {
|
||||
DriverManager.getConnection(s"jdbc:derby:memory:$authDbName;shutdown=true")
|
||||
} catch {
|
||||
case e: Throwable =>
|
||||
}
|
||||
}
|
||||
|
||||
test("authenticate tests") {
|
||||
val unchangedConf = genJdbcAuthConfigs
|
||||
|
||||
val providerImpl = new JdbcAuthenticationProviderImpl(conf)
|
||||
providerImpl.authenticate(authUser, authPasswd)
|
||||
|
||||
val e1 = intercept[AuthenticationException] {
|
||||
providerImpl.authenticate("", "")
|
||||
}
|
||||
assert(e1.getMessage.contains("user is null"))
|
||||
|
||||
val e2 = intercept[AuthenticationException] {
|
||||
providerImpl.authenticate(authUser, "")
|
||||
}
|
||||
assert(e2.getMessage.contains("password is null"))
|
||||
|
||||
val e4 = intercept[AuthenticationException] {
|
||||
providerImpl.authenticate(authUser, "wrong_password")
|
||||
}
|
||||
assert(e4.isInstanceOf[AuthenticationException])
|
||||
|
||||
conf = unchangedConf.clone()
|
||||
conf.unset(AUTHENTICATION_JDBC_URL)
|
||||
val e5 = intercept[IllegalArgumentException] { new JdbcAuthenticationProviderImpl(conf) }
|
||||
assert(e5.getMessage.contains("JDBC url is not configured"))
|
||||
|
||||
conf = unchangedConf.clone()
|
||||
conf.unset(AUTHENTICATION_JDBC_USERNAME)
|
||||
val e6 = intercept[IllegalArgumentException] { new JdbcAuthenticationProviderImpl(conf) }
|
||||
assert(e6.getMessage.contains("JDBC username or password is not configured"))
|
||||
|
||||
conf = unchangedConf.clone()
|
||||
conf.unset(AUTHENTICATION_JDBC_PASSWORD)
|
||||
val e7 = intercept[IllegalArgumentException] { new JdbcAuthenticationProviderImpl(conf) }
|
||||
assert(e7.getMessage.contains("JDBC username or password is not configured"))
|
||||
|
||||
conf = unchangedConf.clone()
|
||||
conf.unset(AUTHENTICATION_JDBC_QUERY)
|
||||
val e8 = intercept[IllegalArgumentException] { new JdbcAuthenticationProviderImpl(conf) }
|
||||
assert(e8.getMessage.contains("Query SQL is not configured"))
|
||||
|
||||
conf.set(
|
||||
AUTHENTICATION_JDBC_QUERY,
|
||||
"INSERT INTO user_auth (username, password) " +
|
||||
" VALUES ('demouser','demopassword'); ")
|
||||
val e9 = intercept[IllegalArgumentException] { new JdbcAuthenticationProviderImpl(conf) }
|
||||
assert(e9.getMessage.contains("Query SQL must start with \"SELECT\""))
|
||||
|
||||
conf.unset(AUTHENTICATION_JDBC_URL)
|
||||
val e10 = intercept[IllegalArgumentException] { new JdbcAuthenticationProviderImpl(conf) }
|
||||
assert(e10.getMessage.contains("JDBC url is not configured"))
|
||||
}
|
||||
|
||||
private def genJdbcAuthConfigs: KyuubiConf = {
|
||||
conf = new KyuubiConf()
|
||||
conf.set(AUTHENTICATION_JDBC_DRIVER, "org.apache.derby.jdbc.AutoloadedDriver")
|
||||
conf.set(AUTHENTICATION_JDBC_URL, jdbcUrl)
|
||||
conf.set(AUTHENTICATION_JDBC_USERNAME, dbUser)
|
||||
conf.set(AUTHENTICATION_JDBC_PASSWORD, dbPasswd)
|
||||
conf.set(
|
||||
AUTHENTICATION_JDBC_QUERY,
|
||||
"SELECT 1 FROM user_auth " +
|
||||
" WHERE username=${username} and passwd=${password}")
|
||||
conf
|
||||
}
|
||||
}
|
||||
@ -59,7 +59,7 @@ class KyuubiAuthenticationFactorySuite extends KyuubiFunSuite {
|
||||
val conf = KyuubiConf().set(KyuubiConf.AUTHENTICATION_METHOD, Seq("INVALID"))
|
||||
val e = intercept[IllegalArgumentException](new KyuubiAuthenticationFactory(conf))
|
||||
assert(e.getMessage contains "the authentication type should be one or more of" +
|
||||
" NOSASL,NONE,LDAP,KERBEROS,CUSTOM")
|
||||
" NOSASL,NONE,LDAP,JDBC,KERBEROS,CUSTOM")
|
||||
}
|
||||
|
||||
test("AuthType LDAP") {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user