Skip to content

Commit

Permalink
Merge to latest and update changelog
Browse files Browse the repository at this point in the history
  • Loading branch information
Jake Willey committed May 29, 2020
2 parents bba8a4a + b091054 commit cc395dc
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 19 deletions.
5 changes: 4 additions & 1 deletion Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,10 @@ public async override Task<ResponseMessage> UpsertItemStreamAsync(
public override TransactionalBatch CreateTransactionalBatch(
PartitionKey partitionKey)
{
return this.container.CreateTransactionalBatch(partitionKey);
return new EncryptionTransactionalBatch(
this.container.CreateTransactionalBatch(partitionKey),
this.encryptor,
this.cosmosSerializer);
}

public override Task<ContainerResponse> DeleteContainerAsync(
Expand Down
23 changes: 11 additions & 12 deletions Microsoft.Azure.Cosmos.Encryption/src/EncryptionFeedIterator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,25 +29,24 @@ public EncryptionFeedIterator(

public override bool HasMoreResults => this.feedIterator.HasMoreResults;

public async override Task<ResponseMessage> ReadNextAsync(CancellationToken cancellationToken = default)
public override async Task<ResponseMessage> ReadNextAsync(CancellationToken cancellationToken = default)
{
CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(options: null);
using (diagnosticsContext.CreateScope("FeedIterator.ReadNext"))
{
using (ResponseMessage responseMessage = await this.feedIterator.ReadNextAsync(cancellationToken))
ResponseMessage responseMessage = await this.feedIterator.ReadNextAsync(cancellationToken);

if (responseMessage.IsSuccessStatusCode && responseMessage.Content != null)
{
if (responseMessage.IsSuccessStatusCode && responseMessage.Content != null)
{
Stream decryptedContent = await this.DeserializeAndDecryptResponseAsync(
responseMessage.Content,
diagnosticsContext,
cancellationToken);
Stream decryptedContent = await this.DeserializeAndDecryptResponseAsync(
responseMessage.Content,
diagnosticsContext,
cancellationToken);

return new DecryptedResponseMessage(responseMessage, decryptedContent);
}

return responseMessage;
return new DecryptedResponseMessage(responseMessage, decryptedContent);
}

return responseMessage;
}
}

Expand Down
12 changes: 12 additions & 0 deletions Microsoft.Azure.Cosmos.Encryption/src/EncryptionProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ internal static class EncryptionProcessor
{
internal static readonly CosmosJsonDotNetSerializer baseSerializer = new CosmosJsonDotNetSerializer();

/// <remarks>
/// If there isn't any PathsToEncrypt, input stream will be returned without any modification.
/// Else input stream will be disposed, and a new stream is returned.
/// In case of an exception, input stream won't be disposed, but position will be end of stream.
/// </remarks>
public static async Task<Stream> EncryptAsync(
Stream input,
Encryptor encryptor,
Expand Down Expand Up @@ -98,9 +103,15 @@ public static async Task<Stream> EncryptAsync(
encryptedData: cipherText);

itemJObj.Add(Constants.EncryptedInfo, JObject.FromObject(encryptionProperties));
input.Dispose();
return EncryptionProcessor.baseSerializer.ToStream(itemJObj);
}

/// <remarks>
/// If there isn't any data that needs to be decrypted, input stream will be returned without any modification.
/// Else input stream will be disposed, and a new stream is returned.
/// In case of an exception, input stream won't be disposed, but position will be end of stream.
/// </remarks>
public static async Task<Stream> DecryptAsync(
Stream input,
Encryptor encryptor,
Expand Down Expand Up @@ -148,6 +159,7 @@ public static async Task<Stream> DecryptAsync(
}

itemJObj.Remove(Constants.EncryptedInfo);
input.Dispose();
return EncryptionProcessor.baseSerializer.ToStream(itemJObj);
}

Expand Down
229 changes: 229 additions & 0 deletions Microsoft.Azure.Cosmos.Encryption/src/EncryptionTransactionalBatch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------

namespace Microsoft.Azure.Cosmos.Encryption
{
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos;

internal sealed class EncryptionTransactionalBatch : TransactionalBatch
{
private readonly Encryptor encryptor;
private readonly CosmosSerializer cosmosSerializer;
private TransactionalBatch transactionalBatch;

public EncryptionTransactionalBatch(
TransactionalBatch transactionalBatch,
Encryptor encryptor,
CosmosSerializer cosmosSerializer)
{
this.transactionalBatch = transactionalBatch ?? throw new ArgumentNullException(nameof(transactionalBatch));
this.encryptor = encryptor ?? throw new ArgumentNullException(nameof(encryptor));
this.cosmosSerializer = cosmosSerializer ?? throw new ArgumentNullException(nameof(cosmosSerializer));
}

public override TransactionalBatch CreateItem<T>(
T item,
TransactionalBatchItemRequestOptions requestOptions = null)
{
if (!(requestOptions is EncryptionTransactionalBatchItemRequestOptions encryptionItemRequestOptions) ||
encryptionItemRequestOptions.EncryptionOptions == null)
{
this.transactionalBatch = this.transactionalBatch.CreateItem(
item,
requestOptions);

return this;
}

using (Stream itemStream = this.cosmosSerializer.ToStream<T>(item))
{
return this.CreateItemStream(
itemStream,
requestOptions);
}
}

public override TransactionalBatch CreateItemStream(
Stream streamPayload,
TransactionalBatchItemRequestOptions requestOptions = null)
{
if (requestOptions is EncryptionTransactionalBatchItemRequestOptions encryptionItemRequestOptions &&
encryptionItemRequestOptions.EncryptionOptions != null)
{
CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(requestOptions);
using (diagnosticsContext.CreateScope("EncryptItemStream"))
{
streamPayload = EncryptionProcessor.EncryptAsync(
streamPayload,
this.encryptor,
encryptionItemRequestOptions.EncryptionOptions,
diagnosticsContext,
cancellationToken: default).Result;
}
}

this.transactionalBatch = this.transactionalBatch.CreateItemStream(
streamPayload,
requestOptions);

return this;
}

public override TransactionalBatch DeleteItem(
string id,
TransactionalBatchItemRequestOptions requestOptions = null)
{
this.transactionalBatch = this.transactionalBatch.DeleteItem(
id,
requestOptions);

return this;
}

public override TransactionalBatch ReadItem(
string id,
TransactionalBatchItemRequestOptions requestOptions = null)
{
this.transactionalBatch = this.transactionalBatch.ReadItem(
id,
requestOptions);

return this;
}

public override TransactionalBatch ReplaceItem<T>(
string id,
T item,
TransactionalBatchItemRequestOptions requestOptions = null)
{
if (!(requestOptions is EncryptionTransactionalBatchItemRequestOptions encryptionItemRequestOptions) ||
encryptionItemRequestOptions.EncryptionOptions == null)
{
this.transactionalBatch = this.transactionalBatch.ReplaceItem(
id,
item,
requestOptions);

return this;
}

using (Stream itemStream = this.cosmosSerializer.ToStream<T>(item))
{
return this.ReplaceItemStream(
id,
itemStream,
requestOptions);
}
}

public override TransactionalBatch ReplaceItemStream(
string id,
Stream streamPayload,
TransactionalBatchItemRequestOptions requestOptions = null)
{
if (requestOptions is EncryptionTransactionalBatchItemRequestOptions encryptionItemRequestOptions &&
encryptionItemRequestOptions.EncryptionOptions != null)
{
CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(requestOptions);
using (diagnosticsContext.CreateScope("EncryptItemStream"))
{
streamPayload = EncryptionProcessor.EncryptAsync(
streamPayload,
this.encryptor,
encryptionItemRequestOptions.EncryptionOptions,
diagnosticsContext,
cancellationToken: default).Result;
}
}

this.transactionalBatch = this.transactionalBatch.ReplaceItemStream(
id,
streamPayload,
requestOptions);

return this;
}

public override TransactionalBatch UpsertItem<T>(
T item,
TransactionalBatchItemRequestOptions requestOptions = null)
{
if (!(requestOptions is EncryptionTransactionalBatchItemRequestOptions encryptionItemRequestOptions) ||
encryptionItemRequestOptions.EncryptionOptions == null)
{
this.transactionalBatch = this.transactionalBatch.UpsertItem(
item,
requestOptions);

return this;
}

using (Stream itemStream = this.cosmosSerializer.ToStream<T>(item))
{
return this.UpsertItemStream(
itemStream,
requestOptions);
}
}

public override TransactionalBatch UpsertItemStream(
Stream streamPayload,
TransactionalBatchItemRequestOptions requestOptions = null)
{
if (requestOptions is EncryptionTransactionalBatchItemRequestOptions encryptionItemRequestOptions &&
encryptionItemRequestOptions.EncryptionOptions != null)
{
CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(requestOptions);
using (diagnosticsContext.CreateScope("EncryptItemStream"))
{
streamPayload = EncryptionProcessor.EncryptAsync(
streamPayload,
this.encryptor,
encryptionItemRequestOptions.EncryptionOptions,
diagnosticsContext,
cancellationToken: default).Result;
}
}

this.transactionalBatch = this.transactionalBatch.UpsertItemStream(
streamPayload,
requestOptions);

return this;
}

public override async Task<TransactionalBatchResponse> ExecuteAsync(
CancellationToken cancellationToken = default)
{
CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(options: null);
using (diagnosticsContext.CreateScope("TransactionalBatch.ExecuteAsync"))
{
TransactionalBatchResponse response = await this.transactionalBatch.ExecuteAsync(cancellationToken);

if (response.IsSuccessStatusCode)
{
for (int index = 0; index < response.Count; index++)
{
TransactionalBatchOperationResult result = response[index];

if (result.ResourceStream != null)
{
result.ResourceStream = await EncryptionProcessor.DecryptAsync(
result.ResourceStream,
this.encryptor,
diagnosticsContext,
cancellationToken);
}
}
}

return response;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.9.0-preview3" />
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.9.1-preview" />
</ItemGroup>

<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ public async Task EncryptionTransactionBatchCrud()

TestDoc docToDelete = await EncryptionTests.CreateItemAsync(EncryptionTests.encryptionContainer, dek1, TestDoc.PathsToEncrypt, partitionKey);

TransactionalBatchResponse batchResponse = await EncryptionTests.itemContainer.CreateTransactionalBatch(new Cosmos.PartitionKey(partitionKey))
TransactionalBatchResponse batchResponse = await EncryptionTests.encryptionContainer.CreateTransactionalBatch(new Cosmos.PartitionKey(partitionKey))
.CreateItem(doc1ToCreate, EncryptionTests.GetBatchItemRequestOptions(dek1, TestDoc.PathsToEncrypt))
.CreateItemStream(doc2ToCreate.ToStream(), EncryptionTests.GetBatchItemRequestOptions(dek2, TestDoc.PathsToEncrypt))
.ReplaceItem(doc1ToReplace.Id, doc1ToReplace, EncryptionTests.GetBatchItemRequestOptions(dek2, TestDoc.PathsToEncrypt, doc1ToReplaceCreateResponse.ETag))
Expand All @@ -600,8 +600,30 @@ public async Task EncryptionTransactionBatchCrud()
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.encryptionContainer, doc1ToUpsert);
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.encryptionContainer, doc2ToUpsert);

ResponseMessage readResponseMessage = await EncryptionTests.itemContainer.ReadItemStreamAsync(docToDelete.Id, new PartitionKey(docToDelete.PK));
ResponseMessage readResponseMessage = await EncryptionTests.encryptionContainer.ReadItemStreamAsync(docToDelete.Id, new PartitionKey(docToDelete.PK));
Assert.AreEqual(HttpStatusCode.NotFound, readResponseMessage.StatusCode);

// Validate that the documents are encrypted as expected by trying to retrieve through regular (non-encryption) container
doc1ToCreate.Sensitive = null;
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.itemContainer, doc1ToCreate);

doc2ToCreate.Sensitive = null;
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.itemContainer, doc2ToCreate);

// doc3ToCreate wasn't encrypted
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.itemContainer, doc3ToCreate);

doc1ToReplace.Sensitive = null;
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.itemContainer, doc1ToReplace);

doc2ToReplace.Sensitive = null;
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.itemContainer, doc2ToReplace);

doc1ToUpsert.Sensitive = null;
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.itemContainer, doc1ToUpsert);

