Skip to content

Commit

Permalink
Merge 82a5398 into c30b85e
Browse files Browse the repository at this point in the history
  • Loading branch information
sw-joelmut committed Feb 27, 2023
2 parents c30b85e + 82a5398 commit 854b770
Show file tree
Hide file tree
Showing 9 changed files with 378 additions and 159 deletions.
23 changes: 11 additions & 12 deletions libraries/Microsoft.Bot.Builder.Azure.Blobs/BlobsStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,6 @@ namespace Microsoft.Bot.Builder.Azure.Blobs
/// </remarks>
public class BlobsStorage : IStorage
{
private static readonly AllowedTypesSerializationBinder _allowedTypesBinder = new AllowedTypesSerializationBinder(
new List<Type>
{
typeof(Dictionary<string, object>)
});

// If a JsonSerializer is not provided during construction, this will be the default JsonSerializer.
private readonly JsonSerializer _jsonSerializer;
private readonly BlobContainerClient _containerClient;
Expand Down Expand Up @@ -86,8 +80,7 @@ public BlobsStorage(string dataConnectionString, string containerName, StorageTr

_jsonSerializer = jsonSerializer ?? JsonSerializer.Create(new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects, // lgtm [cs/unsafe-type-name-handling]
SerializationBinder = _allowedTypesBinder,
TypeNameHandling = TypeNameHandling.All, // lgtm [cs/unsafe-type-name-handling]
MaxDepth = null,
});

Expand All @@ -113,8 +106,7 @@ internal BlobsStorage(BlobContainerClient containerClient, JsonSerializer jsonSe

_jsonSerializer = jsonSerializer ?? JsonSerializer.Create(new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects, // lgtm [cs/unsafe-type-name-handling]
SerializationBinder = _allowedTypesBinder,
TypeNameHandling = TypeNameHandling.All, // lgtm [cs/unsafe-type-name-handling]
MaxDepth = null,
});

Expand Down Expand Up @@ -231,7 +223,11 @@ public async Task WriteAsync(IDictionary<string, object> changes, CancellationTo
var json = JToken.FromObject(newValue, _jsonSerializer);
if (json.Type == JTokenType.Object || json.Type == JTokenType.Array)
{
(_jsonSerializer.SerializationBinder as AllowedTypesSerializationBinder)?.Verify();
if (_jsonSerializer.SerializationBinder is AllowedTypesSerializationBinder allowedTypesBinder)
{
allowedTypesBinder.Verify();
}

await json.WriteToAsync(jsonWriter).ConfigureAwait(false);
}
else
Expand Down Expand Up @@ -277,7 +273,10 @@ private async Task<object> InnerReadBlobAsync(BlobClient blobReference, Cancella
using (var jsonReader = new JsonTextReader(new StreamReader(download.Content)) { MaxDepth = null })
{
var obj = _jsonSerializer.Deserialize(jsonReader);
(_jsonSerializer.SerializationBinder as AllowedTypesSerializationBinder)?.Verify();
if (_jsonSerializer.SerializationBinder is AllowedTypesSerializationBinder allowedTypesBinder)
{
allowedTypesBinder.Verify();
}

if (obj is IStoreItem storeItem)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ internal BlobsTranscriptStore(BlobContainerClient containerClient, JsonSerialize

_jsonSerializer = jsonSerializer ?? JsonSerializer.Create(new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All, // lgtm [cs/unsafe-type-name-handling]
MaxDepth = null,
});
}
Expand Down
33 changes: 5 additions & 28 deletions libraries/Microsoft.Bot.Builder.Azure/AzureBlobStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
using Microsoft.WindowsAzure.Storage.Core;
using Microsoft.WindowsAzure.Storage.RetryPolicies;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Microsoft.Bot.Builder.Azure
{
Expand All @@ -34,12 +33,8 @@ public class AzureBlobStorage : IStorage
{
private static readonly JsonSerializer JsonSerializer = JsonSerializer.Create(new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects, // lgtm [cs/unsafe-type-name-handling]
SerializationBinder = new AllowedTypesSerializationBinder(
new List<Type>
{
typeof(Dictionary<string, object>)
}),
// we use All so that we get typed roundtrip out of storage, but we don't use validation because we don't know what types are valid
TypeNameHandling = TypeNameHandling.All, // lgtm [cs/unsafe-type-name-handling]
MaxDepth = null,
});

Expand All @@ -59,7 +54,6 @@ public class AzureBlobStorage : IStorage
/// <para>jsonSerializer.TypeNameHandling = TypeNameHandling.All.</para>
/// <para>jsonSerializer.NullValueHandling = NullValueHandling.Include.</para>
/// <para>jsonSerializer.ContractResolver = new DefaultContractResolver().</para>
/// <para>jsonSerializer.SerializationBinder = new AllowedTypesSerializationBinder().</para>
/// </param>
public AzureBlobStorage(CloudStorageAccount storageAccount, string containerName, JsonSerializer jsonSerializer)
{
Expand Down Expand Up @@ -100,14 +94,8 @@ public AzureBlobStorage(CloudStorageAccount storageAccount, string containerName
/// <param name="storageAccount">Azure CloudStorageAccount instance.</param>
/// <param name="containerName">Name of the Blob container where entities will be stored.</param>
/// <param name="blobClient">Custom implementation of CloudBlobClient.</param>
/// <param name="jsonSerializer">If passing in a custom JsonSerializer, we recommend the following settings:
/// <para>jsonSerializer.TypeNameHandling = TypeNameHandling.All.</para>
/// <para>jsonSerializer.NullValueHandling = NullValueHandling.Include.</para>
/// <para>jsonSerializer.ContractResolver = new DefaultContractResolver().</para>
/// <para>jsonSerializer.SerializationBinder = new AllowedTypesSerializationBinder().</para>
/// </param>
internal AzureBlobStorage(CloudStorageAccount storageAccount, string containerName, CloudBlobClient blobClient, JsonSerializer jsonSerializer = default)
: this(storageAccount, containerName, jsonSerializer ?? JsonSerializer)
internal AzureBlobStorage(CloudStorageAccount storageAccount, string containerName, CloudBlobClient blobClient)
: this(storageAccount, containerName, JsonSerializer)
{
_blobClient = blobClient;
}
Expand Down Expand Up @@ -223,18 +211,8 @@ public async Task WriteAsync(IDictionary<string, object> changes, CancellationTo
{
using (var memoryStream = new MultiBufferMemoryStream(blobReference.ServiceClient.BufferManager))
using (var streamWriter = new StreamWriter(memoryStream))
using (var jsonWriter = new JsonTextWriter(streamWriter))
{
var json = JToken.FromObject(newValue, _jsonSerializer);
if (json.Type == JTokenType.Object || json.Type == JTokenType.Array)
{
(_jsonSerializer.SerializationBinder as AllowedTypesSerializationBinder)?.Verify();
await json.WriteToAsync(jsonWriter).ConfigureAwait(false);
}
else
{
_jsonSerializer.Serialize(streamWriter, newValue);
}
_jsonSerializer.Serialize(streamWriter, newValue);

await streamWriter.FlushAsync().ConfigureAwait(false);

Expand Down Expand Up @@ -279,7 +257,6 @@ private async Task<object> InnerReadBlobAsync(CloudBlob blobReference, Cancellat
using (var jsonReader = new JsonTextReader(new StreamReader(blobStream)) { MaxDepth = null })
{
var obj = _jsonSerializer.Deserialize(jsonReader);
(_jsonSerializer.SerializationBinder as AllowedTypesSerializationBinder)?.Verify();

if (obj is IStoreItem storeItem)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,7 @@ public class CosmosDbPartitionedStorage : IStorage, IDisposable

private readonly JsonSerializer _jsonSerializer = JsonSerializer.Create(new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects, // lgtm [cs/unsafe-type-name-handling]
SerializationBinder = new AllowedTypesSerializationBinder(
new List<Type>
{
typeof(Dictionary<string, object>)
}),
TypeNameHandling = TypeNameHandling.All, // lgtm [cs/unsafe-type-name-handling]
MaxDepth = null
});

Expand Down Expand Up @@ -171,7 +166,10 @@ internal CosmosDbPartitionedStorage(CosmosClient client, CosmosDbPartitionedStor

var documentStoreItem = readItemResponse.Resource;
var item = documentStoreItem.Document.ToObject(typeof(object), _jsonSerializer);
(_jsonSerializer.SerializationBinder as AllowedTypesSerializationBinder)?.Verify();
if (_jsonSerializer.SerializationBinder is AllowedTypesSerializationBinder allowedTypesBinder)
{
allowedTypesBinder.Verify();
}

if (item is IStoreItem storeItem)
{
Expand Down Expand Up @@ -227,7 +225,10 @@ public async Task WriteAsync(IDictionary<string, object> changes, CancellationTo
foreach (var change in changes)
{
var json = JObject.FromObject(change.Value, _jsonSerializer);
(_jsonSerializer.SerializationBinder as AllowedTypesSerializationBinder)?.Verify();
if (_jsonSerializer.SerializationBinder is AllowedTypesSerializationBinder allowedTypesBinder)
{
allowedTypesBinder.Verify();
}

// Remove etag from JSON object that was copied from IStoreItem.
// The ETag information is updated as an _etag attribute in the document metadata.
Expand Down
62 changes: 12 additions & 50 deletions libraries/Microsoft.Bot.Builder/AllowedTypesSerializationBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

Expand All @@ -15,10 +14,6 @@ namespace Microsoft.Bot.Builder
/// An implementation of the <see cref="DefaultSerializationBinder"/>,
/// capable of allowing only desired <see cref="Type"/>s to be serialized and deserialized.
/// </summary>
/// <remarks>
/// Internally loads types dynamically, allowing all the exported types from BotBuilder and
/// the project who is using it to be taken into account in the verification process.
/// </remarks>
public class AllowedTypesSerializationBinder : DefaultSerializationBinder
{
/// <summary>
Expand All @@ -27,7 +22,7 @@ public class AllowedTypesSerializationBinder : DefaultSerializationBinder
/// <param name="allowedTypes">A list of types to allow when the binder assign them upon deserialization.</param>
public AllowedTypesSerializationBinder(IList<Type> allowedTypes = default)
{
AllowedTypes = LoadTypes(allowedTypes);
AllowedTypes = allowedTypes ?? new List<Type>();
}

/// <summary>
Expand Down Expand Up @@ -60,7 +55,7 @@ public override void BindToName(Type serializedType, out string assemblyName, ou
assemblyName = null;
typeName = serializedType?.AssemblyQualifiedName;

if (serializedType == null || AllowedTypes.Count == 0)
if (serializedType == null)
{
return;
}
Expand Down Expand Up @@ -162,6 +157,14 @@ private bool IsTypeAllowed(Type serializedType)
return true;
}
}

// Return when the Type is represented as a generic, e.g.: List<T>.
var arguments = serializedType.GetGenericArguments();
var argumentsFound = AllowedTypes.FirstOrDefault(t => arguments.Any(IsTypeEqualTo(t)));
if (argumentsFound != null)
{
return true;
}

// Return when the Type has Interfaces.
var interfaces = serializedType.GetInterfaces();
Expand All @@ -183,7 +186,7 @@ private void AllowType(Type type)

if (!AllowedTypes.Any(IsTypeEqualTo(type)))
{
AllowedTypes.Insert(0, type);
AllowedTypes.Add(type);
}
}

Expand All @@ -199,49 +202,8 @@ private void DenyType(Type type)

if (!DeniedTypes.Any(IsTypeEqualTo(type)))
{
DeniedTypes.Insert(0, type);
DeniedTypes.Add(type);
}
}

private List<Type> LoadTypes(IList<Type> allowedTypes)
{
var types = new List<Type>();
var externalAssembly = Assembly.GetEntryAssembly();
var internalAssembly = Assembly.GetExecutingAssembly();
var internalNestedTypes = GetDeepReferencedTypes(internalAssembly);

if (allowedTypes?.Count > 0)
{
types.AddRange(allowedTypes);
}

types.AddRange(externalAssembly.ExportedTypes);
types.AddRange(internalAssembly.ExportedTypes);
types.AddRange(internalNestedTypes);

return types.Distinct().ToList();
}

private List<Type> GetDeepReferencedTypes(Assembly parent)
{
var result = new List<Type>();
var parentAttr = parent.GetCustomAttribute(typeof(AssemblyProductAttribute)) as AssemblyProductAttribute;
var children = parent.GetReferencedAssemblies();
foreach (var childName in children)
{
var child = Assembly.Load(childName);
var childAttr = child?.GetCustomAttribute(typeof(AssemblyProductAttribute)) as AssemblyProductAttribute;
if (childAttr == null || parentAttr.Product != childAttr.Product)
{
continue;
}

var types = GetDeepReferencedTypes(child);
result.AddRange(types);
result.AddRange(child.ExportedTypes);
}

return result;
}
}
}
15 changes: 1 addition & 14 deletions tests/Microsoft.Bot.Builder.Azure.Tests/AzureBlobStorageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -257,20 +257,7 @@ private void InitStorage()

_mockAccount = new Mock<CloudStorageAccount>(new StorageCredentials("accountName", "S2V5VmFsdWU=", "key"), false);

var jsonSerializer = new JsonSerializer
{
TypeNameHandling = TypeNameHandling.Objects, // lgtm [cs/unsafe-type-name-handling]
MaxDepth = null,
SerializationBinder = new AllowedTypesSerializationBinder(
new List<Type>
{
typeof(IStoreItem),
typeof(Dictionary<string, object>),
typeof(Activity)
}),
};

_blobStorage = new AzureBlobStorage(_mockAccount.Object, ContainerName, _mockBlobClient.Object, jsonSerializer);
_blobStorage = new AzureBlobStorage(_mockAccount.Object, ContainerName, _mockBlobClient.Object);
}

private class StoreItem : IStoreItem
Expand Down

0 comments on commit 854b770

Please sign in to comment.