1
1
/*
2
- * Copyright 2008-2019 the original author or authors.
2
+ * Copyright 2008-2020 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
18
18
import java .io .IOException ;
19
19
import java .io .InputStream ;
20
20
import java .io .OutputStream ;
21
+ import java .util .Arrays ;
22
+ import java .util .Collection ;
23
+ import java .util .Collections ;
21
24
import java .util .Date ;
22
25
import java .util .HashMap ;
26
+ import java .util .HashSet ;
23
27
import java .util .Map ;
28
+ import java .util .Set ;
24
29
30
+ import com .fasterxml .jackson .annotation .JacksonAnnotation ;
25
31
import com .fasterxml .jackson .annotation .JsonIgnore ;
32
+ import com .fasterxml .jackson .annotation .JsonTypeInfo ;
26
33
import com .fasterxml .jackson .core .JsonParser ;
27
34
import com .fasterxml .jackson .core .type .TypeReference ;
35
+ import com .fasterxml .jackson .databind .DatabindContext ;
36
+ import com .fasterxml .jackson .databind .DeserializationConfig ;
28
37
import com .fasterxml .jackson .databind .DeserializationContext ;
29
38
import com .fasterxml .jackson .databind .DeserializationFeature ;
39
+ import com .fasterxml .jackson .databind .JavaType ;
30
40
import com .fasterxml .jackson .databind .JsonNode ;
31
41
import com .fasterxml .jackson .databind .MapperFeature ;
32
42
import com .fasterxml .jackson .databind .ObjectMapper ;
33
-
43
+ import com . fasterxml . jackson . databind . cfg . MapperConfig ;
34
44
import 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 ;
35
50
import com .fasterxml .jackson .databind .module .SimpleModule ;
51
+
36
52
import org .springframework .batch .core .JobParameter ;
37
53
import org .springframework .batch .core .JobParameters ;
38
54
import org .springframework .batch .core .repository .ExecutionContextSerializer ;
55
+ import org .springframework .core .annotation .AnnotationUtils ;
39
56
import org .springframework .util .Assert ;
40
57
41
58
/**
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>
43
95
*
44
96
* @author Marten Deinum
45
97
* @author Mahmoud Ben Hassine
@@ -55,7 +107,8 @@ public Jackson2ExecutionContextStringSerializer() {
55
107
this .objectMapper = new ObjectMapper ();
56
108
this .objectMapper .configure (MapperFeature .DEFAULT_VIEW_INCLUSION , false );
57
109
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 ());
59
112
this .objectMapper .registerModule (new JobParametersModule ());
60
113
}
61
114
@@ -141,4 +194,158 @@ public JobParameter deserialize(JsonParser parser, DeserializationContext contex
141
194
142
195
}
143
196
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
+
144
351
}
0 commit comments