From 0535218cfcabfe521286f35c3471eeb3c9bb41dc Mon Sep 17 00:00:00 2001 From: Howard Gao Date: Mon, 1 Aug 2016 09:59:59 +0800 Subject: [PATCH] ARTEMIS-604 - Message Serialization Improvement - JMS and RA fixes --- .../ObjectInputStreamWithClassLoader.java | 142 ++++++- .../activemq/artemis/utils/StringUtil.java | 58 +++ .../activemq/artemis/util/StringUtilTest.java | 56 +++ .../jms/client/ActiveMQConnection.java | 19 +- .../jms/client/ActiveMQConnectionFactory.java | 34 +- .../artemis/jms/client/ActiveMQMessage.java | 7 +- .../jms/client/ActiveMQMessageConsumer.java | 17 +- .../jms/client/ActiveMQMessageProducer.java | 8 +- .../jms/client/ActiveMQObjectMessage.java | 40 +- .../jms/client/ActiveMQQueueBrowser.java | 8 +- .../artemis/jms/client/ActiveMQSession.java | 27 +- .../jms/client/ActiveMQXAConnection.java | 5 +- .../artemis/jms/client/ActiveMQXASession.java | 5 +- .../jms/client/ConnectionFactoryOptions.java | 33 ++ .../jms/client/JMSMessageListenerWrapper.java | 8 +- .../ConnectionFactoryConfiguration.java | 8 + .../ConnectionFactoryConfigurationImpl.java | 38 +- .../artemis/ra/ActiveMQResourceAdapter.java | 38 ++ .../ra/ConnectionFactoryProperties.java | 46 ++- .../artemis/ra/inflow/ActiveMQActivation.java | 2 +- .../ra/inflow/ActiveMQMessageHandler.java | 9 +- docs/user-manual/en/security.md | 74 ++++ .../jms/ActiveMQConnectionFactoryTest.java | 223 ++++++++++ .../jms/serializables/TestClass1.java | 22 + .../ra/ActiveMQMessageHandlerTest.java | 108 +++++ .../jms/tests/message/MessageHeaderTest.java | 2 +- .../ra/ActiveMQResourceAdapterConfigTest.java | 12 + .../tests/unit/ra/ResourceAdapterTest.java | 37 ++ .../ObjectInputStreamWithClassLoaderTest.java | 383 +++++++++++++++++- .../deserialization/pkg1/EnclosingClass.java | 31 ++ .../util/deserialization/pkg1/TestClass1.java | 22 + .../util/deserialization/pkg1/TestClass2.java | 22 + 32 files changed, 1483 insertions(+), 61 deletions(-) create mode 100644 artemis-core-client/src/main/java/org/apache/activemq/artemis/utils/StringUtil.java create mode 100644 artemis-core-client/src/test/java/org/apache/activemq/artemis/util/StringUtilTest.java create mode 100644 artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ConnectionFactoryOptions.java create mode 100644 tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/serializables/TestClass1.java create mode 100644 tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/util/deserialization/pkg1/EnclosingClass.java create mode 100644 tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/util/deserialization/pkg1/TestClass1.java create mode 100644 tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/util/deserialization/pkg1/TestClass2.java diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/utils/ObjectInputStreamWithClassLoader.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/utils/ObjectInputStreamWithClassLoader.java index ddc3564d83..6aa0f50842 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/utils/ObjectInputStreamWithClassLoader.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/utils/ObjectInputStreamWithClassLoader.java @@ -25,23 +25,79 @@ import java.lang.reflect.Proxy; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.List; public class ObjectInputStreamWithClassLoader extends ObjectInputStream { // 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 ----------------------------------------------------------------------------------- + private List whiteList = new ArrayList<>(); + private List blackList = new ArrayList<>(); + // Static --------------------------------------------------------------------------------------- // Constructors --------------------------------------------------------------------------------- public ObjectInputStreamWithClassLoader(final InputStream in) throws IOException { super(in); + String whiteList = System.getProperty(WHITELIST_PROPERTY, null); + setWhiteList(whiteList); + + String blackList = System.getProperty(BLACKLIST_PROPERTY, null); + setBlackList(blackList); } // 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 ---------------------------------------------------------------------------- // Protected ------------------------------------------------------------------------------------ @@ -97,14 +153,13 @@ public class ObjectInputStreamWithClassLoader extends ObjectInputStream { Class clazz = Class.forName(name, false, loader); // sanity check only.. if a classLoader can't find a clazz, it will throw an exception if (clazz == null) { - return super.resolveClass(desc); - } - else { - return clazz; + clazz = super.resolveClass(desc); } + + return checkSecurity(clazz); } catch (ClassNotFoundException e) { - return super.resolveClass(desc); + return checkSecurity(super.resolveClass(desc)); } } @@ -130,7 +185,7 @@ public class ObjectInputStreamWithClassLoader extends ObjectInputStream { classObjs[i] = cl; } try { - return Proxy.getProxyClass(hasNonPublicInterface ? nonPublicLoader : latestLoader, classObjs); + return checkSecurity(Proxy.getProxyClass(hasNonPublicInterface ? nonPublicLoader : latestLoader, classObjs)); } catch (IllegalArgumentException 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 -------------------------------------------------------------------------------- } diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/utils/StringUtil.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/utils/StringUtil.java new file mode 100644 index 0000000000..1b402098f7 --- /dev/null +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/utils/StringUtil.java @@ -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 strList, String delimit) { + Iterator 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 splitStringList(String strList, String delimit) { + ArrayList list = new ArrayList<>(); + if (strList != null && !strList.isEmpty()) { + list.addAll(Arrays.asList(strList.split(delimit))); + } + return list; + } +} diff --git a/artemis-core-client/src/test/java/org/apache/activemq/artemis/util/StringUtilTest.java b/artemis-core-client/src/test/java/org/apache/activemq/artemis/util/StringUtilTest.java new file mode 100644 index 0000000000..76f3ec198a --- /dev/null +++ b/artemis-core-client/src/test/java/org/apache/activemq/artemis/util/StringUtilTest.java @@ -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 strList = new ArrayList<>(); + strList.add("a"); + strList.add("bc"); + strList.add("def"); + String result = StringUtil.joinStringList(strList, ","); + assertEquals("a,bc,def", result); + + List 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 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); + } +} diff --git a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQConnection.java b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQConnection.java index 80597e3706..fc9c59d2fc 100644 --- a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQConnection.java +++ b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQConnection.java @@ -130,15 +130,20 @@ public class ActiveMQConnection extends ActiveMQConnectionForContextImpl impleme private ActiveMQConnectionFactory factoryReference; + private final ConnectionFactoryOptions options; + // Constructors --------------------------------------------------------------------------------- - public ActiveMQConnection(final String username, + public ActiveMQConnection(final ConnectionFactoryOptions options, + final String username, final String password, final int connectionType, final String clientID, final int dupsOKBatchSize, final int transactionBatchSize, final ClientSessionFactory sessionFactory) { + this.options = options; + this.username = username; this.password = password; @@ -651,10 +656,10 @@ public class ActiveMQConnection extends ActiveMQConnectionForContextImpl impleme ClientSession session, int type) { if (isXA) { - return new ActiveMQXASession(this, transacted, true, acknowledgeMode, session, type); + return new ActiveMQXASession(options, this, transacted, true, acknowledgeMode, session, type); } 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; } + public String getDeserializationBlackList() { + return this.factoryReference.getDeserializationBlackList(); + } + + public String getDeserializationWhiteList() { + return this.factoryReference.getDeserializationWhiteList(); + } + // Inner classes -------------------------------------------------------------------------------- private static class JMSFailureListener implements SessionFailureListener { diff --git a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQConnectionFactory.java b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQConnectionFactory.java index ce2aa3a488..183745d555 100644 --- a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQConnectionFactory.java +++ b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQConnectionFactory.java @@ -61,7 +61,7 @@ import org.apache.activemq.artemis.utils.ClassloadingUtil; *

ActiveMQ Artemis implementation of a JMS ConnectionFactory.

*

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; @@ -79,6 +79,10 @@ public class ActiveMQConnectionFactory implements Externalizable, Referenceable, private String protocolManagerFactoryStr; + private String deserializationBlackList; + + private String deserializationWhiteList; + @Override public void writeExternal(ObjectOutput out) throws IOException { 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 public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { String url = in.readUTF(); @@ -744,24 +764,24 @@ public class ActiveMQConnectionFactory implements Externalizable, Referenceable, if (isXA) { 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) { - 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) { - 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_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) { - 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) { - connection = new ActiveMQConnection(username, password, type, clientID, dupsOKBatchSize, transactionBatchSize, factory); + connection = new ActiveMQConnection(this, username, password, type, clientID, dupsOKBatchSize, transactionBatchSize, factory); } } diff --git a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQMessage.java b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQMessage.java index 2dd0df1f68..ab9e311b97 100644 --- a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQMessage.java +++ b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQMessage.java @@ -121,6 +121,10 @@ public class ActiveMQMessage implements javax.jms.Message { } 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(); ActiveMQMessage msg; @@ -142,7 +146,7 @@ public class ActiveMQMessage implements javax.jms.Message { break; } case ActiveMQObjectMessage.TYPE: { - msg = new ActiveMQObjectMessage(message, session); + msg = new ActiveMQObjectMessage(message, session, options); break; } case ActiveMQStreamMessage.TYPE: // 6 @@ -202,7 +206,6 @@ public class ActiveMQMessage implements javax.jms.Message { */ protected ActiveMQMessage(final byte type, final ClientSession session) { message = session.createMessage(type, true, 0, System.currentTimeMillis(), (byte) 4); - } protected ActiveMQMessage(final ClientSession session) { diff --git a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQMessageConsumer.java b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQMessageConsumer.java index 53242f1beb..5fbf4489b1 100644 --- a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQMessageConsumer.java +++ b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQMessageConsumer.java @@ -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.client.ClientConsumer; 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.jms.ActiveMQJMSConstants; 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 { + private final ConnectionFactoryOptions options; + private final ClientConsumer consumer; private MessageListener listener; @@ -63,13 +66,16 @@ public final class ActiveMQMessageConsumer implements QueueReceiver, TopicSubscr // Constructors -------------------------------------------------- - protected ActiveMQMessageConsumer(final ActiveMQConnection connection, + protected ActiveMQMessageConsumer(final ConnectionFactoryOptions options, + final ActiveMQConnection connection, final ActiveMQSession session, final ClientConsumer consumer, final boolean noLocal, final ActiveMQDestination destination, final String selector, final SimpleString autoDeleteQueueName) throws JMSException { + this.options = options; + this.connection = connection; this.session = session; @@ -107,7 +113,7 @@ public final class ActiveMQMessageConsumer implements QueueReceiver, TopicSubscr public void setMessageListener(final MessageListener listener) throws JMSException { 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 { consumer.setMessageHandler(coreListener); @@ -211,8 +217,11 @@ public final class ActiveMQMessageConsumer implements QueueReceiver, TopicSubscr ActiveMQMessage jmsMsg = null; if (coreMessage != null) { - boolean needSession = ackMode == Session.CLIENT_ACKNOWLEDGE || ackMode == ActiveMQJMSConstants.INDIVIDUAL_ACKNOWLEDGE; - jmsMsg = ActiveMQMessage.createMessage(coreMessage, needSession ? session.getCoreSession() : null); + ClientSession coreSession = session.getCoreSession(); + boolean needSession = ackMode == Session.CLIENT_ACKNOWLEDGE || + ackMode == ActiveMQJMSConstants.INDIVIDUAL_ACKNOWLEDGE || + coreMessage.getType() == ActiveMQObjectMessage.TYPE; + jmsMsg = ActiveMQMessage.createMessage(coreMessage, needSession ? coreSession : null, options); try { jmsMsg.doBeforeReceive(); diff --git a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQMessageProducer.java b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQMessageProducer.java index 5a0d5c3573..8a47e8c3cf 100644 --- a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQMessageProducer.java +++ b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQMessageProducer.java @@ -49,6 +49,8 @@ import org.apache.activemq.artemis.utils.UUIDGenerator; */ public class ActiveMQMessageProducer implements MessageProducer, QueueSender, TopicPublisher { + private final ConnectionFactoryOptions options; + private final ActiveMQConnection connection; private final SimpleString connID; @@ -71,7 +73,9 @@ public class ActiveMQMessageProducer implements MessageProducer, QueueSender, To protected ActiveMQMessageProducer(final ActiveMQConnection connection, final ClientProducer producer, final ActiveMQDestination defaultDestination, - final ClientSession clientSession) throws JMSException { + final ClientSession clientSession, + final ConnectionFactoryOptions options) throws JMSException { + this.options = options; this.connection = connection; 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); } else if (jmsMessage instanceof ObjectMessage) { - activeMQJmsMessage = new ActiveMQObjectMessage((ObjectMessage) jmsMessage, clientSession); + activeMQJmsMessage = new ActiveMQObjectMessage((ObjectMessage) jmsMessage, clientSession, options); } else if (jmsMessage instanceof StreamMessage) { activeMQJmsMessage = new ActiveMQStreamMessage((StreamMessage) jmsMessage, clientSession); diff --git a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQObjectMessage.java b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQObjectMessage.java index c8b121339c..0e6bbda2f8 100644 --- a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQObjectMessage.java +++ b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQObjectMessage.java @@ -21,7 +21,6 @@ import javax.jms.MessageFormatException; import javax.jms.ObjectMessage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.ObjectInputStream; import java.io.ObjectOutputStream; 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 private byte[] data; + private final ConnectionFactoryOptions options; + // Static -------------------------------------------------------- // Constructors -------------------------------------------------- - protected ActiveMQObjectMessage(final ClientSession session) { + protected ActiveMQObjectMessage(final ClientSession session, ConnectionFactoryOptions options) { 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); + this.options = options; } /** * 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); setObject(foreign.getObject()); + this.options = options; } // Public -------------------------------------------------------- @@ -135,7 +139,15 @@ public class ActiveMQObjectMessage extends ActiveMQMessage implements ObjectMess 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(); return object; } @@ -174,4 +186,22 @@ public class ActiveMQObjectMessage extends ActiveMQMessage implements ObjectMess 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(); + } + } } diff --git a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQQueueBrowser.java b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQQueueBrowser.java index 231a681344..adb22192ef 100644 --- a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQQueueBrowser.java +++ b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQQueueBrowser.java @@ -40,6 +40,8 @@ public final class ActiveMQQueueBrowser implements QueueBrowser { // Attributes ----------------------------------------------------------------------------------- + private final ConnectionFactoryOptions options; + private final ClientSession session; private ClientConsumer consumer; @@ -50,9 +52,11 @@ public final class ActiveMQQueueBrowser implements QueueBrowser { // Constructors --------------------------------------------------------------------------------- - protected ActiveMQQueueBrowser(final ActiveMQQueue queue, + protected ActiveMQQueueBrowser(final ConnectionFactoryOptions options, + final ActiveMQQueue queue, final String messageSelector, final ClientSession session) throws JMSException { + this.options = options; this.session = session; this.queue = queue; if (messageSelector != null) { @@ -137,7 +141,7 @@ public final class ActiveMQQueueBrowser implements QueueBrowser { if (hasMoreElements()) { ClientMessage next = current; current = null; - msg = ActiveMQMessage.createMessage(next, session); + msg = ActiveMQMessage.createMessage(next, session, options); try { msg.doBeforeReceive(); } diff --git a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQSession.java b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQSession.java index ee81cf19c8..3ba2945d11 100644 --- a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQSession.java +++ b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQSession.java @@ -77,6 +77,8 @@ public class ActiveMQSession implements QueueSession, TopicSession { private static SimpleString REJECTING_FILTER = new SimpleString("_AMQX=-1"); + private final ConnectionFactoryOptions options; + private final ActiveMQConnection connection; private final ClientSession session; @@ -95,12 +97,15 @@ public class ActiveMQSession implements QueueSession, TopicSession { // Constructors -------------------------------------------------- - protected ActiveMQSession(final ActiveMQConnection connection, + protected ActiveMQSession(final ConnectionFactoryOptions options, + final ActiveMQConnection connection, final boolean transacted, final boolean xa, final int ackMode, final ClientSession session, final int sessionType) { + this.options = options; + this.connection = connection; this.ackMode = ackMode; @@ -141,14 +146,14 @@ public class ActiveMQSession implements QueueSession, TopicSession { public ObjectMessage createObjectMessage() throws JMSException { checkClosed(); - return new ActiveMQObjectMessage(session); + return new ActiveMQObjectMessage(session, options); } @Override public ObjectMessage createObjectMessage(final Serializable object) throws JMSException { checkClosed(); - ActiveMQObjectMessage msg = new ActiveMQObjectMessage(session); + ActiveMQObjectMessage msg = new ActiveMQObjectMessage(session, options); msg.setObject(object); @@ -308,7 +313,7 @@ public class ActiveMQSession implements QueueSession, TopicSession { 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) { throw JMSExceptionHelper.convertFromActiveMQException(e); @@ -522,6 +527,14 @@ public class ActiveMQSession implements QueueSession, TopicSession { return internalCreateSharedConsumer(localTopic, name, messageSelector, ConsumerDurability.DURABLE); } + public String getDeserializationBlackList() { + return connection.getDeserializationBlackList(); + } + + public String getDeserializationWhiteList() { + return connection.getDeserializationWhiteList(); + } + enum ConsumerDurability { DURABLE, NON_DURABLE; } @@ -587,7 +600,7 @@ public class ActiveMQSession implements QueueSession, TopicSession { 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); @@ -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); @@ -806,7 +819,7 @@ public class ActiveMQSession implements QueueSession, TopicSession { throw JMSExceptionHelper.convertFromActiveMQException(e); } - return new ActiveMQQueueBrowser((ActiveMQQueue) jbq, filterString, session); + return new ActiveMQQueueBrowser(options, (ActiveMQQueue) jbq, filterString, session); } diff --git a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQXAConnection.java b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQXAConnection.java index bae4e89f1a..4407fbbb73 100644 --- a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQXAConnection.java +++ b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQXAConnection.java @@ -34,14 +34,15 @@ import org.apache.activemq.artemis.api.core.client.ClientSessionFactory; */ 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 int connectionType, final String clientID, final int dupsOKBatchSize, final int transactionBatchSize, final ClientSessionFactory sessionFactory) { - super(username, password, connectionType, clientID, dupsOKBatchSize, transactionBatchSize, sessionFactory); + super(options, username, password, connectionType, clientID, dupsOKBatchSize, transactionBatchSize, sessionFactory); } @Override diff --git a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQXASession.java b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQXASession.java index 815b488d98..4a7694fc8e 100644 --- a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQXASession.java +++ b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQXASession.java @@ -31,12 +31,13 @@ public class ActiveMQXASession extends ActiveMQSession implements XAQueueSession * @param session * @param sessionType */ - protected ActiveMQXASession(ActiveMQConnection connection, + protected ActiveMQXASession(final ConnectionFactoryOptions options, + ActiveMQConnection connection, boolean transacted, boolean xa, int ackMode, ClientSession session, int sessionType) { - super(connection, transacted, xa, ackMode, session, sessionType); + super(options, connection, transacted, xa, ackMode, session, sessionType); } } diff --git a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ConnectionFactoryOptions.java b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ConnectionFactoryOptions.java new file mode 100644 index 0000000000..2f27b15057 --- /dev/null +++ b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ConnectionFactoryOptions.java @@ -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); + +} diff --git a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/JMSMessageListenerWrapper.java b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/JMSMessageListenerWrapper.java index af5b158762..91ea34f568 100644 --- a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/JMSMessageListenerWrapper.java +++ b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/JMSMessageListenerWrapper.java @@ -28,6 +28,7 @@ import org.apache.activemq.artemis.core.client.impl.ClientSessionInternal; public class JMSMessageListenerWrapper implements MessageHandler { + private final ConnectionFactoryOptions options; private final ActiveMQConnection connection; private final ActiveMQSession session; @@ -40,11 +41,14 @@ public class JMSMessageListenerWrapper implements MessageHandler { private final boolean individualACK; - protected JMSMessageListenerWrapper(final ActiveMQConnection connection, + protected JMSMessageListenerWrapper(final ConnectionFactoryOptions options, + final ActiveMQConnection connection, final ActiveMQSession session, final ClientConsumer consumer, final MessageListener listener, final int ackMode) { + this.options = options; + this.connection = connection; this.session = session; @@ -64,7 +68,7 @@ public class JMSMessageListenerWrapper implements MessageHandler { */ @Override public void onMessage(final ClientMessage message) { - ActiveMQMessage msg = ActiveMQMessage.createMessage(message, session.getCoreSession()); + ActiveMQMessage msg = ActiveMQMessage.createMessage(message, session.getCoreSession(), options); if (individualACK) { msg.setIndividualAcknowledge(); diff --git a/artemis-jms-server/src/main/java/org/apache/activemq/artemis/jms/server/config/ConnectionFactoryConfiguration.java b/artemis-jms-server/src/main/java/org/apache/activemq/artemis/jms/server/config/ConnectionFactoryConfiguration.java index ab4990c4da..edba5d1a94 100644 --- a/artemis-jms-server/src/main/java/org/apache/activemq/artemis/jms/server/config/ConnectionFactoryConfiguration.java +++ b/artemis-jms-server/src/main/java/org/apache/activemq/artemis/jms/server/config/ConnectionFactoryConfiguration.java @@ -177,4 +177,12 @@ public interface ConnectionFactoryConfiguration extends EncodingSupport { String getProtocolManagerFactoryStr(); JMSFactoryType getFactoryType(); + + String getDeserializationBlackList(); + + void setDeserializationBlackList(String blackList); + + String getDeserializationWhiteList(); + + void setDeserializationWhiteList(String whiteList); } diff --git a/artemis-jms-server/src/main/java/org/apache/activemq/artemis/jms/server/config/impl/ConnectionFactoryConfigurationImpl.java b/artemis-jms-server/src/main/java/org/apache/activemq/artemis/jms/server/config/impl/ConnectionFactoryConfigurationImpl.java index 74fcdf47c0..b72a566c55 100644 --- a/artemis-jms-server/src/main/java/org/apache/activemq/artemis/jms/server/config/impl/ConnectionFactoryConfigurationImpl.java +++ b/artemis-jms-server/src/main/java/org/apache/activemq/artemis/jms/server/config/impl/ConnectionFactoryConfigurationImpl.java @@ -118,6 +118,10 @@ public class ConnectionFactoryConfigurationImpl implements ConnectionFactoryConf private JMSFactoryType factoryType = JMSFactoryType.CF; + private String deserializationBlackList; + + private String deserializationWhiteList; + // Static -------------------------------------------------------- // Constructors -------------------------------------------------- @@ -614,6 +618,10 @@ public class ConnectionFactoryConfigurationImpl implements ConnectionFactoryConf factoryType = JMSFactoryType.valueOf(buffer.readInt()); protocolManagerFactoryStr = BufferHelper.readNullableSimpleStringAsString(buffer); + + deserializationBlackList = BufferHelper.readNullableSimpleStringAsString(buffer); + + deserializationWhiteList = BufferHelper.readNullableSimpleStringAsString(buffer); } @Override @@ -700,6 +708,10 @@ public class ConnectionFactoryConfigurationImpl implements ConnectionFactoryConf buffer.writeInt(factoryType.intValue()); BufferHelper.writeAsNullableSimpleString(buffer, protocolManagerFactoryStr); + + BufferHelper.writeAsNullableSimpleString(buffer, deserializationBlackList); + + BufferHelper.writeAsNullableSimpleString(buffer, deserializationWhiteList); } @Override @@ -809,7 +821,11 @@ public class ConnectionFactoryConfigurationImpl implements ConnectionFactoryConf DataConstants.SIZE_INT + // factoryType - BufferHelper.sizeOfNullableSimpleString(protocolManagerFactoryStr); + BufferHelper.sizeOfNullableSimpleString(protocolManagerFactoryStr) + + + BufferHelper.sizeOfNullableSimpleString(deserializationBlackList) + + + BufferHelper.sizeOfNullableSimpleString(deserializationWhiteList); return size; } @@ -825,6 +841,26 @@ public class ConnectionFactoryConfigurationImpl implements ConnectionFactoryConf 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 public ConnectionFactoryConfiguration setCompressLargeMessages(boolean compressLargeMessage) { this.compressLargeMessage = compressLargeMessage; diff --git a/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/ActiveMQResourceAdapter.java b/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/ActiveMQResourceAdapter.java index 209ae75375..2804de2299 100644 --- a/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/ActiveMQResourceAdapter.java +++ b/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/ActiveMQResourceAdapter.java @@ -901,6 +901,36 @@ public class ActiveMQResourceAdapter implements ResourceAdapter, Serializable { 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 * @@ -2004,6 +2034,14 @@ public class ActiveMQResourceAdapter implements ResourceAdapter, Serializable { if (val5 != null) { 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) { diff --git a/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/ConnectionFactoryProperties.java b/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/ConnectionFactoryProperties.java index 21371865fb..ff7817eb3f 100644 --- a/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/ConnectionFactoryProperties.java +++ b/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/ConnectionFactoryProperties.java @@ -19,7 +19,9 @@ package org.apache.activemq.artemis.ra; import java.util.List; import java.util.Map; -public class ConnectionFactoryProperties { +import org.apache.activemq.artemis.jms.client.ConnectionFactoryOptions; + +public class ConnectionFactoryProperties implements ConnectionFactoryOptions { /** * Trace enabled @@ -120,6 +122,10 @@ public class ConnectionFactoryProperties { private String protocolManagerFactoryStr; + private String deserializationBlackList; + + private String deserializationWhiteList; + /** * @return the transportType */ @@ -689,6 +695,28 @@ public class ConnectionFactoryProperties { 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() { return hasBeenUpdated; } @@ -960,6 +988,20 @@ public class ConnectionFactoryProperties { } else if (!connectionParameters.equals(other.connectionParameters)) 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; } @@ -1010,6 +1052,8 @@ public class ConnectionFactoryProperties { result = prime * result + ((groupID == null) ? 0 : groupID.hashCode()); result = prime * result + ((connectorClassName == null) ? 0 : connectorClassName.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; } } diff --git a/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/inflow/ActiveMQActivation.java b/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/inflow/ActiveMQActivation.java index 510e6803c6..a9057d0a5b 100644 --- a/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/inflow/ActiveMQActivation.java +++ b/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/inflow/ActiveMQActivation.java @@ -315,7 +315,7 @@ public class ActiveMQActivation { try { cf = factory.getServerLocator().createSessionFactory(); 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(); handlers.add(handler); } diff --git a/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/inflow/ActiveMQMessageHandler.java b/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/inflow/ActiveMQMessageHandler.java index b0d64ccf5c..353bc73d55 100644 --- a/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/inflow/ActiveMQMessageHandler.java +++ b/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/inflow/ActiveMQMessageHandler.java @@ -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.jms.client.ActiveMQDestination; 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.ActiveMQResourceAdapter; import org.apache.activemq.artemis.service.extensions.ServiceUtils; @@ -68,6 +69,8 @@ public class ActiveMQMessageHandler implements MessageHandler, FailoverEventList */ private MessageEndpoint endpoint; + private final ConnectionFactoryOptions options; + private final ActiveMQActivation activation; private boolean useLocalTx; @@ -84,11 +87,13 @@ public class ActiveMQMessageHandler implements MessageHandler, FailoverEventList private volatile boolean connected; - public ActiveMQMessageHandler(final ActiveMQActivation activation, + public ActiveMQMessageHandler(final ConnectionFactoryOptions options, + final ActiveMQActivation activation, final TransactionManager tm, final ClientSessionInternal session, final ClientSessionFactory cf, final int sessionNr) { + this.options = options; this.activation = activation; this.session = session; this.cf = cf; @@ -286,7 +291,7 @@ public class ActiveMQMessageHandler implements MessageHandler, FailoverEventList ActiveMQRALogger.LOGGER.trace("onMessage(" + message + ")"); } - ActiveMQMessage msg = ActiveMQMessage.createMessage(message, session); + ActiveMQMessage msg = ActiveMQMessage.createMessage(message, session, options); boolean beforeDelivery = false; try { diff --git a/docs/user-manual/en/security.md b/docs/user-manual/en/security.md index 0f6517a262..4b06f25081 100644 --- a/docs/user-manual/en/security.md +++ b/docs/user-manual/en/security.md @@ -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`. - `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. + + + + + + + + diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/ActiveMQConnectionFactoryTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/ActiveMQConnectionFactoryTest.java index 9e8bc8fae2..a40cb07be7 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/ActiveMQConnectionFactoryTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/ActiveMQConnectionFactoryTest.java @@ -16,22 +16,34 @@ */ 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.jms.serializables.TestClass1; 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.core.config.ha.SharedStoreMasterPolicyConfiguration; import org.junit.Before; import org.junit.Test; +import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; +import java.util.Hashtable; import java.util.List; import java.util.Map; import javax.jms.Connection; 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.naming.Context; +import javax.naming.InitialContext; import org.junit.Assert; @@ -213,6 +225,217 @@ public class ActiveMQConnectionFactoryTest extends ActiveMQTestBase { 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 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) { String clientID = RandomUtil.randomString(); diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/serializables/TestClass1.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/serializables/TestClass1.java new file mode 100644 index 0000000000..ec73aa5ba9 --- /dev/null +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/serializables/TestClass1.java @@ -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 { +} diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ra/ActiveMQMessageHandlerTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ra/ActiveMQMessageHandlerTest.java index 0aab4c5968..5101cd8449 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ra/ActiveMQMessageHandlerTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ra/ActiveMQMessageHandlerTest.java @@ -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.postoffice.Binding; 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.inflow.ActiveMQActivation; import org.apache.activemq.artemis.ra.inflow.ActiveMQActivationSpec; import org.apache.activemq.artemis.tests.integration.IntegrationTestLogger; import org.junit.Test; +import javax.jms.Connection; +import javax.jms.JMSException; 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.spi.InvalidPropertyException; +import java.io.Serializable; import java.lang.reflect.Method; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -78,6 +86,103 @@ public class ActiveMQMessageHandlerTest extends ActiveMQRATestBase { 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 public void testSimpleMessageReceivedOnQueueManyMessages() throws Exception { ActiveMQResourceAdapter qResourceAdapter = newResourceAdapter(); @@ -863,4 +968,7 @@ public class ActiveMQMessageHandlerTest extends ActiveMQRATestBase { } } } + + static class DummySerializable implements Serializable { + } } diff --git a/tests/jms-tests/src/test/java/org/apache/activemq/artemis/jms/tests/message/MessageHeaderTest.java b/tests/jms-tests/src/test/java/org/apache/activemq/artemis/jms/tests/message/MessageHeaderTest.java index ad2e8d20d1..dcb5d2f1fa 100644 --- a/tests/jms-tests/src/test/java/org/apache/activemq/artemis/jms/tests/message/MessageHeaderTest.java +++ b/tests/jms-tests/src/test/java/org/apache/activemq/artemis/jms/tests/message/MessageHeaderTest.java @@ -739,7 +739,7 @@ public class MessageHeaderTest extends MessageHeaderTestBase { ObjectMessage foreignObjectMessage = new SimpleJMSObjectMessage(); - ActiveMQObjectMessage copy = new ActiveMQObjectMessage(foreignObjectMessage, session); + ActiveMQObjectMessage copy = new ActiveMQObjectMessage(foreignObjectMessage, session, null); MessageHeaderTestBase.ensureEquivalent(foreignObjectMessage, copy); } diff --git a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/ra/ActiveMQResourceAdapterConfigTest.java b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/ra/ActiveMQResourceAdapterConfigTest.java index bfdeaa279f..7b5ecf87d4 100644 --- a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/ra/ActiveMQResourceAdapterConfigTest.java +++ b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/ra/ActiveMQResourceAdapterConfigTest.java @@ -390,6 +390,18 @@ public class ActiveMQResourceAdapterConfigTest extends ActiveMQTestBase { " ProtocolManagerFactoryStr" + " java.lang.String" + " " + + " " + + " " + + " List of package/class names against which matching objects are permitted to be deserilized" + + " DeserializationWhiteList" + + " java.lang.String" + + " " + + " " + + " " + + " List of package/classe names against which matching objects are forbidden to be deserialized" + + " DeserializationBlackList" + + " java.lang.String" + + " " + " "; private static String rootConfig = "" + config + commentedOutConfigs + ""; diff --git a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/ra/ResourceAdapterTest.java b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/ra/ResourceAdapterTest.java index 3932f7550e..eb2bb600a8 100644 --- a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/ra/ResourceAdapterTest.java +++ b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/ra/ResourceAdapterTest.java @@ -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 public void testForConnectionLeakDuringActivationWhenSessionCreationFails() throws Exception { ActiveMQServer server = createServer(false); diff --git a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/util/ObjectInputStreamWithClassLoaderTest.java b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/util/ObjectInputStreamWithClassLoaderTest.java index 19d55866e2..00720cd6e2 100644 --- a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/util/ObjectInputStreamWithClassLoaderTest.java +++ b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/util/ObjectInputStreamWithClassLoaderTest.java @@ -16,12 +16,16 @@ */ 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.junit.Test; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; @@ -33,12 +37,17 @@ import java.net.URLClassLoader; import java.security.CodeSource; import java.security.ProtectionDomain; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.StringTokenizer; import org.junit.Assert; import org.apache.activemq.artemis.utils.ObjectInputStreamWithClassLoader; +import org.junit.Test; public class ObjectInputStreamWithClassLoaderTest extends ActiveMQTestBase { // Constants ----------------------------------------------------- @@ -47,29 +56,33 @@ public class ObjectInputStreamWithClassLoaderTest extends ActiveMQTestBase { // Static -------------------------------------------------------- - public static ClassLoader newClassLoader(final Class anyUserClass) throws Exception { - ProtectionDomain protectionDomain = anyUserClass.getProtectionDomain(); - CodeSource codeSource = protectionDomain.getCodeSource(); - URL classLocation = codeSource.getLocation(); + public static ClassLoader newClassLoader(final Class... userClasses) throws Exception { + + Set userClassUrls = new HashSet<>(); + 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); String pathIgnore = System.getProperty("java.home"); if (pathIgnore == null) { - pathIgnore = classLocation.toString(); + pathIgnore = userClassUrls.iterator().next().toString(); } List urls = new ArrayList<>(); while (tokenString.hasMoreElements()) { String value = tokenString.nextToken(); 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); } } - URL[] urlArray = urls.toArray(new URL[urls.size()]); 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; } @@ -85,7 +98,11 @@ public class ObjectInputStreamWithClassLoaderTest extends ActiveMQTestBase { AnObject obj = new AnObjectImpl(); 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); ByteArrayInputStream bais = new ByteArrayInputStream(bytes); @@ -113,7 +130,8 @@ public class ObjectInputStreamWithClassLoaderTest extends ActiveMQTestBase { originalProxy.setMyInt(100); 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); ByteArrayInputStream bais = new ByteArrayInputStream(bytes); 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 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 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 --------------------------------------------- // Protected ----------------------------------------------------- diff --git a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/util/deserialization/pkg1/EnclosingClass.java b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/util/deserialization/pkg1/EnclosingClass.java new file mode 100644 index 0000000000..379da857af --- /dev/null +++ b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/util/deserialization/pkg1/EnclosingClass.java @@ -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(); + } +} diff --git a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/util/deserialization/pkg1/TestClass1.java b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/util/deserialization/pkg1/TestClass1.java new file mode 100644 index 0000000000..3c6cbef301 --- /dev/null +++ b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/util/deserialization/pkg1/TestClass1.java @@ -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 { +} diff --git a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/util/deserialization/pkg1/TestClass2.java b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/util/deserialization/pkg1/TestClass2.java new file mode 100644 index 0000000000..afc52ccc76 --- /dev/null +++ b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/util/deserialization/pkg1/TestClass2.java @@ -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 { +}