From c456a08a2adccad646040d5ad5c7d4558c28745e Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 27 Apr 2019 22:23:35 -0700 Subject: [PATCH] Yet more work for #2195 --- .../jackson/databind/DatabindContext.java | 1 + .../jackson/databind/ObjectMapper.java | 8 ++++- .../BasicPolymorphicTypeValidator.java | 6 ++++ .../jsontype/PolymorphicTypeValidator.java | 25 +++++++++++++- .../jsontype/impl/ClassNameIdResolver.java | 16 +-------- .../impl/MinimalClassNameIdResolver.java | 3 -- .../jsontype/impl/StdTypeResolverBuilder.java | 33 +++++++++++++++---- .../vld/ValidatePolymSubTypeTest.java | 20 ++++++++--- 8 files changed, 80 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/DatabindContext.java b/src/main/java/com/fasterxml/jackson/databind/DatabindContext.java index dd32007b8b..d96766b378 100644 --- a/src/main/java/com/fasterxml/jackson/databind/DatabindContext.java +++ b/src/main/java/com/fasterxml/jackson/databind/DatabindContext.java @@ -251,6 +251,7 @@ public JavaType resolveAndValidateSubType(JavaType baseType, String subClass, return _throwNotASubtype(baseType, subClass); } final JavaType subType = config.getTypeFactory().constructSpecializedType(baseType, cls); + // May skip check if type was allowed by subclass name already if (vld != Validity.ALLOWED) { if (ptv.validateSubType(config, baseType, subType) != Validity.ALLOWED) { return _throwSubtypeClassNotAllowed(baseType, subClass, ptv); diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java index 9df0c509fd..c1bc87cb42 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java @@ -319,7 +319,13 @@ protected final static class LaissezFaireValidator private static final long serialVersionUID = 1L; public final static LaissezFaireValidator instance = new LaissezFaireValidator(); - + + @Override + public Validity validateBaseType(MapperConfig ctxt, JavaType baseType) + throws JsonMappingException { + return Validity.INDETERMINATE; + } + @Override public Validity validateSubClassName(MapperConfig ctxt, JavaType baseType, String subClassName) { diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/BasicPolymorphicTypeValidator.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/BasicPolymorphicTypeValidator.java index ac0ef779f9..cd1299314a 100644 --- a/src/main/java/com/fasterxml/jackson/databind/jsontype/BasicPolymorphicTypeValidator.java +++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/BasicPolymorphicTypeValidator.java @@ -228,6 +228,12 @@ public static Builder builder() { return new Builder(); } + // !!! TODO + @Override + public Validity validateBaseType(MapperConfig ctxt, JavaType baseType) { + return Validity.INDETERMINATE; + } + @Override public Validity validateSubClassName(MapperConfig ctxt, JavaType baseType, String subClassName) throws JsonMappingException diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/PolymorphicTypeValidator.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/PolymorphicTypeValidator.java index 59ab342d97..870ae9ff81 100644 --- a/src/main/java/com/fasterxml/jackson/databind/jsontype/PolymorphicTypeValidator.java +++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/PolymorphicTypeValidator.java @@ -6,12 +6,14 @@ /** * Interface for classes that handle validation of class name-based subtypes used * with Polymorphic Deserialization: both via "default typing" and explicit - * {@code @JsonTypeInfo} when using class name as Type Identifier. + * {@code @JsonTypeInfo} when using Java Class name as Type Identifier. * The main purpose, initially, is to allow pluggable allow/deny lists to avoid * security problems that occur with unlimited class names * (See * this article for full explanation). *

+ * Call fal + *

