Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setting MaxDepth parameter in the JsonSerializerSettings as safe defaults #3045

Merged
merged 3 commits into from
Jan 6, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions iothub/device/src/InternalClient.cs
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@
using System.Security.Cryptography.X509Certificates;
using System.IO;
using Microsoft.Azure.Devices.Client.Exceptions;
using Newtonsoft.Json;

#if NET451

@@ -156,6 +157,8 @@ public InternalClient(IotHubConnectionString iotHubConnectionString, ITransportS
Logging.Enter(this, transportSettings, pipelineBuilder, nameof(InternalClient) + "_ctor");
}

JsonConvert.DefaultSettings = JsonSerializerSettingsInitializer.GetDefaultJsonSerializerSettingsDelegate();

TlsVersions.Instance.SetLegacyAcceptableVersions();

IotHubConnectionString = iotHubConnectionString;
@@ -989,7 +992,7 @@ internal async Task OnMethodCalledAsync(MethodRequestInternal methodRequestInter
methodResponseInternal?.Dispose();
}
finally
{
{
// Need to release this semaphore even if the above dispose call fails
_methodsDictionarySemaphore.Release();
}
@@ -1405,7 +1408,7 @@ private async Task OnDeviceMessageReceivedAsync(Message message)
return;
}

// Grab this semaphore so that there is no chance that the _deviceReceiveMessageCallback instance is set in between the read of the
// Grab this semaphore so that there is no chance that the _deviceReceiveMessageCallback instance is set in between the read of the
// item1 and the read of the item2
await _deviceReceiveMessageSemaphore.WaitAsync().ConfigureAwait(false);
ReceiveMessageCallback callback = _deviceReceiveMessageCallback?.Item1;
43 changes: 42 additions & 1 deletion iothub/device/tests/DeviceClientTests.cs
Original file line number Diff line number Diff line change
@@ -5,10 +5,12 @@
using Microsoft.Azure.Devices.Client.Transport.Mqtt;
using Microsoft.Azure.Devices.Shared;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using NSubstitute;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -309,7 +311,6 @@ await innerHandler
}

[TestMethod]
// Tests_SRS_DEVICECLIENT_10_012: [** If the given methodRequestInternal argument is null, fail silently **]**
public async Task DeviceClient_OnMethodCalled_NullMethodRequest()
{
using var deviceClient = DeviceClient.CreateFromConnectionString(fakeConnectionString);
@@ -1565,5 +1566,45 @@ public async Task MessageIdDefaultSetToGuid_SendEventBatchSetMessageIdIfNotSet()
messageWithoutId.MessageId.Should().NotBeNullOrEmpty();
messageWithId.MessageId.Should().Be(messageId);
}

[TestMethod]
public void DeviceClient_DefaultMaxDepth()
{
// arrange
string hostName = "acme.azure-devices.net";
var authMethod = new DeviceAuthenticationWithSakRefresh("device1", s_cs);

using var deviceClient = DeviceClient.Create(hostName, authMethod);

// above arragement is only for setting the defaultJsonSerializerSettings

var defaultSettings = JsonSerializerSettingsInitializer.GetDefaultJsonSerializerSettings();
defaultSettings.MaxDepth.Should().Be(128);
}

[TestMethod]
public void DeviceClient_OverrideDefaultMaxDepth_ExceedMaxDepthThrows()
{
// arrange
const string fakeConnectionString = "HostName=acme.azure-devices.net;SharedAccessKeyName=AllAccessKey;DeviceId=fake;SharedAccessKey=dGVzdFN0cmluZzE=";
using var deviceClient = DeviceClient.CreateFromConnectionString(fakeConnectionString, TransportType.Http1, new ClientOptions
{
ModelId = "TestModel"
});
// above arragement is only for setting the defaultJsonSerializerSettings

//Create a string representation of a nested object (JSON serialized)
int nRep = 3;
string json = string.Concat(Enumerable.Repeat("{a:", nRep)) + "1" +
string.Concat(Enumerable.Repeat("}", nRep));

var settings = new JsonSerializerSettings { MaxDepth = 2 };
//// deserialize
// act
Func<object> act = () => JsonConvert.DeserializeObject(json, settings);

// assert
act.Should().Throw<Newtonsoft.Json.JsonReaderException>();
}
}
}
36 changes: 36 additions & 0 deletions iothub/device/tests/ModuleClientTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Azure.Devices.Shared;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using NSubstitute;

