Remove support for deprecated Java SecurityManager (-> JDK 17 build compatibility)
Includes hard JDK 9+ API dependency in CGLIB ReflectUtils (Lookup.defineClass) and removal of OutputStream spy proxy usage (avoiding invalid Mockito proxy on JDK 17) Closes gh-26901
This commit is contained in:
@@ -26,9 +26,6 @@ import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -56,11 +53,6 @@ public class ReflectUtils {
|
||||
|
||||
private static final ClassLoader defaultLoader = ReflectUtils.class.getClassLoader();
|
||||
|
||||
// SPRING PATCH BEGIN
|
||||
private static final Method privateLookupInMethod;
|
||||
|
||||
private static final Method lookupDefineClassMethod;
|
||||
|
||||
private static final Method classLoaderDefineClassMethod;
|
||||
|
||||
private static final ProtectionDomain PROTECTION_DOMAIN;
|
||||
@@ -69,63 +61,28 @@ public class ReflectUtils {
|
||||
|
||||
private static final List<Method> OBJECT_METHODS = new ArrayList<Method>();
|
||||
|
||||
// SPRING PATCH BEGIN
|
||||
static {
|
||||
Method privateLookupIn;
|
||||
Method lookupDefineClass;
|
||||
Method classLoaderDefineClass;
|
||||
ProtectionDomain protectionDomain;
|
||||
Throwable throwable = null;
|
||||
try {
|
||||
privateLookupIn = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() {
|
||||
public Object run() throws Exception {
|
||||
try {
|
||||
return MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
lookupDefineClass = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() {
|
||||
public Object run() throws Exception {
|
||||
try {
|
||||
return MethodHandles.Lookup.class.getMethod("defineClass", byte[].class);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
classLoaderDefineClass = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() {
|
||||
public Object run() throws Exception {
|
||||
return ClassLoader.class.getDeclaredMethod("defineClass",
|
||||
classLoaderDefineClass = ClassLoader.class.getDeclaredMethod("defineClass",
|
||||
String.class, byte[].class, Integer.TYPE, Integer.TYPE, ProtectionDomain.class);
|
||||
}
|
||||
});
|
||||
protectionDomain = getProtectionDomain(ReflectUtils.class);
|
||||
AccessController.doPrivileged(new PrivilegedExceptionAction() {
|
||||
public Object run() throws Exception {
|
||||
Method[] methods = Object.class.getDeclaredMethods();
|
||||
for (Method method : methods) {
|
||||
if ("finalize".equals(method.getName())
|
||||
|| (method.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) > 0) {
|
||||
continue;
|
||||
}
|
||||
OBJECT_METHODS.add(method);
|
||||
}
|
||||
return null;
|
||||
for (Method method : Object.class.getDeclaredMethods()) {
|
||||
if ("finalize".equals(method.getName())
|
||||
|| (method.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) > 0) {
|
||||
continue;
|
||||
}
|
||||
});
|
||||
OBJECT_METHODS.add(method);
|
||||
}
|
||||
}
|
||||
catch (Throwable t) {
|
||||
privateLookupIn = null;
|
||||
lookupDefineClass = null;
|
||||
classLoaderDefineClass = null;
|
||||
protectionDomain = null;
|
||||
throwable = t;
|
||||
}
|
||||
privateLookupInMethod = privateLookupIn;
|
||||
lookupDefineClassMethod = lookupDefineClass;
|
||||
classLoaderDefineClassMethod = classLoaderDefineClass;
|
||||
PROTECTION_DOMAIN = protectionDomain;
|
||||
THROWABLE = throwable;
|
||||
@@ -160,11 +117,7 @@ public class ReflectUtils {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
return (ProtectionDomain) AccessController.doPrivileged(new PrivilegedAction() {
|
||||
public Object run() {
|
||||
return source.getProtectionDomain();
|
||||
}
|
||||
});
|
||||
return source.getProtectionDomain();
|
||||
}
|
||||
|
||||
public static Type[] getExceptionTypes(Member member) {
|
||||
@@ -336,15 +289,7 @@ public class ReflectUtils {
|
||||
public static Constructor getConstructor(Class type, Class[] parameterTypes) {
|
||||
try {
|
||||
Constructor constructor = type.getDeclaredConstructor(parameterTypes);
|
||||
if (System.getSecurityManager() != null) {
|
||||
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
|
||||
constructor.setAccessible(true);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
else {
|
||||
constructor.setAccessible(true);
|
||||
}
|
||||
constructor.setAccessible(true);
|
||||
return constructor;
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
@@ -501,18 +446,12 @@ public class ReflectUtils {
|
||||
Class c = null;
|
||||
|
||||
// Preferred option: JDK 9+ Lookup.defineClass API if ClassLoader matches
|
||||
if (contextClass != null && contextClass.getClassLoader() == loader &&
|
||||
privateLookupInMethod != null && lookupDefineClassMethod != null) {
|
||||
if (contextClass != null && contextClass.getClassLoader() == loader) {
|
||||
try {
|
||||
MethodHandles.Lookup lookup = (MethodHandles.Lookup)
|
||||
privateLookupInMethod.invoke(null, contextClass, MethodHandles.lookup());
|
||||
c = (Class) lookupDefineClassMethod.invoke(lookup, b);
|
||||
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(contextClass, MethodHandles.lookup());
|
||||
c = lookup.defineClass(b);
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
Throwable target = ex.getTargetException();
|
||||
if (target.getClass() != LinkageError.class && target.getClass() != IllegalArgumentException.class) {
|
||||
throw new CodeGenerationException(target);
|
||||
}
|
||||
catch (LinkageError | IllegalArgumentException ex) {
|
||||
// in case of plain LinkageError (class already defined)
|
||||
// or IllegalArgumentException (class in different package):
|
||||
// fall through to traditional ClassLoader.defineClass below
|
||||
@@ -567,15 +506,10 @@ public class ReflectUtils {
|
||||
}
|
||||
|
||||
// Fallback option: JDK 9+ Lookup.defineClass API even if ClassLoader does not match
|
||||
if (c == null && contextClass != null && contextClass.getClassLoader() != loader &&
|
||||
privateLookupInMethod != null && lookupDefineClassMethod != null) {
|
||||
if (c == null && contextClass != null && contextClass.getClassLoader() != loader) {
|
||||
try {
|
||||
MethodHandles.Lookup lookup = (MethodHandles.Lookup)
|
||||
privateLookupInMethod.invoke(null, contextClass, MethodHandles.lookup());
|
||||
c = (Class) lookupDefineClassMethod.invoke(lookup, b);
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
throw new CodeGenerationException(ex.getTargetException());
|
||||
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(contextClass, MethodHandles.lookup());
|
||||
c = lookup.defineClass(b);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new CodeGenerationException(ex);
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.core.env;
|
||||
|
||||
import java.security.AccessControlException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
@@ -60,8 +59,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
|
||||
* <p>The default is "false", falling back to system environment variable checks if a
|
||||
* Spring environment property (e.g. a placeholder in a configuration String) isn't
|
||||
* resolvable otherwise. Consider switching this flag to "true" if you experience
|
||||
* log warnings from {@code getenv} calls coming from Spring, e.g. on WebSphere
|
||||
* with strict SecurityManager settings and AccessControlExceptions warnings.
|
||||
* log warnings from {@code getenv} calls coming from Spring.
|
||||
* @see #suppressGetenvAccess()
|
||||
*/
|
||||
public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore";
|
||||
@@ -438,27 +436,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
|
||||
@Override
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public Map<String, Object> getSystemProperties() {
|
||||
try {
|
||||
return (Map) System.getProperties();
|
||||
}
|
||||
catch (AccessControlException ex) {
|
||||
return (Map) new ReadOnlySystemAttributesMap() {
|
||||
@Override
|
||||
@Nullable
|
||||
protected String getSystemAttribute(String attributeName) {
|
||||
try {
|
||||
return System.getProperty(attributeName);
|
||||
}
|
||||
catch (AccessControlException ex) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Caught AccessControlException when accessing system property '" +
|
||||
attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return (Map) System.getProperties();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -467,27 +445,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
|
||||
if (suppressGetenvAccess()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
try {
|
||||
return (Map) System.getenv();
|
||||
}
|
||||
catch (AccessControlException ex) {
|
||||
return (Map) new ReadOnlySystemAttributesMap() {
|
||||
@Override
|
||||
@Nullable
|
||||
protected String getSystemAttribute(String attributeName) {
|
||||
try {
|
||||
return System.getenv(attributeName);
|
||||
}
|
||||
catch (AccessControlException ex) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Caught AccessControlException when accessing system environment variable '" +
|
||||
attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return (Map) System.getenv();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -119,32 +119,20 @@ public interface ConfigurableEnvironment extends Environment, ConfigurableProper
|
||||
MutablePropertySources getPropertySources();
|
||||
|
||||
/**
|
||||
* Return the value of {@link System#getProperties()} if allowed by the current
|
||||
* {@link SecurityManager}, otherwise return a map implementation that will attempt
|
||||
* to access individual keys using calls to {@link System#getProperty(String)}.
|
||||
* Return the value of {@link System#getProperties()}.
|
||||
* <p>Note that most {@code Environment} implementations will include this system
|
||||
* properties map as a default {@link PropertySource} to be searched. Therefore, it is
|
||||
* recommended that this method not be used directly unless bypassing other property
|
||||
* sources is expressly intended.
|
||||
* <p>Calls to {@link Map#get(Object)} on the Map returned will never throw
|
||||
* {@link IllegalAccessException}; in cases where the SecurityManager forbids access
|
||||
* to a property, {@code null} will be returned and an INFO-level log message will be
|
||||
* issued noting the exception.
|
||||
*/
|
||||
Map<String, Object> getSystemProperties();
|
||||
|
||||
/**
|
||||
* Return the value of {@link System#getenv()} if allowed by the current
|
||||
* {@link SecurityManager}, otherwise return a map implementation that will attempt
|
||||
* to access individual keys using calls to {@link System#getenv(String)}.
|
||||
* Return the value of {@link System#getenv()}.
|
||||
* <p>Note that most {@link Environment} implementations will include this system
|
||||
* environment map as a default {@link PropertySource} to be searched. Therefore, it
|
||||
* is recommended that this method not be used directly unless bypassing other
|
||||
* property sources is expressly intended.
|
||||
* <p>Calls to {@link Map#get(Object)} on the Map returned will never throw
|
||||
* {@link IllegalAccessException}; in cases where the SecurityManager forbids access
|
||||
* to a property, {@code null} will be returned and an INFO-level log message will be
|
||||
* issued noting the exception.
|
||||
*/
|
||||
Map<String, Object> getSystemEnvironment();
|
||||
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.springframework.core.env;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Read-only {@code Map<String, String>} implementation that is backed by system
|
||||
* properties or environment variables.
|
||||
*
|
||||
* <p>Used by {@link AbstractEnvironment} when a {@link SecurityManager} prohibits
|
||||
* access to {@link System#getProperties()} or {@link System#getenv()}. It is for this
|
||||
* reason that the implementations of {@link #keySet()}, {@link #entrySet()}, and
|
||||
* {@link #values()} always return empty even though {@link #get(Object)} may in fact
|
||||
* return non-null if the current security manager allows access to individual keys.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Chris Beams
|
||||
* @since 3.0
|
||||
*/
|
||||
abstract class ReadOnlySystemAttributesMap implements Map<String, String> {
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return (get(key) != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value to which the specified key is mapped, or {@code null} if this map
|
||||
* contains no mapping for the key.
|
||||
* @param key the name of the system attribute to retrieve
|
||||
* @throws IllegalArgumentException if given key is non-String
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public String get(Object key) {
|
||||
if (!(key instanceof String)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Type of key [" + key.getClass().getName() + "] must be java.lang.String");
|
||||
}
|
||||
return getSystemAttribute((String) key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Template method that returns the underlying system attribute.
|
||||
* <p>Implementations typically call {@link System#getProperty(String)} or {@link System#getenv(String)} here.
|
||||
*/
|
||||
@Nullable
|
||||
protected abstract String getSystemAttribute(String attributeName);
|
||||
|
||||
|
||||
// Unsupported
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String put(String key, String value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String remove(Object key) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends String, ? extends String> map) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> values() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<String, String>> entrySet() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -122,34 +122,26 @@ public class SystemEnvironmentPropertySource extends MapPropertySource {
|
||||
@Nullable
|
||||
private String checkPropertyName(String name) {
|
||||
// Check name as-is
|
||||
if (containsKey(name)) {
|
||||
if (this.source.containsKey(name)) {
|
||||
return name;
|
||||
}
|
||||
// Check name with just dots replaced
|
||||
String noDotName = name.replace('.', '_');
|
||||
if (!name.equals(noDotName) && containsKey(noDotName)) {
|
||||
if (!name.equals(noDotName) && this.source.containsKey(noDotName)) {
|
||||
return noDotName;
|
||||
}
|
||||
// Check name with just hyphens replaced
|
||||
String noHyphenName = name.replace('-', '_');
|
||||
if (!name.equals(noHyphenName) && containsKey(noHyphenName)) {
|
||||
if (!name.equals(noHyphenName) && this.source.containsKey(noHyphenName)) {
|
||||
return noHyphenName;
|
||||
}
|
||||
// Check name with dots and hyphens replaced
|
||||
String noDotNoHyphenName = noDotName.replace('-', '_');
|
||||
if (!noDotName.equals(noDotNoHyphenName) && containsKey(noDotNoHyphenName)) {
|
||||
if (!noDotName.equals(noDotNoHyphenName) && this.source.containsKey(noDotNoHyphenName)) {
|
||||
return noDotNoHyphenName;
|
||||
}
|
||||
// Give up
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean containsKey(String name) {
|
||||
return (isSecurityManagerPresent() ? this.source.keySet().contains(name) : this.source.containsKey(name));
|
||||
}
|
||||
|
||||
protected boolean isSecurityManagerPresent() {
|
||||
return (System.getSecurityManager() != null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,7 +17,6 @@
|
||||
package org.springframework.core.type.classreading;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.security.AccessControlException;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -97,7 +96,7 @@ abstract class AbstractRecursiveAnnotationVisitor extends AnnotationVisitor {
|
||||
catch (ClassNotFoundException | NoClassDefFoundError ex) {
|
||||
logger.debug("Failed to classload enum type while reading annotation metadata", ex);
|
||||
}
|
||||
catch (IllegalAccessException | AccessControlException ex) {
|
||||
catch (IllegalAccessException ex) {
|
||||
logger.debug("Could not access enum value while reading annotation metadata", ex);
|
||||
}
|
||||
return valueToUse;
|
||||
|
||||
@@ -107,9 +107,7 @@ final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttrib
|
||||
String annotationName = annotationType.getName();
|
||||
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotationName) && visited.add(annotation)) {
|
||||
try {
|
||||
// Only do attribute scanning for public annotations; we'd run into
|
||||
// IllegalAccessExceptions otherwise, and we don't want to mess with
|
||||
// accessibility in a SecurityManager environment.
|
||||
// Only do attribute scanning for public annotations.
|
||||
if (Modifier.isPublic(annotationType.getModifiers())) {
|
||||
this.attributesMap.add(annotationName,
|
||||
AnnotationUtils.getAnnotationAttributes(annotation, false, true));
|
||||
|
||||
@@ -190,8 +190,7 @@ public abstract class ReflectionUtils {
|
||||
/**
|
||||
* Make the given constructor accessible, explicitly setting it accessible
|
||||
* if necessary. The {@code setAccessible(true)} method is only called
|
||||
* when actually necessary, to avoid unnecessary conflicts with a JVM
|
||||
* SecurityManager (if active).
|
||||
* when actually necessary, to avoid unnecessary conflicts.
|
||||
* @param ctor the constructor to make accessible
|
||||
* @see java.lang.reflect.Constructor#setAccessible
|
||||
*/
|
||||
@@ -441,10 +440,9 @@ public abstract class ReflectionUtils {
|
||||
|
||||
/**
|
||||
* Variant of {@link Class#getDeclaredMethods()} that uses a local cache in
|
||||
* order to avoid the JVM's SecurityManager check and new Method instances.
|
||||
* In addition, it also includes Java 8 default methods from locally
|
||||
* implemented interfaces, since those are effectively to be treated just
|
||||
* like declared methods.
|
||||
* order to avoid new Method instances. In addition, it also includes Java 8
|
||||
* default methods from locally implemented interfaces, since those are
|
||||
* effectively to be treated just like declared methods.
|
||||
* @param clazz the class to introspect
|
||||
* @return the cached array of methods
|
||||
* @throws IllegalStateException if introspection fails
|
||||
@@ -561,8 +559,7 @@ public abstract class ReflectionUtils {
|
||||
/**
|
||||
* Make the given method accessible, explicitly setting it accessible if
|
||||
* necessary. The {@code setAccessible(true)} method is only called
|
||||
* when actually necessary, to avoid unnecessary conflicts with a JVM
|
||||
* SecurityManager (if active).
|
||||
* when actually necessary, to avoid unnecessary conflicts.
|
||||
* @param method the method to make accessible
|
||||
* @see java.lang.reflect.Method#setAccessible
|
||||
*/
|
||||
@@ -720,7 +717,7 @@ public abstract class ReflectionUtils {
|
||||
|
||||
/**
|
||||
* This variant retrieves {@link Class#getDeclaredFields()} from a local cache
|
||||
* in order to avoid the JVM's SecurityManager check and defensive array copying.
|
||||
* in order to avoid defensive array copying.
|
||||
* @param clazz the class to introspect
|
||||
* @return the cached array of fields
|
||||
* @throws IllegalStateException if introspection fails
|
||||
@@ -774,8 +771,7 @@ public abstract class ReflectionUtils {
|
||||
/**
|
||||
* Make the given field accessible, explicitly setting it accessible if
|
||||
* necessary. The {@code setAccessible(true)} method is only called
|
||||
* when actually necessary, to avoid unnecessary conflicts with a JVM
|
||||
* SecurityManager (if active).
|
||||
* when actually necessary, to avoid unnecessary conflicts.
|
||||
* @param field the field to make accessible
|
||||
* @see java.lang.reflect.Field#setAccessible
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,15 +16,12 @@
|
||||
|
||||
package org.springframework.core.env;
|
||||
|
||||
import java.security.AccessControlException;
|
||||
import java.security.Permission;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.SpringProperties;
|
||||
import org.springframework.core.testfixture.env.EnvironmentTestUtils;
|
||||
import org.springframework.core.testfixture.env.MockPropertySource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -381,72 +378,22 @@ public class StandardEnvironmentTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSystemProperties_withAndWithoutSecurityManager() {
|
||||
void getSystemProperties() {
|
||||
System.setProperty(ALLOWED_PROPERTY_NAME, ALLOWED_PROPERTY_VALUE);
|
||||
System.setProperty(DISALLOWED_PROPERTY_NAME, DISALLOWED_PROPERTY_VALUE);
|
||||
System.getProperties().put(STRING_PROPERTY_NAME, NON_STRING_PROPERTY_VALUE);
|
||||
System.getProperties().put(NON_STRING_PROPERTY_NAME, STRING_PROPERTY_VALUE);
|
||||
|
||||
{
|
||||
try {
|
||||
Map<?, ?> systemProperties = environment.getSystemProperties();
|
||||
assertThat(systemProperties).isNotNull();
|
||||
assertThat(System.getProperties()).isSameAs(systemProperties);
|
||||
assertThat(systemProperties.get(ALLOWED_PROPERTY_NAME)).isEqualTo(ALLOWED_PROPERTY_VALUE);
|
||||
assertThat(systemProperties.get(DISALLOWED_PROPERTY_NAME)).isEqualTo(DISALLOWED_PROPERTY_VALUE);
|
||||
|
||||
// non-string keys and values work fine... until the security manager is introduced below
|
||||
assertThat(systemProperties.get(STRING_PROPERTY_NAME)).isEqualTo(NON_STRING_PROPERTY_VALUE);
|
||||
assertThat(systemProperties.get(NON_STRING_PROPERTY_NAME)).isEqualTo(STRING_PROPERTY_VALUE);
|
||||
}
|
||||
|
||||
SecurityManager oldSecurityManager = System.getSecurityManager();
|
||||
SecurityManager securityManager = new SecurityManager() {
|
||||
@Override
|
||||
public void checkPropertiesAccess() {
|
||||
// see https://download.oracle.com/javase/1.5.0/docs/api/java/lang/System.html#getProperties()
|
||||
throw new AccessControlException("Accessing the system properties is disallowed");
|
||||
}
|
||||
@Override
|
||||
public void checkPropertyAccess(String key) {
|
||||
// see https://download.oracle.com/javase/1.5.0/docs/api/java/lang/System.html#getProperty(java.lang.String)
|
||||
if (DISALLOWED_PROPERTY_NAME.equals(key)) {
|
||||
throw new AccessControlException(
|
||||
String.format("Accessing the system property [%s] is disallowed", DISALLOWED_PROPERTY_NAME));
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void checkPermission(Permission perm) {
|
||||
// allow everything else
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
System.setSecurityManager(securityManager);
|
||||
|
||||
{
|
||||
Map<?, ?> systemProperties = environment.getSystemProperties();
|
||||
assertThat(systemProperties).isNotNull();
|
||||
assertThat(systemProperties).isInstanceOf(ReadOnlySystemAttributesMap.class);
|
||||
assertThat((String)systemProperties.get(ALLOWED_PROPERTY_NAME)).isEqualTo(ALLOWED_PROPERTY_VALUE);
|
||||
assertThat(systemProperties.get(DISALLOWED_PROPERTY_NAME)).isNull();
|
||||
|
||||
// nothing we can do here in terms of warning the user that there was
|
||||
// actually a (non-string) value available. By this point, we only
|
||||
// have access to calling System.getProperty(), which itself returns null
|
||||
// if the value is non-string. So we're stuck with returning a potentially
|
||||
// misleading null.
|
||||
assertThat(systemProperties.get(STRING_PROPERTY_NAME)).isNull();
|
||||
|
||||
// in the case of a non-string *key*, however, we can do better. Alert
|
||||
// the user that under these very special conditions (non-object key +
|
||||
// SecurityManager that disallows access to system properties), they
|
||||
// cannot do what they're attempting.
|
||||
assertThatIllegalArgumentException().as("searching with non-string key against ReadOnlySystemAttributesMap").isThrownBy(() ->
|
||||
systemProperties.get(NON_STRING_PROPERTY_NAME));
|
||||
}
|
||||
}
|
||||
finally {
|
||||
System.setSecurityManager(oldSecurityManager);
|
||||
System.clearProperty(ALLOWED_PROPERTY_NAME);
|
||||
System.clearProperty(DISALLOWED_PROPERTY_NAME);
|
||||
System.getProperties().remove(STRING_PROPERTY_NAME);
|
||||
@@ -455,48 +402,10 @@ public class StandardEnvironmentTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSystemEnvironment_withAndWithoutSecurityManager() {
|
||||
EnvironmentTestUtils.getModifiableSystemEnvironment().put(ALLOWED_PROPERTY_NAME, ALLOWED_PROPERTY_VALUE);
|
||||
EnvironmentTestUtils.getModifiableSystemEnvironment().put(DISALLOWED_PROPERTY_NAME, DISALLOWED_PROPERTY_VALUE);
|
||||
|
||||
{
|
||||
Map<String, Object> systemEnvironment = environment.getSystemEnvironment();
|
||||
assertThat(systemEnvironment).isNotNull();
|
||||
assertThat(System.getenv()).isSameAs(systemEnvironment);
|
||||
}
|
||||
|
||||
SecurityManager oldSecurityManager = System.getSecurityManager();
|
||||
SecurityManager securityManager = new SecurityManager() {
|
||||
@Override
|
||||
public void checkPermission(Permission perm) {
|
||||
//see https://download.oracle.com/javase/1.5.0/docs/api/java/lang/System.html#getenv()
|
||||
if ("getenv.*".equals(perm.getName())) {
|
||||
throw new AccessControlException("Accessing the system environment is disallowed");
|
||||
}
|
||||
//see https://download.oracle.com/javase/1.5.0/docs/api/java/lang/System.html#getenv(java.lang.String)
|
||||
if (("getenv."+DISALLOWED_PROPERTY_NAME).equals(perm.getName())) {
|
||||
throw new AccessControlException(
|
||||
String.format("Accessing the system environment variable [%s] is disallowed", DISALLOWED_PROPERTY_NAME));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
System.setSecurityManager(securityManager);
|
||||
{
|
||||
Map<String, Object> systemEnvironment = environment.getSystemEnvironment();
|
||||
assertThat(systemEnvironment).isNotNull();
|
||||
assertThat(systemEnvironment).isInstanceOf(ReadOnlySystemAttributesMap.class);
|
||||
assertThat(systemEnvironment.get(ALLOWED_PROPERTY_NAME)).isEqualTo(ALLOWED_PROPERTY_VALUE);
|
||||
assertThat(systemEnvironment.get(DISALLOWED_PROPERTY_NAME)).isNull();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
System.setSecurityManager(oldSecurityManager);
|
||||
}
|
||||
|
||||
EnvironmentTestUtils.getModifiableSystemEnvironment().remove(ALLOWED_PROPERTY_NAME);
|
||||
EnvironmentTestUtils.getModifiableSystemEnvironment().remove(DISALLOWED_PROPERTY_NAME);
|
||||
void getSystemEnvironment() {
|
||||
Map<String, Object> systemEnvironment = environment.getSystemEnvironment();
|
||||
assertThat(systemEnvironment).isNotNull();
|
||||
assertThat(System.getenv()).isSameAs(systemEnvironment);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,16 +17,13 @@
|
||||
package org.springframework.core.env;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for {@link SystemEnvironmentPropertySource}.
|
||||
*
|
||||
@@ -148,30 +145,4 @@ class SystemEnvironmentPropertySourceTests {
|
||||
assertThat(ps.getProperty("A.hyphen-KEY")).isEqualTo("a_hyphen_value");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("serial")
|
||||
void withSecurityConstraints() throws Exception {
|
||||
envMap = new HashMap<String, Object>() {
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return new HashSet<>(super.keySet());
|
||||
}
|
||||
};
|
||||
envMap.put("A_KEY", "a_value");
|
||||
|
||||
ps = new SystemEnvironmentPropertySource("sysEnv", envMap) {
|
||||
@Override
|
||||
protected boolean isSecurityManagerPresent() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
assertThat(ps.containsProperty("A_KEY")).isEqualTo(true);
|
||||
assertThat(ps.getProperty("A_KEY")).isEqualTo("a_value");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -33,8 +33,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link StreamUtils}.
|
||||
@@ -57,53 +55,47 @@ class StreamUtilsTests {
|
||||
|
||||
@Test
|
||||
void copyToByteArray() throws Exception {
|
||||
InputStream inputStream = spy(new ByteArrayInputStream(bytes));
|
||||
InputStream inputStream = new ByteArrayInputStream(bytes);
|
||||
byte[] actual = StreamUtils.copyToByteArray(inputStream);
|
||||
assertThat(actual).isEqualTo(bytes);
|
||||
verify(inputStream, never()).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyToString() throws Exception {
|
||||
Charset charset = Charset.defaultCharset();
|
||||
InputStream inputStream = spy(new ByteArrayInputStream(string.getBytes(charset)));
|
||||
InputStream inputStream = new ByteArrayInputStream(string.getBytes(charset));
|
||||
String actual = StreamUtils.copyToString(inputStream, charset);
|
||||
assertThat(actual).isEqualTo(string);
|
||||
verify(inputStream, never()).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyBytes() throws Exception {
|
||||
ByteArrayOutputStream out = spy(new ByteArrayOutputStream());
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamUtils.copy(bytes, out);
|
||||
assertThat(out.toByteArray()).isEqualTo(bytes);
|
||||
verify(out, never()).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyString() throws Exception {
|
||||
Charset charset = Charset.defaultCharset();
|
||||
ByteArrayOutputStream out = spy(new ByteArrayOutputStream());
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamUtils.copy(string, charset, out);
|
||||
assertThat(out.toByteArray()).isEqualTo(string.getBytes(charset));
|
||||
verify(out, never()).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyStream() throws Exception {
|
||||
ByteArrayOutputStream out = spy(new ByteArrayOutputStream());
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamUtils.copy(new ByteArrayInputStream(bytes), out);
|
||||
assertThat(out.toByteArray()).isEqualTo(bytes);
|
||||
verify(out, never()).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyRange() throws Exception {
|
||||
ByteArrayOutputStream out = spy(new ByteArrayOutputStream());
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamUtils.copyRange(new ByteArrayInputStream(bytes), out, 0, 100);
|
||||
byte[] range = Arrays.copyOfRange(bytes, 0, 101);
|
||||
assertThat(out.toByteArray()).isEqualTo(range);
|
||||
verify(out, never()).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.springframework.core.testfixture.env;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
|
||||
/**
|
||||
* Test utilities for {@link StandardEnvironment}.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
public class EnvironmentTestUtils {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Map<String, String> getModifiableSystemEnvironment() {
|
||||
// for os x / linux
|
||||
Class<?>[] classes = Collections.class.getDeclaredClasses();
|
||||
Map<String, String> env = System.getenv();
|
||||
for (Class<?> cl : classes) {
|
||||
if ("java.util.Collections$UnmodifiableMap".equals(cl.getName())) {
|
||||
try {
|
||||
Field field = cl.getDeclaredField("m");
|
||||
field.setAccessible(true);
|
||||
Object obj = field.get(env);
|
||||
if (obj != null && obj.getClass().getName().equals("java.lang.ProcessEnvironment$StringEnvironment")) {
|
||||
return (Map<String, String>) obj;
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for windows
|
||||
Class<?> processEnvironmentClass;
|
||||
try {
|
||||
processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
|
||||
try {
|
||||
Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment");
|
||||
theCaseInsensitiveEnvironmentField.setAccessible(true);
|
||||
Object obj = theCaseInsensitiveEnvironmentField.get(null);
|
||||
return (Map<String, String>) obj;
|
||||
}
|
||||
catch (NoSuchFieldException ex) {
|
||||
// do nothing
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
|
||||
try {
|
||||
Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment");
|
||||
theEnvironmentField.setAccessible(true);
|
||||
Object obj = theEnvironmentField.get(null);
|
||||
return (Map<String, String>) obj;
|
||||
}
|
||||
catch (NoSuchFieldException ex) {
|
||||
// do nothing
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user