* Notes on implementations: implementations must be thread-safe and shareable (usually meaning they * are stateless). Determinations for validity are usually effectively cached on per-property * basis (by virtue of subtype deserializers being cached by polymorphic deserializers) so @@ -51,6 +53,27 @@ public enum Validity { ; } + /** + * Method called when a property with polymorphic value is encountered, and a + * {@code TypeResolverBuilder} is needed. Intent is to allow early determination + * of cases where subtyping is completely denied (for example for security reasons), + * or, conversely, allowed for allow subtypes (when base type guarantees that all subtypes + * are known to be safe). Check can be thought of as both optimization (for latter case) + * and eager-fail (for former case) to give better feedback. + * + * @param ctxt Context for resolution: typically will be {@code DeserializationContext} + * @param baseType Nominal base type used for polymorphic handling: subtypes MUST be instances + * of this type and assignment compatibility is verified by Jackson core + * + * @return Determination of general validity of all subtypes of given base type; if + * {@link Validity#ALLOWED} returned, all subtypes will automatically be accepted without + * further checks; is {@link Validity#DENIED} returned no subtyping allowed at all + * (caller will usually throw an exception); otherwise (return {@link Validity#INDETERMINATE}) + * per sub-type validation calls are made for each new subclass encountered. + */ + public abstract Validity validateBaseType(MapperConfig ctxt, JavaType baseType) + throws JsonMappingException; + /** * Method called after intended class name for subtype has been read (and in case of minimal * class name, expanded to fully-qualified class name) but before attempt is made to diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/ClassNameIdResolver.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/ClassNameIdResolver.java index 0047b24a46..c0457513e7 100644 --- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/ClassNameIdResolver.java +++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/ClassNameIdResolver.java @@ -20,28 +20,14 @@ public class ClassNameIdResolver { private final static String JAVA_UTIL_PKG = "java.util."; - /** - * @since 2.10 - */ protected final PolymorphicTypeValidator _subTypeValidator; - /** - * @deprecated since 2.10 - */ - @Deprecated - public ClassNameIdResolver(JavaType baseType, TypeFactory typeFactory) { - this(baseType, typeFactory, null); - } - public ClassNameIdResolver(JavaType baseType, TypeFactory typeFactory, PolymorphicTypeValidator ptv) { super(baseType, typeFactory); _subTypeValidator = ptv; } - /** - * @since 2.10 - */ public static ClassNameIdResolver construct(JavaType baseType, MapperConfig config, PolymorphicTypeValidator ptv) { return new ClassNameIdResolver(baseType, config.getTypeFactory(), ptv); @@ -72,7 +58,7 @@ public JavaType typeFromId(DatabindContext context, String id) throws IOExceptio protected JavaType _typeFromId(String id, DatabindContext ctxt) throws IOException { // 24-Apr-2019, tatu: [databind#2195] validate as well as resolve: - JavaType t = ctxt.resolveAndValidateSubType(_baseType, id); + JavaType t = ctxt.resolveAndValidateSubType(_baseType, id, _subTypeValidator); if (t == null) { if (ctxt instanceof DeserializationContext) { // First: we may have problem handlers that can deal with it? diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/MinimalClassNameIdResolver.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/MinimalClassNameIdResolver.java index 649dfc42ab..dc7dfb39ea 100644 --- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/MinimalClassNameIdResolver.java +++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/MinimalClassNameIdResolver.java @@ -39,9 +39,6 @@ protected MinimalClassNameIdResolver(JavaType baseType, TypeFactory typeFactory, } } - /** - * @since 2.10 - */ public static MinimalClassNameIdResolver construct(JavaType baseType, MapperConfig config, PolymorphicTypeValidator ptv) { return new MinimalClassNameIdResolver(baseType, config.getTypeFactory(), ptv); diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdTypeResolverBuilder.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdTypeResolverBuilder.java index b9dd513a51..cf8c4d241b 100644 --- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdTypeResolverBuilder.java +++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdTypeResolverBuilder.java @@ -84,7 +84,8 @@ public TypeSerializer buildTypeSerializer(SerializationConfig config, if (baseType.isPrimitive()) { return null; } - TypeIdResolver idRes = idResolver(config, baseType, subtypes, true, false); + TypeIdResolver idRes = idResolver(config, baseType, subTypeValidator(config), + subtypes, true, false); switch (_includeAs) { case WRAPPER_ARRAY: return new AsArrayTypeSerializer(idRes, null); @@ -118,7 +119,11 @@ public TypeDeserializer buildTypeDeserializer(DeserializationConfig config, return null; } - TypeIdResolver idRes = idResolver(config, baseType, subtypes, false, true); + // 27-Apr-2019, tatu: Part of [databind#2195]; must first check whether any subtypes + // of basetypes might be denied or allowed + final PolymorphicTypeValidator subTypeValidator = verifyBaseTypeValidity(config, baseType); + + TypeIdResolver idRes = idResolver(config, baseType, subTypeValidator, subtypes, false, true); JavaType defaultImpl = defineDefaultImpl(config, baseType); @@ -236,10 +241,10 @@ public StdTypeResolverBuilder typeIdVisibility(boolean isVisible) { public String getTypeProperty() { return _typeProperty; } public boolean isTypeIdVisible() { return _typeIdVisible; } - + /* /********************************************************** - /* Internal/subtype methods + /* Internal/subtype factory methods /********************************************************** */ @@ -249,16 +254,17 @@ public StdTypeResolverBuilder typeIdVisibility(boolean isVisible) { * given configuration. */ protected TypeIdResolver idResolver(MapperConfig config, - JavaType baseType, Collection subtypes, boolean forSer, boolean forDeser) + JavaType baseType, PolymorphicTypeValidator subtypeValidator, + Collection subtypes, boolean forSer, boolean forDeser) { // Custom id resolver? if (_customIdResolver != null) { return _customIdResolver; } if (_idType == null) throw new IllegalStateException("Cannot build, 'init()' not yet called"); switch (_idType) { case CLASS: - return ClassNameIdResolver.construct(baseType, config, subTypeValidator(config)); + return ClassNameIdResolver.construct(baseType, config, subtypeValidator); case MINIMAL_CLASS: - return MinimalClassNameIdResolver.construct(baseType, config, subTypeValidator(config)); + return MinimalClassNameIdResolver.construct(baseType, config, subtypeValidator); case NAME: return TypeNameIdResolver.construct(config, baseType, subtypes, forSer, forDeser); case NONE: // hmmh. should never get this far with 'none' @@ -268,6 +274,19 @@ protected TypeIdResolver idResolver(MapperConfig config, throw new IllegalStateException("Do not know how to construct standard type id resolver for idType: "+_idType); } + /* + /********************************************************** + /* Internal/subtype factory methods + /********************************************************** + */ + + + protected PolymorphicTypeValidator verifyBaseTypeValidity(MapperConfig config, + JavaType baseType) + { + return subTypeValidator(config); + } + /** * Overridable helper method for determining actual validator to use when constructing * type serializers and type deserializers. diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/vld/ValidatePolymSubTypeTest.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/vld/ValidatePolymSubTypeTest.java index 2d39b08c70..72bb3c8eb1 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/vld/ValidatePolymSubTypeTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/vld/ValidatePolymSubTypeTest.java @@ -8,7 +8,7 @@ /** * Tests to verify working of customizable {@PolymorphicTypeValidator}, - * see [databind#2195] + * see [databind#2195], regarding verification of subtype instances. * * @since 2.10 */ @@ -33,7 +33,7 @@ static class GoodValue extends BaseValue { } static class MehValue extends BaseValue { } // // // Wrapper types - + static class AnnotatedWrapper { @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) public BaseValue value; @@ -62,6 +62,12 @@ protected DefTypeWrapper() { } static class SimpleNameBasedValidator extends PolymorphicTypeValidator { private static final long serialVersionUID = 1L; + // No categoric determination, depends on subtype + @Override + public Validity validateBaseType(MapperConfig ctxt, JavaType baseType) { + return Validity.INDETERMINATE; + } + @Override public Validity validateSubClassName(MapperConfig ctxt, JavaType baseType, String subClassName) { if (subClassName.equals(BadValue.class.getName())) { @@ -82,6 +88,11 @@ public Validity validateSubType(MapperConfig ctxt, JavaType baseType, JavaTyp static class SimpleClassBasedValidator extends PolymorphicTypeValidator { private static final long serialVersionUID = 1L; + @Override + public Validity validateBaseType(MapperConfig ctxt, JavaType baseType) { + return Validity.INDETERMINATE; + } + @Override public Validity validateSubClassName(MapperConfig ctxt, JavaType baseType, String subClassName) { return Validity.INDETERMINATE; @@ -96,8 +107,7 @@ public Validity validateSubType(MapperConfig ctxt, JavaType baseType, JavaTyp return Validity.ALLOWED; } // defaults to denied, then: - return Validity.INDETERMINATE; - } + return Validity.INDETERMINATE; } } // // // Mappers with Default Typing @@ -121,7 +131,7 @@ public Validity validateSubType(MapperConfig ctxt, JavaType baseType, JavaTyp private final ObjectMapper MAPPER_EXPLICIT_CLASS_CHECK = jsonMapperBuilder() .polymorphicTypeValidator(new SimpleClassBasedValidator()) .build(); - + /* /********************************************************************** /* Test methods: default typing