11/*
2- * Copyright 2008-2019 the original author or authors.
2+ * Copyright 2008-2020 the original author or authors.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
1818import java .io .IOException ;
1919import java .io .InputStream ;
2020import java .io .OutputStream ;
21+ import java .util .Arrays ;
22+ import java .util .Collection ;
23+ import java .util .Collections ;
2124import java .util .Date ;
2225import java .util .HashMap ;
26+ import java .util .HashSet ;
2327import java .util .Map ;
28+ import java .util .Set ;
2429
30+ import com .fasterxml .jackson .annotation .JacksonAnnotation ;
2531import com .fasterxml .jackson .annotation .JsonIgnore ;
32+ import com .fasterxml .jackson .annotation .JsonTypeInfo ;
2633import com .fasterxml .jackson .core .JsonParser ;
2734import com .fasterxml .jackson .core .type .TypeReference ;
35+ import com .fasterxml .jackson .databind .DatabindContext ;
36+ import com .fasterxml .jackson .databind .DeserializationConfig ;
2837import com .fasterxml .jackson .databind .DeserializationContext ;
2938import com .fasterxml .jackson .databind .DeserializationFeature ;
39+ import com .fasterxml .jackson .databind .JavaType ;
3040import com .fasterxml .jackson .databind .JsonNode ;
3141import com .fasterxml .jackson .databind .MapperFeature ;
3242import com .fasterxml .jackson .databind .ObjectMapper ;
33-
43+ import com . fasterxml . jackson . databind . cfg . MapperConfig ;
3444import com .fasterxml .jackson .databind .deser .std .StdDeserializer ;
45+ import com .fasterxml .jackson .databind .jsontype .BasicPolymorphicTypeValidator ;
46+ import com .fasterxml .jackson .databind .jsontype .NamedType ;
47+ import com .fasterxml .jackson .databind .jsontype .PolymorphicTypeValidator ;
48+ import com .fasterxml .jackson .databind .jsontype .TypeIdResolver ;
49+ import com .fasterxml .jackson .databind .jsontype .TypeResolverBuilder ;
3550import com .fasterxml .jackson .databind .module .SimpleModule ;
51+
3652import org .springframework .batch .core .JobParameter ;
3753import org .springframework .batch .core .JobParameters ;
3854import org .springframework .batch .core .repository .ExecutionContextSerializer ;
55+ import org .springframework .core .annotation .AnnotationUtils ;
3956import org .springframework .util .Assert ;
4057
4158/**
42- * Implementation that uses Jackson2 to provide (de)serialization.
59+ * Implementation that uses Jackson2 to provide (de)serialization.
60+ *
61+ * By default, this implementation trusts a limited set of classes to be
62+ * deserialized from the execution context. If a class is not trusted by default
63+ * and is safe to deserialize, you can provide an explicit mapping using Jackson
64+ * annotations, as shown in the following example:
65+ *
66+ * <pre class="code">
67+ * @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
68+ * public class MyTrustedType implements Serializable {
69+ *
70+ * }
71+ * </pre>
72+ *
73+ * It is also possible to provide a custom {@link ObjectMapper} with a mixin for
74+ * the trusted type:
75+ *
76+ * <pre class="code">
77+ * ObjectMapper objectMapper = new ObjectMapper();
78+ * objectMapper.addMixIn(MyTrustedType.class, Object.class);
79+ * Jackson2ExecutionContextStringSerializer serializer = new Jackson2ExecutionContextStringSerializer();
80+ * serializer.setObjectMapper(objectMapper);
81+ * // register serializer in JobRepositoryFactoryBean
82+ * </pre>
83+ *
84+ * If the (de)serialization is only done by a trusted source, you can also enable
85+ * default typing:
86+ *
87+ * <pre class="code">
88+ * PolymorphicTypeValidator polymorphicTypeValidator = .. // configure your trusted PolymorphicTypeValidator
89+ * ObjectMapper objectMapper = new ObjectMapper();
90+ * objectMapper.activateDefaultTyping(polymorphicTypeValidator);
91+ * Jackson2ExecutionContextStringSerializer serializer = new Jackson2ExecutionContextStringSerializer();
92+ * serializer.setObjectMapper(objectMapper);
93+ * // register serializer in JobRepositoryFactoryBean
94+ * </pre>
4395 *
4496 * @author Marten Deinum
4597 * @author Mahmoud Ben Hassine
@@ -55,7 +107,8 @@ public Jackson2ExecutionContextStringSerializer() {
55107 this .objectMapper = new ObjectMapper ();
56108 this .objectMapper .configure (MapperFeature .DEFAULT_VIEW_INCLUSION , false );
57109 this .objectMapper .configure (DeserializationFeature .FAIL_ON_UNKNOWN_PROPERTIES , true );
58- this .objectMapper .enableDefaultTyping ();
110+ this .objectMapper .configure (MapperFeature .BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES , true );
111+ this .objectMapper .setDefaultTyping (createTrustedDefaultTyping ());
59112 this .objectMapper .registerModule (new JobParametersModule ());
60113 }
61114
@@ -141,4 +194,158 @@ public JobParameter deserialize(JsonParser parser, DeserializationContext contex
141194
142195 }
143196
197+ /**
198+ * Creates a TypeResolverBuilder that checks if a type is trusted.
199+ * @return a TypeResolverBuilder that checks if a type is trusted.
200+ */
201+ private static TypeResolverBuilder <? extends TypeResolverBuilder > createTrustedDefaultTyping () {
202+ TypeResolverBuilder <? extends TypeResolverBuilder > result = new TrustedTypeResolverBuilder (ObjectMapper .DefaultTyping .NON_FINAL );
203+ result = result .init (JsonTypeInfo .Id .CLASS , null );
204+ result = result .inclusion (JsonTypeInfo .As .PROPERTY );
205+ return result ;
206+ }
207+
208+ /**
209+ * An implementation of {@link ObjectMapper.DefaultTypeResolverBuilder}
210+ * that inserts an {@code allow all} {@link PolymorphicTypeValidator}
211+ * and overrides the {@code TypeIdResolver}
212+ * @author Rob Winch
213+ */
214+ static class TrustedTypeResolverBuilder extends ObjectMapper .DefaultTypeResolverBuilder {
215+
216+ TrustedTypeResolverBuilder (ObjectMapper .DefaultTyping defaultTyping ) {
217+ super (
218+ defaultTyping ,
219+ //we do explicit validation in the TypeIdResolver
220+ BasicPolymorphicTypeValidator .builder ()
221+ .allowIfSubType (Object .class )
222+ .build ()
223+ );
224+ }
225+
226+ @ Override
227+ protected TypeIdResolver idResolver (MapperConfig <?> config ,
228+ JavaType baseType ,
229+ PolymorphicTypeValidator subtypeValidator ,
230+ Collection <NamedType > subtypes , boolean forSer , boolean forDeser ) {
231+ TypeIdResolver result = super .idResolver (config , baseType , subtypeValidator , subtypes , forSer , forDeser );
232+ return new TrustedTypeIdResolver (result );
233+ }
234+ }
235+
236+ /**
237+ * A {@link TypeIdResolver} that delegates to an existing implementation and throws an IllegalStateException if the
238+ * class being looked up is not trusted, does not provide an explicit mixin, and is not annotated with Jackson
239+ * mappings.
240+ */
241+ static class TrustedTypeIdResolver implements TypeIdResolver {
242+ private static final Set <String > TRUSTED_CLASS_NAMES = Collections .unmodifiableSet (new HashSet (Arrays .asList (
243+ "java.util.ArrayList" ,
244+ "java.util.LinkedList" ,
245+ "java.util.Collections$EmptyList" ,
246+ "java.util.Collections$EmptyMap" ,
247+ "java.util.Collections$EmptySet" ,
248+ "java.util.Collections$UnmodifiableRandomAccessList" ,
249+ "java.util.Collections$UnmodifiableList" ,
250+ "java.util.Collections$UnmodifiableMap" ,
251+ "java.util.Collections$UnmodifiableSet" ,
252+ "java.util.Collections$SingletonList" ,
253+ "java.util.Collections$SingletonMap" ,
254+ "java.util.Collections$SingletonSet" ,
255+ "java.util.Date" ,
256+ "java.time.Instant" ,
257+ "java.time.Duration" ,
258+ "java.time.LocalDate" ,
259+ "java.time.LocalTime" ,
260+ "java.time.LocalDateTime" ,
261+ "java.net.URL" ,
262+ "java.util.TreeMap" ,
263+ "java.util.HashMap" ,
264+ "java.util.LinkedHashMap" ,
265+ "java.util.TreeSet" ,
266+ "java.util.HashSet" ,
267+ "java.util.LinkedHashSet" ,
268+ "java.lang.Boolean" ,
269+ "java.lang.Byte" ,
270+ "java.lang.Short" ,
271+ "java.lang.Integer" ,
272+ "java.lang.Long" ,
273+ "java.lang.Double" ,
274+ "java.lang.Float" ,
275+ "java.math.BigDecimal" ,
276+ "java.math.BigInteger" ,
277+ "java.lang.String" ,
278+ "java.lang.Character" ,
279+ "java.lang.CharSequence" ,
280+ "java.util.Properties" ,
281+ "[Ljava.util.Properties;" ,
282+ "org.springframework.batch.core.JobParameter" ,
283+ "org.springframework.batch.core.JobParameters" ,
284+ "org.springframework.batch.core.jsr.partition.JsrPartitionHandler$PartitionPlanState"
285+ )));
286+
287+ private final TypeIdResolver delegate ;
288+
289+ TrustedTypeIdResolver (TypeIdResolver delegate ) {
290+ this .delegate = delegate ;
291+ }
292+
293+ @ Override
294+ public void init (JavaType baseType ) {
295+ delegate .init (baseType );
296+ }
297+
298+ @ Override
299+ public String idFromValue (Object value ) {
300+ return delegate .idFromValue (value );
301+ }
302+
303+ @ Override
304+ public String idFromValueAndType (Object value , Class <?> suggestedType ) {
305+ return delegate .idFromValueAndType (value , suggestedType );
306+ }
307+
308+ @ Override
309+ public String idFromBaseType () {
310+ return delegate .idFromBaseType ();
311+ }
312+
313+ @ Override
314+ public JavaType typeFromId (DatabindContext context , String id ) throws IOException {
315+ DeserializationConfig config = (DeserializationConfig ) context .getConfig ();
316+ JavaType result = delegate .typeFromId (context , id );
317+ String className = result .getRawClass ().getName ();
318+ if (isTrusted (className )) {
319+ return result ;
320+ }
321+ boolean isExplicitMixin = config .findMixInClassFor (result .getRawClass ()) != null ;
322+ if (isExplicitMixin ) {
323+ return result ;
324+ }
325+ Class <?> rawClass = result .getRawClass ();
326+ JacksonAnnotation jacksonAnnotation = AnnotationUtils .findAnnotation (rawClass , JacksonAnnotation .class );
327+ if (jacksonAnnotation != null ) {
328+ return result ;
329+ }
330+ throw new IllegalArgumentException ("The class with " + id + " and name of " + className + " is not trusted. " +
331+ "If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or a custom ObjectMapper. " +
332+ "If the serialization is only done by a trusted source, you can also enable default typing." );
333+ }
334+
335+ private boolean isTrusted (String id ) {
336+ return TRUSTED_CLASS_NAMES .contains (id );
337+ }
338+
339+ @ Override
340+ public String getDescForKnownTypeIds () {
341+ return delegate .getDescForKnownTypeIds ();
342+ }
343+
344+ @ Override
345+ public JsonTypeInfo .Id getMechanism () {
346+ return delegate .getMechanism ();
347+ }
348+
349+ }
350+
144351}
0 commit comments