Skip to content

Commit 3f56b0e

Browse files
garyrussellartembilan
authored andcommitted
INT-4342: White List for Payload Deserializer
JIRA: https://jira.spring.io/browse/INT-4342 Use similar code to Spring AMQP to add white list support for Integration's use of the `DeserializingMessageConverter`; introduce the `WhiteListDeserializingMessageConverter`. Polishing Missed this change in PR. Fix XSD attribute # Conflicts: # spring-integration-core/src/main/java/org/springframework/integration/dsl/Transformers.java # spring-integration-core/src/test/java/org/springframework/integration/config/xml/PayloadDeserializingTransformerParserTests.java # spring-integration-core/src/test/java/org/springframework/integration/dsl/flows/IntegrationFlowTests.java # spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/JdbcChannelMessageStore.java # spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/channel/MessageRowMapper.java # spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/MongoDbMessageStore.java # spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/support/BinaryToMessageConverter.java # spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/support/MongoDbMessageBytesConverter.java # Conflicts: # spring-integration-core/src/main/java/org/springframework/integration/transformer/PayloadDeserializingTransformer.java # spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcMessageStore.java # spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/JdbcChannelMessageStore.java # spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/channel/MessageRowMapper.java # spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/MongoDbMessageStore.java # src/reference/asciidoc/transformer.adoc
1 parent b93ba90 commit 3f56b0e

File tree

14 files changed

+357
-41
lines changed

14 files changed

+357
-41
lines changed

spring-integration-core/src/main/java/org/springframework/integration/config/xml/PayloadDeserializingTransformerParser.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2017 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.
@@ -26,6 +26,7 @@
2626
* Parser for the 'payload-deserializing-transformer' element.
2727
*
2828
* @author Mark Fisher
29+
* @author Gary Russell
2930
*/
3031
public class PayloadDeserializingTransformerParser extends AbstractTransformerParser {
3132

@@ -37,6 +38,7 @@ protected String getTransformerClassName() {
3738
@Override
3839
protected void parseTransformer(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
3940
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "deserializer");
41+
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "white-list", "whiteListPatterns");
4042
}
4143