namespace Microsoft.Azure.Devices.Client.Test
@@ -266,5 +270,37 @@ public void ModuleClient_InvokeMethodAsyncWithoutBodyShouldNotThrow()
// act
_ = new MethodInvokeRequest(request.Name, request.DataAsJson, request.ResponseTimeout, request.ConnectionTimeout);
}

[TestMethod]
public void ModuleClient_DefaultMaxDepth()
{
// arrange
var moduleClient = ModuleClient.CreateFromConnectionString(FakeConnectionString);
// above arragement is only for setting the defaultJsonSerializerSettings

var defaultSettings = JsonSerializerSettingsInitializer.GetDefaultJsonSerializerSettings();
defaultSettings.MaxDepth.Should().Be(128);
}

[TestMethod]
public void ModuleClient_OverrideDefaultMaxDepth_ExceedMaxDepthThrows()
{
// arrange
var moduleClient = ModuleClient.CreateFromConnectionString(FakeConnectionString);
// above arragement is only for setting the defaultJsonSerializerSettings

//Create a string representation of a nested object (JSON serialized)
int nRep = 3;
string json = string.Concat(Enumerable.Repeat("{a:", nRep)) + "1" +
string.Concat(Enumerable.Repeat("}", nRep));

var settings = new JsonSerializerSettings { MaxDepth = 2 };
//// deserialize
// act
Func<object> act = () => JsonConvert.DeserializeObject(json, settings);

// assert
act.Should().Throw<Newtonsoft.Json.JsonReaderException>();
}
}
}
53 changes: 28 additions & 25 deletions iothub/service/src/DigitalTwin/DigitalTwinClient.cs
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@
using Microsoft.Azure.Devices.Authentication;
using Microsoft.Azure.Devices.Extensions;
using Microsoft.Azure.Devices.Generated;
using Microsoft.Azure.Devices.Shared;
using Microsoft.Rest;
using Newtonsoft.Json;

