diff --git a/rest-client-base/src/main/java/org/jboss/resteasy/microprofile/client/RestClientDelegateBean.java b/rest-client-base/src/main/java/org/jboss/resteasy/microprofile/client/RestClientDelegateBean.java index bd85c7e5..ead3abaa 100644 --- a/rest-client-base/src/main/java/org/jboss/resteasy/microprofile/client/RestClientDelegateBean.java +++ b/rest-client-base/src/main/java/org/jboss/resteasy/microprofile/client/RestClientDelegateBean.java @@ -179,7 +179,7 @@ private void configureSsl(RestClientBuilder builder) { private void registerHostnameVerifier(String verifier, RestClientBuilder builder) { try { - Class verifierClass = Class.forName(verifier, true, Thread.currentThread().getContextClassLoader()); + Class verifierClass = Class.forName(verifier, true, SecurityActions.getContextClassLoader()); builder.hostnameVerifier((HostnameVerifier) verifierClass.newInstance()); } catch (ClassNotFoundException e) { throw new RuntimeException("Could not find hostname verifier class" + verifier, e); @@ -275,7 +275,7 @@ private void registerProviders(RestClientBuilder builder, String providersAsStri private Class providerClassForName(String name) { try { - return Class.forName(name, true, Thread.currentThread().getContextClassLoader()); + return Class.forName(name, true, SecurityActions.getContextClassLoader()); } catch (ClassNotFoundException e) { throw new RuntimeException("Could not find provider class: " + name); } diff --git a/rest-client-base/src/main/java/org/jboss/resteasy/microprofile/client/RestClientListeners.java b/rest-client-base/src/main/java/org/jboss/resteasy/microprofile/client/RestClientListeners.java index 36e94545..7ebedefe 100644 --- a/rest-client-base/src/main/java/org/jboss/resteasy/microprofile/client/RestClientListeners.java +++ b/rest-client-base/src/main/java/org/jboss/resteasy/microprofile/client/RestClientListeners.java @@ -43,7 +43,7 @@ private RestClientListeners() { .synchronizedMap(new WeakHashMap<>()); public static Collection get() { - ClassLoader loader = getClassLoader(); + ClassLoader loader = SecurityActions.getClassLoader(RestClientListeners.class); if (loader == null) { return Collections.emptyList(); } @@ -59,15 +59,4 @@ public static Collection get() { return AccessController.doPrivileged(action); }); } - - private static ClassLoader getClassLoader() { - if (System.getSecurityManager() == null) { - ClassLoader cl = Thread.currentThread().getContextClassLoader(); - return cl == null ? RestClientListeners.class.getClassLoader() : cl; - } - return AccessController.doPrivileged((PrivilegedAction) () -> { - ClassLoader cl = Thread.currentThread().getContextClassLoader(); - return cl == null ? RestClientListeners.class.getClassLoader() : cl; - }); - } } diff --git a/rest-client-base/src/main/java/org/jboss/resteasy/microprofile/client/SecurityActions.java b/rest-client-base/src/main/java/org/jboss/resteasy/microprofile/client/SecurityActions.java new file mode 100644 index 00000000..352fe801 --- /dev/null +++ b/rest-client-base/src/main/java/org/jboss/resteasy/microprofile/client/SecurityActions.java @@ -0,0 +1,62 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2025 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.resteasy.microprofile.client; + +import java.security.AccessController; +import java.security.PrivilegedAction; + +/** + * This class must never be made public. + * + * @author James R. Perkins + */ +class SecurityActions { + + /** + * Returns the current threads context class loader. + * + * @return the current threads context class loader or {@code null} if there is not one + */ + static ClassLoader getContextClassLoader() { + if (System.getSecurityManager() == null) { + return Thread.currentThread().getContextClassLoader(); + } + return AccessController.doPrivileged((PrivilegedAction) () -> Thread.currentThread() + .getContextClassLoader()); + } + + /** + * Gets the current context class loader or the class loader of the passed in class. + * + * @param clazz the class to get the class loader from if the context class loader is {@code null} + * + * @return the available class loader + */ + static ClassLoader getClassLoader(final Class clazz) { + if (System.getSecurityManager() == null) { + final ClassLoader cl = Thread.currentThread().getContextClassLoader(); + return cl == null ? clazz.getClassLoader() : cl; + } + return AccessController.doPrivileged((PrivilegedAction) () -> { + final ClassLoader cl = Thread.currentThread().getContextClassLoader(); + return cl == null ? clazz.getClassLoader() : cl; + }); + } +} diff --git a/rest-client/src/main/java/org/jboss/resteasy/microprofile/client/header/HeaderUtils.java b/rest-client/src/main/java/org/jboss/resteasy/microprofile/client/header/HeaderUtils.java index d02c48ef..18160d12 100644 --- a/rest-client/src/main/java/org/jboss/resteasy/microprofile/client/header/HeaderUtils.java +++ b/rest-client/src/main/java/org/jboss/resteasy/microprofile/client/header/HeaderUtils.java @@ -23,6 +23,8 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.List; import java.util.stream.Collectors; @@ -79,7 +81,13 @@ public static Method resolveMethod(String methodSpecifier, methodName = methodSpecifier.substring(lastDot + 1); String className = methodSpecifier.substring(0, lastDot); - ClassLoader loader = Thread.currentThread().getContextClassLoader(); + final ClassLoader loader; + if (System.getSecurityManager() == null) { + loader = Thread.currentThread().getContextClassLoader(); + } else { + loader = AccessController + .doPrivileged((PrivilegedAction) () -> Thread.currentThread().getContextClassLoader()); + } try { clazz = Class.forName(className, true, loader); } catch (ClassNotFoundException e) { diff --git a/testsuite/integration-tests/pom.xml b/testsuite/integration-tests/pom.xml index 9ed16557..63879c72 100644 --- a/testsuite/integration-tests/pom.xml +++ b/testsuite/integration-tests/pom.xml @@ -237,18 +237,4 @@ - - - security.manager - - - security.manager - - - - -secmgr - - - - \ No newline at end of file diff --git a/testsuite/microprofile-rest-client-tck/pom.xml b/testsuite/microprofile-rest-client-tck/pom.xml index 5563ccb9..5bafbf4f 100644 --- a/testsuite/microprofile-rest-client-tck/pom.xml +++ b/testsuite/microprofile-rest-client-tck/pom.xml @@ -39,6 +39,17 @@ + + + org.jboss.arquillian.container + arquillian-container-test-spi + + + org.wildfly.arquillian + wildfly-testing-tools + + + org.jboss.resteasy.microprofile diff --git a/testsuite/microprofile-rest-client-tck/src/main/java/dev/resteasy/microprofile/rest/client/tck/RestClientTckApplicationArchiveProcessor.java b/testsuite/microprofile-rest-client-tck/src/main/java/dev/resteasy/microprofile/rest/client/tck/RestClientTckApplicationArchiveProcessor.java new file mode 100644 index 00000000..18544f54 --- /dev/null +++ b/testsuite/microprofile-rest-client-tck/src/main/java/dev/resteasy/microprofile/rest/client/tck/RestClientTckApplicationArchiveProcessor.java @@ -0,0 +1,111 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2025 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 dev.resteasy.microprofile.rest.client.tck; + +import java.io.FilePermission; +import java.lang.reflect.ReflectPermission; +import java.net.SocketPermission; +import java.util.ArrayList; +import java.util.List; +import java.util.PropertyPermission; + +import org.jboss.arquillian.container.test.spi.client.deployment.ApplicationArchiveProcessor; +import org.jboss.arquillian.test.spi.TestClass; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.container.ManifestContainer; +import org.wildfly.testing.tools.deployments.DeploymentDescriptors; + +/** + * @author James R. Perkins + */ +public class RestClientTckApplicationArchiveProcessor implements ApplicationArchiveProcessor { + private static final List ALL_FILE_TESTS = List.of( + // Creates a /tmp/ssl* keystore file + "org.eclipse.microprofile.rest.client.tck.ssl.SslContextTest", + "org.eclipse.microprofile.rest.client.tck.ssl.SslHostnameVerifierTest", + "org.eclipse.microprofile.rest.client.tck.ssl.SslMutualTest", + "org.eclipse.microprofile.rest.client.tck.ssl.SslTrustStoreTest"); + + private static final List GET_CLASS_LOADER_TESTS = List.of( + // The org.jboss.arquillian.testenricher.cdi.CDIInjectionEnricher requires getClassLoader() + "org.eclipse.microprofile.rest.client.tck.cditests.CDIProxyServerTest", + "org.eclipse.microprofile.rest.client.tck.ssl.SslHostnameVerifierTest", + "org.eclipse.microprofile.rest.client.tck.ssl.SslMutualTest", + "org.eclipse.microprofile.rest.client.tck.ssl.SslTrustStoreTest", + // Required for the Jetty thread pool + "org.eclipse.microprofile.rest.client.tck.ProxyServerTest"); + + private static final List JETTY_SERVER_TESTS = List.of( + "org.eclipse.microprofile.rest.client.tck.ProxyServerTest", + "org.eclipse.microprofile.rest.client.tck.cditests.CDIProxyServerTest"); + + @Override + public void process(final Archive applicationArchive, final TestClass testClass) { + if (applicationArchive instanceof ManifestContainer) { + final var container = (ManifestContainer) applicationArchive; + + final var permissions = new ArrayList<>(List.of( + // Required by the Arquillian ServiceLoader to allow access to the constructor + new ReflectPermission("suppressAccessChecks"), + new PropertyPermission("arquillian.*", "read"), + // Required by Jetty + new PropertyPermission("jetty.*", "read,write"), + // Required by the MP REST Client TCK + new PropertyPermission("org.eclipse.microprofile.rest.client.*", "read"), + new RuntimePermission("getenv.JETTY_AVAILABLE_PROCESSORS"), + // Required of OTel is available on the deployment class path + new RuntimePermission("getenv.OTEL_JAVAAGENT_DEBUG"), + new RuntimePermission("getenv.OTEL_INSTRUMENTATION_EXPERIMENTAL_SPAN_SUPPRESSION_STRATEGY"), + // Required by TestNG + new PropertyPermission("testng.*", "read"), + new PropertyPermission("user.dir", "read"), + new RuntimePermission("accessDeclaredMembers"), + // Required by Wiremock + new PropertyPermission("wiremock.*", "read"), + new SocketPermission("localhost", "resolve"), + new SocketPermission("localhost:*", "connect,listen,resolve"), + new SocketPermission("127.0.0.1:*", "connect,resolve"))); + + if (ALL_FILE_TESTS.contains(testClass.getName())) { + permissions.add(new FilePermission("<>", "read")); + } + if (GET_CLASS_LOADER_TESTS.contains(testClass.getName())) { + permissions.add(new RuntimePermission("getClassLoader")); + } + if (JETTY_SERVER_TESTS.contains(testClass.getName())) { + permissions.add(new RuntimePermission("modifyThread")); + } + + var currentPermissionsXml = applicationArchive.delete("META-INF/permissions.xml"); + // A WAR might be in a different location + if (currentPermissionsXml == null) { + currentPermissionsXml = applicationArchive.delete("WEB-INF/classes/META-INF/permissions.yaml"); + } + if (currentPermissionsXml != null) { + container.addAsManifestResource( + DeploymentDescriptors.appendPermissions(currentPermissionsXml.getAsset(), permissions), + "permissions.xml"); + } else { + container.addAsManifestResource(DeploymentDescriptors.createPermissionsXmlAsset(permissions), + "permissions.xml"); + } + } + } +} diff --git a/testsuite/microprofile-rest-client-tck/src/main/java/dev/resteasy/microprofile/rest/client/tck/RestClientTckExtension.java b/testsuite/microprofile-rest-client-tck/src/main/java/dev/resteasy/microprofile/rest/client/tck/RestClientTckExtension.java new file mode 100644 index 00000000..c7b7e6ef --- /dev/null +++ b/testsuite/microprofile-rest-client-tck/src/main/java/dev/resteasy/microprofile/rest/client/tck/RestClientTckExtension.java @@ -0,0 +1,33 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2025 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 dev.resteasy.microprofile.rest.client.tck; + +import org.jboss.arquillian.container.test.spi.client.deployment.ApplicationArchiveProcessor; +import org.jboss.arquillian.core.spi.LoadableExtension; + +/** + * @author James R. Perkins + */ +public class RestClientTckExtension implements LoadableExtension { + @Override + public void register(final ExtensionBuilder builder) { + builder.service(ApplicationArchiveProcessor.class, RestClientTckApplicationArchiveProcessor.class); + } +} diff --git a/testsuite/microprofile-rest-client-tck/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension b/testsuite/microprofile-rest-client-tck/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension new file mode 100644 index 00000000..8f91a628 --- /dev/null +++ b/testsuite/microprofile-rest-client-tck/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension @@ -0,0 +1 @@ +dev.resteasy.microprofile.rest.client.tck.RestClientTckExtension \ No newline at end of file diff --git a/testsuite/pom.xml b/testsuite/pom.xml index 88c89cd6..f96632a2 100644 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -233,6 +233,17 @@ wildfly-preview + + security.manager + + + security.manager + + + + -secmgr + + \ No newline at end of file