4244
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/*
2+
* Copyright 2002-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.integration.support.converter;
18+
19+
import java.io.ByteArrayInputStream;
20+
import java.io.IOException;
21+
import java.io.ObjectInputStream;
22+
import java.io.ObjectStreamClass;
23+
import java.util.Collections;
24+
import java.util.LinkedHashSet;
25+
import java.util.Set;
26+
27+
import org.springframework.beans.DirectFieldAccessor;
28+
import org.springframework.core.ConfigurableObjectInputStream;
29+
import org.springframework.core.NestedIOException;
30+
import org.springframework.core.convert.converter.Converter;
31+
import org.springframework.core.serializer.DefaultDeserializer;
32+
import org.springframework.core.serializer.Deserializer;
33+
import org.springframework.core.serializer.support.SerializationFailedException;
34+
import org.springframework.util.Assert;
35+
import org.springframework.util.PatternMatchUtils;
36+
37+
/**
38+
* A {@link Converter} that delegates to a
39+
* {@link org.springframework.core.serializer.Deserializer} to convert data in a byte
40+
* array to an object. By default, if using a {@link DefaultDeserializer} all
41+
* classes/packages are deserialized. If you receive data from untrusted sources, consider
42+
* adding trusted classes/packages using {@link #setWhiteListPatterns(String...)} or
43+
* {@link #addWhiteListPatterns(String...)}.
44+
*
45+
* @author Gary Russell
46+
* @author Mark Fisher
47+
* @author Juergen Hoeller
48+
* @since 4.2.13
49+
*/
50+
public class WhiteListDeserializingConverter implements Converter<byte[], Object> {
51+
52+
private final Deserializer<Object> deserializer;
53+
54+
private final ClassLoader defaultDeserializerClassLoader;
55+
56+
private final boolean usingDefaultDeserializer;
57+
58+
private final Set<String> whiteListPatterns = new LinkedHashSet<String>();
59+
60+
61+
/**
62+
* Create a {@code WhiteListDeserializingConverter} with default
63+
* {@link java.io.ObjectInputStream} configuration, using the "latest user-defined
64+
* ClassLoader".
65+
*/
66+
public WhiteListDeserializingConverter() {
67+
this(new DefaultDeserializer());
68+
}
69+
70+
/**
71+
* Create a {@code WhiteListDeserializingConverter} for using an
72+
* {@link java.io.ObjectInputStream} with the given {@code ClassLoader}.
73+
* @param classLoader the class loader to use for deserialization.
74+
*/
75+
public WhiteListDeserializingConverter(ClassLoader classLoader) {
76+
this(new DefaultDeserializer(classLoader));
77+
}
78+
79+
/**
80+
* Create a {@code WhiteListDeserializingConverter} that delegates to the provided
81+
* {@link Deserializer}.
82+
* @param deserializer the deserializer to use.
83+
*/
84+
public WhiteListDeserializingConverter(Deserializer<Object> deserializer) {
85+
Assert.notNull(deserializer, "Deserializer must not be null");
86+
this.deserializer = deserializer;
87+
if (deserializer instanceof DefaultDeserializer) {
88+
ClassLoader classLoader = null;
89+
try {
90+
classLoader = (ClassLoader) new DirectFieldAccessor(deserializer).getPropertyValue("classLoader");
91+
}
92+
catch (Exception e) {
93+
// no-op
94+
}
95+
this.defaultDeserializerClassLoader = classLoader;
96+
this.usingDefaultDeserializer = true;
97+
}
98+
else {
99+
this.defaultDeserializerClassLoader = null;
100+
this.usingDefaultDeserializer = false;
101+
}
102+
}
103+
104+
/**
105+
* Set simple patterns for allowable packages/classes for deserialization.
106+
* The patterns will be applied in order until a match is found.
107+
* A class can be fully qualified or a wildcard '*' is allowed at the
108+
* beginning or end of the class name.
109+
* Examples: {@code com.foo.*}, {@code *.MyClass}.
110+
* @param whiteListPatterns the patterns.
111+
*/
112+
public void setWhiteListPatterns(String... whiteListPatterns) {
113+
this.whiteListPatterns.clear();
114+
Collections.addAll(this.whiteListPatterns, whiteListPatterns);
115+
}
116+
117+
/**
118+
* Add package/class patterns to the white list.
119+
* @param patterns the patterns to add.
120+
* @see #setWhiteListPatterns(String...)
121+
*/
122+
public void addWhiteListPatterns(String... patterns) {
123+
Collections.addAll(this.whiteListPatterns, patterns);
124+
}
125+
126+
@Override
127+
public Object convert(byte[] source) {
128+
ByteArrayInputStream byteStream = new ByteArrayInputStream(source);
129+
try {
130+
if (this.usingDefaultDeserializer) {
131+
return deserialize(byteStream);
132+
}
133+
else {
134+
return this.deserializer.deserialize(byteStream);
135+
}
136+
}
137+
catch (Throwable ex) {
138+
throw new SerializationFailedException("Failed to deserialize payload. " +
139+
"Is the byte array a result of corresponding serialization for " +
140+
this.deserializer.getClass().getSimpleName() + "?", ex);
141+
}
142+
}
143+
144+
protected Object deserialize(ByteArrayInputStream inputStream) throws IOException {
145+
try {
146+
ObjectInputStream objectInputStream = new ConfigurableObjectInputStream(inputStream,
147+
this.defaultDeserializerClassLoader) {
148+
149+
@Override
150+
protected Class<?> resolveClass(ObjectStreamClass classDesc)
151+
throws IOException, ClassNotFoundException {
152+
Class<?> clazz = super.resolveClass(classDesc);
153+
checkWhiteList(clazz);
154+
return clazz;
155+
}
156+
157+
};
158+
return objectInputStream.readObject();
159+
}
160+
catch (ClassNotFoundException ex) {
161+
throw new NestedIOException("Failed to deserialize object type", ex);
162+
}
163+
}
164+
165+
protected void checkWhiteList(Class<?> clazz) throws IOException {
166+
if (this.whiteListPatterns.isEmpty()) {
167+
return;
168+
}
169+
if (clazz.isArray() || clazz.isPrimitive() || clazz.equals(String.class)
170+
|| Number.class.isAssignableFrom(clazz)) {
171+
return;
172+
}
173+
String className = clazz.getName();
174+
for (String pattern : this.whiteListPatterns) {
175+
if (PatternMatchUtils.simpleMatch(pattern, className)) {
176+
return;
177+
}
178+
}
179+
throw new SecurityException("Attempt to deserialize unauthorized " + clazz);
180+
}
181+
182+
}