@@ -38,6 +39,8 @@ private DigitalTwinClient(Uri uri, IotServiceClientCredentials credentials, para
{
_client = new IotHubGatewayServiceAPIs(uri, credentials, handlers);
_protocolLayer = new DigitalTwin(_client);

JsonConvert.DefaultSettings = JsonSerializerSettingsInitializer.GetDefaultJsonSerializerSettingsDelegate();
}

/// <summary>
@@ -69,9 +72,9 @@ public async Task<HttpOperationResponse<T, DigitalTwinGetHeaders>> GetDigitalTwi
/// <param name="cancellationToken">The cancellationToken.</param>
/// <returns>The http response.</returns>
public Task<HttpOperationHeaderResponse<DigitalTwinUpdateHeaders>> UpdateDigitalTwinAsync(
string digitalTwinId,
string digitalTwinUpdateOperations,
DigitalTwinUpdateRequestOptions requestOptions = default,
string digitalTwinId,
string digitalTwinUpdateOperations,
DigitalTwinUpdateRequestOptions requestOptions = default,
CancellationToken cancellationToken = default)
{
return _protocolLayer.UpdateDigitalTwinWithHttpMessagesAsync(digitalTwinId, digitalTwinUpdateOperations, requestOptions?.IfMatch, null, cancellationToken);
@@ -87,19 +90,19 @@ public Task<HttpOperationHeaderResponse<DigitalTwinUpdateHeaders>> UpdateDigital
/// <param name="cancellationToken">The cancellationToken.</param>
/// <returns>The application/json command invocation response and the http response. </returns>
public async Task<HttpOperationResponse<DigitalTwinCommandResponse, DigitalTwinInvokeCommandHeaders>> InvokeCommandAsync(
string digitalTwinId,
string commandName,
string payload = default,
DigitalTwinInvokeCommandRequestOptions requestOptions = default,
string digitalTwinId,
string commandName,
string payload = default,
DigitalTwinInvokeCommandRequestOptions requestOptions = default,
CancellationToken cancellationToken = default)
{
using HttpOperationResponse<string, DigitalTwinInvokeRootLevelCommandHeaders> response = await _protocolLayer.InvokeRootLevelCommandWithHttpMessagesAsync(
digitalTwinId,
commandName,
payload,
requestOptions?.ConnectTimeoutInSeconds,
requestOptions?.ResponseTimeoutInSeconds,
null,
digitalTwinId,
commandName,
payload,
requestOptions?.ConnectTimeoutInSeconds,
requestOptions?.ResponseTimeoutInSeconds,
null,
cancellationToken)
.ConfigureAwait(false);
return new HttpOperationResponse<DigitalTwinCommandResponse, DigitalTwinInvokeCommandHeaders>
@@ -122,21 +125,21 @@ public async Task<HttpOperationResponse<DigitalTwinCommandResponse, DigitalTwinI
/// <param name="cancellationToken">The cancellationToken.</param>
/// <returns>The application/json command invocation response and the http response. </returns>
public async Task<HttpOperationResponse<DigitalTwinCommandResponse, DigitalTwinInvokeCommandHeaders>> InvokeComponentCommandAsync(
string digitalTwinId,
string componentName,
string commandName,
string payload = default,
DigitalTwinInvokeCommandRequestOptions requestOptions = default,
string digitalTwinId,
string componentName,
string commandName,
string payload = default,
DigitalTwinInvokeCommandRequestOptions requestOptions = default,
CancellationToken cancellationToken = default)
{
using HttpOperationResponse<string, DigitalTwinInvokeComponentCommandHeaders> response = await _protocolLayer.InvokeComponentCommandWithHttpMessagesAsync(
digitalTwinId,
componentName,
commandName,
payload,
requestOptions?.ConnectTimeoutInSeconds,
requestOptions?.ResponseTimeoutInSeconds,
null,
digitalTwinId,
componentName,
commandName,
payload,
requestOptions?.ConnectTimeoutInSeconds,
requestOptions?.ResponseTimeoutInSeconds,
null,
cancellationToken)
.ConfigureAwait(false);
return new HttpOperationResponse<DigitalTwinCommandResponse, DigitalTwinInvokeCommandHeaders>
5 changes: 4 additions & 1 deletion iothub/service/src/JobClient/JobClient.cs
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
namespace Microsoft.Azure.Devices
{
using Microsoft.Azure.Devices.Shared;
using Newtonsoft.Json;
using System;
using System.Threading;
using System.Threading.Tasks;
@@ -35,6 +36,7 @@ public static JobClient CreateFromConnectionString(string connectionString, Http
{
throw new ArgumentNullException(nameof(transportSettings), "HTTP Transport settings cannot be null.");
}
JsonConvert.DefaultSettings = JsonSerializerSettingsInitializer.GetDefaultJsonSerializerSettingsDelegate();
TlsVersions.Instance.SetLegacyAcceptableVersions();

var iotHubConnectionString = IotHubConnectionString.Parse(connectionString);
@@ -52,7 +54,8 @@ public void Dispose()
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing) { }
protected virtual void Dispose(bool disposing)
{ }

/// <summary>
/// Explicitly open the JobClient instance.
6 changes: 5 additions & 1 deletion iothub/service/src/RegistryManager.cs
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Devices.Shared;
using Newtonsoft.Json;

namespace Microsoft.Azure.Devices
{
@@ -40,6 +41,8 @@ public static RegistryManager CreateFromConnectionString(string connectionString
{
throw new ArgumentNullException(nameof(transportSettings), "The HTTP transport settings cannot be null.");
}

JsonConvert.DefaultSettings = JsonSerializerSettingsInitializer.GetDefaultJsonSerializerSettingsDelegate();
TlsVersions.Instance.SetLegacyAcceptableVersions();

var iotHubConnectionString = IotHubConnectionString.Parse(connectionString);
@@ -57,7 +60,8 @@ public void Dispose()
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing) { }
protected virtual void Dispose(bool disposing)
{ }

/// <summary>
/// Explicitly open the RegistryManager instance.
5 changes: 4 additions & 1 deletion iothub/service/src/ServiceClient.cs
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Devices.Shared;
using Newtonsoft.Json;

namespace Microsoft.Azure.Devices
{
@@ -39,6 +40,7 @@ public abstract class ServiceClient : IDisposable
/// </summary>
internal ServiceClient()
{
JsonConvert.DefaultSettings = JsonSerializerSettingsInitializer.GetDefaultJsonSerializerSettingsDelegate();
TlsVersions.Instance.SetLegacyAcceptableVersions();
}

@@ -64,7 +66,8 @@ public void Dispose()
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing) { }
protected virtual void Dispose(bool disposing)
{ }

/// <summary>
/// Create an instance of ServiceClient from the specified IoT Hub connection string using specified Transport Type.
53 changes: 53 additions & 0 deletions iothub/service/tests/JobClient/JobClientTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Azure.Devices.Shared;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Azure.Devices;
using FluentAssertions;
using Newtonsoft.Json;
using System.Linq;

namespace Microsoft.Azure.Devices
{
[TestClass]
[TestCategory("Unit")]
public class JobClientTests
{
[TestMethod]
public void JobClient_DefaultMaxDepth()
{
// arrange
string fakeConnectionString = "HostName=acme.azure-devices.net;SharedAccessKeyName=AllAccessKey;SharedAccessKey=dGVzdFN0cmluZzE=";
using var jobClient = JobClient.CreateFromConnectionString(fakeConnectionString);
// above arragement is only for setting the defaultJsonSerializerSettings

var defaultSettings = JsonSerializerSettingsInitializer.GetDefaultJsonSerializerSettings();
defaultSettings.MaxDepth.Should().Be(128);
}

[TestMethod]
public void JobClient_Json_OverrideDefaultJsonSerializer_ExceedMaxDepthThrows()
{
// arrange
string fakeConnectionString = "HostName=acme.azure-devices.net;SharedAccessKeyName=AllAccessKey;SharedAccessKey=dGVzdFN0cmluZzE=";
using var jobClient = JobClient.CreateFromConnectionString(fakeConnectionString);
// above arragement is only for setting the defaultJsonSerializerSettings

//Create a string representation of a nested object (JSON serialized)
int nRep = 3;
string json = string.Concat(Enumerable.Repeat("{a:", nRep)) + "1" +
string.Concat(Enumerable.Repeat("}", nRep));

var settings = new JsonSerializerSettings { MaxDepth = 2 };
//// deserialize
// act
Func<object> act = () => JsonConvert.DeserializeObject(json, settings);

// assert
act.Should().Throw<Newtonsoft.Json.JsonReaderException>();
}
}
}
32 changes: 31 additions & 1 deletion iothub/service/tests/QueryTests.cs
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ namespace Microsoft.Azure.Devices.Api.Test
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using Microsoft.Azure.Devices.Shared;
using FluentAssertions;

[TestClass]
[TestCategory("Unit")]
@@ -292,5 +293,34 @@ public void QueryResultUserSuppliedContinuationTest()
Assert.AreEqual("test", r.ElementAt(0).DeviceId);
Assert.IsTrue(q.HasMoreResults);
}

[TestMethod]
public void QueryResult_OverrideDefaultJsonSerializer_ExceedMaxDepthThrows()
{
var settings = new JsonSerializerSettings { MaxDepth = 2 };
// simulate json deserialize
const string jsonString = @"
{
""type"":""twin"",
""items"":
[{
""deviceId"": ""test"",
""etag"":null,
""version"":null,
""properties"":
{
""desired"":{},
""reported"":{}
}
}],
""continuationToken"":""GYUVJDBJFKJ""
}";
// deserialize
// act
Func<QueryResult> act = () => JsonConvert.DeserializeObject<QueryResult>(jsonString, settings);

// assert
act.Should().Throw<Newtonsoft.Json.JsonReaderException>();
}
}
}
}
36 changes: 36 additions & 0 deletions iothub/service/tests/RegistryManagerTests.cs
Original file line number Diff line number Diff line change
@@ -10,10 +10,12 @@ namespace Microsoft.Azure.Devices.Api.Test
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Azure.Devices;
using Microsoft.Azure.Devices.Shared;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Newtonsoft.Json;

[TestClass]
[TestCategory("Unit")]
@@ -889,5 +891,39 @@ public async Task CloseAsyncTest()
await registryManager.CloseAsync().ConfigureAwait(false);
restOpMock.Verify(restOp => restOp.Dispose(), Times.Never());
}

[TestMethod]
public void RegistryManager_DefaultMaxDepth()
{
// arrange
string fakeConnectionString = "HostName=acme.azure-devices.net;SharedAccessKeyName=AllAccessKey;SharedAccessKey=dGVzdFN0cmluZzE=";
var registryManager = RegistryManager.CreateFromConnectionString(fakeConnectionString);
// above arragement is only for setting the defaultJsonSerializerSettings

var defaultSettings = JsonSerializerSettingsInitializer.GetDefaultJsonSerializerSettings();
defaultSettings.MaxDepth.Should().Be(128);
}

[TestMethod]
public void RegistryManager_OverrideDefaultMaxDepth_ExceedMaxDepthThrows()
{
// arrange
string fakeConnectionString = "HostName=acme.azure-devices.net;SharedAccessKeyName=AllAccessKey;SharedAccessKey=dGVzdFN0cmluZzE=";
var registryManager = RegistryManager.CreateFromConnectionString(fakeConnectionString);
// above arragement is only for setting the defaultJsonSerializerSettings

//Create a string representation of a nested object (JSON serialized)
int nRep = 3;
string json = string.Concat(Enumerable.Repeat("{a:", nRep)) + "1" +
string.Concat(Enumerable.Repeat("}", nRep));

var settings = new JsonSerializerSettings { MaxDepth = 2 };
//// deserialize
// act
Func<object> act = () => JsonConvert.DeserializeObject(json, settings);

// assert
act.Should().Throw<Newtonsoft.Json.JsonReaderException>();
}
}
}
31 changes: 31 additions & 0 deletions iothub/service/tests/SerializationTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using FluentAssertions;
using Microsoft.Azure.Devices.Shared;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
@@ -68,5 +69,35 @@ public void Configuration_TestNullSchema_Ok()

