From 5522bb468ec6a8e7c13ab86f678031d91f789230 Mon Sep 17 00:00:00 2001 From: Anuj Toshniwal Date: Fri, 7 May 2021 14:05:56 +0530 Subject: [PATCH 1/3] Add support for Patch for public model of client-side encryption --- .../src/EncryptionContainer.cs | 131 +++++++- .../src/EncryptionProcessor.cs | 67 ++++ .../tests/EmulatorTests/MdeEncryptionTests.cs | 289 +++++++++++++----- 3 files changed, 413 insertions(+), 74 deletions(-) diff --git a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs index 35a132cfbb..8555b41557 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs @@ -12,6 +12,7 @@ namespace Microsoft.Azure.Cosmos.Encryption using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos; + using Microsoft.Data.Encryption.Cryptography; using Newtonsoft.Json.Linq; internal sealed class EncryptionContainer : Container @@ -657,24 +658,146 @@ public override FeedIterator GetChangeFeedIterator( this.ResponseFactory); } - public override Task> PatchItemAsync( + public async override Task> PatchItemAsync( string id, PartitionKey partitionKey, IReadOnlyList patchOperations, PatchItemRequestOptions requestOptions = null, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + ResponseMessage responseMessage = await this.PatchItemStreamAsync( + id, + partitionKey, + patchOperations, + requestOptions, + cancellationToken); + + return this.ResponseFactory.CreateItemResponse(responseMessage); } - public override Task PatchItemStreamAsync( + public async override Task PatchItemStreamAsync( string id, PartitionKey partitionKey, IReadOnlyList patchOperations, PatchItemRequestOptions requestOptions = null, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + EncryptionSettings encryptionSettings = await this.GetOrUpdateEncryptionSettingsFromCacheAsync( + obsoleteEncryptionSettings: null, + cancellationToken: cancellationToken); + + CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(requestOptions); + using (diagnosticsContext.CreateScope("PatchItem")) + { + List encryptedPatchOperations = await this.PatchItemHelperAsync( + id, + partitionKey, + patchOperations, + encryptionSettings, + cancellationToken); + + ResponseMessage responseMessage = await this.container.PatchItemStreamAsync( + id, + partitionKey, + encryptedPatchOperations, + requestOptions, + cancellationToken); + + responseMessage.Content = await EncryptionProcessor.DecryptAsync( + responseMessage.Content, + encryptionSettings, + diagnosticsContext, + cancellationToken); + + return responseMessage; + } + } + + private async Task> PatchItemHelperAsync( + string id, + PartitionKey partitionKey, + IReadOnlyList patchOperations, + EncryptionSettings encryptionSettings, + CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(id)) + { + throw new ArgumentNullException(nameof(id)); + } + + if (partitionKey == null) + { + throw new ArgumentNullException(nameof(partitionKey)); + } + + if (patchOperations == null || + !patchOperations.Any()) + { + throw new ArgumentNullException(nameof(patchOperations)); + } + + List encryptedPatchOperations = new List(patchOperations.Count); + + foreach (PatchOperation patchOperation in patchOperations) + { + if (patchOperation.OperationType == PatchOperationType.Remove) + { + encryptedPatchOperations.Add(patchOperation); + continue; + } + + if (string.IsNullOrWhiteSpace(patchOperation.Path) || patchOperation.Path[0] != '/') + { + throw new ArgumentException($"Invalid path '{patchOperation.Path}'."); + } + + // get the top level path's encryption setting. + EncryptionSettingForProperty settingforProperty = encryptionSettings.GetEncryptionSettingForProperty( + patchOperation.Path.Split('/')[1]); + + // non-encrypted path + if (settingforProperty == null) + { + encryptedPatchOperations.Add(patchOperation); + continue; + } + else if (patchOperation.OperationType == PatchOperationType.Increment) + { + throw new InvalidOperationException($"Increment patch operation is not allowed for encrypted path '{patchOperation.Path}'."); + } + + if (!patchOperation.TrySerializeValueParameter(this.CosmosSerializer, out Stream valueParam)) + { + throw new ArgumentException($"Cannot serialize value parameter for operation: {patchOperation.OperationType}, path: {patchOperation.Path}."); + } + + AeadAes256CbcHmac256EncryptionAlgorithm aeadAes256CbcHmac256EncryptionAlgorithm = await settingforProperty.BuildEncryptionAlgorithmForSettingAsync(cancellationToken); + + JToken propertyValue = EncryptionProcessor.BaseSerializer.FromStream(valueParam); + JToken encryptedPropertyValue = EncryptionProcessor.EncryptProperty( + propertyValue, + aeadAes256CbcHmac256EncryptionAlgorithm); + + switch (patchOperation.OperationType) + { + case PatchOperationType.Add: + encryptedPatchOperations.Add(PatchOperation.Add(patchOperation.Path, this.CosmosSerializer.ToStream(encryptedPropertyValue))); + break; + + case PatchOperationType.Replace: + encryptedPatchOperations.Add(PatchOperation.Replace(patchOperation.Path, this.CosmosSerializer.ToStream(encryptedPropertyValue))); + break; + + case PatchOperationType.Set: + encryptedPatchOperations.Add(PatchOperation.Set(patchOperation.Path, this.CosmosSerializer.ToStream(encryptedPropertyValue))); + break; + + default: + throw new NotSupportedException(nameof(patchOperation.OperationType)); + } + } + + return encryptedPatchOperations; } public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( diff --git a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionProcessor.cs b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionProcessor.cs index 9383fbcad3..6bcdfd5a81 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionProcessor.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionProcessor.cs @@ -198,6 +198,73 @@ private static JToken DeserializeAndAddProperty( }; } + internal static JToken EncryptProperty( + JToken propertyValue, + AeadAes256CbcHmac256EncryptionAlgorithm aeadAes256CbcHmac256EncryptionAlgorithm) + { + /* Top Level can be an Object*/ + if (propertyValue.Type == JTokenType.Object) + { + foreach (JProperty jProperty in propertyValue.Children()) + { + jProperty.Value = EncryptionProcessor.EncryptProperty( + jProperty.Value, + aeadAes256CbcHmac256EncryptionAlgorithm); + } + } + else if (propertyValue.Type == JTokenType.Array) + { + if (propertyValue.Children().Any()) + { + // objects as array elements. + if (propertyValue.Children().First().Type == JTokenType.Object) + { + foreach (JObject arrayjObject in propertyValue.Children()) + { + foreach (JProperty jProperty in arrayjObject.Properties()) + { + jProperty.Value = EncryptionProcessor.EncryptProperty( + jProperty.Value, + aeadAes256CbcHmac256EncryptionAlgorithm); + } + } + } + + // array as elements. + else if (propertyValue.Children().First().Type == JTokenType.Array) + { + foreach (JArray jArray in propertyValue.Value()) + { + for (int i = 0; i < jArray.Count(); i++) + { + // iterates over individual elements + jArray[i] = EncryptionProcessor.EncryptProperty( + jArray[i], + aeadAes256CbcHmac256EncryptionAlgorithm); + } + } + } + + // array of primitive types. + else + { + for (int i = 0; i < propertyValue.Count(); i++) + { + propertyValue[i] = SerializeAndEncryptValue(propertyValue[i], aeadAes256CbcHmac256EncryptionAlgorithm); + } + } + } + } + else + { + propertyValue = SerializeAndEncryptValue( + propertyValue, + aeadAes256CbcHmac256EncryptionAlgorithm); + } + + return propertyValue; + } + private static void EncryptProperty( JObject itemJObj, JToken propertyValue, diff --git a/Microsoft.Azure.Cosmos.Encryption/tests/EmulatorTests/MdeEncryptionTests.cs b/Microsoft.Azure.Cosmos.Encryption/tests/EmulatorTests/MdeEncryptionTests.cs index b1a85b2702..51e4ae6c1f 100644 --- a/Microsoft.Azure.Cosmos.Encryption/tests/EmulatorTests/MdeEncryptionTests.cs +++ b/Microsoft.Azure.Cosmos.Encryption/tests/EmulatorTests/MdeEncryptionTests.cs @@ -452,7 +452,7 @@ await MdeEncryptionTests.ValidateQueryResultsAsync( await MdeEncryptionTests.ValidateQueryResultsAsync( MdeEncryptionTests.encryptionContainer, - "SELECT c.id, c.PK, c.NonSensitive FROM c", + "SELECT c.id, c.PK, c.NonSensitive, c.NonSensitiveInt FROM c", expectedDoc); } @@ -1500,6 +1500,122 @@ public async Task EncryptionRudItem() await MdeEncryptionTests.MdeDeleteItemAsync(MdeEncryptionTests.encryptionContainer, replacedDoc); } + [TestMethod] + public async Task EncryptionPatchItem() + { + TestDoc docToPatch = await MdeEncryptionTests.MdeCreateItemAsync(MdeEncryptionTests.encryptionContainer); + docToPatch.NonSensitive = Guid.NewGuid().ToString(); + docToPatch.NonSensitiveInt++; + docToPatch.Sensitive_StringFormat = Guid.NewGuid().ToString(); + docToPatch.Sensitive_DateFormat = new DateTime(2020, 02, 02); + docToPatch.Sensitive_DecimalFormat = 11.11m; + docToPatch.Sensitive_IntArray[1] = 1; + docToPatch.Sensitive_IntMultiDimArray[1, 0] = 7; + docToPatch.Sensitive_IntFormat = 2020; + docToPatch.Sensitive_BoolFormat = false; + docToPatch.Sensitive_FloatFormat = 2020.20f; + + // Maximum 10 operations at a time (current limit) + List patchOperations = new List + { + PatchOperation.Increment("/NonSensitiveInt", 1), + PatchOperation.Replace("/NonSensitive", docToPatch.NonSensitive), + PatchOperation.Replace("/Sensitive_StringFormat", docToPatch.Sensitive_StringFormat), + PatchOperation.Replace("/Sensitive_DateFormat", docToPatch.Sensitive_DateFormat), + PatchOperation.Replace("/Sensitive_DecimalFormat", docToPatch.Sensitive_DecimalFormat), + PatchOperation.Set("/Sensitive_IntArray/1", docToPatch.Sensitive_IntArray[1]), + PatchOperation.Set("/Sensitive_IntMultiDimArray/1/0", docToPatch.Sensitive_IntMultiDimArray[1,0]), + PatchOperation.Replace("/Sensitive_IntFormat", docToPatch.Sensitive_IntFormat), + PatchOperation.Replace("/Sensitive_BoolFormat", docToPatch.Sensitive_BoolFormat), + PatchOperation.Replace("/Sensitive_FloatFormat", docToPatch.Sensitive_FloatFormat), + }; + + await MdeEncryptionTests.MdePatchItemAsync( + MdeEncryptionTests.encryptionContainer, + patchOperations, + docToPatch, + HttpStatusCode.OK); + + docToPatch.Sensitive_ArrayFormat = new TestDoc.Sensitive_ArrayData[] + { + new TestDoc.Sensitive_ArrayData + { + Sensitive_ArrayIntFormat = 1111, + Sensitive_ArrayDecimalFormat = 1111.11m + }, + new TestDoc.Sensitive_ArrayData + { + Sensitive_ArrayIntFormat = 2222, + Sensitive_ArrayDecimalFormat = 2222.22m + } + }; + + docToPatch.Sensitive_ArrayMultiTypes.SetValue( + new TestDoc.Sensitive_ArrayMultiType() + { + Sensitive_NestedObjectFormatL0 = new TestDoc.Sensitive_NestedObjectL0() + { + Sensitive_IntFormatL0 = 123, + Sensitive_DecimalFormatL0 = 123.1m, + }, + Sensitive_StringArrayMultiType = new string[1] { "sensitivedata" }, + Sensitive_ArrayMultiTypeDecimalFormat = 123.2m, + Sensitive_IntArrayMultiType = new int[2] { 1, 2 } + }, + 0, + 1); + + docToPatch.Sensitive_NestedObjectFormatL1 = new TestDoc.Sensitive_NestedObjectL1() + { + Sensitive_IntArrayL1 = new int[2] { 999, 100 }, + Sensitive_IntFormatL1 = 1999, + Sensitive_DecimalFormatL1 = 1999.1m, + Sensitive_ArrayFormatL1 = new TestDoc.Sensitive_ArrayData[] + { + new TestDoc.Sensitive_ArrayData + { + Sensitive_ArrayIntFormat = 0, + Sensitive_ArrayDecimalFormat = 0.1m + }, + new TestDoc.Sensitive_ArrayData + { + Sensitive_ArrayIntFormat = 1, + Sensitive_ArrayDecimalFormat = 2.1m + }, + new TestDoc.Sensitive_ArrayData + { + Sensitive_ArrayIntFormat = 2, + Sensitive_ArrayDecimalFormat = 3.1m + } + } + }; + + patchOperations.Clear(); + patchOperations.Add(PatchOperation.Add("/Sensitive_ArrayFormat/1", docToPatch.Sensitive_ArrayFormat[1])); + patchOperations.Add(PatchOperation.Replace("/Sensitive_ArrayMultiTypes/0/1", docToPatch.Sensitive_ArrayMultiTypes[0, 1])); + patchOperations.Add(PatchOperation.Remove("/Sensitive_NestedObjectFormatL1/Sensitive_NestedObjectFormatL2")); + patchOperations.Add(PatchOperation.Set("/Sensitive_NestedObjectFormatL1/Sensitive_ArrayFormatL1/0", docToPatch.Sensitive_NestedObjectFormatL1.Sensitive_ArrayFormatL1[0])); + + await MdeEncryptionTests.MdePatchItemAsync( + MdeEncryptionTests.encryptionContainer, + patchOperations, + docToPatch, + HttpStatusCode.OK); + + patchOperations.Add(PatchOperation.Increment("/Sensitive_IntFormat", 1)); + try + { + await MdeEncryptionTests.encryptionContainer.PatchItemAsync( + docToPatch.Id, + new PartitionKey(docToPatch.PK), + patchOperations); + } + catch(InvalidOperationException ex) + { + Assert.AreEqual("Increment patch operation is not allowed for encrypted path '/Sensitive_IntFormat'.", ex.Message); + } + } + [TestMethod] public async Task EncryptionTransactionalBatchWithCustomSerializer() { @@ -1847,6 +1963,23 @@ private static async Task> MdeReplaceItemAsync( VerifyExpectedDocResponse(testDoc, replaceResponse.Resource); return replaceResponse; } + + private static async Task> MdePatchItemAsync( + Container container, + List patchOperations, + TestDoc expectedTestDoc, + HttpStatusCode expectedStatusCode) + { + ItemResponse patchResponse = await container.PatchItemAsync( + expectedTestDoc.Id, + new PartitionKey(expectedTestDoc.PK), + patchOperations); + + Assert.AreEqual(expectedStatusCode, patchResponse.StatusCode); + VerifyExpectedDocResponse(expectedTestDoc, patchResponse.Resource); + return patchResponse; + } + private static ItemRequestOptions MdeGetRequestOptions( string ifMatchEtag = null) { @@ -1928,8 +2061,8 @@ private static void VerifyExpectedDocResponse(TestDoc expectedDoc, TestDoc verif for (int k = 0; k < expectedDoc.Sensitive_ArrayMultiTypes[i,j].Sensitive_IntArrayMultiType.Length; k++) { - Assert.AreEqual(expectedDoc.Sensitive_ArrayMultiTypes[i,j].Sensitive_StringArrayMultiType[k], - verifyDoc.Sensitive_ArrayMultiTypes[i,j].Sensitive_StringArrayMultiType[k]); + Assert.AreEqual(expectedDoc.Sensitive_ArrayMultiTypes[i,j].Sensitive_IntArrayMultiType[k], + verifyDoc.Sensitive_ArrayMultiTypes[i,j].Sensitive_IntArrayMultiType[k]); } } } @@ -1938,41 +2071,49 @@ private static void VerifyExpectedDocResponse(TestDoc expectedDoc, TestDoc verif if (expectedDoc.Sensitive_NestedObjectFormatL1 != null) { Assert.AreEqual(expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_IntFormatL1, verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_IntFormatL1); - Assert.AreEqual( - expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_IntFormatL2, - verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_IntFormatL2); - Assert.AreEqual( - expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_IntFormatL3, - verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_IntFormatL3); + if (expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2 == null) + { + Assert.IsNull(verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2); + } + else + { + Assert.AreEqual( + expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_IntFormatL2, + verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_IntFormatL2); + + Assert.AreEqual( + expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_IntFormatL3, + verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_IntFormatL3); - Assert.AreEqual( - expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_DecimalFormatL3, - verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_DecimalFormatL3); + Assert.AreEqual( + expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_DecimalFormatL3, + verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_DecimalFormatL3); - Assert.AreEqual( - expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayFormatL3[0].Sensitive_ArrayIntFormat, - verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayFormatL3[0].Sensitive_ArrayIntFormat); + Assert.AreEqual( + expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayFormatL3[0].Sensitive_ArrayIntFormat, + verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayFormatL3[0].Sensitive_ArrayIntFormat); - Assert.AreEqual( - expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayFormatL3[0].Sensitive_ArrayDecimalFormat, - verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayFormatL3[0].Sensitive_ArrayDecimalFormat); + Assert.AreEqual( + expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayFormatL3[0].Sensitive_ArrayDecimalFormat, + verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayFormatL3[0].Sensitive_ArrayDecimalFormat); - Assert.AreEqual( - expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_ArrayDecimalFormat, - verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_ArrayDecimalFormat); + Assert.AreEqual( + expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_ArrayDecimalFormat, + verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_ArrayDecimalFormat); - Assert.AreEqual( - expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_ArrayIntFormat, - verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_ArrayIntFormat); + Assert.AreEqual( + expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_ArrayIntFormat, + verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_ArrayIntFormat); - Assert.AreEqual( - expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_NestedObjectFormatL0.Sensitive_IntFormatL0, - verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_NestedObjectFormatL0.Sensitive_IntFormatL0); + Assert.AreEqual( + expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_NestedObjectFormatL0.Sensitive_IntFormatL0, + verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_NestedObjectFormatL0.Sensitive_IntFormatL0); - Assert.AreEqual( - expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_NestedObjectFormatL0.Sensitive_DecimalFormatL0, - verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_NestedObjectFormatL0.Sensitive_DecimalFormatL0); + Assert.AreEqual( + expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_NestedObjectFormatL0.Sensitive_DecimalFormatL0, + verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_NestedObjectFormatL0.Sensitive_DecimalFormatL0); + } } else { @@ -1985,6 +2126,7 @@ private static void VerifyExpectedDocResponse(TestDoc expectedDoc, TestDoc verif Assert.AreEqual(expectedDoc.Sensitive_FloatFormat, verifyDoc.Sensitive_FloatFormat); Assert.AreEqual(expectedDoc.Sensitive_BoolFormat, verifyDoc.Sensitive_BoolFormat); Assert.AreEqual(expectedDoc.NonSensitive, verifyDoc.NonSensitive); + Assert.AreEqual(expectedDoc.NonSensitiveInt, verifyDoc.NonSensitiveInt); } public class TestDoc @@ -1997,6 +2139,8 @@ public class TestDoc public string NonSensitive { get; set; } + public int NonSensitiveInt { get; set; } + public string Sensitive_StringFormat { get; set; } public DateTime Sensitive_DateFormat { get; set; } @@ -2080,6 +2224,7 @@ public TestDoc(TestDoc other) this.Id = other.Id; this.PK = other.PK; this.NonSensitive = other.NonSensitive; + this.NonSensitiveInt = other.NonSensitiveInt; this.Sensitive_StringFormat = other.Sensitive_StringFormat; this.Sensitive_DateFormat = other.Sensitive_DateFormat; this.Sensitive_DecimalFormat = other.Sensitive_DecimalFormat; @@ -2098,6 +2243,7 @@ public override bool Equals(object obj) && this.Id == doc.Id && this.PK == doc.PK && this.NonSensitive == doc.NonSensitive + && this.NonSensitiveInt == doc.NonSensitiveInt && this.Sensitive_StringFormat == doc.Sensitive_StringFormat && this.Sensitive_DateFormat == doc.Sensitive_DateFormat && this.Sensitive_DecimalFormat == doc.Sensitive_DecimalFormat @@ -2116,6 +2262,7 @@ public bool EqualsExceptEncryptedProperty(object obj) && this.Id == doc.Id && this.PK == doc.PK && this.NonSensitive == doc.NonSensitive + && this.NonSensitiveInt == doc.NonSensitiveInt && this.Sensitive_StringFormat != doc.Sensitive_StringFormat; } public override int GetHashCode() @@ -2124,6 +2271,7 @@ public override int GetHashCode() hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.Id); hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.PK); hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.NonSensitive); + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.NonSensitiveInt); hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.Sensitive_StringFormat); hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.Sensitive_DateFormat); hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.Sensitive_DecimalFormat); @@ -2143,6 +2291,7 @@ public static TestDoc Create(string partitionKey = null) Id = Guid.NewGuid().ToString(), PK = partitionKey ?? Guid.NewGuid().ToString(), NonSensitive = Guid.NewGuid().ToString(), + NonSensitiveInt = 10, Sensitive_StringFormat = Guid.NewGuid().ToString(), Sensitive_DateFormat = new DateTime(1987, 12, 25), Sensitive_DecimalFormat = 472.3108m, @@ -2162,7 +2311,7 @@ public static TestDoc Create(string partitionKey = null) Sensitive_ArrayMultiTypes = new Sensitive_ArrayMultiType[,] { { - new Sensitive_ArrayMultiType() + new Sensitive_ArrayMultiType() { Sensitive_NestedObjectFormatL0 = new Sensitive_NestedObjectL0() { @@ -2173,7 +2322,7 @@ public static TestDoc Create(string partitionKey = null) Sensitive_ArrayMultiTypeDecimalFormat = 10.2m, Sensitive_IntArrayMultiType = new int[2] { 999, 1000 } }, - new Sensitive_ArrayMultiType() + new Sensitive_ArrayMultiType() { Sensitive_NestedObjectFormatL0 = new Sensitive_NestedObjectL0() { @@ -2186,7 +2335,7 @@ public static TestDoc Create(string partitionKey = null) } }, { - new Sensitive_ArrayMultiType() + new Sensitive_ArrayMultiType() { Sensitive_NestedObjectFormatL0 = new Sensitive_NestedObjectL0() { @@ -2197,7 +2346,7 @@ public static TestDoc Create(string partitionKey = null) Sensitive_ArrayMultiTypeDecimalFormat = 9876.2m, Sensitive_IntArrayMultiType = new int[2] { 1, 2 } }, - new Sensitive_ArrayMultiType() + new Sensitive_ArrayMultiType() { Sensitive_NestedObjectFormatL0 = new Sensitive_NestedObjectL0() { @@ -2216,55 +2365,55 @@ public static TestDoc Create(string partitionKey = null) Sensitive_IntFormatL1 = 1999, Sensitive_DecimalFormatL1 = 1999.1m, Sensitive_ArrayFormatL1 = new Sensitive_ArrayData[] - { - new Sensitive_ArrayData { - Sensitive_ArrayIntFormat = 1, - Sensitive_ArrayDecimalFormat = 2.1m + new Sensitive_ArrayData + { + Sensitive_ArrayIntFormat = 1, + Sensitive_ArrayDecimalFormat = 2.1m + }, + new Sensitive_ArrayData + { + Sensitive_ArrayIntFormat = 2, + Sensitive_ArrayDecimalFormat = 3.1m + } }, - new Sensitive_ArrayData - { - Sensitive_ArrayIntFormat = 2, - Sensitive_ArrayDecimalFormat = 3.1m - } - }, Sensitive_NestedObjectFormatL2 = new Sensitive_NestedObjectL2() { Sensitive_IntFormatL2 = 2000, Sensitive_DecimalFormatL2 = 2000.1m, Sensitive_ArrayFormatL2 = new Sensitive_ArrayData[] - { - new Sensitive_ArrayData - { - Sensitive_ArrayIntFormat = 2, - Sensitive_ArrayDecimalFormat = 3.1m - } - }, + { + new Sensitive_ArrayData + { + Sensitive_ArrayIntFormat = 2, + Sensitive_ArrayDecimalFormat = 3.1m + } + }, Sensitive_NestedObjectFormatL3 = new Sensitive_NestedObjectL3() { Sensitive_IntFormatL3 = 3000, Sensitive_DecimalFormatL3 = 3000.1m, Sensitive_ArrayFormatL3 = new Sensitive_ArrayData[] - { - new Sensitive_ArrayData - { - Sensitive_ArrayIntFormat = 3, - Sensitive_ArrayDecimalFormat = 4.1m, - } - }, + { + new Sensitive_ArrayData + { + Sensitive_ArrayIntFormat = 3, + Sensitive_ArrayDecimalFormat = 4.1m, + } + }, Sensitive_ArrayWithObjectFormat = new Sensitive_ArrayDataWithObject[] - { - new Sensitive_ArrayDataWithObject - { - Sensitive_ArrayIntFormat = 4, - Sensitive_ArrayDecimalFormat = 5.1m, - Sensitive_NestedObjectFormatL0 = new Sensitive_NestedObjectL0() - { - Sensitive_IntFormatL0 = 888, - Sensitive_DecimalFormatL0 = 888.1m, - } - } - } + { + new Sensitive_ArrayDataWithObject + { + Sensitive_ArrayIntFormat = 4, + Sensitive_ArrayDecimalFormat = 5.1m, + Sensitive_NestedObjectFormatL0 = new Sensitive_NestedObjectL0() + { + Sensitive_IntFormatL0 = 888, + Sensitive_DecimalFormatL0 = 888.1m, + } + } + } } } } From 0e46706cc464d3f0a47e43c8de7f045b32236765 Mon Sep 17 00:00:00 2001 From: Anuj Toshniwal Date: Wed, 12 May 2021 11:27:26 +0530 Subject: [PATCH 2/3] Update variable name in test --- .../tests/EmulatorTests/MdeEncryptionTests.cs | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/Microsoft.Azure.Cosmos.Encryption/tests/EmulatorTests/MdeEncryptionTests.cs b/Microsoft.Azure.Cosmos.Encryption/tests/EmulatorTests/MdeEncryptionTests.cs index 51e4ae6c1f..085527421f 100644 --- a/Microsoft.Azure.Cosmos.Encryption/tests/EmulatorTests/MdeEncryptionTests.cs +++ b/Microsoft.Azure.Cosmos.Encryption/tests/EmulatorTests/MdeEncryptionTests.cs @@ -1503,40 +1503,40 @@ public async Task EncryptionRudItem() [TestMethod] public async Task EncryptionPatchItem() { - TestDoc docToPatch = await MdeEncryptionTests.MdeCreateItemAsync(MdeEncryptionTests.encryptionContainer); - docToPatch.NonSensitive = Guid.NewGuid().ToString(); - docToPatch.NonSensitiveInt++; - docToPatch.Sensitive_StringFormat = Guid.NewGuid().ToString(); - docToPatch.Sensitive_DateFormat = new DateTime(2020, 02, 02); - docToPatch.Sensitive_DecimalFormat = 11.11m; - docToPatch.Sensitive_IntArray[1] = 1; - docToPatch.Sensitive_IntMultiDimArray[1, 0] = 7; - docToPatch.Sensitive_IntFormat = 2020; - docToPatch.Sensitive_BoolFormat = false; - docToPatch.Sensitive_FloatFormat = 2020.20f; + TestDoc docPostPatching = await MdeEncryptionTests.MdeCreateItemAsync(MdeEncryptionTests.encryptionContainer); + docPostPatching.NonSensitive = Guid.NewGuid().ToString(); + docPostPatching.NonSensitiveInt++; + docPostPatching.Sensitive_StringFormat = Guid.NewGuid().ToString(); + docPostPatching.Sensitive_DateFormat = new DateTime(2020, 02, 02); + docPostPatching.Sensitive_DecimalFormat = 11.11m; + docPostPatching.Sensitive_IntArray[1] = 1; + docPostPatching.Sensitive_IntMultiDimArray[1, 0] = 7; + docPostPatching.Sensitive_IntFormat = 2020; + docPostPatching.Sensitive_BoolFormat = false; + docPostPatching.Sensitive_FloatFormat = 2020.20f; // Maximum 10 operations at a time (current limit) List patchOperations = new List { PatchOperation.Increment("/NonSensitiveInt", 1), - PatchOperation.Replace("/NonSensitive", docToPatch.NonSensitive), - PatchOperation.Replace("/Sensitive_StringFormat", docToPatch.Sensitive_StringFormat), - PatchOperation.Replace("/Sensitive_DateFormat", docToPatch.Sensitive_DateFormat), - PatchOperation.Replace("/Sensitive_DecimalFormat", docToPatch.Sensitive_DecimalFormat), - PatchOperation.Set("/Sensitive_IntArray/1", docToPatch.Sensitive_IntArray[1]), - PatchOperation.Set("/Sensitive_IntMultiDimArray/1/0", docToPatch.Sensitive_IntMultiDimArray[1,0]), - PatchOperation.Replace("/Sensitive_IntFormat", docToPatch.Sensitive_IntFormat), - PatchOperation.Replace("/Sensitive_BoolFormat", docToPatch.Sensitive_BoolFormat), - PatchOperation.Replace("/Sensitive_FloatFormat", docToPatch.Sensitive_FloatFormat), + PatchOperation.Replace("/NonSensitive", docPostPatching.NonSensitive), + PatchOperation.Replace("/Sensitive_StringFormat", docPostPatching.Sensitive_StringFormat), + PatchOperation.Replace("/Sensitive_DateFormat", docPostPatching.Sensitive_DateFormat), + PatchOperation.Replace("/Sensitive_DecimalFormat", docPostPatching.Sensitive_DecimalFormat), + PatchOperation.Set("/Sensitive_IntArray/1", docPostPatching.Sensitive_IntArray[1]), + PatchOperation.Set("/Sensitive_IntMultiDimArray/1/0", docPostPatching.Sensitive_IntMultiDimArray[1,0]), + PatchOperation.Replace("/Sensitive_IntFormat", docPostPatching.Sensitive_IntFormat), + PatchOperation.Replace("/Sensitive_BoolFormat", docPostPatching.Sensitive_BoolFormat), + PatchOperation.Replace("/Sensitive_FloatFormat", docPostPatching.Sensitive_FloatFormat), }; await MdeEncryptionTests.MdePatchItemAsync( MdeEncryptionTests.encryptionContainer, patchOperations, - docToPatch, + docPostPatching, HttpStatusCode.OK); - docToPatch.Sensitive_ArrayFormat = new TestDoc.Sensitive_ArrayData[] + docPostPatching.Sensitive_ArrayFormat = new TestDoc.Sensitive_ArrayData[] { new TestDoc.Sensitive_ArrayData { @@ -1550,7 +1550,7 @@ await MdeEncryptionTests.MdePatchItemAsync( } }; - docToPatch.Sensitive_ArrayMultiTypes.SetValue( + docPostPatching.Sensitive_ArrayMultiTypes.SetValue( new TestDoc.Sensitive_ArrayMultiType() { Sensitive_NestedObjectFormatL0 = new TestDoc.Sensitive_NestedObjectL0() @@ -1565,7 +1565,7 @@ await MdeEncryptionTests.MdePatchItemAsync( 0, 1); - docToPatch.Sensitive_NestedObjectFormatL1 = new TestDoc.Sensitive_NestedObjectL1() + docPostPatching.Sensitive_NestedObjectFormatL1 = new TestDoc.Sensitive_NestedObjectL1() { Sensitive_IntArrayL1 = new int[2] { 999, 100 }, Sensitive_IntFormatL1 = 1999, @@ -1591,23 +1591,23 @@ await MdeEncryptionTests.MdePatchItemAsync( }; patchOperations.Clear(); - patchOperations.Add(PatchOperation.Add("/Sensitive_ArrayFormat/1", docToPatch.Sensitive_ArrayFormat[1])); - patchOperations.Add(PatchOperation.Replace("/Sensitive_ArrayMultiTypes/0/1", docToPatch.Sensitive_ArrayMultiTypes[0, 1])); + patchOperations.Add(PatchOperation.Add("/Sensitive_ArrayFormat/1", docPostPatching.Sensitive_ArrayFormat[1])); + patchOperations.Add(PatchOperation.Replace("/Sensitive_ArrayMultiTypes/0/1", docPostPatching.Sensitive_ArrayMultiTypes[0, 1])); patchOperations.Add(PatchOperation.Remove("/Sensitive_NestedObjectFormatL1/Sensitive_NestedObjectFormatL2")); - patchOperations.Add(PatchOperation.Set("/Sensitive_NestedObjectFormatL1/Sensitive_ArrayFormatL1/0", docToPatch.Sensitive_NestedObjectFormatL1.Sensitive_ArrayFormatL1[0])); + patchOperations.Add(PatchOperation.Set("/Sensitive_NestedObjectFormatL1/Sensitive_ArrayFormatL1/0", docPostPatching.Sensitive_NestedObjectFormatL1.Sensitive_ArrayFormatL1[0])); await MdeEncryptionTests.MdePatchItemAsync( MdeEncryptionTests.encryptionContainer, patchOperations, - docToPatch, + docPostPatching, HttpStatusCode.OK); patchOperations.Add(PatchOperation.Increment("/Sensitive_IntFormat", 1)); try { await MdeEncryptionTests.encryptionContainer.PatchItemAsync( - docToPatch.Id, - new PartitionKey(docToPatch.PK), + docPostPatching.Id, + new PartitionKey(docPostPatching.PK), patchOperations); } catch(InvalidOperationException ex) From 2ddd490449c4fa5e1d383b9c7dca294e21e957a9 Mon Sep 17 00:00:00 2001 From: Anuj Toshniwal Date: Fri, 14 May 2021 14:58:07 +0530 Subject: [PATCH 3/3] Address comments. --- .../src/EncryptionContainer.cs | 52 ++++++++----------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs index 8b80e793c6..fff1fb0a8b 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs @@ -629,6 +629,22 @@ public async override Task PatchItemStreamAsync( PatchItemRequestOptions requestOptions = null, CancellationToken cancellationToken = default) { + if (string.IsNullOrWhiteSpace(id)) + { + throw new ArgumentNullException(nameof(id)); + } + + if (partitionKey == null) + { + throw new ArgumentNullException(nameof(partitionKey)); + } + + if (patchOperations == null || + !patchOperations.Any()) + { + throw new ArgumentNullException(nameof(patchOperations)); + } + EncryptionSettings encryptionSettings = await this.GetOrUpdateEncryptionSettingsFromCacheAsync( obsoleteEncryptionSettings: null, cancellationToken: cancellationToken); @@ -637,8 +653,6 @@ public async override Task PatchItemStreamAsync( using (diagnosticsContext.CreateScope("PatchItem")) { List encryptedPatchOperations = await this.PatchItemHelperAsync( - id, - partitionKey, patchOperations, encryptionSettings, cancellationToken); @@ -661,28 +675,10 @@ public async override Task PatchItemStreamAsync( } private async Task> PatchItemHelperAsync( - string id, - PartitionKey partitionKey, IReadOnlyList patchOperations, EncryptionSettings encryptionSettings, CancellationToken cancellationToken = default) { - if (string.IsNullOrWhiteSpace(id)) - { - throw new ArgumentNullException(nameof(id)); - } - - if (partitionKey == null) - { - throw new ArgumentNullException(nameof(partitionKey)); - } - - if (patchOperations == null || - !patchOperations.Any()) - { - throw new ArgumentNullException(nameof(patchOperations)); - } - List encryptedPatchOperations = new List(patchOperations.Count); foreach (PatchOperation patchOperation in patchOperations) @@ -718,25 +714,23 @@ private async Task> PatchItemHelperAsync( throw new ArgumentException($"Cannot serialize value parameter for operation: {patchOperation.OperationType}, path: {patchOperation.Path}."); } - AeadAes256CbcHmac256EncryptionAlgorithm aeadAes256CbcHmac256EncryptionAlgorithm = await settingforProperty.BuildEncryptionAlgorithmForSettingAsync(cancellationToken); - - JToken propertyValue = EncryptionProcessor.BaseSerializer.FromStream(valueParam); - JToken encryptedPropertyValue = EncryptionProcessor.EncryptProperty( - propertyValue, - aeadAes256CbcHmac256EncryptionAlgorithm); + Stream encryptedPropertyValue = await EncryptionProcessor.EncryptValueStreamAsync( + valueParam, + settingforProperty, + cancellationToken); switch (patchOperation.OperationType) { case PatchOperationType.Add: - encryptedPatchOperations.Add(PatchOperation.Add(patchOperation.Path, this.CosmosSerializer.ToStream(encryptedPropertyValue))); + encryptedPatchOperations.Add(PatchOperation.Add(patchOperation.Path, encryptedPropertyValue)); break; case PatchOperationType.Replace: - encryptedPatchOperations.Add(PatchOperation.Replace(patchOperation.Path, this.CosmosSerializer.ToStream(encryptedPropertyValue))); + encryptedPatchOperations.Add(PatchOperation.Replace(patchOperation.Path, encryptedPropertyValue)); break; case PatchOperationType.Set: - encryptedPatchOperations.Add(PatchOperation.Set(patchOperation.Path, this.CosmosSerializer.ToStream(encryptedPropertyValue))); + encryptedPatchOperations.Add(PatchOperation.Set(patchOperation.Path, encryptedPropertyValue)); break; default: