diff --git a/gradle.properties b/gradle.properties index 1e3db0530f..533f7a55c1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -68,7 +68,7 @@ httpcore.version=4.4.11 jackson.version=2.10.0 jcip-annotations.version=1.0-1 jcommander.version=1.72 -jetty.version=9.4.31.v20200723 +jetty.version=9.4.42.v20210604 junit.version=4.12 kerby.version=1.1.1 mockito.version=2.23.4 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 f883b87bd6..c094fc8fa1 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 @@ -209,8 +209,11 @@ protected void internalStart() { throw new RuntimeException("Server is already started"); } - final QueuedThreadPool threadPool = new QueuedThreadPool(); - threadPool.setDaemon(true); + final SubjectPreservingPrivilegedThreadFactory subjectPreservingPrivilegedThreadFactory = + new SubjectPreservingPrivilegedThreadFactory(); + //The constructor parameters are the Jetty defaults, except for the ThreadFactory + final QueuedThreadPool threadPool = new QueuedThreadPool(200, 8, 60000, -1, null, null, + subjectPreservingPrivilegedThreadFactory); server = new Server(threadPool); server.manage(threadPool); 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 new file mode 100644 index 0000000000..7a6d6b6841 --- /dev/null +++ b/server/src/main/java/org/apache/calcite/avatica/server/SubjectPreservingPrivilegedThreadFactory.java @@ -0,0 +1,60 @@ +/* + * 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.server; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.concurrent.ThreadFactory; +import javax.security.auth.Subject; + +/** + * Encapsulates creating the new Thread in a doPrivileged and a doAs call. + * The doPrivilieged block is taken from Jetty, and prevents some classloader leak isses. + * Saving the subject, and creating the Thread in the inner doAs call works around + * doPriviliged resetting the kerberos subject, which breaks SPNEGO authentication. + * + * Also sets the daemon flag and name for the Thread, as the QueuedThreadPool parameters are + * not applied with a custom ThreadFactory. + * + * see https://www.ibm.com/docs/en/was-zos/8.5.5\\ + * ?topic=service-java-authentication-authorization-authorization + */ +class SubjectPreservingPrivilegedThreadFactory implements ThreadFactory { + + /** + * @param Runnable object for the thread + * @return a new thread, protected from classloader pinning, but keeping the current Subject + */ + public Thread newThread(Runnable runnable) { + Subject subject = Subject.getSubject(AccessController.getContext()); + return AccessController.doPrivileged(new PrivilegedAction() { + @Override public Thread run() { + return Subject.doAs(subject, new PrivilegedAction() { + @Override public Thread run() { + Thread thread = new Thread(runnable); + thread.setDaemon(true); + thread.setName("avatica_qtp" + hashCode() + "-" + thread.getId()); + thread.setContextClassLoader(getClass().getClassLoader()); + return thread; + } + }); + } + }); + } +} + +// End SubjectPreservingPrivilegedThreadFactory.java diff --git a/server/src/test/java/org/apache/calcite/avatica/server/BasicAuthHttpServerTest.java b/server/src/test/java/org/apache/calcite/avatica/server/BasicAuthHttpServerTest.java index 9f9914ad85..fc6993dd00 100644 --- a/server/src/test/java/org/apache/calcite/avatica/server/BasicAuthHttpServerTest.java +++ b/server/src/test/java/org/apache/calcite/avatica/server/BasicAuthHttpServerTest.java @@ -25,7 +25,7 @@ import org.junit.BeforeClass; import org.junit.Test; -import java.net.URLDecoder; +import java.io.File; import java.util.Properties; import static org.hamcrest.core.StringContains.containsString; @@ -44,8 +44,8 @@ public class BasicAuthHttpServerTest extends HttpAuthBase { private static String url; @BeforeClass public static void startServer() throws Exception { - final String userPropertiesFile = URLDecoder.decode(BasicAuthHttpServerTest.class - .getResource("/auth-users.properties").getFile(), "UTF-8"); + final String userPropertiesFile = (new File(BasicAuthHttpServerTest.class + .getResource("/auth-users.properties").toURI())).getPath(); assertNotNull("Could not find properties file for basic auth users", userPropertiesFile); // Create a LocalService around HSQLDB diff --git a/server/src/test/java/org/apache/calcite/avatica/server/DigestAuthHttpServerTest.java b/server/src/test/java/org/apache/calcite/avatica/server/DigestAuthHttpServerTest.java index a5d8e8667b..67fee72249 100644 --- a/server/src/test/java/org/apache/calcite/avatica/server/DigestAuthHttpServerTest.java +++ b/server/src/test/java/org/apache/calcite/avatica/server/DigestAuthHttpServerTest.java @@ -25,7 +25,7 @@ import org.junit.BeforeClass; import org.junit.Test; -import java.net.URLDecoder; +import java.io.File; import java.util.Properties; import static org.hamcrest.core.StringContains.containsString; @@ -44,8 +44,8 @@ public class DigestAuthHttpServerTest extends HttpAuthBase { private static String url; @BeforeClass public static void startServer() throws Exception { - final String userPropertiesFile = URLDecoder.decode(BasicAuthHttpServerTest.class - .getResource("/auth-users.properties").getFile(), "UTF-8"); + final String userPropertiesFile = (new File(BasicAuthHttpServerTest.class + .getResource("/auth-users.properties").toURI())).getPath(); assertNotNull("Could not find properties file for digest auth users", userPropertiesFile); // Create a LocalService around HSQLDB diff --git a/server/src/test/java/org/apache/calcite/avatica/server/HttpAuthBase.java b/server/src/test/java/org/apache/calcite/avatica/server/HttpAuthBase.java index 6ce0afefe2..84a3e5e571 100644 --- a/server/src/test/java/org/apache/calcite/avatica/server/HttpAuthBase.java +++ b/server/src/test/java/org/apache/calcite/avatica/server/HttpAuthBase.java @@ -18,8 +18,8 @@ import org.apache.calcite.avatica.ConnectionSpec; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; +import java.io.File; +import java.net.URISyntaxException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; @@ -80,12 +80,11 @@ void readWriteData(String url, String tableName, Properties props) throws Except static String getHashLoginServicePropertiesString() { try { - final String userPropertiesFile = - URLDecoder.decode(HttpQueryStringParameterRemoteUserExtractorTest.class - .getResource("/auth-users.properties").getFile(), "UTF-8"); + final String userPropertiesFile = (new File(BasicAuthHttpServerTest.class + .getResource("/auth-users.properties").toURI())).getPath(); assertNotNull("Could not find properties file for basic auth users", userPropertiesFile); return userPropertiesFile; - } catch (UnsupportedEncodingException e) { + } catch (URISyntaxException e) { throw new RuntimeException(e); } }