spring-integration-core/src/main/java/org/springframework/integration/transformer/PayloadDeserializingTransformer.java

+34-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2010 the original author or authors.
2+
* Copyright 2002-2017 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.
@@ -16,31 +16,55 @@
1616

1717
package org.springframework.integration.transformer;
1818

19+
import org.springframework.core.convert.converter.Converter;
1920
import org.springframework.core.serializer.Deserializer;
20-
import org.springframework.core.serializer.support.DeserializingConverter;
21+
import org.springframework.integration.support.converter.WhiteListDeserializingConverter;
22+
import org.springframework.util.Assert;
2123

2224
/**
23-
* Transformer that deserializes the inbound byte array payload to an object by delegating to a
24-
* Converter&lt;byte[], Object&gt;. Default delegate is a {@link DeserializingConverter} using
25-
* Java serialization.
25+
* Transformer that deserializes the inbound byte array payload to an object by delegating
26+
* to a Converter&lt;byte[], Object&gt;. Default delegate is a
27+
* {@link WhiteListDeserializingConverter} using Java serialization.
2628
*
27-
* <p>The byte array payload must be a result of equivalent serialization.
29+
* <p>
30+
* The byte array payload must be a result of equivalent serialization.
2831
*
2932
* @author Mark Fisher
3033
* @author Gary Russell
3134
* @since 1.0.1
3235
*/
3336
public class PayloadDeserializingTransformer extends PayloadTypeConvertingTransformer<byte[], Object> {
3437

38+
39+
public PayloadDeserializingTransformer() {
40+
doSetConverter(new WhiteListDeserializingConverter());
41+
}
42+
43+
private void doSetConverter(Converter<byte[], Object> converter) {
44+
this.converter = converter;
45+
}
46+
3547
public void setDeserializer(Deserializer<Object> deserializer) {
36-
this.setConverter(new DeserializingConverter(deserializer));
48+
setConverter(new WhiteListDeserializingConverter(deserializer));
49+
}
50+
51+
/**
52+
* When using a {@link WhiteListDeserializingConverter} (the default) add patterns
53+
* for packages/classes that are allowed to be deserialized.
54+
* A class can be fully qualified or a wildcard '*' is allowed at the
55+
* beginning or end of the class name.
56+
* Examples: {@code com.foo.*}, {@code *.MyClass}.
57+
* @param patterns the patterns.
58+
* @since 4.2.13
59+
*/
60+
public void setWhiteListPatterns(String... patterns) {
61+
Assert.isTrue(this.converter instanceof WhiteListDeserializingConverter,
62+
"Patterns can only be provided when using a 'WhiteListDeserializingConverter'");
63+
((WhiteListDeserializingConverter) this.converter).setWhiteListPatterns(patterns);
3764
}
3865

3966
@Override
4067
protected Object transformPayload(byte[] payload) throws Exception {
41-
if (this.converter == null) {
42-
this.setConverter(new DeserializingConverter());
43-
}
4468
return this.converter.convert(payload);
4569
}
4670

spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-4.2.xsd

+10-1
Original file line numberDiff line numberDiff line change
@@ -2639,7 +2639,7 @@
26392639
<xsd:choice minOccurs="0" maxOccurs="unbounded">
26402640
<xsd:element ref="poller" />
26412641
</xsd:choice>
2642-
<xsd:attribute name="deserializer" use="optional">
2642+
<xsd:attribute name="deserializer">
26432643
<xsd:annotation>
26442644
<xsd:documentation>
26452645
Reference to a Deserializer instance to convert from a byte array to an object.
@@ -2653,6 +2653,15 @@
26532653
</xsd:appinfo>
26542654
</xsd:annotation>
26552655
</xsd:attribute>
2656+
<xsd:attribute name="white-list">
2657+
<xsd:annotation>
2658+
<xsd:documentation>
2659+
When using the default Deserializer, a list of package/class patterns indicating
2660+
classes that are allowed to be deserialized. Consider providing this if you receive
2661+
data from untrusted sources. Example: "com.mycom.*, com.yourcom.*".
2662+
</xsd:documentation>
2663+
</xsd:annotation>
2664+
</xsd:attribute>
26562665
<xsd:attribute name="id" type="xsd:string" />
26572666
</xsd:complexType>
26582667

spring-integration-core/src/test/java/org/springframework/integration/config/xml/PayloadDeserializingTransformerParserTests-context.xml

+6-3
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,17 @@
1919
<queue capacity="1"/>
2020
</channel>
2121

22-
<payload-deserializing-transformer input-channel="directInput" output-channel="output"/>
22+
<payload-deserializing-transformer id="direct" input-channel="directInput" output-channel="output"
23+
white-list="*" />
2324

2425
<payload-deserializing-transformer input-channel="queueInput" output-channel="output">
2526
<poller fixed-delay="10000"/>
2627
</payload-deserializing-transformer>
2728

28-
<payload-deserializing-transformer input-channel="customDeserializerInput" output-channel="output" deserializer="customDeserializer"/>
29+
<payload-deserializing-transformer input-channel="customDeserializerInput" output-channel="output"
30+
deserializer="customDeserializer"/>
2931

30-
<beans:bean id="customDeserializer" class="org.springframework.integration.config.xml.PayloadDeserializingTransformerParserTests$TestDeserializer"/>
32+
<beans:bean id="customDeserializer"
33+
class="org.springframework.integration.config.xml.PayloadDeserializingTransformerParserTests$TestDeserializer"/>
3134

3235
</beans:beans>

spring-integration-core/src/test/java/org/springframework/integration/config/xml/PayloadDeserializingTransformerParserTests.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616

1717
package org.springframework.integration.config.xml;
1818

19+
import static org.hamcrest.Matchers.equalTo;
1920
import static org.junit.Assert.assertEquals;
2021
import static org.junit.Assert.assertNotNull;
22+
import static org.junit.Assert.assertThat;
2123
import static org.junit.Assert.assertTrue;
2224

2325
import java.io.ByteArrayOutputStream;
@@ -26,17 +28,21 @@
2628
import java.io.InputStreamReader;
2729
import java.io.ObjectOutputStream;
2830
import java.io.Serializable;
31+
import java.util.Set;
2932

3033
import org.junit.Test;
3134
import org.junit.runner.RunWith;
3235

3336
import org.springframework.beans.factory.annotation.Autowired;
37+
import org.springframework.beans.factory.annotation.Qualifier;
3438
import org.springframework.core.serializer.Deserializer;
39+
import org.springframework.integration.test.util.TestUtils;
40+
import org.springframework.integration.transformer.MessageTransformationException;
3541
import org.springframework.messaging.Message;
3642
import org.springframework.messaging.MessageChannel;
43+
import org.springframework.messaging.MessageHandler;
3744
import org.springframework.messaging.PollableChannel;
3845
import org.springframework.messaging.support.GenericMessage;
39-
import org.springframework.integration.transformer.MessageTransformationException;
4046
import org.springframework.test.context.ContextConfiguration;
4147
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
4248
import org.springframework.util.FileCopyUtils;
@@ -60,6 +66,10 @@ public class PayloadDeserializingTransformerParserTests {
6066
@Autowired
6167
private PollableChannel output;
6268

69+
@Autowired
70+
@Qualifier("direct.handler")
71+
private MessageHandler handler;
72+
6373

6474
@Test
6575
public void directChannelWithSerializedStringMessage() throws Exception {
@@ -69,6 +79,10 @@ public void directChannelWithSerializedStringMessage() throws Exception {
6979
assertNotNull(result);
7080
assertTrue(result.getPayload() instanceof String);
7181
assertEquals("foo", result.getPayload());
82+
Set<?> patterns = TestUtils.getPropertyValue(this.handler, "transformer.converter.whiteListPatterns",
83+
Set.class);
84+
assertThat(patterns.size(), equalTo(1));
85+
assertThat(patterns.iterator().next(), equalTo("*"));
7286
}
7387

7488
@Test

0 commit comments

Comments
 (0)