Skip to content

Commit 3b86651

Browse files
authored
fix(EnhancedClient): Recurse through entire table map (#260)
1 parent 6c9494d commit 3b86651

18 files changed

+255
-68
lines changed

DynamoDbEncryption/runtimes/java/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ dependencies {
105105
testImplementation("junit:junit:4.13.2")
106106
// https://mvnrepository.com/artifact/edu.umd.cs.mtc/multithreadedtc
107107
testImplementation("edu.umd.cs.mtc:multithreadedtc:1.01")
108+
// https://mvnrepository.com/artifact/org.projectlombok/lombok
109+
testImplementation("org.projectlombok:lombok:1.18.28")
110+
testAnnotationProcessor("org.projectlombok:lombok:1.18.28")
108111
}
109112

110113
publishing {

DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryption.java

Lines changed: 79 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
package software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient;
22

3-
import java.util.*;
4-
import java.util.stream.Collectors;
3+
import java.util.Collections;
4+
import java.util.HashMap;
5+
import java.util.HashSet;
6+
import java.util.List;
7+
import java.util.Map;
8+
import java.util.Objects;
9+
import java.util.Optional;
10+
import java.util.Set;
511

612
import software.amazon.awssdk.enhanced.dynamodb.IndexMetadata;
713
import software.amazon.awssdk.enhanced.dynamodb.KeyAttributeMetadata;
@@ -23,9 +29,8 @@ public class DynamoDbEnhancedClientEncryption {
2329
public static DynamoDbEncryptionInterceptor CreateDynamoDbEncryptionInterceptor(
2430
CreateDynamoDbEncryptionInterceptorInput input) {
2531
Map<String, DynamoDbTableEncryptionConfig> tableConfigs = new HashMap<>();
26-
for (String tableName : input.tableEncryptionConfigs().keySet()) {
27-
tableConfigs.put(tableName, getTableConfig(input.tableEncryptionConfigs().get(tableName)));
28-
}
32+
input.tableEncryptionConfigs().forEach(
33+
(name, config) -> tableConfigs.put(name, getTableConfig(config, name)));
2934

3035
return DynamoDbEncryptionInterceptor.builder()
3136
.config(DynamoDbTablesEncryptionConfig.builder()
@@ -37,42 +42,50 @@ public static DynamoDbEncryptionInterceptor CreateDynamoDbEncryptionInterceptor(
3742
private static Set<String> attributeNamesUsedInIndices(
3843
final TableMetadata tableMetadata
3944
) {
40-
Set<String> partitionAttributeNames = tableMetadata.indices().stream()
45+
Set<String> allIndexAttributes = new HashSet<>();
46+
tableMetadata.indices().stream()
4147
.map(IndexMetadata::partitionKey)
4248
.filter(Optional::isPresent)
4349
.map(Optional::get)
4450
.map(KeyAttributeMetadata::name)
45-
.collect(Collectors.toSet());
46-
Set<String> sortAttributeNames = tableMetadata.indices().stream()
51+
.forEach(allIndexAttributes::add);
52+
tableMetadata.indices().stream()
4753
.map(IndexMetadata::sortKey)
4854
.filter(Optional::isPresent)
4955
.map(Optional::get)
5056
.map(KeyAttributeMetadata::name)
51-
.collect(Collectors.toSet());
52-
Set<String> allIndexAttributes = new HashSet<>();
53-
allIndexAttributes.addAll(partitionAttributeNames);
54-
allIndexAttributes.addAll(sortAttributeNames);
57+
.forEach(allIndexAttributes::add);
5558
return allIndexAttributes;
5659
}
5760

58-
private static DynamoDbTableEncryptionConfig getTableConfig(DynamoDbEnhancedTableEncryptionConfig configWithSchema) {
61+
private static DynamoDbTableEncryptionConfig getTableConfig(
62+
final DynamoDbEnhancedTableEncryptionConfig configWithSchema,
63+
final String tableName
64+
) {
5965
Map<String, CryptoAction> actions = new HashMap<>();
6066

61-
Set<String> signOnlyAttributes = configWithSchema.schemaOnEncrypt().tableMetadata().customMetadataObject(CUSTOM_DDB_ENCRYPTION_SIGN_ONLY_PREFIX, Set.class).orElseGet(HashSet::new);
62-
Set<String> doNothingAttributes = configWithSchema.schemaOnEncrypt().tableMetadata().customMetadataObject(CUSTOM_DDB_ENCRYPTION_DO_NOTHING_PREFIX, Set.class).orElseGet(HashSet::new);
63-
Set<String> keyAttributes = attributeNamesUsedInIndices(configWithSchema.schemaOnEncrypt().tableMetadata());
67+
TableSchema<?> topTableSchema = configWithSchema.schemaOnEncrypt();
68+
Set<String> signOnlyAttributes = getSignOnlyAttributes(topTableSchema);
69+
Set<String> doNothingAttributes = getDoNothingAttributes(topTableSchema);
70+
Set<String> keyAttributes = attributeNamesUsedInIndices(topTableSchema.tableMetadata());
6471

6572
if (!Collections.disjoint(keyAttributes, doNothingAttributes)) {
6673
throw DynamoDbEncryptionException.builder()
67-
.message("Cannot use @DynamoDbEncryptionDoNothing on primary key attributes.")
74+
.message(String.format(
75+
"Cannot use @DynamoDbEncryptionDoNothing on primary key attributes. Found on Table Name: %s",
76+
tableName))
6877
.build();
6978
} else if (!Collections.disjoint(signOnlyAttributes, doNothingAttributes)) {
7079
throw DynamoDbEncryptionException.builder()
71-
.message("Cannot use @DynamoDbEncryptionDoNothing and @DynamoDbEncryptionSignOnly on same attribute.")
80+
.message(String.format(
81+
"Cannot use @DynamoDbEncryptionDoNothing and @DynamoDbEncryptionSignOnly on same attribute. Found on Table Name: %s",
82+
tableName))
7283
.build();
7384
}
7485

75-
List<String> attributeNames = configWithSchema.schemaOnEncrypt().attributeNames();
86+
List<String> attributeNames = topTableSchema.attributeNames();
87+
StringBuilder path = new StringBuilder();
88+
path.append(tableName).append(".");
7689
for (String attributeName : attributeNames) {
7790
if (keyAttributes.contains(attributeName)) {
7891
// key attributes are always SIGN_ONLY
@@ -87,14 +100,14 @@ private static DynamoDbTableEncryptionConfig getTableConfig(DynamoDbEnhancedTabl
87100
}
88101

89102
// Detect Encryption Flags that are Ignored b/c they are in a Nested Class
90-
scanForIgnoredEncryptionTagsShallow(configWithSchema, attributeName);
103+
scanForIgnoredEncryptionTags(topTableSchema, attributeName, path);
91104
}
92105

93106
DynamoDbTableEncryptionConfig.Builder builder = DynamoDbTableEncryptionConfig.builder();
94-
String partitionName = configWithSchema.schemaOnEncrypt().tableMetadata().primaryPartitionKey();
107+
String partitionName = topTableSchema.tableMetadata().primaryPartitionKey();
95108
builder = builder.partitionKeyName(partitionName);
96109

97-
Optional<String> sortName = configWithSchema.schemaOnEncrypt().tableMetadata().primarySortKey();
110+
Optional<String> sortName = topTableSchema.tableMetadata().primarySortKey();
98111
if (sortName.isPresent()) {
99112
builder = builder.sortKeyName(sortName.get());
100113
}
@@ -122,10 +135,22 @@ private static DynamoDbTableEncryptionConfig getTableConfig(DynamoDbEnhancedTabl
122135
.build();
123136
}
124137

138+
@SuppressWarnings("unchecked")
139+
private static Set<String> getSignOnlyAttributes(TableSchema<?> tableSchema) {
140+
return tableSchema.tableMetadata()
141+
.customMetadataObject(CUSTOM_DDB_ENCRYPTION_SIGN_ONLY_PREFIX, Set.class)
142+
.orElseGet(HashSet::new);
143+
}
144+
145+
@SuppressWarnings("unchecked")
146+
private static Set<String> getDoNothingAttributes(TableSchema<?> tableSchema) {
147+
return tableSchema.tableMetadata()
148+
.customMetadataObject(CUSTOM_DDB_ENCRYPTION_DO_NOTHING_PREFIX, Set.class)
149+
.orElseGet(HashSet::new);
150+
}
151+
125152
/**
126153
* Detects DynamoDB Encryption Tags in Nested Enhanced Types.<p>
127-
* This method ONLY parses ONE Layer of nesting.<p>
128-
* It does NOT traverse further nested Enhanced Types.<p>
129154
* DynamoDB Encryption Tags in Nested Classes are IGNORED by the
130155
* Database Encryption SDK for DynamoDB.<p>
131156
* As such, Detection of a nested DynamoDB Encryption Tag on a Nested Type
@@ -138,30 +163,42 @@ private static DynamoDbTableEncryptionConfig getTableConfig(DynamoDbEnhancedTabl
138163
* as any Encryption Tag on the field that will be "flattened" is ignored.<p>
139164
* This method DOES NOT detect these "ignored-by-flattened" tags.
140165
*/
141-
private static void scanForIgnoredEncryptionTagsShallow(
142-
final DynamoDbEnhancedTableEncryptionConfig configWithSchema,
143-
final String attributeName
166+
private static void scanForIgnoredEncryptionTags(
167+
final TableSchema<?> tableSchema,
168+
final String attributeName,
169+
final StringBuilder path
144170
) {
145-
AttributeConverter attributeConverter = configWithSchema.schemaOnEncrypt().converterForAttribute(attributeName);
171+
AttributeConverter<?> attributeConverter = tableSchema.converterForAttribute(attributeName);
172+
StringBuilder attributePath = new StringBuilder(path).append(attributeName).append(".");
146173
if (
147174
Objects.nonNull(attributeConverter) &&
148175
Objects.nonNull(attributeConverter.type()) &&
149176
attributeConverter.type().tableSchema().isPresent()
150177
) {
151-
Object maybeTableSchema = attributeConverter.type().tableSchema().get();
152-
if (maybeTableSchema instanceof TableSchema) {
153-
TableSchema subTableSchema = (TableSchema) maybeTableSchema;
154-
if (
155-
subTableSchema.tableMetadata().customMetadata().containsKey(CUSTOM_DDB_ENCRYPTION_SIGN_ONLY_PREFIX) ||
156-
subTableSchema.tableMetadata().customMetadata().containsKey(CUSTOM_DDB_ENCRYPTION_DO_NOTHING_PREFIX)
157-
) {
158-
throw DynamoDbEncryptionException.builder()
159-
.message(String.format(
160-
"Detected a DynamoDbEncryption Tag/Configuration on a nested attribute of %s. " +
161-
"This is NOT Supported at this time!",
162-
attributeName))
163-
.build();
164-
}
178+
TableSchema<?> subTableSchema = attributeConverter.type().tableSchema().get();
179+
Set<String> signOnlyAttributes = getSignOnlyAttributes(subTableSchema);
180+
if (signOnlyAttributes.size() > 0) {
181+
throw DynamoDbEncryptionException.builder()
182+
.message(String.format(
183+
"Detected DynamoDbEncryption Tag %s on a nested attribute with Path %s. " +
184+
"This is NOT Supported at this time!",
185+
CUSTOM_DDB_ENCRYPTION_SIGN_ONLY_PREFIX,
186+
attributePath.append(signOnlyAttributes.toArray()[0])))
187+
.build();
188+
}
189+
Set<String> doNothingAttributes = getDoNothingAttributes(subTableSchema);
190+
if (doNothingAttributes.size() > 0) {
191+
throw DynamoDbEncryptionException.builder()
192+
.message(String.format(
193+
"Detected DynamoDbEncryption Tag %s on a nested attribute with Path %s. " +
194+
"This is NOT Supported at this time!",
195+
CUSTOM_DDB_ENCRYPTION_DO_NOTHING_PREFIX,
196+
attributePath.append(doNothingAttributes.toArray()[0])))
197+
.build();
198+
}
199+
List<String> subAttributeNames = subTableSchema.attributeNames();
200+
for (String subAttributeName : subAttributeNames) {
201+
scanForIgnoredEncryptionTags(subTableSchema, subAttributeName, attributePath);
165202
}
166203
}
167204
}

DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedTableEncryptionConfig.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
public class DynamoDbEnhancedTableEncryptionConfig {
1616
private final String logicalTableName;
17-
private final TableSchema schemaOnEncrypt;
17+
private final TableSchema<?> schemaOnEncrypt;
1818
private final List<String> allowedUnsignedAttributes;
1919
private final String allowedUnsignedAttributePrefix;
2020
private final Keyring keyring;
@@ -39,7 +39,7 @@ protected DynamoDbEnhancedTableEncryptionConfig(BuilderImpl builder) {
3939

4040
public String logicalTableName() { return this.logicalTableName; }
4141

42-
public TableSchema schemaOnEncrypt() {
42+
public TableSchema<?> schemaOnEncrypt() {
4343
return this.schemaOnEncrypt;
4444
}
4545

@@ -82,8 +82,8 @@ public static Builder builder() {
8282
public interface Builder {
8383
String logicalTableName();
8484
Builder logicalTableName(String logicalTableName);
85-
Builder schemaOnEncrypt(TableSchema schemaOnEncrypt);
86-
TableSchema schemaOnEncrypt();
85+
Builder schemaOnEncrypt(TableSchema<?> schemaOnEncrypt);
86+
TableSchema<?> schemaOnEncrypt();
8787
Builder allowedUnsignedAttributes(List<String> allowedUnsignedAttributes);
8888
List<String> allowedUnsignedAttributes();
8989
Builder allowedUnsignedAttributePrefix(String allowedUnsignedAttributePrefix);
@@ -101,7 +101,7 @@ public interface Builder {
101101

102102
protected static class BuilderImpl implements Builder {
103103
protected String logicalTableName;
104-
protected TableSchema schemaOnEncrypt;
104+
protected TableSchema<?> schemaOnEncrypt;
105105
protected List<String> allowedUnsignedAttributes;
106106
protected String allowedUnsignedAttributePrefix;
107107
protected Keyring keyring;
@@ -131,12 +131,12 @@ public Builder logicalTableName(String logicalTableName) {
131131

132132
public String logicalTableName() { return this.logicalTableName; }
133133

134-
public Builder schemaOnEncrypt(TableSchema schemaOnEncrypt) {
134+
public Builder schemaOnEncrypt(TableSchema<?> schemaOnEncrypt) {
135135
this.schemaOnEncrypt = schemaOnEncrypt;
136136
return this;
137137
}
138138

139-
public TableSchema schemaOnEncrypt() {
139+
public TableSchema<?> schemaOnEncrypt() {
140140
return this.schemaOnEncrypt;
141141
}
142142

DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEncryptionEnhancedClientIntegrationTests.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@
1919
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
2020
import software.amazon.awssdk.services.kms.model.KmsException;
2121

22-
import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.AnnotatedConvertedBy.ConvertedByNestedBean;
23-
import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.AnnotatedFlattenedBean.FlattenedNestedBean;
24-
import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.DynamoDbEncryptionException;
22+
import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.validdatamodels.*;
2523
import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.LegacyOverride;
2624
import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.LegacyPolicy;
2725
import software.amazon.cryptography.dbencryptionsdk.structuredencryption.model.CryptoAction;
@@ -127,7 +125,7 @@ public void TestPutAndGetAnnotatedFlattenedBean() {
127125
AnnotatedFlattenedBean record = new AnnotatedFlattenedBean();
128126
record.setPartitionKey(PARTITION);
129127
record.setSortKey(SORT);
130-
FlattenedNestedBean nestedBean = new FlattenedNestedBean(
128+
AnnotatedFlattenedBean.FlattenedNestedBean nestedBean = new AnnotatedFlattenedBean.FlattenedNestedBean(
131129
"9305B367-C477-4A58-9E6C-BF7D59D17C8A", "James", "Bond"
132130
);
133131
record.setNestedBeanClass(nestedBean);
@@ -163,7 +161,7 @@ public void TestPutAndGetAnnotatedConvertedBy() {
163161
AnnotatedConvertedBy record = new AnnotatedConvertedBy();
164162
record.setPartitionKey(PARTITION);
165163
record.setSortKey(SORT);
166-
ConvertedByNestedBean nestedBean = new ConvertedByNestedBean(
164+
AnnotatedConvertedBy.ConvertedByNestedBean nestedBean = new AnnotatedConvertedBy.ConvertedByNestedBean(
167165
"9305B367-C477-4A58-9E6C-BF7D59D17C8A", "Winnie", "the-Pooh"
168166
);
169167
record.setNestedEncrypted(nestedBean);

0 commit comments

Comments
 (0)