Skip to content

Commit ee4e790

Browse files
sdeleuzebclozel
authored andcommitted
Add trusted packages to MappingJackson2MessageConverter
This commit introduces trusted packages, specified via the related setter for untrusted use cases. It allows explicit configuration of which Java packages are allowed to be deserialized. Closes gh-36792
1 parent ce90e4b commit ee4e790

2 files changed

Lines changed: 120 additions & 0 deletions

File tree

spring-jms/src/main/java/org/springframework/jms/support/converter/MappingJackson2MessageConverter.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.io.OutputStreamWriter;
2222
import java.io.StringWriter;
2323
import java.io.UnsupportedEncodingException;
24+
import java.util.Arrays;
2425
import java.util.HashMap;
2526
import java.util.Map;
2627

@@ -49,6 +50,8 @@
4950
* {@link #setTargetType targetType} is set to {@link MessageType#TEXT}.
5051
* Converts from a {@link TextMessage} or {@link BytesMessage} to an object.
5152
*
53+
* <p>For untrusted environments, use {@link #setTrustedPackages(String...)}.
54+
*
5255
* <p>It customizes Jackson's default properties with the following ones:
5356
* <ul>
5457
* <li>{@link MapperFeature#DEFAULT_VIEW_INCLUSION} is disabled</li>
@@ -59,6 +62,7 @@
5962
* @author Dave Syer
6063
* @author Juergen Hoeller
6164
* @author Stephane Nicoll
65+
* @author Sebastien Deleuze
6266
* @since 3.1.4
6367
*/
6468
public class MappingJackson2MessageConverter implements SmartMessageConverter, BeanClassLoaderAware {
@@ -86,12 +90,16 @@ public class MappingJackson2MessageConverter implements SmartMessageConverter, B
8690

8791
private final Map<Class<?>, String> classIdMappings = new HashMap<>();
8892

93+
@Nullable
94+
private String[] trustedPackages;
95+
8996
@Nullable
9097
private ClassLoader beanClassLoader;
9198

9299

93100
/**
94101
* Construct a {@code MappingJackson2MessageConverter} with a default {@link ObjectMapper}.
102+
* @see #setTrustedPackages(String...)
95103
*/
96104
@SuppressWarnings("deprecation") // on Jackson 2.13: configure(MapperFeature, boolean)
97105
public MappingJackson2MessageConverter() {
@@ -104,12 +112,21 @@ public MappingJackson2MessageConverter() {
104112
* Construct a {@code MappingJackson2MessageConverter} with a custom {@link ObjectMapper}.
105113
* @param objectMapper the {@code ObjectMapper} to use
106114
* @since 6.1
115+
* @see #setTrustedPackages(String...)
107116
*/
108117
public MappingJackson2MessageConverter(ObjectMapper objectMapper) {
109118
Assert.notNull(objectMapper, "ObjectMapper must not be null");
110119
this.objectMapper = objectMapper;
111120
}
112121

122+
/**
123+
* Specify the trusted Java packages for deserialization.
124+
* @param trustedPackages the trusted Java packages for deserialization
125+
* @since 6.2.19
126+
*/
127+
public void setTrustedPackages(String... trustedPackages) {
128+
this.trustedPackages = trustedPackages.clone();
129+
}
113130

114131
/**
115132
* Set the {@code ObjectMapper} for this converter.
@@ -183,6 +200,23 @@ public void setTypeIdMappings(Map<String, Class<?>> typeIdMappings) {
183200
});
184201
}
185202

203+
private boolean isTrustedPackage(String requestedType) {
204+
if (this.trustedPackages != null) {
205+
String packageName = ClassUtils.getPackageName(requestedType);
206+
int lastBracketIndex = packageName.lastIndexOf('[');
207+
if (lastBracketIndex != -1 && packageName.length() > lastBracketIndex + 1 && packageName.charAt(lastBracketIndex + 1) == 'L') {
208+
packageName = packageName.substring(lastBracketIndex + 2);
209+
}
210+
for (String trustedPackage : this.trustedPackages) {
211+
if (packageName.equals(trustedPackage)) {
212+
return true;
213+
}
214+
}
215+
return false;
216+
}
217+
return true;
218+
}
219+
186220
@Override
187221
public void setBeanClassLoader(ClassLoader classLoader) {
188222
this.beanClassLoader = classLoader;
@@ -463,6 +497,10 @@ protected JavaType getJavaTypeForMessage(Message message) throws JMSException {
463497
if (mappedClass != null) {
464498
return this.objectMapper.constructType(mappedClass);
465499
}
500+
if (!isTrustedPackage(typeId)) {
501+
throw new MessageConversionException("The class '" + typeId + "' is not in the trusted packages: " +
502+
Arrays.toString(this.trustedPackages));
503+
}
466504
try {
467505
Class<?> typeClass = ClassUtils.forName(typeId, this.beanClassLoader);
468506
return this.objectMapper.constructType(typeClass);

spring-jms/src/test/java/org/springframework/jms/support/converter/MappingJackson2MessageConverterTests.java

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.springframework.lang.Nullable;
3838

3939
import static org.assertj.core.api.Assertions.assertThat;
40+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
4041
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
4142
import static org.mockito.ArgumentMatchers.any;
4243
import static org.mockito.ArgumentMatchers.isA;
@@ -48,6 +49,7 @@
4849
* @author Arjen Poutsma
4950
* @author Dave Syer
5051
* @author Stephane Nicoll
52+
* @author Sebastien Deleuze
5153
*/
5254
class MappingJackson2MessageConverterTests {
5355

@@ -133,6 +135,86 @@ void fromTextMessage() throws Exception {
133135
assertThat(unmarshalled).as("Invalid result").isEqualTo(result);
134136
}
135137

138+
@Test
139+
void fromTextMessageUntrusted() throws Exception {
140+
converter = new MappingJackson2MessageConverter();
141+
converter.setTrustedPackages("java.lang");
142+
converter.setTypeIdPropertyName("__typeid__");
143+
TextMessage textMessageMock = mock();
144+
145+
String text = "{\"foo\":\"bar\"}";
146+
given(textMessageMock.getStringProperty("__typeid__")).willReturn(MyBean.class.getName());
147+
given(textMessageMock.getText()).willReturn(text);
148+
149+
assertThatExceptionOfType(MessageConversionException.class)
150+
.isThrownBy(() -> converter.fromMessage(textMessageMock))
151+
.withMessageContaining("is not in the trusted packages");
152+
}
153+
154+
@Test
155+
void fromTextMessageTrusted() throws Exception {
156+
converter = new MappingJackson2MessageConverter();
157+
converter.setTrustedPackages("java.lang", "org.springframework.jms.support.converter");
158+
converter.setTypeIdPropertyName("__typeid__");
159+
TextMessage textMessageMock = mock();
160+
MyBean unmarshalled = new MyBean("bar");
161+
162+
String text = "{\"foo\":\"bar\"}";
163+
given(textMessageMock.getStringProperty("__typeid__")).willReturn(MyBean.class.getName());
164+
given(textMessageMock.getText()).willReturn(text);
165+
166+
MyBean result = (MyBean) converter.fromMessage(textMessageMock);
167+
assertThat(unmarshalled).as("Invalid result").isEqualTo(result);
168+
}
169+
170+
@Test
171+
void fromTextMessageTrustedEmpty() throws Exception {
172+
converter = new MappingJackson2MessageConverter();
173+
converter.setTrustedPackages();
174+
converter.setTypeIdPropertyName("__typeid__");
175+
TextMessage textMessageMock = mock();
176+
177+
String text = "{\"foo\":\"bar\"}";
178+
given(textMessageMock.getStringProperty("__typeid__")).willReturn(MyBean.class.getName());
179+
given(textMessageMock.getText()).willReturn(text);
180+
181+
assertThatExceptionOfType(MessageConversionException.class)
182+
.isThrownBy(() -> converter.fromMessage(textMessageMock))
183+
.withMessageContaining("is not in the trusted packages");
184+
}
185+
186+
@Test
187+
void fromTextMessageTrusted1DArray() throws Exception {
188+
converter = new MappingJackson2MessageConverter();
189+
converter.setTrustedPackages("org.springframework.jms.support.converter");
190+
converter.setTypeIdPropertyName("__typeid__");
191+
TextMessage textMessageMock = mock();
192+
MyBean[] unmarshalled = new MyBean[] { new MyBean("bar") };
193+
194+
String text = "[{\"foo\":\"bar\"}]";
195+
given(textMessageMock.getStringProperty("__typeid__")).willReturn("[L" + MyBean.class.getName() + ";");
196+
given(textMessageMock.getText()).willReturn(text);
197+
198+
MyBean[] result = (MyBean[]) converter.fromMessage(textMessageMock);
199+
assertThat(unmarshalled).as("Invalid result").isEqualTo(result);
200+
}
201+
202+
@Test
203+
void fromTextMessageTrusted2DArray() throws Exception {
204+
converter = new MappingJackson2MessageConverter();
205+
converter.setTrustedPackages("org.springframework.jms.support.converter");
206+
converter.setTypeIdPropertyName("__typeid__");
207+
TextMessage textMessageMock = mock();
208+
MyBean[][] unmarshalled = new MyBean[][] { { new MyBean("bar") } };
209+
210+
String text = "[[{\"foo\":\"bar\"}]]";
211+
given(textMessageMock.getStringProperty("__typeid__")).willReturn("[[L" + MyBean.class.getName() + ";");
212+
given(textMessageMock.getText()).willReturn(text);
213+
214+
MyBean[][] result = (MyBean[][]) converter.fromMessage(textMessageMock);
215+
assertThat(unmarshalled).as("Invalid result").isEqualTo(result);
216+
}
217+
136218
@Test
137219
void fromTextMessageWithUnknownProperty() throws Exception {
138220
TextMessage textMessageMock = mock();

0 commit comments

Comments
 (0)