diff --git a/eng/jacoco-test-coverage/pom.xml b/eng/jacoco-test-coverage/pom.xml
index 7424f244fe62b..314cab44db83a 100644
--- a/eng/jacoco-test-coverage/pom.xml
+++ b/eng/jacoco-test-coverage/pom.xml
@@ -188,7 +188,7 @@
com.azure
azure-cosmos-encryption
- 1.13.0-beta.1
+ 2.0.0-beta.1
com.azure
diff --git a/eng/versioning/version_client.txt b/eng/versioning/version_client.txt
index 041c912139880..62996fe3e5ea4 100644
--- a/eng/versioning/version_client.txt
+++ b/eng/versioning/version_client.txt
@@ -97,7 +97,7 @@ com.azure.cosmos.spark:azure-cosmos-spark_3_2-12;1.0.0-beta.1;1.0.0-beta.1
com.azure.cosmos.spark:azure-cosmos-spark_3-1_2-12;4.18.1;4.19.0-beta.1
com.azure.cosmos.spark:azure-cosmos-spark_3-2_2-12;4.18.1;4.19.0-beta.1
com.azure.cosmos.spark:azure-cosmos-spark_3-3_2-12;4.18.1;4.19.0-beta.1
-com.azure:azure-cosmos-encryption;1.12.0;1.13.0-beta.1
+com.azure:azure-cosmos-encryption;1.12.0;2.0.0-beta.1
com.azure:azure-cosmos-test;1.0.0-beta.2;1.0.0-beta.3
com.azure:azure-cosmos-tests;1.0.0-beta.1;1.0.0-beta.1
com.azure:azure-data-appconfiguration;1.4.3;1.5.0-beta.1
diff --git a/sdk/cosmos/azure-cosmos-benchmark/pom.xml b/sdk/cosmos/azure-cosmos-benchmark/pom.xml
index 85c5a49472bd8..f6c7ef4cc3fdf 100644
--- a/sdk/cosmos/azure-cosmos-benchmark/pom.xml
+++ b/sdk/cosmos/azure-cosmos-benchmark/pom.xml
@@ -57,7 +57,7 @@ Licensed under the MIT License.
com.azure
azure-cosmos-encryption
- 1.13.0-beta.1
+ 2.0.0-beta.1
diff --git a/sdk/cosmos/azure-cosmos-encryption/CHANGELOG.md b/sdk/cosmos/azure-cosmos-encryption/CHANGELOG.md
index 5a574d32aac98..bba039889c08b 100644
--- a/sdk/cosmos/azure-cosmos-encryption/CHANGELOG.md
+++ b/sdk/cosmos/azure-cosmos-encryption/CHANGELOG.md
@@ -1,14 +1,12 @@
## Release History
-### 1.13.0-beta.1 (Unreleased)
+### 2.0.0-beta.1 (Unreleased)
#### Features Added
+* Added support for allowing partition key path and id to be part of client encryption policy - See [PR 33648](https://github.com/Azure/azure-sdk-for-java/pull/33648)
#### Breaking Changes
-
-#### Bugs Fixed
-
-#### Other Changes
+* Adds support for ParititonKey and Id encryption, when the PolicyFormatVersion is set to 2 - See [PR 33648](https://github.com/Azure/azure-sdk-for-java/pull/33648)
### 1.12.0 (2023-03-17)
diff --git a/sdk/cosmos/azure-cosmos-encryption/pom.xml b/sdk/cosmos/azure-cosmos-encryption/pom.xml
index 3f3b1cca5d213..7638446995187 100644
--- a/sdk/cosmos/azure-cosmos-encryption/pom.xml
+++ b/sdk/cosmos/azure-cosmos-encryption/pom.xml
@@ -13,7 +13,7 @@ Licensed under the MIT License.
com.azure
azure-cosmos-encryption
- 1.13.0-beta.1
+ 2.0.0-beta.1
Encryption Plugin for Azure Cosmos DB SDK
This Package contains Encryption Plugin for Microsoft Azure Cosmos SDK
jar
@@ -48,6 +48,7 @@ Licensed under the MIT License.
--add-opens com.azure.cosmos.encryption/com.azure.cosmos.encryption.keyprovider=ALL-UNNAMED
--add-opens com.azure.cosmos.encryption/com.azure.cosmos.encryption.util=ALL-UNNAMED
--add-opens com.azure.cosmos.encryption/com.azure.cosmos.encryption.models=ALL-UNNAMED
+ --add-opens com.azure.cosmos/com.azure.cosmos.implementation=ALL-UNNAMED
diff --git a/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/CosmosEncryptionAsyncClient.java b/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/CosmosEncryptionAsyncClient.java
index 9de68048575ff..aa42d6639164b 100644
--- a/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/CosmosEncryptionAsyncClient.java
+++ b/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/CosmosEncryptionAsyncClient.java
@@ -222,7 +222,7 @@ private CosmosContainerProperties getContainerPropertiesWithVersionValidation(Co
throw new IllegalArgumentException("Container without client encryption policy cannot be used");
}
- if (cosmosContainerResponse.getProperties().getClientEncryptionPolicy().getPolicyFormatVersion() > 1) {
+ if (cosmosContainerResponse.getProperties().getClientEncryptionPolicy().getPolicyFormatVersion() > 2) {
throw new UnsupportedOperationException("This version of the Encryption library cannot be used with this " +
"container. Please upgrade to the latest version of the same.");
}
diff --git a/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/CosmosEncryptionAsyncContainer.java b/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/CosmosEncryptionAsyncContainer.java
index 353bfc54dc56f..f6a4c84142f0a 100644
--- a/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/CosmosEncryptionAsyncContainer.java
+++ b/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/CosmosEncryptionAsyncContainer.java
@@ -10,7 +10,11 @@
import com.azure.cosmos.encryption.implementation.Constants;
import com.azure.cosmos.encryption.implementation.CosmosResponseFactory;
import com.azure.cosmos.encryption.implementation.EncryptionImplementationBridgeHelpers;
+import com.azure.cosmos.encryption.implementation.EncryptionProcessor;
+import com.azure.cosmos.encryption.implementation.EncryptionSettings;
import com.azure.cosmos.encryption.implementation.EncryptionUtils;
+import com.azure.cosmos.encryption.implementation.mdesrc.cryptography.MicrosoftDataEncryptionException;
+import com.azure.cosmos.encryption.models.SqlQuerySpecWithEncryption;
import com.azure.cosmos.implementation.CosmosPagedFluxOptions;
import com.azure.cosmos.implementation.HttpConstants;
import com.azure.cosmos.implementation.ImplementationBridgeHelpers;
@@ -39,22 +43,26 @@
import com.azure.cosmos.models.FeedResponse;
import com.azure.cosmos.models.ModelBridgeInternal;
import com.azure.cosmos.models.PartitionKey;
+import com.azure.cosmos.models.PartitionKeyBuilder;
import com.azure.cosmos.models.SqlParameter;
import com.azure.cosmos.models.SqlQuerySpec;
import com.azure.cosmos.util.CosmosPagedFlux;
import com.azure.cosmos.util.UtilBridgeInternal;
-import com.azure.cosmos.encryption.implementation.EncryptionProcessor;
-import com.azure.cosmos.encryption.models.SqlQuerySpecWithEncryption;
+import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;
+import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -131,7 +139,7 @@ public Mono> createItem(T item,
requestOptions = new CosmosItemRequestOptions();
}
byte[] streamPayload = cosmosSerializerToStream(item);
- return createItemHelper(streamPayload, null, requestOptions,(Class) item.getClass(), false );
+ return createItemHelper(streamPayload, requestOptions,(Class) item.getClass(), false );
}
@@ -193,7 +201,93 @@ public Mono> deleteItem(String itemId,
PartitionKey partitionKey,
CosmosItemRequestOptions requestOptions) {
- return container.deleteItem(itemId, partitionKey, requestOptions);
+ return deleteItemInternal(itemId, partitionKey, requestOptions);
+ }
+
+ private Mono> deleteItemInternal(String itemId, PartitionKey partitionKey, CosmosItemRequestOptions requestOptions) {
+ this.encryptionProcessor.initEncryptionSettingsIfNotInitializedAsync();
+ return Mono.just(this.encryptionProcessor.getEncryptionSettings())
+ .flatMap(settings -> {
+ try {
+ return Mono.zip(
+ checkAndGetEncryptedId(itemId, settings),
+ checkAndGetEncryptedPartitionKey(partitionKey, settings)
+ ).flatMap(encryptedIdPartitionTuple -> container.deleteItem(encryptedIdPartitionTuple.getT1(), encryptedIdPartitionTuple.getT2(), requestOptions));
+ } catch (Exception ex) {
+ return Mono.error(ex);
+ }
+ });
+ }
+
+ private Mono checkAndGetEncryptedId(String itemId, EncryptionSettings encryptionSettings)
+ {
+ if (this.encryptionProcessor.getClientEncryptionPolicy().getIncludedPaths().stream().
+ anyMatch(includedPath -> includedPath.getPath().substring(1).equals(Constants.PROPERTY_NAME_ID))) {
+ return this.getEncryptedItem(encryptionSettings, Constants.PROPERTY_NAME_ID, itemId);
+ }
+ return Mono.just(itemId);
+ }
+
+ private Mono checkAndGetEncryptedPartitionKey(PartitionKey partitionKey, EncryptionSettings encryptionSettings) {
+ if (encryptionSettings.getPartitionKeyPaths().isEmpty()) {
+ return Mono.just(partitionKey);
+ }
+
+ JsonNode partitionKeyNode;
+ try {
+ partitionKeyNode = EncryptionUtils.getSimpleObjectMapper().readTree(partitionKey.toString());
+ } catch (JsonProcessingException ex) {
+ return Mono.error(ex);
+ }
+
+ if (partitionKeyNode.isArray() && partitionKeyNode.size() > 1) {
+ ArrayNode arrayNode = (ArrayNode) partitionKeyNode;
+
+ return Mono.just(new PartitionKeyBuilder())
+ .flatMap(partitionKeyBuilder -> Flux.fromIterable(encryptionSettings.getPartitionKeyPaths())
+ .flatMap(path -> {
+ // case: partition key path is /a/b/c and the client encryption policy has /a in path.
+ // hence encrypt the partition key value with using its top level path /a since
+ // /c would have been encrypted in the document using /a's policy.
+ String partitionKeyPath = path.split("/")[1];
+
+ String childPartitionKey = arrayNode.elements().next().textValue();
+ if (this.encryptionProcessor.getClientEncryptionPolicy().getIncludedPaths().stream().
+ anyMatch(includedPath -> includedPath.getPath().substring(1).equals(partitionKeyPath))) {
+ partitionKeyBuilder.add(childPartitionKey);
+ return Mono.empty();
+ }
+ return getEncryptedItem(encryptionSettings, partitionKeyPath, childPartitionKey);
+ })
+ .collectList()
+ .flatMapMany(Flux::fromIterable)
+ .doOnNext(partitionKeyBuilder::add)
+ .then(Mono.just(partitionKeyBuilder.build())));
+ } else {
+ return Mono.just(encryptionSettings.getPartitionKeyPaths().get(0))
+ .flatMap(path -> {
+ String partitionKeyPath = path.split("/")[1];
+ if (this.encryptionProcessor.getClientEncryptionPolicy().getIncludedPaths().stream().
+ noneMatch(includedPath -> includedPath.getPath().substring(1).equals(partitionKeyPath))) {
+ return Mono.just(partitionKeyNode.elements().next().textValue());
+ }
+ return getEncryptedItem(encryptionSettings, partitionKeyPath, partitionKeyNode.elements().next().textValue());
+ })
+ .flatMap(encryptedPartitionKey -> Mono.just(new PartitionKey(encryptedPartitionKey)));
+ }
+ }
+
+ private Mono getEncryptedItem(EncryptionSettings encryptionSettings, String propertyName, String propertyValue) {
+ return encryptionSettings
+ .getEncryptionSettingForPropertyAsync(propertyName, this.encryptionProcessor)
+ .flatMap(settings -> {
+ try {
+ return Mono.just(
+ this.encryptionProcessor.encryptAndSerializeValue(settings, propertyValue, propertyName));
+ } catch (MicrosoftDataEncryptionException ex) {
+ return Mono.error(ex);
+ }
+ });
}
/**
@@ -225,10 +319,17 @@ public Mono> deleteItem(T item, CosmosItemRequest
*/
// TODO Make this api public once it is GA in cosmos core library
Mono> deleteAllItemsByPartitionKey(PartitionKey partitionKey, CosmosItemRequestOptions requestOptions) {
- if (requestOptions == null) {
- requestOptions = new CosmosItemRequestOptions();
- }
- return container.deleteAllItemsByPartitionKey(partitionKey, requestOptions);
+ final CosmosItemRequestOptions options = Optional.ofNullable(requestOptions)
+ .orElse(new CosmosItemRequestOptions());
+
+ return deleteAllItemsByPartitionKeyInternal(partitionKey, options);
+ }
+
+ private Mono> deleteAllItemsByPartitionKeyInternal(PartitionKey partitionKey, CosmosItemRequestOptions requestOptions) {
+ return this.encryptionProcessor.initEncryptionSettingsIfNotInitializedAsync()
+ .thenReturn(this.encryptionProcessor.getEncryptionSettings())
+ .flatMap(encryptedSettings -> checkAndGetEncryptedPartitionKey(partitionKey, encryptedSettings))
+ .flatMap(encryptedPartitionKey -> container.deleteAllItemsByPartitionKey(encryptedPartitionKey, requestOptions));
}
/**
@@ -267,7 +368,7 @@ public Mono> upsertItem(T item, CosmosItemRequestOptio
}
byte[] streamPayload = cosmosSerializerToStream(item);
- return upsertItemHelper(streamPayload, null, requestOptions, (Class) item.getClass(), false);
+ return upsertItemHelper(streamPayload, requestOptions, (Class) item.getClass(), false);
}
/**
@@ -380,11 +481,10 @@ public Mono> readItem(String id,
PartitionKey partitionKey,
CosmosItemRequestOptions requestOptions,
Class classType) {
- if (requestOptions == null) {
- requestOptions = new CosmosItemRequestOptions();
- }
+ final CosmosItemRequestOptions options = Optional.ofNullable(requestOptions)
+ .orElse(new CosmosItemRequestOptions());
- Mono> responseMessageMono = this.readItemHelper(id, partitionKey, requestOptions, false);
+ Mono> responseMessageMono = this.readItemHelper(id, partitionKey, options, false);
return responseMessageMono.publishOn(encryptionScheduler).flatMap(cosmosItemResponse -> setByteArrayContent(cosmosItemResponse,
this.encryptionProcessor.decrypt(cosmosItemResponseBuilderAccessor.getByteArrayContent(cosmosItemResponse)))
@@ -554,11 +654,10 @@ public Mono> patchItem(
checkNotNull(partitionKey, "expected non-null partitionKey for patchItem");
checkNotNull(cosmosPatchOperations, "expected non-null cosmosPatchOperations");
- if (options == null) {
- options = new CosmosPatchItemRequestOptions();
- }
+ final CosmosPatchItemRequestOptions patchOptions = Optional.ofNullable(options)
+ .orElse(new CosmosPatchItemRequestOptions());
- return patchItemHelper(itemId, partitionKey, cosmosPatchOperations, options, itemType);
+ return patchItemHelper(itemId, partitionKey, cosmosPatchOperations, patchOptions, itemType);
}
private Mono> patchItemHelper(String itemId,
@@ -623,11 +722,17 @@ private Mono> patchItemInternalHelper(String itemId,
boolean isRetry) {
setRequestHeaders(requestOptions);
- return this.container.patchItem(itemId, partitionKey, encryptedCosmosPatchOperations, requestOptions, itemType).publishOn(encryptionScheduler).
- flatMap(cosmosItemResponse -> setByteArrayContent((CosmosItemResponse) cosmosItemResponse,
- this.encryptionProcessor.decrypt(cosmosItemResponseBuilderAccessor.getByteArrayContent((CosmosItemResponse) cosmosItemResponse)))
- .map(bytes -> this.responseFactory.createItemResponse((CosmosItemResponse) cosmosItemResponse,
- itemType))).onErrorResume(exception -> {
+ return this.encryptionProcessor.initEncryptionSettingsIfNotInitializedAsync()
+ .thenReturn(this.encryptionProcessor.getEncryptionSettings())
+ .flatMap(encryptionSettings -> Mono.zip(
+ checkAndGetEncryptedId(itemId, encryptionSettings),
+ checkAndGetEncryptedPartitionKey(partitionKey, encryptionSettings))
+ .flatMap(encryptedIdPartitionKeyTuple ->
+ this.container.patchItem(encryptedIdPartitionKeyTuple.getT1(), encryptedIdPartitionKeyTuple.getT2(), encryptedCosmosPatchOperations, requestOptions, itemType).publishOn(encryptionScheduler).
+ flatMap(cosmosItemResponse -> setByteArrayContent((CosmosItemResponse) cosmosItemResponse,
+ this.encryptionProcessor.decrypt(cosmosItemResponseBuilderAccessor.getByteArrayContent((CosmosItemResponse) cosmosItemResponse)))
+ .map(bytes -> this.responseFactory.createItemResponse((CosmosItemResponse) cosmosItemResponse,
+ itemType))).onErrorResume(exception -> {
if (!isRetry && exception instanceof CosmosException) {
final CosmosException cosmosException = (CosmosException) exception;
if (isIncorrectContainerRid(cosmosException)) {
@@ -637,7 +742,7 @@ private Mono> patchItemInternalHelper(String itemId,
}
}
return Mono.error(exception);
- });
+ })));
}
/**
@@ -720,10 +825,16 @@ private Mono> readItemHelper(String id,
CosmosItemRequestOptions requestOptions,
boolean isRetry) {
this.setRequestHeaders(requestOptions);
- Mono> responseMessageMono = this.container.readItem(
- id,
- partitionKey,
- requestOptions, byte[].class);
+ Mono> responseMessageMono = this.encryptionProcessor.initEncryptionSettingsIfNotInitializedAsync()
+ .thenReturn(this.encryptionProcessor.getEncryptionSettings())
+ .flatMap(encryptionSettings -> Mono.zip(
+ checkAndGetEncryptedId(id, encryptionSettings),
+ checkAndGetEncryptedPartitionKey(partitionKey, encryptionSettings))
+ .flatMap(encryptedIdPartitionKeyTuple ->
+ this.container.readItem(
+ encryptedIdPartitionKeyTuple.getT1(),
+ encryptedIdPartitionKeyTuple.getT2(),
+ requestOptions, byte[].class)));
return responseMessageMono.onErrorResume(exception -> {
if (!isRetry && exception instanceof CosmosException) {
final CosmosException cosmosException = (CosmosException) exception;
@@ -738,15 +849,13 @@ private Mono> readItemHelper(String id,
}
private Mono> createItemHelper(byte[] streamPayload,
- PartitionKey partitionKey,
CosmosItemRequestOptions requestOptions,
Class itemClass,
boolean isRetry) {
this.setRequestHeaders(requestOptions);
return this.encryptionProcessor.encrypt(streamPayload)
- .flatMap(encryptedPayload -> createItemHelper(
+ .flatMap(encryptedPayload -> this.container.createItem(
encryptedPayload,
- partitionKey,
requestOptions)
.publishOn(encryptionScheduler)
.flatMap(cosmosItemResponse -> setByteArrayContent(cosmosItemResponse,
@@ -758,7 +867,7 @@ private Mono> createItemHelper(byte[] streamPayload,
if (isIncorrectContainerRid(cosmosException)) {
this.encryptionProcessor.getIsEncryptionSettingsInitDone().set(false);
return this.encryptionProcessor.initializeEncryptionSettingsAsync(true).then
- (Mono.defer(() -> createItemHelper(streamPayload, partitionKey, requestOptions,
+ (Mono.defer(() -> createItemHelper(streamPayload, requestOptions,
itemClass, true)));
}
}
@@ -766,24 +875,53 @@ private Mono> createItemHelper(byte[] streamPayload,
}));
}
- private Mono> createItemHelper(byte[] encryptedPayload,
- PartitionKey partitionKey,
- CosmosItemRequestOptions requestOptions) {
- return partitionKey != null
- ? this.container.createItem(encryptedPayload, partitionKey, requestOptions)
- : this.container.createItem(encryptedPayload, requestOptions);
+ private Mono> createItemHelper(byte[] streamPayload,
+ PartitionKey partitionKey,
+ CosmosItemRequestOptions requestOptions,
+ Class itemClass,
+ boolean isRetry) {
+ this.setRequestHeaders(requestOptions);
+ AtomicReference encryptedPK = new AtomicReference<>();
+ Mono encryptedPayloadMono =
+ this.encryptionProcessor.initEncryptionSettingsIfNotInitializedAsync()
+ .thenReturn(this.encryptionProcessor.getEncryptionSettings())
+ .flatMap(encryptionSettings -> checkAndGetEncryptedPartitionKey(partitionKey, encryptionSettings))
+ .flatMap(encryptedPartitionKey -> {
+ encryptedPK.set(encryptedPartitionKey);
+ return this.encryptionProcessor.encrypt(streamPayload);
+ });
+
+ return encryptedPayloadMono
+ .flatMap(encryptedPayload -> this.container.createItem(
+ encryptedPayload,
+ encryptedPK.get(),
+ requestOptions)
+ .publishOn(encryptionScheduler)
+ .flatMap(cosmosItemResponse -> setByteArrayContent(cosmosItemResponse,
+ this.encryptionProcessor.decrypt(cosmosItemResponseBuilderAccessor.getByteArrayContent(cosmosItemResponse)))
+ .map(bytes -> this.responseFactory.createItemResponse(cosmosItemResponse,
+ itemClass))).onErrorResume(exception -> {
+ if (!isRetry && exception instanceof CosmosException) {
+ final CosmosException cosmosException = (CosmosException) exception;
+ if (isIncorrectContainerRid(cosmosException)) {
+ this.encryptionProcessor.getIsEncryptionSettingsInitDone().set(false);
+ return this.encryptionProcessor.initializeEncryptionSettingsAsync(true).then
+ (Mono.defer(() -> createItemHelper(streamPayload, partitionKey, requestOptions,
+ itemClass, true)));
+ }
+ }
+ return Mono.error(exception);
+ }));
}
private Mono> upsertItemHelper(byte[] streamPayload,
- PartitionKey partitionKey,
CosmosItemRequestOptions requestOptions,
Class itemClass,
boolean isRetry) {
this.setRequestHeaders(requestOptions);
return this.encryptionProcessor.encrypt(streamPayload)
- .flatMap(encryptedPayload -> upsertItemHelper(
+ .flatMap(encryptedPayload -> this.container.upsertItem(
encryptedPayload,
- partitionKey,
requestOptions)
.publishOn(encryptionScheduler)
.flatMap(cosmosItemResponse -> setByteArrayContent(cosmosItemResponse,
@@ -795,7 +933,7 @@ private Mono> upsertItemHelper(byte[] streamPayload,
if (isIncorrectContainerRid(cosmosException)) {
this.encryptionProcessor.getIsEncryptionSettingsInitDone().set(false);
return this.encryptionProcessor.initializeEncryptionSettingsAsync(true).then
- (Mono.defer(() -> upsertItemHelper(streamPayload, partitionKey, requestOptions,
+ (Mono.defer(() -> upsertItemHelper(streamPayload, requestOptions,
itemClass, true)));
}
}
@@ -803,12 +941,42 @@ private Mono> upsertItemHelper(byte[] streamPayload,
}));
}
- private Mono> upsertItemHelper(byte[] encryptedPayload,
+ private Mono> upsertItemHelper(byte[] streamPayload,
PartitionKey partitionKey,
- CosmosItemRequestOptions requestOptions) {
- return partitionKey != null
- ? this.container.upsertItem(encryptedPayload, partitionKey, requestOptions)
- : this.container.upsertItem(encryptedPayload, requestOptions);
+ CosmosItemRequestOptions requestOptions,
+ Class itemClass,
+ boolean isRetry) {
+ this.setRequestHeaders(requestOptions);
+ AtomicReference encryptedPK = new AtomicReference<>();
+ Mono encryptedPayloadMono = this.encryptionProcessor.initEncryptionSettingsIfNotInitializedAsync()
+ .thenReturn(this.encryptionProcessor.getEncryptionSettings())
+ .flatMap(encryptionSettings -> checkAndGetEncryptedPartitionKey(partitionKey, encryptionSettings))
+ .flatMap(encryptedPartitionKey -> {
+ encryptedPK.set(encryptedPartitionKey);
+ return this.encryptionProcessor.encrypt(streamPayload);
+ });
+
+ return encryptedPayloadMono
+ .flatMap(encryptedPayload -> this.container.upsertItem(
+ encryptedPayload,
+ encryptedPK.get(),
+ requestOptions)
+ .publishOn(encryptionScheduler)
+ .flatMap(cosmosItemResponse -> setByteArrayContent(cosmosItemResponse,
+ this.encryptionProcessor.decrypt(cosmosItemResponseBuilderAccessor.getByteArrayContent(cosmosItemResponse)))
+ .map(bytes -> this.responseFactory.createItemResponse(cosmosItemResponse, itemClass)))
+ .onErrorResume(exception -> {
+ if (!isRetry && exception instanceof CosmosException) {
+ final CosmosException cosmosException = (CosmosException) exception;
+ if (isIncorrectContainerRid(cosmosException)) {
+ this.encryptionProcessor.getIsEncryptionSettingsInitDone().set(false);
+ return this.encryptionProcessor.initializeEncryptionSettingsAsync(true).then
+ (Mono.defer(() -> upsertItemHelper(streamPayload, partitionKey, requestOptions,
+ itemClass, true)));
+ }
+ }
+ return Mono.error(exception);
+ }));
}
private Mono> replaceItemHelper(byte[] streamPayload,
@@ -818,11 +986,24 @@ private Mono> replaceItemHelper(byte[] streamPayload,
Class itemClass,
boolean isRetry) {
this.setRequestHeaders(requestOptions);
- return this.encryptionProcessor.encrypt(streamPayload)
+ AtomicReference encryptedPK = new AtomicReference<>();
+ AtomicReference encryptedId = new AtomicReference<>();
+ Mono encryptedPayloadMono = this.encryptionProcessor.initEncryptionSettingsIfNotInitializedAsync()
+ .thenReturn(this.encryptionProcessor.getEncryptionSettings())
+ .flatMap(encryptionSettings -> Mono.zip(
+ checkAndGetEncryptedId(itemId, encryptionSettings),
+ checkAndGetEncryptedPartitionKey(partitionKey, encryptionSettings)))
+ .flatMap(encryptedIdPartitionKeyTuple -> {
+ encryptedId.set(encryptedIdPartitionKeyTuple.getT1());
+ encryptedPK.set(encryptedIdPartitionKeyTuple.getT2());
+ return this.encryptionProcessor.encrypt(streamPayload);
+ });
+
+ return encryptedPayloadMono
.flatMap(encryptedPayload -> this.container.replaceItem(
encryptedPayload,
- itemId,
- partitionKey,
+ encryptedId.get(),
+ encryptedPK.get(),
requestOptions)
.publishOn(encryptionScheduler)
.flatMap(cosmosItemResponse -> setByteArrayContent(cosmosItemResponse,
@@ -999,49 +1180,66 @@ public Mono executeCosmosBatch(CosmosBatch cosmosBatch) {
* transactional batch succeeded.
*/
public Mono executeCosmosBatch(CosmosBatch cosmosBatch, CosmosBatchRequestOptions requestOptions) {
- if (requestOptions == null) {
- requestOptions = new CosmosBatchRequestOptions();
- }
+ final CosmosBatchRequestOptions cosmosBatchRequestOptions = Optional.ofNullable(requestOptions)
+ .orElse(new CosmosBatchRequestOptions());
List>> monoList = new ArrayList<>();
for (ItemBatchOperation> itemBatchOperation : cosmosBatchAccessor.getOperationsInternal(cosmosBatch)) {
Mono> itemBatchOperationMono = null;
if (itemBatchOperation.getItem() != null) {
- ObjectNode objectNode =
- EncryptionUtils.getSimpleObjectMapper().valueToTree(itemBatchOperation.getItem());
- itemBatchOperationMono =
- encryptionProcessor.encryptObjectNode(objectNode).map(encryptedItem -> {
- return new ItemBatchOperation<>(
+ itemBatchOperationMono = this.encryptionProcessor.initEncryptionSettingsIfNotInitializedAsync()
+ .thenReturn(this.encryptionProcessor.getEncryptionSettings())
+ .flatMap(encryptionSettings -> {
+ try {
+ Field id = itemBatchOperation.getItem().getClass().getDeclaredField(Constants.PROPERTY_NAME_ID);
+ id.setAccessible(true);
+ return Mono.zip(
+ checkAndGetEncryptedId((String) id.get(itemBatchOperation.getItem()), encryptionSettings),
+ checkAndGetEncryptedPartitionKey(itemBatchOperation.getPartitionKeyValue(), encryptionSettings));
+ } catch (IllegalAccessException | NoSuchFieldException e) {
+ return Mono.error(e);
+ }
+ })
+ .flatMap(encryptedIdPartitionKeyTuple -> {
+ ObjectNode objectNode =
+ EncryptionUtils.getSimpleObjectMapper().valueToTree(itemBatchOperation.getItem());
+ return encryptionProcessor.encryptObjectNode(objectNode).map(encryptedItem -> new ItemBatchOperation<>(
itemBatchOperation.getOperationType(),
- itemBatchOperation.getId(),
- itemBatchOperation.getPartitionKeyValue(),
+ encryptedIdPartitionKeyTuple.getT1(),
+ encryptedIdPartitionKeyTuple.getT2(),
itemBatchOperation.getRequestOptions(),
encryptedItem
- );
+ ));
});
+
} else {
- itemBatchOperationMono =
- Mono.just(
+ itemBatchOperationMono = this.encryptionProcessor.initEncryptionSettingsIfNotInitializedAsync()
+ .thenReturn(this.encryptionProcessor.getEncryptionSettings())
+ .flatMap(encryptionSettings -> {
+ return Mono.zip(
+ checkAndGetEncryptedId(itemBatchOperation.getId(), encryptionSettings),
+ checkAndGetEncryptedPartitionKey(itemBatchOperation.getPartitionKeyValue(), encryptionSettings));
+ })
+ .flatMap(encryptedIdPartitionKeyTuple -> Mono.just(
new ItemBatchOperation<>(
itemBatchOperation.getOperationType(),
- itemBatchOperation.getId(),
- itemBatchOperation.getPartitionKeyValue(),
+ encryptedIdPartitionKeyTuple.getT1(),
+ encryptedIdPartitionKeyTuple.getT2(),
itemBatchOperation.getRequestOptions(),
null
- )
- );
+ )));
}
monoList.add(itemBatchOperationMono);
+
}
Mono>> encryptedOperationListMono =
Flux.mergeSequential(monoList).collectList();
- CosmosBatchRequestOptions finalRequestOptions = requestOptions;
CosmosBatch encryptedCosmosBatch = CosmosBatch.createCosmosBatch(cosmosBatch.getPartitionKeyValue());
return encryptedOperationListMono.flatMap(itemBatchOperations -> {
cosmosBatchAccessor.getOperationsInternal(encryptedCosmosBatch).addAll(itemBatchOperations);
- return executeCosmosBatchHelper(encryptedCosmosBatch, finalRequestOptions, false);
+ return executeCosmosBatchHelper(encryptedCosmosBatch, cosmosBatchRequestOptions, false);
});
}
@@ -1140,37 +1338,53 @@ public Flux> executeBulkOperati
public Flux> executeBulkOperations(
Flux operations,
CosmosBulkExecutionOptions bulkOptions) {
- if (bulkOptions == null) {
- bulkOptions = new CosmosBulkExecutionOptions();
- }
+ final CosmosBulkExecutionOptions cosmosBulkExecutionOptions = Optional.ofNullable(bulkOptions)
+ .orElse(new CosmosBulkExecutionOptions());
- final CosmosBulkExecutionOptions cosmosBulkExecutionOptions = bulkOptions;
Flux operationFlux = operations.flatMap(cosmosItemOperation -> {
- Mono cosmosItemOperationMono;
+ Mono cosmosItemOperationMono = null;
if (cosmosItemOperation.getItem() != null) {
- ObjectNode objectNode =
- EncryptionUtils.getSimpleObjectMapper().valueToTree(cosmosItemOperation.getItem());
- assert cosmosItemOperation instanceof ItemBulkOperation;
- cosmosItemOperationMono =
- this.encryptionProcessor.encryptObjectNode(objectNode).map(encryptedItem -> new ItemBulkOperation<>(
- cosmosItemOperation.getOperationType(),
- cosmosItemOperation.getId(),
- cosmosItemOperation.getPartitionKeyValue(),
- ((ItemBulkOperation) cosmosItemOperation).getRequestOptions(),
- encryptedItem,
- cosmosItemOperation.getContext()
- ));
+ cosmosItemOperationMono = this.encryptionProcessor.initEncryptionSettingsIfNotInitializedAsync()
+ .thenReturn(this.encryptionProcessor.getEncryptionSettings())
+ .flatMap( encryptionSettings -> {
+ try {
+ Field id = cosmosItemOperation.getItem().getClass().getDeclaredField(Constants.PROPERTY_NAME_ID);
+ id.setAccessible(true);
+ return Mono.zip(
+ checkAndGetEncryptedId((String) id.get(cosmosItemOperation.getItem()), encryptionSettings),
+ checkAndGetEncryptedPartitionKey(cosmosItemOperation.getPartitionKeyValue(), encryptionSettings));
+ } catch (IllegalAccessException | NoSuchFieldException e) {
+ return Mono.error(e);
+ }
+ })
+ .flatMap(encryptedIdPartitionKeyTuple -> {
+ ObjectNode objectNode =
+ EncryptionUtils.getSimpleObjectMapper().valueToTree(cosmosItemOperation.getItem());
+ assert cosmosItemOperation instanceof ItemBulkOperation;
+ return this.encryptionProcessor.encryptObjectNode(objectNode).map(encryptedItem -> new ItemBulkOperation<>(
+ cosmosItemOperation.getOperationType(),
+ encryptedIdPartitionKeyTuple.getT1(),
+ encryptedIdPartitionKeyTuple.getT2(),
+ ((ItemBulkOperation) cosmosItemOperation).getRequestOptions(),
+ encryptedItem,
+ cosmosItemOperation.getContext()
+ ));
+ });
} else {
- cosmosItemOperationMono = Mono.just(
- new ItemBulkOperation<>(
- cosmosItemOperation.getOperationType(),
- cosmosItemOperation.getId(),
- cosmosItemOperation.getPartitionKeyValue(),
- ((ItemBulkOperation) cosmosItemOperation).getRequestOptions(),
- null,
- cosmosItemOperation.getContext()
- )
- );
+ cosmosItemOperationMono = this.encryptionProcessor.initEncryptionSettingsIfNotInitializedAsync()
+ .thenReturn(this.encryptionProcessor.getEncryptionSettings())
+ .flatMap( encryptionSettings -> Mono.zip(
+ checkAndGetEncryptedId(cosmosItemOperation.getId() , encryptionSettings),
+ checkAndGetEncryptedPartitionKey(cosmosItemOperation.getPartitionKeyValue(), encryptionSettings)))
+ .flatMap(encryptedIdPartitionKeyTuple -> Mono.just(
+ new ItemBulkOperation<>(
+ cosmosItemOperation.getOperationType(),
+ encryptedIdPartitionKeyTuple.getT1(),
+ encryptedIdPartitionKeyTuple.getT2(),
+ ((ItemBulkOperation) cosmosItemOperation).getRequestOptions(),
+ null,
+ cosmosItemOperation.getContext()
+ )));
}
return cosmosItemOperationMono;
});
diff --git a/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/implementation/Constants.java b/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/implementation/Constants.java
index c06ae1b71f2ca..888752d3959f1 100644
--- a/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/implementation/Constants.java
+++ b/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/implementation/Constants.java
@@ -15,4 +15,6 @@ public class Constants {
public static final String ALLOW_CACHED_READS_HEADER = "x-ms-cosmos-allow-cachedreads";
public static final String DATABASE_RID_HEADER = "x-ms-cosmos-database-rid";
+
+ public static final String PROPERTY_NAME_ID = "id";
}
diff --git a/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/implementation/EncryptionProcessor.java b/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/implementation/EncryptionProcessor.java
index 42032b0f2947f..76cc8094d5626 100644
--- a/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/implementation/EncryptionProcessor.java
+++ b/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/implementation/EncryptionProcessor.java
@@ -22,8 +22,10 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.BinaryNode;
import com.fasterxml.jackson.databind.node.BooleanNode;
import com.fasterxml.jackson.databind.node.DoubleNode;
+import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.LongNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
@@ -41,6 +43,7 @@
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Base64;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -58,6 +61,7 @@ public class EncryptionProcessor {
private ClientEncryptionPolicy clientEncryptionPolicy;
private String containerRid;
private String databaseRid;
+ private List partitionKeyPaths;
private CosmosClientEncryptionKeyProperties cosmosClientEncryptionKeyProperties;
private final EncryptionKeyStoreProviderImpl encryptionKeyStoreProviderImpl;
private final static ImplementationBridgeHelpers.CosmosContainerPropertiesHelper.CosmosContainerPropertiesAccessor cosmosContainerPropertiesAccessor = ImplementationBridgeHelpers.CosmosContainerPropertiesHelper.getCosmosContainerPropertiesAccessor();
@@ -99,7 +103,15 @@ public Mono initializeEncryptionSettingsAsync(boolean isRetry) {
{
this.containerRid = cosmosContainerProperties.getResourceId();
this.databaseRid = cosmosContainerPropertiesAccessor.getSelfLink(cosmosContainerProperties).split("/")[1];
+
+ if (!cosmosContainerProperties.getPartitionKeyDefinition().getPaths().isEmpty()) {
+ this.partitionKeyPaths = cosmosContainerProperties.getPartitionKeyDefinition().getPaths();
+ } else {
+ this.partitionKeyPaths = new ArrayList<>();
+ }
+
this.encryptionSettings.setDatabaseRid(this.databaseRid);
+ this.encryptionSettings.setPartitionKeyPaths(partitionKeyPaths);
if (cosmosContainerProperties.getClientEncryptionPolicy() == null) {
this.isEncryptionSettingsInitDone.set(true);
return Mono.empty();
@@ -206,7 +218,7 @@ public Mono initEncryptionSettingsIfNotInitializedAsync() {
return Mono.empty();
}
- ClientEncryptionPolicy getClientEncryptionPolicy() {
+ public ClientEncryptionPolicy getClientEncryptionPolicy() {
return clientEncryptionPolicy;
}
@@ -438,16 +450,41 @@ public void encryptAndSerializeProperty(EncryptionSettings encryptionSettings, J
}
}
+ public String encryptAndSerializeValue(EncryptionSettings encryptionSettings, String propertyValue, String propertyName) throws MicrosoftDataEncryptionException {
+ JsonNode propertyValueHolder = toJsonNode(propertyValue.getBytes(StandardCharsets.US_ASCII), TypeMarker.STRING);
+ if (propertyName.equals(Constants.PROPERTY_NAME_ID)) {
+ return new String(encryptAndSerializeValue(encryptionSettings, null, propertyValueHolder, propertyName), StandardCharsets.UTF_8);
+ } else {
+ return BinaryNode.valueOf(encryptAndSerializeValue(encryptionSettings, null, propertyValueHolder, propertyName)).asText();
+ }
+ }
+
public byte[] encryptAndSerializeValue(EncryptionSettings encryptionSettings, ObjectNode objectNode,
JsonNode propertyValueHolder, String propertyName) throws MicrosoftDataEncryptionException {
byte[] cipherText;
byte[] cipherTextWithTypeMarker;
+ if (propertyName.equals(Constants.PROPERTY_NAME_ID)) {
+ if (propertyValueHolder.getNodeType() != JsonNodeType.STRING) {
+ throw new IllegalArgumentException("Unsupported argument type. The value to escape has to be string " +
+ "type. Please refer to https://aka.ms/CosmosClientEncryption for more details.");
+ }
+ }
Pair typeMarkerPair = toByteArray(propertyValueHolder);
cipherText =
encryptionSettings.getAeadAes256CbcHmac256EncryptionAlgorithm().encrypt(typeMarkerPair.getRight());
cipherTextWithTypeMarker = new byte[cipherText.length + 1];
cipherTextWithTypeMarker[0] = (byte) typeMarkerPair.getLeft().getValue();
System.arraycopy(cipherText, 0, cipherTextWithTypeMarker, 1, cipherText.length);
+
+ if (propertyName.equals(Constants.PROPERTY_NAME_ID)) {
+ // case: id does not support '/','\','?','#'. Convert Base64 string to Uri safe string
+ String base64UriSafeString = convertToBase64UriSafeString(cipherTextWithTypeMarker);
+ if (objectNode != null && !objectNode.isNull()) {
+ objectNode.put(propertyName, base64UriSafeString);
+ }
+ return base64UriSafeString.getBytes(StandardCharsets.UTF_8);
+ }
+
if (objectNode != null && !objectNode.isNull()) {
objectNode.put(propertyName, cipherTextWithTypeMarker);
}
@@ -567,7 +604,14 @@ public JsonNode decryptAndSerializeValue(EncryptionSettings encryptionSettings,
JsonNode propertyValueHolder, String propertyName) throws MicrosoftDataEncryptionException, IOException {
byte[] cipherText;
byte[] cipherTextWithTypeMarker;
- cipherTextWithTypeMarker = propertyValueHolder.binaryValue();
+ if (propertyName.equals(Constants.PROPERTY_NAME_ID)) {
+ if (propertyValueHolder.getNodeType() == JsonNodeType.NULL) {
+ return null;
+ }
+ cipherTextWithTypeMarker = convertFromBase64UriSafeString(propertyValueHolder.asText());
+ } else {
+ cipherTextWithTypeMarker = propertyValueHolder.binaryValue();
+ }
cipherText = new byte[cipherTextWithTypeMarker.length - 1];
System.arraycopy(cipherTextWithTypeMarker, 1, cipherText, 0,
cipherTextWithTypeMarker.length - 1);
@@ -627,6 +671,17 @@ public static JsonNode toJsonNode(byte[] serializedBytes, TypeMarker typeMarker)
throw BridgeInternal.createCosmosException(0, "Invalid or Unsupported Data Type Passed " + typeMarker);
}
+ private String convertToBase64UriSafeString(byte[] bytesToProcess) {
+ // Base 64 Encoding with URL and Filename Safe Alphabet https://datatracker.ietf.org/doc/html/rfc4648#section-5
+ // https://docs.microsoft.com/en-us/azure/cosmos-db/concepts-limits#per-item-limits, due to base64 conversion and encryption
+ // the permissible size of the property will further reduce.
+ return Base64.getUrlEncoder().encodeToString(bytesToProcess);
+ }
+
+ private byte[] convertFromBase64UriSafeString(String base64UriSafeString) {
+ return Base64.getUrlDecoder().decode(base64UriSafeString);
+ }
+
public enum TypeMarker {
NULL(1), // not used
BOOLEAN(2),
diff --git a/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/implementation/EncryptionSettings.java b/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/implementation/EncryptionSettings.java
index 8c45d9be3e33e..e25c13be68e07 100644
--- a/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/implementation/EncryptionSettings.java
+++ b/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/implementation/EncryptionSettings.java
@@ -25,6 +25,7 @@
import java.security.InvalidKeyException;
import java.time.Duration;
import java.time.Instant;
+import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@@ -39,6 +40,7 @@ public final class EncryptionSettings {
private EncryptionType encryptionType;
private String databaseRid;
private CosmosClientEncryptionKeyProperties cosmosClientEncryptionKeyProperties;
+ private List partitionKeyPaths;
private final static EncryptionImplementationBridgeHelpers.CosmosEncryptionAsyncClientHelper.CosmosEncryptionAsyncClientAccessor cosmosEncryptionAsyncClientAccessor =
EncryptionImplementationBridgeHelpers.CosmosEncryptionAsyncClientHelper.getCosmosEncryptionAsyncClientAccessor();
@@ -226,6 +228,14 @@ public void setEncryptionSettingForProperty(String propertyName, EncryptionSetti
this.encryptionSettingCacheByPropertyName.set(propertyName, cachedEncryptionSettings);
}
+ public List getPartitionKeyPaths() {
+ return partitionKeyPaths;
+ }
+
+ public void setPartitionKeyPaths(List partitionKeyPaths) {
+ this.partitionKeyPaths = partitionKeyPaths;
+ }
+
static EncryptionSettings create(
EncryptionSettings settingsForKey,
EncryptionType encryptionType) throws MicrosoftDataEncryptionException {
diff --git a/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/models/SqlQuerySpecWithEncryption.java b/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/models/SqlQuerySpecWithEncryption.java
index b9bfd921c66d4..938d749a18942 100644
--- a/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/models/SqlQuerySpecWithEncryption.java
+++ b/sdk/cosmos/azure-cosmos-encryption/src/main/java/com/azure/cosmos/encryption/models/SqlQuerySpecWithEncryption.java
@@ -4,6 +4,7 @@
package com.azure.cosmos.encryption.models;
import com.azure.cosmos.encryption.CosmosEncryptionAsyncContainer;
+import com.azure.cosmos.encryption.implementation.Constants;
import com.azure.cosmos.encryption.implementation.EncryptionImplementationBridgeHelpers;
import com.azure.cosmos.encryption.implementation.EncryptionProcessor;
import com.azure.cosmos.encryption.implementation.EncryptionUtils;
@@ -17,6 +18,8 @@
import com.fasterxml.jackson.databind.JsonNode;
import reactor.core.publisher.Mono;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
import java.util.HashMap;
import java.util.List;
@@ -56,11 +59,11 @@ Mono addEncryptionParameterAsync(String path, SqlParameter sqlParameter,
List parameters = sqlQuerySpec.getParameters();
if (parameters != null) {
return cosmosEncryptionAsyncContainerAccessor.getEncryptionProcessor(cosmosEncryptionAsyncContainer)
- .initEncryptionSettingsIfNotInitializedAsync().then(Mono.defer(() -> {
-
+ .initEncryptionSettingsIfNotInitializedAsync().then(Mono.defer(() -> {
+ String propertyName = path.substring(1);
return cosmosEncryptionAsyncContainerAccessor.getEncryptionProcessor(cosmosEncryptionAsyncContainer)
.getEncryptionSettings()
- .getEncryptionSettingForPropertyAsync(sqlParameter.getName().substring(1),
+ .getEncryptionSettingForPropertyAsync(propertyName,
cosmosEncryptionAsyncContainerAccessor.getEncryptionProcessor(cosmosEncryptionAsyncContainer)).flatMap(encryptionSettings -> { // encryptionSettings.
if (encryptionSettings == null) {
// property not encrypted.
@@ -73,6 +76,12 @@ Mono addEncryptionParameterAsync(String path, SqlParameter sqlParameter,
"query because of randomized encryption", path)));
}
try {
+ if (propertyName.equals(Constants.PROPERTY_NAME_ID)) {
+ if (sqlParameter.getValue(Object.class).getClass() != String.class) {
+ throw new IllegalArgumentException("Unsupported argument type. The value to escape has to be string " +
+ "type. Please refer to https://aka.ms/CosmosClientEncryption for more details.");
+ }
+ }
byte[] valueByte =
EncryptionUtils.serializeJsonToByteArray(EncryptionUtils.getSimpleObjectMapper(),
sqlParameter.getValue(Object.class));
@@ -84,9 +93,20 @@ Mono addEncryptionParameterAsync(String path, SqlParameter sqlParameter,
byte[] cipherTextWithTypeMarker = new byte[cipherText.length + 1];
cipherTextWithTypeMarker[0] = (byte) typeMarkerPair.getLeft().getValue();
System.arraycopy(cipherText, 0, cipherTextWithTypeMarker, 1, cipherText.length);
- SqlParameter encryptedParameter = new SqlParameter(sqlParameter.getName(),
- cipherTextWithTypeMarker);
+
+ SqlParameter encryptedParameter;
+ if (propertyName.equals(Constants.PROPERTY_NAME_ID)) {
+ // case: id does not support '/','\','?','#'. Convert Base64 string to Uri safe string
+ String base64UriSafeString = convertToBase64UriSafeString(cipherTextWithTypeMarker);
+ encryptedParameter = new SqlParameter(sqlParameter.getName(),
+ base64UriSafeString.getBytes(StandardCharsets.UTF_8));
+
+ } else {
+ encryptedParameter = new SqlParameter(sqlParameter.getName(),
+ cipherTextWithTypeMarker);
+ }
parameters.add(encryptedParameter);
+
} catch (MicrosoftDataEncryptionException ex) {
return Mono.error(ex);
}
@@ -98,6 +118,13 @@ Mono addEncryptionParameterAsync(String path, SqlParameter sqlParameter,
return Mono.empty();
}
+ private String convertToBase64UriSafeString(byte[] bytesToProcess) {
+ // Base 64 Encoding with URL and Filename Safe Alphabet https://datatracker.ietf.org/doc/html/rfc4648#section-5
+ // https://docs.microsoft.com/en-us/azure/cosmos-db/concepts-limits#per-item-limits, due to base64 conversion and encryption
+ // the permissible size of the property will further reduce.
+ return Base64.getUrlEncoder().encodeToString(bytesToProcess);
+ }
+
HashMap getEncryptionParamMap() {
return encryptionParamMap;
}
diff --git a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/CosmosEncryptionClientCachesTest.java b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/CosmosEncryptionClientCachesTest.java
index efc84fa304dce..39048a0c9e0c8 100644
--- a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/CosmosEncryptionClientCachesTest.java
+++ b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/CosmosEncryptionClientCachesTest.java
@@ -68,7 +68,7 @@ public void before_CosmosItemTest() {
CosmosEncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA256.getName(), metadata2).block();
//Create collection with clientEncryptionPolicy
- ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(getPaths());
+ ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(getPaths(1), 1);
CosmosContainerProperties containerProperties = new CosmosContainerProperties("TestCollForEncryptionCacheTest"
, "/mypk");
containerProperties.setClientEncryptionPolicy(clientEncryptionPolicy);
diff --git a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/DotNetCompatibleTest.java b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/DotNetCompatibleTest.java
index 58fe54893f845..dccd29bab41e1 100644
--- a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/DotNetCompatibleTest.java
+++ b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/DotNetCompatibleTest.java
@@ -70,7 +70,7 @@ public void before_CosmosItemTest() throws IOException {
cosmosEncryptionAsyncClient.getCosmosEncryptionAsyncDatabase(cosmosAsyncDatabase.getId());
ClientEncryptionPolicy clientEncryptionPolicy =
- new ClientEncryptionPolicy(getPaths());
+ new ClientEncryptionPolicy(getPaths(2), 2);
String containerId = UUID.randomUUID().toString();
CosmosContainerProperties containerProperties = new CosmosContainerProperties(containerId, "/mypk");
containerProperties.setClientEncryptionPolicy(clientEncryptionPolicy);
@@ -100,12 +100,13 @@ public void createItemEncrypt_readItemDecrypt() throws IOException {
this.cosmosEncryptionAsyncContainer.getCosmosAsyncContainer().createItem(dotNetEncryptedPocoJsonNode,
partitionKey, requestOptions).block();
+ JsonNode dotNetPOCOJsonNode = MAPPER.readTree(new File("src/test/resources/dotnetEncryption/POCO.json"));
+ partitionKey = new PartitionKey(dotNetPOCOJsonNode.get("mypk").asText());
//reading above saved .net encrypted json via java encryption library
EncryptionPojo unencryptedPojo =
- this.cosmosEncryptionAsyncContainer.readItem(dotNetEncryptedPocoJsonNode.get("id").asText(), partitionKey
+ this.cosmosEncryptionAsyncContainer.readItem(dotNetPOCOJsonNode.get("id").asText(), partitionKey
, new CosmosItemRequestOptions(), EncryptionPojo.class).block().getItem();
- JsonNode dotNetPOCOJsonNode = MAPPER.readTree(new File("src/test/resources/dotnetEncryption/POCO.json"));
EncryptionPojo unencryptedPoco = MAPPER.treeToValue(dotNetPOCOJsonNode, EncryptionPojo.class);
//validating java decrypted pojo similar to original .net unencrypted poco
diff --git a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionAsyncApiCrudTest.java b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionAsyncApiCrudTest.java
index a5415f557db45..1fbda8eb17f0d 100644
--- a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionAsyncApiCrudTest.java
+++ b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionAsyncApiCrudTest.java
@@ -7,8 +7,6 @@
import com.azure.cosmos.CosmosAsyncClient;
import com.azure.cosmos.CosmosClientBuilder;
import com.azure.cosmos.encryption.models.CosmosEncryptionAlgorithm;
-import com.azure.cosmos.encryption.models.CosmosEncryptionType;
-import com.azure.cosmos.models.ClientEncryptionIncludedPath;
import com.azure.cosmos.models.ClientEncryptionPolicy;
import com.azure.cosmos.models.CosmosBatch;
import com.azure.cosmos.models.CosmosBatchItemRequestOptions;
@@ -24,13 +22,9 @@
import com.azure.cosmos.models.CosmosPatchOperations;
import com.azure.cosmos.models.CosmosQueryRequestOptions;
import com.azure.cosmos.models.EncryptionKeyWrapMetadata;
-import com.azure.cosmos.models.FeedResponse;
import com.azure.cosmos.models.PartitionKey;
-import com.azure.cosmos.models.SqlParameter;
import com.azure.cosmos.models.SqlQuerySpec;
import com.azure.cosmos.util.CosmosPagedFlux;
-import com.azure.cosmos.encryption.implementation.ReflectionUtils;
-import com.azure.cosmos.encryption.models.SqlQuerySpecWithEncryption;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
@@ -40,7 +34,6 @@
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
-import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -76,8 +69,7 @@ public void before_CosmosItemTest() {
cosmosEncryptionAsyncDatabase = getSharedEncryptionDatabase(cosmosEncryptionAsyncClient);
cosmosEncryptionAsyncContainer = getSharedEncryptionContainer(cosmosEncryptionAsyncClient);
- ClientEncryptionPolicy clientEncryptionWithPolicyFormatVersion2 = new ClientEncryptionPolicy(getPaths());
- ReflectionUtils.setPolicyFormatVersion(clientEncryptionWithPolicyFormatVersion2, 2);
+ ClientEncryptionPolicy clientEncryptionWithPolicyFormatVersion2 = new ClientEncryptionPolicy(getPaths(2), 2);
String containerId = UUID.randomUUID().toString();
CosmosContainerProperties properties = new CosmosContainerProperties(containerId, "/mypk");
properties.setClientEncryptionPolicy(clientEncryptionWithPolicyFormatVersion2);
@@ -147,195 +139,6 @@ public void upsertItem_readItem() {
validateResponse(properties, readItem);
}
- @Test(groups = {"encryption"}, timeOut = TIMEOUT)
- public void queryItems() {
- EncryptionPojo properties = getItem(UUID.randomUUID().toString());
- CosmosItemResponse itemResponse = cosmosEncryptionAsyncContainer.createItem(properties,
- new PartitionKey(properties.getMypk()), new CosmosItemRequestOptions()).block();
- assertThat(itemResponse.getRequestCharge()).isGreaterThan(0);
- EncryptionPojo responseItem = itemResponse.getItem();
- validateResponse(properties, responseItem);
-
- String query = String.format("SELECT * from c where c.id = '%s'", properties.getId());
- CosmosQueryRequestOptions cosmosQueryRequestOptions = new CosmosQueryRequestOptions();
-
- SqlQuerySpec querySpec = new SqlQuerySpec(query);
- CosmosPagedFlux feedResponseIterator =
- cosmosEncryptionAsyncContainer.queryItems(querySpec, cosmosQueryRequestOptions, EncryptionPojo.class);
- List feedResponse = feedResponseIterator.byPage().blockFirst().getResults();
- assertThat(feedResponse.size()).isGreaterThanOrEqualTo(1);
- for (EncryptionPojo pojo : feedResponse) {
- if (pojo.getId().equals(properties.getId())) {
- validateResponse(pojo, responseItem);
- }
- }
- }
-
- @Test(groups = {"encryption"}, timeOut = TIMEOUT)
- public void queryItemsAggregate() {
- long startTime = Instant.now().getEpochSecond();
- List actualIds = new ArrayList<>();
- EncryptionPojo properties = getItem(UUID.randomUUID().toString());
- cosmosEncryptionAsyncContainer.createItem(properties, new PartitionKey(properties.getMypk()),
- new CosmosItemRequestOptions()).block();
- actualIds.add(properties.getId());
- properties = getItem(UUID.randomUUID().toString());
- cosmosEncryptionAsyncContainer.createItem(properties, new PartitionKey(properties.getMypk()),
- new CosmosItemRequestOptions()).block();
- actualIds.add(properties.getId());
- properties = getItem(UUID.randomUUID().toString());
- cosmosEncryptionAsyncContainer.createItem(properties, new PartitionKey(properties.getMypk()),
- new CosmosItemRequestOptions()).block();
- actualIds.add(properties.getId());
-
- // MAX query
- String query1 = String.format("Select value max(c._ts) from c");
- CosmosQueryRequestOptions cosmosQueryRequestOptions1 = new CosmosQueryRequestOptions();
-
- SqlQuerySpec querySpec1 = new SqlQuerySpec(query1);
- CosmosPagedFlux feedResponseIterator1 =
- cosmosEncryptionAsyncContainer.queryItems(querySpec1, cosmosQueryRequestOptions1, Integer.class);
- List feedResponse1 = feedResponseIterator1.byPage().blockFirst().getResults();
- int timeStamp = feedResponse1.get(0);
- long endTime = Instant.now().getEpochSecond();
-
- assertThat(timeStamp).isGreaterThanOrEqualTo((int)startTime);
- assertThat(timeStamp).isLessThanOrEqualTo((int)endTime);
- assertThat(feedResponse1.size()).isEqualTo(1);
-
- // COUNT query
- String query2 = String.format("Select top 1 value count(c) from c order by c._ts");
- CosmosQueryRequestOptions cosmosQueryRequestOptions2 = new CosmosQueryRequestOptions();
-
- SqlQuerySpec querySpec2 = new SqlQuerySpec(query2);
- CosmosPagedFlux feedResponseIterator2 =
- cosmosEncryptionAsyncContainer.queryItems(querySpec2, cosmosQueryRequestOptions2, Integer.class);
- List feedResponse2 = feedResponseIterator2.byPage().blockFirst().getResults();
- assertThat(feedResponse2.size()).isEqualTo(1);
-
- // MAX query for String class type
- String query3 = String.format("Select value max(c.sensitiveString) from c");
- CosmosQueryRequestOptions cosmosQueryRequestOptions3 = new CosmosQueryRequestOptions();
-
- SqlQuerySpec querySpec3 = new SqlQuerySpec(query3);
- CosmosPagedFlux feedResponseIterator3 =
- cosmosEncryptionAsyncContainer.queryItems(querySpec3, cosmosQueryRequestOptions3, String.class);
- List feedResponse3 = feedResponseIterator3.byPage().blockFirst().getResults();
- assertThat(feedResponse3.size()).isEqualTo(1);
- }
-
- @Test(groups = {"encryption"}, timeOut = TIMEOUT)
- public void queryItemsOnEncryptedProperties() {
- EncryptionPojo properties = getItem(UUID.randomUUID().toString());
- CosmosItemResponse itemResponse = cosmosEncryptionAsyncContainer.createItem(properties,
- new PartitionKey(properties.getMypk()), new CosmosItemRequestOptions()).block();
- assertThat(itemResponse.getRequestCharge()).isGreaterThan(0);
- EncryptionPojo responseItem = itemResponse.getItem();
- validateResponse(properties, responseItem);
-
- String query = String.format("SELECT * FROM c where c.sensitiveString = @sensitiveString and c.nonSensitive =" +
- " " +
- "@nonSensitive and c.sensitiveLong = @sensitiveLong");
- SqlQuerySpec querySpec = new SqlQuerySpec(query);
- SqlParameter parameter1 = new SqlParameter("@nonSensitive", properties.getNonSensitive());
- querySpec.getParameters().add(parameter1);
-
- SqlParameter parameter2 = new SqlParameter("@sensitiveString", properties.getSensitiveString());
- SqlParameter parameter3 = new SqlParameter("@sensitiveLong", properties.getSensitiveLong());
- SqlQuerySpecWithEncryption sqlQuerySpecWithEncryption = new SqlQuerySpecWithEncryption(querySpec);
- sqlQuerySpecWithEncryption.addEncryptionParameter("/sensitiveString", parameter2);
- sqlQuerySpecWithEncryption.addEncryptionParameter("/sensitiveLong", parameter3);
-
- CosmosQueryRequestOptions cosmosQueryRequestOptions = new CosmosQueryRequestOptions();
- CosmosPagedFlux feedResponseIterator =
- cosmosEncryptionAsyncContainer.queryItemsOnEncryptedProperties(sqlQuerySpecWithEncryption,
- cosmosQueryRequestOptions, EncryptionPojo.class);
- List feedResponse = feedResponseIterator.byPage().blockFirst().getResults();
- assertThat(feedResponse.size()).isGreaterThanOrEqualTo(1);
- for (EncryptionPojo pojo : feedResponse) {
- if (pojo.getId().equals(properties.getId())) {
- validateResponse(pojo, responseItem);
- }
- }
- }
-
- @Test(groups = {"encryption"}, timeOut = TIMEOUT)
- public void queryItemsOnRandomizedEncryption() {
- EncryptionPojo properties = getItem(UUID.randomUUID().toString());
- CosmosItemResponse itemResponse = cosmosEncryptionAsyncContainer.createItem(properties,
- new PartitionKey(properties.getMypk()), new CosmosItemRequestOptions()).block();
- assertThat(itemResponse.getRequestCharge()).isGreaterThan(0);
- EncryptionPojo responseItem = itemResponse.getItem();
- validateResponse(properties, responseItem);
-
- String query = String.format("SELECT * FROM c where c.sensitiveString = @sensitiveString and c.nonSensitive =" +
- " " +
- "@nonSensitive and c.sensitiveDouble = @sensitiveDouble");
- SqlQuerySpec querySpec = new SqlQuerySpec(query);
- SqlParameter parameter1 = new SqlParameter("@nonSensitive", properties.getNonSensitive());
- querySpec.getParameters().add(parameter1);
-
- SqlParameter parameter2 = new SqlParameter("@sensitiveString", properties.getSensitiveString());
- SqlParameter parameter3 = new SqlParameter("@sensitiveDouble", properties.getSensitiveDouble());
- SqlQuerySpecWithEncryption sqlQuerySpecWithEncryption = new SqlQuerySpecWithEncryption(querySpec);
- sqlQuerySpecWithEncryption.addEncryptionParameter("/sensitiveString", parameter2);
- sqlQuerySpecWithEncryption.addEncryptionParameter("/sensitiveDouble", parameter3);
-
- CosmosQueryRequestOptions cosmosQueryRequestOptions = new CosmosQueryRequestOptions();
- CosmosPagedFlux feedResponseIterator =
- cosmosEncryptionAsyncContainer.queryItemsOnEncryptedProperties(sqlQuerySpecWithEncryption,
- cosmosQueryRequestOptions, EncryptionPojo.class);
- try {
- List feedResponse = feedResponseIterator.byPage().blockFirst().getResults();
- fail("Query on randomized parameter should fail");
- } catch (IllegalArgumentException ex) {
- assertThat(ex.getMessage()).contains("Path /sensitiveDouble cannot be used in the " +
- "query because of randomized encryption");
- }
- }
-
- @Test(groups = {"encryption"}, timeOut = TIMEOUT)
- public void queryItemsWithContinuationTokenAndPageSize() {
- List actualIds = new ArrayList<>();
- EncryptionPojo properties = getItem(UUID.randomUUID().toString());
- cosmosEncryptionAsyncContainer.createItem(properties, new PartitionKey(properties.getMypk()),
- new CosmosItemRequestOptions()).block();
- actualIds.add(properties.getId());
- properties = getItem(UUID.randomUUID().toString());
- cosmosEncryptionAsyncContainer.createItem(properties, new PartitionKey(properties.getMypk()),
- new CosmosItemRequestOptions()).block();
- actualIds.add(properties.getId());
- properties = getItem(UUID.randomUUID().toString());
- cosmosEncryptionAsyncContainer.createItem(properties, new PartitionKey(properties.getMypk()),
- new CosmosItemRequestOptions()).block();
- actualIds.add(properties.getId());
-
- String query = String.format("SELECT * from c where c.id in ('%s', '%s', '%s')", actualIds.get(0),
- actualIds.get(1), actualIds.get(2));
- CosmosQueryRequestOptions cosmosQueryRequestOptions = new CosmosQueryRequestOptions();
- String continuationToken = null;
- int pageSize = 1;
-
- int initialDocumentCount = 3;
- int finalDocumentCount = 0;
-
- CosmosPagedFlux feedResponseIterator =
- cosmosEncryptionAsyncContainer.queryItems(query, cosmosQueryRequestOptions, EncryptionPojo.class);
-
- do {
- Iterable> feedResponseIterable =
- feedResponseIterator.byPage(continuationToken, 1).toIterable();
- for (FeedResponse fr : feedResponseIterable) {
- int resultSize = fr.getResults().size();
- assertThat(resultSize).isEqualTo(pageSize);
- finalDocumentCount += fr.getResults().size();
- continuationToken = fr.getContinuationToken();
- }
- } while (continuationToken != null);
-
- assertThat(finalDocumentCount).isEqualTo(initialDocumentCount);
- }
-
@Ignore("Ignoring it temporarily because server always returning policyFormatVersion 0")
@Test(groups = {"encryption"}, timeOut = TIMEOUT)
public void incompatiblePolicyFormatVersion() {
@@ -352,214 +155,6 @@ public void incompatiblePolicyFormatVersion() {
}
}
- @Test(groups = {"encryption"}, timeOut = TIMEOUT)
- public void crudQueryStaleCache() {
- String databaseId = UUID.randomUUID().toString();
- try {
- createNewDatabaseWithClientEncryptionKey(databaseId);
- CosmosAsyncClient asyncClient = getClientBuilder().buildAsyncClient();
- KeyEncryptionKeyResolver keyEncryptionKeyResolver = new TestKeyEncryptionKeyResolver();
- CosmosEncryptionAsyncClient cosmosEncryptionAsyncClient = new CosmosEncryptionClientBuilder().cosmosAsyncClient(asyncClient).keyEncryptionKeyResolver(
- keyEncryptionKeyResolver).keyEncryptionKeyResolverName("TEST_KEY_RESOLVER").buildAsyncClient();
- CosmosEncryptionAsyncDatabase cosmosEncryptionAsyncDatabase =
- cosmosEncryptionAsyncClient.getCosmosEncryptionAsyncDatabase(asyncClient.getDatabase(databaseId));
-
- String containerId = UUID.randomUUID().toString();
- ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(getPaths());
- createEncryptionContainer(cosmosEncryptionAsyncDatabase, clientEncryptionPolicy, containerId);
- CosmosEncryptionAsyncContainer encryptionAsyncContainerOriginal =
- cosmosEncryptionAsyncDatabase.getCosmosEncryptionAsyncContainer(containerId);
-
- EncryptionPojo encryptionPojo = getItem(UUID.randomUUID().toString());
- CosmosItemResponse createResponse = encryptionAsyncContainerOriginal.createItem(encryptionPojo,
- new PartitionKey(encryptionPojo.getMypk()), new CosmosItemRequestOptions()).block();
- validateResponse(encryptionPojo, createResponse.getItem());
-
- String query = String.format("SELECT * from c where c.id = '%s'", encryptionPojo.getId());
- SqlQuerySpec querySpec = new SqlQuerySpec(query);
- CosmosPagedFlux feedResponseIterator =
- encryptionAsyncContainerOriginal.queryItems(querySpec, null, EncryptionPojo.class);
- List feedResponse = feedResponseIterator.byPage().blockFirst().getResults();
-
- EncryptionPojo readItem = encryptionAsyncContainerOriginal.readItem(encryptionPojo.getId(),
- new PartitionKey(encryptionPojo.getMypk()),
- new CosmosItemRequestOptions(), EncryptionPojo.class).block().getItem();
- validateResponse(encryptionPojo, readItem);
-
- //Deleting database and creating database, container again
- cosmosEncryptionAsyncDatabase.getCosmosAsyncDatabase().delete().block();
- createNewDatabaseWithClientEncryptionKey(databaseId);
- createEncryptionContainer(cosmosEncryptionAsyncDatabase, clientEncryptionPolicy, containerId);
-
- //Validating create on original encryptionAsyncContainer
- createResponse = encryptionAsyncContainerOriginal.createItem(encryptionPojo,
- new PartitionKey(encryptionPojo.getMypk()), new CosmosItemRequestOptions()).block();
- validateResponse(encryptionPojo, createResponse.getItem());
-
- //Deleting and creating container
- encryptionAsyncContainerOriginal.getCosmosAsyncContainer().delete().block();
- ClientEncryptionPolicy policyWithOneEncryptionPolicy = new ClientEncryptionPolicy(getPathWithOneEncryptionField());
- createEncryptionContainer(cosmosEncryptionAsyncDatabase, policyWithOneEncryptionPolicy, containerId);
- CosmosEncryptionAsyncContainer encryptionAsyncContainerNew = getNewEncryptionContainerProxyObject(cosmosEncryptionAsyncDatabase.getCosmosAsyncDatabase().getId(), containerId);
- encryptionAsyncContainerNew.createItem(encryptionPojo,
- new PartitionKey(encryptionPojo.getMypk()), new CosmosItemRequestOptions()).block();
- EncryptionPojo pojoWithOneFieldEncrypted = encryptionAsyncContainerNew.getCosmosAsyncContainer().readItem(encryptionPojo.getId(), new PartitionKey(encryptionPojo.getMypk()),
- new CosmosItemRequestOptions(), EncryptionPojo.class).block().getItem();
- validateResponseWithOneFieldEncryption(encryptionPojo, pojoWithOneFieldEncrypted);
-
- //Validating read on original encryptionAsyncContainer
- readItem = encryptionAsyncContainerOriginal.readItem(encryptionPojo.getId(), new PartitionKey(encryptionPojo.getMypk()),
- new CosmosItemRequestOptions(), EncryptionPojo.class).block().getItem();
- validateResponse(encryptionPojo, readItem);
-
- //Deleting and creating container
- encryptionAsyncContainerOriginal.getCosmosAsyncContainer().delete().block();
- createEncryptionContainer(cosmosEncryptionAsyncDatabase, clientEncryptionPolicy, containerId);
-
- CosmosItemResponse upsertResponse = encryptionAsyncContainerOriginal.upsertItem(encryptionPojo,
- new PartitionKey(encryptionPojo.getMypk()), new CosmosItemRequestOptions()).block();
- assertThat(upsertResponse.getRequestCharge()).isGreaterThan(0);
- EncryptionPojo responseItem = upsertResponse.getItem();
- validateResponse(encryptionPojo, responseItem);
-
- //Deleting and creating container
- encryptionAsyncContainerOriginal.getCosmosAsyncContainer().delete().block();
- createEncryptionContainer(cosmosEncryptionAsyncDatabase, clientEncryptionPolicy, containerId);
- encryptionAsyncContainerNew = getNewEncryptionContainerProxyObject(cosmosEncryptionAsyncDatabase.getCosmosAsyncDatabase().getId(), containerId);
- encryptionAsyncContainerNew.createItem(encryptionPojo,
- new PartitionKey(encryptionPojo.getMypk()), new CosmosItemRequestOptions()).block();
-
-
- CosmosItemResponse replaceResponse =
- encryptionAsyncContainerOriginal.replaceItem(encryptionPojo, encryptionPojo.getId(),
- new PartitionKey(encryptionPojo.getMypk()), new CosmosItemRequestOptions()).block();
- assertThat(upsertResponse.getRequestCharge()).isGreaterThan(0);
- responseItem = replaceResponse.getItem();
- validateResponse(encryptionPojo, responseItem);
-
- // First query fail on core sdk as there will be no pkrange cache, and collection cache have wrong information of collection rid,
- // pkrange call will fail will null pointer, therefore querying before deleting the container making sure we have pkrange cache to begin with
- encryptionAsyncContainerOriginal.queryItems(querySpec, null, EncryptionPojo.class).byPage().blockFirst().getResults();
- //Deleting and creating container
- encryptionAsyncContainerOriginal.getCosmosAsyncContainer().delete().block();
- createEncryptionContainer(cosmosEncryptionAsyncDatabase, clientEncryptionPolicy, containerId);
- CosmosEncryptionAsyncContainer newEncryptionAsyncContainer = getNewEncryptionContainerProxyObject(cosmosEncryptionAsyncDatabase.getCosmosAsyncDatabase().getId(), containerId);
-
- for (int i = 0; i < 10; i++) {
- EncryptionPojo pojo = getItem(UUID.randomUUID().toString());
- newEncryptionAsyncContainer.createItem(pojo,
- new PartitionKey(pojo.getMypk()), new CosmosItemRequestOptions()).block();
- }
-
- feedResponseIterator =
- encryptionAsyncContainerOriginal.queryItems("Select * from C", null, EncryptionPojo.class);
- String continuationToken = null;
- int pageSize = 3;
- int finalDocumentCount = 0;
- do {
- Iterable> feedResponseIterable =
- feedResponseIterator.byPage(continuationToken, pageSize).toIterable();
- for (FeedResponse fr : feedResponseIterable) {
- int resultSize = fr.getResults().size();
- assertThat(resultSize).isLessThanOrEqualTo(pageSize);
- finalDocumentCount += fr.getResults().size();
- continuationToken = fr.getContinuationToken();
- }
- } while (continuationToken != null);
-
- assertThat(finalDocumentCount).isEqualTo(10);
-
-
- //Deleting and creating container
- encryptionAsyncContainerOriginal.getCosmosAsyncContainer().delete().block();
- createEncryptionContainer(cosmosEncryptionAsyncDatabase, clientEncryptionPolicy, containerId);
- newEncryptionAsyncContainer = getNewEncryptionContainerProxyObject(cosmosEncryptionAsyncDatabase.getCosmosAsyncDatabase().getId(), containerId);
-
- EncryptionPojo encryptionPojoForQueryItemsOnEncryptedProperties = getItem(UUID.randomUUID().toString());
- newEncryptionAsyncContainer.createItem(encryptionPojoForQueryItemsOnEncryptedProperties,
- new PartitionKey(encryptionPojoForQueryItemsOnEncryptedProperties.getMypk()), new CosmosItemRequestOptions()).block();
-
- query = String.format("SELECT * FROM c where c.sensitiveString = @sensitiveString and c.nonSensitive =" +
- " " +
- "@nonSensitive and c.sensitiveLong = @sensitiveLong");
- querySpec = new SqlQuerySpec(query);
- SqlParameter parameter1 = new SqlParameter("@nonSensitive", encryptionPojoForQueryItemsOnEncryptedProperties.getNonSensitive());
- querySpec.getParameters().add(parameter1);
-
- SqlParameter parameter2 = new SqlParameter("@sensitiveString", encryptionPojoForQueryItemsOnEncryptedProperties.getSensitiveString());
- SqlParameter parameter3 = new SqlParameter("@sensitiveLong", encryptionPojoForQueryItemsOnEncryptedProperties.getSensitiveLong());
- SqlQuerySpecWithEncryption sqlQuerySpecWithEncryption = new SqlQuerySpecWithEncryption(querySpec);
- sqlQuerySpecWithEncryption.addEncryptionParameter("/sensitiveString", parameter2);
- sqlQuerySpecWithEncryption.addEncryptionParameter("/sensitiveLong", parameter3);
-
- feedResponseIterator =
- encryptionAsyncContainerOriginal.queryItemsOnEncryptedProperties(sqlQuerySpecWithEncryption,
- null, EncryptionPojo.class);
- feedResponse = feedResponseIterator.byPage().blockFirst().getResults();
- assertThat(feedResponse.size()).isGreaterThanOrEqualTo(1);
- for (EncryptionPojo pojo : feedResponse) {
- if (pojo.getId().equals(encryptionPojoForQueryItemsOnEncryptedProperties.getId())) {
- validateResponse(encryptionPojoForQueryItemsOnEncryptedProperties, pojo);
- }
- }
-
- //Deleting and creating container
- encryptionAsyncContainerOriginal.getCosmosAsyncContainer().delete().block();
- createEncryptionContainer(cosmosEncryptionAsyncDatabase, clientEncryptionPolicy, containerId);
-
- String itemId= UUID.randomUUID().toString();
- EncryptionPojo createPojo = getItem(itemId);
- CosmosBatch cosmosEncryptionBatch = CosmosBatch.createCosmosBatch(new PartitionKey(itemId));
- cosmosEncryptionBatch.createItemOperation(createPojo);
- cosmosEncryptionBatch.readItemOperation(itemId);
-
- CosmosBatchResponse batchResponse = encryptionAsyncContainerOriginal.executeCosmosBatch(cosmosEncryptionBatch).block();
- assertThat(batchResponse.getResults().size()).isEqualTo(2);
- validateResponse(createPojo, batchResponse.getResults().get(0).getItem(EncryptionPojo.class));
- validateResponse(createPojo, batchResponse.getResults().get(1).getItem(EncryptionPojo.class));
-
- //Deleting and creating container
- encryptionAsyncContainerOriginal.getCosmosAsyncContainer().delete().block();
- createEncryptionContainer(cosmosEncryptionAsyncDatabase, clientEncryptionPolicy, containerId);
-
- itemId= UUID.randomUUID().toString();
- createPojo = getItem(itemId);
- CosmosItemResponse itemResponse = encryptionAsyncContainerOriginal.createItem(createPojo,
- new PartitionKey(createPojo.getMypk()), new CosmosItemRequestOptions()).block();
-
- int originalSensitiveInt = createPojo.getSensitiveInt();
- int newSensitiveInt = originalSensitiveInt + 1;
-
- CosmosPatchOperations cosmosPatchOperations = CosmosPatchOperations.create();
- cosmosPatchOperations.add("/sensitiveString", "patched");
- cosmosPatchOperations.remove("/sensitiveDouble");
- cosmosPatchOperations.replace("/sensitiveInt", newSensitiveInt);
-
- CosmosItemResponse patchResponse = encryptionAsyncContainerOriginal.patchItem(
- createPojo.getId(),
- new PartitionKey(createPojo.getMypk()),
- cosmosPatchOperations,
- new CosmosPatchItemRequestOptions(),
- EncryptionPojo.class).block();
-
- CosmosItemResponse readResponse = encryptionAsyncContainerOriginal.readItem(
- createPojo.getId(),
- new PartitionKey(createPojo.getMypk()),
- new CosmosPatchItemRequestOptions(),
- EncryptionPojo.class).block();
-
- validateResponse(patchResponse.getItem(), readResponse.getItem());
-
- } finally {
- try {
- //deleting the database created for this test
- this.client.getDatabase(databaseId).delete().block();
- } catch(Exception ex) {
- // do nothing as we are clearing database created for this test
- }
- }
- }
-
@Test(groups = {"encryption"}, timeOut = TIMEOUT)
public void invalidDataEncryptionKeyAlgorithm() {
try {
@@ -911,132 +506,130 @@ private void createItemsAndVerify(List cosmosItemOperations
@Test(groups = {"encryption"}, timeOut = TIMEOUT)
public void crudOnDifferentOverload() {
- List actualProperties = new ArrayList<>();
- // Read item
- EncryptionPojo properties = getItem(UUID.randomUUID().toString());
- CosmosItemResponse itemResponse = cosmosEncryptionAsyncContainer.createItem(properties).block();
- assertThat(itemResponse.getRequestCharge()).isGreaterThan(0);
- EncryptionPojo responseItem = itemResponse.getItem();
- validateResponse(properties, responseItem);
- actualProperties.add(properties);
+ String databaseId = UUID.randomUUID().toString();
+ try {
+ createNewDatabaseWithClientEncryptionKey(databaseId);
+ CosmosAsyncClient asyncClient = getClientBuilder().buildAsyncClient();
+ KeyEncryptionKeyResolver keyEncryptionKeyResolver = new TestKeyEncryptionKeyResolver();
+ CosmosEncryptionAsyncClient cosmosEncryptionAsyncClient = new CosmosEncryptionClientBuilder().cosmosAsyncClient(asyncClient).keyEncryptionKeyResolver(
+ keyEncryptionKeyResolver).keyEncryptionKeyResolverName("TEST_KEY_RESOLVER").buildAsyncClient();
+ CosmosEncryptionAsyncDatabase cosmosEncryptionAsyncDatabase =
+ cosmosEncryptionAsyncClient.getCosmosEncryptionAsyncDatabase(asyncClient.getDatabase(databaseId));
- properties = getItem(UUID.randomUUID().toString());
- CosmosItemResponse itemResponse1 = cosmosEncryptionAsyncContainer.createItem(properties, new CosmosItemRequestOptions()).block();
- assertThat(itemResponse1.getRequestCharge()).isGreaterThan(0);
- EncryptionPojo responseItem1 = itemResponse1.getItem();
- validateResponse(properties, responseItem1);
- actualProperties.add(properties);
+ String containerId = UUID.randomUUID().toString();
+ ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(getPaths(1), 1);
+ createEncryptionContainer(cosmosEncryptionAsyncDatabase, clientEncryptionPolicy, containerId);
+ CosmosEncryptionAsyncContainer encryptionAsyncContainerOriginal =
+ cosmosEncryptionAsyncDatabase.getCosmosEncryptionAsyncContainer(containerId);
- //Upsert Item
- properties = getItem(UUID.randomUUID().toString());
- CosmosItemResponse upsertResponse1 = cosmosEncryptionAsyncContainer.upsertItem(properties).block();
- assertThat(upsertResponse1.getRequestCharge()).isGreaterThan(0);
- EncryptionPojo responseItem2 = upsertResponse1.getItem();
- validateResponse(properties, responseItem2);
- actualProperties.add(properties);
+ List actualProperties = new ArrayList<>();
+ // Read item
+ EncryptionPojo properties = getItem(UUID.randomUUID().toString());
+ CosmosItemResponse itemResponse = encryptionAsyncContainerOriginal.createItem(properties).block();
+ assertThat(itemResponse.getRequestCharge()).isGreaterThan(0);
+ EncryptionPojo responseItem = itemResponse.getItem();
+ validateResponse(properties, responseItem);
+ actualProperties.add(properties);
+
+ properties = getItem(UUID.randomUUID().toString());
+ CosmosItemResponse itemResponse1 = encryptionAsyncContainerOriginal.createItem(properties, new CosmosItemRequestOptions()).block();
+ assertThat(itemResponse1.getRequestCharge()).isGreaterThan(0);
+ EncryptionPojo responseItem1 = itemResponse1.getItem();
+ validateResponse(properties, responseItem1);
+ actualProperties.add(properties);
+
+ //Upsert Item
+ properties = getItem(UUID.randomUUID().toString());
+ CosmosItemResponse upsertResponse1 = encryptionAsyncContainerOriginal.upsertItem(properties).block();
+ assertThat(upsertResponse1.getRequestCharge()).isGreaterThan(0);
+ EncryptionPojo responseItem2 = upsertResponse1.getItem();
+ validateResponse(properties, responseItem2);
+ actualProperties.add(properties);
+
+ properties = getItem(UUID.randomUUID().toString());
+ CosmosItemResponse upsertResponse2 = encryptionAsyncContainerOriginal.upsertItem(properties, new CosmosItemRequestOptions()).block();
+ assertThat(upsertResponse2.getRequestCharge()).isGreaterThan(0);
+ EncryptionPojo responseItem3 = upsertResponse2.getItem();
+ validateResponse(properties, responseItem3);
+ actualProperties.add(properties);
+
+ //Read Item
+ EncryptionPojo readItem = encryptionAsyncContainerOriginal.readItem(actualProperties.get(0).getId(),
+ new PartitionKey(actualProperties.get(0).getMypk()), EncryptionPojo.class).block().getItem();
+ validateResponse(actualProperties.get(0), readItem);
+
+ //Query Item
+ String query = String.format("SELECT * from c where c.id = '%s'", actualProperties.get(1).getId());
- properties = getItem(UUID.randomUUID().toString());
- CosmosItemResponse upsertResponse2 = cosmosEncryptionAsyncContainer.upsertItem(properties, new CosmosItemRequestOptions()).block();
- assertThat(upsertResponse2.getRequestCharge()).isGreaterThan(0);
- EncryptionPojo responseItem3 = upsertResponse2.getItem();
- validateResponse(properties, responseItem3);
- actualProperties.add(properties);
-
- //Read Item
- EncryptionPojo readItem = cosmosEncryptionAsyncContainer.readItem(actualProperties.get(0).getId(),
- new PartitionKey(actualProperties.get(0).getMypk()), EncryptionPojo.class).block().getItem();
- validateResponse(actualProperties.get(0), readItem);
-
- //Query Item
- String query = String.format("SELECT * from c where c.id = '%s'", actualProperties.get(1).getId());
-
- CosmosPagedFlux feedResponseIterator =
- cosmosEncryptionAsyncContainer.queryItems(query, EncryptionPojo.class);
- List feedResponse = feedResponseIterator.byPage().blockFirst().getResults();
- assertThat(feedResponse.size()).isGreaterThanOrEqualTo(1);
- for (EncryptionPojo pojo : feedResponse) {
- if (pojo.getId().equals(actualProperties.get(1).getId())) {
- validateResponse(pojo, responseItem1);
+ CosmosPagedFlux feedResponseIterator =
+ encryptionAsyncContainerOriginal.queryItems(query, EncryptionPojo.class);
+ List feedResponse = feedResponseIterator.byPage().blockFirst().getResults();
+ assertThat(feedResponse.size()).isGreaterThanOrEqualTo(1);
+ for (EncryptionPojo pojo : feedResponse) {
+ if (pojo.getId().equals(actualProperties.get(1).getId())) {
+ validateResponse(pojo, responseItem1);
+ }
}
- }
- CosmosQueryRequestOptions cosmosQueryRequestOptions1 = new CosmosQueryRequestOptions();
+ CosmosQueryRequestOptions cosmosQueryRequestOptions1 = new CosmosQueryRequestOptions();
- CosmosPagedFlux feedResponseIterator1 =
- cosmosEncryptionAsyncContainer.queryItems(query, cosmosQueryRequestOptions1, EncryptionPojo.class);
- List feedResponse1 = feedResponseIterator1.byPage().blockFirst().getResults();
- assertThat(feedResponse1.size()).isGreaterThanOrEqualTo(1);
- for (EncryptionPojo pojo : feedResponse1) {
- if (pojo.getId().equals(actualProperties.get(1).getId())) {
- validateResponse(pojo, responseItem1);
+ CosmosPagedFlux feedResponseIterator1 =
+ encryptionAsyncContainerOriginal.queryItems(query, cosmosQueryRequestOptions1, EncryptionPojo.class);
+ List feedResponse1 = feedResponseIterator1.byPage().blockFirst().getResults();
+ assertThat(feedResponse1.size()).isGreaterThanOrEqualTo(1);
+ for (EncryptionPojo pojo : feedResponse1) {
+ if (pojo.getId().equals(actualProperties.get(1).getId())) {
+ validateResponse(pojo, responseItem1);
+ }
}
- }
- CosmosQueryRequestOptions cosmosQueryRequestOptions2 = new CosmosQueryRequestOptions();
- SqlQuerySpec querySpec = new SqlQuerySpec(query);
+ CosmosQueryRequestOptions cosmosQueryRequestOptions2 = new CosmosQueryRequestOptions();
+ SqlQuerySpec querySpec = new SqlQuerySpec(query);
- CosmosPagedFlux feedResponseIterator2 =
- cosmosEncryptionAsyncContainer.queryItems(querySpec, cosmosQueryRequestOptions2, EncryptionPojo.class);
- List feedResponse2 = feedResponseIterator2.byPage().blockFirst().getResults();
- assertThat(feedResponse2.size()).isGreaterThanOrEqualTo(1);
- for (EncryptionPojo pojo : feedResponse2) {
- if (pojo.getId().equals(actualProperties.get(1).getId())) {
- validateResponse(pojo, responseItem1);
+ CosmosPagedFlux feedResponseIterator2 =
+ encryptionAsyncContainerOriginal.queryItems(querySpec, cosmosQueryRequestOptions2, EncryptionPojo.class);
+ List feedResponse2 = feedResponseIterator2.byPage().blockFirst().getResults();
+ assertThat(feedResponse2.size()).isGreaterThanOrEqualTo(1);
+ for (EncryptionPojo pojo : feedResponse2) {
+ if (pojo.getId().equals(actualProperties.get(1).getId())) {
+ validateResponse(pojo, responseItem1);
+ }
}
- }
- //Replace Item
- CosmosItemResponse replaceResponse =
- cosmosEncryptionAsyncContainer.replaceItem(actualProperties.get(2), actualProperties.get(2).getId(),
- new PartitionKey(actualProperties.get(2).getMypk())).block();
- assertThat(upsertResponse1.getRequestCharge()).isGreaterThan(0);
- responseItem = replaceResponse.getItem();
- validateResponse(actualProperties.get(2), responseItem);
-
- //Delete Item
- CosmosItemResponse> deleteResponse = cosmosEncryptionAsyncContainer.deleteItem(actualProperties.get(0).getId(),
- new PartitionKey(actualProperties.get(0).getMypk())).block();
- assertThat(deleteResponse.getStatusCode()).isEqualTo(204);
-
- CosmosItemResponse> deleteResponse1 = cosmosEncryptionAsyncContainer.deleteItem(actualProperties.get(1).getId(),
- new PartitionKey(actualProperties.get(1).getMypk()), new CosmosItemRequestOptions()).block();
- assertThat(deleteResponse1.getStatusCode()).isEqualTo(204);
-
- CosmosItemResponse> deleteResponse2 = cosmosEncryptionAsyncContainer.deleteItem(actualProperties.get(2),
- new CosmosItemRequestOptions()).block();
- assertThat(deleteResponse2.getStatusCode()).isEqualTo(204);
-
- CosmosItemResponse> deleteResponse3 = cosmosEncryptionAsyncContainer.deleteAllItemsByPartitionKey(new PartitionKey(actualProperties.get(3).getMypk()),
- new CosmosItemRequestOptions()).block();
- assertThat(deleteResponse3.getStatusCode()).isEqualTo(200);
- }
+ //Replace Item
+ CosmosItemResponse replaceResponse =
+ encryptionAsyncContainerOriginal.replaceItem(actualProperties.get(2), actualProperties.get(2).getId(),
+ new PartitionKey(actualProperties.get(2).getMypk())).block();
+ assertThat(upsertResponse1.getRequestCharge()).isGreaterThan(0);
+ responseItem = replaceResponse.getItem();
+ validateResponse(actualProperties.get(2), responseItem);
- static void validateResponseWithOneFieldEncryption(EncryptionPojo originalItem, EncryptionPojo result) {
- assertThat(result.getId()).isEqualTo(originalItem.getId());
- assertThat(result.getNonSensitive()).isEqualTo(originalItem.getNonSensitive());
- assertThat(result.getSensitiveString()).isNotEqualTo(originalItem.getSensitiveString());
- assertThat(result.getSensitiveInt()).isEqualTo(originalItem.getSensitiveInt());
- assertThat(result.getSensitiveFloat()).isEqualTo(originalItem.getSensitiveFloat());
- assertThat(result.getSensitiveLong()).isEqualTo(originalItem.getSensitiveLong());
- assertThat(result.getSensitiveDouble()).isEqualTo(originalItem.getSensitiveDouble());
- assertThat(result.isSensitiveBoolean()).isEqualTo(originalItem.isSensitiveBoolean());
- assertThat(result.getSensitiveIntArray()).isEqualTo(originalItem.getSensitiveIntArray());
- assertThat(result.getSensitiveStringArray()).isEqualTo(originalItem.getSensitiveStringArray());
- assertThat(result.getSensitiveString3DArray()).isEqualTo(originalItem.getSensitiveString3DArray());
- }
-
- public static List getPathWithOneEncryptionField() {
- ClientEncryptionIncludedPath includedPath = new ClientEncryptionIncludedPath();
- includedPath.setClientEncryptionKeyId("key1");
- includedPath.setPath("/sensitiveString");
- includedPath.setEncryptionType(CosmosEncryptionType.DETERMINISTIC.toString());
- includedPath.setEncryptionAlgorithm(CosmosEncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA256.getName());
-
- List paths = new ArrayList<>();
- paths.add(includedPath);
- return paths;
- }
+ //Delete Item
+ CosmosItemResponse> deleteResponse = encryptionAsyncContainerOriginal.deleteItem(actualProperties.get(0).getId(),
+ new PartitionKey(actualProperties.get(0).getMypk())).block();
+ assertThat(deleteResponse.getStatusCode()).isEqualTo(204);
+ CosmosItemResponse> deleteResponse1 = encryptionAsyncContainerOriginal.deleteItem(actualProperties.get(1).getId(),
+ new PartitionKey(actualProperties.get(1).getMypk()), new CosmosItemRequestOptions()).block();
+ assertThat(deleteResponse1.getStatusCode()).isEqualTo(204);
+
+ CosmosItemResponse> deleteResponse2 = encryptionAsyncContainerOriginal.deleteItem(actualProperties.get(2),
+ new CosmosItemRequestOptions()).block();
+ assertThat(deleteResponse2.getStatusCode()).isEqualTo(204);
+
+ CosmosItemResponse> deleteResponse3 = encryptionAsyncContainerOriginal.deleteAllItemsByPartitionKey(new PartitionKey(actualProperties.get(3).getMypk()),
+ new CosmosItemRequestOptions()).block();
+ assertThat(deleteResponse3.getStatusCode()).isEqualTo(200);
+ } finally {
+ try {
+ //deleting the database created for this test
+ this.client.getDatabase(databaseId).delete().block();
+ } catch (Exception e) {
+ // do nothing as we are clearing database created for this test
+ }
+ }
+
+ }
private void createEncryptionContainer(CosmosEncryptionAsyncDatabase cosmosEncryptionAsyncDatabase,
ClientEncryptionPolicy clientEncryptionPolicy,
String containerId) {
@@ -1055,15 +648,4 @@ private void createNewDatabaseWithClientEncryptionKey(String databaseId){
encryptionAsyncDatabase.createClientEncryptionKey("key2",
CosmosEncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA256.getName(), metadata2).block();
}
-
- private CosmosEncryptionAsyncContainer getNewEncryptionContainerProxyObject(String databaseId, String containerId) {
- CosmosAsyncClient client = getClientBuilder().buildAsyncClient();
- KeyEncryptionKeyResolver keyEncryptionKeyResolver = new TestKeyEncryptionKeyResolver();
- CosmosEncryptionAsyncClient cosmosEncryptionAsyncClient = new CosmosEncryptionClientBuilder().cosmosAsyncClient(client).keyEncryptionKeyResolver(
- keyEncryptionKeyResolver).keyEncryptionKeyResolverName("TEST_KEY_RESOLVER").buildAsyncClient();
- CosmosEncryptionAsyncDatabase cosmosEncryptionAsyncDatabase =
- cosmosEncryptionAsyncClient.getCosmosEncryptionAsyncDatabase(client.getDatabase(databaseId));
- CosmosEncryptionAsyncContainer cosmosEncryptionAsyncContainer = cosmosEncryptionAsyncDatabase.getCosmosEncryptionAsyncContainer(containerId);
- return cosmosEncryptionAsyncContainer;
- }
}
diff --git a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionAsyncApiHierarchicalPkTest.java b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionAsyncApiHierarchicalPkTest.java
new file mode 100644
index 0000000000000..445812cfb0621
--- /dev/null
+++ b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionAsyncApiHierarchicalPkTest.java
@@ -0,0 +1,87 @@
+package com.azure.cosmos.encryption;
+
+import com.azure.core.cryptography.KeyEncryptionKeyResolver;
+import com.azure.cosmos.CosmosAsyncClient;
+import com.azure.cosmos.CosmosClientBuilder;
+import com.azure.cosmos.models.ClientEncryptionPolicy;
+import com.azure.cosmos.models.CosmosContainerProperties;
+import com.azure.cosmos.models.CosmosItemRequestOptions;
+import com.azure.cosmos.models.CosmosItemResponse;
+import com.azure.cosmos.models.PartitionKey;
+import com.azure.cosmos.models.PartitionKeyDefinition;
+import com.azure.cosmos.models.PartitionKeyDefinitionVersion;
+import com.azure.cosmos.models.PartitionKind;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class EncryptionAsyncApiHierarchicalPkTest extends TestSuiteBase {
+ private CosmosAsyncClient client;
+ private CosmosEncryptionAsyncClient cosmosEncryptionAsyncClient;
+
+ CosmosEncryptionAsyncContainer cosmosEncryptionAsyncContainer;
+ CosmosEncryptionAsyncDatabase cosmosEncryptionAsyncDatabase;
+
+ @Factory(dataProvider = "clientBuildersWithSessionConsistency")
+ public EncryptionAsyncApiHierarchicalPkTest(CosmosClientBuilder clientBuilder) {
+ super(clientBuilder);
+ }
+
+ @BeforeClass(groups = {"encryption"}, timeOut = SETUP_TIMEOUT)
+ public void before_CosmosItemTest() {
+ assertThat(this.client).isNull();
+ this.client = getClientBuilder().buildAsyncClient();
+ KeyEncryptionKeyResolver keyEncryptionKeyResolver = new TestKeyEncryptionKeyResolver();
+ cosmosEncryptionAsyncClient = new CosmosEncryptionClientBuilder().cosmosAsyncClient(this.client).keyEncryptionKeyResolver(
+ keyEncryptionKeyResolver).keyEncryptionKeyResolverName("TEST_KEY_RESOLVER").buildAsyncClient();
+ cosmosEncryptionAsyncDatabase = getSharedEncryptionDatabase(cosmosEncryptionAsyncClient);
+ cosmosEncryptionAsyncContainer = getSharedEncryptionContainer(cosmosEncryptionAsyncClient);
+
+ PartitionKeyDefinition partitionKeyDefinition = new PartitionKeyDefinition();
+ partitionKeyDefinition.setKind(PartitionKind.MULTI_HASH);
+ partitionKeyDefinition.setVersion(PartitionKeyDefinitionVersion.V2);
+ List subParitionKeyPaths = new ArrayList<>();
+ subParitionKeyPaths.add("/sensitiveNestedPojo");
+ subParitionKeyPaths.add("/mypk");
+ partitionKeyDefinition.setPaths(subParitionKeyPaths);
+
+ ClientEncryptionPolicy clientEncryptionWithPolicyFormatVersion2 = new ClientEncryptionPolicy(getPaths(2), 2);
+ String containerId = UUID.randomUUID().toString();
+ CosmosContainerProperties properties = getCollectionDefinition(containerId, partitionKeyDefinition);
+ properties.setClientEncryptionPolicy(clientEncryptionWithPolicyFormatVersion2);
+ cosmosEncryptionAsyncDatabase.getCosmosAsyncDatabase().createContainer(properties).block();
+
+ }
+
+ @AfterClass(groups = {"encryption"}, timeOut = SHUTDOWN_TIMEOUT, alwaysRun = true)
+ public void afterClass() {
+ assertThat(this.client).isNotNull();
+ this.client.close();
+ }
+
+ @Test(groups = {"encryption"}, timeOut = TIMEOUT)
+ public void crudOnHierarchicalPk() {
+ EncryptionPojo properties = getItem(UUID.randomUUID().toString());
+ CosmosItemResponse itemResponse = cosmosEncryptionAsyncContainer.createItem(properties,
+ new PartitionKey(properties.getMypk()), new CosmosItemRequestOptions()).block();
+ assertThat(itemResponse.getRequestCharge()).isGreaterThan(0);
+ EncryptionPojo responseItem = itemResponse.getItem();
+ validateResponse(properties, responseItem);
+
+ EncryptionPojo readItem = cosmosEncryptionAsyncContainer.readItem(properties.getId(), new PartitionKey(properties.getMypk()),
+ new CosmosItemRequestOptions(), EncryptionPojo.class).block().getItem();
+ validateResponse(properties, readItem);
+
+ // Deleting this item so query max of string in the query_aggregate test passes
+ cosmosEncryptionAsyncContainer.deleteItem(properties.getId(), new PartitionKey(properties.getMypk())).block();
+ }
+
+
+}
diff --git a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionAsyncApiQueryTest.java b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionAsyncApiQueryTest.java
new file mode 100644
index 0000000000000..2a431b3e20e07
--- /dev/null
+++ b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionAsyncApiQueryTest.java
@@ -0,0 +1,530 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.cosmos.encryption;
+
+import com.azure.core.cryptography.KeyEncryptionKeyResolver;
+import com.azure.cosmos.CosmosAsyncClient;
+import com.azure.cosmos.CosmosClientBuilder;
+import com.azure.cosmos.encryption.models.CosmosEncryptionAlgorithm;
+import com.azure.cosmos.encryption.models.CosmosEncryptionType;
+import com.azure.cosmos.encryption.models.SqlQuerySpecWithEncryption;
+import com.azure.cosmos.models.ClientEncryptionIncludedPath;
+import com.azure.cosmos.models.ClientEncryptionPolicy;
+import com.azure.cosmos.models.CosmosBatch;
+import com.azure.cosmos.models.CosmosBatchResponse;
+import com.azure.cosmos.models.CosmosContainerProperties;
+import com.azure.cosmos.models.CosmosItemRequestOptions;
+import com.azure.cosmos.models.CosmosItemResponse;
+import com.azure.cosmos.models.CosmosPatchItemRequestOptions;
+import com.azure.cosmos.models.CosmosPatchOperations;
+import com.azure.cosmos.models.CosmosQueryRequestOptions;
+import com.azure.cosmos.models.EncryptionKeyWrapMetadata;
+import com.azure.cosmos.models.FeedResponse;
+import com.azure.cosmos.models.PartitionKey;
+import com.azure.cosmos.models.SqlParameter;
+import com.azure.cosmos.models.SqlQuerySpec;
+import com.azure.cosmos.util.CosmosPagedFlux;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+
+public class EncryptionAsyncApiQueryTest extends TestSuiteBase {
+ private CosmosAsyncClient client;
+ private CosmosEncryptionAsyncClient cosmosEncryptionAsyncClient;
+
+ CosmosEncryptionAsyncContainer cosmosEncryptionAsyncContainer;
+ CosmosEncryptionAsyncDatabase cosmosEncryptionAsyncDatabase;
+
+ @Factory(dataProvider = "clientBuildersWithSessionConsistency")
+ public EncryptionAsyncApiQueryTest(CosmosClientBuilder clientBuilder) {
+ super(clientBuilder);
+ }
+
+ @BeforeClass(groups = {"encryption"}, timeOut = SETUP_TIMEOUT)
+ public void before_CosmosItemTest() {
+ assertThat(this.client).isNull();
+ this.client = getClientBuilder().buildAsyncClient();
+ KeyEncryptionKeyResolver keyEncryptionKeyResolver = new TestKeyEncryptionKeyResolver();
+ cosmosEncryptionAsyncClient = new CosmosEncryptionClientBuilder().cosmosAsyncClient(this.client).keyEncryptionKeyResolver(
+ keyEncryptionKeyResolver).keyEncryptionKeyResolverName("TEST_KEY_RESOLVER").buildAsyncClient();
+ cosmosEncryptionAsyncDatabase = getSharedEncryptionDatabase(cosmosEncryptionAsyncClient);
+ cosmosEncryptionAsyncContainer = getSharedEncryptionContainer(cosmosEncryptionAsyncClient);
+
+ ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(getPaths(1), 1);
+ String containerId = UUID.randomUUID().toString();
+ CosmosContainerProperties properties = new CosmosContainerProperties(containerId, "/mypk");
+ properties.setClientEncryptionPolicy(clientEncryptionPolicy);
+ cosmosEncryptionAsyncDatabase.getCosmosAsyncDatabase().createContainer(properties).block();
+ }
+
+ @AfterClass(groups = {"encryption"}, timeOut = SHUTDOWN_TIMEOUT, alwaysRun = true)
+ public void afterClass() {
+ assertThat(this.client).isNotNull();
+ this.client.close();
+ }
+
+ @Test(groups = {"encryption"}, timeOut = TIMEOUT)
+ public void queryItems() {
+ EncryptionPojo properties = getItem(UUID.randomUUID().toString());
+ CosmosItemResponse itemResponse = cosmosEncryptionAsyncContainer.createItem(properties,
+ new PartitionKey(properties.getMypk()), new CosmosItemRequestOptions()).block();
+ assertThat(itemResponse.getRequestCharge()).isGreaterThan(0);
+ EncryptionPojo responseItem = itemResponse.getItem();
+ validateResponse(properties, responseItem);
+
+ String query = String.format("SELECT * from c where c.id = '%s'", properties.getId());
+ CosmosQueryRequestOptions cosmosQueryRequestOptions = new CosmosQueryRequestOptions();
+
+ SqlQuerySpec querySpec = new SqlQuerySpec(query);
+ CosmosPagedFlux feedResponseIterator =
+ cosmosEncryptionAsyncContainer.queryItems(querySpec, cosmosQueryRequestOptions, EncryptionPojo.class);
+ List feedResponse = feedResponseIterator.byPage().blockFirst().getResults();
+ assertThat(feedResponse.size()).isGreaterThanOrEqualTo(1);
+ for (EncryptionPojo pojo : feedResponse) {
+ if (pojo.getId().equals(properties.getId())) {
+ validateResponse(pojo, responseItem);
+ }
+ }
+ }
+
+ @Test(groups = {"encryption"}, timeOut = TIMEOUT)
+ public void queryItemsAggregate() {
+ long startTime = Instant.now().getEpochSecond();
+ List actualIds = new ArrayList<>();
+ EncryptionPojo properties = getItem(UUID.randomUUID().toString());
+ cosmosEncryptionAsyncContainer.createItem(properties, new PartitionKey(properties.getMypk()),
+ new CosmosItemRequestOptions()).block();
+ actualIds.add(properties.getId());
+ properties = getItem(UUID.randomUUID().toString());
+ cosmosEncryptionAsyncContainer.createItem(properties, new PartitionKey(properties.getMypk()),
+ new CosmosItemRequestOptions()).block();
+ actualIds.add(properties.getId());
+ properties = getItem(UUID.randomUUID().toString());
+ cosmosEncryptionAsyncContainer.createItem(properties, new PartitionKey(properties.getMypk()),
+ new CosmosItemRequestOptions()).block();
+ actualIds.add(properties.getId());
+
+ // MAX query
+ String query1 = String.format("Select value max(c._ts) from c");
+ CosmosQueryRequestOptions cosmosQueryRequestOptions1 = new CosmosQueryRequestOptions();
+
+ SqlQuerySpec querySpec1 = new SqlQuerySpec(query1);
+ CosmosPagedFlux feedResponseIterator1 =
+ cosmosEncryptionAsyncContainer.queryItems(querySpec1, cosmosQueryRequestOptions1, Integer.class);
+ List feedResponse1 = feedResponseIterator1.byPage().blockFirst().getResults();
+ int timeStamp = feedResponse1.get(0);
+ long endTime = Instant.now().getEpochSecond();
+
+ assertThat(timeStamp).isGreaterThanOrEqualTo((int)startTime);
+ assertThat(timeStamp).isLessThanOrEqualTo((int)endTime);
+ assertThat(feedResponse1.size()).isEqualTo(1);
+
+ // COUNT query
+ String query2 = String.format("Select top 1 value count(c) from c order by c._ts");
+ CosmosQueryRequestOptions cosmosQueryRequestOptions2 = new CosmosQueryRequestOptions();
+
+ SqlQuerySpec querySpec2 = new SqlQuerySpec(query2);
+ CosmosPagedFlux feedResponseIterator2 =
+ cosmosEncryptionAsyncContainer.queryItems(querySpec2, cosmosQueryRequestOptions2, Integer.class);
+ List feedResponse2 = feedResponseIterator2.byPage().blockFirst().getResults();
+ assertThat(feedResponse2.size()).isEqualTo(1);
+
+ // MAX query for String class type
+ String query3 = String.format("Select value max(c.sensitiveString) from c");
+ CosmosQueryRequestOptions cosmosQueryRequestOptions3 = new CosmosQueryRequestOptions();
+
+ SqlQuerySpec querySpec3 = new SqlQuerySpec(query3);
+ CosmosPagedFlux feedResponseIterator3 =
+ cosmosEncryptionAsyncContainer.queryItems(querySpec3, cosmosQueryRequestOptions3, String.class);
+ List feedResponse3 = feedResponseIterator3.byPage().blockFirst().getResults();
+ assertThat(feedResponse3.size()).isEqualTo(1);
+ }
+
+ @Test(groups = {"encryption"}, timeOut = TIMEOUT)
+ public void queryItemsOnEncryptedProperties() {
+ EncryptionPojo properties = getItem(UUID.randomUUID().toString());
+ CosmosItemResponse itemResponse = cosmosEncryptionAsyncContainer.createItem(properties,
+ new PartitionKey(properties.getMypk()), new CosmosItemRequestOptions()).block();
+ assertThat(itemResponse.getRequestCharge()).isGreaterThan(0);
+ EncryptionPojo responseItem = itemResponse.getItem();
+ validateResponse(properties, responseItem);
+
+ String query = String.format("SELECT * FROM c where c.sensitiveString = @sensitiveString and c.nonSensitive =" +
+ " " +
+ "@nonSensitive and c.sensitiveLong = @sensitiveLong");
+ SqlQuerySpec querySpec = new SqlQuerySpec(query);
+ SqlParameter parameter1 = new SqlParameter("@nonSensitive", properties.getNonSensitive());
+ querySpec.getParameters().add(parameter1);
+
+ SqlParameter parameter2 = new SqlParameter("@sensitiveString", properties.getSensitiveString());
+ SqlParameter parameter3 = new SqlParameter("@sensitiveLong", properties.getSensitiveLong());
+ SqlQuerySpecWithEncryption sqlQuerySpecWithEncryption = new SqlQuerySpecWithEncryption(querySpec);
+ sqlQuerySpecWithEncryption.addEncryptionParameter("/sensitiveString", parameter2);
+ sqlQuerySpecWithEncryption.addEncryptionParameter("/sensitiveLong", parameter3);
+
+ CosmosQueryRequestOptions cosmosQueryRequestOptions = new CosmosQueryRequestOptions();
+ CosmosPagedFlux feedResponseIterator =
+ cosmosEncryptionAsyncContainer.queryItemsOnEncryptedProperties(sqlQuerySpecWithEncryption,
+ cosmosQueryRequestOptions, EncryptionPojo.class);
+ List feedResponse = feedResponseIterator.byPage().blockFirst().getResults();
+ assertThat(feedResponse.size()).isGreaterThanOrEqualTo(1);
+ for (EncryptionPojo pojo : feedResponse) {
+ if (pojo.getId().equals(properties.getId())) {
+ validateResponse(pojo, responseItem);
+ }
+ }
+ }
+
+ @Test(groups = {"encryption"}, timeOut = TIMEOUT)
+ public void queryItemsOnRandomizedEncryption() {
+ EncryptionPojo properties = getItem(UUID.randomUUID().toString());
+ CosmosItemResponse itemResponse = cosmosEncryptionAsyncContainer.createItem(properties,
+ new PartitionKey(properties.getMypk()), new CosmosItemRequestOptions()).block();
+ assertThat(itemResponse.getRequestCharge()).isGreaterThan(0);
+ EncryptionPojo responseItem = itemResponse.getItem();
+ validateResponse(properties, responseItem);
+
+ String query = String.format("SELECT * FROM c where c.sensitiveString = @sensitiveString and c.nonSensitive =" +
+ " " +
+ "@nonSensitive and c.sensitiveDouble = @sensitiveDouble");
+ SqlQuerySpec querySpec = new SqlQuerySpec(query);
+ SqlParameter parameter1 = new SqlParameter("@nonSensitive", properties.getNonSensitive());
+ querySpec.getParameters().add(parameter1);
+
+ SqlParameter parameter2 = new SqlParameter("@sensitiveString", properties.getSensitiveString());
+ SqlParameter parameter3 = new SqlParameter("@sensitiveDouble", properties.getSensitiveDouble());
+ SqlQuerySpecWithEncryption sqlQuerySpecWithEncryption = new SqlQuerySpecWithEncryption(querySpec);
+ sqlQuerySpecWithEncryption.addEncryptionParameter("/sensitiveString", parameter2);
+ sqlQuerySpecWithEncryption.addEncryptionParameter("/sensitiveDouble", parameter3);
+
+ CosmosQueryRequestOptions cosmosQueryRequestOptions = new CosmosQueryRequestOptions();
+ CosmosPagedFlux feedResponseIterator =
+ cosmosEncryptionAsyncContainer.queryItemsOnEncryptedProperties(sqlQuerySpecWithEncryption,
+ cosmosQueryRequestOptions, EncryptionPojo.class);
+ try {
+ List feedResponse = feedResponseIterator.byPage().blockFirst().getResults();
+ fail("Query on randomized parameter should fail");
+ } catch (IllegalArgumentException ex) {
+ assertThat(ex.getMessage()).contains("Path /sensitiveDouble cannot be used in the " +
+ "query because of randomized encryption");
+ }
+ }
+
+ @Test(groups = {"encryption"}, timeOut = TIMEOUT)
+ public void queryItemsWithContinuationTokenAndPageSize() {
+ List actualIds = new ArrayList<>();
+ EncryptionPojo properties = getItem(UUID.randomUUID().toString());
+ cosmosEncryptionAsyncContainer.createItem(properties, new PartitionKey(properties.getMypk()),
+ new CosmosItemRequestOptions()).block();
+ actualIds.add(properties.getId());
+ properties = getItem(UUID.randomUUID().toString());
+ cosmosEncryptionAsyncContainer.createItem(properties, new PartitionKey(properties.getMypk()),
+ new CosmosItemRequestOptions()).block();
+ actualIds.add(properties.getId());
+ properties = getItem(UUID.randomUUID().toString());
+ cosmosEncryptionAsyncContainer.createItem(properties, new PartitionKey(properties.getMypk()),
+ new CosmosItemRequestOptions()).block();
+ actualIds.add(properties.getId());
+
+ String query = String.format("SELECT * from c where c.id in ('%s', '%s', '%s')", actualIds.get(0),
+ actualIds.get(1), actualIds.get(2));
+ CosmosQueryRequestOptions cosmosQueryRequestOptions = new CosmosQueryRequestOptions();
+ String continuationToken = null;
+ int pageSize = 1;
+
+ int initialDocumentCount = 3;
+ int finalDocumentCount = 0;
+
+ CosmosPagedFlux feedResponseIterator =
+ cosmosEncryptionAsyncContainer.queryItems(query, cosmosQueryRequestOptions, EncryptionPojo.class);
+
+ do {
+ Iterable> feedResponseIterable =
+ feedResponseIterator.byPage(continuationToken, 1).toIterable();
+ for (FeedResponse fr : feedResponseIterable) {
+ int resultSize = fr.getResults().size();
+ assertThat(resultSize).isEqualTo(pageSize);
+ finalDocumentCount += fr.getResults().size();
+ continuationToken = fr.getContinuationToken();
+ }
+ } while (continuationToken != null);
+
+ assertThat(finalDocumentCount).isEqualTo(initialDocumentCount);
+ }
+
+ @Test(groups = {"encryption"}, timeOut = TIMEOUT)
+ public void crudQueryStaleCache() {
+ String databaseId = UUID.randomUUID().toString();
+ try {
+ createNewDatabaseWithClientEncryptionKey(databaseId);
+ CosmosAsyncClient asyncClient = getClientBuilder().buildAsyncClient();
+ KeyEncryptionKeyResolver keyEncryptionKeyResolver = new TestKeyEncryptionKeyResolver();
+ CosmosEncryptionAsyncClient cosmosEncryptionAsyncClient = new CosmosEncryptionClientBuilder().cosmosAsyncClient(asyncClient).keyEncryptionKeyResolver(
+ keyEncryptionKeyResolver).keyEncryptionKeyResolverName("TEST_KEY_RESOLVER").buildAsyncClient();
+ CosmosEncryptionAsyncDatabase cosmosEncryptionAsyncDatabase =
+ cosmosEncryptionAsyncClient.getCosmosEncryptionAsyncDatabase(asyncClient.getDatabase(databaseId));
+
+ String containerId = UUID.randomUUID().toString();
+ ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(getPaths(1), 1);
+ createEncryptionContainer(cosmosEncryptionAsyncDatabase, clientEncryptionPolicy, containerId);
+ CosmosEncryptionAsyncContainer encryptionAsyncContainerOriginal =
+ cosmosEncryptionAsyncDatabase.getCosmosEncryptionAsyncContainer(containerId);
+
+ EncryptionPojo encryptionPojo = getItem(UUID.randomUUID().toString());
+ CosmosItemResponse createResponse = encryptionAsyncContainerOriginal.createItem(encryptionPojo,
+ new PartitionKey(encryptionPojo.getMypk()), new CosmosItemRequestOptions()).block();
+ validateResponse(encryptionPojo, createResponse.getItem());
+
+ String query = String.format("SELECT * from c where c.id = '%s'", encryptionPojo.getId());
+ SqlQuerySpec querySpec = new SqlQuerySpec(query);
+ CosmosPagedFlux feedResponseIterator =
+ encryptionAsyncContainerOriginal.queryItems(querySpec, null, EncryptionPojo.class);
+ List feedResponse = feedResponseIterator.byPage().blockFirst().getResults();
+
+ EncryptionPojo readItem = encryptionAsyncContainerOriginal.readItem(encryptionPojo.getId(),
+ new PartitionKey(encryptionPojo.getMypk()),
+ new CosmosItemRequestOptions(), EncryptionPojo.class).block().getItem();
+ validateResponse(encryptionPojo, readItem);
+
+ //Deleting database and creating database, container again
+ cosmosEncryptionAsyncDatabase.getCosmosAsyncDatabase().delete().block();
+ createNewDatabaseWithClientEncryptionKey(databaseId);
+ createEncryptionContainer(cosmosEncryptionAsyncDatabase, clientEncryptionPolicy, containerId);
+
+ //Validating create on original encryptionAsyncContainer
+ createResponse = encryptionAsyncContainerOriginal.createItem(encryptionPojo,
+ new PartitionKey(encryptionPojo.getMypk()), new CosmosItemRequestOptions()).block();
+ validateResponse(encryptionPojo, createResponse.getItem());
+
+ //Deleting and creating container
+ encryptionAsyncContainerOriginal.getCosmosAsyncContainer().delete().block();
+ ClientEncryptionPolicy policyWithOneEncryptionPolicy = new ClientEncryptionPolicy(getPathWithOneEncryptionField());
+ createEncryptionContainer(cosmosEncryptionAsyncDatabase, policyWithOneEncryptionPolicy, containerId);
+ CosmosEncryptionAsyncContainer encryptionAsyncContainerNew = getNewEncryptionContainerProxyObject(cosmosEncryptionAsyncDatabase.getCosmosAsyncDatabase().getId(), containerId);
+ encryptionAsyncContainerNew.createItem(encryptionPojo,
+ new PartitionKey(encryptionPojo.getMypk()), new CosmosItemRequestOptions()).block();
+ EncryptionPojo pojoWithOneFieldEncrypted = encryptionAsyncContainerNew.getCosmosAsyncContainer().readItem(encryptionPojo.getId(), new PartitionKey(encryptionPojo.getMypk()),
+ new CosmosItemRequestOptions(), EncryptionPojo.class).block().getItem();
+ validateResponseWithOneFieldEncryption(encryptionPojo, pojoWithOneFieldEncrypted);
+
+ //Validating read on original encryptionAsyncContainer
+ readItem = encryptionAsyncContainerOriginal.readItem(encryptionPojo.getId(), new PartitionKey(encryptionPojo.getMypk()),
+ new CosmosItemRequestOptions(), EncryptionPojo.class).block().getItem();
+ validateResponse(encryptionPojo, readItem);
+
+ //Deleting and creating container
+ encryptionAsyncContainerOriginal.getCosmosAsyncContainer().delete().block();
+ createEncryptionContainer(cosmosEncryptionAsyncDatabase, clientEncryptionPolicy, containerId);
+
+ CosmosItemResponse upsertResponse = encryptionAsyncContainerOriginal.upsertItem(encryptionPojo,
+ new PartitionKey(encryptionPojo.getMypk()), new CosmosItemRequestOptions()).block();
+ assertThat(upsertResponse.getRequestCharge()).isGreaterThan(0);
+ EncryptionPojo responseItem = upsertResponse.getItem();
+ validateResponse(encryptionPojo, responseItem);
+
+ //Deleting and creating container
+ encryptionAsyncContainerOriginal.getCosmosAsyncContainer().delete().block();
+ createEncryptionContainer(cosmosEncryptionAsyncDatabase, clientEncryptionPolicy, containerId);
+ encryptionAsyncContainerNew = getNewEncryptionContainerProxyObject(cosmosEncryptionAsyncDatabase.getCosmosAsyncDatabase().getId(), containerId);
+ encryptionAsyncContainerNew.createItem(encryptionPojo,
+ new PartitionKey(encryptionPojo.getMypk()), new CosmosItemRequestOptions()).block();
+
+
+ CosmosItemResponse replaceResponse =
+ encryptionAsyncContainerOriginal.replaceItem(encryptionPojo, encryptionPojo.getId(),
+ new PartitionKey(encryptionPojo.getMypk()), new CosmosItemRequestOptions()).block();
+ assertThat(upsertResponse.getRequestCharge()).isGreaterThan(0);
+ responseItem = replaceResponse.getItem();
+ validateResponse(encryptionPojo, responseItem);
+
+ // First query fail on core sdk as there will be no pkrange cache, and collection cache have wrong information of collection rid,
+ // pkrange call will fail will null pointer, therefore querying before deleting the container making sure we have pkrange cache to begin with
+ encryptionAsyncContainerOriginal.queryItems(querySpec, null, EncryptionPojo.class).byPage().blockFirst().getResults();
+ //Deleting and creating container
+ encryptionAsyncContainerOriginal.getCosmosAsyncContainer().delete().block();
+ createEncryptionContainer(cosmosEncryptionAsyncDatabase, clientEncryptionPolicy, containerId);
+ CosmosEncryptionAsyncContainer newEncryptionAsyncContainer = getNewEncryptionContainerProxyObject(cosmosEncryptionAsyncDatabase.getCosmosAsyncDatabase().getId(), containerId);
+
+ for (int i = 0; i < 10; i++) {
+ EncryptionPojo pojo = getItem(UUID.randomUUID().toString());
+ newEncryptionAsyncContainer.createItem(pojo,
+ new PartitionKey(pojo.getMypk()), new CosmosItemRequestOptions()).block();
+ }
+
+ feedResponseIterator =
+ encryptionAsyncContainerOriginal.queryItems("Select * from C", null, EncryptionPojo.class);
+ String continuationToken = null;
+ int pageSize = 3;
+ int finalDocumentCount = 0;
+ do {
+ Iterable> feedResponseIterable =
+ feedResponseIterator.byPage(continuationToken, pageSize).toIterable();
+ for (FeedResponse fr : feedResponseIterable) {
+ int resultSize = fr.getResults().size();
+ assertThat(resultSize).isLessThanOrEqualTo(pageSize);
+ finalDocumentCount += fr.getResults().size();
+ continuationToken = fr.getContinuationToken();
+ }
+ } while (continuationToken != null);
+
+ assertThat(finalDocumentCount).isEqualTo(10);
+
+
+ //Deleting and creating container
+ encryptionAsyncContainerOriginal.getCosmosAsyncContainer().delete().block();
+ createEncryptionContainer(cosmosEncryptionAsyncDatabase, clientEncryptionPolicy, containerId);
+ newEncryptionAsyncContainer = getNewEncryptionContainerProxyObject(cosmosEncryptionAsyncDatabase.getCosmosAsyncDatabase().getId(), containerId);
+
+ EncryptionPojo encryptionPojoForQueryItemsOnEncryptedProperties = getItem(UUID.randomUUID().toString());
+ newEncryptionAsyncContainer.createItem(encryptionPojoForQueryItemsOnEncryptedProperties,
+ new PartitionKey(encryptionPojoForQueryItemsOnEncryptedProperties.getMypk()), new CosmosItemRequestOptions()).block();
+
+ query = String.format("SELECT * FROM c where c.sensitiveString = @sensitiveString and c.nonSensitive =" +
+ " " +
+ "@nonSensitive and c.sensitiveLong = @sensitiveLong");
+ querySpec = new SqlQuerySpec(query);
+ SqlParameter parameter1 = new SqlParameter("@nonSensitive", encryptionPojoForQueryItemsOnEncryptedProperties.getNonSensitive());
+ querySpec.getParameters().add(parameter1);
+
+ SqlParameter parameter2 = new SqlParameter("@sensitiveString", encryptionPojoForQueryItemsOnEncryptedProperties.getSensitiveString());
+ SqlParameter parameter3 = new SqlParameter("@sensitiveLong", encryptionPojoForQueryItemsOnEncryptedProperties.getSensitiveLong());
+ SqlQuerySpecWithEncryption sqlQuerySpecWithEncryption = new SqlQuerySpecWithEncryption(querySpec);
+ sqlQuerySpecWithEncryption.addEncryptionParameter("/sensitiveString", parameter2);
+ sqlQuerySpecWithEncryption.addEncryptionParameter("/sensitiveLong", parameter3);
+
+ feedResponseIterator =
+ encryptionAsyncContainerOriginal.queryItemsOnEncryptedProperties(sqlQuerySpecWithEncryption,
+ null, EncryptionPojo.class);
+ feedResponse = feedResponseIterator.byPage().blockFirst().getResults();
+ assertThat(feedResponse.size()).isGreaterThanOrEqualTo(1);
+ for (EncryptionPojo pojo : feedResponse) {
+ if (pojo.getId().equals(encryptionPojoForQueryItemsOnEncryptedProperties.getId())) {
+ validateResponse(encryptionPojoForQueryItemsOnEncryptedProperties, pojo);
+ }
+ }
+
+ //Deleting and creating container
+ encryptionAsyncContainerOriginal.getCosmosAsyncContainer().delete().block();
+ createEncryptionContainer(cosmosEncryptionAsyncDatabase, clientEncryptionPolicy, containerId);
+
+ String itemId= UUID.randomUUID().toString();
+ EncryptionPojo createPojo = getItem(itemId);
+ CosmosBatch cosmosEncryptionBatch = CosmosBatch.createCosmosBatch(new PartitionKey(itemId));
+ cosmosEncryptionBatch.createItemOperation(createPojo);
+ cosmosEncryptionBatch.readItemOperation(itemId);
+
+ CosmosBatchResponse batchResponse = encryptionAsyncContainerOriginal.executeCosmosBatch(cosmosEncryptionBatch).block();
+ assertThat(batchResponse.getResults().size()).isEqualTo(2);
+ validateResponse(createPojo, batchResponse.getResults().get(0).getItem(EncryptionPojo.class));
+ validateResponse(createPojo, batchResponse.getResults().get(1).getItem(EncryptionPojo.class));
+
+ //Deleting and creating container
+ encryptionAsyncContainerOriginal.getCosmosAsyncContainer().delete().block();
+ createEncryptionContainer(cosmosEncryptionAsyncDatabase, clientEncryptionPolicy, containerId);
+
+ itemId= UUID.randomUUID().toString();
+ createPojo = getItem(itemId);
+ CosmosItemResponse itemResponse = encryptionAsyncContainerOriginal.createItem(createPojo,
+ new PartitionKey(createPojo.getMypk()), new CosmosItemRequestOptions()).block();
+
+ int originalSensitiveInt = createPojo.getSensitiveInt();
+ int newSensitiveInt = originalSensitiveInt + 1;
+
+ CosmosPatchOperations cosmosPatchOperations = CosmosPatchOperations.create();
+ cosmosPatchOperations.add("/sensitiveString", "patched");
+ cosmosPatchOperations.remove("/sensitiveDouble");
+ cosmosPatchOperations.replace("/sensitiveInt", newSensitiveInt);
+
+ CosmosItemResponse patchResponse = encryptionAsyncContainerOriginal.patchItem(
+ createPojo.getId(),
+ new PartitionKey(createPojo.getMypk()),
+ cosmosPatchOperations,
+ new CosmosPatchItemRequestOptions(),
+ EncryptionPojo.class).block();
+
+ CosmosItemResponse readResponse = encryptionAsyncContainerOriginal.readItem(
+ createPojo.getId(),
+ new PartitionKey(createPojo.getMypk()),
+ new CosmosPatchItemRequestOptions(),
+ EncryptionPojo.class).block();
+
+ validateResponse(patchResponse.getItem(), readResponse.getItem());
+
+ } finally {
+ try {
+ //deleting the database created for this test
+ this.client.getDatabase(databaseId).delete().block();
+ } catch(Exception ex) {
+ // do nothing as we are clearing database created for this test
+ }
+ }
+ }
+
+ static void validateResponseWithOneFieldEncryption(EncryptionPojo originalItem, EncryptionPojo result) {
+ assertThat(result.getId()).isEqualTo(originalItem.getId());
+ assertThat(result.getNonSensitive()).isEqualTo(originalItem.getNonSensitive());
+ assertThat(result.getSensitiveString()).isNotEqualTo(originalItem.getSensitiveString());
+ assertThat(result.getSensitiveInt()).isEqualTo(originalItem.getSensitiveInt());
+ assertThat(result.getSensitiveFloat()).isEqualTo(originalItem.getSensitiveFloat());
+ assertThat(result.getSensitiveLong()).isEqualTo(originalItem.getSensitiveLong());
+ assertThat(result.getSensitiveDouble()).isEqualTo(originalItem.getSensitiveDouble());
+ assertThat(result.isSensitiveBoolean()).isEqualTo(originalItem.isSensitiveBoolean());
+ assertThat(result.getSensitiveIntArray()).isEqualTo(originalItem.getSensitiveIntArray());
+ assertThat(result.getSensitiveStringArray()).isEqualTo(originalItem.getSensitiveStringArray());
+ assertThat(result.getSensitiveString3DArray()).isEqualTo(originalItem.getSensitiveString3DArray());
+ }
+
+ public static List getPathWithOneEncryptionField() {
+ ClientEncryptionIncludedPath includedPath = new ClientEncryptionIncludedPath();
+ includedPath.setClientEncryptionKeyId("key1");
+ includedPath.setPath("/sensitiveString");
+ includedPath.setEncryptionType(CosmosEncryptionType.DETERMINISTIC.toString());
+ includedPath.setEncryptionAlgorithm(CosmosEncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA256.getName());
+
+ List paths = new ArrayList<>();
+ paths.add(includedPath);
+ return paths;
+ }
+
+ private void createEncryptionContainer(CosmosEncryptionAsyncDatabase cosmosEncryptionAsyncDatabase,
+ ClientEncryptionPolicy clientEncryptionPolicy,
+ String containerId) {
+ CosmosContainerProperties properties = new CosmosContainerProperties(containerId, "/mypk");
+ properties.setClientEncryptionPolicy(clientEncryptionPolicy);
+ cosmosEncryptionAsyncDatabase.getCosmosAsyncDatabase().createContainer(properties).block();
+ }
+
+ private void createNewDatabaseWithClientEncryptionKey(String databaseId){
+ EncryptionKeyWrapMetadata metadata1 = new EncryptionKeyWrapMetadata("TEST_KEY_RESOLVER", "key1", "tempmetadata1", "RSA-OAEP");
+ EncryptionKeyWrapMetadata metadata2 = new EncryptionKeyWrapMetadata("TEST_KEY_RESOLVER", "key2", "tempmetadata2", "RSA-OAEP");
+ cosmosEncryptionAsyncClient.getCosmosAsyncClient().createDatabase(databaseId).block();
+ CosmosEncryptionAsyncDatabase encryptionAsyncDatabase = cosmosEncryptionAsyncClient.getCosmosEncryptionAsyncDatabase(databaseId);
+ encryptionAsyncDatabase.createClientEncryptionKey("key1",
+ CosmosEncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA256.getName(), metadata1).block();
+ encryptionAsyncDatabase.createClientEncryptionKey("key2",
+ CosmosEncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA256.getName(), metadata2).block();
+ }
+
+ private CosmosEncryptionAsyncContainer getNewEncryptionContainerProxyObject(String databaseId, String containerId) {
+ CosmosAsyncClient client = getClientBuilder().buildAsyncClient();
+ KeyEncryptionKeyResolver keyEncryptionKeyResolver = new TestKeyEncryptionKeyResolver();
+ CosmosEncryptionAsyncClient cosmosEncryptionAsyncClient = new CosmosEncryptionClientBuilder().cosmosAsyncClient(client).keyEncryptionKeyResolver(
+ keyEncryptionKeyResolver).keyEncryptionKeyResolverName("TEST_KEY_RESOLVER").buildAsyncClient();
+ CosmosEncryptionAsyncDatabase cosmosEncryptionAsyncDatabase =
+ cosmosEncryptionAsyncClient.getCosmosEncryptionAsyncDatabase(client.getDatabase(databaseId));
+ CosmosEncryptionAsyncContainer cosmosEncryptionAsyncContainer = cosmosEncryptionAsyncDatabase.getCosmosEncryptionAsyncContainer(containerId);
+ return cosmosEncryptionAsyncContainer;
+ }
+
+
+}
diff --git a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionCosmosEncryptionChangeFeedTest.java b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionCosmosEncryptionChangeFeedTest.java
index 497487b8deeb8..b0992c85f4d00 100644
--- a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionCosmosEncryptionChangeFeedTest.java
+++ b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionCosmosEncryptionChangeFeedTest.java
@@ -264,7 +264,7 @@ private CosmosAsyncContainer createLeaseCollection(int provisionedThroughput) {
}
private CosmosEncryptionAsyncContainer createFeedCollection() {
- ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(getPaths());
+ ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(getPaths(2), 2);
String containerId = UUID.randomUUID().toString();
CosmosContainerProperties properties = new CosmosContainerProperties(containerId, "/mypk");
properties.setClientEncryptionPolicy(clientEncryptionPolicy);
diff --git a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/TestSuiteBase.java b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/TestSuiteBase.java
index c2db3b777d325..def361f00739b 100644
--- a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/TestSuiteBase.java
+++ b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/TestSuiteBase.java
@@ -240,7 +240,7 @@ public static void beforeSuite() {
SHARED_ENCRYPTION_DATABASE.createClientEncryptionKey("key2",
CosmosEncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA256.getName(), metadata2).block();
- ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(getPaths());
+ ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(getPaths(1), 1);
String containerId = UUID.randomUUID().toString();
CosmosContainerProperties properties = new CosmosContainerProperties(containerId, "/mypk");
properties.setClientEncryptionPolicy(clientEncryptionPolicy);
@@ -567,6 +567,10 @@ static protected CosmosContainerProperties getCollectionDefinition() {
return collectionDefinition;
}
+ static protected CosmosContainerProperties getCollectionDefinition(String collectionId, PartitionKeyDefinition partitionKeyDefinition) {
+ return new CosmosContainerProperties(collectionId, partitionKeyDefinition);
+ }
+
static protected CosmosContainerProperties getCollectionDefinitionWithRangeRangeIndexWithIdAsPartitionKey() {
return getCollectionDefinitionWithRangeRangeIndex(Collections.singletonList("/id"));
}
@@ -1213,7 +1217,7 @@ public KeyEncryptionKey buildKeyEncryptionKey(String keyId) {
}
}
- protected static List getPaths() {
+ protected static List getPaths(int policyFormatVersion) {
ClientEncryptionIncludedPath includedPath1 = new ClientEncryptionIncludedPath();
includedPath1.setClientEncryptionKeyId("key1");
includedPath1.setPath("/sensitiveString");
@@ -1292,6 +1296,18 @@ protected static List getPaths() {
includedPath13.setEncryptionType(CosmosEncryptionType.DETERMINISTIC.getName());
includedPath13.setEncryptionAlgorithm(CosmosEncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA256.getName());
+ ClientEncryptionIncludedPath includedPath14 = new ClientEncryptionIncludedPath();
+ includedPath14.setClientEncryptionKeyId("key1");
+ includedPath14.setPath("/id");
+ includedPath14.setEncryptionType(CosmosEncryptionType.DETERMINISTIC.getName());
+ includedPath14.setEncryptionAlgorithm(CosmosEncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA256.getName());
+
+ ClientEncryptionIncludedPath includedPath15 = new ClientEncryptionIncludedPath();
+ includedPath15.setClientEncryptionKeyId("key1");
+ includedPath15.setPath("/pk");
+ includedPath15.setEncryptionType(CosmosEncryptionType.DETERMINISTIC.getName());
+ includedPath15.setEncryptionAlgorithm(CosmosEncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA256.getName());
+
List paths = new ArrayList<>();
paths.add(includedPath1);
paths.add(includedPath2);
@@ -1306,6 +1322,10 @@ protected static List getPaths() {
paths.add(includedPath11);
paths.add(includedPath12);
paths.add(includedPath13);
+ if (policyFormatVersion == 2) {
+ paths.add(includedPath14);
+ paths.add(includedPath15);
+ }
return paths;
}
diff --git a/sdk/cosmos/azure-cosmos-encryption/src/test/resources/dotnetEncryption/EncryptedPOCO.json b/sdk/cosmos/azure-cosmos-encryption/src/test/resources/dotnetEncryption/EncryptedPOCO.json
index 448e61486361c..7f1e2684fbcdc 100644
--- a/sdk/cosmos/azure-cosmos-encryption/src/test/resources/dotnetEncryption/EncryptedPOCO.json
+++ b/sdk/cosmos/azure-cosmos-encryption/src/test/resources/dotnetEncryption/EncryptedPOCO.json
@@ -1,20 +1,20 @@
{
- "id": "7d925bf9-d583-4c6d-a49f-137bee7cff91",
+ "id": "BQF1OMHlXj-zDxKasrXQFwusq5lZf-AjwlS5Z4ZFdtljRO9ljKsPhPdZ4bGsXYbgtO4WrsHdqVy7r_EB101kO7xfjmGMvTOBiRt_M80AcNwiwJ1vFX9-uLudp3ttD-wKyZs=",
"mypk": "7d925bf9-d583-4c6d-a49f-137bee7cff91",
"nonSensitive": "cf012e6a-3dd1-4f1a-bd93-dc2b7fee5e97",
"sensitiveString": "BQE8sgzM2aDfOlOiU9tYsgR5CghB/WZ/jsluf4HoYotfeUBqDBn3hVfx3A+o4m2mm5dEvnJyOR2Sl+NZezsYN71d",
"sensitiveInt": "BAF+h8xlYiIqaH9TmN8/mgzX8ulodNhbYPASSPEGIVb+3qTR8J4qo/jZoz/qQdkxkEv8TV/aj0UdPEpGF4EBe+88",
- "sensitiveFloat": "AwHBSYf0AqKfOX60Ppn/yj9ycfuFlOWEHGQ230BypHacyeBX/o3jRmlgnRNJuZpr1I9n0pNQl1yCfJkscMUVs8ao",
+ "sensitiveFloat": "BAFjPNt6v/gmO/v1uZCeTPgT6Tioja+rOzr1I8OK3hjC6tU6eZlXk0TUBVtu6ZkbIMo+cMAxH7IVC5diCnJfkjlS",
"sensitiveLong": "BAG8flsdV8r870XeoPwTErbRBPCU5wdoAPSICreKQigwYblfexcEAosleZ+FO261u/m8X97ObH5m8hkxEuQzPURL",
- "sensitiveDouble": "AwHu2zdLESFrjQ7L9KbCenSeEXHOXKjV9d2UCfhNhR/ImYmODXymlKGxapHhzmUabdEp6hKP6ZNIjqJ5xvFD6EYs",
+ "sensitiveDouble": "AwHMrObWBev4hnUulDL5dvLxybF22O3VzY7//PXyano3Hrl8A967TAp3fdRATdaFHydATozMzgREtbhjJNk1WT1d",
"sensitiveBoolean": "AgHOBKDUuHWgivJCu+I8JgIqI3CuSgNKYKpn6nNUN8nqtyPkwnOJXYIeKpXS55/UzzaMAfRd1CZbT2oHdkIzUfuN",
"sensitiveNestedPojo": {
- "id": "BQERuT+l/kKc51OtABiPG3/lu8hpIJNGwUgJVzr71dKJk9wcuuzZbMqtsxj+Ue4B0oY0OYbKCuOYF3wLctg7P0cO",
+ "id": "BQERuT-l_kKc51OtABiPG3_lu8hpIJNGwUgJVzr71dKJk9wcuuzZbMqtsxj-Ue4B0oY0OYbKCuOYF3wLctg7P0cO",
"mypk": "BQERuT+l/kKc51OtABiPG3/lu8hpIJNGwUgJVzr71dKJk9wcuuzZbMqtsxj+Ue4B0oY0OYbKCuOYF3wLctg7P0cO",
"nonSensitive": null,
"sensitiveString": "BQERuT+l/kKc51OtABiPG3/lu8hpIJNGwUgJVzr71dKJk9wcuuzZbMqtsxj+Ue4B0oY0OYbKCuOYF3wLctg7P0cO",
"sensitiveInt": "BAFZBTqQK+9Z7UC1W1NBh+LHLzAEcfFgCc53BD6EtcfGxLZXjKm/F9OFNB+gMeM3T4SFj6jG5DH6TtjVh1XRum4/",
- "sensitiveFloat": "AwHRoUMnYT23ocPkUoERjaSVcWxN05sjBivH0Dt4S5rC92YyZoyZokxGa5wnfpKuK49wr9wPynxF5QurRIha2d+c",
+ "sensitiveFloat": "BAHRoUMnYT23ocPkUoERjaSVcWxN05sjBivH0Dt4S5rC92YyZoyZokxGa5wnfpKuK49wr9wPynxF5QurRIha2d+c",
"sensitiveLong": "BAG8flsdV8r870XeoPwTErbRBPCU5wdoAPSICreKQigwYblfexcEAosleZ+FO261u/m8X97ObH5m8hkxEuQzPURL",
"sensitiveDouble": "AwHAQXtks16DzqIkLNWYZ7TWcjNCLPIYdiTe3NG7hXqg+DJirv5y2hx/fPiGuSKQ30ocEKkiqnxwISegmxZQt1De",
"sensitiveBoolean": "AgHOBKDUuHWgivJCu+I8JgIqI3CuSgNKYKpn6nNUN8nqtyPkwnOJXYIeKpXS55/UzzaMAfRd1CZbT2oHdkIzUfuN",
@@ -82,12 +82,12 @@
"sensitiveChildPojo2DArray": [
[
{
- "id": "BQHRkzWrw6w88Rak29a/mWMh57hvictUtZEK0YuYV+LNRuvCBPU74+btLrxseJ4VZ3zesySbaBGkICzT7zq44M1j",
+ "id": "BQHRkzWrw6w88Rak29a_mWMh57hvictUtZEK0YuYV-LNRuvCBPU74-btLrxseJ4VZ3zesySbaBGkICzT7zq44M1j",
"mypk": null,
"nonSensitive": null,
"sensitiveString": "BQFfH6UGQgHuCZ1SKb8uDdEEdpjZ9+oFgKXDZGR5awXt5hY7B0D5i+6WQQNp2y2Vwsql32ZhCGEyg2vpew/heajPaIqKEPCCklz0WiVc/lrePg==",
"sensitiveInt": "BAFZBTqQK+9Z7UC1W1NBh+LHLzAEcfFgCc53BD6EtcfGxLZXjKm/F9OFNB+gMeM3T4SFj6jG5DH6TtjVh1XRum4/",
- "sensitiveFloat": "AwHRoUMnYT23ocPkUoERjaSVcWxN05sjBivH0Dt4S5rC92YyZoyZokxGa5wnfpKuK49wr9wPynxF5QurRIha2d+c",
+ "sensitiveFloat": "BAHRoUMnYT23ocPkUoERjaSVcWxN05sjBivH0Dt4S5rC92YyZoyZokxGa5wnfpKuK49wr9wPynxF5QurRIha2d+c",
"sensitiveLong": "BAG8flsdV8r870XeoPwTErbRBPCU5wdoAPSICreKQigwYblfexcEAosleZ+FO261u/m8X97ObH5m8hkxEuQzPURL",
"sensitiveDouble": "AwHAQXtks16DzqIkLNWYZ7TWcjNCLPIYdiTe3NG7hXqg+DJirv5y2hx/fPiGuSKQ30ocEKkiqnxwISegmxZQt1De",
"sensitiveBoolean": "AgHOBKDUuHWgivJCu+I8JgIqI3CuSgNKYKpn6nNUN8nqtyPkwnOJXYIeKpXS55/UzzaMAfRd1CZbT2oHdkIzUfuN",
@@ -123,12 +123,12 @@
"sensitiveChildPojoList": null
},
{
- "id": "BQEUkOOtKpvfp4tbzdsgHToSUv8fUstDi025Q6n6oOvi5mFelP1C/wEauxKAYYswqUJ6DWd5eiIsFB/bjGViIq5A",
+ "id": "BQEUkOOtKpvfp4tbzdsgHToSUv8fUstDi025Q6n6oOvi5mFelP1C_wEauxKAYYswqUJ6DWd5eiIsFB_bjGViIq5A",
"mypk": null,
"nonSensitive": null,
"sensitiveString": "BQG3BuavLLquGcjrQw50+NLMm/86/+qVs+75b0jVTcfgjdkwXpTyFY7bRb/SSvxb7DYG7/za32PJ9Q5L4t5d3jvluLs2JWe6wb0fzjw5HSVuEA==",
"sensitiveInt": "BAFZBTqQK+9Z7UC1W1NBh+LHLzAEcfFgCc53BD6EtcfGxLZXjKm/F9OFNB+gMeM3T4SFj6jG5DH6TtjVh1XRum4/",
- "sensitiveFloat": "AwHRoUMnYT23ocPkUoERjaSVcWxN05sjBivH0Dt4S5rC92YyZoyZokxGa5wnfpKuK49wr9wPynxF5QurRIha2d+c",
+ "sensitiveFloat": "BAHRoUMnYT23ocPkUoERjaSVcWxN05sjBivH0Dt4S5rC92YyZoyZokxGa5wnfpKuK49wr9wPynxF5QurRIha2d+c",
"sensitiveLong": "BAG8flsdV8r870XeoPwTErbRBPCU5wdoAPSICreKQigwYblfexcEAosleZ+FO261u/m8X97ObH5m8hkxEuQzPURL",
"sensitiveDouble": "AwHAQXtks16DzqIkLNWYZ7TWcjNCLPIYdiTe3NG7hXqg+DJirv5y2hx/fPiGuSKQ30ocEKkiqnxwISegmxZQt1De",
"sensitiveBoolean": "AgHOBKDUuHWgivJCu+I8JgIqI3CuSgNKYKpn6nNUN8nqtyPkwnOJXYIeKpXS55/UzzaMAfRd1CZbT2oHdkIzUfuN",
@@ -142,12 +142,12 @@
],
[
{
- "id": "BQHRkzWrw6w88Rak29a/mWMh57hvictUtZEK0YuYV+LNRuvCBPU74+btLrxseJ4VZ3zesySbaBGkICzT7zq44M1j",
+ "id": "BQHRkzWrw6w88Rak29a_mWMh57hvictUtZEK0YuYV-LNRuvCBPU74-btLrxseJ4VZ3zesySbaBGkICzT7zq44M1j",
"mypk": null,
"nonSensitive": null,
"sensitiveString": "BQFfH6UGQgHuCZ1SKb8uDdEEdpjZ9+oFgKXDZGR5awXt5hY7B0D5i+6WQQNp2y2Vwsql32ZhCGEyg2vpew/heajPaIqKEPCCklz0WiVc/lrePg==",
"sensitiveInt": "BAFZBTqQK+9Z7UC1W1NBh+LHLzAEcfFgCc53BD6EtcfGxLZXjKm/F9OFNB+gMeM3T4SFj6jG5DH6TtjVh1XRum4/",
- "sensitiveFloat": "AwHRoUMnYT23ocPkUoERjaSVcWxN05sjBivH0Dt4S5rC92YyZoyZokxGa5wnfpKuK49wr9wPynxF5QurRIha2d+c",
+ "sensitiveFloat": "BAHRoUMnYT23ocPkUoERjaSVcWxN05sjBivH0Dt4S5rC92YyZoyZokxGa5wnfpKuK49wr9wPynxF5QurRIha2d+c",
"sensitiveLong": "BAG8flsdV8r870XeoPwTErbRBPCU5wdoAPSICreKQigwYblfexcEAosleZ+FO261u/m8X97ObH5m8hkxEuQzPURL",
"sensitiveDouble": "AwHAQXtks16DzqIkLNWYZ7TWcjNCLPIYdiTe3NG7hXqg+DJirv5y2hx/fPiGuSKQ30ocEKkiqnxwISegmxZQt1De",
"sensitiveBoolean": "AgHOBKDUuHWgivJCu+I8JgIqI3CuSgNKYKpn6nNUN8nqtyPkwnOJXYIeKpXS55/UzzaMAfRd1CZbT2oHdkIzUfuN",
@@ -183,12 +183,12 @@
"sensitiveChildPojoList": null
},
{
- "id": "BQEUkOOtKpvfp4tbzdsgHToSUv8fUstDi025Q6n6oOvi5mFelP1C/wEauxKAYYswqUJ6DWd5eiIsFB/bjGViIq5A",
+ "id": "BQEUkOOtKpvfp4tbzdsgHToSUv8fUstDi025Q6n6oOvi5mFelP1C_wEauxKAYYswqUJ6DWd5eiIsFB_bjGViIq5A",
"mypk": null,
"nonSensitive": null,
"sensitiveString": "BQG3BuavLLquGcjrQw50+NLMm/86/+qVs+75b0jVTcfgjdkwXpTyFY7bRb/SSvxb7DYG7/za32PJ9Q5L4t5d3jvluLs2JWe6wb0fzjw5HSVuEA==",
"sensitiveInt": "BAFZBTqQK+9Z7UC1W1NBh+LHLzAEcfFgCc53BD6EtcfGxLZXjKm/F9OFNB+gMeM3T4SFj6jG5DH6TtjVh1XRum4/",
- "sensitiveFloat": "AwHRoUMnYT23ocPkUoERjaSVcWxN05sjBivH0Dt4S5rC92YyZoyZokxGa5wnfpKuK49wr9wPynxF5QurRIha2d+c",
+ "sensitiveFloat": "BAHRoUMnYT23ocPkUoERjaSVcWxN05sjBivH0Dt4S5rC92YyZoyZokxGa5wnfpKuK49wr9wPynxF5QurRIha2d+c",
"sensitiveLong": "BAG8flsdV8r870XeoPwTErbRBPCU5wdoAPSICreKQigwYblfexcEAosleZ+FO261u/m8X97ObH5m8hkxEuQzPURL",
"sensitiveDouble": "AwHAQXtks16DzqIkLNWYZ7TWcjNCLPIYdiTe3NG7hXqg+DJirv5y2hx/fPiGuSKQ30ocEKkiqnxwISegmxZQt1De",
"sensitiveBoolean": "AgHOBKDUuHWgivJCu+I8JgIqI3CuSgNKYKpn6nNUN8nqtyPkwnOJXYIeKpXS55/UzzaMAfRd1CZbT2oHdkIzUfuN",
@@ -203,12 +203,12 @@
],
"sensitiveChildPojoList": [
{
- "id": "BQHRkzWrw6w88Rak29a/mWMh57hvictUtZEK0YuYV+LNRuvCBPU74+btLrxseJ4VZ3zesySbaBGkICzT7zq44M1j",
+ "id": "BQHRkzWrw6w88Rak29a_mWMh57hvictUtZEK0YuYV-LNRuvCBPU74-btLrxseJ4VZ3zesySbaBGkICzT7zq44M1j",
"mypk": null,
"nonSensitive": null,
"sensitiveString": "BQFfH6UGQgHuCZ1SKb8uDdEEdpjZ9+oFgKXDZGR5awXt5hY7B0D5i+6WQQNp2y2Vwsql32ZhCGEyg2vpew/heajPaIqKEPCCklz0WiVc/lrePg==",
"sensitiveInt": "BAFZBTqQK+9Z7UC1W1NBh+LHLzAEcfFgCc53BD6EtcfGxLZXjKm/F9OFNB+gMeM3T4SFj6jG5DH6TtjVh1XRum4/",
- "sensitiveFloat": "AwHRoUMnYT23ocPkUoERjaSVcWxN05sjBivH0Dt4S5rC92YyZoyZokxGa5wnfpKuK49wr9wPynxF5QurRIha2d+c",
+ "sensitiveFloat": "BAHRoUMnYT23ocPkUoERjaSVcWxN05sjBivH0Dt4S5rC92YyZoyZokxGa5wnfpKuK49wr9wPynxF5QurRIha2d+c",
"sensitiveLong": "BAG8flsdV8r870XeoPwTErbRBPCU5wdoAPSICreKQigwYblfexcEAosleZ+FO261u/m8X97ObH5m8hkxEuQzPURL",
"sensitiveDouble": "AwHAQXtks16DzqIkLNWYZ7TWcjNCLPIYdiTe3NG7hXqg+DJirv5y2hx/fPiGuSKQ30ocEKkiqnxwISegmxZQt1De",
"sensitiveBoolean": "AgHOBKDUuHWgivJCu+I8JgIqI3CuSgNKYKpn6nNUN8nqtyPkwnOJXYIeKpXS55/UzzaMAfRd1CZbT2oHdkIzUfuN",
@@ -244,12 +244,12 @@
"sensitiveChildPojoList": null
},
{
- "id": "BQEUkOOtKpvfp4tbzdsgHToSUv8fUstDi025Q6n6oOvi5mFelP1C/wEauxKAYYswqUJ6DWd5eiIsFB/bjGViIq5A",
+ "id": "BQEUkOOtKpvfp4tbzdsgHToSUv8fUstDi025Q6n6oOvi5mFelP1C_wEauxKAYYswqUJ6DWd5eiIsFB_bjGViIq5A",
"mypk": null,
"nonSensitive": null,
"sensitiveString": "BQG3BuavLLquGcjrQw50+NLMm/86/+qVs+75b0jVTcfgjdkwXpTyFY7bRb/SSvxb7DYG7/za32PJ9Q5L4t5d3jvluLs2JWe6wb0fzjw5HSVuEA==",
"sensitiveInt": "BAFZBTqQK+9Z7UC1W1NBh+LHLzAEcfFgCc53BD6EtcfGxLZXjKm/F9OFNB+gMeM3T4SFj6jG5DH6TtjVh1XRum4/",
- "sensitiveFloat": "AwHRoUMnYT23ocPkUoERjaSVcWxN05sjBivH0Dt4S5rC92YyZoyZokxGa5wnfpKuK49wr9wPynxF5QurRIha2d+c",
+ "sensitiveFloat": "BAHRoUMnYT23ocPkUoERjaSVcWxN05sjBivH0Dt4S5rC92YyZoyZokxGa5wnfpKuK49wr9wPynxF5QurRIha2d+c",
"sensitiveLong": "BAG8flsdV8r870XeoPwTErbRBPCU5wdoAPSICreKQigwYblfexcEAosleZ+FO261u/m8X97ObH5m8hkxEuQzPURL",
"sensitiveDouble": "AwHAQXtks16DzqIkLNWYZ7TWcjNCLPIYdiTe3NG7hXqg+DJirv5y2hx/fPiGuSKQ30ocEKkiqnxwISegmxZQt1De",
"sensitiveBoolean": "AgHOBKDUuHWgivJCu+I8JgIqI3CuSgNKYKpn6nNUN8nqtyPkwnOJXYIeKpXS55/UzzaMAfRd1CZbT2oHdkIzUfuN",
@@ -261,9 +261,9 @@
"sensitiveChildPojoList": null
}
],
- "_rid": "-dspAMVPSPMBAAAAAAAAAA==",
- "_self": "dbs/-dspAA==/colls/-dspAMVPSPM=/docs/-dspAMVPSPMBAAAAAAAAAA==/",
- "_etag": "00000000-0000-0000-984f-74dd1e6e01d7",
+ "_rid": "frBRAOuISWIBAAAAAAAAAA==",
+ "_self": "dbs/frBRAA==/colls/frBRAOuISWI=/docs/frBRAOuISWIBAAAAAAAAAA==/",
+ "_etag": "\"01094fbb-0000-0300-0000-63f528970000\"",
"_attachments": "attachments/",
- "_ts": 1629744506
+ "_ts": 1677011095
}
diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/CosmosContainerTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/CosmosContainerTest.java
index 36ce99c42b75e..d2c6093c3d3ee 100644
--- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/CosmosContainerTest.java
+++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/CosmosContainerTest.java
@@ -119,16 +119,23 @@ public void createContainer_withEncryption() {
path1.setClientEncryptionKeyId("containerTestKey1");
ClientEncryptionIncludedPath path2 = new ClientEncryptionIncludedPath();
- path2.setPath("/path2");
+ path2.setPath("/mypk");
path2.setEncryptionAlgorithm("AEAD_AES_256_CBC_HMAC_SHA256");
path2.setEncryptionType("Deterministic");
path2.setClientEncryptionKeyId("containerTestKey2");
+ ClientEncryptionIncludedPath path3 = new ClientEncryptionIncludedPath();
+ path3.setPath("/id");
+ path3.setEncryptionAlgorithm("AEAD_AES_256_CBC_HMAC_SHA256");
+ path3.setEncryptionType("Deterministic");
+ path3.setClientEncryptionKeyId("containerTestKey2");
+
List paths = new ArrayList<>();
paths.add(path1);
paths.add(path2);
+ paths.add(path3);
- ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(paths);
+ ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(paths, 2);
containerProperties.setClientEncryptionPolicy(clientEncryptionPolicy);
CosmosContainerResponse containerResponse = createdDatabase.createContainer(containerProperties);
@@ -136,6 +143,110 @@ public void createContainer_withEncryption() {
validateContainerResponseWithEncryption(containerProperties, containerResponse, clientEncryptionPolicy);
}
+ @Test(groups = {"emulator"}, timeOut = TIMEOUT)
+ public void createContainer_withPartitionKeyWrongPolicyFormatVersion() {
+ String collectionName = UUID.randomUUID().toString();
+ CosmosContainerProperties containerProperties = getCollectionDefinition(collectionName);
+
+ ClientEncryptionIncludedPath path1 = new ClientEncryptionIncludedPath();
+ path1.setPath("/mypk");
+ path1.setEncryptionAlgorithm("AEAD_AES_256_CBC_HMAC_SHA256");
+ path1.setEncryptionType("Deterministic");
+ path1.setClientEncryptionKeyId("containerTestKey1");
+
+ ClientEncryptionIncludedPath path2 = new ClientEncryptionIncludedPath();
+ path2.setPath("/path2");
+ path2.setEncryptionAlgorithm("AEAD_AES_256_CBC_HMAC_SHA256");
+ path2.setEncryptionType("Deterministic");
+ path2.setClientEncryptionKeyId("containerTestKey2");
+
+ List paths = new ArrayList<>();
+ paths.add(path1);
+ paths.add(path2);
+
+ ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(paths, 1);
+ CosmosContainerResponse containerResponse = null;
+
+ //Verify partition key in CosmosContainerProperties constructor with encrypted field.
+ try {
+ containerProperties.setClientEncryptionPolicy(clientEncryptionPolicy);
+ containerResponse = createdDatabase.createContainer(containerProperties);
+ fail("createContainer should fail as mypk which is part of the partition key cannot be encrypted with " +
+ "PolicyFormatVersion 1.");
+ } catch (IllegalArgumentException ex) {
+ assertThat(ex.getMessage()).isEqualTo("Path mypk which is part of the partition key cannot be encrypted with PolicyFormatVersion 1. Please use PolicyFormatVersion 2.");
+ }
+
+
+ //Verify for composite key
+ collectionName = UUID.randomUUID().toString();
+ containerProperties = new CosmosContainerProperties(collectionName, "/mypk/mypk1");
+ try {
+ containerProperties.setClientEncryptionPolicy(clientEncryptionPolicy);
+ containerResponse = createdDatabase.createContainer(containerProperties);
+ fail("createContainer should fail as mypk which is part of the partition key cannot be encrypted with " +
+ "PolicyFormatVersion 1.");
+ } catch (IllegalArgumentException ex) {
+ assertThat(ex.getMessage()).isEqualTo("Path mypk which is part of the partition key cannot be encrypted with PolicyFormatVersion 1. Please use PolicyFormatVersion 2.");
+ }
+
+
+ //Verify setPartitionKeyDefinition with encrypted field.
+ collectionName = UUID.randomUUID().toString();
+ containerProperties = new CosmosContainerProperties(collectionName, "/differentKey");
+ try {
+ containerProperties.setClientEncryptionPolicy(clientEncryptionPolicy);
+ PartitionKeyDefinition partitionKeyDefinition = new PartitionKeyDefinition();
+ List keyPaths = new ArrayList<>();
+ keyPaths.add("/mypk");
+ partitionKeyDefinition.setPaths(keyPaths);
+ containerProperties.setPartitionKeyDefinition(partitionKeyDefinition);
+ containerResponse = createdDatabase.createContainer(containerProperties);
+ fail("createContainer should fail as mypk which is part of the partition key cannot be encrypted with " +
+ "PolicyFormatVersion 1.");
+ } catch (IllegalArgumentException ex) {
+ assertThat(ex.getMessage()).isEqualTo("Path mypk which is part of the partition key cannot be encrypted with PolicyFormatVersion 1. Please use PolicyFormatVersion 2.");
+ }
+
+ //This should pass as we check only the first key of the composite key.
+ collectionName = UUID.randomUUID().toString();
+ containerProperties = new CosmosContainerProperties(collectionName, "/mypk1/mypk");
+ containerProperties.setClientEncryptionPolicy(clientEncryptionPolicy);
+ containerResponse = createdDatabase.createContainer(containerProperties);
+ assertThat(containerResponse.getRequestCharge()).isGreaterThan(0);
+ validateContainerResponseWithEncryption(containerProperties, containerResponse, clientEncryptionPolicy);
+ }
+
+ @Test(groups = {"emulator"}, timeOut = TIMEOUT)
+ public void createEncryptionPolicy_withIdWrongPolicyFormatVersion() {
+ String collectionName = UUID.randomUUID().toString();
+ CosmosContainerProperties containerProperties = getCollectionDefinition(collectionName);
+
+ ClientEncryptionIncludedPath path1 = new ClientEncryptionIncludedPath();
+ path1.setPath("/id");
+ path1.setEncryptionAlgorithm("AEAD_AES_256_CBC_HMAC_SHA256");
+ path1.setEncryptionType("Deterministic");
+ path1.setClientEncryptionKeyId("containerTestKey1");
+
+ ClientEncryptionIncludedPath path2 = new ClientEncryptionIncludedPath();
+ path2.setPath("/path2");
+ path2.setEncryptionAlgorithm("AEAD_AES_256_CBC_HMAC_SHA256");
+ path2.setEncryptionType("Deterministic");
+ path2.setClientEncryptionKeyId("containerTestKey2");
+
+ List paths = new ArrayList<>();
+ paths.add(path1);
+ paths.add(path2);
+
+ try {
+ ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(paths, 1);
+ fail("clientEncryptionPolicy should fail as id which is part of the partition key cannot be encrypted with " +
+ "PolicyFormatVersion 1.");
+ } catch (IllegalArgumentException ex) {
+ assertThat(ex.getMessage()).isEqualTo("Path /id cannot be encrypted with policyFormatVersion 1.");
+ }
+ }
+
@Test(groups = {"emulator"}, timeOut = TIMEOUT)
public void createContainer_withPartitionKeyInEncryption() {
String collectionName = UUID.randomUUID().toString();
@@ -157,18 +268,18 @@ public void createContainer_withPartitionKeyInEncryption() {
paths.add(path1);
paths.add(path2);
- ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(paths);
+ ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(paths, 2);
CosmosContainerResponse containerResponse = null;
//Verify partition key in CosmosContainerProperties constructor with encrypted field.
try {
containerProperties.setClientEncryptionPolicy(clientEncryptionPolicy);
containerResponse = createdDatabase.createContainer(containerProperties);
- fail("createContainer should fail as mypk which is part of the partition key cannot be included in the " +
- "ClientEncryptionPolicy.");
+ fail("createContainer should fail as mypk which is part of the partition key has to be encrypted with " +
+ "Deterministic type encryption.");
} catch (IllegalArgumentException ex) {
- assertThat(ex.getMessage()).isEqualTo("Path mypk which is part of the partition key cannot be included in" +
- " the ClientEncryptionPolicy.");
+ assertThat(ex.getMessage()).isEqualTo("Path mypk which is part of the partition key has to be encrypted with " +
+ "Deterministic type Encryption.");
}
@@ -178,11 +289,11 @@ public void createContainer_withPartitionKeyInEncryption() {
try {
containerProperties.setClientEncryptionPolicy(clientEncryptionPolicy);
containerResponse = createdDatabase.createContainer(containerProperties);
- fail("createContainer should fail as mypk which is part of the partition key cannot be included in the " +
- "ClientEncryptionPolicy.");
+ fail("createContainer should fail as mypk which is part of the partition key has to be encrypted with " +
+ "Deterministic type encryption.");
} catch (IllegalArgumentException ex) {
- assertThat(ex.getMessage()).isEqualTo("Path mypk which is part of the partition key cannot be included in" +
- " the ClientEncryptionPolicy.");
+ assertThat(ex.getMessage()).isEqualTo("Path mypk which is part of the partition key has to be encrypted with " +
+ "Deterministic type Encryption.");
}
@@ -197,11 +308,11 @@ public void createContainer_withPartitionKeyInEncryption() {
partitionKeyDefinition.setPaths(keyPaths);
containerProperties.setPartitionKeyDefinition(partitionKeyDefinition);
containerResponse = createdDatabase.createContainer(containerProperties);
- fail("createContainer should fail as mypk which is part of the partition key cannot be included in the " +
- "ClientEncryptionPolicy.");
+ fail("createContainer should fail as mypk which is part of the partition key has to be encrypted with " +
+ "Deterministic type encryption.");
} catch (IllegalArgumentException ex) {
- assertThat(ex.getMessage()).isEqualTo("Path mypk which is part of the partition key cannot be included in" +
- " the ClientEncryptionPolicy.");
+ assertThat(ex.getMessage()).isEqualTo("Path mypk which is part of the partition key has to be encrypted with " +
+ "Deterministic type Encryption.");
}
//This should pass as we check only the first key of the composite key.
@@ -213,6 +324,36 @@ public void createContainer_withPartitionKeyInEncryption() {
validateContainerResponseWithEncryption(containerProperties, containerResponse, clientEncryptionPolicy);
}
+ @Test(groups = {"emulator"}, timeOut = TIMEOUT)
+ public void createEncryptionPolicy_withIdWrongEncryptionType() {
+ String collectionName = UUID.randomUUID().toString();
+ CosmosContainerProperties containerProperties = getCollectionDefinition(collectionName);
+
+ ClientEncryptionIncludedPath path1 = new ClientEncryptionIncludedPath();
+ path1.setPath("/id");
+ path1.setEncryptionAlgorithm("AEAD_AES_256_CBC_HMAC_SHA256");
+ path1.setEncryptionType("Randomized");
+ path1.setClientEncryptionKeyId("containerTestKey1");
+
+ ClientEncryptionIncludedPath path2 = new ClientEncryptionIncludedPath();
+ path2.setPath("/path2");
+ path2.setEncryptionAlgorithm("AEAD_AES_256_CBC_HMAC_SHA256");
+ path2.setEncryptionType("Deterministic");
+ path2.setClientEncryptionKeyId("containerTestKey2");
+
+ List paths = new ArrayList<>();
+ paths.add(path1);
+ paths.add(path2);
+
+ try {
+ ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(paths, 2);
+ fail("clientEncryptionPolicy should fail as id which is part of the partition key has to be encrypted with " +
+ "Deterministic type Encryption.");
+ } catch (IllegalArgumentException ex) {
+ assertThat(ex.getMessage()).isEqualTo("Only deterministic encryption type is supported for path /id.");
+ }
+ }
+
@DataProvider
public static Object[][] analyticalTTLProvider() {
return new Object[][]{
@@ -594,7 +735,7 @@ public void getFeedRanges_withMultiplePartitions_HashV2() throws Exception {
assertFeedRange(
feedRanges.get(1),
"{\"Range\":{\"min\":\"15555555555555555555555555555555\"," +
- "\"max\":\"2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"}}");
+ "\"max\":\"2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"}}");
assertFeedRange(
feedRanges.get(2),
"{\"Range\":{\"min\":\"2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\",\"max\":\"FF\"}}");
@@ -678,17 +819,17 @@ public void replace() throws Exception {
assertThat(containerResponse.getProperties().getIndexingPolicy().getIndexingMode()).isEqualTo(IndexingMode.CONSISTENT);
CosmosContainerResponse replaceResponse = createdDatabase.getContainer(containerProperties.getId())
- .replace(containerResponse.getProperties().setIndexingPolicy(
- new IndexingPolicy().setAutomatic(false).setIndexingMode(IndexingMode.NONE)));
+ .replace(containerResponse.getProperties().setIndexingPolicy(
+ new IndexingPolicy().setAutomatic(false).setIndexingMode(IndexingMode.NONE)));
assertThat(replaceResponse.getProperties().getIndexingPolicy().getIndexingMode())
.isEqualTo(IndexingMode.NONE);
assertThat(replaceResponse.getProperties().getIndexingPolicy().isAutomatic())
.isEqualTo(false);
replaceResponse = createdDatabase.getContainer(containerProperties.getId())
- .replace(containerResponse.getProperties().setIndexingPolicy(
- new IndexingPolicy().setAutomatic(true).setIndexingMode(IndexingMode.CONSISTENT)),
- options);
+ .replace(containerResponse.getProperties().setIndexingPolicy(
+ new IndexingPolicy().setAutomatic(true).setIndexingMode(IndexingMode.CONSISTENT)),
+ options);
assertThat(replaceResponse.getProperties().getIndexingPolicy().getIndexingMode())
.isEqualTo(IndexingMode.CONSISTENT);
assertThat(replaceResponse.getProperties().getIndexingPolicy().isAutomatic())
@@ -711,10 +852,10 @@ public void enableFullFidelityChangeFeedForExistingContainer() throws Exception
CosmosContainerResponse replaceResponse =
createdDatabase.getContainer(containerProperties.getId())
- .replace(containerResponse
- .getProperties()
- .setChangeFeedPolicy(
- ChangeFeedPolicy.createAllVersionsAndDeletesPolicy(Duration.ofMinutes(4))));
+ .replace(containerResponse
+ .getProperties()
+ .setChangeFeedPolicy(
+ ChangeFeedPolicy.createAllVersionsAndDeletesPolicy(Duration.ofMinutes(4))));
assertThat(containerResponse.getProperties()).isNotNull();
assertThat(containerResponse.getProperties().getChangeFeedPolicy()).isNotNull();
assertThat(containerResponse.getProperties().getChangeFeedPolicy().getRetentionDurationForAllVersionsAndDeletesPolicy())
@@ -738,10 +879,10 @@ public void changeFullFidelityChangeFeedRetentionDurationForExistingContainer()
CosmosContainerResponse replaceResponse =
createdDatabase.getContainer(containerProperties.getId())
- .replace(containerResponse
- .getProperties()
- .setChangeFeedPolicy(
- ChangeFeedPolicy.createAllVersionsAndDeletesPolicy(Duration.ofMinutes(6))));
+ .replace(containerResponse
+ .getProperties()
+ .setChangeFeedPolicy(
+ ChangeFeedPolicy.createAllVersionsAndDeletesPolicy(Duration.ofMinutes(6))));
assertThat(containerResponse.getProperties()).isNotNull();
assertThat(containerResponse.getProperties().getChangeFeedPolicy()).isNotNull();
assertThat(containerResponse.getProperties().getChangeFeedPolicy().getRetentionDurationForAllVersionsAndDeletesPolicy())
diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/ClientEncryptionPolicy.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/ClientEncryptionPolicy.java
index 9e58f9da1b991..5ea5efe4ac4e0 100644
--- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/ClientEncryptionPolicy.java
+++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/ClientEncryptionPolicy.java
@@ -7,7 +7,6 @@
import com.azure.cosmos.implementation.JsonSerializable;
import com.azure.cosmos.implementation.apachecommons.lang.StringUtils;
import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.node.ObjectNode;
import java.util.ArrayList;
import java.util.List;
@@ -35,40 +34,40 @@ public final class ClientEncryptionPolicy {
* Constructor.
*
* @param paths list of path of the item that need encryption along with path-specific settings.
+ * the PolicyFormatVersion will be set to 1 which is the default value.
+ * Note: If you need to include partition key or id field paths as part of the ClientEncryptionPolicy, please set PolicyFormatVersion to 2.
*/
public ClientEncryptionPolicy(List paths) {
- this.validateIncludedPaths(paths);
- this.includedPaths = paths;
this.policyFormatVersion = 1;
- }
-
- /**
- * Constructor.
- */
- public ClientEncryptionPolicy() {
- this.jsonSerializable = new JsonSerializable();
+ validateIncludedPaths(paths, policyFormatVersion);
+ this.includedPaths = paths;
}
/**
* Constructor.
*
- * @param jsonString the json string that represents the client encryption policy.
+ * @param paths list of path of the item that need encryption along with path-specific settings.
+ * @param policyFormatVersion version of the client encryption policy definition. Current supported versions are 1 and 2. Default version is 1.
+ * Note: If you need to include partition key or id field paths as part of the ClientEncryptionPolicy, please set PolicyFormatVersion to 2.
*/
- ClientEncryptionPolicy(String jsonString) {
- this.jsonSerializable = new JsonSerializable(jsonString);
+ public ClientEncryptionPolicy(List paths, int policyFormatVersion) {
+ if (policyFormatVersion > 2 || policyFormatVersion < 1) {
+ throw new IllegalArgumentException("Supported versions of client encryption policy are 1 and 2.");
+ }
+ this.policyFormatVersion = policyFormatVersion;
+ validateIncludedPaths(paths, policyFormatVersion);
+ this.includedPaths = paths;
}
/**
* Constructor.
- *
- * @param objectNode the object node that represents the client encryption policy.
*/
- ClientEncryptionPolicy(ObjectNode objectNode) {
- this.jsonSerializable = new JsonSerializable(objectNode);
+ public ClientEncryptionPolicy() {
+ this.jsonSerializable = new JsonSerializable();
}
/**
- * Gets the list of path of the item that need encryption along with path-specific settings.
+ * Gets the list of paths of the item that need encryption along with path-specific settings.
* @return includedPaths
*/
public List getIncludedPaths() {
@@ -83,28 +82,46 @@ public int getPolicyFormatVersion() {
return policyFormatVersion;
}
- void validatePartitionKeyPathsAreNotEncrypted(List> partitionKeyPathTokens) {
+ /**
+ * Ensures that partition key paths specified in the client encryption policy for encryption are encrypted using Deterministic encryption algorithm.
+ * @param partitionKeyPathTokens Tokens corresponding to validated partition key.
+ */
+ void validatePartitionKeyPathsIfEncrypted(List> partitionKeyPathTokens) {
checkNotNull(partitionKeyPathTokens, "partitionKeyPathTokens cannot be null");
- List propertiesToEncrypt =
- this.includedPaths.stream().map(clientEncryptionIncludedPath -> clientEncryptionIncludedPath.getPath().substring(1)).collect(Collectors.toList());
+
for (List tokensInPath : partitionKeyPathTokens) {
checkNotNull(tokensInPath);
if (tokensInPath.size() > 0) {
String topLevelToken = tokensInPath.get(0);
- if (propertiesToEncrypt.contains(topLevelToken)) {
- throw new IllegalArgumentException(String.format("Path %s which is part of the partition key " +
- "cannot be included" +
- " in the ClientEncryptionPolicy.", topLevelToken));
+
+ // paths in included paths start with "/". Get the ClientEncryptionIncludedPath and validate.
+ List encrypterPartitionKeyPath =
+ this.includedPaths.stream().filter(clientEncryptionIncludedPath -> clientEncryptionIncludedPath.getPath().substring(1).equals(topLevelToken)).collect(Collectors.toList());
+
+ if (encrypterPartitionKeyPath.size() >0) {
+ if (this.policyFormatVersion < 2) {
+ throw new IllegalArgumentException(String.format("Path %s which is part of the partition key " +
+ "cannot be encrypted" +
+ " with PolicyFormatVersion %s. Please use PolicyFormatVersion 2.", topLevelToken, policyFormatVersion));
+ }
+
+ // for the ClientEncryptionIncludedPath found check the encryption type.
+ if (!encrypterPartitionKeyPath.stream().map(encrypter -> encrypter.getEncryptionType()).findFirst().orElse(null).equals(Constants.Properties.DETERMINISTIC)) {
+ throw new IllegalArgumentException(String.format("Path %s which is part of the partition key " +
+ "has to be encrypted" +
+ " with Deterministic type Encryption.", topLevelToken));
+ }
+
}
}
}
}
- private void validateIncludedPaths(List clientEncryptionIncludedPath) {
+ private static void validateIncludedPaths(List clientEncryptionIncludedPath, int policyFormatVersion) {
List includedPathsList = new ArrayList<>();
for (ClientEncryptionIncludedPath path : clientEncryptionIncludedPath) {
- this.validateClientEncryptionIncludedPath(path);
+ validateClientEncryptionIncludedPath(path, policyFormatVersion);
if (includedPathsList.contains(path.getPath())) {
throw new IllegalArgumentException("Duplicate Path found in clientEncryptionIncludedPath.");
}
@@ -113,7 +130,7 @@ private void validateIncludedPaths(List clientEncr
}
}
- private void validateClientEncryptionIncludedPath(ClientEncryptionIncludedPath clientEncryptionIncludedPath) {
+ private static void validateClientEncryptionIncludedPath(ClientEncryptionIncludedPath clientEncryptionIncludedPath, int policyFormatVersion) {
if (clientEncryptionIncludedPath == null) {
throw new IllegalArgumentException("clientEncryptionIncludedPath is null");
}
@@ -123,11 +140,20 @@ private void validateClientEncryptionIncludedPath(ClientEncryptionIncludedPath c
}
if (clientEncryptionIncludedPath.getPath().charAt(0) != '/'
- || clientEncryptionIncludedPath.getPath().lastIndexOf('/') != 0
- || clientEncryptionIncludedPath.getPath().substring(1).equals("id")) {
+ || clientEncryptionIncludedPath.getPath().lastIndexOf('/') != 0) {
throw new IllegalArgumentException("Invalid path " + clientEncryptionIncludedPath.getPath());
}
+ if (clientEncryptionIncludedPath.getPath().substring(1).equals(Constants.Properties.ID)) {
+ if (policyFormatVersion < 2) {
+ throw new IllegalArgumentException(String.format("Path %s cannot be encrypted with policyFormatVersion %s.", clientEncryptionIncludedPath.getPath(), policyFormatVersion));
+ }
+
+ if (!clientEncryptionIncludedPath.getEncryptionType().equals(Constants.Properties.DETERMINISTIC)) {
+ throw new IllegalArgumentException(String.format("Only deterministic encryption type is supported for path %s.", clientEncryptionIncludedPath.getPath()));
+ }
+ }
+
if (StringUtils.isEmpty(clientEncryptionIncludedPath.getClientEncryptionKeyId())) {
throw new IllegalArgumentException("clientEncryptionKeyId in clientEncryptionIncludedPath is empty");
}
diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CosmosContainerProperties.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CosmosContainerProperties.java
index 78675b6e29ffd..565eb6a367908 100644
--- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CosmosContainerProperties.java
+++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CosmosContainerProperties.java
@@ -126,7 +126,7 @@ public PartitionKeyDefinition getPartitionKeyDefinition() {
public CosmosContainerProperties setPartitionKeyDefinition(PartitionKeyDefinition partitionKeyDefinition) {
this.documentCollection.setPartitionKey(partitionKeyDefinition);
if (this.getClientEncryptionPolicy() != null) {
- this.getClientEncryptionPolicy().validatePartitionKeyPathsAreNotEncrypted(this.getPartitionKeyPathTokensList());
+ this.getClientEncryptionPolicy().validatePartitionKeyPathsIfEncrypted(this.getPartitionKeyPathTokensList());
}
return this;
@@ -315,7 +315,7 @@ public ClientEncryptionPolicy getClientEncryptionPolicy() {
*/
public CosmosContainerProperties setClientEncryptionPolicy(ClientEncryptionPolicy value) {
if (value != null) {
- value.validatePartitionKeyPathsAreNotEncrypted(this.getPartitionKeyPathTokensList());
+ value.validatePartitionKeyPathsIfEncrypted(this.getPartitionKeyPathTokensList());
}
this.documentCollection.setClientEncryptionPolicy(value);