JsonConvert.DeserializeObject<Configuration>(jsonString);
}

[TestMethod]
public void Twin_Json_OverrideDefaultJsonSerializer_ExceedMaxDepthThrows()
{
// arrange
string fakeConnectionString = "HostName=acme.azure-devices.net;SharedAccessKeyName=AllAccessKey;SharedAccessKey=dGVzdFN0cmluZzE=";
var ServiceClinet = ServiceClient.CreateFromConnectionString(fakeConnectionString);
// above arragement is only for setting the defaultJsonSerializerSettings

const string jsonString = @"
{
""deviceId"": ""test"",
""etag"": ""AAAAAAAAAAM="",
""version"": 5,
""status"": ""enabled"",
""statusUpdateTime"": ""2018-06-29T21:17:08.7759733"",
""connectionState"": ""Connected"",
""lastActivityTime"": ""2018-06-29T21:17:08.7759733"",
""Capabilities"": {
""IotEdge"": false
}
}";
var settings = new JsonSerializerSettings { MaxDepth = 1 };

// act
Func<Twin> act = () => JsonConvert.DeserializeObject<Twin>(jsonString, settings);

// assert
act.Should().Throw<Newtonsoft.Json.JsonReaderException>();
}
}
}
40 changes: 39 additions & 1 deletion iothub/service/tests/ServiceClientTests.cs
Original file line number Diff line number Diff line change
@@ -5,15 +5,19 @@ namespace Microsoft.Azure.Devices.Api.Test
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Azure.Amqp;
using Microsoft.Azure.Devices;
using Microsoft.Azure.Devices.Common.Exceptions;
using Microsoft.Azure.Devices.Shared;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Newtonsoft.Json;

