-
Notifications
You must be signed in to change notification settings - Fork 969
[KYUUBI #3222] JDBC Authentication Provider for server #3235
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
bowenliang123
wants to merge
31
commits into
apache:master
from
bowenliang123:feature-jdbc-auth-provider
Closed
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
0dc75fe
implement JDBC Authentication Method
bowenliang123 0e7f0ad
refactor config and init process.remove unused import.
bowenliang123 996f796
add unit test in JdbcAuthenticationProviderImplSuite
bowenliang123 49c18c2
update
bowenliang123 df4be56
update code style
bowenliang123 7025330
fix derby startup error in test
bowenliang123 46cc1dd
add config docs in docs/deployment/settings.md
bowenliang123 15176b2
fix import orders
bowenliang123 cd2c7c2
update settings.md config doc
bowenliang123 1dc4187
update settings.md config doc
bowenliang123 575301c
update options usage
bowenliang123 30974d1
update format
bowenliang123 3672919
fix ddl statement and remove truncate statement in test
bowenliang123 cdec206
more test cases
bowenliang123 653bc12
add more checks for query sql
bowenliang123 aeb19ce
update doc
bowenliang123 b9ffac3
Merge branch 'master' into feature-jdbc-auth-provider
bowenliang123 9885f81
add JDBC condition for getValidPasswordAuthMethod
bowenliang123 4ebe12e
add JDBC value to AuthTypes enum
bowenliang123 1c956df
update KyuubiAuthenticationFactorySuite
bowenliang123 5a0ac49
output password length only in checkConfigs
bowenliang123 3a4d5fe
update checkConfigs() signature
bowenliang123 a4fe582
refactor connection creation on using HikariDataSource in HikariCP. a…
bowenliang123 543c66c
prefer scala style string usage
bowenliang123 6765aff
changed to use in-memory derby db for test
bowenliang123 77f5f86
remove unuseful comment
bowenliang123 a9404fa
use {} for intercept
bowenliang123 6fc42bf
code styling
bowenliang123 e9af096
use clone instead of repeatly generating configs
bowenliang123 d5f43e0
remove unuseful logs for unrecognized placeholder error
bowenliang123 17403b3
cleanup docs
bowenliang123 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
204 changes: 204 additions & 0 deletions
204
.../main/scala/org/apache/kyuubi/service/authentication/JdbcAuthenticationProviderImpl.scala
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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}") | ||
| } | ||
bowenliang123 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // 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) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.