org.infinispan.commons.configuration.ClassAllowList Maven / Gradle / Ivy
package org.infinispan.commons.configuration;
import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
import static java.util.Objects.requireNonNull;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.infinispan.commons.logging.Log;
import org.infinispan.commons.logging.LogFactory;
import org.infinispan.commons.util.KeyValueWithPrevious;
/**
* The {@link ClassAllowList} maintains classes definitions either by name or regular expression and is used for
* permissioning.
*
* By default it includes regular expressions from the system property "infinispan.deserialization.allowlist.regexps"
* and fully qualified class names from "infinispan.deserialization.allowlist.classes".
*
* Classes are checked first against the set of class names, and in case not present each of the regular expressions are
* evaluated in the order supplied.
*
* @since 9.4
*/
public class ClassAllowList {
private static final Log log = LogFactory.getLog(ClassAllowList.class);
@Deprecated(forRemoval=true, since = "12.0")
private static final String CLASSES_PROPERTY_NAME_LEGACY = "infinispan.deserialization.whitelist.classes";
@Deprecated(forRemoval=true, since = "12.0")
private static final String REGEXPS_PROPERTY_NAME_LEGACY = "infinispan.deserialization.whitelist.regexps";
private static final String CLASSES_PROPERTY_NAME = "infinispan.deserialization.allowlist.classes";
private static final String REGEXPS_PROPERTY_NAME = "infinispan.deserialization.allowlist.regexps";
private static final Set SYS_ALLOWED_CLASSES = new HashSet<>();
private static final List SYS_ALLOWED_REGEXP = new ArrayList<>();
static {
// Classes always allowed
// Primitive Arrays
SYS_ALLOWED_CLASSES.add(byte[].class.getName());
SYS_ALLOWED_CLASSES.add(short[].class.getName());
SYS_ALLOWED_CLASSES.add(int[].class.getName());
SYS_ALLOWED_CLASSES.add(long[].class.getName());
SYS_ALLOWED_CLASSES.add(float[].class.getName());
SYS_ALLOWED_CLASSES.add(double[].class.getName());
SYS_ALLOWED_CLASSES.add(char[].class.getName());
SYS_ALLOWED_CLASSES.add(boolean[].class.getName());
// Boxed Primitives
SYS_ALLOWED_CLASSES.add(Byte.class.getName());
SYS_ALLOWED_CLASSES.add(Short.class.getName());
SYS_ALLOWED_CLASSES.add(Integer.class.getName());
SYS_ALLOWED_CLASSES.add(Long.class.getName());
SYS_ALLOWED_CLASSES.add(Float.class.getName());
SYS_ALLOWED_CLASSES.add(Double.class.getName());
SYS_ALLOWED_CLASSES.add(Character.class.getName());
SYS_ALLOWED_CLASSES.add(String.class.getName());
SYS_ALLOWED_CLASSES.add(Boolean.class.getName());
// Java.math
SYS_ALLOWED_CLASSES.add(BigInteger.class.getName());
SYS_ALLOWED_CLASSES.add(BigDecimal.class.getName());
// Java.time
SYS_ALLOWED_CLASSES.add(Instant.class.getName());
SYS_ALLOWED_CLASSES.add("java.time.Ser");
// Util
SYS_ALLOWED_CLASSES.add(Date.class.getName());
// Misc
SYS_ALLOWED_CLASSES.add(Enum.class.getName());
SYS_ALLOWED_CLASSES.add(Number.class.getName());
// Reference array regex, for arrray representations of allowed classes e.g '[Ljava.lang.Byte;'
SYS_ALLOWED_REGEXP.add("^\\[[\\[L].*\\;$");
// Infinispan classes
// Used by client listeners
SYS_ALLOWED_CLASSES.add(KeyValueWithPrevious.class.getName());
// Legacy handling
String regexps = System.getProperty(REGEXPS_PROPERTY_NAME_LEGACY);
if (regexps != null) {
log.deprecatedProperty(REGEXPS_PROPERTY_NAME_LEGACY, REGEXPS_PROPERTY_NAME);
SYS_ALLOWED_REGEXP.addAll(asList(regexps.trim().split(",")));
}
regexps = System.getProperty(REGEXPS_PROPERTY_NAME);
if (regexps != null) {
SYS_ALLOWED_REGEXP.addAll(asList(regexps.trim().split(",")));
}
String cls = System.getProperty(CLASSES_PROPERTY_NAME_LEGACY);
if (cls != null) {
log.deprecatedProperty(CLASSES_PROPERTY_NAME_LEGACY, CLASSES_PROPERTY_NAME);
SYS_ALLOWED_CLASSES.addAll(asList(cls.trim().split(",")));
}
cls = System.getProperty(CLASSES_PROPERTY_NAME);
if (cls != null) {
SYS_ALLOWED_CLASSES.addAll(asList(cls.trim().split(",")));
}
}
private final Set classes = new CopyOnWriteArraySet<>(SYS_ALLOWED_CLASSES);
private final List regexps = new CopyOnWriteArrayList<>(SYS_ALLOWED_REGEXP);
private final List compiled = new CopyOnWriteArrayList<>();
private final ClassLoader classLoader;
public ClassAllowList() {
this(Collections.emptySet(), Collections.emptyList(), null);
}
public ClassAllowList(List regexps) {
this(Collections.emptySet(), regexps, null);
}
public ClassAllowList(Collection classes, List regexps, ClassLoader classLoader) {
Collection classList = requireNonNull(classes, "Classes must not be null");
Collection regexList = requireNonNull(regexps, "Regexps must not be null");
this.classes.addAll(classList);
this.regexps.addAll(regexList);
this.compiled.addAll(this.regexps.stream().map(Pattern::compile).collect(Collectors.toList()));
this.classLoader = classLoader;
}
public boolean isSafeClass(String className) {
// Test for classes first (faster)
boolean isClassAllowed = classes.contains(className);
if (isClassAllowed) return true;
boolean regexMatch = compiled.stream().anyMatch(p -> p.matcher(className).find());
if (regexMatch) {
// Add the class name to the classes set to avoid future regex checks
classes.add(className);
return true;
}
if (log.isTraceEnabled())
log.tracef("Class '%s' not in allowlist", className);
return false;
}
public void addClasses(Class>... classes) {
stream(classes).forEach(c -> this.classes.add(c.getName()));
}
public void addClasses(String... classes) {
this.classes.addAll(Arrays.asList(classes));
}
public void addRegexps(String... regexps) {
this.regexps.addAll(asList(regexps));
this.compiled.addAll(stream(regexps).map(Pattern::compile).collect(Collectors.toList()));
}
public void read(ClassAllowList allowList) {
this.regexps.addAll(allowList.regexps);
this.compiled.addAll(allowList.compiled);
this.classes.addAll(allowList.classes);
}
public ClassLoader getClassLoader() {
return classLoader;
}
}