Skip to content

Commit a0bcd7c

Browse files
committed
Add ability to amend trusted classes in Jackson2ExecutionContextStringSerializer
This commit adds the ability to specify additional trusted classes without having to provide a custom object mapper. Issue #3765
1 parent 15d24b3 commit a0bcd7c

File tree

2 files changed

+53
-11
lines changed

2 files changed

+53
-11
lines changed

spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/Jackson2ExecutionContextStringSerializer.java

+31-11
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Date;
2525
import java.util.HashMap;
2626
import java.util.HashSet;
27+
import java.util.LinkedHashSet;
2728
import java.util.Map;
2829
import java.util.Set;
2930

@@ -60,8 +61,9 @@
6061
*
6162
* By default, this implementation trusts a limited set of classes to be
6263
* 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:
64+
* and is safe to deserialize, you can add it to the base set of trusted classes
65+
* at {@link Jackson2ExecutionContextStringSerializer construction time} or provide
66+
* an explicit mapping using Jackson annotations, as shown in the following example:
6567
*
6668
* <pre class="code">
6769
* &#064;JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@@ -103,12 +105,19 @@ public class Jackson2ExecutionContextStringSerializer implements ExecutionContex
103105

104106
private ObjectMapper objectMapper;
105107

106-
public Jackson2ExecutionContextStringSerializer() {
108+
/**
109+
* Create a new {@link Jackson2ExecutionContextStringSerializer}.
110+
*
111+
* @param trustedClassNames fully qualified names of classes that are safe
112+
* to deserialize from the execution context and which should be added to the
113+
* default set of trusted classes.
114+
*/
115+
public Jackson2ExecutionContextStringSerializer(String... trustedClassNames) {
107116
this.objectMapper = new ObjectMapper();
108117
this.objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
109118
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
110119
this.objectMapper.configure(MapperFeature.BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES, true);
111-
this.objectMapper.setDefaultTyping(createTrustedDefaultTyping());
120+
this.objectMapper.setDefaultTyping(createTrustedDefaultTyping(trustedClassNames));
112121
this.objectMapper.registerModule(new JobParametersModule());
113122
}
114123

@@ -197,9 +206,10 @@ public JobParameter deserialize(JsonParser parser, DeserializationContext contex
197206
/**
198207
* Creates a TypeResolverBuilder that checks if a type is trusted.
199208
* @return a TypeResolverBuilder that checks if a type is trusted.
209+
* @param trustedClassNames array of fully qualified trusted class names
200210
*/
201-
private static TypeResolverBuilder<? extends TypeResolverBuilder> createTrustedDefaultTyping() {
202-
TypeResolverBuilder<? extends TypeResolverBuilder> result = new TrustedTypeResolverBuilder(ObjectMapper.DefaultTyping.NON_FINAL);
211+
private static TypeResolverBuilder<? extends TypeResolverBuilder> createTrustedDefaultTyping(String[] trustedClassNames) {
212+
TypeResolverBuilder<? extends TypeResolverBuilder> result = new TrustedTypeResolverBuilder(ObjectMapper.DefaultTyping.NON_FINAL, trustedClassNames);
203213
result = result.init(JsonTypeInfo.Id.CLASS, null);
204214
result = result.inclusion(JsonTypeInfo.As.PROPERTY);
205215
return result;
@@ -213,14 +223,18 @@ private static TypeResolverBuilder<? extends TypeResolverBuilder> createTrustedD
213223
*/
214224
static class TrustedTypeResolverBuilder extends ObjectMapper.DefaultTypeResolverBuilder {
215225

216-
TrustedTypeResolverBuilder(ObjectMapper.DefaultTyping defaultTyping) {
226+
private final String[] trustedClassNames;
227+
228+
TrustedTypeResolverBuilder(ObjectMapper.DefaultTyping defaultTyping, String[] trustedClassNames) {
217229
super(
218230
defaultTyping,
219231
//we do explicit validation in the TypeIdResolver
220232
BasicPolymorphicTypeValidator.builder()
221233
.allowIfSubType(Object.class)
222234
.build()
223235
);
236+
this.trustedClassNames =
237+
trustedClassNames != null ? Arrays.copyOf(trustedClassNames, trustedClassNames.length) : null;
224238
}
225239

226240
@Override
@@ -229,7 +243,7 @@ protected TypeIdResolver idResolver(MapperConfig<?> config,
229243
PolymorphicTypeValidator subtypeValidator,
230244
Collection<NamedType> subtypes, boolean forSer, boolean forDeser) {
231245
TypeIdResolver result = super.idResolver(config, baseType, subtypeValidator, subtypes, forSer, forDeser);
232-
return new TrustedTypeIdResolver(result);
246+
return new TrustedTypeIdResolver(result, this.trustedClassNames);
233247
}
234248
}
235249

@@ -284,10 +298,15 @@ static class TrustedTypeIdResolver implements TypeIdResolver {
284298
"org.springframework.batch.core.jsr.partition.JsrPartitionHandler$PartitionPlanState"
285299
)));
286300

301+
private final Set<String> trustedClassNames = new LinkedHashSet<>(TRUSTED_CLASS_NAMES);
302+
287303
private final TypeIdResolver delegate;
288304

289-
TrustedTypeIdResolver(TypeIdResolver delegate) {
305+
TrustedTypeIdResolver(TypeIdResolver delegate, String[] trustedClassNames) {
290306
this.delegate = delegate;
307+
if (trustedClassNames != null) {
308+
this.trustedClassNames.addAll(Arrays.asList(trustedClassNames));
309+
}
291310
}
292311

293312
@Override
@@ -328,12 +347,13 @@ public JavaType typeFromId(DatabindContext context, String id) throws IOExceptio
328347
return result;
329348
}
330349
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. " +
350+
"If you believe this class is safe to deserialize, you can add it to the base set of trusted classes " +
351+
"at construction time or provide an explicit mapping using Jackson annotations or a custom ObjectMapper. " +
332352
"If the serialization is only done by a trusted source, you can also enable default typing.");
333353
}
334354

335355
private boolean isTrusted(String id) {
336-
return TRUSTED_CLASS_NAMES.contains(id);
356+
return this.trustedClassNames.contains(id);
337357
}
338358

339359
@Override

spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/Jackson2ExecutionContextStringSerializerTests.java

+22
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
import java.io.IOException;
2121
import java.io.InputStream;
2222
import java.util.HashMap;
23+
import java.util.Locale;
2324
import java.util.Map;
2425

2526
import com.fasterxml.jackson.annotation.JsonTypeInfo;
27+
import org.junit.Assert;
2628
import org.junit.Before;
2729
import org.junit.Test;
2830

@@ -33,6 +35,7 @@
3335
/**
3436
* @author Marten Deinum
3537
* @author Michael Minella
38+
* @author Mahmoud Ben Hassine
3639
*/
3740
public class Jackson2ExecutionContextStringSerializerTests extends AbstractExecutionContextSerializerTests {
3841

@@ -73,6 +76,25 @@ public void mappedTypeTest() throws IOException {
7376
}
7477
}
7578

79+
@Test
80+
public void testAdditionalTrustedClass() throws IOException {
81+
// given
82+
Jackson2ExecutionContextStringSerializer serializer =
83+
new Jackson2ExecutionContextStringSerializer("java.util.Locale");
84+
Map<String, Object> context = new HashMap<>(1);
85+
context.put("locale", Locale.getDefault());
86+
87+
// when
88+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
89+
serializer.serialize(context, outputStream);
90+
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
91+
Map<String, Object> deserializedContext = serializer.deserialize(inputStream);
92+
93+
// then
94+
Locale locale = (Locale) deserializedContext.get("locale");
95+
Assert.assertNotNull(locale);
96+
}
97+
7698
@Override
7799
protected ExecutionContextSerializer getSerializer() {
78100
return this.serializer;

0 commit comments

Comments
 (0)