ARTEMIS-604 - Message Serialization Improvement

- JMS and RA fixes
This commit is contained in:
Howard Gao 2016-08-01 09:59:59 +08:00 committed by Martyn Taylor
parent d88ede9e3a
commit 0535218cfc
32 changed files with 1483 additions and 61 deletions

View File

@ -25,23 +25,79 @@ import java.lang.reflect.Proxy;
import java.security.AccessController; import java.security.AccessController;
import java.security.PrivilegedActionException; import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction; import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.List;
public class ObjectInputStreamWithClassLoader extends ObjectInputStream { public class ObjectInputStreamWithClassLoader extends ObjectInputStream {
// Constants ------------------------------------------------------------------------------------ // Constants ------------------------------------------------------------------------------------
/**
* Value used to indicate that all classes should be white or black listed,
*/
public static final String CATCH_ALL_WILDCARD = "*";
public static final String WHITELIST_PROPERTY = "org.apache.activemq.artemis.jms.deserialization.whitelist";
public static final String BLACKLIST_PROPERTY = "org.apache.activemq.artemis.jms.deserialization.blacklist";
// Attributes ----------------------------------------------------------------------------------- // Attributes -----------------------------------------------------------------------------------
private List<String> whiteList = new ArrayList<>();
private List<String> blackList = new ArrayList<>();
// Static --------------------------------------------------------------------------------------- // Static ---------------------------------------------------------------------------------------
// Constructors --------------------------------------------------------------------------------- // Constructors ---------------------------------------------------------------------------------
public ObjectInputStreamWithClassLoader(final InputStream in) throws IOException { public ObjectInputStreamWithClassLoader(final InputStream in) throws IOException {
super(in); super(in);
String whiteList = System.getProperty(WHITELIST_PROPERTY, null);
setWhiteList(whiteList);
String blackList = System.getProperty(BLACKLIST_PROPERTY, null);
setBlackList(blackList);
} }
// Public --------------------------------------------------------------------------------------- // Public ---------------------------------------------------------------------------------------
/**
* @return the whiteList configured on this policy instance.
*/
public String getWhiteList() {
return StringUtil.joinStringList(whiteList, ",");
}
/**
* @return the blackList configured on this policy instance.
*/
public String getBlackList() {
return StringUtil.joinStringList(blackList, ",");
}
/**
* Replaces the currently configured whiteList with a comma separated
* string containing the new whiteList. Null or empty string denotes
* no whiteList entries, {@value #CATCH_ALL_WILDCARD} indicates that
* all classes are whiteListed.
*
* @param whiteList the whiteList that this policy is configured to recognize.
*/
public void setWhiteList(String whiteList) {
this.whiteList = StringUtil.splitStringList(whiteList, ",");
}
/**
* Replaces the currently configured blackList with a comma separated
* string containing the new blackList. Null or empty string denotes
* no blacklist entries, {@value #CATCH_ALL_WILDCARD} indicates that
* all classes are blacklisted.
*
* @param blackList the blackList that this policy is configured to recognize.
*/
public void setBlackList(String blackList) {
this.blackList = StringUtil.splitStringList(blackList, ",");
}
// Package protected ---------------------------------------------------------------------------- // Package protected ----------------------------------------------------------------------------
// Protected ------------------------------------------------------------------------------------ // Protected ------------------------------------------------------------------------------------
@ -97,14 +153,13 @@ public class ObjectInputStreamWithClassLoader extends ObjectInputStream {
Class clazz = Class.forName(name, false, loader); Class clazz = Class.forName(name, false, loader);
// sanity check only.. if a classLoader can't find a clazz, it will throw an exception // sanity check only.. if a classLoader can't find a clazz, it will throw an exception
if (clazz == null) { if (clazz == null) {
return super.resolveClass(desc); clazz = super.resolveClass(desc);
}
else {
return clazz;
} }
return checkSecurity(clazz);
} }
catch (ClassNotFoundException e) { catch (ClassNotFoundException e) {
return super.resolveClass(desc); return checkSecurity(super.resolveClass(desc));
} }
} }
@ -130,7 +185,7 @@ public class ObjectInputStreamWithClassLoader extends ObjectInputStream {
classObjs[i] = cl; classObjs[i] = cl;
} }
try { try {
return Proxy.getProxyClass(hasNonPublicInterface ? nonPublicLoader : latestLoader, classObjs); return checkSecurity(Proxy.getProxyClass(hasNonPublicInterface ? nonPublicLoader : latestLoader, classObjs));
} }
catch (IllegalArgumentException e) { catch (IllegalArgumentException e) {
throw new ClassNotFoundException(null, e); throw new ClassNotFoundException(null, e);
@ -156,6 +211,81 @@ public class ObjectInputStreamWithClassLoader extends ObjectInputStream {
} }
} }
private Class<?> checkSecurity(Class<?> clazz) throws ClassNotFoundException {
Class<?> target = clazz;
while (target.isArray()) {
target = target.getComponentType();
}
while (target.isAnonymousClass() || target.isLocalClass()) {
target = target.getEnclosingClass();
}
if (!target.isPrimitive()) {
if (!isTrustedType(target)) {
throw new ClassNotFoundException("Forbidden " + clazz + "! " +
"This class is not trusted to be deserialized under the current configuration. " +
"Please refer to the documentation for more information on how to configure trusted classes.");
}
}
return clazz;
}
private boolean isTrustedType(Class<?> clazz) {
if (clazz == null) {
return true;
}
String className = clazz.getCanonicalName();
if (className == null) {
// Shouldn't happen as we pre-processed things, but just in case..
className = clazz.getName();
}
for (String blackListEntry : blackList) {
if (CATCH_ALL_WILDCARD.equals(blackListEntry)) {
return false;
}
else if (isClassOrPackageMatch(className, blackListEntry)) {
return false;
}
}
for (String whiteListEntry : whiteList) {
if (CATCH_ALL_WILDCARD.equals(whiteListEntry)) {
return true;
}
else if (isClassOrPackageMatch(className, whiteListEntry)) {
return true;
}
}
// Failing outright rejection or allow from above
// reject only if the whiteList is not empty.
return whiteList.size() == 0;
}
private boolean isClassOrPackageMatch(String className, String listEntry) {
if (className == null) {
return false;
}
// Check if class is an exact match of the entry
if (className.equals(listEntry)) {
return true;
}
// Check if class is from a [sub-]package matching the entry
int entryLength = listEntry.length();
if (className.length() > entryLength && className.startsWith(listEntry) && '.' == className.charAt(entryLength)) {
return true;
}
return false;
}
// Inner classes -------------------------------------------------------------------------------- // Inner classes --------------------------------------------------------------------------------
} }

View File

@ -0,0 +1,58 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public class StringUtil {
/**
* Convert a list of Strings into a single String
* @param strList the string list
* @param delimit the delimiter used to separate each string entry in the list
* @return the converted string
*/
public static String joinStringList(List<String> strList, String delimit) {
Iterator<String> entries = strList.iterator();
StringBuilder builder = new StringBuilder();
while (entries.hasNext()) {
builder.append(entries.next());
if (entries.hasNext()) {
builder.append(delimit);
}
}
return builder.toString();
}
/**
* Convert a String into a list of String
* @param strList the String
* @param delimit used to separate items within the string.
* @return the string list
*/
public static List<String> splitStringList(String strList, String delimit) {
ArrayList<String> list = new ArrayList<>();
if (strList != null && !strList.isEmpty()) {
list.addAll(Arrays.asList(strList.split(delimit)));
}
return list;
}
}

View File

@ -0,0 +1,56 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.util;
import org.apache.activemq.artemis.utils.StringUtil;
import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
public class StringUtilTest extends Assert {
@Test
public void testJoinStringList() throws Exception {
List<String> strList = new ArrayList<>();
strList.add("a");
strList.add("bc");
strList.add("def");
String result = StringUtil.joinStringList(strList, ",");
assertEquals("a,bc,def", result);
List<String> newList = StringUtil.splitStringList(result, ",");
assertEquals(strList.size(), newList.size());
String result2 = StringUtil.joinStringList(newList, ",");
assertEquals(result, result2);
}
@Test
public void testSplitStringList() throws Exception {
String listStr = "white,blue,yellow,green";
List<String> result = StringUtil.splitStringList(listStr, ",");
assertEquals(4, result.size());
assertEquals("white", result.get(0));
assertEquals("blue", result.get(1));
assertEquals("yellow", result.get(2));
assertEquals("green", result.get(3));
String result2 = StringUtil.joinStringList(result, ",");
assertEquals(listStr, result2);
}
}

View File