[TestClass]
[TestCategory("Unit")]
@@ -56,7 +60,7 @@ public async Task PurgeMessageQueueDeviceNotFoundTest()
PurgeMessageQueueResult result = await serviceClient.PurgeMessageQueueAsync("TestDevice", CancellationToken.None).ConfigureAwait(false);
}

Tuple<Mock<IHttpClientHelper>, AmqpServiceClient, PurgeMessageQueueResult> SetupPurgeMessageQueueTests()
private Tuple<Mock<IHttpClientHelper>, AmqpServiceClient, PurgeMessageQueueResult> SetupPurgeMessageQueueTests()
{
// Create expected return object
var deviceId = "TestDevice";
@@ -117,5 +121,39 @@ public async Task CloseAsyncTest()
restOpMock.Verify(restOp => restOp.Dispose(), Times.Never());
Assert.IsTrue(connectionClosed);
}

[TestMethod]
public void ServiceClient_DefaultMaxDepth()
{
// arrange
string fakeConnectionString = "HostName=acme.azure-devices.net;SharedAccessKeyName=AllAccessKey;SharedAccessKey=dGVzdFN0cmluZzE=";
var serviceClinet = ServiceClient.CreateFromConnectionString(fakeConnectionString);
// above arragement is only for setting the defaultJsonSerializerSettings

var defaultSettings = JsonSerializerSettingsInitializer.GetDefaultJsonSerializerSettings();
defaultSettings.MaxDepth.Should().Be(128);
}

