Skip to content

Commit

Permalink
AMQP-766: Deserialization in Message.toString()
Browse files Browse the repository at this point in the history
JIRA: https://jira.spring.io/browse/AMQP-766

Add white list for classes/packages that can be deserialized in Message.toString().

__cherry-pick to 1.7.x, 1.6.x, 1.5.x__

* Polishing - checkstyle and PR Comments
  • Loading branch information
garyrussell authored and artembilan committed Sep 6, 2017
1 parent 06bc198 commit 36e5599
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import java.nio.charset.Charset;
import java.util.Arrays;

import org.springframework.amqp.utils.SerializationUtils;
import org.springframework.amqp.support.converter.SerializerMessageConverter;

/**
* The 0-8 and 0-9-1 AMQP specifications do not define an Message class or interface. Instead, when performing an
Expand All @@ -42,6 +42,12 @@ public class Message implements Serializable {

private static final String ENCODING = Charset.defaultCharset().name();

private static final SerializerMessageConverter SERIALIZER_MESSAGE_CONVERTER = new SerializerMessageConverter();

static {
SERIALIZER_MESSAGE_CONVERTER.setWhiteListPatterns(Arrays.asList("java.util.*", "java.lang.*"));
}

private final MessageProperties messageProperties;

private final byte[] body;
Expand All @@ -51,6 +57,22 @@ public Message(byte[] body, MessageProperties messageProperties) { //NOSONAR
this.messageProperties = messageProperties;
}

/**
* Add patterns to the white list of permissable package/class name patterns for
* deserialization in {@link #toString()}.
* The patterns will be applied in order until a match is found.
* A class can be fully qualified or a wildcard '*' is allowed at the
* beginning or end of the class name.
* Examples: {@code com.foo.*}, {@code *.MyClass}.
* By default, only {@code java.util} and {@code java.lang} classes will be
* deserialized.
* @param patterns the patterns.
* @since 1.5.7
*/
public static void addWhiteListPatterns(String... patterns) {
SERIALIZER_MESSAGE_CONVERTER.addWhiteListPatterns(patterns);
}

public byte[] getBody() {
return this.body; //NOSONAR
}
Expand Down Expand Up @@ -78,7 +100,7 @@ private String getBodyContentAsString() {
try {
String contentType = (this.messageProperties != null) ? this.messageProperties.getContentType() : null;
if (MessageProperties.CONTENT_TYPE_SERIALIZED_OBJECT.equals(contentType)) {
return SerializationUtils.deserialize(this.body).toString();
return SERIALIZER_MESSAGE_CONVERTER.fromMessage(this).toString();
}
if (MessageProperties.CONTENT_TYPE_TEXT_PLAIN.equals(contentType)
|| MessageProperties.CONTENT_TYPE_JSON.equals(contentType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.springframework.amqp.support.converter;

import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
Expand Down Expand Up @@ -47,6 +48,16 @@ public void setWhiteListPatterns(List<String> whiteListPatterns) {
this.whiteListPatterns.addAll(whiteListPatterns);
}

/**
* Add package/class patterns to the white list.
* @param patterns the patterns to add.
* @since 1.5.7
* @see #setWhiteListPatterns(List)
*/
public void addWhiteListPatterns(String... patterns) {
Collections.addAll(this.whiteListPatterns, patterns);
}

protected void checkWhiteList(Class<?> clazz) throws IOException {
if (this.whiteListPatterns.isEmpty()) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,23 @@

package org.springframework.amqp.core;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Collections;
import java.util.Date;

import org.junit.Test;

import org.springframework.amqp.support.converter.SimpleMessageConverter;
import org.springframework.amqp.utils.SerializationUtils;

/**
Expand Down Expand Up @@ -86,4 +92,27 @@ public void serialization() throws Exception {
assertEquals(new String(message.getBody()), new String(out.getBody()));
assertEquals(message.toString(), out.toString());
}

@Test
public void fooNotDeserialized() {
Message message = new SimpleMessageConverter().toMessage(new Foo(), new MessageProperties());
assertThat(message.toString(), not(containsString("aFoo")));
Message listMessage = new SimpleMessageConverter().toMessage(Collections.singletonList(new Foo()),
new MessageProperties());
assertThat(listMessage.toString(), not(containsString("aFoo")));
Message.addWhiteListPatterns(Foo.class.getName());
assertThat(message.toString(), containsString("aFoo"));
assertThat(listMessage.toString(), containsString("aFoo"));
}

@SuppressWarnings("serial")
public static class Foo implements Serializable {

@Override
public String toString() {
return "aFoo";
}

}

}
12 changes: 11 additions & 1 deletion src/reference/asciidoc/amqp.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ If you are not, then have a look at the resources listed in <<resources>>

===== Message

The 0-8 and 0-9-1 AMQP specifications do not define a Message class or interface.
The 0-9-1 AMQP specification does not define a Message class or interface.
Instead, when performing an operation such as `basicPublish()`, the content is passed as a byte-array argument and additional properties are passed in as separate arguments.
Spring AMQP defines a Message class as part of a more general AMQP domain model representation.
The purpose of the Message class is to simply encapsulate the body and properties within a single instance so that the API can in turn be simpler.
Expand Down Expand Up @@ -55,6 +55,16 @@ The `MessageProperties` interface defines several common properties such as 'mes
Those properties can also be extended with user-defined 'headers' by calling the `setHeader(String
key, Object value)` method.

[IMPORTANT]
----
Starting with versions `1.5.7, 1.6.11, 1.7.4, 2.0.0`, if a message body is a serialized `Serializable` java object, it is no longer deserialized (by default) when performing `toString()` operations (such as in log messages).
This is to prevent unsafe deserialization.
By default, only `java.util` and `java.lang` classes are deserialized.
To revert to the previous behavior, you can add allowable class/package patterns by invoking `Message.addWhiteListPatterns(...)`.
A simple `*` wildcard is supported, for example `com.foo.*, *.MyClass`.
Bodies that cannot be deserialized will be represented by `byte[<size>]` in log messages.
----

===== Exchange

The `Exchange` interface represents an AMQP Exchange, which is what a Message Producer sends to.
Expand Down

0 comments on commit 36e5599

Please sign in to comment.