@ -130,15 +130,20 @@ public class ActiveMQConnection extends ActiveMQConnectionForContextImpl impleme
private ActiveMQConnectionFactory factoryReference; private ActiveMQConnectionFactory factoryReference;
private final ConnectionFactoryOptions options;
// Constructors --------------------------------------------------------------------------------- // Constructors ---------------------------------------------------------------------------------
public ActiveMQConnection(final String username, public ActiveMQConnection(final ConnectionFactoryOptions options,
final String username,
final String password, final String password,
final int connectionType, final int connectionType,
final String clientID, final String clientID,
final int dupsOKBatchSize, final int dupsOKBatchSize,
final int transactionBatchSize, final int transactionBatchSize,
final ClientSessionFactory sessionFactory) { final ClientSessionFactory sessionFactory) {
this.options = options;
this.username = username; this.username = username;
this.password = password; this.password = password;
@ -651,10 +656,10 @@ public class ActiveMQConnection extends ActiveMQConnectionForContextImpl impleme
ClientSession session, ClientSession session,
int type) { int type) {
if (isXA) { if (isXA) {
return new ActiveMQXASession(this, transacted, true, acknowledgeMode, session, type); return new ActiveMQXASession(options, this, transacted, true, acknowledgeMode, session, type);
} }
else { else {
return new ActiveMQSession(this, transacted, false, acknowledgeMode, session, type); return new ActiveMQSession(options, this, transacted, false, acknowledgeMode, session, type);
} }
} }
@ -693,6 +698,14 @@ public class ActiveMQConnection extends ActiveMQConnectionForContextImpl impleme
return started; return started;
} }
public String getDeserializationBlackList() {
return this.factoryReference.getDeserializationBlackList();
}
public String getDeserializationWhiteList() {
return this.factoryReference.getDeserializationWhiteList();
}
// Inner classes -------------------------------------------------------------------------------- // Inner classes --------------------------------------------------------------------------------
private static class JMSFailureListener implements SessionFailureListener { private static class JMSFailureListener implements SessionFailureListener {

View File

@ -61,7 +61,7 @@ import org.apache.activemq.artemis.utils.ClassloadingUtil;
* <p>ActiveMQ Artemis implementation of a JMS ConnectionFactory.</p> * <p>ActiveMQ Artemis implementation of a JMS ConnectionFactory.</p>
* <p>This connection factory will use defaults defined by {@link DefaultConnectionProperties}. * <p>This connection factory will use defaults defined by {@link DefaultConnectionProperties}.
*/ */
public class ActiveMQConnectionFactory implements Externalizable, Referenceable, ConnectionFactory, XAConnectionFactory, AutoCloseable { public class ActiveMQConnectionFactory implements ConnectionFactoryOptions, Externalizable, Referenceable, ConnectionFactory, XAConnectionFactory, AutoCloseable {
private ServerLocator serverLocator; private ServerLocator serverLocator;
@ -79,6 +79,10 @@ public class ActiveMQConnectionFactory implements Externalizable, Referenceable,
private String protocolManagerFactoryStr; private String protocolManagerFactoryStr;
private String deserializationBlackList;
private String deserializationWhiteList;
@Override @Override
public void writeExternal(ObjectOutput out) throws IOException { public void writeExternal(ObjectOutput out) throws IOException {
URI uri = toURI(); URI uri = toURI();
@ -150,6 +154,22 @@ public class ActiveMQConnectionFactory implements Externalizable, Referenceable,
} }
} }
public String getDeserializationBlackList() {
return deserializationBlackList;
}
public void setDeserializationBlackList(String blackList) {
this.deserializationBlackList = blackList;
}
public String getDeserializationWhiteList() {
return deserializationWhiteList;
}
public void setDeserializationWhiteList(String whiteList) {
this.deserializationWhiteList = whiteList;
}
@Override @Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
String url = in.readUTF(); String url = in.readUTF();
@ -744,24 +764,24 @@ public class ActiveMQConnectionFactory implements Externalizable, Referenceable,
if (isXA) { if (isXA) {
if (type == ActiveMQConnection.TYPE_GENERIC_CONNECTION) { if (type == ActiveMQConnection.TYPE_GENERIC_CONNECTION) {
connection = new ActiveMQXAConnection(username, password, type, clientID, dupsOKBatchSize, transactionBatchSize, factory); connection = new ActiveMQXAConnection(this, username, password, type, clientID, dupsOKBatchSize, transactionBatchSize, factory);
} }
else if (type == ActiveMQConnection.TYPE_QUEUE_CONNECTION) { else if (type == ActiveMQConnection.TYPE_QUEUE_CONNECTION) {
connection = new ActiveMQXAConnection(username, password, type, clientID, dupsOKBatchSize, transactionBatchSize, factory); connection = new ActiveMQXAConnection(this, username, password, type, clientID, dupsOKBatchSize, transactionBatchSize, factory);
} }
else if (type == ActiveMQConnection.TYPE_TOPIC_CONNECTION) { else if (type == ActiveMQConnection.TYPE_TOPIC_CONNECTION) {
connection = new ActiveMQXAConnection(username, password, type, clientID, dupsOKBatchSize, transactionBatchSize, factory); connection = new ActiveMQXAConnection(this, username, password, type, clientID, dupsOKBatchSize, transactionBatchSize, factory);
} }
} }
else { else {
if (type == ActiveMQConnection.TYPE_GENERIC_CONNECTION) { if (type == ActiveMQConnection.TYPE_GENERIC_CONNECTION) {
connection = new ActiveMQConnection(username, password, type, clientID, dupsOKBatchSize, transactionBatchSize, factory); connection = new ActiveMQConnection(this, username, password, type, clientID, dupsOKBatchSize, transactionBatchSize, factory);
} }
else if (type == ActiveMQConnection.TYPE_QUEUE_CONNECTION) { else if (type == ActiveMQConnection.TYPE_QUEUE_CONNECTION) {
connection = new ActiveMQConnection(username, password, type, clientID, dupsOKBatchSize, transactionBatchSize, factory); connection = new ActiveMQConnection(this, username, password, type, clientID, dupsOKBatchSize, transactionBatchSize, factory);
} }
else if (type == ActiveMQConnection.TYPE_TOPIC_CONNECTION) { else if (type == ActiveMQConnection.TYPE_TOPIC_CONNECTION) {
connection = new ActiveMQConnection(username, password, type, clientID, dupsOKBatchSize, transactionBatchSize, factory); connection = new ActiveMQConnection(this, username, password, type, clientID, dupsOKBatchSize, transactionBatchSize, factory);
} }
} }

View File

@ -121,6 +121,10 @@ public class ActiveMQMessage implements javax.jms.Message {
} }
public static ActiveMQMessage createMessage(final ClientMessage message, final ClientSession session) { public static ActiveMQMessage createMessage(final ClientMessage message, final ClientSession session) {
return createMessage(message, session, null);
}
public static ActiveMQMessage createMessage(final ClientMessage message, final ClientSession session, final ConnectionFactoryOptions options) {
int type = message.getType(); int type = message.getType();
ActiveMQMessage msg; ActiveMQMessage msg;
@ -142,7 +146,7 @@ public class ActiveMQMessage implements javax.jms.Message {
break; break;
} }
case ActiveMQObjectMessage.TYPE: { case ActiveMQObjectMessage.TYPE: {
msg = new ActiveMQObjectMessage(message, session); msg = new ActiveMQObjectMessage(message, session, options);
break; break;
} }
case ActiveMQStreamMessage.TYPE: // 6 case ActiveMQStreamMessage.TYPE: // 6
@ -202,7 +206,6 @@ public class ActiveMQMessage implements javax.jms.Message {
*/ */
protected ActiveMQMessage(final byte type, final ClientSession session) { protected ActiveMQMessage(final byte type, final ClientSession session) {
message = session.createMessage(type, true, 0, System.currentTimeMillis(), (byte) 4); message = session.createMessage(type, true, 0, System.currentTimeMillis(), (byte) 4);
} }
protected ActiveMQMessage(final ClientSession session) { protected ActiveMQMessage(final ClientSession session) {

View File

@ -31,6 +31,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQInterruptedException;
import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.api.core.client.ClientConsumer; import org.apache.activemq.artemis.api.core.client.ClientConsumer;
import org.apache.activemq.artemis.api.core.client.ClientMessage; import org.apache.activemq.artemis.api.core.client.ClientMessage;
import org.apache.activemq.artemis.api.core.client.ClientSession;
import org.apache.activemq.artemis.api.core.client.MessageHandler; import org.apache.activemq.artemis.api.core.client.MessageHandler;
import org.apache.activemq.artemis.api.jms.ActiveMQJMSConstants; import org.apache.activemq.artemis.api.jms.ActiveMQJMSConstants;
import org.apache.activemq.artemis.core.client.ActiveMQClientLogger; import org.apache.activemq.artemis.core.client.ActiveMQClientLogger;
@ -41,6 +42,8 @@ import org.apache.activemq.artemis.core.client.impl.ClientSessionInternal;
*/ */
public final class ActiveMQMessageConsumer implements QueueReceiver, TopicSubscriber { public final class ActiveMQMessageConsumer implements QueueReceiver, TopicSubscriber {
private final ConnectionFactoryOptions options;
private final ClientConsumer consumer; private final ClientConsumer consumer;
private MessageListener listener; private MessageListener listener;
@ -63,13 +66,16 @@ public final class ActiveMQMessageConsumer implements QueueReceiver, TopicSubscr
// Constructors -------------------------------------------------- // Constructors --------------------------------------------------
protected ActiveMQMessageConsumer(final ActiveMQConnection connection, protected ActiveMQMessageConsumer(final ConnectionFactoryOptions options,
final ActiveMQConnection connection,
final ActiveMQSession session, final ActiveMQSession session,
final ClientConsumer consumer, final ClientConsumer consumer,
final boolean noLocal, final boolean noLocal,
final ActiveMQDestination destination, final ActiveMQDestination destination,
final String selector, final String selector,
final SimpleString autoDeleteQueueName) throws JMSException { final SimpleString autoDeleteQueueName) throws JMSException {
this.options = options;
this.connection = connection; this.connection = connection;
this.session = session; this.session = session;
@ -107,7 +113,7 @@ public final class ActiveMQMessageConsumer implements QueueReceiver, TopicSubscr
public void setMessageListener(final MessageListener listener) throws JMSException { public void setMessageListener(final MessageListener listener) throws JMSException {
this.listener = listener; this.listener = listener;
coreListener = listener == null ? null : new JMSMessageListenerWrapper(connection, session, consumer, listener, ackMode); coreListener = listener == null ? null : new JMSMessageListenerWrapper(options, connection, session, consumer, listener, ackMode);
try { try {
consumer.setMessageHandler(coreListener); consumer.setMessageHandler(coreListener);
@ -211,8 +217,11 @@ public final class ActiveMQMessageConsumer implements QueueReceiver, TopicSubscr
ActiveMQMessage jmsMsg = null; ActiveMQMessage jmsMsg = null;
if (coreMessage != null) { if (coreMessage != null) {
boolean needSession = ackMode == Session.CLIENT_ACKNOWLEDGE || ackMode == ActiveMQJMSConstants.INDIVIDUAL_ACKNOWLEDGE; ClientSession coreSession = session.getCoreSession();
jmsMsg = ActiveMQMessage.createMessage(coreMessage, needSession ? session.getCoreSession() : null); boolean needSession = ackMode == Session.CLIENT_ACKNOWLEDGE ||
ackMode == ActiveMQJMSConstants.INDIVIDUAL_ACKNOWLEDGE ||
coreMessage.getType() == ActiveMQObjectMessage.TYPE;
jmsMsg = ActiveMQMessage.createMessage(coreMessage, needSession ? coreSession : null, options);
try { try {
jmsMsg.doBeforeReceive(); jmsMsg.doBeforeReceive();

View File

@ -49,6 +49,8 @@ import org.apache.activemq.artemis.utils.UUIDGenerator;
*/ */
public class ActiveMQMessageProducer implements MessageProducer, QueueSender, TopicPublisher { public class ActiveMQMessageProducer implements MessageProducer, QueueSender, TopicPublisher {
private final ConnectionFactoryOptions options;
private final ActiveMQConnection connection; private final ActiveMQConnection connection;
private final SimpleString connID; private final SimpleString connID;
@ -71,7 +73,9 @@ public class ActiveMQMessageProducer implements MessageProducer, QueueSender, To
protected ActiveMQMessageProducer(final ActiveMQConnection connection, protected ActiveMQMessageProducer(final ActiveMQConnection connection,
final ClientProducer producer, final ClientProducer producer,
final ActiveMQDestination defaultDestination, final ActiveMQDestination defaultDestination,
final ClientSession clientSession) throws JMSException { final ClientSession clientSession,
final ConnectionFactoryOptions options) throws JMSException {
this.options = options;
this.connection = connection; this.connection = connection;
connID = connection.getClientID() != null ? new SimpleString(connection.getClientID()) : connection.getUID(); connID = connection.getClientID() != null ? new SimpleString(connection.getClientID()) : connection.getUID();
@ -434,7 +438,7 @@ public class ActiveMQMessageProducer implements MessageProducer, QueueSender, To
activeMQJmsMessage = new ActiveMQMapMessage((MapMessage) jmsMessage, clientSession); activeMQJmsMessage = new ActiveMQMapMessage((MapMessage) jmsMessage, clientSession);
} }
else if (jmsMessage instanceof ObjectMessage) { else if (jmsMessage instanceof ObjectMessage) {
activeMQJmsMessage = new ActiveMQObjectMessage((ObjectMessage) jmsMessage, clientSession); activeMQJmsMessage = new ActiveMQObjectMessage((ObjectMessage) jmsMessage, clientSession, options);
} }
else if (jmsMessage instanceof StreamMessage) { else if (jmsMessage instanceof StreamMessage) {
activeMQJmsMessage = new ActiveMQStreamMessage((StreamMessage) jmsMessage, clientSession); activeMQJmsMessage = new ActiveMQStreamMessage((StreamMessage) jmsMessage, clientSession);

View File

@ -21,7 +21,6 @@ import javax.jms.MessageFormatException;
import javax.jms.ObjectMessage; import javax.jms.ObjectMessage;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.io.Serializable; import java.io.Serializable;
@ -48,25 +47,30 @@ public class ActiveMQObjectMessage extends ActiveMQMessage implements ObjectMess
// keep a snapshot of the Serializable Object as a byte[] to provide Object isolation // keep a snapshot of the Serializable Object as a byte[] to provide Object isolation
private byte[] data; private byte[] data;
private final ConnectionFactoryOptions options;
// Static -------------------------------------------------------- // Static --------------------------------------------------------
// Constructors -------------------------------------------------- // Constructors --------------------------------------------------
protected ActiveMQObjectMessage(final ClientSession session) { protected ActiveMQObjectMessage(final ClientSession session, ConnectionFactoryOptions options) {
super(ActiveMQObjectMessage.TYPE, session); super(ActiveMQObjectMessage.TYPE, session);
this.options = options;
} }
protected ActiveMQObjectMessage(final ClientMessage message, final ClientSession session) { protected ActiveMQObjectMessage(final ClientMessage message, final ClientSession session, ConnectionFactoryOptions options) {
super(message, session); super(message, session);
this.options = options;
} }
/** /**
* A copy constructor for foreign JMS ObjectMessages. * A copy constructor for foreign JMS ObjectMessages.
*/ */
public ActiveMQObjectMessage(final ObjectMessage foreign, final ClientSession session) throws JMSException { public ActiveMQObjectMessage(final ObjectMessage foreign, final ClientSession session, ConnectionFactoryOptions options) throws JMSException {
super(foreign, ActiveMQObjectMessage.TYPE, session); super(foreign, ActiveMQObjectMessage.TYPE, session);
setObject(foreign.getObject()); setObject(foreign.getObject());
this.options = options;
} }
// Public -------------------------------------------------------- // Public --------------------------------------------------------
@ -135,7 +139,15 @@ public class ActiveMQObjectMessage extends ActiveMQMessage implements ObjectMess
return null; return null;
} }
try (ObjectInputStream ois = new ObjectInputStreamWithClassLoader(new ByteArrayInputStream(data))) { try (ObjectInputStreamWithClassLoader ois = new ObjectInputStreamWithClassLoader(new ByteArrayInputStream(data))) {
String blackList = getDeserializationBlackList();
if (blackList != null) {
ois.setBlackList(blackList);
}
String whiteList = getDeserializationWhiteList();
if (whiteList != null) {
ois.setWhiteList(whiteList);
}
Serializable object = (Serializable) ois.readObject(); Serializable object = (Serializable) ois.readObject();
return object; return object;
} }
@ -174,4 +186,22 @@ public class ActiveMQObjectMessage extends ActiveMQMessage implements ObjectMess
return false; return false;
} }
} }
private String getDeserializationBlackList() {
if (options == null) {
return null;
}
else {
return options.getDeserializationBlackList();
}
}
private String getDeserializationWhiteList() {
if (options == null) {
return null;
}
else {
return options.getDeserializationWhiteList();
}
}
} }

View File

@ -40,6 +40,8 @@ public final class ActiveMQQueueBrowser implements QueueBrowser {
// Attributes ----------------------------------------------------------------------------------- // Attributes -----------------------------------------------------------------------------------
private final ConnectionFactoryOptions options;
private final ClientSession session; private final ClientSession session;
private ClientConsumer consumer; private ClientConsumer consumer;
@ -50,9 +52,11 @@ public final class ActiveMQQueueBrowser implements QueueBrowser {
// Constructors --------------------------------------------------------------------------------- // Constructors ---------------------------------------------------------------------------------
protected ActiveMQQueueBrowser(final ActiveMQQueue queue, protected ActiveMQQueueBrowser(final ConnectionFactoryOptions options,
final ActiveMQQueue queue,
final String messageSelector, final String messageSelector,
final ClientSession session) throws JMSException { final ClientSession session) throws JMSException {
this.options = options;
this.session = session; this.session = session;
this.queue = queue; this.queue = queue;
if (messageSelector != null) { if (messageSelector != null) {
@ -137,7 +141,7 @@ public final class ActiveMQQueueBrowser implements QueueBrowser {
if (hasMoreElements()) { if (hasMoreElements()) {
ClientMessage next = current; ClientMessage next = current;
current = null; current = null;
msg = ActiveMQMessage.createMessage(next, session); msg = ActiveMQMessage.createMessage(next, session, options);
try { try {
msg.doBeforeReceive(); msg.doBeforeReceive();
} }

View File

@ -77,6 +77,8 @@ public class ActiveMQSession implements QueueSession, TopicSession {
private static SimpleString REJECTING_FILTER = new SimpleString("_AMQX=-1"); private static SimpleString REJECTING_FILTER = new SimpleString("_AMQX=-1");
private final ConnectionFactoryOptions options;
private final ActiveMQConnection connection; private final ActiveMQConnection connection;
private final ClientSession session; private final ClientSession session;
@ -95,12 +97,15 @@ public class ActiveMQSession implements QueueSession, TopicSession {
// Constructors -------------------------------------------------- // Constructors --------------------------------------------------
protected ActiveMQSession(final ActiveMQConnection connection, protected ActiveMQSession(final ConnectionFactoryOptions options,
final ActiveMQConnection connection,
final boolean transacted, final boolean transacted,
final boolean xa, final boolean xa,
final int ackMode, final int ackMode,
final ClientSession session, final ClientSession session,
final int sessionType) { final int sessionType) {
this.options = options;
this.connection = connection; this.connection = connection;
this.ackMode = ackMode; this.ackMode = ackMode;
@ -141,14 +146,14 @@ public class ActiveMQSession implements QueueSession, TopicSession {
public ObjectMessage createObjectMessage() throws JMSException { public ObjectMessage createObjectMessage() throws JMSException {
checkClosed(); checkClosed();
return new ActiveMQObjectMessage(session); return new ActiveMQObjectMessage(session, options);
} }
@Override @Override
public ObjectMessage createObjectMessage(final Serializable object) throws JMSException { public ObjectMessage createObjectMessage(final Serializable object) throws JMSException {
checkClosed(); checkClosed();
ActiveMQObjectMessage msg = new ActiveMQObjectMessage(session); ActiveMQObjectMessage msg = new ActiveMQObjectMessage(session, options);
msg.setObject(object); msg.setObject(object);
@ -308,7 +313,7 @@ public class ActiveMQSession implements QueueSession, TopicSession {
ClientProducer producer = session.createProducer(jbd == null ? null : jbd.getSimpleAddress()); ClientProducer producer = session.createProducer(jbd == null ? null : jbd.getSimpleAddress());
return new ActiveMQMessageProducer(connection, producer, jbd, session); return new ActiveMQMessageProducer(connection, producer, jbd, session, options);
} }
catch (ActiveMQException e) { catch (ActiveMQException e) {
throw JMSExceptionHelper.convertFromActiveMQException(e); throw JMSExceptionHelper.convertFromActiveMQException(e);
@ -522,6 +527,14 @@ public class ActiveMQSession implements QueueSession, TopicSession {
return internalCreateSharedConsumer(localTopic, name, messageSelector, ConsumerDurability.DURABLE); return internalCreateSharedConsumer(localTopic, name, messageSelector, ConsumerDurability.DURABLE);
} }
public String getDeserializationBlackList() {
return connection.getDeserializationBlackList();
}
public String getDeserializationWhiteList() {
return connection.getDeserializationWhiteList();
}
enum ConsumerDurability { enum ConsumerDurability {
DURABLE, NON_DURABLE; DURABLE, NON_DURABLE;
} }
@ -587,7 +600,7 @@ public class ActiveMQSession implements QueueSession, TopicSession {
consumer = session.createConsumer(queueName, null, false); consumer = session.createConsumer(queueName, null, false);
ActiveMQMessageConsumer jbc = new ActiveMQMessageConsumer(connection, this, consumer, false, dest, selectorString, autoDeleteQueueName); ActiveMQMessageConsumer jbc = new ActiveMQMessageConsumer(options, connection, this, consumer, false, dest, selectorString, autoDeleteQueueName);
consumers.add(jbc); consumers.add(jbc);
@ -739,7 +752,7 @@ public class ActiveMQSession implements QueueSession, TopicSession {
} }
} }
ActiveMQMessageConsumer jbc = new ActiveMQMessageConsumer(connection, this, consumer, noLocal, dest, selectorString, autoDeleteQueueName); ActiveMQMessageConsumer jbc = new ActiveMQMessageConsumer(options, connection, this, consumer, noLocal, dest, selectorString, autoDeleteQueueName);
consumers.add(jbc); consumers.add(jbc);
@ -806,7 +819,7 @@ public class ActiveMQSession implements QueueSession, TopicSession {
throw JMSExceptionHelper.convertFromActiveMQException(e); throw JMSExceptionHelper.convertFromActiveMQException(e);
} }
return new ActiveMQQueueBrowser((ActiveMQQueue) jbq, filterString, session); return new ActiveMQQueueBrowser(options, (ActiveMQQueue) jbq, filterString, session);
} }

View File

@ -34,14 +34,15 @@ import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
*/ */
public final class ActiveMQXAConnection extends ActiveMQConnection implements XATopicConnection, XAQueueConnection { public final class ActiveMQXAConnection extends ActiveMQConnection implements XATopicConnection, XAQueueConnection {
public ActiveMQXAConnection(final String username, public ActiveMQXAConnection(final ConnectionFactoryOptions options,
final String username,
final String password, final String password,
final int connectionType, final int connectionType,
final String clientID, final String clientID,
final int dupsOKBatchSize, final int dupsOKBatchSize,
final int transactionBatchSize, final int transactionBatchSize,
final ClientSessionFactory sessionFactory) { final ClientSessionFactory sessionFactory) {
super(username, password, connectionType, clientID, dupsOKBatchSize, transactionBatchSize, sessionFactory); super(options, username, password, connectionType, clientID, dupsOKBatchSize, transactionBatchSize, sessionFactory);
} }
@Override @Override

View File

@ -31,12 +31,13 @@ public class ActiveMQXASession extends ActiveMQSession implements XAQueueSession
* @param session * @param session
* @param sessionType * @param sessionType
*/ */
protected ActiveMQXASession(ActiveMQConnection connection, protected ActiveMQXASession(final ConnectionFactoryOptions options,
ActiveMQConnection connection,
boolean transacted, boolean transacted,
boolean xa, boolean xa,
int ackMode, int ackMode,
ClientSession session, ClientSession session,
int sessionType) { int sessionType) {
super(connection, transacted, xa, ackMode, session, sessionType); super(options, connection, transacted, xa, ackMode, session, sessionType);
} }
} }

View File

@ -0,0 +1,33 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.jms.client;
/** Common interface to be used to share common parameters between the RA and client JMS.
* Initially developed to carry on Serialization packages white list but it could eventually be expanded. */
public interface ConnectionFactoryOptions {
String getDeserializationBlackList();
void setDeserializationBlackList(String blackList);
String getDeserializationWhiteList();
void setDeserializationWhiteList(String whiteList);
}

View File

@ -28,6 +28,7 @@ import org.apache.activemq.artemis.core.client.impl.ClientSessionInternal;
public class JMSMessageListenerWrapper implements MessageHandler { public class JMSMessageListenerWrapper implements MessageHandler {
private final ConnectionFactoryOptions options;
private final ActiveMQConnection connection; private final ActiveMQConnection connection;
private final ActiveMQSession session; private final ActiveMQSession session;
@ -40,11 +41,14 @@ public class JMSMessageListenerWrapper implements MessageHandler {
private final boolean individualACK; private final boolean individualACK;
protected JMSMessageListenerWrapper(final ActiveMQConnection connection, protected JMSMessageListenerWrapper(final ConnectionFactoryOptions options,
final ActiveMQConnection connection,
final ActiveMQSession session, final ActiveMQSession session,
final ClientConsumer consumer, final ClientConsumer consumer,
final MessageListener listener, final MessageListener listener,
final int ackMode) { final int ackMode) {
this.options = options;
this.connection = connection; this.connection = connection;
this.session = session; this.session = session;
@ -64,7 +68,7 @@ public class JMSMessageListenerWrapper implements MessageHandler {
*/ */
@Override @Override
public void onMessage(final ClientMessage message) { public void onMessage(final ClientMessage message) {
ActiveMQMessage msg = ActiveMQMessage.createMessage(message, session.getCoreSession()); ActiveMQMessage msg = ActiveMQMessage.createMessage(message, session.getCoreSession(), options);
if (individualACK) { if (individualACK) {
msg.setIndividualAcknowledge(); msg.setIndividualAcknowledge();

View File

@ -177,4 +177,12 @@ public interface ConnectionFactoryConfiguration extends EncodingSupport {
String getProtocolManagerFactoryStr(); String getProtocolManagerFactoryStr();
JMSFactoryType getFactoryType(); JMSFactoryType getFactoryType();
String getDeserializationBlackList();
void setDeserializationBlackList(String blackList);
String getDeserializationWhiteList();
void setDeserializationWhiteList(String whiteList);
} }

View File

@ -118,6 +118,10 @@ public class ConnectionFactoryConfigurationImpl implements ConnectionFactoryConf
private JMSFactoryType factoryType = JMSFactoryType.CF; private JMSFactoryType factoryType = JMSFactoryType.CF;
private String deserializationBlackList;
private String deserializationWhiteList;
// Static -------------------------------------------------------- // Static --------------------------------------------------------
// Constructors -------------------------------------------------- // Constructors --------------------------------------------------
@ -614,6 +618,10 @@ public class ConnectionFactoryConfigurationImpl implements ConnectionFactoryConf
factoryType = JMSFactoryType.valueOf(buffer.readInt()); factoryType = JMSFactoryType.valueOf(buffer.readInt());
protocolManagerFactoryStr = BufferHelper.readNullableSimpleStringAsString(buffer); protocolManagerFactoryStr = BufferHelper.readNullableSimpleStringAsString(buffer);
deserializationBlackList = BufferHelper.readNullableSimpleStringAsString(buffer);
deserializationWhiteList = BufferHelper.readNullableSimpleStringAsString(buffer);
} }
@Override @Override
@ -700,6 +708,10 @@ public class ConnectionFactoryConfigurationImpl implements ConnectionFactoryConf
buffer.writeInt(factoryType.intValue()); buffer.writeInt(factoryType.intValue());
BufferHelper.writeAsNullableSimpleString(buffer, protocolManagerFactoryStr); BufferHelper.writeAsNullableSimpleString(buffer, protocolManagerFactoryStr);
BufferHelper.writeAsNullableSimpleString(buffer, deserializationBlackList);
BufferHelper.writeAsNullableSimpleString(buffer, deserializationWhiteList);
} }
@Override @Override
@ -809,7 +821,11 @@ public class ConnectionFactoryConfigurationImpl implements ConnectionFactoryConf
DataConstants.SIZE_INT + DataConstants.SIZE_INT +
// factoryType // factoryType
BufferHelper.sizeOfNullableSimpleString(protocolManagerFactoryStr); BufferHelper.sizeOfNullableSimpleString(protocolManagerFactoryStr) +
BufferHelper.sizeOfNullableSimpleString(deserializationBlackList) +
BufferHelper.sizeOfNullableSimpleString(deserializationWhiteList);
return size; return size;
} }
@ -825,6 +841,26 @@ public class ConnectionFactoryConfigurationImpl implements ConnectionFactoryConf
return factoryType; return factoryType;
} }
@Override
public String getDeserializationBlackList() {
return deserializationBlackList;
}
@Override
public void setDeserializationBlackList(String blackList) {
this.deserializationBlackList = blackList;
}
@Override
public String getDeserializationWhiteList() {
return this.deserializationWhiteList;
}
@Override
public void setDeserializationWhiteList(String whiteList) {
this.deserializationWhiteList = whiteList;
}
@Override @Override
public ConnectionFactoryConfiguration setCompressLargeMessages(boolean compressLargeMessage) { public ConnectionFactoryConfiguration setCompressLargeMessages(boolean compressLargeMessage) {
this.compressLargeMessage = compressLargeMessage; this.compressLargeMessage = compressLargeMessage;

View File

@ -901,6 +901,36 @@ public class ActiveMQResourceAdapter implements ResourceAdapter, Serializable {
raProperties.setProtocolManagerFactoryStr(protocolManagerFactoryStr); raProperties.setProtocolManagerFactoryStr(protocolManagerFactoryStr);
} }
public String getDeserializationBlackList() {
if (ActiveMQResourceAdapter.trace) {
ActiveMQRALogger.LOGGER.trace("getDeserializationBlackList()");
}
return raProperties.getDeserializationBlackList();
}
public void setDeserializationBlackList(String deserializationBlackList) {
if (ActiveMQResourceAdapter.trace) {
ActiveMQRALogger.LOGGER.trace("setDeserializationBlackList(" + deserializationBlackList + ")");
}
raProperties.setDeserializationBlackList(deserializationBlackList);
}
public String getDeserializationWhiteList() {
if (ActiveMQResourceAdapter.trace) {
ActiveMQRALogger.LOGGER.trace("getDeserializationWhiteList()");
}
return raProperties.getDeserializationWhiteList();
}
public void setDeserializationWhiteList(String deserializationWhiteList) {
if (ActiveMQResourceAdapter.trace) {
ActiveMQRALogger.LOGGER.trace("setDeserializationWhiteList(" + deserializationWhiteList + ")");
}
raProperties.setDeserializationWhiteList(deserializationWhiteList);
}
/** /**
* Get min large message size * Get min large message size
* *
@ -2004,6 +2034,14 @@ public class ActiveMQResourceAdapter implements ResourceAdapter, Serializable {
if (val5 != null) { if (val5 != null) {
cf.setProtocolManagerFactoryStr(val5); cf.setProtocolManagerFactoryStr(val5);
} }
val5 = overrideProperties.getDeserializationBlackList() != null ? overrideProperties.getDeserializationBlackList() : raProperties.getDeserializationBlackList();
if (val5 != null) {
cf.setDeserializationBlackList(val5);
}
val5 = overrideProperties.getDeserializationWhiteList() != null ? overrideProperties.getDeserializationWhiteList() : raProperties.getDeserializationWhiteList();
if (val5 != null) {
cf.setDeserializationWhiteList(val5);
}
} }
public void setManagedConnectionFactory(ActiveMQRAManagedConnectionFactory activeMQRAManagedConnectionFactory) { public void setManagedConnectionFactory(ActiveMQRAManagedConnectionFactory activeMQRAManagedConnectionFactory) {

View File

@ -19,7 +19,9 @@ package org.apache.activemq.artemis.ra;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
public class ConnectionFactoryProperties { import org.apache.activemq.artemis.jms.client.ConnectionFactoryOptions;
public class ConnectionFactoryProperties implements ConnectionFactoryOptions {
/** /**
* Trace enabled * Trace enabled
@ -120,6 +122,10 @@ public class ConnectionFactoryProperties {
private String protocolManagerFactoryStr; private String protocolManagerFactoryStr;
private String deserializationBlackList;
private String deserializationWhiteList;
/** /**
* @return the transportType * @return the transportType
*/ */
@ -689,6 +695,28 @@ public class ConnectionFactoryProperties {
this.protocolManagerFactoryStr = protocolManagerFactoryStr; this.protocolManagerFactoryStr = protocolManagerFactoryStr;
} }
@Override
public String getDeserializationBlackList() {
return deserializationBlackList;
}
@Override
public void setDeserializationBlackList(String deserializationBlackList) {
this.deserializationBlackList = deserializationBlackList;
hasBeenUpdated = true;
}
@Override
public String getDeserializationWhiteList() {
return this.deserializationWhiteList;
}
@Override
public void setDeserializationWhiteList(String deserializationWhiteList) {
this.deserializationWhiteList = deserializationWhiteList;
hasBeenUpdated = true;
}
public boolean isHasBeenUpdated() { public boolean isHasBeenUpdated() {
return hasBeenUpdated; return hasBeenUpdated;
} }
@ -960,6 +988,20 @@ public class ConnectionFactoryProperties {
} }
else if (!connectionParameters.equals(other.connectionParameters)) else if (!connectionParameters.equals(other.connectionParameters))
return false; return false;
if (deserializationBlackList == null) {
if (other.deserializationBlackList != null)
return false;
}
else if (!deserializationBlackList.equals(other.deserializationBlackList))
return false;
if (deserializationWhiteList == null) {
if (other.deserializationWhiteList != null)
return false;
}
else if (!deserializationWhiteList.equals(other.deserializationWhiteList))
return false;
return true; return true;
} }
@ -1010,6 +1052,8 @@ public class ConnectionFactoryProperties {
result = prime * result + ((groupID == null) ? 0 : groupID.hashCode()); result = prime * result + ((groupID == null) ? 0 : groupID.hashCode());
result = prime * result + ((connectorClassName == null) ? 0 : connectorClassName.hashCode()); result = prime * result + ((connectorClassName == null) ? 0 : connectorClassName.hashCode());
result = prime * result + ((connectionParameters == null) ? 0 : connectionParameters.hashCode()); result = prime * result + ((connectionParameters == null) ? 0 : connectionParameters.hashCode());
result = prime * result + ((deserializationBlackList == null) ? 0 : deserializationBlackList.hashCode());
result = prime * result + ((deserializationWhiteList == null) ? 0 : deserializationWhiteList.hashCode());
return result; return result;
} }
} }

View File

@ -315,7 +315,7 @@ public class ActiveMQActivation {
try { try {
cf = factory.getServerLocator().createSessionFactory(); cf = factory.getServerLocator().createSessionFactory();
session = setupSession(cf); session = setupSession(cf);
ActiveMQMessageHandler handler = new ActiveMQMessageHandler(this, ra.getTM(), (ClientSessionInternal) session, cf, i); ActiveMQMessageHandler handler = new ActiveMQMessageHandler(factory, this, ra.getTM(), (ClientSessionInternal) session, cf, i);
handler.setup(); handler.setup();
handlers.add(handler); handlers.add(handler);
} }

View File

@ -40,6 +40,7 @@ import org.apache.activemq.artemis.core.client.impl.ClientSessionFactoryInternal
import org.apache.activemq.artemis.core.client.impl.ClientSessionInternal; import org.apache.activemq.artemis.core.client.impl.ClientSessionInternal;
import org.apache.activemq.artemis.jms.client.ActiveMQDestination; import org.apache.activemq.artemis.jms.client.ActiveMQDestination;
import org.apache.activemq.artemis.jms.client.ActiveMQMessage; import org.apache.activemq.artemis.jms.client.ActiveMQMessage;
import org.apache.activemq.artemis.jms.client.ConnectionFactoryOptions;
import org.apache.activemq.artemis.ra.ActiveMQRALogger; import org.apache.activemq.artemis.ra.ActiveMQRALogger;
import org.apache.activemq.artemis.ra.ActiveMQResourceAdapter; import org.apache.activemq.artemis.ra.ActiveMQResourceAdapter;
import org.apache.activemq.artemis.service.extensions.ServiceUtils; import org.apache.activemq.artemis.service.extensions.ServiceUtils;
@ -68,6 +69,8 @@ public class ActiveMQMessageHandler implements MessageHandler, FailoverEventList
*/ */
private MessageEndpoint endpoint; private MessageEndpoint endpoint;
private final ConnectionFactoryOptions options;
private final ActiveMQActivation activation; private final ActiveMQActivation activation;
private boolean useLocalTx; private boolean useLocalTx;
@ -84,11 +87,13 @@ public class ActiveMQMessageHandler implements MessageHandler, FailoverEventList
private volatile boolean connected; private volatile boolean connected;
public ActiveMQMessageHandler(final ActiveMQActivation activation, public ActiveMQMessageHandler(final ConnectionFactoryOptions options,
final ActiveMQActivation activation,
final TransactionManager tm, final TransactionManager tm,
final ClientSessionInternal session, final ClientSessionInternal session,
final ClientSessionFactory cf, final ClientSessionFactory cf,
final int sessionNr) { final int sessionNr) {
this.options = options;
this.activation = activation; this.activation = activation;
this.session = session; this.session = session;
this.cf = cf; this.cf = cf;
@ -286,7 +291,7 @@ public class ActiveMQMessageHandler implements MessageHandler, FailoverEventList
ActiveMQRALogger.LOGGER.trace("onMessage(" + message + ")"); ActiveMQRALogger.LOGGER.trace("onMessage(" + message + ")");
} }
ActiveMQMessage msg = ActiveMQMessage.createMessage(message, session); ActiveMQMessage msg = ActiveMQMessage.createMessage(message, session, options);
boolean beforeDelivery = false; boolean beforeDelivery = false;
try { try {

View File

@ -690,3 +690,77 @@ You will have to configure a few extra properties desribed as below.
- `trustStorePath` - The path of the trust store file. This is needed only if `clientAuth` is `true`. - `trustStorePath` - The path of the trust store file. This is needed only if `clientAuth` is `true`.
- `trustStorePassword` - The trust store's password. - `trustStorePassword` - The trust store's password.
## Controlling JMS ObjectMessage deserialization
Artemis provides a simple class filtering mechanism with which a user can specify which
packages are to be trusted and which are not. Objects whose classes are from trusted packages
can be deserialized without problem, whereas those from 'not trusted' packages will be denied
deserialization.
Artemis keeps a `black list` to keep track of packages that are not trusted and a `white list`
for trusted packages. By default both lists are empty, meaning any serializable object is
allowed to be deserialized. If an object whose class matches one of the packages in black list,
it is not allowed to be deserialized. If it matches one in the white list
the object can be deserialized. If a package appears in both black list and white list,
the one in black list takes precedence. If a class neither matches with `black list`
nor with the `white list`, the class deserialization will be denied
unless the white list is empty (meaning the user doesn't specify the white list at all).
A class is considered as a 'match' if
- its full name exactly matches one of the entries in the list.
- its package matches one of the entries in the list or is a sub-package of one of the entries.
For example, if a class full name is "org.apache.pkg1.Class1", some matching entries could be:
- `org.apache.pkg1.Class1` - exact match.
- `org.apache.pkg1` - exact package match.
- `org.apache` -- sub package match.
A `*` means 'match-all' in a black or white list.
### Specifying black list and white list via Connection Factories
To specify the white and black lists one can append properties `deserializationBlackList` and `deserializationWhiteList` respectively
to a Connection Factory's url string. For example:
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("vm://0?deserializationBlackList=org.apache.pkg1,org.some.pkg2");
The above statement creates a factory that has a black list contains two forbidden packages, "org.apache.pkg1" and "org.some.pkg2",
separated by a comma.
You can also set the values via ActiveMQConnectionFactory's API:
public void setDeserializationBlackList(String blackList);
public void setDeserializationWhiteList(String whiteList);
Again the parameters are comma separated list of package/class names.
### Specifying black list and white list via system properties
There are two system properties available for specifying black list and white list:
- `org.apache.activemq.artemis.jms.deserialization.whitelist` - comma separated list of entries for the white list.
- `org.apache.activemq.artemis.jms.deserialization.blacklist` - comma separated list of entries for the black list.
Once defined, all JMS object message deserialization in the VM is subject to checks against the two lists. However if you create a ConnectionFactory
and set a new set of black/white lists on it, the new values will override the system properties.
### Specifying black list and white list for resource adapters
Message beans using a JMS resource adapter to receive messages can also control their object deserialization via properly configuring relevant
properties for their resource adapters. There are two properties that you can configure with connection factories in a resource adapter:
- `deserializationBlackList` - comma separated values for black list
- `deserializationWhiteList` - comma separated values for white list
These properties, once specified, are eventually set on the corresponding internal factories.

View File

@ -16,22 +16,34 @@
*/ */
package org.apache.activemq.artemis.tests.integration.jms; package org.apache.activemq.artemis.tests.integration.jms;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.tests.integration.IntegrationTestLogger; import org.apache.activemq.artemis.tests.integration.IntegrationTestLogger;
import org.apache.activemq.artemis.tests.integration.jms.serializables.TestClass1;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.apache.activemq.artemis.utils.ObjectInputStreamWithClassLoader;
import org.apache.activemq.artemis.utils.RandomUtil; import org.apache.activemq.artemis.utils.RandomUtil;
import org.apache.activemq.artemis.core.config.ha.SharedStoreMasterPolicyConfiguration; import org.apache.activemq.artemis.core.config.ha.SharedStoreMasterPolicyConfiguration;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Hashtable;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.jms.Connection; import javax.jms.Connection;
import javax.jms.JMSException; import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.QueueBrowser;
import javax.jms.Session; import javax.jms.Session;
import javax.naming.Context;
import javax.naming.InitialContext;
import org.junit.Assert; import org.junit.Assert;
@ -213,6 +225,217 @@ public class ActiveMQConnectionFactoryTest extends ActiveMQTestBase {
cf.close(); cf.close();
} }
@Test
public void testDeserializationOptions() throws Exception {
testDeserializationOptions(false, false);
}
@Test
public void testDeserializationOptionsJndi() throws Exception {
testDeserializationOptions(true, false);
}
@Test
public void testDeserializationOptionsBrowser() throws Exception {
testDeserializationOptions(false, true);
}
@Test
public void testDeserializationOptionsJndiBrowser() throws Exception {
testDeserializationOptions(true, true);
}
private void testDeserializationOptions(boolean useJndi, boolean useBrowser) throws Exception {
String qname = "SerialTestQueue";
SimpleString qaddr = new SimpleString("jms.queue." + qname);
liveService.createQueue(qaddr, qaddr, null, true, false);
//default ok
String blackList = null;
String whiteList = null;
Object obj = receiveObjectMessage(blackList, whiteList, qname, new TestClass1(), useJndi, useBrowser);
assertTrue("Object is " + obj, obj instanceof TestClass1);
//not in the white list
blackList = "java.lang";
whiteList = "some.other.package1";
obj = receiveObjectMessage(blackList, whiteList, qname, new TestClass1(), useJndi, useBrowser);
assertTrue("Object is " + obj, obj instanceof JMSException);
//but String always trusted
obj = receiveObjectMessage(blackList, whiteList, qname, new String("hello"), useJndi, useBrowser);
assertTrue("java.lang.String always trusted ", "hello".equals(obj));
//in the blacklist
blackList = "org.apache.activemq.artemis.tests.integration.jms.serializables";
whiteList = "org.apache.activemq.artemis.tests.integration.jms.serializables";
obj = receiveObjectMessage(blackList, whiteList, qname, new TestClass1(), useJndi, useBrowser);
assertTrue("Object is " + obj, obj instanceof JMSException);
//black list parent package
blackList = "org.apache.activemq.artemis";
whiteList = "org.apache.activemq.artemis.tests.integration.jms.serializables";
obj = receiveObjectMessage(blackList, whiteList, qname, new TestClass1(), useJndi, useBrowser);
assertTrue("Object is " + obj, obj instanceof JMSException);
//in white list
blackList = "some.other.package";
whiteList = "org.apache.activemq.artemis.tests.integration.jms.serializables";
obj = receiveObjectMessage(blackList, whiteList, qname, new TestClass1(), useJndi, useBrowser);
assertTrue("Object is " + obj, obj instanceof TestClass1);
//parent in white list
blackList = "some.other.package";
whiteList = "org.apache.activemq.artemis.tests.integration.jms";
obj = receiveObjectMessage(blackList, whiteList, qname, new TestClass1(), useJndi, useBrowser);
assertTrue("Object is " + obj, obj instanceof TestClass1);
//sub package in white list
blackList = "some.other.package";
whiteList = "org.apache.activemq.artemis.tests.integration.jms.serializables.pkg1";
obj = receiveObjectMessage(blackList, whiteList, qname, new TestClass1(), useJndi, useBrowser);
assertTrue("Object is " + obj, obj instanceof JMSException);
//wild card white list but black listed
blackList = "org.apache.activemq.artemis.tests.integration.jms.serializables";
whiteList = "*";
obj = receiveObjectMessage(blackList, whiteList, qname, new TestClass1(), useJndi, useBrowser);
assertTrue("Object is " + obj, obj instanceof JMSException);
//wild card white list and not black listed
blackList = "some.other.package";
whiteList = "*";
obj = receiveObjectMessage(blackList, whiteList, qname, new TestClass1(), useJndi, useBrowser);
assertTrue("Object is " + obj, obj instanceof TestClass1);
//wild card black list
blackList = "*";
whiteList = "*";
obj = receiveObjectMessage(blackList, whiteList, qname, new TestClass1(), useJndi, useBrowser);
assertTrue("Object is " + obj, obj instanceof JMSException);
}
@Test
public void testSystemPropertyBlackWhiteListDefault() throws Exception {
System.setProperty(ObjectInputStreamWithClassLoader.BLACKLIST_PROPERTY, "*");
System.setProperty(ObjectInputStreamWithClassLoader.WHITELIST_PROPERTY, "some.other.package");
String qname = "SerialTestQueue";
SimpleString qaddr = new SimpleString("jms.queue." + qname);
liveService.createQueue(qaddr, qaddr, null, true, false);
try {
String blackList = null;
String whiteList = null;
Object obj = receiveObjectMessage(blackList, whiteList, qname, new TestClass1(), false, false);
assertTrue("Object is " + obj, obj instanceof JMSException);
//but String always trusted
obj = receiveObjectMessage(blackList, whiteList, qname, new String("hello"), false, false);
assertTrue("java.lang.String always trusted " + obj, "hello".equals(obj));
//override
blackList = "some.other.package";
whiteList = "org.apache.activemq.artemis.tests.integration";
obj = receiveObjectMessage(blackList, whiteList, qname, new TestClass1(), false, false);
assertTrue("Object is " + obj, obj instanceof TestClass1);
//but String always trusted
obj = receiveObjectMessage(blackList, whiteList, qname, new String("hello"), false, false);
assertTrue("java.lang.String always trusted " + obj, "hello".equals(obj));
}
finally {
System.clearProperty(ObjectInputStreamWithClassLoader.BLACKLIST_PROPERTY);
System.clearProperty(ObjectInputStreamWithClassLoader.WHITELIST_PROPERTY);
}
}
private Object receiveObjectMessage(String blackList, String whiteList, String qname,
Serializable obj, boolean useJndi, boolean useBrowser) throws Exception {
sendObjectMessage(qname, obj);
StringBuilder query = new StringBuilder("");
if (blackList != null) {
query.append("?");
query.append("deserializationBlackList=");
query.append(blackList);
if (whiteList != null) {
query.append("&");
query.append("deserializationWhiteList=");
query.append(whiteList);
}
}
else {
if (whiteList != null) {
query.append("?deserializationWhiteList=");
query.append(whiteList);
}
}
System.out.println("query string: " + query);
ActiveMQConnectionFactory factory = null;
if (useJndi) {
Hashtable<String, Object> props = new Hashtable<>();
props.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory");
props.put("connectionFactory.VmConnectionFactory", "vm://0" + query);
Context ctx = new InitialContext(props);
factory = (ActiveMQConnectionFactory) ctx.lookup("VmConnectionFactory");
}
else {
factory = new ActiveMQConnectionFactory("vm://0" + query);
}
Connection connection = null;
try {
connection = factory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(qname);
Object result = null;
if (useBrowser) {
QueueBrowser browser = session.createBrowser(queue);
ObjectMessage objMessage = (ObjectMessage) browser.getEnumeration().nextElement();
//drain message before triggering deserialization
MessageConsumer consumer = session.createConsumer(queue);
consumer.receive(5000);
result = objMessage.getObject();
}
else {
MessageConsumer consumer = session.createConsumer(queue);
ObjectMessage objMessage = (ObjectMessage) consumer.receive(5000);
assertNotNull(objMessage);
result = objMessage.getObject();
}
return result;
}
catch (Exception e) {
return e;
}
finally {
if (connection != null) {
try {
connection.close();
}
catch (JMSException e) {
return e;
}
}
}
}
private void sendObjectMessage(String qname, Serializable obj) throws Exception {
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("vm://0");
Connection connection = factory.createConnection();
try {
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue q = session.createQueue(qname);
MessageProducer producer = session.createProducer(q);
ObjectMessage objMessage = session.createObjectMessage();
objMessage.setObject(obj);
producer.send(objMessage);
}
finally {
connection.close();
}
}
private void testSettersThrowException(final ActiveMQConnectionFactory cf) { private void testSettersThrowException(final ActiveMQConnectionFactory cf) {
String clientID = RandomUtil.randomString(); String clientID = RandomUtil.randomString();

View File

@ -0,0 +1,22 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.tests.integration.jms.serializables;
import java.io.Serializable;
public class TestClass1 implements Serializable {
}

View File

@ -26,15 +26,23 @@ import org.apache.activemq.artemis.api.core.client.SessionFailureListener;
import org.apache.activemq.artemis.core.client.impl.ClientSessionFactoryInternal; import org.apache.activemq.artemis.core.client.impl.ClientSessionFactoryInternal;
import org.apache.activemq.artemis.core.postoffice.Binding; import org.apache.activemq.artemis.core.postoffice.Binding;
import org.apache.activemq.artemis.core.postoffice.impl.LocalQueueBinding; import org.apache.activemq.artemis.core.postoffice.impl.LocalQueueBinding;
import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
import org.apache.activemq.artemis.ra.ActiveMQResourceAdapter; import org.apache.activemq.artemis.ra.ActiveMQResourceAdapter;
import org.apache.activemq.artemis.ra.inflow.ActiveMQActivation; import org.apache.activemq.artemis.ra.inflow.ActiveMQActivation;
import org.apache.activemq.artemis.ra.inflow.ActiveMQActivationSpec; import org.apache.activemq.artemis.ra.inflow.ActiveMQActivationSpec;
import org.apache.activemq.artemis.tests.integration.IntegrationTestLogger; import org.apache.activemq.artemis.tests.integration.IntegrationTestLogger;
import org.junit.Test; import org.junit.Test;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message; import javax.jms.Message;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.Session;
import javax.resource.ResourceException; import javax.resource.ResourceException;
import javax.resource.spi.InvalidPropertyException; import javax.resource.spi.InvalidPropertyException;
import java.io.Serializable;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -78,6 +86,103 @@ public class ActiveMQMessageHandlerTest extends ActiveMQRATestBase {
qResourceAdapter.stop(); qResourceAdapter.stop();
} }
@Test
public void testObjectMessageReceiveSerializationControl() throws Exception {
String blackList = "org.apache.activemq.artemis.tests.integration.ra";
String whiteList = "*";
testDeserialization(blackList, whiteList, false);
}
@Test
public void testObjectMessageReceiveSerializationControl1() throws Exception {
String blackList = "some.other.pkg";
String whiteList = "org.apache.activemq.artemis.tests.integration.ra";
testDeserialization(blackList, whiteList, true);
}
@Test
public void testObjectMessageReceiveSerializationControl2() throws Exception {
String blackList = "*";
String whiteList = "org.apache.activemq.artemis.tests.integration.ra";
testDeserialization(blackList, whiteList, false);
}
@Test
public void testObjectMessageReceiveSerializationControl3() throws Exception {
String blackList = "org.apache.activemq.artemis.tests";
String whiteList = "org.apache.activemq.artemis.tests.integration.ra";
testDeserialization(blackList, whiteList, false);
}
@Test
public void testObjectMessageReceiveSerializationControl4() throws Exception {
String blackList = null;
String whiteList = "some.other.pkg";
testDeserialization(blackList, whiteList, false);
}
@Test
public void testObjectMessageReceiveSerializationControl5() throws Exception {
String blackList = null;
String whiteList = null;
testDeserialization(blackList, whiteList, true);
}
private void testDeserialization(String blackList, String whiteList, boolean shouldSucceed) throws Exception {
ActiveMQResourceAdapter qResourceAdapter = newResourceAdapter();
qResourceAdapter.setDeserializationBlackList(blackList);
qResourceAdapter.setDeserializationWhiteList(whiteList);
MyBootstrapContext ctx = new MyBootstrapContext();
qResourceAdapter.start(ctx);
ActiveMQActivationSpec spec = new ActiveMQActivationSpec();
spec.setResourceAdapter(qResourceAdapter);
spec.setUseJNDI(false);
spec.setDestinationType("javax.jms.Queue");
spec.setDestination(MDBQUEUE);
qResourceAdapter.setConnectorClassName(INVM_CONNECTOR_FACTORY);
CountDownLatch latch = new CountDownLatch(1);
DummyMessageEndpoint endpoint = new DummyMessageEndpoint(latch);
DummyMessageEndpointFactory endpointFactory = new DummyMessageEndpointFactory(endpoint, false);
qResourceAdapter.endpointActivation(endpointFactory, spec);
//send using jms
ActiveMQConnectionFactory jmsFactory = new ActiveMQConnectionFactory("vm://0");
Connection connection = jmsFactory.createConnection();
try {
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue jmsQueue = session.createQueue(MDBQUEUE);
ObjectMessage objMsg = session.createObjectMessage();
objMsg.setObject(new DummySerializable());
MessageProducer producer = session.createProducer(jmsQueue);
producer.send(objMsg);
}
finally {
connection.close();
}
latch.await(5, TimeUnit.SECONDS);
assertNotNull(endpoint.lastMessage);
ObjectMessage objMsg = (ObjectMessage) endpoint.lastMessage;
try {
Object obj = objMsg.getObject();
assertTrue("deserialization should fail but got: " + obj, shouldSucceed);
assertTrue(obj instanceof DummySerializable);
}
catch (JMSException e) {
assertFalse("got unexpected exception: " + e, shouldSucceed);
}
qResourceAdapter.endpointDeactivation(endpointFactory, spec);
qResourceAdapter.stop();
}
@Test @Test
public void testSimpleMessageReceivedOnQueueManyMessages() throws Exception { public void testSimpleMessageReceivedOnQueueManyMessages() throws Exception {
ActiveMQResourceAdapter qResourceAdapter = newResourceAdapter(); ActiveMQResourceAdapter qResourceAdapter = newResourceAdapter();
@ -863,4 +968,7 @@ public class ActiveMQMessageHandlerTest extends ActiveMQRATestBase {
} }
} }
} }
static class DummySerializable implements Serializable {
}
} }

View File

@ -739,7 +739,7 @@ public class MessageHeaderTest extends MessageHeaderTestBase {
ObjectMessage foreignObjectMessage = new SimpleJMSObjectMessage(); ObjectMessage foreignObjectMessage = new SimpleJMSObjectMessage();
ActiveMQObjectMessage copy = new ActiveMQObjectMessage(foreignObjectMessage, session); ActiveMQObjectMessage copy = new ActiveMQObjectMessage(foreignObjectMessage, session, null);
MessageHeaderTestBase.ensureEquivalent(foreignObjectMessage, copy); MessageHeaderTestBase.ensureEquivalent(foreignObjectMessage, copy);
} }

View File

@ -390,6 +390,18 @@ public class ActiveMQResourceAdapterConfigTest extends ActiveMQTestBase {
" <config-property-name>ProtocolManagerFactoryStr</config-property-name>" + " <config-property-name>ProtocolManagerFactoryStr</config-property-name>" +
" <config-property-type>java.lang.String</config-property-type>" + " <config-property-type>java.lang.String</config-property-type>" +
" <config-property-value></config-property-value>" + " <config-property-value></config-property-value>" +
" </config-property>" +
" <config-property>" +
" <description>List of package/class names against which matching objects are permitted to be deserilized</description>" +
" <config-property-name>DeserializationWhiteList</config-property-name>" +
" <config-property-type>java.lang.String</config-property-type>" +
" <config-property-value></config-property-value>" +
" </config-property>" +
" <config-property>" +
" <description>List of package/classe names against which matching objects are forbidden to be deserialized</description>" +
" <config-property-name>DeserializationBlackList</config-property-name>" +
" <config-property-type>java.lang.String</config-property-type>" +
" <config-property-value></config-property-value>" +
" </config-property>"; " </config-property>";
private static String rootConfig = "<root>" + config + commentedOutConfigs + "</root>"; private static String rootConfig = "<root>" + config + commentedOutConfigs + "</root>";

View File

@ -474,6 +474,43 @@ public class ResourceAdapterTest extends ActiveMQTestBase {
} }
} }
@Test
public void testActivationDeserializationParameters() throws Exception {
ActiveMQServer server = createServer(false);
try {
server.start();
ActiveMQResourceAdapter ra = new ActiveMQResourceAdapter();
ra.setConnectorClassName(INVM_CONNECTOR_FACTORY);
ra.setUserName("userGlobal");
ra.setPassword("passwordGlobal");
ra.setDeserializationWhiteList("a.b.c.d.e");
ra.setDeserializationBlackList("f.g.h.i.j");
ra.start(new BootstrapContext());
ActiveMQConnectionFactory factory = ra.getDefaultActiveMQConnectionFactory();
assertEquals("a.b.c.d.e", factory.getDeserializationWhiteList());
assertEquals("f.g.h.i.j", factory.getDeserializationBlackList());
ConnectionFactoryProperties overrides = new ConnectionFactoryProperties();
overrides.setDeserializationWhiteList("k.l.m.n");
overrides.setDeserializationBlackList("o.p.q.r");
factory = ra.newConnectionFactory(overrides);
assertEquals("k.l.m.n", factory.getDeserializationWhiteList());
assertEquals("o.p.q.r", factory.getDeserializationBlackList());
ra.stop();
}
finally {
server.stop();
}
}
@Test @Test
public void testForConnectionLeakDuringActivationWhenSessionCreationFails() throws Exception { public void testForConnectionLeakDuringActivationWhenSessionCreationFails() throws Exception {
ActiveMQServer server = createServer(false); ActiveMQServer server = createServer(false);

View File

@ -16,12 +16,16 @@
*/ */
package org.apache.activemq.artemis.tests.unit.util; package org.apache.activemq.artemis.tests.unit.util;
import org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1.EnclosingClass;
import org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1.TestClass1;
import org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1.TestClass2;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.junit.Test;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.io.Serializable; import java.io.Serializable;
@ -33,12 +37,17 @@ import java.net.URLClassLoader;
import java.security.CodeSource; import java.security.CodeSource;
import java.security.ProtectionDomain; import java.security.ProtectionDomain;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import org.junit.Assert; import org.junit.Assert;
import org.apache.activemq.artemis.utils.ObjectInputStreamWithClassLoader; import org.apache.activemq.artemis.utils.ObjectInputStreamWithClassLoader;
import org.junit.Test;
public class ObjectInputStreamWithClassLoaderTest extends ActiveMQTestBase { public class ObjectInputStreamWithClassLoaderTest extends ActiveMQTestBase {
// Constants ----------------------------------------------------- // Constants -----------------------------------------------------
@ -47,29 +56,33 @@ public class ObjectInputStreamWithClassLoaderTest extends ActiveMQTestBase {
// Static -------------------------------------------------------- // Static --------------------------------------------------------
public static ClassLoader newClassLoader(final Class anyUserClass) throws Exception { public static ClassLoader newClassLoader(final Class... userClasses) throws Exception {
ProtectionDomain protectionDomain = anyUserClass.getProtectionDomain();
CodeSource codeSource = protectionDomain.getCodeSource(); Set<URL> userClassUrls = new HashSet<>();
URL classLocation = codeSource.getLocation(); for (Class anyUserClass : userClasses) {
ProtectionDomain protectionDomain = anyUserClass.getProtectionDomain();
CodeSource codeSource = protectionDomain.getCodeSource();
URL classLocation = codeSource.getLocation();
userClassUrls.add(classLocation);
}
StringTokenizer tokenString = new StringTokenizer(System.getProperty("java.class.path"), File.pathSeparator); StringTokenizer tokenString = new StringTokenizer(System.getProperty("java.class.path"), File.pathSeparator);
String pathIgnore = System.getProperty("java.home"); String pathIgnore = System.getProperty("java.home");
if (pathIgnore == null) { if (pathIgnore == null) {
pathIgnore = classLocation.toString(); pathIgnore = userClassUrls.iterator().next().toString();
} }
List<URL> urls = new ArrayList<>(); List<URL> urls = new ArrayList<>();
while (tokenString.hasMoreElements()) { while (tokenString.hasMoreElements()) {
String value = tokenString.nextToken(); String value = tokenString.nextToken();
URL itemLocation = new File(value).toURI().toURL(); URL itemLocation = new File(value).toURI().toURL();
if (!itemLocation.equals(classLocation) && itemLocation.toString().indexOf(pathIgnore) >= 0) { if (!userClassUrls.contains(itemLocation) && itemLocation.toString().indexOf(pathIgnore) >= 0) {
urls.add(itemLocation); urls.add(itemLocation);
} }
} }
URL[] urlArray = urls.toArray(new URL[urls.size()]); URL[] urlArray = urls.toArray(new URL[urls.size()]);
ClassLoader masterClassLoader = URLClassLoader.newInstance(urlArray, null); ClassLoader masterClassLoader = URLClassLoader.newInstance(urlArray, null);
ClassLoader appClassLoader = URLClassLoader.newInstance(new URL[]{classLocation}, masterClassLoader); ClassLoader appClassLoader = URLClassLoader.newInstance(userClassUrls.toArray(new URL[0]), masterClassLoader);
return appClassLoader; return appClassLoader;
} }
@ -85,7 +98,11 @@ public class ObjectInputStreamWithClassLoaderTest extends ActiveMQTestBase {
AnObject obj = new AnObjectImpl(); AnObject obj = new AnObjectImpl();
byte[] bytes = ObjectInputStreamWithClassLoaderTest.toBytes(obj); byte[] bytes = ObjectInputStreamWithClassLoaderTest.toBytes(obj);
ClassLoader testClassLoader = ObjectInputStreamWithClassLoaderTest.newClassLoader(obj.getClass()); //Class.isAnonymousClass() call used in ObjectInputStreamWithClassLoader
//need to access the enclosing class and its parent class of the obj
//i.e. ActiveMQTestBase and Assert.
ClassLoader testClassLoader = ObjectInputStreamWithClassLoaderTest.newClassLoader(
obj.getClass(), ActiveMQTestBase.class, Assert.class);
Thread.currentThread().setContextClassLoader(testClassLoader); Thread.currentThread().setContextClassLoader(testClassLoader);
ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
@ -113,7 +130,8 @@ public class ObjectInputStreamWithClassLoaderTest extends ActiveMQTestBase {
originalProxy.setMyInt(100); originalProxy.setMyInt(100);
byte[] bytes = ObjectInputStreamWithClassLoaderTest.toBytes(originalProxy); byte[] bytes = ObjectInputStreamWithClassLoaderTest.toBytes(originalProxy);
ClassLoader testClassLoader = ObjectInputStreamWithClassLoaderTest.newClassLoader(this.getClass()); ClassLoader testClassLoader = ObjectInputStreamWithClassLoaderTest.newClassLoader(this.getClass(),
ActiveMQTestBase.class, Assert.class);
Thread.currentThread().setContextClassLoader(testClassLoader); Thread.currentThread().setContextClassLoader(testClassLoader);
ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStreamWithClassLoader ois = new ObjectInputStreamWithClassLoader(bais); ObjectInputStreamWithClassLoader ois = new ObjectInputStreamWithClassLoader(bais);
@ -132,6 +150,349 @@ public class ObjectInputStreamWithClassLoaderTest extends ActiveMQTestBase {
} }
@Test
public void testWhiteBlackList() throws Exception {
File serailizeFile = new File(temporaryFolder.getRoot(), "testclass.bin");
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(serailizeFile));
try {
outputStream.writeObject(new TestClass1());
outputStream.flush();
}
finally {
outputStream.close();
}
//default
assertNull(readSerializedObject(null, null, serailizeFile));
//white list
String whiteList = "org.apache.activemq.artemis.tests.unit.util.deserialization";
assertNull(readSerializedObject(whiteList, null, serailizeFile));
whiteList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1";
assertNull(readSerializedObject(whiteList, null, serailizeFile));
whiteList = "some.other.package";
Exception result = readSerializedObject(whiteList, null, serailizeFile);
assertTrue(result instanceof ClassNotFoundException);
//blacklist
String blackList = "org.apache.activemq.artemis.tests.unit.util";
result = readSerializedObject(null, blackList, serailizeFile);
assertTrue(result instanceof ClassNotFoundException);
blackList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1";
result = readSerializedObject(null, blackList, serailizeFile);
assertTrue(result instanceof ClassNotFoundException);
blackList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg2";
result = readSerializedObject(null, blackList, serailizeFile);
assertNull(result);
blackList = "some.other.package";
whiteList = "some.other.package1";
result = readSerializedObject(whiteList, blackList, serailizeFile);
assertTrue(result instanceof ClassNotFoundException);
//blacklist priority
blackList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1, some.other.package";
whiteList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1";
result = readSerializedObject(whiteList, blackList, serailizeFile);
assertTrue(result instanceof ClassNotFoundException);
blackList = "org.apache.activemq.artemis.tests.unit, some.other.package";
whiteList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1";
result = readSerializedObject(whiteList, blackList, serailizeFile);
assertTrue(result instanceof ClassNotFoundException);
blackList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1.pkg2, some.other.package";
whiteList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1";
result = readSerializedObject(whiteList, blackList, serailizeFile);
assertNull(result);
blackList = "some.other.package, org.apache.activemq.artemis.tests.unit.util.deserialization.pkg2";
whiteList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1";
result = readSerializedObject(whiteList, blackList, serailizeFile);
assertNull(result);
//wildcard
blackList = "*";
whiteList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1";
result = readSerializedObject(whiteList, blackList, serailizeFile);
assertTrue(result instanceof ClassNotFoundException);
blackList = "*";
whiteList = "*";
result = readSerializedObject(whiteList, blackList, serailizeFile);
assertTrue(result instanceof ClassNotFoundException);
result = readSerializedObject(whiteList, null, serailizeFile);
assertNull(result);
}
@Test
public void testWhiteBlackListAgainstArrayObject() throws Exception {
File serailizeFile = new File(temporaryFolder.getRoot(), "testclass.bin");
TestClass1[] sourceObject = new TestClass1[]{new TestClass1()};
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(serailizeFile));
try {
outputStream.writeObject(sourceObject);
outputStream.flush();
}
finally {
outputStream.close();
}
//default ok
String blackList = null;
String whiteList = null;
Object result = readSerializedObject(whiteList, blackList, serailizeFile);
assertNull(result);
//now blacklist TestClass1
blackList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1.TestClass1";
whiteList = null;
result = readSerializedObject(whiteList, blackList, serailizeFile);
assertTrue(result instanceof ClassNotFoundException);
//now whitelist TestClass1, it should pass.
blackList = null;
whiteList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1.TestClass1";
result = readSerializedObject(whiteList, blackList, serailizeFile);
assertNull(result);
}
@Test
public void testWhiteBlackListAgainstListObject() throws Exception {
File serailizeFile = new File(temporaryFolder.getRoot(), "testclass.bin");
List<TestClass1> sourceObject = new ArrayList<>();
sourceObject.add(new TestClass1());
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(serailizeFile));
try {
outputStream.writeObject(sourceObject);
outputStream.flush();
}
finally {
outputStream.close();
}
//default ok
String blackList = null;
String whiteList = null;
Object result = readSerializedObject(whiteList, blackList, serailizeFile);
assertNull(result);
//now blacklist TestClass1
blackList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1.TestClass1";
whiteList = null;
result = readSerializedObject(whiteList, blackList, serailizeFile);
assertTrue(result instanceof ClassNotFoundException);
//now whitelist TestClass1, should fail because the List type is not allowed
blackList = null;
whiteList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1.TestClass1";
result = readSerializedObject(whiteList, blackList, serailizeFile);
assertTrue(result instanceof ClassNotFoundException);
//now add List to white list, it should pass
blackList = null;
whiteList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1.TestClass1," +
"java.util.ArrayList";
result = readSerializedObject(whiteList, blackList, serailizeFile);
assertNull(result);
}
@Test
public void testWhiteBlackListAgainstListMapObject() throws Exception {
File serailizeFile = new File(temporaryFolder.getRoot(), "testclass.bin");
Map<TestClass1, TestClass2> sourceObject = new HashMap<>();
sourceObject.put(new TestClass1(), new TestClass2());
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(serailizeFile));
try {
outputStream.writeObject(sourceObject);
outputStream.flush();
}
finally {
outputStream.close();
}
String blackList = null;
String whiteList = null;
Object result = readSerializedObject(whiteList, blackList, serailizeFile);
assertNull(result);
//now blacklist the key
blackList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1.TestClass1";
whiteList = null;
result = readSerializedObject(whiteList, blackList, serailizeFile);
assertTrue(result instanceof ClassNotFoundException);
//now blacklist the value
blackList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1.TestClass2";
whiteList = null;
result = readSerializedObject(whiteList, blackList, serailizeFile);
assertTrue(result instanceof ClassNotFoundException);
//now white list the key, should fail too because value is forbidden
blackList = null;
whiteList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1.TestClass1";
result = readSerializedObject(whiteList, blackList, serailizeFile);
assertTrue(result instanceof ClassNotFoundException);
//now white list the value, should fail too because the key is forbidden
blackList = null;
whiteList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1.TestClass2";
result = readSerializedObject(whiteList, blackList, serailizeFile);
assertTrue(result instanceof ClassNotFoundException);
//both key and value are in the whitelist, it should fail because HashMap not permitted
blackList = null;
whiteList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1.TestClass1," +
"org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1.TestClass2";
result = readSerializedObject(whiteList, blackList, serailizeFile);
assertTrue(result instanceof ClassNotFoundException);
//now add HashMap, test should pass.
blackList = null;
whiteList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1.TestClass1," +
"org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1.TestClass2," +
"java.util.HashMap";
result = readSerializedObject(whiteList, blackList, serailizeFile);
assertNull(result);
}
@Test
public void testWhiteBlackListAnonymousObject() throws Exception {
File serailizeFile = new File(temporaryFolder.getRoot(), "testclass.bin");
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(serailizeFile));
try {
Serializable object = EnclosingClass.anonymousObject;
assertTrue(object.getClass().isAnonymousClass());
outputStream.writeObject(object);
outputStream.flush();
}
finally {
outputStream.close();
}
//default
String blackList = null;
String whiteList = null;
assertNull(readSerializedObject(whiteList, blackList, serailizeFile));
//forbidden by specifying the enclosing class
blackList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1.EnclosingClass";
Object result = readSerializedObject(whiteList, blackList, serailizeFile);
assertTrue(result instanceof ClassNotFoundException);
//do it in whiteList
blackList = null;
whiteList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1.EnclosingClass";
result = readSerializedObject(whiteList, blackList, serailizeFile);
assertNull(result);
}
@Test
public void testWhiteBlackListLocalObject() throws Exception {
File serailizeFile = new File(temporaryFolder.getRoot(), "testclass.bin");
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(serailizeFile));
try {
Object object = EnclosingClass.getLocalObject();
assertTrue(object.getClass().isLocalClass());
outputStream.writeObject(object);
outputStream.flush();
}
finally {
outputStream.close();
}
//default
String blackList = null;
String whiteList = null;
assertNull(readSerializedObject(whiteList, blackList, serailizeFile));
//forbidden by specifying the enclosing class
blackList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1.EnclosingClass";
Object result = readSerializedObject(whiteList, blackList, serailizeFile);
assertTrue(result instanceof ClassNotFoundException);
//do it in whiteList
blackList = null;
whiteList = "org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1.EnclosingClass";
result = readSerializedObject(whiteList, blackList, serailizeFile);
assertNull(result);
}
@Test
public void testWhiteBlackListSystemProperty() throws Exception {
File serailizeFile = new File(temporaryFolder.getRoot(), "testclass.bin");
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(serailizeFile));
try {
outputStream.writeObject(new TestClass1());
outputStream.flush();
}
finally {
outputStream.close();
}
System.setProperty(ObjectInputStreamWithClassLoader.BLACKLIST_PROPERTY, "system.defined.black.list");
System.setProperty(ObjectInputStreamWithClassLoader.WHITELIST_PROPERTY, "system.defined.white.list");
try {
ObjectInputStreamWithClassLoader ois = new ObjectInputStreamWithClassLoader(new FileInputStream(serailizeFile));
String bList = ois.getBlackList();
String wList = ois.getWhiteList();
assertEquals("wrong black list: " + bList, "system.defined.black.list", bList);
assertEquals("wrong white list: " + wList, "system.defined.white.list", wList);
ois.close();
}
finally {
System.clearProperty(ObjectInputStreamWithClassLoader.BLACKLIST_PROPERTY);
System.clearProperty(ObjectInputStreamWithClassLoader.WHITELIST_PROPERTY);
}
}
private Exception readSerializedObject(String whiteList, String blackList, File serailizeFile) {
Exception result = null;
ObjectInputStreamWithClassLoader ois = null;
try {
ois = new ObjectInputStreamWithClassLoader(new FileInputStream(serailizeFile));
ois.setWhiteList(whiteList);
ois.setBlackList(blackList);
ois.readObject();
}
catch (Exception e) {
result = e;
}
finally {
try {
ois.close();
}
catch (IOException e) {
result = e;
}
}
return result;
}
// Package protected --------------------------------------------- // Package protected ---------------------------------------------
// Protected ----------------------------------------------------- // Protected -----------------------------------------------------

View File

@ -0,0 +1,31 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1;
import java.io.Serializable;
public class EnclosingClass implements Serializable {
public static Serializable anonymousObject = new Serializable() {
};
public static Object getLocalObject() {
class LocalClass implements Serializable {
}
return new LocalClass();
}
}

View File

@ -0,0 +1,22 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1;
import java.io.Serializable;
public class TestClass1 implements Serializable {
}

View File

@ -0,0 +1,22 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.tests.unit.util.deserialization.pkg1;
import java.io.Serializable;
public class TestClass2 implements Serializable {
}