[TestMethod]
public void ServiceClient_Json_OverrideDefaultJsonSerializer_ExceedMaxDepthThrows()
{
// arrange
string fakeConnectionString = "HostName=acme.azure-devices.net;SharedAccessKeyName=AllAccessKey;SharedAccessKey=dGVzdFN0cmluZzE=";
var serviceClinet = ServiceClient.CreateFromConnectionString(fakeConnectionString);
// above arragement is only for setting the defaultJsonSerializerSettings

//Create a string representation of a nested object (JSON serialized)
int nRep = 3;
string json = string.Concat(Enumerable.Repeat("{a:", nRep)) + "1" +
string.Concat(Enumerable.Repeat("}", nRep));

var settings = new JsonSerializerSettings { MaxDepth = 2 };
//// deserialize
// act
Func<object> act = () => JsonConvert.DeserializeObject(json, settings);

// assert
act.Should().Throw<Newtonsoft.Json.JsonReaderException>();
}
}
}
4 changes: 4 additions & 0 deletions provisioning/service/src/ProvisioningServiceClient.cs
Original file line number Diff line number Diff line change
@@ -6,6 +6,8 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Devices.Common.Service.Auth;
using Microsoft.Azure.Devices.Shared;
using Newtonsoft.Json;

namespace Microsoft.Azure.Devices.Provisioning.Service
{
@@ -112,6 +114,8 @@ public static ProvisioningServiceClient CreateFromConnectionString(string connec
/// <exception cref="ArgumentException">if the connectionString is <code>null</code>, empty, or invalid.</exception>
private ProvisioningServiceClient(string connectionString, HttpTransportSettings httpTransportSettings)
{
JsonConvert.DefaultSettings = JsonSerializerSettingsInitializer.GetDefaultJsonSerializerSettingsDelegate();

/* SRS_PROVISIONING_SERVICE_CLIENT_21_002: [The constructor shall throw ArgumentException if the provided connectionString is null or empty.] */
if (string.IsNullOrWhiteSpace(connectionString ?? throw new ArgumentNullException(nameof(connectionString))))
{
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Linq;
using System;
using Microsoft.Azure.Devices.Shared;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;

namespace Microsoft.Azure.Devices.Provisioning.Service.Tests.Config
{
[TestClass]
[TestCategory("Unit")]
public class ProvisioningServiceClientTests
{
[TestMethod]
public void ProvisioningServiceClient_DefaultMaxDepth()
{
// arrange
string fakeConnectionString = "HostName=acme-dps.azure-devices-provisioning.net;SharedAccessKeyName=provisioningserviceowner;SharedAccessKey=dGVzdFN0cmluZzE=";
using var deviceClient = ProvisioningServiceClient.CreateFromConnectionString(fakeConnectionString);
// above arragement is only for setting the defaultJsonSerializerSettings

var defaultSettings = JsonSerializerSettingsInitializer.GetDefaultJsonSerializerSettings();
Assert.AreEqual(defaultSettings.MaxDepth, 128);
}

[TestMethod]
[ExpectedException(typeof(Newtonsoft.Json.JsonReaderException))]
public void ProvisioningServiceClientJson_OverrideDefaultJsonSerializer_ExceedMaxDepthThrows()
{
// arrange
string fakeConnectionString = "HostName=acme-dps.azure-devices-provisioning.net;SharedAccessKeyName=provisioningserviceowner;SharedAccessKey=dGVzdFN0cmluZzE=";
using var deviceClient = ProvisioningServiceClient.CreateFromConnectionString(fakeConnectionString);
// above arragement is only for setting the defaultJsonSerializerSettings

//Create a string representation of a nested object (JSON serialized)
int nRep = 3;
string json = string.Concat(Enumerable.Repeat("{a:", nRep)) + "1" +
string.Concat(Enumerable.Repeat("}", nRep));

var settings = new JsonSerializerSettings { MaxDepth = 2 };
//// deserialize
// act
JsonConvert.DeserializeObject(json, settings);
}
}
}
37 changes: 37 additions & 0 deletions shared/src/JsonSerializerInitializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using Newtonsoft.Json;

namespace Microsoft.Azure.Devices.Shared
{
/// <summary>
/// A class to initialize JsonSerializerSettings with the default MaxDepth of 128 for the project
/// </summary>
public static class JsonSerializerSettingsInitializer
{
/// <summary>
/// A static instance of JsonSerializerSettings which sets MaxDepth to 128.
/// </summary>
public static readonly JsonSerializerSettings SettingsInstance = new JsonSerializerSettings
{
MaxDepth = 128
};

/// <summary>
/// Returns DefaultJsonSerializerSettings Func delegate
/// </summary>
public static Func<JsonSerializerSettings> GetDefaultJsonSerializerSettingsDelegate()
{
return () => SettingsInstance;
}

/// <summary>
/// For testing only, returns DefaultJsonSerializerSettings
/// </summary>
public static JsonSerializerSettings GetDefaultJsonSerializerSettings()
{
return JsonConvert.DefaultSettings();
}
}
}