diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs index 4dc48eefcf..b968ad5bb9 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionContainer.cs @@ -155,8 +155,7 @@ private async Task CreateItemHelperAsync( streamPayload = await EncryptionProcessor.EncryptAsync( streamPayload, this.Encryptor, - encryptionItemRequestOptions.EncryptionOptions, - requestOptions, + encryptionItemRequestOptions, diagnosticsContext, cancellationToken); @@ -414,7 +413,7 @@ private async Task ReplaceItemHelperAsync( streamPayload = await EncryptionProcessor.EncryptAsync( streamPayload, this.Encryptor, - encryptionItemRequestOptions.EncryptionOptions, + encryptionItemRequestOptions, diagnosticsContext, cancellationToken); @@ -548,7 +547,7 @@ private async Task UpsertItemHelperAsync( streamPayload = await EncryptionProcessor.EncryptAsync( streamPayload, this.Encryptor, - encryptionItemRequestOptions.EncryptionOptions, + encryptionItemRequestOptions, diagnosticsContext, cancellationToken); diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionOptions.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionOptions.cs index 4d3ab6d465..a5dff8f12b 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionOptions.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionOptions.cs @@ -6,25 +6,6 @@ namespace Microsoft.Azure.Cosmos.Encryption.Custom { using System.Collections.Generic; - /// - /// API for JSON processing - /// - public enum JsonProcessor - { - /// - /// Newtonsoft.Json - /// - Newtonsoft, - -#if NET8_0_OR_GREATER - /// - /// Ut8JsonReader/Writer - /// - /// Available with .NET8.0 package only. - Stream, -#endif - } - /// /// Options for encryption of data. /// @@ -54,11 +35,5 @@ public sealed class EncryptionOptions /// Example of a path specification: /sensitive /// public IEnumerable PathsToEncrypt { get; set; } - - /// - /// Gets or sets API used for Json processing - /// - /// Setting only applies with Mde encryption is used. - public JsonProcessor JsonProcessor { get; set; } = JsonProcessor.Newtonsoft; } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionOptionsExtensions.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionOptionsExtensions.cs index 3074de99df..604e9062f1 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionOptionsExtensions.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionOptionsExtensions.cs @@ -10,12 +10,14 @@ namespace Microsoft.Azure.Cosmos.Encryption.Custom internal static class EncryptionOptionsExtensions { - internal static void Validate(this EncryptionOptions options) + internal static void Validate(this EncryptionOptions options, JsonProcessor jsonProcessor) { ArgumentValidation.ThrowIfNullOrWhiteSpace(options.DataEncryptionKeyId, nameof(options.DataEncryptionKeyId)); ArgumentValidation.ThrowIfNullOrWhiteSpace(options.EncryptionAlgorithm, nameof(options.EncryptionAlgorithm)); ArgumentValidation.ThrowIfNull(options.PathsToEncrypt, nameof(options.PathsToEncrypt)); + options.ValidateJsonProcessor(jsonProcessor); + if (options.PathsToEncrypt is not HashSet && options.PathsToEncrypt.Distinct().Count() != options.PathsToEncrypt.Count()) { throw new InvalidOperationException("Duplicate paths in PathsToEncrypt passed via EncryptionOptions."); @@ -34,5 +36,17 @@ internal static void Validate(this EncryptionOptions options) } } } + + public static void ValidateJsonProcessor(this EncryptionOptions options, JsonProcessor jsonProcessor) + { +#if NET8_0_OR_GREATER +#pragma warning disable CS0618 // Type or member is obsolete + if (jsonProcessor != JsonProcessor.Newtonsoft && options.EncryptionAlgorithm == CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized) + { + throw new NotSupportedException("JsonProcessor.Stream is not supported for AE AES encryption algorithm."); + } +#pragma warning restore CS0618 // Type or member is obsolete +#endif + } } } diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionProcessor.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionProcessor.cs index 4fd79e54be..d0efc48fe6 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionProcessor.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionProcessor.cs @@ -38,13 +38,15 @@ public static async Task EncryptAsync( Stream input, Encryptor encryptor, EncryptionOptions encryptionOptions, + JsonProcessor jsonProcessor, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { ValidateInputForEncrypt( input, encryptor, - encryptionOptions); + encryptionOptions, + jsonProcessor); if (!encryptionOptions.PathsToEncrypt.Any()) { @@ -53,7 +55,7 @@ public static async Task EncryptAsync( #pragma warning disable CS0618 // Type or member is obsolete return encryptionOptions.EncryptionAlgorithm switch { - CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized => await MdeEncryptionProcessor.EncryptAsync(input, encryptor, encryptionOptions, diagnosticsContext, cancellationToken), + CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized => await MdeEncryptionProcessor.EncryptAsync(input, encryptor, encryptionOptions, jsonProcessor, diagnosticsContext, cancellationToken), CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized => await AeAesEncryptionProcessor.EncryptAsync(input, encryptor, encryptionOptions, cancellationToken), _ => throw new NotSupportedException($"Encryption Algorithm : {encryptionOptions.EncryptionAlgorithm} is not supported."), }; @@ -63,35 +65,55 @@ public static async Task EncryptAsync( public static Task EncryptAsync( Stream input, Encryptor encryptor, - EncryptionOptions encryptionOptions, - RequestOptions requestOptions, + EncryptionItemRequestOptions requestOptions, + CosmosDiagnosticsContext diagnosticsContext, + CancellationToken cancellationToken) + { + return EncryptAsync( + input, + encryptor, + requestOptions.EncryptionOptions, + requestOptions.GetJsonProcessor(), + diagnosticsContext, + cancellationToken); + } + + public static Task EncryptAsync( + Stream input, + Encryptor encryptor, + EncryptionTransactionalBatchItemRequestOptions requestOptions, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { - requestOptions.ResolveJsonProcessorSelection(encryptionOptions); - return EncryptAsync(input, encryptor, encryptionOptions, diagnosticsContext, cancellationToken); + return EncryptAsync( + input, + encryptor, + requestOptions.EncryptionOptions, + requestOptions.GetJsonProcessor(), + diagnosticsContext, + cancellationToken); } +#if NET8_0_OR_GREATER public static async Task EncryptAsync( Stream input, Stream output, Encryptor encryptor, EncryptionOptions encryptionOptions, + JsonProcessor jsonProcessor, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { ValidateInputForEncrypt( input, encryptor, - encryptionOptions); + encryptionOptions, + jsonProcessor); if (!encryptionOptions.PathsToEncrypt.Any()) { -#if NET8_0_OR_GREATER await input.CopyToAsync(output, cancellationToken); -#else - await input.CopyToAsync(output); -#endif + return; } @@ -100,15 +122,14 @@ public static async Task EncryptAsync( throw new NotSupportedException($"Streaming mode is only allowed for {nameof(CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized)}"); } -#if NET8_0_OR_GREATER - if (encryptionOptions.JsonProcessor != JsonProcessor.Stream) + if (jsonProcessor != JsonProcessor.Stream) { throw new NotSupportedException($"Streaming mode is only allowed for {nameof(JsonProcessor.Stream)}"); } -#endif - await MdeEncryptionProcessor.EncryptAsync(input, output, encryptor, encryptionOptions, diagnosticsContext, cancellationToken); + await MdeEncryptionProcessor.EncryptAsync(input, output, encryptor, encryptionOptions, jsonProcessor, diagnosticsContext, cancellationToken); } +#endif /// /// If there isn't any data that needs to be decrypted, input stream will be returned without any modification. @@ -310,13 +331,14 @@ internal static DecryptionContext CreateDecryptionContext( private static void ValidateInputForEncrypt( Stream input, Encryptor encryptor, - EncryptionOptions encryptionOptions) + EncryptionOptions encryptionOptions, + JsonProcessor jsonProcessor) { ArgumentValidation.ThrowIfNull(input); ArgumentValidation.ThrowIfNull(encryptor); ArgumentValidation.ThrowIfNull(encryptionOptions); - encryptionOptions.Validate(); + encryptionOptions.Validate(jsonProcessor); } private static JObject RetrieveItem( diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionTransactionalBatch.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionTransactionalBatch.cs index 8e4dbe33d2..59e59bbc54 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionTransactionalBatch.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/EncryptionTransactionalBatch.cs @@ -61,8 +61,7 @@ public override TransactionalBatch CreateItemStream( streamPayload = EncryptionProcessor.EncryptAsync( streamPayload, this.encryptor, - encryptionItemRequestOptions.EncryptionOptions, - requestOptions, + encryptionItemRequestOptions, diagnosticsContext, cancellationToken: default).Result; } @@ -134,8 +133,7 @@ public override TransactionalBatch ReplaceItemStream( streamPayload = EncryptionProcessor.EncryptAsync( streamPayload, this.encryptor, - encryptionItemRequestOptions.EncryptionOptions, - requestOptions, + encryptionItemRequestOptions, diagnosticsContext, cancellationToken: default).Result; } @@ -182,8 +180,7 @@ public override TransactionalBatch UpsertItemStream( streamPayload = EncryptionProcessor.EncryptAsync( streamPayload, this.encryptor, - encryptionItemRequestOptions.EncryptionOptions, - requestOptions, + encryptionItemRequestOptions, diagnosticsContext, cancellationToken: default).Result; } diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/JsonProcessor.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/JsonProcessor.cs new file mode 100644 index 0000000000..35742a4421 --- /dev/null +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/JsonProcessor.cs @@ -0,0 +1,25 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Encryption.Custom +{ + /// + /// API for JSON processing + /// + internal enum JsonProcessor + { + /// + /// Newtonsoft.Json + /// + Newtonsoft, + +#if NET8_0_OR_GREATER + /// + /// Ut8JsonReader/Writer + /// + /// Available with .NET8.0 package only. + Stream, +#endif + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/JsonProcessorRequestOptionsExtensions.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/JsonProcessorRequestOptionsExtensions.cs index 011e70ed60..deef608d53 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/JsonProcessorRequestOptionsExtensions.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/JsonProcessorRequestOptionsExtensions.cs @@ -5,7 +5,6 @@ namespace Microsoft.Azure.Cosmos.Encryption.Custom { using System; - using System.Collections.Generic; using Microsoft.Azure.Cosmos; /// @@ -19,38 +18,6 @@ internal static class JsonProcessorRequestOptionsExtensions /// internal const string JsonProcessorPropertyBagKey = "encryption-json-processor"; - /// - /// Resolves and validates the JsonProcessor selection for the given encryption options. - /// Applies any override from RequestOptions.Properties and validates compatibility with the encryption algorithm. - /// - /// The request options that may contain a JsonProcessor override. - /// The encryption options to configure. - /// Thrown when an unsupported combination of algorithm and processor is detected. - internal static void ResolveJsonProcessorSelection(this RequestOptions requestOptions, EncryptionOptions encryptionOptions) - { -#pragma warning disable CS0618 // legacy algorithm still supported - if (encryptionOptions == null) - { - return; - } - - bool hasOverride = TryReadJsonProcessorOverride(requestOptions, out JsonProcessor overrideProcessor); - if (hasOverride) - { - encryptionOptions.JsonProcessor = overrideProcessor; - } - - // Normalize unsupported combinations - if (encryptionOptions.EncryptionAlgorithm == CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized && - encryptionOptions.JsonProcessor != JsonProcessor.Newtonsoft) - { - throw new NotSupportedException("JsonProcessor.Stream is not supported for AE AES encryption algorithm."); - } - - SynchronizeJsonProcessorProperty(requestOptions, encryptionOptions.JsonProcessor, hasOverride); -#pragma warning restore CS0618 - } - /// /// Attempts to read a JsonProcessor override from the RequestOptions.Properties dictionary. /// Supports both JsonProcessor enum values and string representations (case-insensitive). @@ -69,7 +36,7 @@ internal static bool TryReadJsonProcessorOverride(this RequestOptions requestOpt jsonProcessor = enumVal; return true; } - else if (value is string s && Enum.TryParse(s, true, out JsonProcessor parsed)) + else if (value is string s && Enum.TryParse(s, true, out JsonProcessor parsed)) { jsonProcessor = parsed; return true; @@ -79,34 +46,14 @@ internal static bool TryReadJsonProcessorOverride(this RequestOptions requestOpt return false; } - private static void SynchronizeJsonProcessorProperty(this RequestOptions requestOptions, JsonProcessor selectedProcessor, bool hasOverride) + internal static JsonProcessor GetJsonProcessor(this RequestOptions requestOptions, JsonProcessor defaultJsonProcessor = JsonProcessor.Newtonsoft) { - if (requestOptions == null) - { - return; - } - - if (!hasOverride && selectedProcessor == JsonProcessor.Newtonsoft) - { - return; - } - - Dictionary properties; - if (requestOptions.Properties != null) - { - properties = new Dictionary(capacity: requestOptions.Properties.Count); - foreach (KeyValuePair kvp in requestOptions.Properties) - { - properties[kvp.Key] = kvp.Value; - } - } - else + if (requestOptions.TryReadJsonProcessorOverride(out JsonProcessor jsonProcessor)) { - properties = new Dictionary(); + return jsonProcessor; } - properties[JsonProcessorPropertyBagKey] = selectedProcessor; - requestOptions.Properties = properties; + return defaultJsonProcessor; } } } diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/Transformation/IMdeJsonProcessorAdapter.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/Transformation/IMdeJsonProcessorAdapter.cs index 26e52adfe2..d510652521 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/Transformation/IMdeJsonProcessorAdapter.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/Transformation/IMdeJsonProcessorAdapter.cs @@ -42,9 +42,10 @@ internal interface IMdeJsonProcessorAdapter /// Output stream to write encrypted JSON document to. /// Encryptor to use for encryption operations. /// Encryption options including paths to encrypt. + /// JSON processor. /// Cancellation token. /// Thrown by NewtonsoftAdapter as it requires in-memory processing. - Task EncryptAsync(Stream input, Stream output, Encryptor encryptor, EncryptionOptions options, CancellationToken cancellationToken); + Task EncryptAsync(Stream input, Stream output, Encryptor encryptor, EncryptionOptions options, JsonProcessor jsonProcessor, CancellationToken cancellationToken); /// /// Decrypts a JSON stream and returns the decrypted result as a new stream along with decryption context. diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/Transformation/MdeEncryptionProcessor.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/Transformation/MdeEncryptionProcessor.cs index d073796a7b..2de713774e 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/Transformation/MdeEncryptionProcessor.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/Transformation/MdeEncryptionProcessor.cs @@ -35,12 +35,12 @@ public async Task EncryptAsync( Stream input, Encryptor encryptor, EncryptionOptions encryptionOptions, + JsonProcessor jsonProcessor, CosmosDiagnosticsContext diagnosticsContext, CancellationToken token) { ArgumentValidation.ThrowIfNull(diagnosticsContext); - JsonProcessor jsonProcessor = encryptionOptions.JsonProcessor; using IDisposable selectionScope = diagnosticsContext.CreateScope(CosmosDiagnosticsContext.ScopeEncryptModeSelectionPrefix + jsonProcessor); IMdeJsonProcessorAdapter adapter = this.GetAdapter(jsonProcessor); @@ -107,17 +107,18 @@ public async Task EncryptAsync( Stream output, Encryptor encryptor, EncryptionOptions encryptionOptions, + JsonProcessor jsonProcessor, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { ArgumentValidation.ThrowIfNull(diagnosticsContext); #if NET8_0_OR_GREATER - if (encryptionOptions.JsonProcessor == JsonProcessor.Stream) + if (jsonProcessor == JsonProcessor.Stream) { using IDisposable selectionScope = diagnosticsContext.CreateScope(CosmosDiagnosticsContext.ScopeEncryptModeSelectionPrefix + JsonProcessor.Stream); IMdeJsonProcessorAdapter adapter = this.GetAdapter(JsonProcessor.Stream); - await adapter.EncryptAsync(input, output, encryptor, encryptionOptions, cancellationToken); + await adapter.EncryptAsync(input, output, encryptor, encryptionOptions, jsonProcessor, cancellationToken); return; } #endif diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/Transformation/NewtonsoftAdapter.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/Transformation/NewtonsoftAdapter.cs index 5854eab9a1..87a41d610d 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/Transformation/NewtonsoftAdapter.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/Transformation/NewtonsoftAdapter.cs @@ -31,7 +31,7 @@ public Task EncryptAsync(Stream input, Encryptor encryptor, EncryptionOp return this.jObjectProcessor.EncryptAsync(input, encryptor, options, cancellationToken); } - public Task EncryptAsync(Stream input, Stream output, Encryptor encryptor, EncryptionOptions options, CancellationToken cancellationToken) + public Task EncryptAsync(Stream input, Stream output, Encryptor encryptor, EncryptionOptions options, JsonProcessor jsonProcessor, CancellationToken cancellationToken) { throw new NotSupportedException("This overload is only supported for Stream JsonProcessor"); } diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/Transformation/SystemTextJsonStreamAdapter.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/Transformation/SystemTextJsonStreamAdapter.cs index 9c1731c2ca..9da7361335 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/Transformation/SystemTextJsonStreamAdapter.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/Transformation/SystemTextJsonStreamAdapter.cs @@ -27,9 +27,9 @@ public async Task EncryptAsync(Stream input, Encryptor encryptor, Encryp return ms; } - public Task EncryptAsync(Stream input, Stream output, Encryptor encryptor, EncryptionOptions options, CancellationToken cancellationToken) + public Task EncryptAsync(Stream input, Stream output, Encryptor encryptor, EncryptionOptions options, JsonProcessor jsonProcessor, CancellationToken cancellationToken) { - if (options.JsonProcessor != JsonProcessor.Stream) + if (jsonProcessor != JsonProcessor.Stream) { throw new NotSupportedException("This overload is only supported for Stream JsonProcessor"); } diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/EmulatorTests/MdeCustomEncryptionTests.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/EmulatorTests/MdeCustomEncryptionTests.cs index b7e4a594cf..37171e41fc 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/EmulatorTests/MdeCustomEncryptionTests.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/EmulatorTests/MdeCustomEncryptionTests.cs @@ -292,7 +292,7 @@ public async Task BackCompat_RoundTrip_WithAndWithoutJsonProcessorOverride() { Properties = new Dictionary { - { "encryption-json-processor", JsonProcessor.Stream } + { "encryption-json-processor", "Stream" } } }; @@ -364,7 +364,10 @@ public async Task StreamProcessor_EndToEnd_RoundTrip() DataEncryptionKeyId = dekProperties.Id, EncryptionAlgorithm = CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized, PathsToEncrypt = new List { "/Sensitive" }, - JsonProcessor = JsonProcessor.Stream + }, + Properties = new Dictionary + { + { "encryption-json-processor", "Stream" } } }; @@ -374,7 +377,7 @@ public async Task StreamProcessor_EndToEnd_RoundTrip() // Read back with stream override explicit (should decrypt) ItemResponse read = await encryptionContainer.ReadItemAsync(testItem.Id, new PartitionKey(testItem.PK), new ItemRequestOptions { - Properties = new Dictionary { { "encryption-json-processor", JsonProcessor.Stream } } + Properties = new Dictionary { { "encryption-json-processor", "Stream" } } }); Assert.AreEqual(testItem.Sensitive, read.Resource.Sensitive); } @@ -392,16 +395,10 @@ public async Task StreamProcessor_ScopesReflectStreamOptIn() Sensitive = "diag-secret" }; - EncryptionItemRequestOptions createOptions = new() - { - EncryptionOptions = new EncryptionOptions - { - DataEncryptionKeyId = dekProperties.Id, - EncryptionAlgorithm = CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized, - PathsToEncrypt = new List { "/Sensitive" }, - JsonProcessor = JsonProcessor.Stream - } - }; + EncryptionItemRequestOptions createOptions = CreateEncryptionItemRequestOptions( + dekProperties.Id, + CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized, + JsonProcessor.Stream); bool created = false; try @@ -420,7 +417,7 @@ public async Task StreamProcessor_ScopesReflectStreamOptIn() { Properties = new Dictionary { - { "encryption-json-processor", JsonProcessor.Stream } + { "encryption-json-processor", "Stream" } } }); @@ -523,93 +520,6 @@ await encryptionContainer.DeleteItemAsync( $"Expected to capture newtonsoft decrypt scope. Scopes: {string.Join(", ", scopes)}"); } - [TestMethod] - public async Task StreamProcessor_NewtonsoftOverrideStaysNewtonsoft() - { - List scopes = await CaptureEncryptionScopesAsync(async () => - { - TestItem testItem = new TestItem - { - Id = Guid.NewGuid().ToString(), - PK = Guid.NewGuid().ToString(), - NonSensitive = "explicit-newtonsoft-plain", - Sensitive = "explicit-newtonsoft-secret" - }; - - EncryptionItemRequestOptions newtonsoftOptions = new() - { - EncryptionOptions = new EncryptionOptions - { - DataEncryptionKeyId = dekProperties.Id, - EncryptionAlgorithm = CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized, - PathsToEncrypt = new List { "/Sensitive" }, - JsonProcessor = JsonProcessor.Newtonsoft - }, - Properties = new Dictionary - { - { JsonProcessorRequestOptionsExtensions.JsonProcessorPropertyBagKey, JsonProcessor.Newtonsoft } - } - }; - - bool created = false; - try - { - ItemResponse createResponse = await encryptionContainer.CreateItemAsync( - testItem, - new PartitionKey(testItem.PK), - newtonsoftOptions); - Assert.AreEqual(HttpStatusCode.Created, createResponse.StatusCode); - created = true; - - ItemResponse readResponse = await encryptionContainer.ReadItemAsync( - testItem.Id, - new PartitionKey(testItem.PK), - new ItemRequestOptions - { - Properties = new Dictionary - { - { JsonProcessorRequestOptionsExtensions.JsonProcessorPropertyBagKey, JsonProcessor.Newtonsoft } - } - }); - - Assert.AreEqual(testItem.Sensitive, readResponse.Resource.Sensitive); - } - finally - { - if (created) - { - await encryptionContainer.DeleteItemAsync( - testItem.Id, - new PartitionKey(testItem.PK), - new ItemRequestOptions - { - Properties = new Dictionary - { - { JsonProcessorRequestOptionsExtensions.JsonProcessorPropertyBagKey, JsonProcessor.Newtonsoft } - } - }); - } - } - }); - - (int streamEncrypt, int streamDecrypt, int newtonsoftEncrypt, int newtonsoftDecrypt) = CountJsonProcessorScopes(scopes); - - Assert.AreEqual( - 0, - streamEncrypt, - $"Did not expect stream encrypt scope when overriding with newtonsoft. Scopes: {string.Join(", ", scopes)}"); - Assert.AreEqual( - 0, - streamDecrypt, - $"Did not expect stream decrypt scope when overriding with newtonsoft. Scopes: {string.Join(", ", scopes)}"); - Assert.IsTrue( - newtonsoftEncrypt >= 1, - $"Expected to capture newtonsoft encrypt scope when overriding with newtonsoft. Scopes: {string.Join(", ", scopes)}"); - Assert.IsTrue( - newtonsoftDecrypt >= 1, - $"Expected to capture newtonsoft decrypt scope when overriding with newtonsoft. Scopes: {string.Join(", ", scopes)}"); - } - private static async Task> CaptureEncryptionScopesAsync(Func action) { List scopes = new List(); @@ -671,16 +581,10 @@ public async Task ProvidedOutputDecrypt_StreamProcessor_SuccessAndRewinds() Sensitive = "po-secret" }; - EncryptionItemRequestOptions options = new() - { - EncryptionOptions = new EncryptionOptions - { - DataEncryptionKeyId = dekProperties.Id, - EncryptionAlgorithm = CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized, - PathsToEncrypt = new List { "/Sensitive" }, - JsonProcessor = JsonProcessor.Stream - } - }; + EncryptionItemRequestOptions options = CreateEncryptionItemRequestOptions( + dekProperties.Id, + CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized, + JsonProcessor.Stream); _ = await encryptionContainer.CreateItemAsync(testItem, new PartitionKey(testItem.PK), options); @@ -695,7 +599,7 @@ public async Task ProvidedOutputDecrypt_StreamProcessor_SuccessAndRewinds() output, encryptor, new CosmosDiagnosticsContext(), - new ItemRequestOptions { Properties = new Dictionary { { "encryption-json-processor", JsonProcessor.Stream } } }, + new ItemRequestOptions { Properties = new Dictionary { { "encryption-json-processor", "Stream" } } }, CancellationToken.None); Assert.IsNotNull(ctx); Assert.AreEqual(0, output.Position); // rewound for caller consumption @@ -713,18 +617,13 @@ public async Task ProvidedOutputDecrypt_StreamOverrideWithLegacyAlgorithm_Throws Sensitive = "legacy-secret" }; - EncryptionItemRequestOptions options = new() - { - EncryptionOptions = new EncryptionOptions - { - DataEncryptionKeyId = dekProperties.Id, + EncryptionItemRequestOptions options = CreateEncryptionItemRequestOptions( + dekProperties.Id, #pragma warning disable CS0618 - EncryptionAlgorithm = CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized, + CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized, #pragma warning restore CS0618 - PathsToEncrypt = new List { "/Sensitive" }, - JsonProcessor = JsonProcessor.Newtonsoft - } - }; + JsonProcessor.Newtonsoft); + _ = await encryptionContainer.CreateItemAsync(testItem, new PartitionKey(testItem.PK), options); // Get raw encrypted (legacy algorithm) payload without automatic decrypt. @@ -736,7 +635,7 @@ public async Task ProvidedOutputDecrypt_StreamOverrideWithLegacyAlgorithm_Throws output, encryptor, new CosmosDiagnosticsContext(), - new ItemRequestOptions { Properties = new Dictionary { { "encryption-json-processor", JsonProcessor.Stream } } }, + new ItemRequestOptions { Properties = new Dictionary { { "encryption-json-processor", "Stream" } } }, CancellationToken.None); } @@ -749,6 +648,25 @@ private class TestItem } #endif + private static EncryptionItemRequestOptions CreateEncryptionItemRequestOptions(string dekId, string encryptionAlgorithm, JsonProcessor jsonProcessor) + { + EncryptionItemRequestOptions options = new () + { + EncryptionOptions = new EncryptionOptions + { + DataEncryptionKeyId = dekId, + EncryptionAlgorithm = encryptionAlgorithm, + PathsToEncrypt = new List { "/Sensitive" }, + }, + Properties = new Dictionary + { + { JsonProcessorRequestOptionsExtensions.JsonProcessorPropertyBagKey, jsonProcessor.ToString() } + } + }; + + return options; + } + [TestMethod] [ExpectedException(typeof(NotSupportedException))] public async Task UnsupportedJsonProcessor_ThrowsNotSupportedException() @@ -768,7 +686,10 @@ public async Task UnsupportedJsonProcessor_ThrowsNotSupportedException() DataEncryptionKeyId = dekId, EncryptionAlgorithm = CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized, PathsToEncrypt = TestDoc.PathsToEncrypt, - JsonProcessor = unsupportedProcessor + }, + Properties = new Dictionary + { + { JsonProcessorRequestOptionsExtensions.JsonProcessorPropertyBagKey, unsupportedProcessor } } }; diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Performance.Tests/EncryptionBenchmark.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Performance.Tests/EncryptionBenchmark.cs index 1276de90f1..dbae723beb 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Performance.Tests/EncryptionBenchmark.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Performance.Tests/EncryptionBenchmark.cs @@ -24,7 +24,7 @@ private static class RequestOptionsOverrideHelper { Properties = new System.Collections.Generic.Dictionary { - { "encryption-json-processor", processor } + { "encryption-json-processor", processor.ToString() } } }; #else @@ -58,7 +58,7 @@ private static class RequestOptionsOverrideHelper #else [Params(JsonProcessor.Newtonsoft)] #endif - public JsonProcessor JsonProcessor { get; set; } + internal JsonProcessor JsonProcessor { get; set; } [GlobalSetup] public async Task Setup() @@ -80,6 +80,7 @@ public async Task Setup() new MemoryStream(this.plaintext), this.encryptor, this.encryptionOptions, + this.JsonProcessor, new CosmosDiagnosticsContext(), CancellationToken.None); @@ -96,6 +97,7 @@ await EncryptionProcessor.EncryptAsync( new MemoryStream(this.plaintext!), this.encryptor, this.encryptionOptions, + this.JsonProcessor, new CosmosDiagnosticsContext(), CancellationToken.None); } @@ -110,6 +112,7 @@ await EncryptionProcessor.EncryptAsync( rms, this.encryptor, this.encryptionOptions, + this.JsonProcessor, new CosmosDiagnosticsContext(), CancellationToken.None); } @@ -148,7 +151,6 @@ private EncryptionOptions CreateEncryptionOptions() DataEncryptionKeyId = "dekId", EncryptionAlgorithm = CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized, PathsToEncrypt = TestDoc.PathsToEncrypt, - JsonProcessor = this.JsonProcessor, }; return options; diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Contracts/DotNetSDKEncryptionCustomAPI.net6.json b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Contracts/DotNetSDKEncryptionCustomAPI.net6.json index 1806d9725d..0af1227a3e 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Contracts/DotNetSDKEncryptionCustomAPI.net6.json +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Contracts/DotNetSDKEncryptionCustomAPI.net6.json @@ -979,18 +979,6 @@ "Microsoft.Azure.Cosmos.Encryption.Custom.EncryptionOptions;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { - "Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor get_JsonProcessor()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor get_JsonProcessor();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor JsonProcessor": { - "Type": "Property", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor JsonProcessor;CanRead:True;CanWrite:True;Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor get_JsonProcessor();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_JsonProcessor(Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, "System.Collections.Generic.IEnumerable`1[System.String] get_PathsToEncrypt()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ @@ -1046,13 +1034,6 @@ ], "MethodInfo": "Void set_EncryptionAlgorithm(System.String);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Void set_JsonProcessor(Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "Void set_JsonProcessor(Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, "Void set_PathsToEncrypt(System.Collections.Generic.IEnumerable`1[System.String])[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ @@ -1158,22 +1139,6 @@ } }, "NestedTypes": {} - }, - "Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor;System.Enum;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:True;IsClass:False;IsValueType:True;IsNested:False;IsGenericType:False;IsSerializable:True": { - "Subclasses": {}, - "Members": { - "Int32 value__": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Int32 value__;IsInitOnly:False;IsStatic:False;" - }, - "Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor Newtonsoft": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor Newtonsoft;IsInitOnly:False;IsStatic:True;" - } - }, - "NestedTypes": {} } }, "Members": { @@ -1217,4 +1182,4 @@ } }, "NestedTypes": {} -} \ No newline at end of file +} diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Contracts/DotNetSDKEncryptionCustomAPI.net8.json b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Contracts/DotNetSDKEncryptionCustomAPI.net8.json index 7bec9b8958..2c4f18f5ae 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Contracts/DotNetSDKEncryptionCustomAPI.net8.json +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Contracts/DotNetSDKEncryptionCustomAPI.net8.json @@ -979,18 +979,6 @@ "Microsoft.Azure.Cosmos.Encryption.Custom.EncryptionOptions;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { - "Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor get_JsonProcessor()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor get_JsonProcessor();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor JsonProcessor": { - "Type": "Property", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor JsonProcessor;CanRead:True;CanWrite:True;Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor get_JsonProcessor();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_JsonProcessor(Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, "System.Collections.Generic.IEnumerable`1[System.String] get_PathsToEncrypt()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ @@ -1046,13 +1034,6 @@ ], "MethodInfo": "Void set_EncryptionAlgorithm(System.String);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Void set_JsonProcessor(Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "Void set_JsonProcessor(Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, "Void set_PathsToEncrypt(System.Collections.Generic.IEnumerable`1[System.String])[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ @@ -1158,27 +1139,6 @@ } }, "NestedTypes": {} - }, - "Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor;System.Enum;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:True;IsClass:False;IsValueType:True;IsNested:False;IsGenericType:False;IsSerializable:True": { - "Subclasses": {}, - "Members": { - "Int32 value__": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Int32 value__;IsInitOnly:False;IsStatic:False;" - }, - "Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor Newtonsoft": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor Newtonsoft;IsInitOnly:False;IsStatic:True;" - }, - "Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor Stream": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.Encryption.Custom.JsonProcessor Stream;IsInitOnly:False;IsStatic:True;" - } - }, - "NestedTypes": {} } }, "Members": { diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/EncryptionOptionsExtensionsTests.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/EncryptionOptionsExtensionsTests.cs index e5b6c8c629..5a1bb0c9c9 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/EncryptionOptionsExtensionsTests.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/EncryptionOptionsExtensionsTests.cs @@ -16,21 +16,32 @@ public void Validate_EncryptionOptions_Throws() DataEncryptionKeyId = null, EncryptionAlgorithm = "something", PathsToEncrypt = new List() - }.Validate()); + }.Validate(default)); Assert.ThrowsException(() => new EncryptionOptions() { DataEncryptionKeyId = "something", EncryptionAlgorithm = null, PathsToEncrypt = new List() - }.Validate()); + }.Validate(default)); Assert.ThrowsException(() => new EncryptionOptions() { DataEncryptionKeyId = "something", EncryptionAlgorithm = "something", PathsToEncrypt = null - }.Validate()); + }.Validate(default)); + +#if NET8_0_OR_GREATER +#pragma warning disable CS0618 // Type or member is obsolete + Assert.ThrowsException(() => new EncryptionOptions() + { + DataEncryptionKeyId = "something", + EncryptionAlgorithm = CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized, + PathsToEncrypt = new List { "/id" }, + }.Validate(JsonProcessor.Stream)); +#pragma warning restore CS0618 // Type or member is obsolete +#endif } } } diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/EncryptionProcessorTests.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/EncryptionProcessorTests.cs index be7b6a1ee5..d0154ceef2 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/EncryptionProcessorTests.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/EncryptionProcessorTests.cs @@ -35,21 +35,23 @@ public static void ClassInitialize(TestContext ctx) } #if NET8_0_OR_GREATER - private static EncryptionOptions CreateMdeOptions(JsonProcessor processor) => new EncryptionOptions + private static EncryptionOptions CreateMdeOptions() { - DataEncryptionKeyId = DekId, + return new() + { + DataEncryptionKeyId = DekId, #pragma warning disable CS0618 - EncryptionAlgorithm = CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized, + EncryptionAlgorithm = CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized, #pragma warning restore CS0618 - PathsToEncrypt = TestDoc.PathsToEncrypt, - JsonProcessor = processor, - }; + PathsToEncrypt = TestDoc.PathsToEncrypt, + }; + } [TestMethod] public async Task EncryptDecrypt_StreamProcessor_WithProvidedOutput() { TestDoc doc = TestDoc.Create(); - EncryptionOptions opts = CreateMdeOptions(JsonProcessor.Stream); + EncryptionOptions opts = CreateMdeOptions(); // Capture activities to validate scopes are created List capturedActivities = new List(); @@ -63,7 +65,7 @@ public async Task EncryptDecrypt_StreamProcessor_WithProvidedOutput() CosmosDiagnosticsContext diagEncrypt = CosmosDiagnosticsContext.Create(null); MemoryStream encrypted = new(); - await EncryptionProcessor.EncryptAsync(doc.ToStream(), encrypted, mockEncryptor.Object, opts, diagEncrypt, CancellationToken.None); + await EncryptionProcessor.EncryptAsync(doc.ToStream(), encrypted, mockEncryptor.Object, opts, JsonProcessor.Stream, diagEncrypt, CancellationToken.None); encrypted.Position = 0; CosmosDiagnosticsContext diagDecrypt = CosmosDiagnosticsContext.Create(null); @@ -94,7 +96,7 @@ public async Task EncryptDecrypt_StreamProcessor_WithProvidedOutput() public async Task Encrypt_NewtonsoftProcessor_Works() { TestDoc doc = TestDoc.Create(); - EncryptionOptions opts = CreateMdeOptions(JsonProcessor.Newtonsoft); + EncryptionOptions opts = CreateMdeOptions(); // Capture activities to validate scopes are created List capturedActivities = new List(); @@ -107,7 +109,7 @@ public async Task Encrypt_NewtonsoftProcessor_Works() ActivitySource.AddActivityListener(listener); CosmosDiagnosticsContext diagEncrypt = CosmosDiagnosticsContext.Create(null); - Stream encrypted = await EncryptionProcessor.EncryptAsync(doc.ToStream(), mockEncryptor.Object, opts, diagEncrypt, CancellationToken.None); + Stream encrypted = await EncryptionProcessor.EncryptAsync(doc.ToStream(), mockEncryptor.Object, opts, JsonProcessor.Newtonsoft, diagEncrypt, CancellationToken.None); Assert.IsNotNull(encrypted); encrypted.Dispose(); @@ -147,10 +149,10 @@ public async Task Decrypt_StreamSelection_LegacyAlgorithm_FallsBackToNewtonsoft( #pragma warning restore CS0618 PathsToEncrypt = TestDoc.PathsToEncrypt, }; - Stream legacyEncrypted = await EncryptionProcessor.EncryptAsync(doc.ToStream(), mockEncryptor.Object, legacy, CosmosDiagnosticsContext.Create(null), CancellationToken.None); + Stream legacyEncrypted = await EncryptionProcessor.EncryptAsync(doc.ToStream(), mockEncryptor.Object, legacy, JsonProcessor.Newtonsoft, CosmosDiagnosticsContext.Create(null), CancellationToken.None); legacyEncrypted.Position = 0; - ItemRequestOptions opts = new() { Properties = new Dictionary { { JsonProcessorRequestOptionsExtensions.JsonProcessorPropertyBagKey, JsonProcessor.Stream } } }; + ItemRequestOptions opts = new() { Properties = new Dictionary { { JsonProcessorRequestOptionsExtensions.JsonProcessorPropertyBagKey, "Stream" } } }; CosmosDiagnosticsContext diag = CosmosDiagnosticsContext.Create(null); // Legacy algorithm should decrypt successfully by falling back to the legacy decryption path @@ -175,10 +177,10 @@ public async Task DecryptProvidedOutput_StreamSelection_LegacyAlgorithm_Throws() #pragma warning restore CS0618 PathsToEncrypt = TestDoc.PathsToEncrypt, }; - Stream legacyEncrypted = await EncryptionProcessor.EncryptAsync(doc.ToStream(), mockEncryptor.Object, legacy, CosmosDiagnosticsContext.Create(null), CancellationToken.None); + Stream legacyEncrypted = await EncryptionProcessor.EncryptAsync(doc.ToStream(), mockEncryptor.Object, legacy, JsonProcessor.Newtonsoft, CosmosDiagnosticsContext.Create(null), CancellationToken.None); legacyEncrypted.Position = 0; - ItemRequestOptions opts = new() { Properties = new Dictionary { { JsonProcessorRequestOptionsExtensions.JsonProcessorPropertyBagKey, JsonProcessor.Stream } } }; + ItemRequestOptions opts = new() { Properties = new Dictionary { { JsonProcessorRequestOptionsExtensions.JsonProcessorPropertyBagKey, "Stream" } } }; CosmosDiagnosticsContext diag = CosmosDiagnosticsContext.Create(null); MemoryStream output = new(); @@ -194,22 +196,27 @@ public async Task DecryptProvidedOutput_StreamSelection_LegacyAlgorithm_Throws() } [TestMethod] - public async Task Encrypt_LegacyAlgorithm_StreamProcessorOverride_Throws() + public async Task Encrypt_LegacyAlgorithm_StreamProcessor_Throws() { TestDoc doc = TestDoc.Create(); - EncryptionOptions legacy = new() - { - DataEncryptionKeyId = DekId, + EncryptionItemRequestOptions ro = new() + { + EncryptionOptions = new() + { + DataEncryptionKeyId = DekId, #pragma warning disable CS0618 - EncryptionAlgorithm = CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized, + EncryptionAlgorithm = CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized, #pragma warning restore CS0618 - PathsToEncrypt = TestDoc.PathsToEncrypt, + PathsToEncrypt = TestDoc.PathsToEncrypt, + }, + Properties = new Dictionary { { JsonProcessorRequestOptionsExtensions.JsonProcessorPropertyBagKey, "Stream" } } }; - ItemRequestOptions ro = new() { Properties = new Dictionary { { JsonProcessorRequestOptionsExtensions.JsonProcessorPropertyBagKey, JsonProcessor.Stream } } }; + CosmosDiagnosticsContext diag = CosmosDiagnosticsContext.Create(null); + try { - await EncryptionProcessor.EncryptAsync(doc.ToStream(), mockEncryptor.Object, legacy, ro, diag, CancellationToken.None); + await EncryptionProcessor.EncryptAsync(doc.ToStream(), mockEncryptor.Object, ro, diag, CancellationToken.None); Assert.Fail("Expected NotSupportedException for legacy algorithm with Stream processor override."); } catch (NotSupportedException ex) diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/JsonProcessorRequestOptionsExtensionsTests.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/JsonProcessorRequestOptionsExtensionsTests.cs index a13d8e8935..aa948cd24c 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/JsonProcessorRequestOptionsExtensionsTests.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/JsonProcessorRequestOptionsExtensionsTests.cs @@ -19,7 +19,7 @@ public void TryReadOverride_EnumValue_Succeeds() { Properties = new Dictionary { - { JsonProcessorRequestOptionsExtensions.JsonProcessorPropertyBagKey, JsonProcessor.Stream } + { JsonProcessorRequestOptionsExtensions.JsonProcessorPropertyBagKey, "Stream" } } }; @@ -76,55 +76,6 @@ public void TryReadOverride_InvalidString_Ignored() Assert.AreEqual(JsonProcessor.Newtonsoft, jp); } - [TestMethod] - public void ResolveSelection_UnsupportedCombination_Throws() - { -#pragma warning disable CS0618 // testing legacy path rejection for Stream processor - EncryptionOptions opts = new() - { - EncryptionAlgorithm = CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized, - JsonProcessor = JsonProcessor.Stream - }; -#pragma warning restore CS0618 - RequestOptions ro = new ItemRequestOptions(); - - Assert.ThrowsException(() => ro.ResolveJsonProcessorSelection(opts)); - } - - [TestMethod] - public void ResolveSelection_OverrideApplied() - { - EncryptionOptions opts = new() - { - EncryptionAlgorithm = CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized, - JsonProcessor = JsonProcessor.Newtonsoft - }; - RequestOptions ro = new ItemRequestOptions - { - Properties = new Dictionary - { - { JsonProcessorRequestOptionsExtensions.JsonProcessorPropertyBagKey, JsonProcessor.Stream } - } - }; - - ro.ResolveJsonProcessorSelection(opts); - Assert.AreEqual(JsonProcessor.Stream, opts.JsonProcessor); - } - - [TestMethod] - public void ResolveSelection_NoOverride_NoChange() - { - EncryptionOptions opts = new() - { - EncryptionAlgorithm = CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized, - JsonProcessor = JsonProcessor.Newtonsoft - }; - RequestOptions ro = new ItemRequestOptions(); - - ro.ResolveJsonProcessorSelection(opts); - Assert.AreEqual(JsonProcessor.Newtonsoft, opts.JsonProcessor); - } - [TestMethod] public void TryReadOverride_NullRequestOptions_Default() { @@ -151,7 +102,7 @@ public void TryReadOverride_MixedCaseKey_NotRecognized() Properties = new Dictionary { // Intentionally different casing pattern; should not match for perf (no ToUpper/ToLower) - { "Encryption-Json-Processor", JsonProcessor.Stream } + { "Encryption-Json-Processor", "Stream" } } }; @@ -167,7 +118,7 @@ public void TryReadOverride_MismatchedKey_NotRecognized() { Properties = new Dictionary { - { JsonProcessorRequestOptionsExtensions.JsonProcessorPropertyBagKey + "-extra", JsonProcessor.Stream } + { JsonProcessorRequestOptionsExtensions.JsonProcessorPropertyBagKey + "-extra", "Stream" } } }; diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/LegacyEncryptionProcessorTests.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/LegacyEncryptionProcessorTests.cs index 99d8e93022..2f805b64fd 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/LegacyEncryptionProcessorTests.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/LegacyEncryptionProcessorTests.cs @@ -39,7 +39,9 @@ public static void ClassInitialize(TestContext testContext) } [TestMethod] - public async Task InvalidPathToEncrypt() + [DynamicData(nameof(JsonProcessors))] + + internal async Task InvalidPathToEncrypt(JsonProcessor jsonProcessor) { TestDoc testDoc = TestDoc.Create(); EncryptionOptions encryptionOptionsWithInvalidPathToEncrypt = new () @@ -53,6 +55,7 @@ public async Task InvalidPathToEncrypt() testDoc.ToStream(), LegacyEncryptionProcessorTests.mockEncryptor.Object, encryptionOptionsWithInvalidPathToEncrypt, + jsonProcessor, new CosmosDiagnosticsContext(), CancellationToken.None); @@ -73,12 +76,13 @@ public async Task InvalidPathToEncrypt() } [TestMethod] - public async Task EncryptDecryptPropertyWithNullValue() + [DynamicData(nameof(JsonProcessors))] + internal async Task EncryptDecryptPropertyWithNullValue(JsonProcessor jsonProcessor) { TestDoc testDoc = TestDoc.Create(); testDoc.SensitiveStr = null; - JObject encryptedDoc = await LegacyEncryptionProcessorTests.VerifyEncryptionSucceeded(testDoc); + JObject encryptedDoc = await LegacyEncryptionProcessorTests.VerifyEncryptionSucceeded(testDoc, jsonProcessor); (JObject decryptedDoc, DecryptionContext decryptionContext) = await EncryptionProcessor.DecryptAsync( encryptedDoc, @@ -94,11 +98,12 @@ public async Task EncryptDecryptPropertyWithNullValue() } [TestMethod] - public async Task ValidateEncryptDecryptDocument() + [DynamicData(nameof(JsonProcessors))] + internal async Task ValidateEncryptDecryptDocument(JsonProcessor jsonProcessor) { TestDoc testDoc = TestDoc.Create(); - JObject encryptedDoc = await LegacyEncryptionProcessorTests.VerifyEncryptionSucceeded(testDoc); + JObject encryptedDoc = await LegacyEncryptionProcessorTests.VerifyEncryptionSucceeded(testDoc, jsonProcessor); (JObject decryptedDoc, DecryptionContext decryptionContext) = await EncryptionProcessor.DecryptAsync( encryptedDoc, @@ -114,7 +119,8 @@ public async Task ValidateEncryptDecryptDocument() } [TestMethod] - public async Task ValidateDecryptStream() + [DynamicData(nameof(JsonProcessors))] + internal async Task ValidateDecryptStream(JsonProcessor jsonProcessor) { TestDoc testDoc = TestDoc.Create(); @@ -122,6 +128,7 @@ public async Task ValidateDecryptStream() testDoc.ToStream(), LegacyEncryptionProcessorTests.mockEncryptor.Object, LegacyEncryptionProcessorTests.encryptionOptions, + jsonProcessor, new CosmosDiagnosticsContext(), CancellationToken.None); @@ -157,12 +164,13 @@ public async Task DecryptStreamWithoutEncryptedProperty() Assert.IsNull(decryptionContext); } - private static async Task VerifyEncryptionSucceeded(TestDoc testDoc) + private static async Task VerifyEncryptionSucceeded(TestDoc testDoc, JsonProcessor jsonProcessor) { Stream encryptedStream = await EncryptionProcessor.EncryptAsync( testDoc.ToStream(), LegacyEncryptionProcessorTests.mockEncryptor.Object, LegacyEncryptionProcessorTests.encryptionOptions, + jsonProcessor, new CosmosDiagnosticsContext(), CancellationToken.None); @@ -214,6 +222,17 @@ private static void VerifyDecryptionSucceeded( Assert.IsTrue(TestDoc.PathsToEncrypt.Exists(path => !decryptionInfo.PathsDecrypted.Contains(path))); } } + + public static IEnumerable JsonProcessors + { + get + { + yield return new object[] { JsonProcessor.Newtonsoft }; +#if NET8_0_OR_GREATER + yield return new object[] { JsonProcessor.Stream }; +#endif + } + } } #pragma warning restore CS0618 // Type or member is obsolete diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/MdeEncryptionProcessorTests.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/MdeEncryptionProcessorTests.cs index cbb6607b24..4a69e0c6ad 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/MdeEncryptionProcessorTests.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/MdeEncryptionProcessorTests.cs @@ -42,25 +42,23 @@ public static void ClassInitialize(TestContext testContext) } [TestMethod] - [DataRow(JsonProcessor.Newtonsoft)] -#if NET8_0_OR_GREATER - [DataRow(JsonProcessor.Stream)] -#endif - public async Task InvalidPathToEncrypt(JsonProcessor jsonProcessor) + [DynamicData(nameof(JsonProcessors))] + public async Task InvalidPathToEncrypt(int jsonProcessorValue) { + JsonProcessor jsonProcessor = ResolveJsonProcessor(jsonProcessorValue); TestDoc testDoc = TestDoc.Create(); EncryptionOptions encryptionOptionsWithInvalidPathToEncrypt = new() { DataEncryptionKeyId = dekId, EncryptionAlgorithm = CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized, PathsToEncrypt = new List() { "/SensitiveStr", "/Invalid" }, - JsonProcessor = jsonProcessor, }; Stream encryptedStream = await EncryptionProcessor.EncryptAsync( testDoc.ToStream(), mockEncryptor.Object, encryptionOptionsWithInvalidPathToEncrypt, + jsonProcessor, new CosmosDiagnosticsContext(), CancellationToken.None); @@ -82,19 +80,16 @@ public async Task InvalidPathToEncrypt(JsonProcessor jsonProcessor) } [TestMethod] - [DataRow(JsonProcessor.Newtonsoft)] -#if NET8_0_OR_GREATER - [DataRow(JsonProcessor.Stream)] -#endif - public async Task DuplicatePathToEncrypt(JsonProcessor jsonProcessor) + [DynamicData(nameof(JsonProcessors))] + public async Task DuplicatePathToEncrypt(int jsonProcessorValue) { + JsonProcessor jsonProcessor = ResolveJsonProcessor(jsonProcessorValue); TestDoc testDoc = TestDoc.Create(); EncryptionOptions encryptionOptionsWithDuplicatePathToEncrypt = new() { DataEncryptionKeyId = dekId, EncryptionAlgorithm = CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized, PathsToEncrypt = new List() { "/SensitiveStr", "/SensitiveStr" }, - JsonProcessor = jsonProcessor, }; try @@ -103,6 +98,7 @@ await EncryptionProcessor.EncryptAsync( testDoc.ToStream(), mockEncryptor.Object, encryptionOptionsWithDuplicatePathToEncrypt, + jsonProcessor, new CosmosDiagnosticsContext(), CancellationToken.None); @@ -115,13 +111,15 @@ await EncryptionProcessor.EncryptAsync( } [TestMethod] - [DynamicData(nameof(EncryptionOptionsCombinations))] - public async Task EncryptDecryptPropertyWithNullValue_VerifyByNewtonsoft(EncryptionOptions encryptionOptions) + [DynamicData(nameof(JsonProcessors))] + public async Task EncryptDecryptPropertyWithNullValue_VerifyByNewtonsoft(int jsonProcessorValue) { + JsonProcessor jsonProcessor = ResolveJsonProcessor(jsonProcessorValue); + EncryptionOptions encryptionOptions = this.CreateEncryptionOptions(); TestDoc testDoc = TestDoc.Create(); testDoc.SensitiveStr = null; - JObject encryptedDoc = await VerifyEncryptionSucceededNewtonsoft(testDoc, encryptionOptions); + JObject encryptedDoc = await VerifyEncryptionSucceededNewtonsoft(testDoc, encryptionOptions, jsonProcessor); (JObject decryptedDoc, DecryptionContext decryptionContext) = await EncryptionProcessor.DecryptAsync( encryptedDoc, @@ -137,12 +135,14 @@ public async Task EncryptDecryptPropertyWithNullValue_VerifyByNewtonsoft(Encrypt } [TestMethod] - [DynamicData(nameof(EncryptionOptionsCombinations))] - public async Task ValidateEncryptDecryptDocument_VerifyByNewtonsoft(EncryptionOptions encryptionOptions) + [DynamicData(nameof(JsonProcessors))] + public async Task ValidateEncryptDecryptDocument_VerifyByNewtonsoft(int jsonProcessorValue) { + JsonProcessor jsonProcessor = ResolveJsonProcessor(jsonProcessorValue); + EncryptionOptions encryptionOptions = this.CreateEncryptionOptions(); TestDoc testDoc = TestDoc.Create(); - JObject encryptedDoc = await VerifyEncryptionSucceededNewtonsoft(testDoc, encryptionOptions); + JObject encryptedDoc = await VerifyEncryptionSucceededNewtonsoft(testDoc, encryptionOptions, jsonProcessor); (JObject decryptedDoc, DecryptionContext decryptionContext) = await EncryptionProcessor.DecryptAsync( encryptedDoc, @@ -158,15 +158,18 @@ public async Task ValidateEncryptDecryptDocument_VerifyByNewtonsoft(EncryptionOp } [TestMethod] - [DynamicData(nameof(EncryptionOptionsCombinations))] - public async Task ValidateDecryptByNewtonsoftStream_VerifyByNewtonsoft(EncryptionOptions encryptionOptions) + [DynamicData(nameof(JsonProcessors))] + public async Task ValidateDecryptByNewtonsoftStream_VerifyByNewtonsoft(int jsonProcessorValue) { + JsonProcessor jsonProcessor = ResolveJsonProcessor(jsonProcessorValue); + EncryptionOptions encryptionOptions = this.CreateEncryptionOptions(); TestDoc testDoc = TestDoc.Create(); Stream encryptedStream = await EncryptionProcessor.EncryptAsync( testDoc.ToStream(), mockEncryptor.Object, encryptionOptions, + jsonProcessor, new CosmosDiagnosticsContext(), CancellationToken.None); @@ -186,15 +189,19 @@ public async Task ValidateDecryptByNewtonsoftStream_VerifyByNewtonsoft(Encryptio } [TestMethod] - [DynamicData(nameof(EncryptionOptionsStreamTestCombinations))] - public async Task ValidateDecryptBySystemTextStream_VerifyByNewtonsoft(EncryptionOptions encryptionOptions, JsonProcessor decryptionJsonProcessor) + [DynamicData(nameof(JsonProcessorCombinations))] + public async Task ValidateDecryptBySystemTextStream_VerifyByNewtonsoft(int encryptionJsonProcessorValue, int decryptionJsonProcessorValue) { + JsonProcessor encryptionJsonProcessor = ResolveJsonProcessor(encryptionJsonProcessorValue); + JsonProcessor decryptionJsonProcessor = ResolveJsonProcessor(decryptionJsonProcessorValue); + EncryptionOptions encryptionOptions = this.CreateEncryptionOptions(); TestDoc testDoc = TestDoc.Create(); Stream encryptedStream = await EncryptionProcessor.EncryptAsync( testDoc.ToStream(), mockEncryptor.Object, encryptionOptions, + encryptionJsonProcessor, new CosmosDiagnosticsContext(), CancellationToken.None); @@ -215,15 +222,19 @@ public async Task ValidateDecryptBySystemTextStream_VerifyByNewtonsoft(Encryptio #if NET8_0_OR_GREATER [TestMethod] - [DynamicData(nameof(EncryptionOptionsStreamTestCombinations))] - public async Task ValidateDecryptBySystemTextStream_VerifyBySystemText(EncryptionOptions encryptionOptions, JsonProcessor decryptionJsonProcessor) + [DynamicData(nameof(JsonProcessorCombinations))] + public async Task ValidateDecryptBySystemTextStream_VerifyBySystemText(int encryptionJsonProcessorValue, int decryptionJsonProcessorValue) { + JsonProcessor encryptionJsonProcessor = ResolveJsonProcessor(encryptionJsonProcessorValue); + JsonProcessor decryptionJsonProcessor = ResolveJsonProcessor(decryptionJsonProcessorValue); + EncryptionOptions encryptionOptions = this.CreateEncryptionOptions(); TestDoc testDoc = TestDoc.Create(); Stream encryptedStream = await EncryptionProcessor.EncryptAsync( testDoc.ToStream(), mockEncryptor.Object, encryptionOptions, + encryptionJsonProcessor, new CosmosDiagnosticsContext(), CancellationToken.None); @@ -244,12 +255,10 @@ public async Task ValidateDecryptBySystemTextStream_VerifyBySystemText(Encryptio #endif [TestMethod] - [DataRow(JsonProcessor.Newtonsoft)] -#if NET8_0_OR_GREATER - [DataRow(JsonProcessor.Stream)] -#endif - public async Task DecryptStreamWithoutEncryptedProperty(JsonProcessor processor) + [DynamicData(nameof(JsonProcessors))] + public async Task DecryptStreamWithoutEncryptedProperty(int processorValue) { + JsonProcessor processor = ResolveJsonProcessor(processorValue); TestDoc testDoc = TestDoc.Create(); Stream docStream = testDoc.ToStream(); @@ -266,14 +275,13 @@ public async Task DecryptStreamWithoutEncryptedProperty(JsonProcessor processor) Assert.IsNull(decryptionContext); } - // Local CreateRequestOptions helper removed in favor of shared RequestOptionsOverrideHelper. - - private static async Task VerifyEncryptionSucceededNewtonsoft(TestDoc testDoc, EncryptionOptions encryptionOptions) + private static async Task VerifyEncryptionSucceededNewtonsoft(TestDoc testDoc, EncryptionOptions encryptionOptions, JsonProcessor jsonProcessor) { Stream encryptedStream = await EncryptionProcessor.EncryptAsync( testDoc.ToStream(), mockEncryptor.Object, encryptionOptions, + jsonProcessor, new CosmosDiagnosticsContext(), CancellationToken.None); @@ -399,36 +407,58 @@ private static void AssertNullableValueKind(T expectedValue, JsonNode node, s } #endif - private static EncryptionOptions CreateEncryptionOptions(JsonProcessor processor) + private EncryptionOptions CreateEncryptionOptions() { return new EncryptionOptions() { DataEncryptionKeyId = dekId, EncryptionAlgorithm = CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized, PathsToEncrypt = TestDoc.PathsToEncrypt, - JsonProcessor = processor }; } - public static IEnumerable EncryptionOptionsCombinations => new[] { - new object[] { CreateEncryptionOptions(JsonProcessor.Newtonsoft) }, -#if NET8_0_OR_GREATER - new object[] { CreateEncryptionOptions(JsonProcessor.Stream) }, -#endif - }; + public static IEnumerable JsonProcessors + { + get + { + foreach (JsonProcessor processor in EnumerateJsonProcessors()) + { + yield return new object[] { (int)processor }; + } + } + } - public static IEnumerable EncryptionOptionsStreamTestCombinations + public static IEnumerable JsonProcessorCombinations { get { - foreach (object[] encryptionOptions in EncryptionOptionsCombinations) + JsonProcessor[] processors = EnumerateJsonProcessors().ToArray(); + foreach (JsonProcessor encProcessor in processors) { - yield return new object[] { encryptionOptions[0], JsonProcessor.Newtonsoft }; -#if NET8_0_OR_GREATER - yield return new object[] { encryptionOptions[0], JsonProcessor.Stream }; -#endif + foreach (JsonProcessor decProcessor in processors) + { + yield return new object[] { (int)encProcessor, (int)decProcessor }; + } } } } + + private static JsonProcessor ResolveJsonProcessor(int value) + { + if (!Enum.IsDefined(typeof(JsonProcessor), value)) + { + throw new ArgumentOutOfRangeException(nameof(value), "Invalid JsonProcessor value supplied to test."); + } + + return (JsonProcessor)value; + } + + private static IEnumerable EnumerateJsonProcessors() + { + yield return JsonProcessor.Newtonsoft; +#if NET8_0_OR_GREATER + yield return JsonProcessor.Stream; +#endif + } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/RequestOptionsOverrideHelper.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/RequestOptionsOverrideHelper.cs index 5101f1fee7..9c74c88d33 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/RequestOptionsOverrideHelper.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/RequestOptionsOverrideHelper.cs @@ -22,7 +22,7 @@ internal static class RequestOptionsOverrideHelper { Properties = new Dictionary { - { JsonProcessorRequestOptionsExtensions.JsonProcessorPropertyBagKey, processor } + { JsonProcessorRequestOptionsExtensions.JsonProcessorPropertyBagKey, processor.ToString() } } }; return requestOptions; diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/StreamProcessorConcurrencyAndCancellationTests.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/StreamProcessorConcurrencyAndCancellationTests.cs index 9a9032ec39..69808bc0a3 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/StreamProcessorConcurrencyAndCancellationTests.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/StreamProcessorConcurrencyAndCancellationTests.cs @@ -35,13 +35,12 @@ public static void Init(TestContext ctx) mockEncryptor = TestEncryptorFactory.CreateMde(DekId, out _); } - private static EncryptionOptions CreateStreamOptions(IEnumerable paths) + private static EncryptionOptions CreateEncryptionOptions(IEnumerable paths) { return new EncryptionOptions { DataEncryptionKeyId = DekId, EncryptionAlgorithm = CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized, - JsonProcessor = JsonProcessor.Stream, PathsToEncrypt = paths.ToList() }; } @@ -62,12 +61,13 @@ public async Task EncryptDecrypt_LargePayload_StreamProcessor() P1 = new string('a', 1024), P2 = "b", }; - EncryptionOptions options = CreateStreamOptions(new[] { "/Large", "/P1", "/P2" }); + EncryptionOptions options = CreateEncryptionOptions(new[] { "/Large", "/P1", "/P2" }); Stream encrypted = await EncryptionProcessor.EncryptAsync( TestCommon.ToStream(doc), mockEncryptor.Object, options, + JsonProcessor.Stream, new CosmosDiagnosticsContext(), CancellationToken.None); @@ -106,8 +106,8 @@ public async Task Concurrent_EncryptDecrypt_StreamProcessor() { string large = new string((char)('a' + (i % 26)), 50_000); var doc = new { id = Guid.NewGuid().ToString(), Large = large, P1 = i.ToString(), P2 = (i * 2).ToString() }; - EncryptionOptions options = CreateStreamOptions(new[] { "/Large", "/P1", "/P2" }); - Stream encrypted = await EncryptionProcessor.EncryptAsync(TestCommon.ToStream(doc), mockEncryptor.Object, options, new CosmosDiagnosticsContext(), CancellationToken.None); + EncryptionOptions options = CreateEncryptionOptions(new[] { "/Large", "/P1", "/P2" }); + Stream encrypted = await EncryptionProcessor.EncryptAsync(TestCommon.ToStream(doc), mockEncryptor.Object, options, JsonProcessor.Stream, new CosmosDiagnosticsContext(), CancellationToken.None); (Stream decrypted, DecryptionContext ctx) = await EncryptionProcessor.DecryptAsync(encrypted, mockEncryptor.Object, new CosmosDiagnosticsContext(), RequestOptionsOverrideHelper.Create(JsonProcessor.Stream), CancellationToken.None); decrypted.Position = 0; using JsonDocument jd = JsonDocument.Parse(decrypted); @@ -132,8 +132,8 @@ public async Task Encrypt_Cancellation_Aborts() byte[] payload = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(doc)); using SlowCancelableStream slow = new(payload, chunkSize: 64, perReadDelayMs: 1); using CancellationTokenSource cts = new(); - EncryptionOptions options = CreateStreamOptions(new[] { "/Large" }); - Task encryptTask = EncryptionProcessor.EncryptAsync(slow, mockEncryptor.Object, options, new CosmosDiagnosticsContext(), cts.Token); + EncryptionOptions options = CreateEncryptionOptions(new[] { "/Large" }); + Task encryptTask = EncryptionProcessor.EncryptAsync(slow, mockEncryptor.Object, options, JsonProcessor.Stream, new CosmosDiagnosticsContext(), cts.Token); cts.CancelAfter(5); // cancel shortly after start try @@ -153,8 +153,8 @@ public async Task Decrypt_Cancellation_Aborts() // First create a valid encrypted payload string large = new string('y', 50_000); var doc = new { id = Guid.NewGuid().ToString(), Large = large }; - EncryptionOptions options = CreateStreamOptions(new[] { "/Large" }); - Stream encrypted = await EncryptionProcessor.EncryptAsync(TestCommon.ToStream(doc), mockEncryptor.Object, options, new CosmosDiagnosticsContext(), CancellationToken.None); + EncryptionOptions options = CreateEncryptionOptions(new[] { "/Large" }); + Stream encrypted = await EncryptionProcessor.EncryptAsync(TestCommon.ToStream(doc), mockEncryptor.Object, options, JsonProcessor.Stream, new CosmosDiagnosticsContext(), CancellationToken.None); // Wrap encrypted stream in slow stream (must be seekable; we copy bytes) byte[] bytes = ((MemoryStream)encrypted).ToArray(); diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Transformation/Adapters/NewtonsoftAdapterTests.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Transformation/Adapters/NewtonsoftAdapterTests.cs index 996cef53a1..94a3fedca7 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Transformation/Adapters/NewtonsoftAdapterTests.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Transformation/Adapters/NewtonsoftAdapterTests.cs @@ -61,7 +61,7 @@ public async Task EncryptAsync_StreamOverload_Throws() using MemoryStream output = new (); await Assert.ThrowsExceptionAsync( - () => adapter.EncryptAsync(input, output, mockEncryptor.Object, defaultOptions, CancellationToken.None)); + () => adapter.EncryptAsync(input, output, mockEncryptor.Object, defaultOptions, JsonProcessor.Newtonsoft, CancellationToken.None)); } [TestMethod] diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Transformation/Adapters/SystemTextJsonStreamAdapterTests.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Transformation/Adapters/SystemTextJsonStreamAdapterTests.cs index 7a68a5b4a9..2173fb24db 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Transformation/Adapters/SystemTextJsonStreamAdapterTests.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Transformation/Adapters/SystemTextJsonStreamAdapterTests.cs @@ -35,7 +35,6 @@ public static void ClassInitialize(TestContext context) EncryptionAlgorithm = CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized, #pragma warning restore CS0618 PathsToEncrypt = new[] { "/Sensitive" }, - JsonProcessor = JsonProcessor.Stream, }; } @@ -59,7 +58,7 @@ public async Task EncryptAsync_StreamOverload_WritesToOutput() using Stream input = TestCommon.ToStream(new { id = "1", Sensitive = "secret" }); using MemoryStream output = new (); - await adapter.EncryptAsync(input, output, mockEncryptor.Object, defaultOptions, CancellationToken.None); + await adapter.EncryptAsync(input, output, mockEncryptor.Object, defaultOptions, JsonProcessor.Stream, CancellationToken.None); output.Position = 0; using JsonDocument doc = JsonDocument.Parse(output); @@ -80,11 +79,10 @@ public async Task EncryptAsync_StreamOverload_WithNonStreamProcessor_Throws() EncryptionAlgorithm = CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized, #pragma warning restore CS0618 PathsToEncrypt = new[] { "/Sensitive" }, - JsonProcessor = JsonProcessor.Newtonsoft, }; await Assert.ThrowsExceptionAsync( - () => adapter.EncryptAsync(input, output, mockEncryptor.Object, wrongOptions, CancellationToken.None)); + () => adapter.EncryptAsync(input, output, mockEncryptor.Object, wrongOptions, JsonProcessor.Newtonsoft, CancellationToken.None)); } [TestMethod] diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Transformation/StreamProcessorDecryptorTests.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Transformation/StreamProcessorDecryptorTests.cs index 3dc5f23a24..0079175aa5 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Transformation/StreamProcessorDecryptorTests.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Transformation/StreamProcessorDecryptorTests.cs @@ -51,8 +51,7 @@ private static EncryptionOptions CreateOptions(IEnumerable paths) { DataEncryptionKeyId = DekId, EncryptionAlgorithm = CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized, - JsonProcessor = JsonProcessor.Stream, - PathsToEncrypt = paths.ToList() + PathsToEncrypt = paths.ToList() }; } @@ -60,7 +59,7 @@ private static EncryptionOptions CreateOptions(IEnumerable paths) { Stream input = TestCommon.ToStream(doc); MemoryStream encryptedStream = new(); - await EncryptionProcessor.EncryptAsync(input, encryptedStream, mockEncryptor.Object, options, new CosmosDiagnosticsContext(), CancellationToken.None); + await EncryptionProcessor.EncryptAsync(input, encryptedStream, mockEncryptor.Object, options, JsonProcessor.Stream, new CosmosDiagnosticsContext(), CancellationToken.None); encryptedStream.Position = 0; // get properties via System.Text.Json to assert later diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Transformation/StreamProcessorEncryptorTests.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Transformation/StreamProcessorEncryptorTests.cs index edf61c4715..b798f93883 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Transformation/StreamProcessorEncryptorTests.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Transformation/StreamProcessorEncryptorTests.cs @@ -34,14 +34,13 @@ public static void Init(TestContext ctx) mockEncryptor = TestEncryptorFactory.CreateMde(DekId, out mockDek); } - private static EncryptionOptions CreateOptions(IEnumerable paths) + private static EncryptionOptions CreateOptions(IEnumerable paths) { return new EncryptionOptions { DataEncryptionKeyId = DekId, EncryptionAlgorithm = CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized, - JsonProcessor = JsonProcessor.Stream, - PathsToEncrypt = paths.ToList() + PathsToEncrypt = paths.ToList() }; } @@ -49,7 +48,7 @@ private static async Task EncryptAsync(object doc, EncryptionOptio { Stream input = TestCommon.ToStream(doc); MemoryStream output = new(); - await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, new CosmosDiagnosticsContext(), CancellationToken.None); + await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, JsonProcessor.Stream, new CosmosDiagnosticsContext(), CancellationToken.None); output.Position = 0; return output; } @@ -200,7 +199,7 @@ public async Task Encrypt_NullThenPlain_RemainsPlain() MemoryStream output = new(); EncryptionOptions options = CreateOptions(new[] { "/Maybe" }); // Act - await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, new CosmosDiagnosticsContext(), CancellationToken.None); + await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, JsonProcessor.Stream, new CosmosDiagnosticsContext(), CancellationToken.None); output.Position = 0; using JsonDocument jd = JsonDocument.Parse(output); JsonElement root = jd.RootElement; @@ -271,7 +270,7 @@ public async Task Encrypt_InputWithComments_IgnoresComments() MemoryStream output = new(); EncryptionOptions options = CreateOptions(new[] { "/SensitiveStr" }); // Act - await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, new CosmosDiagnosticsContext(), CancellationToken.None); + await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, JsonProcessor.Stream, new CosmosDiagnosticsContext(), CancellationToken.None); output.Position = 0; using JsonDocument jd = Parse(output); JsonElement root = jd.RootElement; @@ -294,7 +293,7 @@ public async Task Encrypt_NonObjectRoot_Array_RemainsUnchanged() MemoryStream output = new(); EncryptionOptions options = CreateOptions(Array.Empty()); // Act - await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, new CosmosDiagnosticsContext(), CancellationToken.None); + await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, JsonProcessor.Stream, new CosmosDiagnosticsContext(), CancellationToken.None); output.Position = 0; using JsonDocument jd = JsonDocument.Parse(output); // Assert @@ -311,7 +310,7 @@ public async Task Encrypt_NonObjectRoot_Primitive_RemainsUnchanged() MemoryStream output = new(); EncryptionOptions options = CreateOptions(Array.Empty()); // Act - await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, new CosmosDiagnosticsContext(), CancellationToken.None); + await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, JsonProcessor.Stream, new CosmosDiagnosticsContext(), CancellationToken.None); output.Position = 0; using JsonDocument jd = JsonDocument.Parse(output); // Assert @@ -340,7 +339,7 @@ public async Task Encrypt_Fails_OnTruncatedJson() try { // Act - await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, new CosmosDiagnosticsContext(), CancellationToken.None); + await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, JsonProcessor.Stream, new CosmosDiagnosticsContext(), CancellationToken.None); // Assert Assert.Fail("Expected exception for truncated JSON"); } @@ -362,7 +361,7 @@ public async Task Encrypt_Fails_OnDoubleInfinity() try { // Act - await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, new CosmosDiagnosticsContext(), CancellationToken.None); + await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, JsonProcessor.Stream, new CosmosDiagnosticsContext(), CancellationToken.None); // Assert Assert.Fail("Expected exception for Infinity double serialization"); } @@ -393,7 +392,7 @@ public async Task Encrypt_Fails_OnInvalidUtf8InString() try { // Act - await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, new CosmosDiagnosticsContext(), CancellationToken.None); + await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, JsonProcessor.Stream, new CosmosDiagnosticsContext(), CancellationToken.None); // Assert Assert.Fail("Expected parsing failure for invalid UTF-8"); } @@ -415,7 +414,7 @@ public async Task Encrypt_Fails_OnNaN_Literal() try { // Act - await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, new CosmosDiagnosticsContext(), CancellationToken.None); + await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, JsonProcessor.Stream, new CosmosDiagnosticsContext(), CancellationToken.None); // Assert Assert.Fail("Expected parsing failure for NaN literal"); } @@ -434,7 +433,7 @@ public async Task Encrypt_NegativeZero_Double_RoundtripsAsZero() MemoryStream encrypted = new(); EncryptionOptions options = CreateOptions(new[] { "/DZ" }); // Act (encrypt) - await EncryptionProcessor.EncryptAsync(input, encrypted, mockEncryptor.Object, options, new CosmosDiagnosticsContext(), CancellationToken.None); + await EncryptionProcessor.EncryptAsync(input, encrypted, mockEncryptor.Object, options, JsonProcessor.Stream, new CosmosDiagnosticsContext(), CancellationToken.None); encrypted.Position = 0; using JsonDocument jenc = JsonDocument.Parse(encrypted); byte[] cipher = Convert.FromBase64String(jenc.RootElement.GetProperty("DZ").GetString()); @@ -467,7 +466,7 @@ public async Task Encrypt_DeepNesting_ExceedsDepth_Fails() try { // Act - await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, new CosmosDiagnosticsContext(), CancellationToken.None); + await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, JsonProcessor.Stream, new CosmosDiagnosticsContext(), CancellationToken.None); // Assert Assert.Fail("Expected parsing failure for deep nesting"); } @@ -486,7 +485,7 @@ public async Task Encrypt_PathToArray_ButValueIsString_EncryptsAsString() MemoryStream output = new(); EncryptionOptions options = CreateOptions(new[] { "/Arr" }); // Act - await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, new CosmosDiagnosticsContext(), CancellationToken.None); + await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, JsonProcessor.Stream, new CosmosDiagnosticsContext(), CancellationToken.None); output.Position = 0; using JsonDocument jd = JsonDocument.Parse(output); // Assert @@ -504,7 +503,7 @@ public async Task Encrypt_PathToObject_ButValueIsNumber_EncryptsAsNumber() MemoryStream output = new(); EncryptionOptions options = CreateOptions(new[] { "/Obj" }); // Act - await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, new CosmosDiagnosticsContext(), CancellationToken.None); + await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, JsonProcessor.Stream, new CosmosDiagnosticsContext(), CancellationToken.None); output.Position = 0; using JsonDocument jd = JsonDocument.Parse(output); // Assert @@ -520,7 +519,7 @@ public async Task Encrypt_RootArray_NoOpWhenNoPaths() using MemoryStream input = new MemoryStream(Encoding.UTF8.GetBytes(json)); MemoryStream output = new(); EncryptionOptions options = CreateOptions(Array.Empty()); - await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, new CosmosDiagnosticsContext(), CancellationToken.None); + await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, JsonProcessor.Newtonsoft, new CosmosDiagnosticsContext(), CancellationToken.None); output.Position = 0; using JsonDocument jd = JsonDocument.Parse(output, new JsonDocumentOptions { CommentHandling = JsonCommentHandling.Skip }); @@ -538,7 +537,7 @@ public async Task Encrypt_PrimitiveRoot_NoOpWhenNoPaths() using MemoryStream input = new MemoryStream(Encoding.UTF8.GetBytes(json)); MemoryStream output = new(); EncryptionOptions options = CreateOptions(Array.Empty()); - await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, new CosmosDiagnosticsContext(), CancellationToken.None); + await EncryptionProcessor.EncryptAsync(input, output, mockEncryptor.Object, options, JsonProcessor.Newtonsoft, new CosmosDiagnosticsContext(), CancellationToken.None); output.Position = 0; using JsonDocument jd = JsonDocument.Parse(output);