Update SecurityJackson2Modules

Fixes gh-4370
This commit is contained in:
Rob Winch
2017-05-31 12:19:44 -05:00
parent b3a60a83f6
commit 947d11f433
2 changed files with 242 additions and 8 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2016 the original author or authors.
* Copyright 2015-2017 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,17 +16,18 @@
package org.springframework.security.jackson2;
import com.fasterxml.jackson.annotation.JacksonAnnotation;
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.LogFactory;
import org.springframework.core.annotation.AnnotationUtils;
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.
@@ -65,7 +66,7 @@ public final class SecurityJackson2Modules {
if(mapper != null) {
TypeResolverBuilder<?> typeBuilder = mapper.getDeserializationConfig().getDefaultTyper(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;
}
/**
* 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();
}
}
}