Skip to content

Commit 7fc4497

Browse files
authored
feat: support update expressions (#69)
* feat: support update expressions
1 parent 4d86007 commit 7fc4497

File tree

5 files changed

+120
-20
lines changed

5 files changed

+120
-20
lines changed

DynamoDbEncryption/runtimes/java/src/test/java/software/aws/cryptography/dynamoDbEncryption/DynamoDbEncryptionInterceptorTest.java

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,11 @@ public void TestPutItemGetItemWithConditionExpression() {
121121
}
122122

123123
@Test
124-
public void TestUpdateItemOnEncryptedTable() {
124+
public void TestUpdateItemOnEncryptedTableGood() {
125125
UpdateItemRequest oldRequest = UpdateItemRequest.builder()
126126
.tableName(TEST_TABLE_NAME)
127-
.updateExpression("foo")
127+
.key(Collections.EMPTY_MAP)
128+
.updateExpression("SET " + TEST_ATTR2_NAME + " = :p")
128129
.build();
129130

130131
Context.ModifyRequest context = InterceptorContext.builder()
@@ -135,10 +136,30 @@ public void TestUpdateItemOnEncryptedTable() {
135136
.put(SdkExecutionAttribute.SERVICE_NAME, "DynamoDb")
136137
.build();
137138

138-
Exception exception = assertThrows(DynamoDbEncryptionException.class, () -> {
139-
interceptor.modifyRequest(context, attributes);
140-
});
141-
assertTrue(exception.getMessage().contains("Update Expressions forbidden on encrypted tables"));
139+
SdkRequest newRequest = interceptor.modifyRequest(context, attributes);
140+
assertEquals(oldRequest, newRequest);
141+
}
142+
143+
@Test
144+
public void TestUpdateItemOnEncryptedTableBad() throws Exception {
145+
UpdateItemRequest oldRequest = UpdateItemRequest.builder()
146+
.tableName(TEST_TABLE_NAME)
147+
.key(Collections.EMPTY_MAP)
148+
.updateExpression("SET " + TEST_ATTR_NAME + " = :p")
149+
.build();
150+
151+
Context.ModifyRequest context = InterceptorContext.builder()
152+
.request(oldRequest)
153+
.build();
154+
ExecutionAttributes attributes = ExecutionAttributes.builder()
155+
.put(SdkExecutionAttribute.OPERATION_NAME, "UpdateItem")
156+
.put(SdkExecutionAttribute.SERVICE_NAME, "DynamoDb")
157+
.build();
158+
159+
Exception exception = assertThrows(DynamoDbEncryptionException.class, () -> {
160+
interceptor.modifyRequest(context, attributes);
161+
});
162+
assertTrue(exception.getMessage().contains("Update Expressions forbidden on signed attributes : " + TEST_ATTR_NAME));
142163
}
143164

144165
@Test
@@ -242,13 +263,39 @@ public void TestTransactWriteItemsWithDeleteConditionExpression() {
242263
}
243264

244265
@Test
245-
public void TestTransactWriteItemsWithUpdateOnEncryptedTable() {
266+
public void TestTransactWriteItemsWithUpdateOnEncryptedTableGood() {
267+
TransactWriteItemsRequest oldRequest = TransactWriteItemsRequest.builder()
268+
.transactItems(
269+
TransactWriteItem.builder()
270+
.update(Update.builder()
271+
.tableName(TEST_TABLE_NAME)
272+
.key(Collections.EMPTY_MAP)
273+
.updateExpression("SET " + TEST_ATTR2_NAME + " = :p")
274+
.build())
275+
.build())
276+
.build();
277+
278+
Context.ModifyRequest context = InterceptorContext.builder()
279+
.request(oldRequest)
280+
.build();
281+
ExecutionAttributes attributes = ExecutionAttributes.builder()
282+
.put(SdkExecutionAttribute.OPERATION_NAME, "TransactWriteItems")
283+
.put(SdkExecutionAttribute.SERVICE_NAME, "DynamoDb")
284+
.build();
285+
286+
SdkRequest newRequest = interceptor.modifyRequest(context, attributes);
287+
assertEquals(oldRequest, newRequest);
288+
}
289+
290+
@Test
291+
public void TestTransactWriteItemsWithUpdateOnEncryptedTableBad() {
246292
TransactWriteItemsRequest oldRequest = TransactWriteItemsRequest.builder()
247293
.transactItems(
248294
TransactWriteItem.builder()
249295
.update(Update.builder()
250296
.tableName(TEST_TABLE_NAME)
251-
.updateExpression("foo")
297+
.key(Collections.EMPTY_MAP)
298+
.updateExpression("SET " + TEST_ATTR_NAME + " = :p")
252299
.build())
253300
.build())
254301
.build();
@@ -264,7 +311,7 @@ public void TestTransactWriteItemsWithUpdateOnEncryptedTable() {
264311
Exception exception = assertThrows(DynamoDbEncryptionException.class, () -> {
265312
interceptor.modifyRequest(context, attributes);
266313
});
267-
assertTrue(exception.getMessage().contains("Update Expressions forbidden on encrypted tables"));
314+
assertTrue(exception.getMessage().contains("Update Expressions forbidden on signed attributes : " + TEST_ATTR_NAME));
268315
}
269316

270317
@Test

DynamoDbEncryptionMiddlewareInternal/test/TestFixtures.dfy

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,13 @@ module TestFixtures {
9494
"foo" := DynamoDbTableEncryptionConfig(
9595
partitionKeyName := "bar",
9696
sortKeyName := None(),
97-
attributeActions := map["bar" := CSE.SIGN_ONLY],
98-
allowedUnauthenticatedAttributes := None(),
97+
attributeActions := map[
98+
"bar" := CSE.SIGN_ONLY,
99+
"sign" := CSE.SIGN_ONLY,
100+
"encrypt" := CSE.ENCRYPT_AND_SIGN,
101+
"plain" := CSE.DO_NOTHING
102+
],
103+
allowedUnauthenticatedAttributes := Some(["plain"]),
99104
allowedUnauthenticatedAttributePrefix := None(),
100105
algorithmSuiteId := None(),
101106
keyring := Some(keyring),

DynamoDbEncryptionMiddlewareInternal/test/UpdateItemTransform.dfy

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ module UpdateItemTransformTest {
3636
expect_equal("UpdateItemInput", transformed.value.transformedInput, input);
3737
}
3838

39-
method {:test} TestUpdateItemInputUpdateExpression() {
39+
method {:test} TestUpdateItemInputUpdateExpressionSigned() {
4040
var middlewareUnderTest := TestFixtures.GetDynamoDbEncryption();
4141
var input := DDB.UpdateItemInput(
4242
TableName := "foo",
@@ -47,7 +47,7 @@ module UpdateItemTransformTest {
4747
ReturnValues := None(),
4848
ReturnConsumedCapacity := None(),
4949
ReturnItemCollectionMetrics := None(),
50-
UpdateExpression := Some("foo"),
50+
UpdateExpression := Some("SET sign = :p"),
5151
ConditionExpression := None(),
5252
ExpressionAttributeNames := None(),
5353
ExpressionAttributeValues := None()
@@ -58,7 +58,60 @@ module UpdateItemTransformTest {
5858
)
5959
);
6060

61-
ExpectFailure(transformed, "Update Expressions forbidden on encrypted tables");
61+
ExpectFailure(transformed, "Update Expressions forbidden on signed attributes : sign");
62+
}
63+
64+
65+
method {:test} TestUpdateItemInputUpdateExpressionEncrypted() {
66+
var middlewareUnderTest := TestFixtures.GetDynamoDbEncryption();
67+
var input := DDB.UpdateItemInput(
68+
TableName := "foo",
69+
Key := map[],
70+
AttributeUpdates := None(),
71+
Expected := None(),
72+
ConditionalOperator := None(),
73+
ReturnValues := None(),
74+
ReturnConsumedCapacity := None(),
75+
ReturnItemCollectionMetrics := None(),
76+
UpdateExpression := Some("SET encrypt = :p"),
77+
ConditionExpression := None(),
78+
ExpressionAttributeNames := None(),
79+
ExpressionAttributeValues := None()
80+
);
81+
var transformed := middlewareUnderTest.UpdateItemInputTransform(
82+
AwsCryptographyDynamoDbEncryptionTypes.UpdateItemInputTransformInput(
83+
sdkInput := input
84+
)
85+
);
86+
87+
ExpectFailure(transformed, "Update Expressions forbidden on signed attributes : encrypt");
88+
}
89+
90+
91+
method {:test} TestUpdateItemInputUpdateExpressionPlain() {
92+
var middlewareUnderTest := TestFixtures.GetDynamoDbEncryption();
93+
var input := DDB.UpdateItemInput(
94+
TableName := "foo",
95+
Key := map[],
96+
AttributeUpdates := None(),
97+
Expected := None(),
98+
ConditionalOperator := None(),
99+
ReturnValues := None(),
100+
ReturnConsumedCapacity := None(),
101+
ReturnItemCollectionMetrics := None(),
102+
UpdateExpression := Some("SET plain = :p"),
103+
ConditionExpression := None(),
104+
ExpressionAttributeNames := None(),
105+
ExpressionAttributeValues := None()
106+
);
107+
var transformed := middlewareUnderTest.UpdateItemInputTransform(
108+
AwsCryptographyDynamoDbEncryptionTypes.UpdateItemInputTransformInput(
109+
sdkInput := input
110+
)
111+
);
112+
113+
expect_ok("UpdateItemInput", transformed);
114+
expect_equal("UpdateItemInput", transformed.value.transformedInput, input);
62115
}
63116

64117
method {:test} TestUpdateItemOutputPassthrough() {

DynamoDbItemEncryptor/src/DDBSupport.dfy

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,18 +90,12 @@ module DynamoDBSupport {
9090
: Result<bool, string>
9191
{
9292
if expr.Some? then
93-
// TODO
94-
// removing this comment will provide correct behavior
95-
// but requires changing many tests
96-
/*
9793
var attrs := Update.ExtractAttributes(expr.value, attrNames);
9894
var encryptedAttrs := Seq.Filter(s => IsSigned(config, s), attrs);
9995
if |encryptedAttrs| == 0 then
10096
Success(true)
10197
else
10298
Failure("Update Expressions forbidden on signed attributes : " + Join(encryptedAttrs, ","))
103-
*/
104-
Failure("Update Expressions forbidden on encrypted tables")
10599
else
106100
Success(true)
107101
}

DynamoDbItemEncryptor/src/Index.dfy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ module
139139
config.allowedUnauthenticatedAttributePrefix
140140
))
141141
{
142+
// TODO - make an error message that a customer might understand
142143
return Failure(DynamoDbItemEncryptorException(
143144
message := "Attribute: " + attribute + " configuration not compatible with unauthenticated configuration."
144145
));

0 commit comments

Comments
 (0)