Merge pull request #85 from apache/allow_deny_list
AMQNET-828 Add allow, deny types support
diff --git a/src/NMS.AMQP/Meta/NmsConnectionInfo.cs b/src/NMS.AMQP/Meta/NmsConnectionInfo.cs
index 103a4cb..f9120c6 100644
--- a/src/NMS.AMQP/Meta/NmsConnectionInfo.cs
+++ b/src/NMS.AMQP/Meta/NmsConnectionInfo.cs
@@ -17,6 +17,7 @@
using System;
using Amqp;
+using Apache.NMS.AMQP.Policies;
namespace Apache.NMS.AMQP.Meta
{
@@ -79,10 +80,8 @@
public bool SharedSubsSupported { get; set; }
public PrefetchPolicyInfo PrefetchPolicy { get; set; } = DEFAULT_PREFETCH_POLICY;
-
-
-
-
+ public INmsDeserializationPolicy DeserializationPolicy { get; set; }
+
public void SetClientId(string clientId, bool explicitClientId)
{
diff --git a/src/NMS.AMQP/Meta/NmsConsumerInfo.cs b/src/NMS.AMQP/Meta/NmsConsumerInfo.cs
index b5506c3..3d808be 100644
--- a/src/NMS.AMQP/Meta/NmsConsumerInfo.cs
+++ b/src/NMS.AMQP/Meta/NmsConsumerInfo.cs
@@ -16,6 +16,7 @@
*/
using System;
+using Apache.NMS.AMQP.Policies;
namespace Apache.NMS.AMQP.Meta
{
@@ -38,7 +39,8 @@
public bool LocalMessageExpiry { get; set; }
public bool IsBrowser { get; set; }
public int LinkCredit { get; set; }
-
+ public INmsDeserializationPolicy DeserializationPolicy { get; set; }
+
public bool HasSelector() => !string.IsNullOrWhiteSpace(Selector);
protected bool Equals(NmsConsumerInfo other)
diff --git a/src/NMS.AMQP/NmsConnectionFactory.cs b/src/NMS.AMQP/NmsConnectionFactory.cs
index 6212ddc..7684f30 100644
--- a/src/NMS.AMQP/NmsConnectionFactory.cs
+++ b/src/NMS.AMQP/NmsConnectionFactory.cs
@@ -17,9 +17,9 @@
using System;
using System.Collections.Specialized;
-using System.Threading;
using System.Threading.Tasks;
using Apache.NMS.AMQP.Meta;
+using Apache.NMS.AMQP.Policies;
using Apache.NMS.AMQP.Provider;
using Apache.NMS.AMQP.Util;
using Apache.NMS.AMQP.Util.Synchronization;
@@ -308,6 +308,12 @@
}
public IRedeliveryPolicy RedeliveryPolicy { get; set; }
+
+ /// <summary>
+ /// The deserialization policy that is applied when a connection is created.
+ /// </summary>
+ public INmsDeserializationPolicy DeserializationPolicy { get; set; } = new NmsDefaultDeserializationPolicy();
+
public ConsumerTransformerDelegate ConsumerTransformer { get; set; }
public ProducerTransformerDelegate ProducerTransformer { get; set; }
@@ -339,7 +345,8 @@
SendTimeout = SendTimeout,
CloseTimeout = CloseTimeout,
LocalMessageExpiry = LocalMessageExpiry,
- PrefetchPolicy = PrefetchPolicy.Clone()
+ PrefetchPolicy = PrefetchPolicy.Clone(),
+ DeserializationPolicy = DeserializationPolicy.Clone()
};
bool userSpecifiedClientId = ClientId != null;
diff --git a/src/NMS.AMQP/NmsMessageConsumer.cs b/src/NMS.AMQP/NmsMessageConsumer.cs
index c214ee0..05261e6 100644
--- a/src/NMS.AMQP/NmsMessageConsumer.cs
+++ b/src/NMS.AMQP/NmsMessageConsumer.cs
@@ -62,7 +62,8 @@
IsDurable = IsDurableSubscription,
IsBrowser = IsBrowser,
LocalMessageExpiry = Session.Connection.ConnectionInfo.LocalMessageExpiry,
- LinkCredit = Session.Connection.ConnectionInfo.PrefetchPolicy.GetLinkCredit(destination, IsBrowser, IsDurableSubscription)
+ LinkCredit = Session.Connection.ConnectionInfo.PrefetchPolicy.GetLinkCredit(destination, IsBrowser, IsDurableSubscription),
+ DeserializationPolicy = Session.Connection.ConnectionInfo.DeserializationPolicy.Clone()
};
deliveryTask = new MessageDeliveryTask(this);
}
diff --git a/src/NMS.AMQP/Policies/INmsDeserializationPolicy.cs b/src/NMS.AMQP/Policies/INmsDeserializationPolicy.cs
new file mode 100644
index 0000000..32026fc
--- /dev/null
+++ b/src/NMS.AMQP/Policies/INmsDeserializationPolicy.cs
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+using System;
+
+namespace Apache.NMS.AMQP.Policies
+{
+ /// <summary>
+ /// Defines the interface for a policy that controls the permissible message content
+ /// during the deserialization of the body of an incoming <see cref="IObjectMessage"/>.
+ /// </summary>
+ public interface INmsDeserializationPolicy
+ {
+ /// <summary>
+ /// Determines if the given class is a trusted type that can be deserialized by the client.
+ /// </summary>
+ /// <param name="destination">The Destination for the message containing the type to be deserialized.</param>
+ /// <param name="type">The type of the object that is about to be read.</param>
+ /// <returns>True if the type is trusted, otherwise false.</returns>
+ bool IsTrustedType(IDestination destination, Type type);
+
+ /// <summary>
+ /// Makes a thread-safe copy of the INmsDeserializationPolicy object.
+ /// </summary>
+ /// <returns>A copy of INmsDeserializationPolicy object.</returns>
+ INmsDeserializationPolicy Clone();
+ }
+}
\ No newline at end of file
diff --git a/src/NMS.AMQP/Policies/NmsDefaultDeserializationPolicy.cs b/src/NMS.AMQP/Policies/NmsDefaultDeserializationPolicy.cs
new file mode 100644
index 0000000..76d2d51
--- /dev/null
+++ b/src/NMS.AMQP/Policies/NmsDefaultDeserializationPolicy.cs
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Apache.NMS.AMQP.Policies
+{
+ /// <summary>
+ /// Default implementation of the deserialization policy that can read allow and deny lists of
+ /// types/namespaces from the connection URI options.
+ ///
+ /// The policy reads a default deny list string value (comma separated) from the connection URI options
+ /// (nms.deserializationPolicy.deny) which defaults to null which indicates an empty deny list.
+ ///
+ /// The policy reads a default allow list string value (comma separated) from the connection URI options
+ /// (nms.deserializationPolicy.allowList) which defaults to <see cref="CATCH_ALL_WILDCARD"/> which
+ /// indicates that all types are allowed.
+ ///
+ /// The deny list overrides the allow list, entries that could match both are counted as denied.
+ ///
+ /// If the policy should treat all classes as untrusted, the deny list should be set to <see cref="CATCH_ALL_WILDCARD"/>.
+ /// </summary>
+ public class NmsDefaultDeserializationPolicy : INmsDeserializationPolicy
+ {
+ /// <summary>
+ /// Value used to indicate that all types should be allowed or denied
+ /// </summary>
+ public const string CATCH_ALL_WILDCARD = "*";
+
+ private IReadOnlyList<string> denyList = Array.Empty<string>();
+ private IReadOnlyList<string> allowList = new[] { CATCH_ALL_WILDCARD };
+
+ public bool IsTrustedType(IDestination destination, Type type)
+ {
+ var typeName = type?.FullName;
+ if (typeName == null)
+ {
+ return true;
+ }
+
+ foreach (var denyListEntry in denyList)
+ {
+ if (CATCH_ALL_WILDCARD == denyListEntry)
+ {
+ return false;
+ }
+ if (IsTypeOrNamespaceMatch(typeName, denyListEntry))
+ {
+ return false;
+ }
+ }
+
+ foreach (var allowListEntry in allowList)
+ {
+ if (CATCH_ALL_WILDCARD == allowListEntry)
+ {
+ return true;
+ }
+ if (IsTypeOrNamespaceMatch(typeName, allowListEntry))
+ {
+ return true;
+ }
+ }
+
+ // Failing outright rejection or allow from above, reject.
+ return false;
+ }
+
+ private bool IsTypeOrNamespaceMatch(string typeName, string listEntry)
+ {
+ // Check if type is an exact match of the entry
+ if (typeName == listEntry)
+ {
+ return true;
+ }
+
+ // Check if the type is from a namespace matching the entry
+ var entryLength = listEntry.Length;
+ return typeName.Length > entryLength && typeName.StartsWith(listEntry) && '.' == typeName[entryLength];
+ }
+
+ public INmsDeserializationPolicy Clone()
+ {
+ return new NmsDefaultDeserializationPolicy
+ {
+ allowList = allowList.ToArray(),
+ denyList = denyList.ToArray()
+ };
+ }
+
+ /// <summary>
+ /// Gets or sets the deny list on this policy instance.
+ /// </summary>
+ public string DenyList
+ {
+ get => string.Join(",", denyList);
+ set => denyList = string.IsNullOrWhiteSpace(value)
+ ? Array.Empty<string>()
+ : value.Split(',');
+ }
+
+ /// <summary>
+ /// Gets or sets the allow list on this policy instance.
+ /// </summary>
+ public string AllowList
+ {
+ get => string.Join(",", allowList);
+ set => allowList = string.IsNullOrWhiteSpace(value)
+ ? Array.Empty<string>()
+ : value.Split(',');
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NMS.AMQP/Provider/Amqp/AmqpConsumer.cs b/src/NMS.AMQP/Provider/Amqp/AmqpConsumer.cs
index fc3a5cf..99666c8 100644
--- a/src/NMS.AMQP/Provider/Amqp/AmqpConsumer.cs
+++ b/src/NMS.AMQP/Provider/Amqp/AmqpConsumer.cs
@@ -33,6 +33,7 @@
{
public interface IAmqpConsumer
{
+ NmsConsumerInfo ResourceInfo { get; }
IDestination Destination { get; }
IAmqpConnection Connection { get; }
}
@@ -60,7 +61,7 @@
}
public NmsConsumerId ConsumerId => this.info.Id;
-
+ public NmsConsumerInfo ResourceInfo => this.info;
public Task Attach()
{
diff --git a/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsObjectMessageFacade.cs b/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsObjectMessageFacade.cs
index d8454fa..94c9074 100644
--- a/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsObjectMessageFacade.cs
+++ b/src/NMS.AMQP/Provider/Amqp/Message/AmqpNmsObjectMessageFacade.cs
@@ -19,16 +19,18 @@
using System.IO;
using Apache.NMS.AMQP.Message;
using Apache.NMS.AMQP.Message.Facade;
+using Apache.NMS.AMQP.Policies;
using Apache.NMS.AMQP.Util;
namespace Apache.NMS.AMQP.Provider.Amqp.Message
{
public class AmqpNmsObjectMessageFacade : AmqpNmsMessageFacade, INmsObjectMessageFacade
{
+ private INmsDeserializationPolicy deserializationPolicy;
private IAmqpObjectTypeDelegate typeDelegate;
public IAmqpObjectTypeDelegate Delegate => typeDelegate;
-
+
public object Object
{
get => Delegate.Object;
@@ -68,15 +70,18 @@
public override void Initialize(IAmqpConsumer consumer, global::Amqp.Message message)
{
base.Initialize(consumer, message);
- bool dotnetSerialized = MessageSupport.SERIALIZED_DOTNET_OBJECT_CONTENT_TYPE.Equals(ContentType);
- InitSerializer(!dotnetSerialized);
+ deserializationPolicy = consumer.ResourceInfo.DeserializationPolicy;
+ bool hasDotNetSerializedType = MessageSupport.SERIALIZED_DOTNET_OBJECT_CONTENT_TYPE.Equals(ContentType);
+ InitSerializer(!hasDotNetSerializedType);
}
+
+
private void InitSerializer(bool useAmqpTypes)
{
if (!useAmqpTypes)
{
- typeDelegate = new AmqpSerializedObjectDelegate(this);
+ typeDelegate = new AmqpSerializedObjectDelegate(this, deserializationPolicy);
}
else
{
@@ -86,9 +91,10 @@
public override INmsMessageFacade Copy()
{
- AmqpNmsObjectMessageFacade copy = new AmqpNmsObjectMessageFacade();
+ var copy = new AmqpNmsObjectMessageFacade();
+ copy.deserializationPolicy = deserializationPolicy;
CopyInto(copy);
- copy.typeDelegate = typeDelegate;
+ copy.InitSerializer(typeDelegate.IsAmqpTypeEncoded());
return copy;
}
diff --git a/src/NMS.AMQP/Provider/Amqp/Message/AmqpSerializedObjectDelegate.cs b/src/NMS.AMQP/Provider/Amqp/Message/AmqpSerializedObjectDelegate.cs
index 9c1a4a4..b6494c1 100644
--- a/src/NMS.AMQP/Provider/Amqp/Message/AmqpSerializedObjectDelegate.cs
+++ b/src/NMS.AMQP/Provider/Amqp/Message/AmqpSerializedObjectDelegate.cs
@@ -15,10 +15,12 @@
* limitations under the License.
*/
+using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Amqp.Framing;
+using Apache.NMS.AMQP.Policies;
using Apache.NMS.AMQP.Util;
namespace Apache.NMS.AMQP.Provider.Amqp.Message
@@ -28,10 +30,13 @@
public static readonly Data NULL_OBJECT_BODY = new Data() {Binary = new byte[] {0xac, 0xed, 0x00, 0x05, 0x70}};
private readonly AmqpNmsObjectMessageFacade facade;
+ private readonly INmsDeserializationPolicy deserializationPolicy;
+ private bool localContent;
- public AmqpSerializedObjectDelegate(AmqpNmsObjectMessageFacade facade)
+ public AmqpSerializedObjectDelegate(AmqpNmsObjectMessageFacade facade, INmsDeserializationPolicy deserializationPolicy)
{
this.facade = facade;
+ this.deserializationPolicy = deserializationPolicy;
facade.ContentType = MessageSupport.SERIALIZED_DOTNET_OBJECT_CONTENT_TYPE;
}
@@ -64,6 +69,8 @@
{
facade.Message.BodySection = NULL_OBJECT_BODY;
}
+
+ localContent = true;
}
}
@@ -74,11 +81,18 @@
facade.Message.BodySection = NULL_OBJECT_BODY;
}
+ public bool IsAmqpTypeEncoded() => false;
+
private object Deserialize(byte[] binary)
{
- using (MemoryStream stream = new MemoryStream(binary))
+ using (var stream = new MemoryStream(binary))
{
IFormatter formatter = new BinaryFormatter();
+ if (localContent == false && deserializationPolicy != null)
+ {
+ formatter.Binder = new TrustedClassFilter(deserializationPolicy, facade.NMSDestination);
+ }
+
return formatter.Deserialize(stream);
}
}
diff --git a/src/NMS.AMQP/Provider/Amqp/Message/AmqpTypedObjectDelegate.cs b/src/NMS.AMQP/Provider/Amqp/Message/AmqpTypedObjectDelegate.cs
index e1dd071..cfaafef 100644
--- a/src/NMS.AMQP/Provider/Amqp/Message/AmqpTypedObjectDelegate.cs
+++ b/src/NMS.AMQP/Provider/Amqp/Message/AmqpTypedObjectDelegate.cs
@@ -103,5 +103,7 @@
if (facade.Message.BodySection == null)
facade.Message.BodySection = NULL_OBJECT_BODY;
}
+
+ public bool IsAmqpTypeEncoded() => true;
}
}
\ No newline at end of file
diff --git a/src/NMS.AMQP/Provider/Amqp/Message/IAmqpObjectTypeDelegate.cs b/src/NMS.AMQP/Provider/Amqp/Message/IAmqpObjectTypeDelegate.cs
index d0a0a28..72e7f01 100644
--- a/src/NMS.AMQP/Provider/Amqp/Message/IAmqpObjectTypeDelegate.cs
+++ b/src/NMS.AMQP/Provider/Amqp/Message/IAmqpObjectTypeDelegate.cs
@@ -25,5 +25,6 @@
{
object Object { get; set; }
void OnSend();
+ bool IsAmqpTypeEncoded();
}
}
\ No newline at end of file
diff --git a/src/NMS.AMQP/Provider/Amqp/Message/TrustedClassFilter.cs b/src/NMS.AMQP/Provider/Amqp/Message/TrustedClassFilter.cs
new file mode 100644
index 0000000..c761487
--- /dev/null
+++ b/src/NMS.AMQP/Provider/Amqp/Message/TrustedClassFilter.cs
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+using System;
+using System.Reflection;
+using System.Runtime.Serialization;
+using Apache.NMS.AMQP.Policies;
+
+namespace Apache.NMS.AMQP.Provider.Amqp.Message
+{
+ internal class TrustedClassFilter : SerializationBinder
+ {
+ private readonly INmsDeserializationPolicy deserializationPolicy;
+ private readonly IDestination destination;
+
+ public TrustedClassFilter(INmsDeserializationPolicy deserializationPolicy, IDestination destination)
+ {
+ this.deserializationPolicy = deserializationPolicy;
+ this.destination = destination;
+ }
+
+ public override Type BindToType(string assemblyName, string typeName)
+ {
+ var name = new AssemblyName(assemblyName);
+ var assembly = Assembly.Load(name);
+ var type = FormatterServices.GetTypeFromAssembly(assembly, typeName);
+ if (deserializationPolicy.IsTrustedType(destination, type))
+ {
+ return type;
+ }
+
+ var message = $"Forbidden {type.FullName}! " +
+ "This type is not trusted to be deserialized under the current configuration. " +
+ "Please refer to the documentation for more information on how to configure trusted types.";
+ throw new SerializationException(message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Interop-Test/AmqpTestSupport.cs b/test/Apache-NMS-AMQP-Interop-Test/AmqpTestSupport.cs
index c742457..2564139 100644
--- a/test/Apache-NMS-AMQP-Interop-Test/AmqpTestSupport.cs
+++ b/test/Apache-NMS-AMQP-Interop-Test/AmqpTestSupport.cs
@@ -49,7 +49,7 @@
return connection;
}
- protected IConnection CreateAmqpConnection(string clientId = null, string options = null)
+ protected IConnection CreateAmqpConnection(string clientId = null, string options = null, Action<NmsConnectionFactory> configureConnectionFactory = null)
{
string brokerUri = Environment.GetEnvironmentVariable("NMS_AMQP_TEST_URI") ?? "amqp://127.0.0.1:5672";
if (options != null)
@@ -61,6 +61,7 @@
NmsConnectionFactory factory = new NmsConnectionFactory(brokerUri);
factory.ClientId = clientId;
+ configureConnectionFactory?.Invoke(factory);
return factory.CreateConnection(userName, password);
}
diff --git a/test/Apache-NMS-AMQP-Interop-Test/NmsMessageConsumerTest.cs b/test/Apache-NMS-AMQP-Interop-Test/NmsMessageConsumerTest.cs
index 7b2de68..ff60deb 100644
--- a/test/Apache-NMS-AMQP-Interop-Test/NmsMessageConsumerTest.cs
+++ b/test/Apache-NMS-AMQP-Interop-Test/NmsMessageConsumerTest.cs
@@ -19,9 +19,11 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Apache.NMS;
+using Apache.NMS.AMQP.Policies;
using NUnit.Framework;
namespace NMS.AMQP.Test
@@ -375,5 +377,87 @@
IMessageConsumer messageConsumer = session.CreateConsumer(topic, null, noLocal: true);
Assert.IsNull(messageConsumer.Receive(TimeSpan.FromMilliseconds(500)));
}
+
+ [Test, Timeout(20_000)]
+ public void TestShouldNotDeserializeUntrustedType()
+ {
+ Connection = CreateAmqpConnection(configureConnectionFactory: factory =>
+ {
+ var deserializationPolicy = new NmsDefaultDeserializationPolicy
+ {
+ DenyList = typeof(UntrustedType).FullName
+ };
+ factory.DeserializationPolicy = deserializationPolicy;
+ });
+ Connection.Start();
+ var session = Connection.CreateSession(AcknowledgementMode.AutoAcknowledge);
+ var queue = session.GetQueue(TestName);
+ var consumer = session.CreateConsumer(queue);
+ var producer = session.CreateProducer(queue);
+
+ var message = producer.CreateObjectMessage(new UntrustedType { Prop1 = "foo" });
+ producer.Send(message);
+
+ var receivedMessage = consumer.Receive();
+ var objectMessage = receivedMessage as IObjectMessage;
+ Assert.NotNull(objectMessage);
+ var exception = Assert.Throws<SerializationException>(() =>
+ {
+ _ = objectMessage.Body;
+ });
+ Assert.AreEqual($"Forbidden {typeof(UntrustedType).FullName}! " +
+ "This type is not trusted to be deserialized under the current configuration. " +
+ "Please refer to the documentation for more information on how to configure trusted types.",
+ exception.Message);
+ }
+
+ [Test]
+ public void TestShouldUseCustomDeserializationPolicy()
+ {
+ Connection = CreateAmqpConnection(configureConnectionFactory: factory =>
+ {
+ factory.DeserializationPolicy = new CustomDeserializationPolicy();
+ });
+ Connection.Start();
+ var session = Connection.CreateSession(AcknowledgementMode.AutoAcknowledge);
+ var queue = session.GetQueue(TestName);
+ var consumer = session.CreateConsumer(queue);
+ var producer = session.CreateProducer(queue);
+
+ var message = producer.CreateObjectMessage(new UntrustedType { Prop1 = "foo" });
+ producer.Send(message);
+
+ var receivedMessage = consumer.Receive();
+ var objectMessage = receivedMessage as IObjectMessage;
+ Assert.NotNull(objectMessage);
+ _ = Assert.Throws<SerializationException>(() =>
+ {
+ _ = objectMessage.Body;
+ });
+ }
+ }
+
+ [Serializable]
+ public class UntrustedType
+ {
+ public string Prop1 { get; set; }
+ }
+
+ public class CustomDeserializationPolicy : INmsDeserializationPolicy
+ {
+ public bool IsTrustedType(IDestination destination, Type type)
+ {
+ if (type == typeof(UntrustedType))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ public INmsDeserializationPolicy Clone()
+ {
+ return this;
+ }
}
}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/ConnectionFactoryTest.cs b/test/Apache-NMS-AMQP-Test/ConnectionFactoryTest.cs
index 096e53f..6c62465 100644
--- a/test/Apache-NMS-AMQP-Test/ConnectionFactoryTest.cs
+++ b/test/Apache-NMS-AMQP-Test/ConnectionFactoryTest.cs
@@ -20,6 +20,7 @@
using System.Threading.Tasks;
using Apache.NMS;
using Apache.NMS.AMQP;
+using Apache.NMS.AMQP.Policies;
using Apache.NMS.AMQP.Provider;
using NMS.AMQP.Test.Provider.Mock;
using NUnit.Framework;
@@ -177,6 +178,21 @@
}
[Test]
+ public void TestSetDeserializationPolicy()
+ {
+ string baseUri = "amqp://localhost:1234";
+ string configuredUri = baseUri +
+ "?nms.deserializationPolicy.allowList=a,b,c" +
+ "&nms.deserializationPolicy.denyList=c,d,e";
+
+ var factory = new NmsConnectionFactory(new Uri(configuredUri));
+ var deserializationPolicy = factory.DeserializationPolicy as NmsDefaultDeserializationPolicy;
+ Assert.IsNotNull(deserializationPolicy);
+ Assert.AreEqual("a,b,c", deserializationPolicy.AllowList);
+ Assert.AreEqual("c,d,e", deserializationPolicy.DenyList);
+ }
+
+ [Test]
public void TestCreateConnectionBadBrokerUri()
{
NmsConnectionFactory factory = new NmsConnectionFactory
diff --git a/test/Apache-NMS-AMQP-Test/Policies/NmsDefaultDeserializationPolicyTest.cs b/test/Apache-NMS-AMQP-Test/Policies/NmsDefaultDeserializationPolicyTest.cs
new file mode 100644
index 0000000..33645b1
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Policies/NmsDefaultDeserializationPolicyTest.cs
@@ -0,0 +1,157 @@
+/*
+ * 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.
+ */
+
+using System;
+using Apache.NMS.AMQP;
+using Apache.NMS.AMQP.Policies;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Policies
+{
+ [TestFixture]
+ public class NmsDefaultDeserializationPolicyTest
+ {
+ [Test]
+ public void TestIsTrustedType()
+ {
+ var destination = new NmsQueue("test-queue");
+ var policy = new NmsDefaultDeserializationPolicy();
+
+ Assert.True(policy.IsTrustedType(destination, null));
+ Assert.True(policy.IsTrustedType(destination, typeof(Guid)));
+ Assert.True(policy.IsTrustedType(destination, typeof(string)));
+ Assert.True(policy.IsTrustedType(destination, typeof(bool)));
+ Assert.True(policy.IsTrustedType(destination, typeof(double)));
+ Assert.True(policy.IsTrustedType(destination, typeof(object)));
+
+ // Only types in System
+ policy.AllowList = "System";
+ Assert.True(policy.IsTrustedType(destination, null));
+ Assert.True(policy.IsTrustedType(destination, typeof(Guid)));
+ Assert.True(policy.IsTrustedType(destination, typeof(string)));
+ Assert.True(policy.IsTrustedType(destination, typeof(bool)));
+ Assert.True(policy.IsTrustedType(destination, typeof(double)));
+ Assert.True(policy.IsTrustedType(destination, typeof(object)));
+ Assert.False(policy.IsTrustedType(destination, GetType()));
+
+ // Entry must be complete namespace name prefix to match
+ // i.e. while "System.C" is a prefix of "System.Collections", this
+ // wont match the Queue class below.
+ policy.AllowList = "System.C";
+ Assert.False(policy.IsTrustedType(destination, typeof(Guid)));
+ Assert.False(policy.IsTrustedType(destination, typeof(string)));
+ Assert.False(policy.IsTrustedType(destination, typeof(System.Collections.Queue)));
+
+ // Add a non-core namespace
+ policy.AllowList = $"System,{GetType().Namespace}";
+ Assert.True(policy.IsTrustedType(destination, typeof(string)));
+ Assert.True(policy.IsTrustedType(destination, GetType()));
+
+ // Try with a type-specific entry
+ policy.AllowList = typeof(string).FullName;
+ Assert.True(policy.IsTrustedType(destination, typeof(string)));
+ Assert.False(policy.IsTrustedType(destination, typeof(bool)));
+
+ // Verify deny list overrides allow list
+ policy.AllowList = "System";
+ policy.DenyList = "System";
+ Assert.False(policy.IsTrustedType(destination, typeof(string)));
+
+ // Verify deny list entry prefix overrides allow list
+ policy.AllowList = typeof(string).FullName;
+ policy.DenyList = typeof(string).Namespace;
+ Assert.False(policy.IsTrustedType(destination, typeof(string)));
+
+ // Verify deny list catch-all overrides allow list
+ policy.AllowList = typeof(string).FullName;
+ policy.DenyList = NmsDefaultDeserializationPolicy.CATCH_ALL_WILDCARD;
+ Assert.False(policy.IsTrustedType(destination, typeof(string)));
+ }
+
+ [Test]
+ public void TestNmsDefaultDeserializationPolicy()
+ {
+ var policy = new NmsDefaultDeserializationPolicy();
+
+ Assert.IsNotEmpty(policy.AllowList);
+ Assert.IsEmpty(policy.DenyList);
+ }
+
+ [Test]
+ public void TestNmsDefaultDeserializationPolicyClone()
+ {
+ var policy = new NmsDefaultDeserializationPolicy
+ {
+ AllowList = "a.b.c",
+ DenyList = "d.e.f"
+ };
+
+ var clone = (NmsDefaultDeserializationPolicy) policy.Clone();
+ Assert.AreEqual(policy.AllowList, clone.AllowList);
+ Assert.AreEqual(policy.DenyList, clone.DenyList);
+ Assert.AreNotSame(clone, policy);
+ }
+
+ [Test]
+ public void TestSetAllowList()
+ {
+ var policy = new NmsDefaultDeserializationPolicy();
+ Assert.NotNull(policy.AllowList);
+
+ policy.AllowList = null;
+ Assert.NotNull(policy.AllowList);
+ Assert.IsEmpty(policy.AllowList);
+
+ policy.AllowList = string.Empty;
+ Assert.NotNull(policy.AllowList);
+ Assert.IsEmpty(policy.AllowList);
+
+ policy.AllowList = "*";
+ Assert.NotNull(policy.AllowList);
+ Assert.IsNotEmpty(policy.AllowList);
+
+ policy.AllowList = "a,b,c";
+ Assert.NotNull(policy.AllowList);
+ Assert.IsNotEmpty(policy.AllowList);
+ Assert.AreEqual("a,b,c", policy.AllowList);
+ }
+
+ [Test]
+ public void TestSetDenyList()
+ {
+ var policy = new NmsDefaultDeserializationPolicy();
+ Assert.NotNull(policy.DenyList);
+
+ policy.DenyList = null;
+ Assert.NotNull(policy.DenyList);
+ Assert.IsEmpty(policy.DenyList);
+
+ policy.DenyList = string.Empty;
+ Assert.NotNull(policy.DenyList);
+ Assert.IsEmpty(policy.DenyList);
+
+ policy.DenyList = "*";
+ Assert.NotNull(policy.DenyList);
+ Assert.IsNotEmpty(policy.DenyList);
+
+ policy.DenyList = "a,b,c";
+ Assert.NotNull(policy.DenyList);
+ Assert.IsNotEmpty(policy.DenyList);
+ Assert.AreEqual("a,b,c", policy.DenyList);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Provider/Amqp/AmqpNmsMessageTypesTestCase.cs b/test/Apache-NMS-AMQP-Test/Provider/Amqp/AmqpNmsMessageTypesTestCase.cs
index c01c6d8..dbfc322 100644
--- a/test/Apache-NMS-AMQP-Test/Provider/Amqp/AmqpNmsMessageTypesTestCase.cs
+++ b/test/Apache-NMS-AMQP-Test/Provider/Amqp/AmqpNmsMessageTypesTestCase.cs
@@ -16,6 +16,7 @@
*/
using Apache.NMS.AMQP;
+using Apache.NMS.AMQP.Meta;
using Apache.NMS.AMQP.Provider.Amqp;
using Apache.NMS.AMQP.Provider.Amqp.Message;
using Moq;
@@ -127,9 +128,10 @@
protected IAmqpConsumer CreateMockConsumer()
{
- Mock<IAmqpConsumer> mockConsumer = new Mock<IAmqpConsumer>();
+ var mockConsumer = new Mock<IAmqpConsumer>();
mockConsumer.Setup(consumer => consumer.Connection).Returns(() => CreateMockAmqpConnection());
mockConsumer.Setup(consumer => consumer.Destination).Returns(new NmsTopic("TestTopic"));
+ mockConsumer.Setup(consumer => consumer.ResourceInfo).Returns(new NmsConsumerInfo(new NmsConsumerId(new NmsSessionId(new NmsConnectionId("1"), 1), 1)));
return mockConsumer.Object;
}
diff --git a/test/Apache-NMS-AMQP-Test/Provider/Amqp/AmqpNmsObjectMessageFacadeTest.cs b/test/Apache-NMS-AMQP-Test/Provider/Amqp/AmqpNmsObjectMessageFacadeTest.cs
index 2a9b224..17a7d80 100644
--- a/test/Apache-NMS-AMQP-Test/Provider/Amqp/AmqpNmsObjectMessageFacadeTest.cs
+++ b/test/Apache-NMS-AMQP-Test/Provider/Amqp/AmqpNmsObjectMessageFacadeTest.cs
@@ -358,6 +358,7 @@
Assert.AreEqual(amqpObjectMessageFacade.Object, copy.Object);
}
+ [Obsolete("Obsolete")]
private static byte[] GetSerializedBytes(object content)
{
using (MemoryStream stream = new MemoryStream())