[KYUUBI #7769] [COMMON][JDBC] Validate class types before reflective instantiation ### Why are the changes needed? Several reflective loading paths can initialize or construct a class before checking its expected type. Use a shared type check before initialization or construction for exception reconstruction and provider, plugin, strategy, and JDBC driver loading. Keep the existing constructor selection and exception reconstruction for supported classes. ### How was this patch tested? - 23 targeted tests passed across common, JDBC client, server, and Data Agent modules, including checks that rejected classes are not initialized or constructed. - Manual checks confirmed missing-dependency error wrapping for session advisors, group providers, and Data Agent providers. - Spotless and `git diff --check` passed. ### Was this patch assisted by generative AI tooling? Assisted-by: Codex (GPT-6) Closes #7769 from wangzhigang1999/zhigang/jdbc-exception-security. Closes #7769 52ce110b9 [wangzhigang] [COMMON][JDBC] Validate class types before reflective instantiation Authored-by: zhigang <iamzhigangwang@gmail.com> Signed-off-by: Akira Ajisaka <aajisaka@apache.org>
diff --git a/externals/kyuubi-data-agent-engine/src/main/java/org/apache/kyuubi/engine/dataagent/provider/DataAgentProvider.java b/externals/kyuubi-data-agent-engine/src/main/java/org/apache/kyuubi/engine/dataagent/provider/DataAgentProvider.java index 97a4340..8e35207 100644 --- a/externals/kyuubi-data-agent-engine/src/main/java/org/apache/kyuubi/engine/dataagent/provider/DataAgentProvider.java +++ b/externals/kyuubi-data-agent-engine/src/main/java/org/apache/kyuubi/engine/dataagent/provider/DataAgentProvider.java
@@ -20,6 +20,7 @@ import java.util.function.Consumer; import org.apache.kyuubi.config.KyuubiConf; import org.apache.kyuubi.engine.dataagent.runtime.event.AgentEvent; +import org.apache.kyuubi.util.reflect.DynClasses; import org.apache.kyuubi.util.reflect.DynConstructors; /** @@ -69,10 +70,12 @@ static DataAgentProvider load(KyuubiConf conf) { String providerClass = conf.get(KyuubiConf.ENGINE_DATA_AGENT_PROVIDER()); try { + Class<? extends DataAgentProvider> implementation = + DynClasses.loadSubclass(providerClass, DataAgentProvider.class); return (DataAgentProvider) DynConstructors.builder(DataAgentProvider.class) - .impl(providerClass, KyuubiConf.class) - .impl(providerClass) + .impl(implementation, KyuubiConf.class) + .impl(implementation) .buildChecked() .newInstanceChecked(conf); } catch (ClassCastException e) { @@ -83,7 +86,7 @@ + DataAgentProvider.class.getName() + "'.", e); - } catch (Exception e) { + } catch (Exception | NoClassDefFoundError e) { throw new IllegalArgumentException("Error while instantiating '" + providerClass + "': ", e); } }
diff --git a/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/connection/ConnectionProvider.scala b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/connection/ConnectionProvider.scala index f8ec72d..ae6eb9c 100644 --- a/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/connection/ConnectionProvider.scala +++ b/externals/kyuubi-jdbc-engine/src/main/scala/org/apache/kyuubi/engine/jdbc/connection/ConnectionProvider.scala
@@ -28,9 +28,18 @@ protected val providers = loadProviders() def getDriverClass(kyuubiConf: KyuubiConf): String = { - val driverClass: Class[_ <: Driver] = Option( - DynClasses.builder().impl(kyuubiConf.get(ENGINE_JDBC_DRIVER_CLASS).get) - .orNull().build[Driver]()).getOrElse { + val driverName = kyuubiConf.get(ENGINE_JDBC_DRIVER_CLASS).get + val classLoader = Thread.currentThread().getContextClassLoader + val configuredDriver: Option[Class[_ <: Driver]] = + try { + val driverClass = DynClasses.loadSubclass(driverName, classOf[Driver], classLoader) + // JDBC drivers register themselves during initialization, after the type check. + Class.forName(driverName, true, classLoader) + Some(driverClass) + } catch { + case _: ClassNotFoundException => None + } + val driverClass: Class[_ <: Driver] = configuredDriver.getOrElse { val url = kyuubiConf.get(ENGINE_JDBC_CONNECTION_URL).get DriverManager.getDriver(url).getClass }
diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/KyuubiSQLException.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/KyuubiSQLException.scala index 6153ac3..d56bb00 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/KyuubiSQLException.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/KyuubiSQLException.scala
@@ -25,7 +25,7 @@ import org.apache.kyuubi.Utils.stringifyException import org.apache.kyuubi.shaded.hive.service.rpc.thrift.{TStatus, TStatusCode} -import org.apache.kyuubi.util.reflect.DynConstructors +import org.apache.kyuubi.util.reflect.{DynClasses, DynConstructors} /** * @param reason a description of the exception @@ -139,12 +139,14 @@ } private def newInstance(className: String, message: String, cause: Throwable): Throwable = { try { + val exceptionClass = DynClasses.loadSubclass(className, classOf[Throwable]) DynConstructors.builder() - .impl(className, classOf[String], classOf[Throwable]) + .impl(exceptionClass, classOf[String], classOf[Throwable]) .buildChecked[Throwable]() .newInstance(message, cause) } catch { - case _: Exception => new RuntimeException(className + ":" + message, cause) + case _: Exception | _: NoClassDefFoundError => + new RuntimeException(className + ":" + message, cause) } }
diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/EngineSecuritySecretProvider.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/EngineSecuritySecretProvider.scala index 5a4cde0..546e93e 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/EngineSecuritySecretProvider.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/EngineSecuritySecretProvider.scala
@@ -19,7 +19,7 @@ import org.apache.kyuubi.config.KyuubiConf import org.apache.kyuubi.config.KyuubiConf._ -import org.apache.kyuubi.util.reflect.DynConstructors +import org.apache.kyuubi.util.reflect.{DynClasses, DynConstructors} trait EngineSecuritySecretProvider { @@ -51,8 +51,11 @@ object EngineSecuritySecretProvider { def create(conf: KyuubiConf): EngineSecuritySecretProvider = { + val providerClass = DynClasses.loadSubclass( + conf.get(INTERNAL_SECURITY_SECRET_PROVIDER), + classOf[EngineSecuritySecretProvider]) val provider = DynConstructors.builder() - .impl(conf.get(INTERNAL_SECURITY_SECRET_PROVIDER)) + .impl(providerClass) .buildChecked[EngineSecuritySecretProvider]() .newInstance(conf) provider.initialize(conf)
diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/util/ClassUtils.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/util/ClassUtils.scala index d8eda34..fc85b24 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/util/ClassUtils.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/util/ClassUtils.scala
@@ -32,15 +32,15 @@ * @return */ def createInstance[T](className: String, expected: Class[T], conf: KyuubiConf): T = { - val classLoader = Thread.currentThread.getContextClassLoader try { - DynConstructors.builder(expected).loader(classLoader) - .impl(className, classOf[KyuubiConf]) - .impl(className) + val implementation = DynClasses.loadSubclass(className, expected) + DynConstructors.builder(expected) + .impl(implementation, classOf[KyuubiConf]) + .impl(implementation) .buildChecked[T]() .newInstance(conf) } catch { - case e: Exception => + case e @ (_: Exception | _: NoClassDefFoundError) => throw new KyuubiException(s"$className must extend of ${expected.getName}", e) } }
diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/KyuubiSQLExceptionSuite.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/KyuubiSQLExceptionSuite.scala index 0b1d65c..995943d 100644 --- a/kyuubi-common/src/test/scala/org/apache/kyuubi/KyuubiSQLExceptionSuite.scala +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/KyuubiSQLExceptionSuite.scala
@@ -77,4 +77,29 @@ val cornerCase = new InvocationTargetException(null) assert(KyuubiSQLException(cornerCase).getCause === cornerCase) } + + test("non-Throwable classes are not initialized or constructed") { + // Refer to the module by name to avoid initializing it in the test itself. + val classNames = Seq( + "org.apache.kyuubi.RemoteInitializationProbe$", + classOf[RemoteConstructorProbe].getName) + classNames.foreach { className => + val cause = KyuubiSQLException.toCause(List(s"*$className:remote failure:0:-1")) + assert(!KyuubiSQLExceptionSuite.remoteCodeExecuted) + assert(cause.getClass === classOf[RuntimeException]) + assert(cause.getMessage === s"$className:remote failure") + } + } +} + +object KyuubiSQLExceptionSuite { + var remoteCodeExecuted = false +} + +private[kyuubi] object RemoteInitializationProbe { + KyuubiSQLExceptionSuite.remoteCodeExecuted = true +} + +private[kyuubi] class RemoteConstructorProbe(message: String, cause: Throwable) { + KyuubiSQLExceptionSuite.remoteCodeExecuted = true }
diff --git a/kyuubi-common/src/test/scala/org/apache/kyuubi/util/ClassUtilsSuite.scala b/kyuubi-common/src/test/scala/org/apache/kyuubi/util/ClassUtilsSuite.scala index cda638b..72f7eee 100644 --- a/kyuubi-common/src/test/scala/org/apache/kyuubi/util/ClassUtilsSuite.scala +++ b/kyuubi-common/src/test/scala/org/apache/kyuubi/util/ClassUtilsSuite.scala
@@ -17,13 +17,20 @@ package org.apache.kyuubi.util -import org.apache.kyuubi.KyuubiFunSuite +import org.apache.kyuubi.{KyuubiException, KyuubiFunSuite} import org.apache.kyuubi.config.KyuubiConf class ClassUtilsSuite extends KyuubiFunSuite { private val _conf = KyuubiConf() + test("reject unrelated classes") { + val error = intercept[KyuubiException] { + ClassUtils.createInstance(classOf[KyuubiConf].getName, classOf[SomeProvider], _conf) + } + assert(error.getCause.isInstanceOf[ClassCastException]) + } + test("create instance with zero-arg arg") { val instance = ClassUtils.createInstance[SomeProvider]( "org.apache.kyuubi.util.ProviderA",
diff --git a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiSQLException.java b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiSQLException.java index b5e6579..6accf7a 100644 --- a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiSQLException.java +++ b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiSQLException.java
@@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.List; import org.apache.kyuubi.shaded.hive.service.rpc.thrift.TStatus; +import org.apache.kyuubi.util.reflect.DynClasses; import org.apache.kyuubi.util.reflect.DynConstructors; public class KyuubiSQLException extends SQLException { @@ -187,11 +188,13 @@ private static Throwable newInstance(String className, String message) { try { + Class<? extends Throwable> exceptionClass = + DynClasses.loadSubclass(className, Throwable.class); return DynConstructors.builder() - .impl(className, String.class) + .impl(exceptionClass, String.class) .<Throwable>buildChecked() .newInstance(message); - } catch (Exception e) { + } catch (Exception | NoClassDefFoundError e) { return new RuntimeException(className + ":" + message); } }
diff --git a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/strategy/ServerSelectStrategyFactory.java b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/strategy/ServerSelectStrategyFactory.java index 9950097..5792732 100644 --- a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/strategy/ServerSelectStrategyFactory.java +++ b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/strategy/ServerSelectStrategyFactory.java
@@ -20,6 +20,7 @@ import java.lang.reflect.Constructor; import org.apache.kyuubi.jdbc.hive.strategy.zk.PollingSelectStrategy; import org.apache.kyuubi.jdbc.hive.strategy.zk.RandomSelectStrategy; +import org.apache.kyuubi.util.reflect.DynClasses; public class ServerSelectStrategyFactory { public static ServerSelectStrategy createStrategy(String strategyName) { @@ -30,15 +31,19 @@ case RandomSelectStrategy.strategyName: return new RandomSelectStrategy(); default: - Class<?> clazz = Class.forName(strategyName); - if (ServerSelectStrategy.class.isAssignableFrom(clazz)) { - Constructor<? extends ServerSelectStrategy> constructor = - clazz.asSubclass(ServerSelectStrategy.class).getConstructor(); - return constructor.newInstance(); - } else { + Class<? extends ServerSelectStrategy> clazz; + try { + clazz = + DynClasses.loadSubclass( + strategyName, + ServerSelectStrategy.class, + ServerSelectStrategyFactory.class.getClassLoader()); + } catch (ClassCastException e) { throw new ClassNotFoundException( "The loaded class does not implement ServerSelectStrategy"); } + Constructor<? extends ServerSelectStrategy> constructor = clazz.getConstructor(); + return constructor.newInstance(); } } catch (Exception e) { throw new RuntimeException("Failed to init server select strategy", e);
diff --git a/kyuubi-hive-jdbc/src/test/java/org/apache/kyuubi/jdbc/hive/KyuubiSQLExceptionTest.java b/kyuubi-hive-jdbc/src/test/java/org/apache/kyuubi/jdbc/hive/KyuubiSQLExceptionTest.java new file mode 100644 index 0000000..35d873a --- /dev/null +++ b/kyuubi-hive-jdbc/src/test/java/org/apache/kyuubi/jdbc/hive/KyuubiSQLExceptionTest.java
@@ -0,0 +1,54 @@ +/* + * 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.jdbc.hive; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.util.Collections; +import org.junit.jupiter.api.Test; + +public class KyuubiSQLExceptionTest { + + private static boolean remoteClassInitialized; + + public static class StaticInitializerProbe { + static { + remoteClassInitialized = true; + } + + public StaticInitializerProbe(String message) {} + } + + @Test + public void decodeOnlyThrowableClasses() { + String className = StaticInitializerProbe.class.getName(); + Throwable cause = + KyuubiSQLException.toCause( + Collections.singletonList("*" + className + ":remote failure:0:-1")); + + assertFalse(remoteClassInitialized); + assertEquals(RuntimeException.class, cause.getClass()); + assertEquals(className + ":remote failure", cause.getMessage()); + + IllegalArgumentException original = new IllegalArgumentException("original message"); + Throwable restored = KyuubiSQLException.toCause(KyuubiSQLException.toString(original)); + assertEquals(original.getClass(), restored.getClass()); + assertEquals(original.getMessage(), restored.getMessage()); + } +}
diff --git a/kyuubi-hive-jdbc/src/test/java/org/apache/kyuubi/jdbc/hive/ZooKeeperHiveClientHelperTest.java b/kyuubi-hive-jdbc/src/test/java/org/apache/kyuubi/jdbc/hive/ZooKeeperHiveClientHelperTest.java index d70f483..0885929 100644 --- a/kyuubi-hive-jdbc/src/test/java/org/apache/kyuubi/jdbc/hive/ZooKeeperHiveClientHelperTest.java +++ b/kyuubi-hive-jdbc/src/test/java/org/apache/kyuubi/jdbc/hive/ZooKeeperHiveClientHelperTest.java
@@ -20,13 +20,38 @@ import static org.apache.kyuubi.jdbc.hive.Utils.extractURLComponents; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.Properties; +import org.apache.kyuubi.jdbc.hive.strategy.ServerSelectStrategyFactory; +import org.apache.kyuubi.jdbc.hive.strategy.zk.PollingSelectStrategy; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; public class ZooKeeperHiveClientHelperTest { + private static boolean rejectedClassInitialized; + + public static class RejectedStrategy { + static { + rejectedClassInitialized = true; + } + } + + @Test + public void validateStrategyBeforeInitialization() { + assertThrows( + RuntimeException.class, + () -> ServerSelectStrategyFactory.createStrategy(RejectedStrategy.class.getName())); + assertFalse(rejectedClassInitialized); + assertEquals( + PollingSelectStrategy.class, + ServerSelectStrategyFactory.createStrategy(PollingSelectStrategy.class.getName()) + .getClass()); + } + @ParameterizedTest @ValueSource( strings = {
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/plugin/PluginLoader.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/plugin/PluginLoader.scala index 1bc80dc..15d76d3 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/plugin/PluginLoader.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/plugin/PluginLoader.scala
@@ -21,7 +21,7 @@ import org.apache.kyuubi.KyuubiException import org.apache.kyuubi.config.KyuubiConf -import org.apache.kyuubi.util.reflect.DynConstructors +import org.apache.kyuubi.util.reflect.{DynClasses, DynConstructors} private[kyuubi] object PluginLoader { @@ -32,13 +32,14 @@ } advisorClass.get.map { advisorClassName => try { - DynConstructors.builder.impl(advisorClassName) + val implementation = DynClasses.loadSubclass(advisorClassName, classOf[SessionConfAdvisor]) + DynConstructors.builder.impl(implementation) .buildChecked[SessionConfAdvisor].newInstance() } catch { case _: ClassCastException => throw new KyuubiException( s"Class $advisorClassName is not a child of '${classOf[SessionConfAdvisor].getName}'.") - case NonFatal(e) => + case e @ (NonFatal(_) | _: NoClassDefFoundError) => throw new IllegalArgumentException(s"Error while instantiating '$advisorClassName': ", e) } } @@ -47,12 +48,13 @@ def loadGroupProvider(conf: KyuubiConf): GroupProvider = { val groupProviderClass = conf.get(KyuubiConf.GROUP_PROVIDER) try { - DynConstructors.builder().impl(groupProviderClass).buildChecked[GroupProvider]().newInstance() + val providerClass = DynClasses.loadSubclass(groupProviderClass, classOf[GroupProvider]) + DynConstructors.builder().impl(providerClass).buildChecked[GroupProvider]().newInstance() } catch { case _: ClassCastException => throw new KyuubiException( s"Class $groupProviderClass is not a child of '${classOf[GroupProvider].getName}'.") - case NonFatal(e) => + case e @ (NonFatal(_) | _: NoClassDefFoundError) => throw new IllegalArgumentException(s"Error while instantiating '$groupProviderClass': ", e) } }
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/BearerAuthenticationHandler.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/BearerAuthenticationHandler.scala index 7d22df8..7aa9cbf 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/BearerAuthenticationHandler.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/BearerAuthenticationHandler.scala
@@ -27,6 +27,7 @@ import org.apache.kyuubi.server.http.util.HttpAuthUtils import org.apache.kyuubi.server.http.util.HttpAuthUtils.{AUTHORIZATION_HEADER, WWW_AUTHENTICATE_HEADER} import org.apache.kyuubi.service.authentication.{AnonymousAuthenticationProviderImpl, AuthenticationProviderFactory, DefaultTokenCredential, TokenAuthenticationProvider} +import org.apache.kyuubi.util.reflect.DynClasses class BearerAuthenticationHandler(providerClass: String) extends AuthenticationHandler with Logging { @@ -42,7 +43,10 @@ override def authenticationSupported: Boolean = { Option(providerClass).exists { _ => try { - Class.forName(providerClass).isAssignableFrom(classOf[TokenAuthenticationProvider]) + DynClasses.loadSubclass( + providerClass, + classOf[TokenAuthenticationProvider], + classOf[BearerAuthenticationHandler].getClassLoader) true } catch { case _: Throwable => false
diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/plugin/PluginLoaderSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/plugin/PluginLoaderSuite.scala index fa4505c..2e032e6 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/plugin/PluginLoaderSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/plugin/PluginLoaderSuite.scala
@@ -131,8 +131,12 @@ } } -class InvalidSessionConfAdvisor -class InvalidGroupProvider +class InvalidSessionConfAdvisor { + throw new AssertionError("Invalid advisor must not be constructed") +} +class InvalidGroupProvider { + throw new AssertionError("Invalid group provider must not be constructed") +} class TestSessionConfAdvisor extends SessionConfAdvisor { override def getConfOverlay(
diff --git a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/http/authentication/AuthenticationFilterSuite.scala b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/http/authentication/AuthenticationFilterSuite.scala index de4b056..33a076e 100644 --- a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/http/authentication/AuthenticationFilterSuite.scala +++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/http/authentication/AuthenticationFilterSuite.scala
@@ -19,7 +19,7 @@ import org.apache.kyuubi.KyuubiFunSuite import org.apache.kyuubi.config.KyuubiConf -import org.apache.kyuubi.service.authentication.AuthTypes +import org.apache.kyuubi.service.authentication.{AnonymousAuthenticationProviderImpl, AuthTypes} class AuthenticationFilterSuite extends KyuubiFunSuite { test("add auth handler and destroy") { @@ -32,6 +32,11 @@ assert(filter.authSchemeHandlers.size == 1) filter.addAuthHandler(new KerberosAuthenticationHandler()) assert(filter.authSchemeHandlers.size == 1) + filter.addAuthHandler(new BearerAuthenticationHandler(classOf[KyuubiConf].getName)) + assert(filter.authSchemeHandlers.size == 1) + filter.addAuthHandler(new BearerAuthenticationHandler( + classOf[AnonymousAuthenticationProviderImpl].getName)) + assert(filter.authSchemeHandlers.size == 2) filter.destroy() assert(filter.authSchemeHandlers.isEmpty) }
diff --git a/kyuubi-util/src/main/java/org/apache/kyuubi/util/reflect/DynClasses.java b/kyuubi-util/src/main/java/org/apache/kyuubi/util/reflect/DynClasses.java index 78bdd54..f01c524 100644 --- a/kyuubi-util/src/main/java/org/apache/kyuubi/util/reflect/DynClasses.java +++ b/kyuubi-util/src/main/java/org/apache/kyuubi/util/reflect/DynClasses.java
@@ -27,6 +27,21 @@ private DynClasses() {} + /** Loads a subclass without initializing it, using the thread context class loader. */ + public static <T> Class<? extends T> loadSubclass(String className, Class<T> expected) + throws ClassNotFoundException { + return loadSubclass(className, expected, Thread.currentThread().getContextClassLoader()); + } + + /** + * Loads and checks a class without running its static initializer. Initialization must wait until + * after the type check, because the class name may come from an untrusted source. + */ + public static <T> Class<? extends T> loadSubclass( + String className, Class<T> expected, ClassLoader loader) throws ClassNotFoundException { + return Class.forName(className, false, loader).asSubclass(expected); + } + public static Builder builder() { return new Builder(); }