diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryption.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryption.java index c9ecd4f1b..16daf0d28 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryption.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryption.java @@ -6,12 +6,15 @@ import software.amazon.awssdk.enhanced.dynamodb.IndexMetadata; import software.amazon.awssdk.enhanced.dynamodb.KeyAttributeMetadata; import software.amazon.awssdk.enhanced.dynamodb.TableMetadata; +import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; -import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.DynamoDbTablesEncryptionConfig; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.DynamoDbEncryptionInterceptor; import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.DynamoDbEncryptionException; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.DynamoDbTablesEncryptionConfig; import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.DynamoDbTableEncryptionConfig; + import software.amazon.cryptography.dbencryptionsdk.structuredencryption.model.CryptoAction; -import software.amazon.cryptography.dbencryptionsdk.dynamodb.DynamoDbEncryptionInterceptor; import static software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DoNothingTag.CUSTOM_DDB_ENCRYPTION_DO_NOTHING_PREFIX; import static software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.SignOnlyTag.CUSTOM_DDB_ENCRYPTION_SIGN_ONLY_PREFIX; @@ -82,6 +85,9 @@ private static DynamoDbTableEncryptionConfig getTableConfig(DynamoDbEnhancedTabl // non-key attributes are ENCRYPT_AND_SIGN unless otherwise annotated actions.put(attributeName, CryptoAction.ENCRYPT_AND_SIGN); } + + // Detect Encryption Flags that are Ignored b/c they are in a Nested Class + scanForIgnoredEncryptionTagsShallow(configWithSchema, attributeName); } DynamoDbTableEncryptionConfig.Builder builder = DynamoDbTableEncryptionConfig.builder(); @@ -115,4 +121,48 @@ private static DynamoDbTableEncryptionConfig getTableConfig(DynamoDbEnhancedTabl .legacyOverride(configWithSchema.legacyOverride()) .build(); } + + /** + * Detects DynamoDB Encryption Tags in Nested Enhanced Types.
+ * This method ONLY parses ONE Layer of nesting.
+ * It does NOT traverse further nested Enhanced Types.
+ * DynamoDB Encryption Tags in Nested Classes are IGNORED by the + * Database Encryption SDK for DynamoDB.
+ * As such, Detection of a nested DynamoDB Encryption Tag on a Nested Type + * triggers a Runtime Exception that MUST NOT BE ignored.
+ * CAVEAT: Encryption Tags on fields of Nested Classes that are + * Flattened onto the top record are Respected.
+ * The behavior of Flatten pushes the Attributes onto the top level record, + * making the "flattened sub-fields" equivalent to any other DynamoDB Attribute.
+ * However, there still exists a possibility for IGNORED Encryption Tags, + * as any Encryption Tag on the field that will be "flattened" is ignored.
+ * This method DOES NOT detect these "ignored-by-flattened" tags. + */ + private static void scanForIgnoredEncryptionTagsShallow( + final DynamoDbEnhancedTableEncryptionConfig configWithSchema, + final String attributeName + ) { + AttributeConverter attributeConverter = configWithSchema.schemaOnEncrypt().converterForAttribute(attributeName); + if ( + Objects.nonNull(attributeConverter) && + Objects.nonNull(attributeConverter.type()) && + attributeConverter.type().tableSchema().isPresent() + ) { + Object maybeTableSchema = attributeConverter.type().tableSchema().get(); + if (maybeTableSchema instanceof TableSchema) { + TableSchema subTableSchema = (TableSchema) maybeTableSchema; + if ( + subTableSchema.tableMetadata().customMetadata().containsKey(CUSTOM_DDB_ENCRYPTION_SIGN_ONLY_PREFIX) || + subTableSchema.tableMetadata().customMetadata().containsKey(CUSTOM_DDB_ENCRYPTION_DO_NOTHING_PREFIX) + ) { + throw DynamoDbEncryptionException.builder() + .message(String.format( + "Detected a DynamoDbEncryption Tag/Configuration on a nested attribute of %s. " + + "This is NOT Supported at this time!", + attributeName)) + .build(); + } + } + } + } } diff --git a/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/AnnotatedConvertedBy.java b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/AnnotatedConvertedBy.java new file mode 100644 index 000000000..fa77fdc17 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/AnnotatedConvertedBy.java @@ -0,0 +1,150 @@ +package software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient; + +import java.util.HashMap; +import java.util.Map; + +import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter; +import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType; +import software.amazon.awssdk.enhanced.dynamodb.EnhancedType; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbConvertedBy; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +/** + * This is a valid use of DynamoDbEncryption annotations attributes with + * DynamoDbConvertedBy.
+ * The DynamoDbEncryption annotations are placed on elements that are converted + * to Maps.
+ * In this case, only {@code nestedEncrypted} will be written to the DynamoDB Table as a
+ * binary. {@code nestedSigned} and {@code nestedIgnored} are recorded as DynamoDB Maps.
+ */
+@DynamoDbBean
+public class AnnotatedConvertedBy {
+ private String partitionKey;
+ private int sortKey;
+ private ConvertedByNestedBean nestedEncrypted;
+ private ConvertedByNestedBean nestedSigned;
+ private ConvertedByNestedBean nestedIgnored;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAttribute(value = "partition_key")
+ public String getPartitionKey() {
+ return this.partitionKey;
+ }
+ public void setPartitionKey(String partitionKey) {
+ this.partitionKey = partitionKey;
+ }
+
+ @DynamoDbSortKey
+ @DynamoDbAttribute(value = "sort_key")
+ public int getSortKey() {
+ return this.sortKey;
+ }
+ public void setSortKey(int sortKey) {
+ this.sortKey = sortKey;
+ }
+
+ @DynamoDbConvertedBy(ConvertedByNestedBean.NestedBeanConverter.class)
+ @DynamoDbAttribute("nestedEncrypted")
+ public ConvertedByNestedBean getNestedEncrypted() {
+ return this.nestedEncrypted;
+ }
+ public void setNestedEncrypted(ConvertedByNestedBean nested) {
+ this.nestedEncrypted = nested;
+ }
+
+ @DynamoDbConvertedBy(ConvertedByNestedBean.NestedBeanConverter.class)
+ @DynamoDbEncryptionSignOnly
+ @DynamoDbAttribute("nestedSigned")
+ public ConvertedByNestedBean getNestedSigned() {
+ return this.nestedSigned;
+ }
+ public void setNestedSigned(ConvertedByNestedBean nested) {
+ this.nestedSigned = nested;
+ }
+
+ @DynamoDbConvertedBy(ConvertedByNestedBean.NestedBeanConverter.class)
+ @DynamoDbEncryptionDoNothing
+ @DynamoDbAttribute("nestedIgnored")
+ public ConvertedByNestedBean getNestedIgnored() {
+ return this.nestedIgnored;
+ }
+ public void setNestedIgnored(ConvertedByNestedBean nested) {
+ this.nestedIgnored = nested;
+ }
+
+ public static class ConvertedByNestedBean {
+ private String id;
+ private String firstName;
+ private String lastName;
+
+ // A Default Empty constructor is needed by the Enhanced Client
+ public ConvertedByNestedBean() {};
+
+ public ConvertedByNestedBean(String id, String firstName, String lastName) {
+ this.id = id;
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ public String getId() { return this.id; }
+ public void setId(String id) { this.id = id; }
+
+ public String getFirstName() { return firstName; }
+ public void setFirstName(String firstName) { this.firstName = firstName; }
+
+ public String getLastName() { return lastName; }
+ public void setLastName(String lastName) { this.lastName = lastName; }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ ConvertedByNestedBean other = (ConvertedByNestedBean) obj;
+ if (id == null) {
+ if (other.getId() != null) return false;
+ } else if (!id.equals(other.getId())) return false;
+ if (firstName == null) {
+ if (other.getFirstName() != null) return false;
+ } else if (!firstName.equals(other.getFirstName())) return false;
+ if (lastName == null) {
+ if (other.getLastName() != null) return false;
+ } else if (!lastName.equals(other.getLastName())) return false;
+ return true;
+ }
+
+ public static final class NestedBeanConverter implements AttributeConverter
+ *
+ * Note that you MUST NOT use a DynamoDbEncryption annotation on the
+ * Flattened Attribute itself.
+ * Only on it's sub-fields.
+ */
+@DynamoDbBean
+public class AnnotatedFlattenedBean {
+ private String partitionKey;
+ private int sortKey;
+ private FlattenedNestedBean nestedBeanClass;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAttribute(value = "partition_key")
+ public String getPartitionKey() {
+ return this.partitionKey;
+ }
+
+ public void setPartitionKey(String partitionKey) {
+ this.partitionKey = partitionKey;
+ }
+
+ @DynamoDbSortKey
+ @DynamoDbAttribute(value = "sort_key")
+ public int getSortKey() {
+ return this.sortKey;
+ }
+
+ public void setSortKey(int sortKey) {
+ this.sortKey = sortKey;
+ }
+
+ // Any @DynamoDbEncryption annotation here would be IGNORED
+ // Instead, the Annotations MUST BE placed on the Getter Methods of
+ // FlattenedNestedBean.
+ @DynamoDbFlatten
+ public FlattenedNestedBean getNestedBeanClass() {
+ return this.nestedBeanClass;
+ }
+
+ public void setNestedBeanClass(FlattenedNestedBean nestedBeanClass) {
+ this.nestedBeanClass = nestedBeanClass;
+ }
+
+ @DynamoDbBean
+ public static class FlattenedNestedBean {
+ private String id;
+ private String firstName;
+ private String lastName;
+
+ public FlattenedNestedBean() {};
+
+ public FlattenedNestedBean(String id, String firstName, String lastName) {
+ this.id = id;
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ @DynamoDbAttribute("id")
+ public String getId() { return this.id; }
+ public void setId(String id) { this.id = id; }
+
+ @DynamoDbEncryptionSignOnly
+ @DynamoDbAttribute("firstName")
+ public String getFirstName() { return firstName; }
+ public void setFirstName(String firstName) { this.firstName = firstName; }
+
+ @DynamoDbEncryptionDoNothing
+ @DynamoDbAttribute("lastName")
+ public String getLastName() { return lastName; }
+ public void setLastName(String lastName) { this.lastName = lastName; }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ FlattenedNestedBean other = (FlattenedNestedBean) obj;
+ if (id == null) {
+ if (other.getId() != null) return false;
+ } else if (!id.equals(other.getId())) return false;
+ if (firstName == null) {
+ if (other.getFirstName() != null) return false;
+ } else if (!firstName.equals(other.getFirstName())) return false;
+ if (lastName == null) {
+ if (other.getLastName() != null) return false;
+ } else if (!lastName.equals(other.getLastName())) return false;
+ return true;
+ }
+ }
+}
diff --git a/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/ConflictingAnnotatedNestedBean.java b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/ConflictingAnnotatedNestedBean.java
new file mode 100644
index 000000000..90020645a
--- /dev/null
+++ b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/ConflictingAnnotatedNestedBean.java
@@ -0,0 +1,130 @@
+package software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient;
+
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;
+
+/**
+ * This is an INVALID use of DynamoDbEncryption annotations on nested attributes.
+ * The DynamoDbEncryption annotations are placed on elements that are NOT
+ * DynamoDB Attributes but that will be mapped together into one DynamoDB
+ * Attribute that is a Map.
+ * Worse yet, the top level annotation conflicts with annotations given in
+ * the subfields.
+ */
+@DynamoDbBean
+public class ConflictingAnnotatedNestedBean {
+ private String partitionKey;
+ private int sortKey;
+ private NestedBean nestedEncrypted;
+ private NestedBean nestedSigned;
+ private NestedBean nestedIgnored;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAttribute(value = "partition_key")
+ public String getPartitionKey() {
+ return this.partitionKey;
+ }
+ public void setPartitionKey(String partitionKey) {
+ this.partitionKey = partitionKey;
+ }
+
+ @DynamoDbSortKey
+ @DynamoDbAttribute(value = "sort_key")
+ public int getSortKey() {
+ return this.sortKey;
+ }
+ public void setSortKey(int sortKey) {
+ this.sortKey = sortKey;
+ }
+
+ // By Default, DB-ESDK for DDB will Encrypt & Sign this field
+ public NestedBean getNestedEncrypted() {
+ return this.nestedEncrypted;
+ }
+ public void setNestedEncrypted(NestedBean nested) {
+ this.nestedEncrypted = nested;
+ }
+
+ @DynamoDbEncryptionSignOnly //This annotation is respected
+ public NestedBean getNestedSigned() {
+ return this.nestedSigned;
+ }
+ public void setNestedSigned(NestedBean nested) {
+ this.nestedSigned = nested;
+ }
+
+ @DynamoDbEncryptionDoNothing //This annotation is respected
+ public NestedBean getNestedIgnored() {
+ return this.nestedIgnored;
+ }
+ public void setNestedIgnored(NestedBean nestedIgnored) {
+ this.nestedIgnored = nestedIgnored;
+ }
+
+ @DynamoDbBean
+ public static class NestedBean {
+ private String id;
+ private String firstName;
+ private String lastName;
+
+ public NestedBean() {};
+
+ NestedBean(String id, String firstName, String lastName) {
+ this.id = id;
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ // Ignored DynamoDb Annotation
+ @DynamoDbAttribute("id")
+ public String getId() {
+ return this.id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ // Ignored DynamoDb & DynamoDbEncryption Annotations
+ @DynamoDbEncryptionSignOnly
+ @DynamoDbAttribute("firstName")
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ // Ignored DynamoDb & DynamoDbEncryption Annotations
+ @DynamoDbEncryptionDoNothing
+ @DynamoDbAttribute("lastName")
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ NestedBean other = (NestedBean) obj;
+ if (id == null) {
+ if (other.getId() != null) return false;
+ } else if (!id.equals(other.getId())) return false;
+ if (firstName == null) {
+ if (other.getFirstName() != null) return false;
+ } else if (!firstName.equals(other.getFirstName())) return false;
+ if (lastName == null) {
+ if (other.getLastName() != null) return false;
+ } else if (!lastName.equals(other.getLastName())) return false;
+ return true;
+ }
+ }
+}
diff --git a/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/ConflictingFlattenedBean.java b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/ConflictingFlattenedBean.java
new file mode 100644
index 000000000..6aa99979b
--- /dev/null
+++ b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/ConflictingFlattenedBean.java
@@ -0,0 +1,239 @@
+package software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient;
+
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbFlatten;
+
+/**
+ * This is an INVALID use of DynamoDbEncryption annotations on top level attributes.
+ * The DynamoDbEncryption annotations are placed on elements that are NOT
+ * DynamoDB Attributes but that will be replaced by the flattened class.
+ *
+ * Worse yet, the top level annotation conflicts with annotations given in
+ * the subfields.
+ */
+@DynamoDbBean
+public class ConflictingFlattenedBean {
+ private String partitionKey;
+ private int sortKey;
+ private NestedBean nestedEncrypted;
+ private AnotherNestedBean nestedSigned;
+ private FinalNestedBean nestedIgnored;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAttribute(value = "partition_key")
+ public String getPartitionKey() {
+ return this.partitionKey;
+ }
+ public void setPartitionKey(String partitionKey) {
+ this.partitionKey = partitionKey;
+ }
+
+ @DynamoDbSortKey
+ @DynamoDbAttribute(value = "sort_key")
+ public int getSortKey() {
+ return this.sortKey;
+ }
+ public void setSortKey(int sortKey) {
+ this.sortKey = sortKey;
+ }
+
+ @DynamoDbFlatten
+ public NestedBean getNestedEncrypted() {
+ return this.nestedEncrypted;
+ }
+ public void setNestedEncrypted(NestedBean nested) {
+ this.nestedEncrypted = nested;
+ }
+
+ @DynamoDbFlatten
+ @DynamoDbEncryptionSignOnly //This annotation is IGNORED
+ public AnotherNestedBean getNestedSigned() {
+ return this.nestedSigned;
+ }
+ public void setNestedSigned(AnotherNestedBean nested) {
+ this.nestedSigned = nested;
+ }
+
+ @DynamoDbFlatten
+ @DynamoDbEncryptionDoNothing //This annotation is IGNORED
+ public FinalNestedBean getNestedIgnored() {
+ return this.nestedIgnored;
+ }
+ public void setNestedIgnored(FinalNestedBean nestedIgnored) {
+ this.nestedIgnored = nestedIgnored;
+ }
+
+ @DynamoDbBean
+ public static class NestedBean {
+ private String id;
+ private String firstName;
+ private String lastName;
+
+ public NestedBean() {};
+
+ NestedBean(String id, String firstName, String lastName) {
+ this.id = id;
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ // By Default, DB-ESDK for DDB will Encrypt & Sign this field
+ public String getId() {
+ return this.id;
+ }
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @DynamoDbEncryptionSignOnly //This annotation is respected
+ public String getFirstName() {
+ return firstName;
+ }
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ @DynamoDbEncryptionDoNothing //This annotation is respected
+ public String getLastName() {
+ return lastName;
+ }
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ NestedBean other = (NestedBean) obj;
+ if (id == null) {
+ if (other.getId() != null) return false;
+ } else if (!id.equals(other.getId())) return false;
+ if (firstName == null) {
+ if (other.getFirstName() != null) return false;
+ } else if (!firstName.equals(other.getFirstName())) return false;
+ if (lastName == null) {
+ if (other.getLastName() != null) return false;
+ } else if (!lastName.equals(other.getLastName())) return false;
+ return true;
+ }
+ }
+
+ @DynamoDbBean
+ public static class AnotherNestedBean {
+ private String anotherId;
+ private String anotherFirstName;
+ private String anotherLastName;
+
+ public AnotherNestedBean() {};
+
+ AnotherNestedBean(String id, String firstName, String lastName) {
+ this.anotherId = id;
+ this.anotherFirstName = firstName;
+ this.anotherLastName = lastName;
+ }
+
+ // By Default, DB-ESDK for DDB will Encrypt & Sign this field
+ public String getAnotherId() {
+ return this.anotherId;
+ }
+ public void setAnotherId(String anotherId) {
+ this.anotherId = anotherId;
+ }
+
+ @DynamoDbEncryptionSignOnly //This annotation is respected
+ public String getAnotherFirstName() {
+ return anotherFirstName;
+ }
+ public void setAnotherFirstName(String anotherFirstName) {
+ this.anotherFirstName = anotherFirstName;
+ }
+
+ @DynamoDbEncryptionDoNothing //This annotation is respected
+ public String getAnotherLastName() {
+ return anotherLastName;
+ }
+ public void setAnotherLastName(String anotherLastName) {
+ this.anotherLastName = anotherLastName;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ AnotherNestedBean other = (AnotherNestedBean) obj;
+ if (anotherId == null) {
+ if (other.getAnotherId() != null) return false;
+ } else if (!anotherId.equals(other.getAnotherId())) return false;
+ if (anotherFirstName == null) {
+ if (other.getAnotherFirstName() != null) return false;
+ } else if (!anotherFirstName.equals(other.getAnotherFirstName())) return false;
+ if (anotherLastName == null) {
+ if (other.getAnotherLastName() != null) return false;
+ } else if (!anotherLastName.equals(other.getAnotherLastName())) return false;
+ return true;
+ }
+ }
+
+ @DynamoDbBean
+ public static class FinalNestedBean {
+ private String finalId;
+ private String finalFirstName;
+ private String finalLastName;
+
+ public FinalNestedBean() {};
+
+ FinalNestedBean(String id, String firstName, String lastName) {
+ this.finalId = id;
+ this.finalFirstName = firstName;
+ this.finalLastName = lastName;
+ }
+
+ // By Default, DB-ESDK for DDB will Encrypt & Sign this field
+ public String getFinalId() {
+ return this.finalId;
+ }
+ public void setFinalId(String finalId) {
+ this.finalId = finalId;
+ }
+
+ @DynamoDbEncryptionSignOnly //This annotation is respected
+ public String getFinalFirstName() {
+ return finalFirstName;
+ }
+ public void setFinalFirstName(String finalFirstName) {
+ this.finalFirstName = finalFirstName;
+ }
+
+ @DynamoDbEncryptionDoNothing //This annotation is respected
+ public String getFinalLastName() {
+ return finalLastName;
+ }
+ public void setFinalLastName(String finalLastName) {
+ this.finalLastName = finalLastName;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ FinalNestedBean other = (FinalNestedBean) obj;
+ if (finalId == null) {
+ if (other.getFinalId() != null) return false;
+ } else if (!finalId.equals(other.getFinalId())) return false;
+ if (finalFirstName == null) {
+ if (other.getFinalFirstName() != null) return false;
+ } else if (!finalFirstName.equals(other.getFinalFirstName())) return false;
+ if (finalLastName == null) {
+ if (other.getFinalLastName() != null) return false;
+ } else if (!finalLastName.equals(other.getFinalLastName())) return false;
+ return true;
+ }
+ }
+}
diff --git a/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEncryptionEnhancedClientIntegrationTests.java b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEncryptionEnhancedClientIntegrationTests.java
index 4e4de6347..38aa32d11 100644
--- a/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEncryptionEnhancedClientIntegrationTests.java
+++ b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEncryptionEnhancedClientIntegrationTests.java
@@ -18,13 +18,20 @@
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.kms.model.KmsException;
+
+import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.AnnotatedConvertedBy.ConvertedByNestedBean;
+import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.AnnotatedFlattenedBean.FlattenedNestedBean;
+import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.DynamoDbEncryptionException;
import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.LegacyOverride;
import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.LegacyPolicy;
import software.amazon.cryptography.dbencryptionsdk.structuredencryption.model.CryptoAction;
import software.amazon.cryptography.dbencryptionsdk.dynamodb.DynamoDbEncryptionInterceptor;
+import software.amazon.cryptography.materialproviders.IKeyring;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import static org.testng.Assert.assertEquals;
@@ -32,33 +39,50 @@
import org.testng.annotations.Test;
+import javax.annotation.Nullable;
+
public class DynamoDbEncryptionEnhancedClientIntegrationTests {
- @Test
- public void TestPutAndGet() {
- TableSchema
+ */
+@DynamoDbBean
+public class InvalidAnnotatedNestedBean {
+ private String partitionKey;
+ private int sortKey;
+ private NestedBean nestedBeanClass;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAttribute(value = "partition_key")
+ public String getPartitionKey() {
+ return this.partitionKey;
+ }
+
+ public void setPartitionKey(String partitionKey) {
+ this.partitionKey = partitionKey;
+ }
+
+ @DynamoDbSortKey
+ @DynamoDbAttribute(value = "sort_key")
+ public int getSortKey() {
+ return this.sortKey;
+ }
+
+ public void setSortKey(int sortKey) {
+ this.sortKey = sortKey;
+ }
+
+ public NestedBean getNestedBeanClass() {
+ return this.nestedBeanClass;
+ }
+ public void setNestedBeanClass(NestedBean nestedBeanClass) {
+ this.nestedBeanClass = nestedBeanClass;
+ }
+
+ @DynamoDbBean
+ public static class NestedBean {
+ private String id;
+ private String firstName;
+ private String lastName;
+
+ public NestedBean() {};
+
+ NestedBean(String id, String firstName, String lastName) {
+ this.id = id;
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ @DynamoDbAttribute("id") //This annotation is IGNORED
+ public String getId() {
+ return this.id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @DynamoDbEncryptionSignOnly //This annotation is IGNORED
+ @DynamoDbAttribute("firstName") //This annotation is IGNORED
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ @DynamoDbEncryptionDoNothing //This annotation is IGNORED
+ @DynamoDbAttribute("lastName") //This annotation is IGNORED
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ NestedBean other = (NestedBean) obj;
+ if (id == null) {
+ if (other.getId() != null) return false;
+ } else if (!id.equals(other.getId())) return false;
+ if (firstName == null) {
+ if (other.getFirstName() != null) return false;
+ } else if (!firstName.equals(other.getFirstName())) return false;
+ if (lastName == null) {
+ if (other.getLastName() != null) return false;
+ } else if (!lastName.equals(other.getLastName())) return false;
+ return true;
+ }
+ }
+}