doc2ToUpsert.Sensitive = null;
await EncryptionTests.VerifyItemByReadAsync(EncryptionTests.itemContainer, doc2ToUpsert);
}

private static async Task ValidateSprocResultsAsync(Container container, TestDoc expectedDoc)
Expand Down
2 changes: 1 addition & 1 deletion Microsoft.Azure.Cosmos/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<ClientOfficialVersion>3.9.1</ClientOfficialVersion>
<ClientPreviewVersion>3.9.1</ClientPreviewVersion>
<DirectVersion>3.10.0</DirectVersion>
<EncryptionVersion>1.0.0-preview3</EncryptionVersion>
<EncryptionVersion>1.0.0-preview4</EncryptionVersion>
<HybridRowVersion>1.0.0-preview</HybridRowVersion>
<AboveDirBuildProps>$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))</AboveDirBuildProps>
</PropertyGroup>
Expand Down
4 changes: 2 additions & 2 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

Query: Add optimization to access the stream buffer
- [#1578](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/1578) Query: Add optimization to access the stream buffer

### Fixed

ApplicationRegion: Fix ApplicationRegion to ensure the correct order is being used for failover scenarios
- [#1578](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/1578) ApplicationRegion: Fix ApplicationRegion to ensure the correct order is being used for failover scenarios

## <a name="3.9.1"/> [3.9.1](https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.9.1) - 2020-05-19
## <a name="3.9.1-preview"/> [3.9.1-preview](https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.9.1-preview) - 2020-05-19
Expand Down

0 comments on commit cc395dc

Please sign in to comment.