This is an automated email from the ASF dual-hosted git repository.
stoty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite-avatica.git
The following commit(s) were added to refs/heads/main by this push:
new b656437ad [CALCITE-6590] Use reflection to handle Java SecurityManager
deprecation in Avatica
b656437ad is described below
commit b656437ad924c349bd225caa089749ac913cb4ff
Author: Istvan Toth <[email protected]>
AuthorDate: Tue Sep 24 13:21:39 2024 +0200
[CALCITE-6590] Use reflection to handle Java SecurityManager deprecation in
Avatica
Co-authored-by: Chris Dennis <[email protected]>
---
.../avatica/remote/DoAsAvaticaHttpClient.java | 12 +-
.../calcite/avatica/remote/KerberosConnection.java | 2 +-
.../apache/calcite/avatica/util/SecurityUtils.java | 203 +++++++++++++++++++++
.../apache/calcite/avatica/server/HttpServer.java | 6 +-
.../SubjectPreservingPrivilegedThreadFactory.java | 13 +-
.../apache/calcite/avatica/AvaticaSpnegoTest.java | 7 +-
.../org/apache/calcite/avatica/SpnegoTestUtil.java | 7 +-
.../server/HttpServerSpnegoWithoutJaasTest.java | 7 +-
8 files changed, 234 insertions(+), 23 deletions(-)
diff --git
a/core/src/main/java/org/apache/calcite/avatica/remote/DoAsAvaticaHttpClient.java
b/core/src/main/java/org/apache/calcite/avatica/remote/DoAsAvaticaHttpClient.java
index 123f82170..c23ce1638 100644
---
a/core/src/main/java/org/apache/calcite/avatica/remote/DoAsAvaticaHttpClient.java
+++
b/core/src/main/java/org/apache/calcite/avatica/remote/DoAsAvaticaHttpClient.java
@@ -16,9 +16,10 @@
*/
package org.apache.calcite.avatica.remote;
-import java.security.PrivilegedAction;
+import org.apache.calcite.avatica.util.SecurityUtils;
+
import java.util.Objects;
-import javax.security.auth.Subject;
+import java.util.concurrent.Callable;
/**
* HTTP client implementation which invokes the wrapped HTTP client in a doAs
with the provided
@@ -33,9 +34,10 @@ public DoAsAvaticaHttpClient(AvaticaHttpClient wrapped,
KerberosConnection kerbe
this.kerberosUtil = Objects.requireNonNull(kerberosUtil);
}
- @Override public byte[] send(final byte[] request) {
- return Subject.doAs(kerberosUtil.getSubject(), new
PrivilegedAction<byte[]>() {
- @Override public byte[] run() {
+ @Override
+ public byte[] send(final byte[] request) {
+ return SecurityUtils.callAs(kerberosUtil.getSubject(), new
Callable<byte[]>() {
+ @Override public byte[] call() {
return wrapped.send(request);
}
});
diff --git
a/core/src/main/java/org/apache/calcite/avatica/remote/KerberosConnection.java
b/core/src/main/java/org/apache/calcite/avatica/remote/KerberosConnection.java
index 438d53336..5a752385f 100644
---
a/core/src/main/java/org/apache/calcite/avatica/remote/KerberosConnection.java
+++
b/core/src/main/java/org/apache/calcite/avatica/remote/KerberosConnection.java
@@ -172,7 +172,7 @@ Entry<RenewalTask, Thread> createRenewalThread(LoginContext
originalContext,
renewalPeriod);
Thread t = new Thread(task);
- // Don't prevent the JVM from existing
+ // Don't prevent the JVM from exiting
t.setDaemon(true);
// Log an error message if this thread somehow dies
t.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
diff --git
a/core/src/main/java/org/apache/calcite/avatica/util/SecurityUtils.java
b/core/src/main/java/org/apache/calcite/avatica/util/SecurityUtils.java
new file mode 100644
index 000000000..b6548d96a
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/avatica/util/SecurityUtils.java
@@ -0,0 +1,203 @@
+/*
+ * 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.calcite.avatica.util;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletionException;
+import javax.security.auth.Subject;
+
+/**
+ * This class is loosely based on SecurityUtils in Jetty 12.0
+ *
+ * <p>Collections of utility methods to deal with the scheduled removal
+ * of the security classes defined by <a
href="https://openjdk.org/jeps/411">JEP 411</a>.</p>
+ */
+public class SecurityUtils {
+ private static final MethodHandle CALL_AS = lookupCallAs();
+ private static final MethodHandle CURRENT = lookupCurrent();
+ private static final MethodHandle DO_PRIVILEGED = lookupDoPrivileged();
+
+ private SecurityUtils() {
+ }
+
+ private static MethodHandle lookupCallAs() {
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ try {
+ try {
+ // Subject.doAs() is deprecated for removal and replaced by
Subject.callAs().
+ // Lookup first the new API, since for Java versions where both exist,
the
+ // new API delegates to the old API (for example Java 18, 19 and 20).
+ // Otherwise (Java 17), lookup the old API.
+ return lookup.findStatic(Subject.class, "callAs",
+ MethodType.methodType(Object.class, Subject.class, Callable.class));
+ } catch (NoSuchMethodException x) {
+ try {
+ // Lookup the old API.
+ MethodType oldSignature =
+ MethodType.methodType(Object.class, Subject.class,
PrivilegedExceptionAction.class);
+ MethodHandle doAs = lookup.findStatic(Subject.class, "doAs",
oldSignature);
+ // Convert the Callable used in the new API to the PrivilegedAction
used in the old
+ // API.
+ MethodType convertSignature =
+ MethodType.methodType(PrivilegedExceptionAction.class,
Callable.class);
+ MethodHandle converter =
+ lookup.findStatic(SecurityUtils.class,
"callableToPrivilegedExceptionAction",
+ convertSignature);
+ return MethodHandles.filterArguments(doAs, 1, converter);
+ } catch (NoSuchMethodException e) {
+ throw new AssertionError(e);
+ }
+ }
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ private static MethodHandle lookupDoPrivileged() {
+ try {
+ // Use reflection to work with Java versions that have and don't have
AccessController.
+ Class<?> klass =
+
ClassLoader.getSystemClassLoader().loadClass("java.security.AccessController");
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ return lookup.findStatic(klass, "doPrivileged",
+ MethodType.methodType(Object.class, PrivilegedAction.class));
+ } catch (NoSuchMethodException | IllegalAccessException x) {
+ // Assume that single methods won't be removed from AcessController
+ throw new AssertionError(x);
+ } catch (ClassNotFoundException e) {
+ return null;
+ }
+ }
+
+ private static MethodHandle lookupCurrent() {
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ try {
+ // Subject.getSubject(AccessControlContext) is deprecated for removal
and replaced by
+ // Subject.current().
+ // Lookup first the new API, since for Java versions where both exists,
the
+ // new API delegates to the old API (for example Java 18, 19 and 20).
+ // Otherwise (Java 17), lookup the old API.
+ return lookup.findStatic(Subject.class, "current",
+ MethodType.methodType(Subject.class));
+ } catch (NoSuchMethodException e) {
+ MethodHandle getContext = lookupGetContext();
+ MethodHandle getSubject = lookupGetSubject();
+ return MethodHandles.filterReturnValue(getContext, getSubject);
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ private static MethodHandle lookupGetSubject() {
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ try {
+ Class<?> contextklass =
+ ClassLoader.getSystemClassLoader()
+ .loadClass("java.security.AccessControlContext");
+ return lookup.findStatic(Subject.class, "getSubject",
+ MethodType.methodType(Subject.class, contextklass));
+ } catch (ClassNotFoundException | NoSuchMethodException |
IllegalAccessException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ private static MethodHandle lookupGetContext() {
+ try {
+ // Use reflection to work with Java versions that have and don't have
AccessController.
+ Class<?> controllerKlass =
+
ClassLoader.getSystemClassLoader().loadClass("java.security.AccessController");
+ Class<?> contextklass =
+ ClassLoader.getSystemClassLoader()
+ .loadClass("java.security.AccessControlContext");
+
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ return lookup.findStatic(controllerKlass, "getContext",
+ MethodType.methodType(contextklass));
+ } catch (ClassNotFoundException | NoSuchMethodException |
IllegalAccessException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ /**
+ * Maps to AccessController#doPrivileged if available, otherwise returns
action.run().
+ * @param action the action to run
+ * @return the result of running the action
+ * @param <T> the type of the result
+ */
+ public static <T> T doPrivileged(PrivilegedAction<T> action) {
+ // Keep this method short and inlineable.
+ if (DO_PRIVILEGED == null) {
+ return action.run();
+ }
+ return doPrivileged(DO_PRIVILEGED, action);
+ }
+
+ private static <T> T doPrivileged(MethodHandle doPrivileged,
PrivilegedAction<T> action) {
+ try {
+ return (T) doPrivileged.invoke(action);
+ } catch (Throwable t) {
+ throw sneakyThrow(t);
+ }
+ }
+
+ /**
+ * Maps to Subject.callAs() if available, otherwise maps to Subject.doAs()
+ * @param subject the subject this action runs as
+ * @param action the action to run
+ * @return the result of the action
+ * @param <T> the type of the result
+ * @throws CompletionException
+ */
+ public static <T> T callAs(Subject subject, Callable<T> action) throws
CompletionException {
+ try {
+ return (T) CALL_AS.invoke(subject, action);
+ } catch (PrivilegedActionException e) {
+ throw new CompletionException(e.getCause());
+ } catch (Throwable t) {
+ throw sneakyThrow(t);
+ }
+ }
+
+ /**
+ * Maps to Subject.currect() is available, otherwise maps to
Subject.getSubject()
+ * @return the current subject
+ */
+ public static Subject currentSubject() {
+ try {
+ return (Subject) CURRENT.invoke();
+ } catch (Throwable t) {
+ throw sneakyThrow(t);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static <T> PrivilegedExceptionAction<T>
callableToPrivilegedExceptionAction(
+ Callable<T> callable) {
+ return callable::call;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <E extends Throwable> RuntimeException sneakyThrow(Throwable
e) throws E {
+ throw (E) e;
+ }
+}
diff --git
a/server/src/main/java/org/apache/calcite/avatica/server/HttpServer.java
b/server/src/main/java/org/apache/calcite/avatica/server/HttpServer.java
index ce9c2c989..9c6b44cb2 100644
--- a/server/src/main/java/org/apache/calcite/avatica/server/HttpServer.java
+++ b/server/src/main/java/org/apache/calcite/avatica/server/HttpServer.java
@@ -21,6 +21,7 @@
import org.apache.calcite.avatica.remote.Driver.Serialization;
import org.apache.calcite.avatica.remote.Service;
import org.apache.calcite.avatica.remote.Service.RpcMetadataResponse;
+import org.apache.calcite.avatica.util.SecurityUtils;
import org.eclipse.jetty.security.Authenticator;
import org.eclipse.jetty.security.ConfigurableSpnegoLoginService;
@@ -53,7 +54,6 @@
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.Principal;
-import java.security.PrivilegedAction;
import java.security.SecureRandom;
import java.time.Duration;
import java.util.ArrayList;
@@ -205,8 +205,8 @@ static AvaticaHandler wrapJettyHandler(Handler handler) {
public void start() {
if (null != subject) {
// Run the start in the privileged block (as the kerberos-identified
user)
- Subject.doAs(subject, new PrivilegedAction<Void>() {
- @Override public Void run() {
+ SecurityUtils.callAs(subject, new Callable<Void>() {
+ @Override public Void call() {
internalStart();
return null;
}
diff --git
a/server/src/main/java/org/apache/calcite/avatica/server/SubjectPreservingPrivilegedThreadFactory.java
b/server/src/main/java/org/apache/calcite/avatica/server/SubjectPreservingPrivilegedThreadFactory.java
index 7a6d6b684..b0df42a2b 100644
---
a/server/src/main/java/org/apache/calcite/avatica/server/SubjectPreservingPrivilegedThreadFactory.java
+++
b/server/src/main/java/org/apache/calcite/avatica/server/SubjectPreservingPrivilegedThreadFactory.java
@@ -16,8 +16,10 @@
*/
package org.apache.calcite.avatica.server;
-import java.security.AccessController;
+import org.apache.calcite.avatica.util.SecurityUtils;
+
import java.security.PrivilegedAction;
+import java.util.concurrent.Callable;
import java.util.concurrent.ThreadFactory;
import javax.security.auth.Subject;
@@ -39,12 +41,13 @@ class SubjectPreservingPrivilegedThreadFactory implements
ThreadFactory {
* @param Runnable object for the thread
* @return a new thread, protected from classloader pinning, but keeping the
current Subject
*/
+ @Override
public Thread newThread(Runnable runnable) {
- Subject subject = Subject.getSubject(AccessController.getContext());
- return AccessController.doPrivileged(new PrivilegedAction<Thread>() {
+ Subject subject = SecurityUtils.currentSubject();
+ return SecurityUtils.doPrivileged(new PrivilegedAction<Thread>() {
@Override public Thread run() {
- return Subject.doAs(subject, new PrivilegedAction<Thread>() {
- @Override public Thread run() {
+ return SecurityUtils.callAs(subject, new Callable<Thread>() {
+ @Override public Thread call() {
Thread thread = new Thread(runnable);
thread.setDaemon(true);
thread.setName("avatica_qtp" + hashCode() + "-" + thread.getId());
diff --git
a/server/src/test/java/org/apache/calcite/avatica/AvaticaSpnegoTest.java
b/server/src/test/java/org/apache/calcite/avatica/AvaticaSpnegoTest.java
index aeb63a16c..1c831ee5b 100644
--- a/server/src/test/java/org/apache/calcite/avatica/AvaticaSpnegoTest.java
+++ b/server/src/test/java/org/apache/calcite/avatica/AvaticaSpnegoTest.java
@@ -19,6 +19,7 @@
import org.apache.calcite.avatica.remote.Driver;
import org.apache.calcite.avatica.server.AvaticaJaasKrbUtil;
import org.apache.calcite.avatica.server.HttpServer;
+import org.apache.calcite.avatica.util.SecurityUtils;
import org.apache.kerby.kerberos.kerb.KrbException;
import org.apache.kerby.kerberos.kerb.client.KrbConfig;
@@ -34,13 +35,13 @@
import org.slf4j.LoggerFactory;
import java.io.File;
-import java.security.PrivilegedExceptionAction;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Callable;
import javax.security.auth.Subject;
import static org.junit.Assert.assertEquals;
@@ -217,8 +218,8 @@ public AvaticaSpnegoTest(String jdbcUrl) {
// The name of the principal
// Run this code, logged in as the subject (the client)
- Subject.doAs(clientSubject, new PrivilegedExceptionAction<Void>() {
- @Override public Void run() throws Exception {
+ SecurityUtils.callAs(clientSubject, new Callable<Void>() {
+ @Override public Void call() throws Exception {
try (Connection conn = DriverManager.getConnection(jdbcUrl)) {
try (Statement stmt = conn.createStatement()) {
assertFalse(stmt.execute("DROP TABLE IF EXISTS " + tableName));
diff --git
a/server/src/test/java/org/apache/calcite/avatica/SpnegoTestUtil.java
b/server/src/test/java/org/apache/calcite/avatica/SpnegoTestUtil.java
index 03857d8e4..11e435530 100644
--- a/server/src/test/java/org/apache/calcite/avatica/SpnegoTestUtil.java
+++ b/server/src/test/java/org/apache/calcite/avatica/SpnegoTestUtil.java
@@ -19,6 +19,7 @@
import org.apache.calcite.avatica.remote.KerberosConnection;
import org.apache.calcite.avatica.remote.Service.RpcMetadataResponse;
import org.apache.calcite.avatica.server.AvaticaHandler;
+import org.apache.calcite.avatica.util.SecurityUtils;
import org.apache.kerby.kerberos.kerb.KrbException;
import org.apache.kerby.kerberos.kerb.server.SimpleKdcServer;
@@ -38,7 +39,6 @@
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.nio.charset.StandardCharsets;
-import java.security.AccessController;
import java.security.Principal;
import java.security.PrivilegedAction;
import javax.security.auth.login.Configuration;
@@ -138,8 +138,9 @@ public static void refreshJaasConfiguration() {
// Configuration keeps a static instance of Configuration that it will
return once it
// has been initialized. We need to nuke that static instance to make sure
our
// serverSpnegoConfigFile gets read.
- AccessController.doPrivileged(new PrivilegedAction<Configuration>() {
- public Configuration run() {
+ SecurityUtils.doPrivileged(new PrivilegedAction<Configuration>() {
+ @Override
+ public Configuration run() {
return Configuration.getConfiguration();
}
}).refresh();
diff --git
a/server/src/test/java/org/apache/calcite/avatica/server/HttpServerSpnegoWithoutJaasTest.java
b/server/src/test/java/org/apache/calcite/avatica/server/HttpServerSpnegoWithoutJaasTest.java
index ad09b6f7d..8481971e2 100644
---
a/server/src/test/java/org/apache/calcite/avatica/server/HttpServerSpnegoWithoutJaasTest.java
+++
b/server/src/test/java/org/apache/calcite/avatica/server/HttpServerSpnegoWithoutJaasTest.java
@@ -22,6 +22,7 @@
import org.apache.calcite.avatica.SpnegoTestUtil;
import org.apache.calcite.avatica.remote.AvaticaCommonsHttpClientImpl;
import org.apache.calcite.avatica.remote.CommonsHttpClientPoolCache;
+import org.apache.calcite.avatica.util.SecurityUtils;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.kerby.kerberos.kerb.KrbException;
@@ -47,9 +48,9 @@
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
-import java.security.PrivilegedExceptionAction;
import java.util.Properties;
import java.util.Set;
+import java.util.concurrent.Callable;
import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosTicket;
@@ -216,8 +217,8 @@ private static void setupUsers(File keytabDir) throws
KrbException {
final String principalName = clientPrincipals.iterator().next().getName();
// Run this code, logged in as the subject (the client)
- byte[] response = Subject.doAs(clientSubject, new
PrivilegedExceptionAction<byte[]>() {
- @Override public byte[] run() throws Exception {
+ byte[] response = SecurityUtils.callAs(clientSubject, new
Callable<byte[]>() {
+ @Override public byte[] call() throws Exception {
// Logs in with Kerberos via GSS
GSSManager gssManager = GSSManager.getInstance();
Oid oid = new Oid(SpnegoTestUtil.JGSS_KERBEROS_TICKET_OID);