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 5f3d31745192..a3afc22f2586 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 @@ -3,20 +3,28 @@ package com.azure.cosmos.encryption; +import com.azure.cosmos.BridgeInternal; import com.azure.cosmos.CosmosAsyncClient; import com.azure.cosmos.CosmosAsyncClientEncryptionKey; import com.azure.cosmos.CosmosAsyncContainer; import com.azure.cosmos.CosmosAsyncDatabase; +import com.azure.cosmos.CosmosException; +import com.azure.cosmos.encryption.implementation.EncryptionProcessor; +import com.azure.cosmos.implementation.HttpConstants; +import com.azure.cosmos.implementation.Utils; import com.azure.cosmos.implementation.caches.AsyncCache; import com.azure.cosmos.models.ClientEncryptionPolicy; import com.azure.cosmos.models.CosmosClientEncryptionKeyProperties; import com.microsoft.data.encryption.cryptography.EncryptionKeyStoreProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import reactor.core.publisher.Mono; /** * CosmosClient with Encryption support. */ public class CosmosEncryptionAsyncClient { + private final static Logger LOGGER = LoggerFactory.getLogger(CosmosEncryptionAsyncClient.class); private final CosmosAsyncClient cosmosAsyncClient; private final AsyncCache clientEncryptionPolicyCacheByContainerId; private final AsyncCache clientEncryptionKeyPropertiesCacheByKeyId; @@ -95,9 +103,26 @@ Mono fetchClientEncryptionKeyPropertiesAsyn return clientEncryptionKey.read().map(cosmosClientEncryptionKeyResponse -> cosmosClientEncryptionKeyResponse.getProperties() - ).onErrorResume(throwable -> Mono.error(new IllegalStateException("Encryption Based Container without Data " + - "Encryption Keys. " + - "Please make sure you have created the Client Encryption Keys", throwable))); + ).onErrorResume(throwable -> { + Throwable unwrappedException = reactor.core.Exceptions.unwrap(throwable); + if (!(unwrappedException instanceof Exception)) { + // fatal error + LOGGER.error("Unexpected failure {}", unwrappedException.getMessage(), unwrappedException); + return Mono.error(unwrappedException); + } + Exception exception = (Exception) unwrappedException; + CosmosException dce = Utils.as(exception, CosmosException.class); + if (dce != null) { + if (dce.getStatusCode() == HttpConstants.StatusCodes.NOTFOUND) { + String message = "Encryption Based Container without Data Encryption Keys. " + + "Please make sure you have created the Client Encryption Keys"; + return Mono.error(BridgeInternal.createCosmosException(HttpConstants.StatusCodes.NOTFOUND, message)); + } + return Mono.error(dce); + } + + return Mono.error(exception); + }); } /** 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 fb5750b0066f..d5e39f23cad9 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 @@ -3,6 +3,7 @@ package com.azure.cosmos.encryption.implementation; +import com.azure.cosmos.BridgeInternal; import com.azure.cosmos.CosmosAsyncContainer; import com.azure.cosmos.encryption.CosmosEncryptionAsyncClient; import com.azure.cosmos.encryption.EncryptionBridgeInternal; @@ -55,7 +56,7 @@ public class EncryptionProcessor { private EncryptionSettings encryptionSettings; private AtomicBoolean isEncryptionSettingsInitDone; private ClientEncryptionPolicy clientEncryptionPolicy; - private final static int STRING_SIZE_ENCRYPTION_LIMIT = 1024; + private final static int STRING_SIZE_ENCRYPTION_LIMIT = 8000; public EncryptionProcessor(CosmosAsyncContainer cosmosAsyncContainer, CosmosEncryptionAsyncClient encryptionCosmosClient) { @@ -377,7 +378,7 @@ public void decryptAndSerializeProperty(EncryptionSettings encryptionSettings, O if (propertyValueHolder.isObject()) { for (Iterator> it = propertyValueHolder.fields(); it.hasNext(); ) { Map.Entry child = it.next(); - if (child.getValue().isObject()) { + if (child.getValue().isObject() || child.getValue().isArray()) { decryptAndSerializeProperty(encryptionSettings, (ObjectNode) propertyValueHolder, child.getValue(), propertyName); } else if (!child.getValue().isNull()) { @@ -467,9 +468,10 @@ public static Pair toByteArray(JsonNode jsonNode) { SqlSerializerFactory.getOrCreate("varchar", STRING_SIZE_ENCRYPTION_LIMIT, 0, 0, StandardCharsets.UTF_8.toString()).serialize(jsonNode.asText())); } } catch (MicrosoftDataEncryptionException ex) { - throw new IllegalStateException("Unable to convert JSON to byte[]", ex); + throw BridgeInternal.createCosmosException("Unable to convert JSON to byte[]", ex, null, 0, null); } - throw new IncompatibleClassChangeError("Invalid or Unsupported Data Type Passed " + jsonNode.getNodeType()); + throw BridgeInternal.createCosmosException(0, + "Invalid or Unsupported Data Type Passed " + jsonNode.getNodeType()); } public static JsonNode toJsonNode(byte[] serializedBytes, TypeMarker typeMarker) { @@ -487,9 +489,9 @@ public static JsonNode toJsonNode(byte[] serializedBytes, TypeMarker typeMarker) STRING_SIZE_ENCRYPTION_LIMIT, 0, 0, StandardCharsets.UTF_8.toString()).deserialize(serializedBytes)); } } catch (MicrosoftDataEncryptionException ex) { - throw new IllegalStateException("Unable to convert byte[] to JSON", ex); + throw BridgeInternal.createCosmosException("Unable to convert byte[] to JSON", ex, null, 0, null); } - throw new IncompatibleClassChangeError("Invalid or Unsupported Data Type Passed " + typeMarker); + throw BridgeInternal.createCosmosException(0, "Invalid or Unsupported Data Type Passed " + typeMarker); } public enum TypeMarker { 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 3672b6f2901b..78adcadfa63d 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 @@ -59,6 +59,13 @@ public void before_CosmosItemTest() { new EncryptionCrudTest.TestEncryptionKeyStoreProvider()); cosmosEncryptionAsyncDatabase = cosmosEncryptionAsyncClient.getCosmosEncryptionAsyncDatabase(cosmosDatabaseProperties.getId()); + //Create ClientEncryptionKeys + metadata1 = new EncryptionKeyWrapMetadata("key1", "tempmetadata1"); + metadata2 = new EncryptionKeyWrapMetadata("key2", "tempmetadata2"); + cosmosEncryptionAsyncDatabase.createClientEncryptionKey("key1", + CosmosEncryptionAlgorithm.AEAES_256_CBC_HMAC_SHA_256, metadata1).block(); + cosmosEncryptionAsyncDatabase.createClientEncryptionKey("key2", + CosmosEncryptionAlgorithm.AEAES_256_CBC_HMAC_SHA_256, metadata2).block(); //Create collection with clientEncryptionPolicy ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(EncryptionCrudTest.getPaths()); @@ -68,14 +75,6 @@ public void before_CosmosItemTest() { cosmosEncryptionAsyncDatabase.getCosmosAsyncDatabase().createContainer(containerProperties).block(); cosmosEncryptionAsyncContainer = cosmosEncryptionAsyncDatabase.getCosmosEncryptionAsyncContainer(containerProperties.getId()); - - //Create collection with ClientEncryptionKeys - metadata1 = new EncryptionKeyWrapMetadata("key1", "tempmetadata1"); - metadata2 = new EncryptionKeyWrapMetadata("key2", "tempmetadata2"); - cosmosEncryptionAsyncDatabase.createClientEncryptionKey("key1", - CosmosEncryptionAlgorithm.AEAES_256_CBC_HMAC_SHA_256, metadata1).block(); - cosmosEncryptionAsyncDatabase.createClientEncryptionKey("key2", - CosmosEncryptionAlgorithm.AEAES_256_CBC_HMAC_SHA_256, metadata2).block(); } @Test(groups = {"encryption"}, priority = 0, timeOut = TIMEOUT) diff --git a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionCrudTest.java b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionCrudTest.java index fd9bfb3b3a8b..cd451ce7eec5 100644 --- a/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionCrudTest.java +++ b/sdk/cosmos/azure-cosmos-encryption/src/test/java/com/azure/cosmos/encryption/EncryptionCrudTest.java @@ -6,8 +6,10 @@ import com.azure.cosmos.CosmosAsyncClient; import com.azure.cosmos.CosmosAsyncDatabase; import com.azure.cosmos.CosmosClientBuilder; +import com.azure.cosmos.CosmosException; 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.CosmosContainerProperties; @@ -19,7 +21,6 @@ import com.azure.cosmos.models.PartitionKey; import com.azure.cosmos.models.SqlParameter; import com.azure.cosmos.models.SqlQuerySpec; -import com.azure.cosmos.encryption.models.SqlQuerySpecWithEncryption; import com.azure.cosmos.rx.TestSuiteBase; import com.azure.cosmos.util.CosmosPagedFlux; import com.fasterxml.jackson.annotation.JsonProperty; @@ -31,6 +32,7 @@ import org.testng.annotations.Factory; import org.testng.annotations.Test; +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -65,19 +67,19 @@ public void before_CosmosItemTest() { cosmosEncryptionAsyncDatabase = cosmosEncryptionAsyncClient.getCosmosEncryptionAsyncDatabase(cosmosAsyncDatabase); - ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(getPaths()); - String containerId = UUID.randomUUID().toString(); - CosmosContainerProperties properties = new CosmosContainerProperties(containerId, "/mypk"); - properties.setClientEncryptionPolicy(clientEncryptionPolicy); - cosmosEncryptionAsyncDatabase.getCosmosAsyncDatabase().createContainer(properties).block(); - cosmosEncryptionAsyncContainer = cosmosEncryptionAsyncDatabase.getCosmosEncryptionAsyncContainer(containerId); metadata1 = new EncryptionKeyWrapMetadata("key1", "tempmetadata1"); metadata2 = new EncryptionKeyWrapMetadata("key2", "tempmetadata2"); cosmosEncryptionAsyncDatabase.createClientEncryptionKey("key1", CosmosEncryptionAlgorithm.AEAES_256_CBC_HMAC_SHA_256, metadata1).block(); cosmosEncryptionAsyncDatabase.createClientEncryptionKey("key2", CosmosEncryptionAlgorithm.AEAES_256_CBC_HMAC_SHA_256, metadata2).block(); - cosmosEncryptionAsyncDatabase.rewrapClientEncryptionKey("key2", metadata2).block(); + + ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(getPaths()); + String containerId = UUID.randomUUID().toString(); + CosmosContainerProperties properties = new CosmosContainerProperties(containerId, "/mypk"); + properties.setClientEncryptionPolicy(clientEncryptionPolicy); + cosmosEncryptionAsyncDatabase.getCosmosAsyncDatabase().createContainer(properties).block(); + cosmosEncryptionAsyncContainer = cosmosEncryptionAsyncDatabase.getCosmosEncryptionAsyncContainer(containerId); } @AfterClass(groups = {"encryption"}, timeOut = SHUTDOWN_TIMEOUT, alwaysRun = true) @@ -87,7 +89,7 @@ public void afterClass() { } @Test(groups = {"encryption"}, timeOut = TIMEOUT) - public void createItemEncrypt_readItemDecrypt() { + public void createItemEncrypt_readItemDecrypt() throws MicrosoftDataEncryptionException, IOException { Pojo properties = getItem(UUID.randomUUID().toString()); CosmosItemResponse itemResponse = cosmosEncryptionAsyncContainer.createItem(properties, new PartitionKey(properties.mypk), new CosmosItemRequestOptions()).block(); @@ -98,10 +100,35 @@ public void createItemEncrypt_readItemDecrypt() { Pojo readItem = cosmosEncryptionAsyncContainer.readItem(properties.id, new PartitionKey(properties.mypk), new CosmosItemRequestOptions(), Pojo.class).block().getItem(); validateResponse(properties, readItem); + + //Check for max length support of 8000 + properties = getItem(UUID.randomUUID().toString()); + String longString = ""; + for (int i = 0; i < 8000; i++) { + longString += "a"; + } + properties.sensitiveString = longString; + itemResponse = cosmosEncryptionAsyncContainer.createItem(properties, + new PartitionKey(properties.mypk), new CosmosItemRequestOptions()).block(); + assertThat(itemResponse.getRequestCharge()).isGreaterThan(0); + responseItem = itemResponse.getItem(); + validateResponse(properties, responseItem); + + //Check for exception for length greater that 8000 + longString += "a"; + properties.sensitiveString = longString; + try { + cosmosEncryptionAsyncContainer.createItem(properties, + new PartitionKey(properties.mypk), new CosmosItemRequestOptions()).block(); + fail("Item create should fail as length of encryption field is greater than 8000"); + } catch (CosmosException ex) { + assertThat(ex.getMessage()).contains("Unable to convert JSON to byte[]"); + assertThat(ex.getCause() instanceof MicrosoftDataEncryptionException).isTrue(); + } } @Test(groups = {"encryption"}, timeOut = TIMEOUT) - public void upsertItem_readItem() throws Exception { + public void upsertItem_readItem() { Pojo properties = getItem(UUID.randomUUID().toString()); CosmosItemResponse itemResponse = cosmosEncryptionAsyncContainer.upsertItem(properties, new PartitionKey(properties.mypk), new CosmosItemRequestOptions()).block(); @@ -299,7 +326,6 @@ private void validateResponse(Pojo originalItem, Pojo result) { assertThat(result.sensitiveChildPojo2DArray[0][0].sensitiveIntArray).isEqualTo(originalItem.sensitiveChildPojo2DArray[0][0].sensitiveIntArray); assertThat(result.sensitiveChildPojo2DArray[0][0].sensitiveStringArray).isEqualTo(originalItem.sensitiveChildPojo2DArray[0][0].sensitiveStringArray); assertThat(result.sensitiveChildPojo2DArray[0][0].sensitiveString3DArray).isEqualTo(originalItem.sensitiveChildPojo2DArray[0][0].sensitiveString3DArray); - } public static Pojo getItem(String documentId) { @@ -316,10 +342,14 @@ public static Pojo getItem(String documentId) { Pojo nestedPojo = new Pojo(); nestedPojo.id = "nestedPojo"; + nestedPojo.mypk = "nestedPojo"; nestedPojo.sensitiveString = "nestedPojo"; nestedPojo.sensitiveDouble = 10.123; nestedPojo.sensitiveInt = 123; nestedPojo.sensitiveLong = 1234; + nestedPojo.sensitiveStringArray = new String[]{"str1", "str1"}; + nestedPojo.sensitiveString3DArray = new String[][][]{{{"str1", "str2"}, {"str3", "str4"}}, {{"str5", "str6"}, { + "str7", "str8"}}}; nestedPojo.sensitiveBoolean = true; pojo.sensitiveNestedPojo = nestedPojo; @@ -336,7 +366,9 @@ public static Pojo getItem(String documentId) { childPojo1.sensitiveInt = 123; childPojo1.sensitiveLong = 1234; childPojo1.sensitiveBoolean = true; - + childPojo1.sensitiveStringArray = new String[]{"str1", "str1"}; + childPojo1.sensitiveString3DArray = new String[][][]{{{"str1", "str2"}, {"str3", "str4"}}, {{"str5", "str6"}, { + "str7", "str8"}}}; Pojo childPojo2 = new Pojo(); childPojo2.id = "childPojo2"; childPojo2.sensitiveString = "child2TestingString"; @@ -475,7 +507,6 @@ public static List getPaths() { includedPath8.setEncryptionType(CosmosEncryptionType.DETERMINISTIC); includedPath8.setEncryptionAlgorithm(CosmosEncryptionAlgorithm.AEAES_256_CBC_HMAC_SHA_256); - ClientEncryptionIncludedPath includedPath9 = new ClientEncryptionIncludedPath(); includedPath9.setClientEncryptionKeyId("key1"); includedPath9.setPath("/sensitiveIntArray"); diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/EncryptionKeyWrapMetadata.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/EncryptionKeyWrapMetadata.java index f3d5834245b0..60aaa9ad9377 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/EncryptionKeyWrapMetadata.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/EncryptionKeyWrapMetadata.java @@ -31,7 +31,7 @@ public EncryptionKeyWrapMetadata() { @Beta(value = Beta.SinceVersion.V4_14_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) public EncryptionKeyWrapMetadata(EncryptionKeyWrapMetadata source) { this.type = source.type; - this.algorithm = source.algorithm; + this.name = source.name; this.value = source.value; } @@ -43,26 +43,21 @@ public EncryptionKeyWrapMetadata(EncryptionKeyWrapMetadata source) { */ @Beta(value = Beta.SinceVersion.V4_14_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) public EncryptionKeyWrapMetadata(String name, String value) { - this("custom", name, value, null); + this("custom", name, value); } - private EncryptionKeyWrapMetadata(String type, String name, String value, String algorithm) { + private EncryptionKeyWrapMetadata(String type, String name, String value) { Preconditions.checkNotNull(type, "type is null"); Preconditions.checkNotNull(value, "value is null"); this.type = type; this.name = name; this.value = value; - this.algorithm = algorithm; } @JsonProperty("type") @JsonInclude(JsonInclude.Include.NON_NULL) private String type; - @JsonProperty("algorithm") - @JsonInclude(JsonInclude.Include.NON_NULL) - private String algorithm; - @JsonProperty("value") @JsonInclude(JsonInclude.Include.NON_NULL) private String value; @@ -107,13 +102,13 @@ public boolean equals(Object obj) { if (obj == null || getClass() != obj.getClass()) return false; EncryptionKeyWrapMetadata that = (EncryptionKeyWrapMetadata) obj; return Objects.equals(type, that.type) && - Objects.equals(algorithm, that.algorithm) && + Objects.equals(name, that.name) && Objects.equals(value, that.value); } @Override public int hashCode() { - return Objects.hash(type, algorithm, value); + return Objects.hash(type, name, value); } } diff --git a/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/CosmosContainerChangeFeedTest.java b/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/CosmosContainerChangeFeedTest.java index 1b00ed79ae03..b72438afa118 100644 --- a/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/CosmosContainerChangeFeedTest.java +++ b/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/CosmosContainerChangeFeedTest.java @@ -414,7 +414,8 @@ public void asyncChangeFeed_fromNow_incremental_forFullRange() throws Exception drainAndValidateChangeFeedResults(options, null, expectedEventCountAfterUpdates); } - @Test(groups = { "emulator" }, timeOut = TIMEOUT) + //TODO Temporarily disabling + @Test(groups = { "emulator" }, timeOut = TIMEOUT, enabled = false) public void asyncChangeFeed_fromNow_fullFidelity_forFullRange() throws Exception { this.createContainer( (cp) -> cp.setChangeFeedPolicy(ChangeFeedPolicy.createFullFidelityPolicy(Duration.ofMinutes(10)))