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 { + @Override + public AttributeValue transformFrom(ConvertedByNestedBean ConvertedByNestedBean) { + Map map = new HashMap<>(3); + map.put("id", AttributeValue.fromS(ConvertedByNestedBean.getId())); + map.put("firstName", AttributeValue.fromS(ConvertedByNestedBean.getFirstName())); + map.put("lastName", AttributeValue.fromS(ConvertedByNestedBean.getLastName())); + return AttributeValue.fromM(map); + } + + @Override + public ConvertedByNestedBean transformTo(AttributeValue attributeValue) { + Map map = attributeValue.m(); + return new ConvertedByNestedBean( + map.get("id").s(), + map.get("firstName").s(), + map.get("lastName").s()); + } + + @Override + public EnhancedType type() { + return EnhancedType.of(ConvertedByNestedBean.class); + } + + @Override + public AttributeValueType attributeValueType() { + return AttributeValueType.M; + } + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/AnnotatedFlattenedBean.java b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/AnnotatedFlattenedBean.java new file mode 100644 index 000000000..b1222f1a3 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/AnnotatedFlattenedBean.java @@ -0,0 +1,102 @@ +package software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient; + +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.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbFlatten; + +/** + * This is a valid use of DynamoDbEncryption annotations on nested attributes. + * Currently, the ONLY WAY to properly specify DynamoDbEncryption actions + * on nested attributes is via the @DynamoDbFlatten attribute.

+ * + * 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 schemaOnEncrypt = TableSchema.fromBean(SimpleClass.class); + + private static DynamoDbEnhancedClient initEnhancedClientWithInterceptor( + final TableSchema schemaOnEncrypt, + final List allowedUnsignedAttributes, + @Nullable String kmsKeyId, + @Nullable String tableName + ) { Map tableConfigs = new HashMap<>(); - tableConfigs.put(TEST_TABLE_NAME, - DynamoDbEnhancedTableEncryptionConfig.builder() - .logicalTableName(TEST_TABLE_NAME) - .keyring(createKmsKeyring()) - .allowedUnsignedAttributes(Arrays.asList("doNothing")) - .schemaOnEncrypt(schemaOnEncrypt) - .build()); + IKeyring kmsKeyring = kmsKeyId == null ? createKmsKeyring() : createKmsKeyring(kmsKeyId); + String testTableName = tableName == null ? TEST_TABLE_NAME : tableName; + tableConfigs.put(testTableName, + DynamoDbEnhancedTableEncryptionConfig.builder() + .logicalTableName(testTableName) + .keyring(kmsKeyring) + .allowedUnsignedAttributes(allowedUnsignedAttributes) + .schemaOnEncrypt(schemaOnEncrypt) + .build()); + DynamoDbEncryptionInterceptor interceptor = - DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( - CreateDynamoDbEncryptionInterceptorInput.builder() - .tableEncryptionConfigs(tableConfigs) - .build() - ); + DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( + CreateDynamoDbEncryptionInterceptorInput.builder() + .tableEncryptionConfigs(tableConfigs) + .build() + ); DynamoDbClient ddb = DynamoDbClient.builder() - .overrideConfiguration( - ClientOverrideConfiguration.builder() - .addExecutionInterceptor(interceptor) - .build()) - .build(); - DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder() - .dynamoDbClient(ddb) - .build(); + .overrideConfiguration( + ClientOverrideConfiguration.builder() + .addExecutionInterceptor(interceptor) + .build()) + .build(); + return DynamoDbEnhancedClient.builder() + .dynamoDbClient(ddb) + .build(); + } + + @Test + public void TestPutAndGet() { + TableSchema schemaOnEncrypt = TableSchema.fromBean(SimpleClass.class); + List allowedUnsignedAttributes = Collections.singletonList("doNothing"); + DynamoDbEnhancedClient enhancedClient = + initEnhancedClientWithInterceptor(schemaOnEncrypt, allowedUnsignedAttributes, null, null); DynamoDbTable table = enhancedClient.table(TEST_TABLE_NAME, schemaOnEncrypt); @@ -87,33 +111,89 @@ public void TestPutAndGet() { assertEquals(result.getDoNothing(), "fizzbuzz"); } + @Test + public void TestPutAndGetAnnotatedFlattenedBean() { + final String PARTITION = "AnnotatedFlattenedBean"; + final int SORT = 20230713; + + TableSchema schemaOnEncrypt = + TableSchema.fromBean(AnnotatedFlattenedBean.class); + List allowedUnsignedAttributes = Collections.singletonList("lastName"); + DynamoDbEnhancedClient enhancedClient = + initEnhancedClientWithInterceptor(schemaOnEncrypt, allowedUnsignedAttributes, null, null); + DynamoDbTable table = + enhancedClient.table(TEST_TABLE_NAME, schemaOnEncrypt); + + AnnotatedFlattenedBean record = new AnnotatedFlattenedBean(); + record.setPartitionKey(PARTITION); + record.setSortKey(SORT); + FlattenedNestedBean nestedBean = new FlattenedNestedBean( + "9305B367-C477-4A58-9E6C-BF7D59D17C8A", "James", "Bond" + ); + record.setNestedBeanClass(nestedBean); + + // Put an item into an Amazon DynamoDB table. + table.putItem(record); + + // Get the item back from the table + Key key = Key.builder() + .partitionValue(PARTITION).sortValue(SORT) + .build(); + + // Get the item by using the key. + AnnotatedFlattenedBean result = table.getItem( + (GetItemEnhancedRequest.Builder requestBuilder) -> requestBuilder.key(key)); + assertEquals(result.getPartitionKey(), record.getPartitionKey()); + assertEquals(result.getSortKey(), record.getSortKey()); + assertEquals(result.getNestedBeanClass(), record.getNestedBeanClass()); + } + + @Test + public void TestPutAndGetAnnotatedConvertedBy() { + final String PARTITION = "AnnotatedConvertedBy"; + final int SORT = 20230713; + TableSchema schemaOnEncrypt = + TableSchema.fromBean(AnnotatedConvertedBy.class); + List allowedUnsignedAttributes = Collections.singletonList("nestedIgnored"); + DynamoDbEnhancedClient enhancedClient = + initEnhancedClientWithInterceptor(schemaOnEncrypt, allowedUnsignedAttributes, null, null); + + DynamoDbTable table = enhancedClient.table(TEST_TABLE_NAME, schemaOnEncrypt); + + AnnotatedConvertedBy record = new AnnotatedConvertedBy(); + record.setPartitionKey(PARTITION); + record.setSortKey(SORT); + ConvertedByNestedBean nestedBean = new ConvertedByNestedBean( + "9305B367-C477-4A58-9E6C-BF7D59D17C8A", "Winnie", "the-Pooh" + ); + record.setNestedEncrypted(nestedBean); + record.setNestedIgnored(nestedBean); + record.setNestedSigned(nestedBean); + + // Put an item into an Amazon DynamoDB table. + table.putItem(record); + + // Get the item back from the table + Key key = Key.builder() + .partitionValue(PARTITION).sortValue(SORT) + .build(); + + // Get the item by using the key. + AnnotatedConvertedBy result = table.getItem( + (GetItemEnhancedRequest.Builder requestBuilder) -> requestBuilder.key(key)); + assertEquals(result.getPartitionKey(), record.getPartitionKey()); + assertEquals(result.getSortKey(), record.getSortKey()); + assertEquals(result.getNestedIgnored(), record.getNestedIgnored()); + assertEquals(result.getNestedEncrypted(), record.getNestedEncrypted()); + assertEquals(result.getNestedSigned(), record.getNestedSigned()); + } + @Test public void TestPutAndGetSignOnly() { TableSchema schemaOnEncrypt = TableSchema.fromBean(SignOnlyClass.class); - Map tableConfigs = new HashMap<>(); - tableConfigs.put(TEST_TABLE_NAME, - DynamoDbEnhancedTableEncryptionConfig.builder() - .logicalTableName(TEST_TABLE_NAME) - .keyring(createKmsKeyring()) - .allowedUnsignedAttributes(Arrays.asList("doNothing")) - .schemaOnEncrypt(schemaOnEncrypt) - .build()); - DynamoDbEncryptionInterceptor interceptor = - DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( - CreateDynamoDbEncryptionInterceptorInput.builder() - .tableEncryptionConfigs(tableConfigs) - .build() - ); - DynamoDbClient ddb = DynamoDbClient.builder() - .overrideConfiguration( - ClientOverrideConfiguration.builder() - .addExecutionInterceptor(interceptor) - .build()) - .build(); - DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder() - .dynamoDbClient(ddb) - .build(); - + List allowedUnsignedAttributes = Collections.singletonList("doNothing"); + DynamoDbEnhancedClient enhancedClient = + initEnhancedClientWithInterceptor(schemaOnEncrypt, allowedUnsignedAttributes, null, null); DynamoDbTable table = enhancedClient.table(TEST_TABLE_NAME, schemaOnEncrypt); SignOnlyClass record = new SignOnlyClass(); @@ -270,30 +350,9 @@ public void TestKmsError() { // Use an KMS Key that does not exist String invalidKey = "arn:aws:kms:us-west-2:658956600833:key/ffffffff-ffff-ffff-ffff-ffffffffffff"; TableSchema schemaOnEncrypt = TableSchema.fromBean(SimpleClass.class); - Map tableConfigs = new HashMap<>(); - tableConfigs.put(TEST_TABLE_NAME, - DynamoDbEnhancedTableEncryptionConfig.builder() - .logicalTableName(TEST_TABLE_NAME) - .keyring(createKmsKeyring(invalidKey)) - .allowedUnsignedAttributes(Arrays.asList("doNothing")) - .schemaOnEncrypt(schemaOnEncrypt) - .build()); - DynamoDbEncryptionInterceptor interceptor = - DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( - CreateDynamoDbEncryptionInterceptorInput.builder() - .tableEncryptionConfigs(tableConfigs) - .build() - ); - DynamoDbClient ddb = DynamoDbClient.builder() - .overrideConfiguration( - ClientOverrideConfiguration.builder() - .addExecutionInterceptor(interceptor) - .build()) - .build(); - DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder() - .dynamoDbClient(ddb) - .build(); - + List allowedUnsignedAttributes = Collections.singletonList("doNothing"); + DynamoDbEnhancedClient enhancedClient = + initEnhancedClientWithInterceptor(schemaOnEncrypt, allowedUnsignedAttributes, invalidKey, null); DynamoDbTable table = enhancedClient.table(TEST_TABLE_NAME, schemaOnEncrypt); SimpleClass record = new SimpleClass(); @@ -312,32 +371,12 @@ public void TestKmsError() { expectedExceptionsMessageRegExp = ".*Status Code: 400.*" ) public void TestDdbError() { - // Use an KMS Key that does not exist + // Use a DynamoDB Table that does not exist String badTableName = "tableDoesNotExist"; TableSchema schemaOnEncrypt = TableSchema.fromBean(SimpleClass.class); - Map tableConfigs = new HashMap<>(); - tableConfigs.put(badTableName, - DynamoDbEnhancedTableEncryptionConfig.builder() - .logicalTableName(badTableName) - .keyring(createKmsKeyring()) - .allowedUnsignedAttributes(Arrays.asList("doNothing")) - .schemaOnEncrypt(schemaOnEncrypt) - .build()); - DynamoDbEncryptionInterceptor interceptor = - DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( - CreateDynamoDbEncryptionInterceptorInput.builder() - .tableEncryptionConfigs(tableConfigs) - .build() - ); - DynamoDbClient ddb = DynamoDbClient.builder() - .overrideConfiguration( - ClientOverrideConfiguration.builder() - .addExecutionInterceptor(interceptor) - .build()) - .build(); - DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder() - .dynamoDbClient(ddb) - .build(); + List allowedUnsignedAttributes = Collections.singletonList("doNothing"); + DynamoDbEnhancedClient enhancedClient = + initEnhancedClientWithInterceptor(schemaOnEncrypt, allowedUnsignedAttributes, null, badTableName); DynamoDbTable table = enhancedClient.table(badTableName, schemaOnEncrypt); diff --git a/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryptionTest.java b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryptionTest.java index 8160270b2..603370762 100644 --- a/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryptionTest.java +++ b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryptionTest.java @@ -1,7 +1,6 @@ package software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient; import com.amazonaws.services.dynamodbv2.datamodeling.encryption.DynamoDBEncryptor; -import com.amazonaws.services.dynamodbv2.datamodeling.encryption.EncryptionFlags; import com.amazonaws.services.dynamodbv2.datamodeling.encryption.providers.DirectKmsMaterialProvider; import com.amazonaws.services.kms.AWSKMS; import com.amazonaws.services.kms.AWSKMSClientBuilder; @@ -9,7 +8,6 @@ import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.DynamoDbItemEncryptorException; import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.LegacyOverride; import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.LegacyPolicy; -import software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.model.OpaqueError; import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.DynamoDbEncryptionException; import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.DynamoDbTableEncryptionConfig; import software.amazon.cryptography.materialproviders.model.DBEAlgorithmSuiteId; @@ -17,9 +15,10 @@ import software.amazon.cryptography.dbencryptionsdk.dynamodb.DynamoDbEncryptionInterceptor; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.Set; import static org.testng.Assert.*; import static software.amazon.cryptography.dbencryptionsdk.dynamodb.TestUtils.*; @@ -223,4 +222,131 @@ public void TestDoubleAnnotationOnAttribute() { .tableEncryptionConfigs(tableConfigs) .build()); } + + @Test( + expectedExceptions = DynamoDbItemEncryptorException.class, + expectedExceptionsMessageRegExp = "Attribute lastName is configured as DO_NOTHING but it must also be in unauthenticatedAttributes or begin with the unauthenticatedPrefix." + ) + public void TestFlattenedNestedBeanAnnotationMissingUnauthenticatedAttributes() { + TableSchema schemaOnEncrypt = + TableSchema.fromBean(AnnotatedFlattenedBean.class); + Map tableConfigs = new HashMap<>(); + tableConfigs.put(TEST_TABLE_NAME, + DynamoDbEnhancedTableEncryptionConfig.builder() + .logicalTableName(TEST_TABLE_NAME) + .keyring(createKmsKeyring()) + .schemaOnEncrypt(schemaOnEncrypt) + .build()); + DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( + CreateDynamoDbEncryptionInterceptorInput.builder() + .tableEncryptionConfigs(tableConfigs) + .build()); + } + + @Test + public void TestFlattenedNestedBeanAnnotation() { + TableSchema schemaOnEncrypt = + TableSchema.fromBean(AnnotatedFlattenedBean.class); + Map tableConfigs = new HashMap<>(); + tableConfigs.put(TEST_TABLE_NAME, + DynamoDbEnhancedTableEncryptionConfig.builder() + .logicalTableName(TEST_TABLE_NAME) + .keyring(createKmsKeyring()) + .schemaOnEncrypt(schemaOnEncrypt) + .allowedUnsignedAttributes(Collections.singletonList("lastName")) + .build()); + DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( + CreateDynamoDbEncryptionInterceptorInput.builder() + .tableEncryptionConfigs(tableConfigs) + .build()); + } + + @Test + public void TestAnnotatedConvertedBy() { + TableSchema schemaOnEncrypt = + TableSchema.fromBean(AnnotatedConvertedBy.class); + Map tableConfigs = new HashMap<>(); + tableConfigs.put(TEST_TABLE_NAME, + DynamoDbEnhancedTableEncryptionConfig.builder() + .logicalTableName(TEST_TABLE_NAME) + .keyring(createKmsKeyring()) + .schemaOnEncrypt(schemaOnEncrypt) + .allowedUnsignedAttributes(Collections.singletonList("nestedIgnored")) + .build()); + DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( + CreateDynamoDbEncryptionInterceptorInput.builder() + .tableEncryptionConfigs(tableConfigs) + .build()); + } + + @Test( + expectedExceptions = DynamoDbEncryptionException.class + ) + public void TestInvalidNestedBeanAnnotation() { + TableSchema schemaOnEncrypt = + TableSchema.fromBean(InvalidAnnotatedNestedBean.class); + Map tableConfigs = new HashMap<>(); + tableConfigs.put(TEST_TABLE_NAME, + DynamoDbEnhancedTableEncryptionConfig.builder() + .logicalTableName(TEST_TABLE_NAME) + .keyring(createKmsKeyring()) + .schemaOnEncrypt(schemaOnEncrypt) + .build()); + DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( + CreateDynamoDbEncryptionInterceptorInput.builder() + .tableEncryptionConfigs(tableConfigs) + .build()); + } + + @Test( + expectedExceptions = DynamoDbEncryptionException.class + ) + public void TestConflictingAnnotatedNestedBean() { + TableSchema schemaOnEncrypt = + TableSchema.fromBean(ConflictingAnnotatedNestedBean.class); + Map tableConfigs = new HashMap<>(); + tableConfigs.put(TEST_TABLE_NAME, + DynamoDbEnhancedTableEncryptionConfig.builder() + .logicalTableName(TEST_TABLE_NAME) + .keyring(createKmsKeyring()) + .schemaOnEncrypt(schemaOnEncrypt) + .allowedUnsignedAttributes(Collections.singletonList("nestedIgnored")) + .build()); + DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( + CreateDynamoDbEncryptionInterceptorInput.builder() + .tableEncryptionConfigs(tableConfigs) + .build()); + } + + @Test( + // We skip this Test. + enabled = false, + // The DB-ESDK-DynamoDB for Java SHOULD detect ALL DynamoDBEncryption + // Tags & Attributes that are IGNORED and throw an Exception. + // However, detecting IGNORED DynamoDBEncryption Tags & Attributes + // when a nested class is Flattened has NOT been implemented. + // See GitHub Issue #259: + // https://github.com/aws/aws-database-encryption-sdk-dynamodb-java/issues/259 + expectedExceptions = DynamoDbEncryptionException.class + ) + public void TestConflictingFlattenedBean() { + TableSchema schemaOnEncrypt = + TableSchema.fromBean(ConflictingFlattenedBean.class); + Map tableConfigs = new HashMap<>(); + List allowedUnsignedAttributes = Arrays.asList( + "lastName", + "anotherLastName", + "finalLastName"); + tableConfigs.put(TEST_TABLE_NAME, + DynamoDbEnhancedTableEncryptionConfig.builder() + .logicalTableName(TEST_TABLE_NAME) + .keyring(createKmsKeyring()) + .schemaOnEncrypt(schemaOnEncrypt) + .allowedUnsignedAttributes(allowedUnsignedAttributes) + .build()); + DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( + CreateDynamoDbEncryptionInterceptorInput.builder() + .tableEncryptionConfigs(tableConfigs) + .build()); + } } diff --git a/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/InvalidAnnotatedNestedBean.java b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/InvalidAnnotatedNestedBean.java new file mode 100644 index 000000000..720d00d58 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/InvalidAnnotatedNestedBean.java @@ -0,0 +1,108 @@ +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.

+ */ +@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; + } + } +}