QPIDJMS-188 Further improvements to the ObjectMessage handling. Project: http://git-wip-us.apache.org/repos/asf/qpid-jms/repo Commit: http://git-wip-us.apache.org/repos/asf/qpid-jms/commit/669cfff8 Tree: http://git-wip-us.apache.org/repos/asf/qpid-jms/tree/669cfff8 Diff: http://git-wip-us.apache.org/repos/asf/qpid-jms/diff/669cfff8
Branch: refs/heads/master Commit: 669cfff838d2798fa89b9db546823e6245433d4e Parents: b05d577 Author: Timothy Bish <[email protected]> Authored: Mon Jun 27 11:31:44 2016 -0400 Committer: Timothy Bish <[email protected]> Committed: Mon Jun 27 11:31:44 2016 -0400 ---------------------------------------------------------------------- .../java/org/apache/qpid/jms/JmsConnection.java | 9 + .../apache/qpid/jms/JmsConnectionFactory.java | 23 + .../org/apache/qpid/jms/JmsMessageConsumer.java | 3 + .../java/org/apache/qpid/jms/JmsSession.java | 6 + .../apache/qpid/jms/meta/JmsConnectionInfo.java | 15 + .../apache/qpid/jms/meta/JmsConsumerInfo.java | 15 + .../apache/qpid/jms/meta/JmsSessionInfo.java | 15 + .../policy/JmsDefaultDeserializationPolicy.java | 244 ++++++++ .../jms/policy/JmsDeserializationPolicy.java | 45 ++ .../amqp/message/AmqpJmsMessageFacade.java | 4 + .../amqp/message/AmqpJmsMessageFactory.java | 3 +- .../message/AmqpJmsObjectMessageFacade.java | 31 +- .../amqp/message/AmqpObjectTypeDelegate.java | 10 + .../message/AmqpSerializedObjectDelegate.java | 59 +- .../amqp/message/AmqpTypedObjectDelegate.java | 58 +- .../ClassLoadingAwareObjectInputStream.java | 57 +- .../qpid/jms/JmsConnectionFactoryTest.java | 75 ++- .../ObjectMessageIntegrationTest.java | 125 +++- .../facade/test/JmsTestObjectMessageFacade.java | 2 +- .../JmsDefaultDeserializationPolicyTest.java | 328 +++++++++++ .../amqp/message/AmqpJmsMessageBuilderTest.java | 5 + .../amqp/message/AmqpJmsMessageFactoryTest.java | 11 +- .../message/AmqpJmsMessageTypesTestCase.java | 15 +- .../jms/util/AnonymousSimplePojoParent.java | 39 ++ .../ClassLoadingAwareObjectInputStreamTest.java | 569 +++++++++++++++++++ .../qpid/jms/util/LocalSimplePojoParent.java | 46 ++ .../apache/qpid/jms/util/PropertyUtilTest.java | 11 + .../org/apache/qpid/jms/util/SimplePojo.java | 73 +++ .../apache/qpid/jms/util/URISupportTest.java | 45 +- qpid-jms-docs/Configuration.md | 11 +- 30 files changed, 1897 insertions(+), 55 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnection.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnection.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnection.java index 7375c3f..827da11 100644 --- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnection.java +++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnection.java @@ -66,6 +66,7 @@ import org.apache.qpid.jms.meta.JmsSessionId; import org.apache.qpid.jms.meta.JmsSessionInfo; import org.apache.qpid.jms.meta.JmsTransactionId; import org.apache.qpid.jms.meta.JmsTransactionInfo; +import org.apache.qpid.jms.policy.JmsDeserializationPolicy; import org.apache.qpid.jms.policy.JmsMessageIDPolicy; import org.apache.qpid.jms.policy.JmsPrefetchPolicy; import org.apache.qpid.jms.policy.JmsPresettlePolicy; @@ -873,6 +874,14 @@ public class JmsConnection implements AutoCloseable, Connection, TopicConnection connectionInfo.setPresettlePolicy(presettlePolicy); } + public JmsDeserializationPolicy getDeserializationPolicy() { + return connectionInfo.getDeserializationPolicy(); + } + + public void setDeserializationPolicy(JmsDeserializationPolicy deserializationPolicy) { + connectionInfo.setDeserializationPolicy(deserializationPolicy); + } + public boolean isReceiveLocalOnly() { return connectionInfo.isReceiveLocalOnly(); } http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnectionFactory.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnectionFactory.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnectionFactory.java index 32cb39a..0f2c1ba 100644 --- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnectionFactory.java +++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnectionFactory.java @@ -35,10 +35,12 @@ import org.apache.qpid.jms.exceptions.JmsExceptionSupport; import org.apache.qpid.jms.jndi.JNDIStorable; import org.apache.qpid.jms.message.JmsMessageIDBuilder; import org.apache.qpid.jms.meta.JmsConnectionInfo; +import org.apache.qpid.jms.policy.JmsDefaultDeserializationPolicy; import org.apache.qpid.jms.policy.JmsDefaultMessageIDPolicy; import org.apache.qpid.jms.policy.JmsDefaultPrefetchPolicy; import org.apache.qpid.jms.policy.JmsDefaultPresettlePolicy; import org.apache.qpid.jms.policy.JmsDefaultRedeliveryPolicy; +import org.apache.qpid.jms.policy.JmsDeserializationPolicy; import org.apache.qpid.jms.policy.JmsMessageIDPolicy; import org.apache.qpid.jms.policy.JmsPrefetchPolicy; import org.apache.qpid.jms.policy.JmsPresettlePolicy; @@ -96,6 +98,7 @@ public class JmsConnectionFactory extends JNDIStorable implements ConnectionFact private JmsRedeliveryPolicy redeliveryPolicy = new JmsDefaultRedeliveryPolicy(); private JmsPresettlePolicy presettlePolicy = new JmsDefaultPresettlePolicy(); private JmsMessageIDPolicy messageIDPolicy = new JmsDefaultMessageIDPolicy(); + private JmsDeserializationPolicy deserializationPolicy = new JmsDefaultDeserializationPolicy(); public JmsConnectionFactory() { } @@ -580,6 +583,26 @@ public class JmsConnectionFactory extends JNDIStorable implements ConnectionFact } /** + * @return the deserializationPolicy that is currently configured. + */ + public JmsDeserializationPolicy getDeserializationPolicy() { + return deserializationPolicy; + } + + /** + * Sets the JmsDeserializationPolicy that is applied when a new connection is created. + * + * @param deserializationPolicy + * the deserializationPolicy that will be applied to new connections. + */ + public void setDeserializationPolicy(JmsDeserializationPolicy deserializationPolicy) { + if (deserializationPolicy == null) { + deserializationPolicy = new JmsDefaultDeserializationPolicy(); + } + this.deserializationPolicy = deserializationPolicy; + } + + /** * @return the currently configured client ID prefix for auto-generated client IDs. */ public synchronized String getClientIDPrefix() { http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageConsumer.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageConsumer.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageConsumer.java index d797918..893a576 100644 --- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageConsumer.java +++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsMessageConsumer.java @@ -34,6 +34,7 @@ import org.apache.qpid.jms.message.JmsInboundMessageDispatch; import org.apache.qpid.jms.message.JmsMessage; import org.apache.qpid.jms.meta.JmsConsumerId; import org.apache.qpid.jms.meta.JmsConsumerInfo; +import org.apache.qpid.jms.policy.JmsDeserializationPolicy; import org.apache.qpid.jms.policy.JmsPrefetchPolicy; import org.apache.qpid.jms.policy.JmsRedeliveryPolicy; import org.apache.qpid.jms.provider.Provider; @@ -88,6 +89,7 @@ public class JmsMessageConsumer implements AutoCloseable, MessageConsumer, JmsMe JmsPrefetchPolicy prefetchPolicy = session.getPrefetchPolicy(); JmsRedeliveryPolicy redeliveryPolicy = session.getRedeliveryPolicy().copy(); + JmsDeserializationPolicy deserializationPolicy = session.getDeserializationPolicy().copy(); consumerInfo = new JmsConsumerInfo(consumerId); consumerInfo.setClientId(connection.getClientID()); @@ -102,6 +104,7 @@ public class JmsMessageConsumer implements AutoCloseable, MessageConsumer, JmsMe consumerInfo.setRedeliveryPolicy(redeliveryPolicy); consumerInfo.setLocalMessageExpiry(connection.isLocalMessageExpiry()); consumerInfo.setPresettle(session.getPresettlePolicy().isConsumerPresttled(session, destination)); + consumerInfo.setDeserializationPolicy(deserializationPolicy); session.getConnection().createResource(consumerInfo); } http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsSession.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsSession.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsSession.java index ff28e59..b8aded8 100644 --- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsSession.java +++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsSession.java @@ -68,6 +68,7 @@ import org.apache.qpid.jms.meta.JmsProducerId; import org.apache.qpid.jms.meta.JmsProducerInfo; import org.apache.qpid.jms.meta.JmsSessionId; import org.apache.qpid.jms.meta.JmsSessionInfo; +import org.apache.qpid.jms.policy.JmsDeserializationPolicy; import org.apache.qpid.jms.policy.JmsMessageIDPolicy; import org.apache.qpid.jms.policy.JmsPrefetchPolicy; import org.apache.qpid.jms.policy.JmsPresettlePolicy; @@ -123,6 +124,7 @@ public class JmsSession implements AutoCloseable, Session, QueueSession, TopicSe sessionInfo.setPrefetchPolicy(connection.getPrefetchPolicy().copy()); sessionInfo.setPresettlePolicy(connection.getPresettlePolicy().copy()); sessionInfo.setRedeliveryPolicy(connection.getRedeliveryPolicy().copy()); + sessionInfo.setDeserializationPolicy(connection.getDeserializationPolicy()); connection.createResource(sessionInfo); @@ -942,6 +944,10 @@ public class JmsSession implements AutoCloseable, Session, QueueSession, TopicSe return sessionInfo.getRedeliveryPolicy(); } + public JmsDeserializationPolicy getDeserializationPolicy() { + return sessionInfo.getDeserializationPolicy(); + } + @Override public void onInboundMessage(JmsInboundMessageDispatch envelope) { if (started.get()) { http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/main/java/org/apache/qpid/jms/meta/JmsConnectionInfo.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/meta/JmsConnectionInfo.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/meta/JmsConnectionInfo.java index b98af14..bc723c0 100644 --- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/meta/JmsConnectionInfo.java +++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/meta/JmsConnectionInfo.java @@ -19,10 +19,12 @@ package org.apache.qpid.jms.meta; import java.net.URI; import java.nio.charset.Charset; +import org.apache.qpid.jms.policy.JmsDefaultDeserializationPolicy; import org.apache.qpid.jms.policy.JmsDefaultMessageIDPolicy; import org.apache.qpid.jms.policy.JmsDefaultPrefetchPolicy; import org.apache.qpid.jms.policy.JmsDefaultPresettlePolicy; import org.apache.qpid.jms.policy.JmsDefaultRedeliveryPolicy; +import org.apache.qpid.jms.policy.JmsDeserializationPolicy; import org.apache.qpid.jms.policy.JmsMessageIDPolicy; import org.apache.qpid.jms.policy.JmsPrefetchPolicy; import org.apache.qpid.jms.policy.JmsPresettlePolicy; @@ -67,6 +69,7 @@ public final class JmsConnectionInfo implements JmsResource, Comparable<JmsConne private JmsRedeliveryPolicy redeliveryPolicy; private JmsPresettlePolicy presettlePolicy; private JmsMessageIDPolicy messageIDPolicy; + private JmsDeserializationPolicy deserializationPolicy; private volatile byte[] encodedUserId; @@ -101,6 +104,7 @@ public final class JmsConnectionInfo implements JmsResource, Comparable<JmsConne copy.prefetchPolicy = getPrefetchPolicy().copy(); copy.redeliveryPolicy = getRedeliveryPolicy().copy(); copy.presettlePolicy = getPresettlePolicy().copy(); + copy.deserializationPolicy = getDeserializationPolicy().copy(); } public boolean isForceAsyncSend() { @@ -320,6 +324,17 @@ public final class JmsConnectionInfo implements JmsResource, Comparable<JmsConne return encodedUserId; } + public JmsDeserializationPolicy getDeserializationPolicy() { + if (deserializationPolicy == null) { + deserializationPolicy = new JmsDefaultDeserializationPolicy(); + } + return deserializationPolicy; + } + + public void setDeserializationPolicy(JmsDeserializationPolicy deserializationPolicy) { + this.deserializationPolicy = deserializationPolicy; + } + @Override public String toString() { return "JmsConnectionInfo { " + getId() + http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/main/java/org/apache/qpid/jms/meta/JmsConsumerInfo.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/meta/JmsConsumerInfo.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/meta/JmsConsumerInfo.java index ab72d7c..dd34397 100644 --- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/meta/JmsConsumerInfo.java +++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/meta/JmsConsumerInfo.java @@ -17,7 +17,9 @@ package org.apache.qpid.jms.meta; import org.apache.qpid.jms.JmsDestination; +import org.apache.qpid.jms.policy.JmsDefaultDeserializationPolicy; import org.apache.qpid.jms.policy.JmsDefaultRedeliveryPolicy; +import org.apache.qpid.jms.policy.JmsDeserializationPolicy; import org.apache.qpid.jms.policy.JmsRedeliveryPolicy; public final class JmsConsumerInfo implements JmsResource, Comparable<JmsConsumerInfo> { @@ -35,6 +37,7 @@ public final class JmsConsumerInfo implements JmsResource, Comparable<JmsConsume private boolean presettle; private JmsRedeliveryPolicy redeliveryPolicy; + private JmsDeserializationPolicy deserializationPolicy; // Can be used to track the last consumed message. private transient long lastDeliveredSequenceId; @@ -71,6 +74,7 @@ public final class JmsConsumerInfo implements JmsResource, Comparable<JmsConsume info.acknowledgementMode = acknowledgementMode; info.lastDeliveredSequenceId = lastDeliveredSequenceId; info.redeliveryPolicy = getRedeliveryPolicy().copy(); + info.deserializationPolicy = getDeserializationPolicy().copy(); } public boolean isDurable() { @@ -177,6 +181,17 @@ public final class JmsConsumerInfo implements JmsResource, Comparable<JmsConsume this.redeliveryPolicy = redeliveryPolicy; } + public JmsDeserializationPolicy getDeserializationPolicy() { + if (deserializationPolicy == null) { + deserializationPolicy = new JmsDefaultDeserializationPolicy(); + } + return deserializationPolicy; + } + + public void setDeserializationPolicy(JmsDeserializationPolicy deserializationPolicy) { + this.deserializationPolicy = deserializationPolicy; + } + public boolean isPresettle() { return presettle; } http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/main/java/org/apache/qpid/jms/meta/JmsSessionInfo.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/meta/JmsSessionInfo.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/meta/JmsSessionInfo.java index 6e87480..1b7c3da 100644 --- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/meta/JmsSessionInfo.java +++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/meta/JmsSessionInfo.java @@ -18,10 +18,12 @@ package org.apache.qpid.jms.meta; import javax.jms.Session; +import org.apache.qpid.jms.policy.JmsDefaultDeserializationPolicy; import org.apache.qpid.jms.policy.JmsDefaultMessageIDPolicy; import org.apache.qpid.jms.policy.JmsDefaultPrefetchPolicy; import org.apache.qpid.jms.policy.JmsDefaultPresettlePolicy; import org.apache.qpid.jms.policy.JmsDefaultRedeliveryPolicy; +import org.apache.qpid.jms.policy.JmsDeserializationPolicy; import org.apache.qpid.jms.policy.JmsMessageIDPolicy; import org.apache.qpid.jms.policy.JmsPrefetchPolicy; import org.apache.qpid.jms.policy.JmsPresettlePolicy; @@ -37,6 +39,7 @@ public final class JmsSessionInfo implements JmsResource, Comparable<JmsSessionI private JmsPrefetchPolicy prefetchPolicy; private JmsPresettlePolicy presettlePolicy; private JmsRedeliveryPolicy redeliveryPolicy; + private JmsDeserializationPolicy deserializationPolicy; public JmsSessionInfo(JmsConnectionInfo connectionInfo, long sessionId) { if (connectionInfo == null) { @@ -66,6 +69,7 @@ public final class JmsSessionInfo implements JmsResource, Comparable<JmsSessionI copy.presettlePolicy = getPresettlePolicy().copy(); copy.prefetchPolicy = getPrefetchPolicy().copy(); copy.messageIDPolicy = getMessageIDPolicy().copy(); + copy.deserializationPolicy = getDeserializationPolicy().copy(); } @Override @@ -172,4 +176,15 @@ public final class JmsSessionInfo implements JmsResource, Comparable<JmsSessionI public void setRedeliveryPolicy(JmsRedeliveryPolicy redeliveryPolicy) { this.redeliveryPolicy = redeliveryPolicy; } + + public JmsDeserializationPolicy getDeserializationPolicy() { + if (deserializationPolicy == null) { + deserializationPolicy = new JmsDefaultDeserializationPolicy(); + } + return deserializationPolicy; + } + + public void setDeserializationPolicy(JmsDeserializationPolicy deserializationPolicy) { + this.deserializationPolicy = deserializationPolicy; + } } http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/main/java/org/apache/qpid/jms/policy/JmsDefaultDeserializationPolicy.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/policy/JmsDefaultDeserializationPolicy.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/policy/JmsDefaultDeserializationPolicy.java new file mode 100644 index 0000000..2bcbed4 --- /dev/null +++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/policy/JmsDefaultDeserializationPolicy.java @@ -0,0 +1,244 @@ +/* + * 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.qpid.jms.policy; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import org.apache.qpid.jms.JmsDestination; + +/** + * Default implementation of the deserialization policy that can read white and black list of + * classes/packages from the environment, and be updated by the connection uri options. + * + * The policy reads a default blackList string value (comma separated) from the system property + * {@value #BLACKLIST_PROPERTY} which defaults to null which indicates an empty blacklist. + * + * The policy reads a default whitelist string value (comma separated) from the system property + * {@value #WHITELIST_PROPERTY} which defaults to a {@value #CATCH_ALL_WILDCARD} which + * indicates that all classes are whitelisted. + * + * The blacklist overrides the whitelist, entries that could match both are counted as blacklisted. + * + * If the policy should treat all classes as untrusted the blacklist should be set to + * {@value #CATCH_ALL_WILDCARD}". + */ +public class JmsDefaultDeserializationPolicy implements JmsDeserializationPolicy { + + /** + * 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.qpid.jms.deserialization.white_list"; + public static final String BLACKLIST_PROPERTY = "org.apache.qpid.jms.deserialization.black_list"; + + private List<String> whiteList = new ArrayList<String>(); + private List<String> blackList = new ArrayList<String>(); + + /** + * Creates an instance of this policy with default configuration. + */ + public JmsDefaultDeserializationPolicy() { + String whitelist = System.getProperty(WHITELIST_PROPERTY, CATCH_ALL_WILDCARD); + setWhiteList(whitelist); + + String blackList = System.getProperty(BLACKLIST_PROPERTY); + setBlackList(blackList); + } + + /** + * @param source + * The instance whose configuration should be copied from. + */ + public JmsDefaultDeserializationPolicy(JmsDefaultDeserializationPolicy source) { + this.whiteList.addAll(source.whiteList); + this.blackList.addAll(source.blackList); + } + + @Override + public JmsDeserializationPolicy copy() { + return new JmsDefaultDeserializationPolicy(this); + } + + @Override + public boolean isTrustedType(JmsDestination destination, 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. + return false; + } + + private final 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; + } + + /** + * @return the whiteList configured on this policy instance. + */ + public String getWhiteList() { + Iterator<String> entries = whiteList.iterator(); + StringBuilder builder = new StringBuilder(); + + while (entries.hasNext()) { + builder.append(entries.next()); + if (entries.hasNext()) { + builder.append(","); + } + } + + return builder.toString(); + } + + /** + * @return the blackList configured on this policy instance. + */ + public String getBlackList() { + Iterator<String> entries = blackList.iterator(); + StringBuilder builder = new StringBuilder(); + + while (entries.hasNext()) { + builder.append(entries.next()); + if (entries.hasNext()) { + builder.append(","); + } + } + + return builder.toString(); + } + + /** + * 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) { + ArrayList<String> list = new ArrayList<String>(); + if (whiteList != null && !whiteList.isEmpty()) { + list.addAll(Arrays.asList(whiteList.split(","))); + } + + this.whiteList = list; + } + + /** + * 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) { + ArrayList<String> list = new ArrayList<String>(); + if (blackList != null && !blackList.isEmpty()) { + list.addAll(Arrays.asList(blackList.split(","))); + } + + this.blackList = list; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((whiteList == null) ? 0 : whiteList.hashCode()); + result = prime * result + ((blackList == null) ? 0 : blackList.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null) { + return false; + } + + if (getClass() != obj.getClass()) { + return false; + } + + JmsDefaultDeserializationPolicy other = (JmsDefaultDeserializationPolicy) obj; + + if (whiteList == null) { + if (other.whiteList != null) { + return false; + } + } else if (!whiteList.equals(other.whiteList)) { + return false; + } + + if (blackList == null) { + if (other.blackList != null) { + return false; + } + } else if (!blackList.equals(other.blackList)) { + return false; + } + + return true; + } +} http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/main/java/org/apache/qpid/jms/policy/JmsDeserializationPolicy.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/policy/JmsDeserializationPolicy.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/policy/JmsDeserializationPolicy.java new file mode 100644 index 0000000..bb70947 --- /dev/null +++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/policy/JmsDeserializationPolicy.java @@ -0,0 +1,45 @@ +/* + * 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.qpid.jms.policy; + +import javax.jms.ObjectMessage; + +import org.apache.qpid.jms.JmsDestination; + +/** + * Defines the interface for a policy object that controls what types of message + * content are permissible when the body of an incoming ObjectMessage is being + * deserialized. + */ +public interface JmsDeserializationPolicy { + + JmsDeserializationPolicy copy(); + + /** + * Returns whether the given class is a trusted type and can be deserialized + * by the client when calls to {@link ObjectMessage#getObject()} are made. + * + * @param destination + * the Destination for the message containing the type to be deserialized. + * @param clazz + * the Type of the object that is about to be read. + * + * @return true if the type is trusted or false if not. + */ + boolean isTrustedType(JmsDestination destination, Class<?> clazz); + +} http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFacade.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFacade.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFacade.java index a739999..ff36db7 100644 --- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFacade.java +++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFacade.java @@ -857,6 +857,10 @@ public class AmqpJmsMessageFacade implements JmsMessageFacade { this.message.setReplyTo(address); } + JmsDestination getConsumerDestination() { + return this.consumerDestination; + } + private Long getAbsoluteExpiryTime() { Long result = null; if (message.getProperties() != null) { http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFactory.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFactory.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFactory.java index e0cc8c4..5b78556 100644 --- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFactory.java +++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFactory.java @@ -95,7 +95,8 @@ public class AmqpJmsMessageFactory implements JmsMessageFactory { @Override public JmsObjectMessage createObjectMessage(Serializable payload) throws JMSException { - JmsObjectMessageFacade facade = new AmqpJmsObjectMessageFacade(connection, connection.isObjectMessageUsesAmqpTypes()); + JmsObjectMessageFacade facade = new AmqpJmsObjectMessageFacade( + connection, connection.isObjectMessageUsesAmqpTypes()); if (payload != null) { try { http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsObjectMessageFacade.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsObjectMessageFacade.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsObjectMessageFacade.java index 4db872a..fabefed 100644 --- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsObjectMessageFacade.java +++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsObjectMessageFacade.java @@ -26,6 +26,7 @@ import javax.jms.JMSException; import org.apache.qpid.jms.exceptions.JmsExceptionSupport; import org.apache.qpid.jms.message.facade.JmsObjectMessageFacade; +import org.apache.qpid.jms.policy.JmsDeserializationPolicy; import org.apache.qpid.jms.provider.amqp.AmqpConnection; import org.apache.qpid.jms.provider.amqp.AmqpConsumer; import org.apache.qpid.proton.message.Message; @@ -40,8 +41,10 @@ public class AmqpJmsObjectMessageFacade extends AmqpJmsMessageFacade implements private AmqpObjectTypeDelegate delegate; + private final JmsDeserializationPolicy deserializationPolicy; + /** - * Creates a new facade instance + * Creates a new facade instance for outgoing message * * @param connection * the AmqpConnection that under which this facade was created. @@ -49,9 +52,14 @@ public class AmqpJmsObjectMessageFacade extends AmqpJmsMessageFacade implements * controls the type used to encode the body. */ public AmqpJmsObjectMessageFacade(AmqpConnection connection, boolean isAmqpTypeEncoded) { + this(connection, isAmqpTypeEncoded, null); + } + + private AmqpJmsObjectMessageFacade(AmqpConnection connection, boolean isAmqpTypeEncoded, JmsDeserializationPolicy deserializationPolicy) { super(connection); - setMessageAnnotation(JMS_MSG_TYPE, JMS_OBJECT_MESSAGE); + this.deserializationPolicy = deserializationPolicy; + setMessageAnnotation(JMS_MSG_TYPE, JMS_OBJECT_MESSAGE); initDelegate(isAmqpTypeEncoded, null); } @@ -68,6 +76,7 @@ public class AmqpJmsObjectMessageFacade extends AmqpJmsMessageFacade implements */ public AmqpJmsObjectMessageFacade(AmqpConsumer consumer, Message message, ByteBuf messageBytes) { super(consumer, message); + deserializationPolicy = consumer.getResourceInfo().getDeserializationPolicy(); boolean javaSerialized = AmqpMessageSupport.SERIALIZED_JAVA_OBJECT_CONTENT_TYPE.equals(message.getContentType()); initDelegate(!javaSerialized, messageBytes); @@ -87,15 +96,13 @@ public class AmqpJmsObjectMessageFacade extends AmqpJmsMessageFacade implements @Override public AmqpJmsObjectMessageFacade copy() throws JMSException { - AmqpJmsObjectMessageFacade copy = new AmqpJmsObjectMessageFacade(connection, isAmqpTypedEncoding()); + AmqpJmsObjectMessageFacade copy = new AmqpJmsObjectMessageFacade(connection, isAmqpTypedEncoding(), deserializationPolicy); copyInto(copy); - try { - copy.setObject(getObject()); - } catch (Exception e) { - throw JmsExceptionSupport.create("Failed to copy object value", e); + delegate.copyInto(copy.delegate); + } catch (Exception ex) { + throw JmsExceptionSupport.create(ex); } - return copy; } @@ -130,9 +137,9 @@ public class AmqpJmsObjectMessageFacade extends AmqpJmsMessageFacade implements AmqpObjectTypeDelegate newDelegate = null; if (useAmqpTypedEncoding) { - newDelegate = new AmqpTypedObjectDelegate(message, null); + newDelegate = new AmqpTypedObjectDelegate(this, null); } else { - newDelegate = new AmqpSerializedObjectDelegate(message, null); + newDelegate = new AmqpSerializedObjectDelegate(this, null, deserializationPolicy); } newDelegate.setObject(existingObject); @@ -146,9 +153,9 @@ public class AmqpJmsObjectMessageFacade extends AmqpJmsMessageFacade implements private void initDelegate(boolean useAmqpTypes, ByteBuf messageBytes) { if (!useAmqpTypes) { - delegate = new AmqpSerializedObjectDelegate(getAmqpMessage(), messageBytes); + delegate = new AmqpSerializedObjectDelegate(this, messageBytes, deserializationPolicy); } else { - delegate = new AmqpTypedObjectDelegate(getAmqpMessage(), messageBytes); + delegate = new AmqpTypedObjectDelegate(this, messageBytes); } } http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpObjectTypeDelegate.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpObjectTypeDelegate.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpObjectTypeDelegate.java index d14ff93..7657343 100644 --- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpObjectTypeDelegate.java +++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpObjectTypeDelegate.java @@ -53,5 +53,15 @@ public interface AmqpObjectTypeDelegate { */ void onSend(); + /** + * Copy the internal data into the given instance. + * + * @param copy + * the new delegate that will receive a copy of this instances object data. + * + * @throws Exception if an error occurs while copying the contents to the target. + */ + void copyInto(AmqpObjectTypeDelegate copy) throws Exception; + boolean isAmqpTypeEncoded(); } http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpSerializedObjectDelegate.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpSerializedObjectDelegate.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpSerializedObjectDelegate.java index 546060b..618d123 100644 --- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpSerializedObjectDelegate.java +++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpSerializedObjectDelegate.java @@ -26,7 +26,9 @@ import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.concurrent.atomic.AtomicReference; +import org.apache.qpid.jms.policy.JmsDeserializationPolicy; import org.apache.qpid.jms.util.ClassLoadingAwareObjectInputStream; +import org.apache.qpid.jms.util.ClassLoadingAwareObjectInputStream.TrustedClassFilter; import org.apache.qpid.proton.amqp.Binary; import org.apache.qpid.proton.amqp.messaging.Data; import org.apache.qpid.proton.amqp.messaging.Section; @@ -38,7 +40,7 @@ import io.netty.buffer.ByteBuf; * Wrapper around an AMQP Message instance that will be treated as a JMS ObjectMessage * type. */ -public class AmqpSerializedObjectDelegate implements AmqpObjectTypeDelegate { +public class AmqpSerializedObjectDelegate implements AmqpObjectTypeDelegate, TrustedClassFilter { static final Data NULL_OBJECT_BODY; static @@ -53,25 +55,32 @@ public class AmqpSerializedObjectDelegate implements AmqpObjectTypeDelegate { NULL_OBJECT_BODY = new Data(new Binary(bytes)); } + private final AmqpJmsMessageFacade parent; private final Message message; private final AtomicReference<Section> cachedReceivedBody = new AtomicReference<Section>(); + private final JmsDeserializationPolicy deserializationPolicy; private ByteBuf messageBytes; + private boolean localContent; /** * Create a new delegate that uses Java serialization to store the message content. * - * @param message - * the AMQP message instance where the object is to be stored / read. + * @param parent + * the AMQP message facade instance where the object is to be stored / read. * @param messageBytes * the raw bytes that comprise the message when it was received. + * @param deserializationPolicy + * the JmsDeserializationPolicy that is used to validate the security of message + * content, may be null (e.g on new outgoing messages). */ - public AmqpSerializedObjectDelegate(Message message, ByteBuf messageBytes) { - this.message = message; + public AmqpSerializedObjectDelegate(AmqpJmsMessageFacade parent, ByteBuf messageBytes, JmsDeserializationPolicy deserializationPolicy) { + this.parent = parent; + this.message = parent.getAmqpMessage(); this.message.setContentType(SERIALIZED_JAVA_OBJECT_CONTENT_TYPE); this.messageBytes = messageBytes; + this.deserializationPolicy = deserializationPolicy; - // We will decode the body on each access, so clear the current value - // so we don't carry along unneeded bloat. + // Cache the body so the first access can grab it without extra work. if (messageBytes != null) { cachedReceivedBody.set(message.getBody()); } @@ -116,7 +125,7 @@ public class AmqpSerializedObjectDelegate implements AmqpObjectTypeDelegate { Serializable serialized = null; try (ByteArrayInputStream bais = new ByteArrayInputStream(bin.getArray(), bin.getArrayOffset(), bin.getLength()); - ClassLoadingAwareObjectInputStream objIn = new ClassLoadingAwareObjectInputStream(bais)) { + ClassLoadingAwareObjectInputStream objIn = new ClassLoadingAwareObjectInputStream(bais, this)) { serialized = (Serializable) objIn.readObject(); } @@ -137,6 +146,7 @@ public class AmqpSerializedObjectDelegate implements AmqpObjectTypeDelegate { } messageBytes = null; + localContent = true; } @Override @@ -148,7 +158,40 @@ public class AmqpSerializedObjectDelegate implements AmqpObjectTypeDelegate { } @Override + public void copyInto(AmqpObjectTypeDelegate copy) throws Exception { + if (!(copy instanceof AmqpSerializedObjectDelegate)) { + copy.setObject(getObject()); + } else { + AmqpSerializedObjectDelegate target = (AmqpSerializedObjectDelegate) copy; + + // Swap our cached value to the copy, we will just decode it if we need it. + target.cachedReceivedBody.set(cachedReceivedBody.getAndSet(null)); + + // If we have the original bytes just copy those and let the next get + // decode them into the payload, otherwise we need to do a deep copy. + if (messageBytes != null) { + target.messageBytes = messageBytes.copy(); + } + + target.localContent = localContent; + + // Copy the already encoded message body if it exists, subsequent gets + // will deserialize the data so no mutations can occur. + target.message.setBody(message.getBody()); + } + } + + @Override public boolean isAmqpTypeEncoded() { return false; } + + @Override + public boolean isTrusted(Class<?> clazz) { + if (!localContent && deserializationPolicy != null) { + return deserializationPolicy.isTrustedType(parent.getConsumerDestination(), clazz); + } else { + return true; + } + } } http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpTypedObjectDelegate.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpTypedObjectDelegate.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpTypedObjectDelegate.java index 99ab86b..cc1038f 100644 --- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpTypedObjectDelegate.java +++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/message/AmqpTypedObjectDelegate.java @@ -48,18 +48,17 @@ public class AmqpTypedObjectDelegate implements AmqpObjectTypeDelegate { /** * Create a new delegate that uses Java serialization to store the message content. * - * @param message - * the AMQP message instance where the object is to be stored / read. + * @param parent + * the AMQP message facade instance where the object is to be stored / read. * @param messageBytes * the raw bytes that comprise the AMQP message that was received. */ - public AmqpTypedObjectDelegate(Message message, ByteBuf messageBytes) { - this.message = message; + public AmqpTypedObjectDelegate(AmqpJmsMessageFacade parent, ByteBuf messageBytes) { + this.message = parent.getAmqpMessage(); this.message.setContentType(null); this.messageBytes = messageBytes; - // We will decode the body on each access, so clear the current value - // so we don't carry along unneeded bloat. + // Cache the body so the first access can grab it without extra work. if (messageBytes != null) { cachedReceivedBody.set(message.getBody()); } @@ -108,15 +107,16 @@ public class AmqpTypedObjectDelegate implements AmqpObjectTypeDelegate { Message transfer = Message.Factory.create(); // Exchange the incoming body value for one that is created from encoding - // and decoding the value. + // and decoding the value. Save the bytes for subsequent getObject and + // copyInto calls to use. transfer.setBody(new AmqpValue(value)); messageBytes = encodeMessage(transfer); transfer = decodeMessage(messageBytes); - messageBytes = null; // This step requires a heavy-weight operation of both encoding and decoding the // incoming body value in order to create a copy such that changes to the original - // do not affect the stored value. In the future it makes sense to try to enhance + // do not affect the stored value, and also verifies we can actually encode it at all + // now instead of later during send. In the future it makes sense to try to enhance // proton such that we can encode the body and use those bytes directly on the // message as it is being sent. @@ -135,6 +135,41 @@ public class AmqpTypedObjectDelegate implements AmqpObjectTypeDelegate { } } + @Override + public void copyInto(AmqpObjectTypeDelegate copy) throws Exception { + if (!(copy instanceof AmqpTypedObjectDelegate)) { + copy.setObject(getObject()); + } else { + AmqpTypedObjectDelegate target = (AmqpTypedObjectDelegate) copy; + + // Swap our cached value (if any) to the copy, we will just decode it if we need it later. + target.cachedReceivedBody.set(cachedReceivedBody.getAndSet(null)); + + if (messageBytes != null) { + // If we have the original bytes just copy those and let the next get + // decode them into the payload (or for the copy, use the cached + // body if it was swapped above). + target.messageBytes = messageBytes.copy(); + + // Internal message body copy to satisfy sends. This is safe since the body was set + // from a copy (decoded from the bytes) to ensure it is a snapshot. Also safe for + // gets as they will use the message bytes (or cached body if set) to return the object. + target.message.setBody(message.getBody()); + } else { + // We have to deep get/set copy here, otherwise a get might return + // the object value carried by the original version. + copy.setObject(getObject()); + } + } + } + + @Override + public boolean isAmqpTypeEncoded() { + return true; + } + + //----- Internal implementation ------------------------------------------// + private boolean isSupportedAmqpValueObjectType(Serializable serializable) { // TODO: augment supported types to encode as an AmqpValue? return serializable instanceof String || @@ -142,9 +177,4 @@ public class AmqpTypedObjectDelegate implements AmqpObjectTypeDelegate { serializable instanceof List<?> || serializable.getClass().isArray(); } - - @Override - public boolean isAmqpTypeEncoded() { - return true; - } } http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/main/java/org/apache/qpid/jms/util/ClassLoadingAwareObjectInputStream.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/util/ClassLoadingAwareObjectInputStream.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/util/ClassLoadingAwareObjectInputStream.java index 0432bc4..21562a8 100644 --- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/util/ClassLoadingAwareObjectInputStream.java +++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/util/ClassLoadingAwareObjectInputStream.java @@ -31,16 +31,29 @@ public class ClassLoadingAwareObjectInputStream extends ObjectInputStream { private static final ClassLoader FALLBACK_CLASS_LOADER = ClassLoadingAwareObjectInputStream.class.getClassLoader(); private final ClassLoader inLoader; + private final TrustedClassFilter securityFilter; - public ClassLoadingAwareObjectInputStream(InputStream in) throws IOException { + /** + * Security Filter used to filter classes that the application deems to be insecure, this filter + * is not applied to the class instances for the primitive types, and array types are narrowed + * to the component type of the array before being passed into this filter. + */ + public interface TrustedClassFilter { + boolean isTrusted(Class<?> clazz); + } + + public ClassLoadingAwareObjectInputStream(InputStream in, TrustedClassFilter filter) throws IOException { super(in); + inLoader = in.getClass().getClassLoader(); + securityFilter = filter; } @Override protected Class<?> resolveClass(ObjectStreamClass classDesc) throws IOException, ClassNotFoundException { ClassLoader cl = Thread.currentThread().getContextClassLoader(); - return load(classDesc.getName(), cl, inLoader); + Class<?> clazz = load(classDesc.getName(), cl, inLoader); + return checkSecurity(clazz); } @Override @@ -51,20 +64,52 @@ public class ClassLoadingAwareObjectInputStream extends ObjectInputStream { cinterfaces[i] = load(interfaces[i], cl); } + Class<?> clazz = null; + Throwable failureCause = null; + try { - return Proxy.getProxyClass(cl, cinterfaces); + clazz = Proxy.getProxyClass(cl, cinterfaces); } catch (IllegalArgumentException e) { + failureCause = e; + try { - return Proxy.getProxyClass(inLoader, cinterfaces); + clazz = Proxy.getProxyClass(inLoader, cinterfaces); } catch (IllegalArgumentException e1) { } try { - return Proxy.getProxyClass(FALLBACK_CLASS_LOADER, cinterfaces); + clazz = Proxy.getProxyClass(FALLBACK_CLASS_LOADER, cinterfaces); } catch (IllegalArgumentException e2) { } + } - throw new ClassNotFoundException(null, e); + if (clazz != null) { + return checkSecurity(clazz); } + + throw new ClassNotFoundException("Failed find class.", failureCause); + } + + 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() && securityFilter != null) { + if (!securityFilter.isTrusted(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 Class<?> load(String className, ClassLoader... cl) throws ClassNotFoundException { http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/test/java/org/apache/qpid/jms/JmsConnectionFactoryTest.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/JmsConnectionFactoryTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/JmsConnectionFactoryTest.java index efb29f5..48c668b 100644 --- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/JmsConnectionFactoryTest.java +++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/JmsConnectionFactoryTest.java @@ -38,6 +38,7 @@ import javax.jms.Connection; import javax.jms.ExceptionListener; import javax.jms.JMSException; +import org.apache.qpid.jms.policy.JmsDefaultDeserializationPolicy; import org.apache.qpid.jms.policy.JmsDefaultPrefetchPolicy; import org.apache.qpid.jms.policy.JmsDefaultPresettlePolicy; import org.apache.qpid.jms.policy.JmsDefaultRedeliveryPolicy; @@ -111,12 +112,16 @@ public class JmsConnectionFactoryTest extends QpidJmsTestCase { public void testConnectionFactoryPrefetchPolicyIsAppliedToConnection() throws JMSException { JmsConnectionFactory factory = new JmsConnectionFactory(USER, PASSWORD, "mock://localhost"); + JmsDefaultPrefetchPolicy prefetchPolicy = (JmsDefaultPrefetchPolicy) factory.getPrefetchPolicy(); + + assertFalse(prefetchPolicy.getQueuePrefetch() == 1); + ((JmsDefaultPrefetchPolicy) factory.getPrefetchPolicy()).setAll(1); JmsConnection connection = (JmsConnection) factory.createConnection(); assertNotNull(connection); - JmsDefaultPrefetchPolicy prefetchPolicy = (JmsDefaultPrefetchPolicy) connection.getPrefetchPolicy(); + prefetchPolicy = (JmsDefaultPrefetchPolicy) connection.getPrefetchPolicy(); assertNotNull(prefetchPolicy); assertNotSame(factory.getPrefetchPolicy(), prefetchPolicy); @@ -132,6 +137,8 @@ public class JmsConnectionFactoryTest extends QpidJmsTestCase { JmsDefaultPresettlePolicy presettlePolicy = (JmsDefaultPresettlePolicy) factory.getPresettlePolicy(); + assertFalse(presettlePolicy.isPresettleAll()); + presettlePolicy.setPresettleAll(true); JmsConnection connection = (JmsConnection) factory.createConnection(); @@ -150,6 +157,8 @@ public class JmsConnectionFactoryTest extends QpidJmsTestCase { JmsDefaultRedeliveryPolicy redeliveryPolicy = (JmsDefaultRedeliveryPolicy) factory.getRedeliveryPolicy(); + assertFalse(redeliveryPolicy.getMaxRedeliveries() == 100); + redeliveryPolicy.setMaxRedeliveries(100); JmsConnection connection = (JmsConnection) factory.createConnection(); @@ -163,6 +172,29 @@ public class JmsConnectionFactoryTest extends QpidJmsTestCase { } @Test + public void testConnectionFactoryDeserializationPolicyIsAppliedToConnection() throws JMSException { + JmsConnectionFactory factory = new JmsConnectionFactory(USER, PASSWORD, "mock://localhost"); + + final String TRUSTED_PACKAGES = "java.lang,java.util"; + + JmsDefaultDeserializationPolicy deserializationPolicy = + (JmsDefaultDeserializationPolicy) factory.getDeserializationPolicy(); + + assertFalse(deserializationPolicy.getWhiteList().equals(TRUSTED_PACKAGES)); + + deserializationPolicy.setWhiteList(TRUSTED_PACKAGES); + + JmsConnection connection = (JmsConnection) factory.createConnection(); + assertNotNull(connection); + + deserializationPolicy = (JmsDefaultDeserializationPolicy) connection.getDeserializationPolicy(); + assertNotNull(deserializationPolicy); + assertNotSame(factory.getDeserializationPolicy(), deserializationPolicy); + + assertEquals(TRUSTED_PACKAGES, deserializationPolicy.getWhiteList()); + } + + @Test public void testConnectionGetConfiguredURIApplied() throws Exception { URI mock = new URI("mock://localhost"); @@ -529,6 +561,47 @@ public class JmsConnectionFactoryTest extends QpidJmsTestCase { assertEquals("Properties were not equal", props, props2); } + /** + * The deserialization policy is maintained in a child-object, which we extract the properties from + * when serializing the factory. Ensure this functions by doing a round trip on a factory + * configured with some new deserialization configuration via the URI. + * + * @throws Exception if an error occurs during the test. + */ + @Test + public void testSerializeThenDeserializeMaintainsDeserializationPolicy() throws Exception { + String whiteListValue = "java.lang"; + String whitelistKey = "deserializationPolicy.whiteList"; + + String blackListValue = "java.lang.foo"; + String blacklistKey = "deserializationPolicy.blackList"; + + String uri = "amqp://localhost:1234?jms." + whitelistKey + "=" + whiteListValue + "&jms." + blacklistKey + "=" + blackListValue; + + JmsConnectionFactory cf = new JmsConnectionFactory(uri); + Map<String, String> props = cf.getProperties(); + + assertTrue("Props dont contain expected deserialization policy change", props.containsKey(whitelistKey)); + assertEquals("Unexpected value", whiteListValue, props.get(whitelistKey)); + + assertTrue("Props dont contain expected deserialization policy change", props.containsKey(blacklistKey)); + assertEquals("Unexpected value", blackListValue, props.get(blacklistKey)); + + Object roundTripped = roundTripSerialize(cf); + + assertNotNull("Null object returned", roundTripped); + assertEquals("Unexpected type", JmsConnectionFactory.class, roundTripped.getClass()); + + Map<String, String> props2 = ((JmsConnectionFactory)roundTripped).getProperties(); + assertTrue("Props dont contain expected deserialization policy change", props2.containsKey(whitelistKey)); + assertEquals("Unexpected value", whiteListValue, props2.get(whitelistKey)); + + assertTrue("Props dont contain expected deserialization policy change", props2.containsKey(blacklistKey)); + assertEquals("Unexpected value", blackListValue, props2.get(blacklistKey)); + + assertEquals("Properties were not equal", props, props2); + } + @Test public void testSetRemoteURIThrowsOnNullURI() throws Exception { JmsConnectionFactory cf = new JmsConnectionFactory(); http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ObjectMessageIntegrationTest.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ObjectMessageIntegrationTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ObjectMessageIntegrationTest.java index bae22c1..a9bc0c5 100644 --- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ObjectMessageIntegrationTest.java +++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/ObjectMessageIntegrationTest.java @@ -23,11 +23,13 @@ import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.util.HashMap; +import java.util.UUID; import javax.jms.Connection; import javax.jms.JMSException; @@ -51,13 +53,18 @@ import org.apache.qpid.jms.test.testpeer.matchers.sections.MessagePropertiesSect import org.apache.qpid.jms.test.testpeer.matchers.sections.TransferPayloadCompositeMatcher; import org.apache.qpid.jms.test.testpeer.matchers.types.EncodedAmqpValueMatcher; import org.apache.qpid.jms.test.testpeer.matchers.types.EncodedDataMatcher; +import org.apache.qpid.jms.util.SimplePojo; import org.apache.qpid.proton.amqp.Binary; import org.apache.qpid.proton.amqp.DescribedType; import org.apache.qpid.proton.amqp.Symbol; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ObjectMessageIntegrationTest extends QpidJmsTestCase { + + private static final Logger LOG = LoggerFactory.getLogger(ObjectMessageIntegrationTest.class); -public class ObjectMessageIntegrationTest extends QpidJmsTestCase -{ private final IntegrationTestFixture testFixture = new IntegrationTestFixture(); //==== Java serialization encoding ==== @@ -224,6 +231,120 @@ public class ObjectMessageIntegrationTest extends QpidJmsTestCase } } + @Test(timeout = 20000) + public void testReceiveBlockedSerializedContentFailsOnGetObject() throws Exception { + // We arent allowing the test class + doTestReceiveSerializedContentPolicyTest("java.lang,java.util", null, false); + } + + @Test(timeout = 20000) + public void testReceiveBlockAllSerializedContentFailsOnGetObject() throws Exception { + // We are blocking everything + doTestReceiveSerializedContentPolicyTest(null, "*", false); + } + + @Test(timeout = 20000) + public void testReceiveBlockSomeSerializedContentFailsOnGetObject() throws Exception { + // We arent allowing the UUID + doTestReceiveSerializedContentPolicyTest("org.apache.qpid.jms", null, false); + } + + @Test(timeout = 20000) + public void testReceiveWithWrongUnblockedSerializedContentFailsOnGetObject() throws Exception { + // We arent allowing the UUID a different way + doTestReceiveSerializedContentPolicyTest("java.lang,org.apache.qpid.jms", null, false); + } + + @Test(timeout = 20000) + public void testReceiveWithFullyWhitelistedSerializedContentSucceeds() throws Exception { + // We are allowing everything needed + doTestReceiveSerializedContentPolicyTest("java.lang,java.util,org.apache.qpid.jms", null, true); + } + + @Test(timeout = 20000) + public void testReceiveWithFullyWhitelistedSerializedContentFailsDueToBlackList() throws Exception { + // We are whitelisting everything needed, but then the blacklist is overriding to block some + doTestReceiveSerializedContentPolicyTest("java.lang,java.util,org.apache.qpid.jms", "java.util", false); + } + + private void doTestReceiveSerializedContentPolicyTest(String whiteList, String blackList, boolean succeed) throws Exception { + try (TestAmqpPeer testPeer = new TestAmqpPeer();) { + String options = null; + if(whiteList != null) { + options = "?jms.deserializationPolicy.whiteList=" + whiteList; + } + + if(blackList != null) { + if(options == null) { + options = "?"; + } else { + options += "&"; + } + + options +="jms.deserializationPolicy.blackList=" + blackList; + } + + Connection connection = testFixture.establishConnecton(testPeer, options); + + connection.start(); + + testPeer.expectBegin(); + + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + Queue queue = session.createQueue("myQueue"); + + MessageAnnotationsDescribedType msgAnnotations = new MessageAnnotationsDescribedType(); + msgAnnotations.setSymbolKeyedAnnotation(AmqpMessageSupport.JMS_MSG_TYPE, AmqpMessageSupport.JMS_OBJECT_MESSAGE); + PropertiesDescribedType properties = new PropertiesDescribedType(); + properties.setContentType(Symbol.valueOf(AmqpMessageSupport.SERIALIZED_JAVA_OBJECT_CONTENT_TYPE)); + + SimplePojo expectedContent = new SimplePojo(UUID.randomUUID()); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(expectedContent); + oos.flush(); + oos.close(); + byte[] bytes = baos.toByteArray(); + + DescribedType dataContent = new DataDescribedType(new Binary(bytes)); + + testPeer.expectReceiverAttach(); + testPeer.expectLinkFlowRespondWithTransfer(null, msgAnnotations, properties, null, dataContent); + testPeer.expectDispositionThatIsAcceptedAndSettled(); + + MessageConsumer messageConsumer = session.createConsumer(queue); + Message receivedMessage = messageConsumer.receive(3000); + testPeer.waitForAllHandlersToComplete(3000); + + assertNotNull(receivedMessage); + assertTrue(receivedMessage instanceof ObjectMessage); + + ObjectMessage objectMessage = (ObjectMessage) receivedMessage; + Object received = null; + try { + received = objectMessage.getObject(); + if(!succeed) { + fail("Should not be able to read blocked content"); + } + } catch (JMSException jmsEx) { + LOG.debug("Caught: ", jmsEx); + if(succeed) { + fail("Should have been able to read blocked content"); + } + } + + if(succeed) { + assertEquals("Content not as expected", expectedContent, received); + } + + testPeer.expectClose(); + connection.close(); + + testPeer.waitForAllHandlersToComplete(3000); + } + } + //==== AMQP type system encoding ==== @Test(timeout = 20000) http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestObjectMessageFacade.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestObjectMessageFacade.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestObjectMessageFacade.java index 6b44f3c..44bab21 100644 --- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestObjectMessageFacade.java +++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/message/facade/test/JmsTestObjectMessageFacade.java @@ -71,7 +71,7 @@ public class JmsTestObjectMessageFacade extends JmsTestMessageFacade implements Serializable serialized = null; try (ByteArrayInputStream dataIn = new ByteArrayInputStream(object); - ClassLoadingAwareObjectInputStream objIn = new ClassLoadingAwareObjectInputStream(dataIn)) { + ClassLoadingAwareObjectInputStream objIn = new ClassLoadingAwareObjectInputStream(dataIn, null)) { serialized = (Serializable) objIn.readObject(); } http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/test/java/org/apache/qpid/jms/policy/JmsDefaultDeserializationPolicyTest.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/policy/JmsDefaultDeserializationPolicyTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/policy/JmsDefaultDeserializationPolicyTest.java new file mode 100644 index 0000000..93ff466 --- /dev/null +++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/policy/JmsDefaultDeserializationPolicyTest.java @@ -0,0 +1,328 @@ +/* + * 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.qpid.jms.policy; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.util.HashMap; +import java.util.UUID; +import java.util.Vector; + +import org.apache.qpid.jms.JmsDestination; +import org.apache.qpid.jms.JmsQueue; +import org.apache.qpid.jms.util.ClassLoadingAwareObjectInputStream; +import org.apache.qpid.jms.util.ClassLoadingAwareObjectInputStream.TrustedClassFilter; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JmsDefaultDeserializationPolicyTest { + + private static final Logger LOG = LoggerFactory.getLogger(JmsDefaultDeserializationPolicyTest.class); + + @Test + public void testIsTrustedType() { + JmsDestination destination = new JmsQueue("test-queue"); + JmsDefaultDeserializationPolicy policy = new JmsDefaultDeserializationPolicy(); + + assertTrue(policy.isTrustedType(destination, null)); + assertTrue(policy.isTrustedType(destination, UUID.class)); + assertTrue(policy.isTrustedType(destination, String.class)); + assertTrue(policy.isTrustedType(destination, Boolean.class)); + assertTrue(policy.isTrustedType(destination, Double.class)); + assertTrue(policy.isTrustedType(destination, Object.class)); + + // Only types in lang + policy.setWhiteList("java.lang"); + + assertTrue(policy.isTrustedType(destination, null)); + assertFalse(policy.isTrustedType(destination, UUID.class)); + assertTrue(policy.isTrustedType(destination, String.class)); + assertTrue(policy.isTrustedType(destination, Boolean.class)); + assertFalse(policy.isTrustedType(destination, getClass())); + + // Entry must be complete package name prefix to match + // i.e while "java.n" is a prefix of "java.net", this + // wont match the socket class below. + policy.setWhiteList("java.n"); + assertFalse(policy.isTrustedType(destination, UUID.class)); + assertFalse(policy.isTrustedType(destination, String.class)); + assertFalse(policy.isTrustedType(destination, java.net.Socket.class)); + + // add a non-core package + policy.setWhiteList("java.lang,org.apache.qpid.jms"); + + assertFalse(policy.isTrustedType(destination, UUID.class)); + assertTrue(policy.isTrustedType(destination, String.class)); + assertTrue(policy.isTrustedType(destination, getClass())); + + // Try with a class-specific entry + policy.setWhiteList("java.lang.Integer"); + + assertTrue(policy.isTrustedType(destination, Integer.class)); + assertFalse(policy.isTrustedType(destination, Boolean.class)); + + // Verify blacklist overrides whitelist + policy.setWhiteList("java.lang.Integer"); + policy.setBlackList("java.lang.Integer"); + + assertFalse(policy.isTrustedType(destination, Integer.class)); + + // Verify blacklist entry prefix overrides whitelist + policy.setWhiteList("java.lang.Integer"); + policy.setBlackList("java.lang"); + + assertFalse(policy.isTrustedType(destination, Integer.class)); + + // Verify blacklist catch-all overrides whitelist + policy.setWhiteList("java.lang.Integer"); + policy.setBlackList("*"); + + assertFalse(policy.isTrustedType(destination, Integer.class)); + } + + @Test + public void testHashCode() { + JmsDeserializationPolicy policy1 = new JmsDefaultDeserializationPolicy(); + JmsDeserializationPolicy policy2 = new JmsDefaultDeserializationPolicy(); + + assertTrue(policy1.hashCode() != 0); + assertEquals(policy1.hashCode(), policy2.hashCode()); + assertEquals(policy2.hashCode(), policy1.hashCode()); + + ((JmsDefaultDeserializationPolicy) policy1).setWhiteList("java.util"); + + assertFalse(policy1.hashCode() == policy2.hashCode()); + assertFalse(policy2.hashCode() == policy1.hashCode()); + + ((JmsDefaultDeserializationPolicy) policy2).setWhiteList("java.util"); + + assertTrue(policy1.hashCode() == policy2.hashCode()); + assertTrue(policy2.hashCode() == policy1.hashCode()); + + ((JmsDefaultDeserializationPolicy) policy1).setBlackList("java.util"); + + assertFalse(policy1.hashCode() == policy2.hashCode()); + assertFalse(policy2.hashCode() == policy1.hashCode()); + + ((JmsDefaultDeserializationPolicy) policy2).setBlackList("java.util"); + + assertTrue(policy1.hashCode() == policy2.hashCode()); + assertTrue(policy2.hashCode() == policy1.hashCode()); + } + + @Test + public void testEqualsObject() { + JmsDefaultDeserializationPolicy policy1 = new JmsDefaultDeserializationPolicy(); + JmsDefaultDeserializationPolicy policy2 = new JmsDefaultDeserializationPolicy(); + + assertTrue(policy1.equals(policy1)); + assertTrue(policy1.equals(policy2)); + assertTrue(policy2.equals(policy1)); + + policy1.setWhiteList("java.util"); + + assertFalse(policy1.equals(policy2)); + assertFalse(policy2.equals(policy1)); + + assertFalse(policy1.equals(null)); + assertFalse(policy1.equals("")); + assertFalse(policy1.equals(this)); + + policy2.setWhiteList("java.util"); + assertTrue(policy1.equals(policy2)); + + policy1.setBlackList("java.util"); + + assertFalse(policy1.equals(policy2)); + assertFalse(policy2.equals(policy1)); + + policy2.setBlackList("java.util"); + assertTrue(policy1.equals(policy2)); + assertTrue(policy2.equals(policy1)); + } + + @Test + public void testJmsDefaultDeserializationPolicy() { + JmsDefaultDeserializationPolicy policy = new JmsDefaultDeserializationPolicy(); + + assertFalse(policy.getWhiteList().isEmpty()); + assertTrue(policy.getBlackList().isEmpty()); + } + + @Test + public void testJmsDefaultDeserializationPolicyCopyCtor() { + JmsDefaultDeserializationPolicy policy = new JmsDefaultDeserializationPolicy(); + + policy.setWhiteList("a.b.c"); + policy.setBlackList("d.e.f"); + + JmsDefaultDeserializationPolicy copy = new JmsDefaultDeserializationPolicy(policy); + + assertEquals("a.b.c", copy.getWhiteList()); + assertEquals("d.e.f", copy.getBlackList()); + } + + @Test + public void testJmsDefaultDeserializationPolicyCopy() { + JmsDefaultDeserializationPolicy policy = new JmsDefaultDeserializationPolicy(); + + policy.setWhiteList("a.b.c"); + policy.setBlackList("d.e.f"); + + JmsDefaultDeserializationPolicy copy = (JmsDefaultDeserializationPolicy) policy.copy(); + + assertEquals("a.b.c", copy.getWhiteList()); + assertEquals("d.e.f", copy.getBlackList()); + } + + @Test + public void testSetWhiteList() { + JmsDefaultDeserializationPolicy policy = new JmsDefaultDeserializationPolicy(); + assertNotNull(policy.getWhiteList()); + + policy.setWhiteList(null); + assertNotNull(policy.getWhiteList()); + assertTrue(policy.getWhiteList().isEmpty()); + + policy.setWhiteList("*"); + assertNotNull(policy.getWhiteList()); + assertFalse(policy.getWhiteList().isEmpty()); + } + + @Test + public void testSetBlackList() { + JmsDefaultDeserializationPolicy policy = new JmsDefaultDeserializationPolicy(); + assertNotNull(policy.getBlackList()); + + policy.setBlackList(null); + assertNotNull(policy.getBlackList()); + assertTrue(policy.getBlackList().isEmpty()); + + policy.setBlackList("*"); + assertNotNull(policy.getBlackList()); + assertFalse(policy.getBlackList().isEmpty()); + } + + @Test + public void testDeserializeVectorUsingPolicy() throws Exception { + Vector<Object> vector = new Vector<Object>(); + vector.add("pi"); + vector.add(new Integer(314159)); + vector.add(new Vector<String>()); + vector.add(Boolean.FALSE); + + final JmsDefaultDeserializationPolicy policy = new JmsDefaultDeserializationPolicy(); + ByteArrayInputStream input = new ByteArrayInputStream(serializeObject(vector)); + TrustedClassFilter filter = new TrustedClassFilter() { + + @Override + public boolean isTrusted(Class<?> clazz) { + LOG.trace("Check for trust status of class: {}", clazz.getName()); + return policy.isTrustedType(new JmsQueue(), clazz); + } + }; + + ClassLoadingAwareObjectInputStream reader = new ClassLoadingAwareObjectInputStream(input, filter); + + Object result = null; + try { + result = reader.readObject(); + } catch (Exception ex) { + fail("Should no throw any errors"); + } finally { + reader.close(); + } + + assertNotNull(result); + assertTrue(result instanceof Vector); + assertEquals(4, ((Vector<?>) result).size()); + } + + @SuppressWarnings("unchecked") + @Test + public void testDeserializeHashMapUsingPolicy() throws Exception { + HashMap<Object, Object> map = new HashMap<Object, Object>(); + + map.put("a", "Value"); + map.put("b", new Integer(1)); + map.put("c", new Vector<Object>()); + map.put("d", Boolean.FALSE); + + final JmsDefaultDeserializationPolicy policy = new JmsDefaultDeserializationPolicy(); + ByteArrayInputStream input = new ByteArrayInputStream(serializeObject(map)); + TrustedClassFilter filter = new TrustedClassFilter() { + + @Override + public boolean isTrusted(Class<?> clazz) { + LOG.trace("Check for trust status of class: {}", clazz.getName()); + return policy.isTrustedType(new JmsQueue(), clazz); + } + }; + + ClassLoadingAwareObjectInputStream reader = new ClassLoadingAwareObjectInputStream(input, filter); + + Object result = null; + try { + result = reader.readObject(); + } catch (Exception ex) { + fail("Should no throw any errors"); + } finally { + reader.close(); + } + + assertNotNull(result); + assertTrue(result instanceof HashMap); + + map = (HashMap<Object, Object>) result; + + assertEquals(4, map.size()); + + assertEquals("Value", map.get("a")); + assertEquals(new Integer(1), map.get("b")); + assertEquals(new Vector<Object>(), map.get("c")); + assertEquals(Boolean.FALSE, map.get("d")); + } + + //----- Internal methods -------------------------------------------------// + + private byte[] serializeObject(Object value) throws IOException { + byte[] result = new byte[0]; + + if (value != null) { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos)) { + + oos.writeObject(value); + oos.flush(); + oos.close(); + + result = baos.toByteArray(); + } + } + + return result; + } +} http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageBuilderTest.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageBuilderTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageBuilderTest.java index a534de0..eee5f8e 100644 --- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageBuilderTest.java +++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageBuilderTest.java @@ -41,6 +41,8 @@ import org.apache.qpid.jms.message.JmsObjectMessage; import org.apache.qpid.jms.message.JmsStreamMessage; import org.apache.qpid.jms.message.JmsTextMessage; import org.apache.qpid.jms.message.facade.JmsMessageFacade; +import org.apache.qpid.jms.meta.JmsConsumerId; +import org.apache.qpid.jms.meta.JmsConsumerInfo; import org.apache.qpid.jms.provider.amqp.AmqpConsumer; import org.apache.qpid.jms.test.QpidJmsTestCase; import org.apache.qpid.proton.Proton; @@ -62,7 +64,10 @@ public class AmqpJmsMessageBuilderTest extends QpidJmsTestCase { @Override public void setUp() throws Exception { super.setUp(); + + JmsConsumerId consumerId = new JmsConsumerId("ID:MOCK:1", 1, 1); mockConsumer = Mockito.mock(AmqpConsumer.class); + Mockito.when(mockConsumer.getResourceInfo()).thenReturn(new JmsConsumerInfo(consumerId)); } @Test http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFactoryTest.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFactoryTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFactoryTest.java index 28530d9..4d28ec1 100644 --- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFactoryTest.java +++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageFactoryTest.java @@ -48,6 +48,8 @@ import org.apache.qpid.jms.message.facade.JmsMessageFacade; import org.apache.qpid.jms.message.facade.JmsObjectMessageFacade; import org.apache.qpid.jms.message.facade.JmsStreamMessageFacade; import org.apache.qpid.jms.message.facade.JmsTextMessageFacade; +import org.apache.qpid.jms.meta.JmsConnectionId; +import org.apache.qpid.jms.meta.JmsConnectionInfo; import org.apache.qpid.jms.provider.amqp.AmqpConnection; import org.apache.qpid.jms.test.QpidJmsTestCase; import org.junit.Test; @@ -194,12 +196,19 @@ public class AmqpJmsMessageFactoryTest extends QpidJmsTestCase { } private AmqpConnection createMockAmqpConnectionAmqpTypes() { + JmsConnectionId connectionId = new JmsConnectionId("ID:MOCK:1"); AmqpConnection connection = Mockito.mock(AmqpConnection.class); Mockito.when(connection.isObjectMessageUsesAmqpTypes()).thenReturn(true); + Mockito.when(connection.getResourceInfo()).thenReturn(new JmsConnectionInfo(connectionId)); + return connection; } private AmqpConnection createMockAmqpConnection() { - return Mockito.mock(AmqpConnection.class); + JmsConnectionId connectionId = new JmsConnectionId("ID:MOCK:1"); + AmqpConnection connection = Mockito.mock(AmqpConnection.class); + Mockito.when(connection.getResourceInfo()).thenReturn(new JmsConnectionInfo(connectionId)); + + return connection; } } http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageTypesTestCase.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageTypesTestCase.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageTypesTestCase.java index 3775269..48a78f6 100644 --- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageTypesTestCase.java +++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/provider/amqp/message/AmqpJmsMessageTypesTestCase.java @@ -22,6 +22,10 @@ import java.nio.charset.StandardCharsets; import org.apache.qpid.jms.JmsDestination; import org.apache.qpid.jms.JmsTopic; +import org.apache.qpid.jms.meta.JmsConnectionId; +import org.apache.qpid.jms.meta.JmsConnectionInfo; +import org.apache.qpid.jms.meta.JmsConsumerId; +import org.apache.qpid.jms.meta.JmsConsumerInfo; import org.apache.qpid.jms.provider.amqp.AmqpConnection; import org.apache.qpid.jms.provider.amqp.AmqpConsumer; import org.apache.qpid.jms.test.QpidJmsTestCase; @@ -91,13 +95,20 @@ public class AmqpJmsMessageTypesTestCase extends QpidJmsTestCase { } protected AmqpConsumer createMockAmqpConsumer() { + JmsConsumerId consumerId = new JmsConsumerId("ID:MOCK:1:1:1"); + AmqpConnection connection = createMockAmqpConnection(); AmqpConsumer consumer = Mockito.mock(AmqpConsumer.class); - Mockito.when(consumer.getConnection()).thenReturn(createMockAmqpConnection()); + Mockito.when(consumer.getConnection()).thenReturn(connection); Mockito.when(consumer.getDestination()).thenReturn(consumerDestination); + Mockito.when(consumer.getResourceInfo()).thenReturn(new JmsConsumerInfo(consumerId)); return consumer; } protected AmqpConnection createMockAmqpConnection() { - return Mockito.mock(AmqpConnection.class); + JmsConnectionId connectionId = new JmsConnectionId("ID:MOCK:1"); + AmqpConnection connection = Mockito.mock(AmqpConnection.class); + Mockito.when(connection.getResourceInfo()).thenReturn(new JmsConnectionInfo(connectionId)); + + return connection; } } http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/669cfff8/qpid-jms-client/src/test/java/org/apache/qpid/jms/util/AnonymousSimplePojoParent.java ---------------------------------------------------------------------- diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/util/AnonymousSimplePojoParent.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/util/AnonymousSimplePojoParent.java new file mode 100644 index 0000000..32eb3f9 --- /dev/null +++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/util/AnonymousSimplePojoParent.java @@ -0,0 +1,39 @@ +/* + * 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.qpid.jms.util; + +import java.io.Serializable; + +public class AnonymousSimplePojoParent implements Serializable { + + private static final long serialVersionUID = 1L; + + private SimplePojo payload; + + public AnonymousSimplePojoParent(Object simplePojoPayload) { + // Create an ANONYMOUS simple payload, itself serializable, like we + // have to be since the object references us and is used + // during the serialization. + payload = new SimplePojo(simplePojoPayload) { + private static final long serialVersionUID = 1L; + }; + } + + public SimplePojo getPayload() { + return payload; + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
