Skip to content

Commit

Permalink
[Client Encryption] Patch: Adds client encryption support for patch (#…
Browse files Browse the repository at this point in the history
…2448)

* Add support for Patch for public model of client-side encryption
  • Loading branch information
anujtoshniwal authored May 14, 2021
1 parent a0a646c commit a9ace36
Show file tree
Hide file tree
Showing 2 changed files with 340 additions and 74 deletions.
125 changes: 121 additions & 4 deletions Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -604,24 +605,140 @@ public override FeedIterator<T> GetChangeFeedIterator<T>(
this.ResponseFactory);
}

public override Task<ItemResponse<T>> PatchItemAsync<T>(
public async override Task<ItemResponse<T>> PatchItemAsync<T>(
string id,
PartitionKey partitionKey,
IReadOnlyList<PatchOperation> patchOperations,
PatchItemRequestOptions requestOptions = null,
CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
ResponseMessage responseMessage = await this.PatchItemStreamAsync(
id,
partitionKey,
patchOperations,
requestOptions,
cancellationToken);

return this.ResponseFactory.CreateItemResponse<T>(responseMessage);
}

public override Task<ResponseMessage> PatchItemStreamAsync(
public async override Task<ResponseMessage> PatchItemStreamAsync(
string id,
PartitionKey partitionKey,
IReadOnlyList<PatchOperation> patchOperations,
PatchItemRequestOptions requestOptions = null,
CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
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);

CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(requestOptions);
using (diagnosticsContext.CreateScope("PatchItem"))
{
List<PatchOperation> encryptedPatchOperations = await this.PatchItemHelperAsync(
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<List<PatchOperation>> PatchItemHelperAsync(
IReadOnlyList<PatchOperation> patchOperations,
EncryptionSettings encryptionSettings,
CancellationToken cancellationToken = default)
{
List<PatchOperation> encryptedPatchOperations = new List<PatchOperation>(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}.");
}

Stream encryptedPropertyValue = await EncryptionProcessor.EncryptValueStreamAsync(
valueParam,
settingforProperty,
cancellationToken);

switch (patchOperation.OperationType)
{
case PatchOperationType.Add:
encryptedPatchOperations.Add(PatchOperation.Add(patchOperation.Path, encryptedPropertyValue));
break;

case PatchOperationType.Replace:
encryptedPatchOperations.Add(PatchOperation.Replace(patchOperation.Path, encryptedPropertyValue));
break;

case PatchOperationType.Set:
encryptedPatchOperations.Add(PatchOperation.Set(patchOperation.Path, encryptedPropertyValue));
break;

default:
throw new NotSupportedException(nameof(patchOperation.OperationType));
}
}

return encryptedPatchOperations;
}

public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder<T>(
Expand Down
Loading

0 comments on commit a9ace36

Please sign in to comment.