|
@@ -16,17 +16,18 @@
|
|
|
|
|
|
package org.springframework.security.jackson2;
|
|
package org.springframework.security.jackson2;
|
|
|
|
|
|
|
|
+import com.fasterxml.jackson.annotation.JacksonAnnotation;
|
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
|
-import com.fasterxml.jackson.databind.Module;
|
|
|
|
-import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
|
-import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
|
|
|
|
|
|
+import com.fasterxml.jackson.databind.*;
|
|
|
|
+import com.fasterxml.jackson.databind.cfg.MapperConfig;
|
|
|
|
+import com.fasterxml.jackson.databind.jsontype.*;
|
|
import org.apache.commons.logging.Log;
|
|
import org.apache.commons.logging.Log;
|
|
import org.apache.commons.logging.LogFactory;
|
|
import org.apache.commons.logging.LogFactory;
|
|
|
|
+import org.springframework.core.annotation.AnnotationUtils;
|
|
import org.springframework.util.ClassUtils;
|
|
import org.springframework.util.ClassUtils;
|
|
|
|
|
|
-import java.util.ArrayList;
|
|
|
|
-import java.util.Arrays;
|
|
|
|
-import java.util.List;
|
|
|
|
|
|
+import java.io.IOException;
|
|
|
|
+import java.util.*;
|
|
|
|
|
|
/**
|
|
/**
|
|
* This utility class will find all the SecurityModules in classpath.
|
|
* This utility class will find all the SecurityModules in classpath.
|
|
@@ -65,7 +66,7 @@ public final class SecurityJackson2Modules {
|
|
if(mapper != null) {
|
|
if(mapper != null) {
|
|
TypeResolverBuilder<?> typeBuilder = mapper.getDeserializationConfig().getDefaultTyper(null);
|
|
TypeResolverBuilder<?> typeBuilder = mapper.getDeserializationConfig().getDefaultTyper(null);
|
|
if (typeBuilder == null) {
|
|
if (typeBuilder == null) {
|
|
- mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
|
|
|
|
|
|
+ mapper.setDefaultTyping(createWhitelistedDefaultTyping());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -103,4 +104,111 @@ public final class SecurityJackson2Modules {
|
|
}
|
|
}
|
|
return modules;
|
|
return modules;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Creates a TypeResolverBuilder that performs whitelisting.
|
|
|
|
+ * @return a TypeResolverBuilder that performs whitelisting.
|
|
|
|
+ */
|
|
|
|
+ private static TypeResolverBuilder<? extends TypeResolverBuilder> createWhitelistedDefaultTyping() {
|
|
|
|
+ TypeResolverBuilder<? extends TypeResolverBuilder> result = new WhitelistTypeResolverBuilder(ObjectMapper.DefaultTyping.NON_FINAL);
|
|
|
|
+ result = result.init(JsonTypeInfo.Id.CLASS, null);
|
|
|
|
+ result = result.inclusion(JsonTypeInfo.As.PROPERTY);
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * An implementation of {@link ObjectMapper.DefaultTypeResolverBuilder} that overrides the {@link TypeIdResolver}
|
|
|
|
+ * with {@link WhitelistTypeIdResolver}.
|
|
|
|
+ * @author Rob Winch
|
|
|
|
+ */
|
|
|
|
+ static class WhitelistTypeResolverBuilder extends ObjectMapper.DefaultTypeResolverBuilder {
|
|
|
|
+
|
|
|
|
+ public WhitelistTypeResolverBuilder(ObjectMapper.DefaultTyping defaultTyping) {
|
|
|
|
+ super(defaultTyping);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ protected TypeIdResolver idResolver(MapperConfig<?> config,
|
|
|
|
+ JavaType baseType, Collection<NamedType> subtypes, boolean forSer, boolean forDeser) {
|
|
|
|
+ TypeIdResolver result = super.idResolver(config, baseType, subtypes, forSer, forDeser);
|
|
|
|
+ return new WhitelistTypeIdResolver(result);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * A {@link TypeIdResolver} that delegates to an existing implementation and throws an IllegalStateException if the
|
|
|
|
+ * class being looked up is not whitelisted, does not provide an explicit mixin, and is not annotated with Jackson
|
|
|
|
+ * mappings. See https://github.com/spring-projects/spring-security/issues/4370
|
|
|
|
+ */
|
|
|
|
+ static class WhitelistTypeIdResolver implements TypeIdResolver {
|
|
|
|
+ private static final Set<String> WHITELIST_CLASS_NAMES = Collections.unmodifiableSet(new HashSet(Arrays.asList(
|
|
|
|
+ "java.util.ArrayList",
|
|
|
|
+ "java.util.Collections$EmptyMap",
|
|
|
|
+ "java.util.Date",
|
|
|
|
+ "java.util.TreeMap",
|
|
|
|
+ "org.springframework.security.core.context.SecurityContextImpl"
|
|
|
|
+ )));
|
|
|
|
+
|
|
|
|
+ private final TypeIdResolver delegate;
|
|
|
|
+
|
|
|
|
+ WhitelistTypeIdResolver(TypeIdResolver delegate) {
|
|
|
|
+ this.delegate = delegate;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void init(JavaType baseType) {
|
|
|
|
+ delegate.init(baseType);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public String idFromValue(Object value) {
|
|
|
|
+ return delegate.idFromValue(value);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public String idFromValueAndType(Object value, Class<?> suggestedType) {
|
|
|
|
+ return delegate.idFromValueAndType(value, suggestedType);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public String idFromBaseType() {
|
|
|
|
+ return delegate.idFromBaseType();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public JavaType typeFromId(DatabindContext context, String id) throws IOException {
|
|
|
|
+ DeserializationConfig config = (DeserializationConfig) context.getConfig();
|
|
|
|
+ JavaType result = delegate.typeFromId(context, id);
|
|
|
|
+ String className = result.getRawClass().getName();
|
|
|
|
+ if(isWhitelisted(className)) {
|
|
|
|
+ return delegate.typeFromId(context, id);
|
|
|
|
+ }
|
|
|
|
+ boolean isExplicitMixin = config.findMixInClassFor(result.getRawClass()) != null;
|
|
|
|
+ if(isExplicitMixin) {
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+ JacksonAnnotation jacksonAnnotation = AnnotationUtils.findAnnotation(result.getRawClass(), JacksonAnnotation.class);
|
|
|
|
+ if(jacksonAnnotation != null) {
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+ throw new IllegalArgumentException("The class with " + id + " and name of " + className + " is not whitelisted. " +
|
|
|
|
+ "If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. " +
|
|
|
|
+ "If the serialization is only done by a trusted source, you can also enable default typing. " +
|
|
|
|
+ "See https://github.com/spring-projects/spring-security/issues/4370 for details");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private boolean isWhitelisted(String id) {
|
|
|
|
+ return WHITELIST_CLASS_NAMES.contains(id);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public String getDescForKnownTypeIds() {
|
|
|
|
+ return delegate.getDescForKnownTypeIds();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public JsonTypeInfo.Id getMechanism() {
|
|
|
|
+ return delegate.getMechanism();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
}
|
|
}
|