diff --git a/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/beacon_styles_searchable_encryption.rs b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/beacon_styles_searchable_encryption.rs index a287c9ecbc..247f7309d9 100644 --- a/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/beacon_styles_searchable_encryption.rs +++ b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/beacon_styles_searchable_encryption.rs @@ -201,9 +201,13 @@ pub async fn put_and_query_with_beacon(branch_key_id: &str) -> Result<(), crate: .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)])) .build()?; + // Generate unique work_id values for this run to avoid collisions with stale items + let work_id1 = uuid::Uuid::new_v4().to_string(); + let work_id2 = uuid::Uuid::new_v4().to_string(); + // 8. Create item one, specifically with "dessert != fruit", and "fruit in basket". let item1 = HashMap::from([ - ("work_id".to_string(), AttributeValue::S("1".to_string())), + ("work_id".to_string(), AttributeValue::S(work_id1.clone())), ( "inspection_date".to_string(), AttributeValue::S("2023-06-13".to_string()), @@ -234,7 +238,7 @@ pub async fn put_and_query_with_beacon(branch_key_id: &str) -> Result<(), crate: // 9. Create item two, specifically with "dessert == fruit", and "fruit not in basket". let item2 = HashMap::from([ - ("work_id".to_string(), AttributeValue::S("2".to_string())), + ("work_id".to_string(), AttributeValue::S(work_id2.clone())), ( "inspection_date".to_string(), AttributeValue::S("2023-06-13".to_string()), @@ -285,43 +289,78 @@ pub async fn put_and_query_with_beacon(branch_key_id: &str) -> Result<(), crate: .send() .await?; + // Run all scan tests, then clean up items regardless of success or failure + let result = run_scan_tests(&ddb, ddb_table_name, &item1, &item2, &work_id1, &work_id2).await; + + // Clean up: delete both items + let plain_ddb = aws_sdk_dynamodb::Client::new(&sdk_config); + let _ = plain_ddb + .delete_item() + .table_name(ddb_table_name) + .key("work_id", AttributeValue::S(work_id1)) + .key("inspection_date", AttributeValue::S("2023-06-13".to_string())) + .send() + .await; + let _ = plain_ddb + .delete_item() + .table_name(ddb_table_name) + .key("work_id", AttributeValue::S(work_id2)) + .key("inspection_date", AttributeValue::S("2023-06-13".to_string())) + .send() + .await; + + result?; + println!("beacon_styles_searchable_encryption successful."); + Ok(()) +} + +async fn run_scan_tests( + ddb: &aws_sdk_dynamodb::Client, + ddb_table_name: &str, + item1: &HashMap, + item2: &HashMap, + work_id1: &str, + work_id2: &str, +) -> Result<(), crate::BoxError> { + // These filters ensure scans only match items created by this test run. + let wid_filter = "work_id IN (:wid1, :wid2)"; + let wid_values = HashMap::from([ + (":wid1".to_string(), AttributeValue::S(work_id1.to_string())), + (":wid2".to_string(), AttributeValue::S(work_id2.to_string())), + ]); + // 12. Test the first type of Set operation : // Select records where the basket attribute holds a particular value - let expression_attribute_values = HashMap::from([( - ":value".to_string(), - AttributeValue::S("banana".to_string()), - )]); + let mut expr_vals = wid_values.clone(); + expr_vals.insert(":value".to_string(), AttributeValue::S("banana".to_string())); let scan_response = ddb .scan() .table_name(ddb_table_name) - .filter_expression("contains(basket, :value)") - .set_expression_attribute_values(Some(expression_attribute_values.clone())) + .filter_expression(format!("{wid_filter} AND contains(basket, :value)")) + .set_expression_attribute_values(Some(expr_vals)) .send() .await?; let attribute_values = scan_response.items.unwrap(); // Validate only 1 item was returned: item1 assert_eq!(attribute_values.len(), 1); - let returned_item = &attribute_values[0]; - // Validate the item has the expected attributes - assert_eq!(returned_item["work_id"], item1["work_id"]); + assert_eq!(attribute_values[0]["work_id"], item1["work_id"]); // 13. Test the second type of Set operation : // Select records where the basket attribute holds the fruit attribute let scan_response = ddb .scan() .table_name(ddb_table_name) - .filter_expression("contains(basket, fruit)") + .filter_expression(format!("{wid_filter} AND contains(basket, fruit)")) + .set_expression_attribute_values(Some(wid_values.clone())) .send() .await?; let attribute_values = scan_response.items.unwrap(); // Validate only 1 item was returned: item1 assert_eq!(attribute_values.len(), 1); - let returned_item = &attribute_values[0]; - // Validate the item has the expected attributes - assert_eq!(returned_item["work_id"], item1["work_id"]); + assert_eq!(attribute_values[0]["work_id"], item1["work_id"]); // 14. Test the third type of Set operation : // Select records where the fruit attribute exists in a particular set @@ -330,80 +369,73 @@ pub async fn put_and_query_with_beacon(branch_key_id: &str) -> Result<(), crate: "orange".to_string(), "grape".to_string(), ]; - let expression_attribute_values = - HashMap::from([(":value".to_string(), AttributeValue::Ss(basket3))]); + let mut expr_vals = wid_values.clone(); + expr_vals.insert(":value".to_string(), AttributeValue::Ss(basket3)); let scan_response = ddb .scan() .table_name(ddb_table_name) - .filter_expression("contains(:value, fruit)") - .set_expression_attribute_values(Some(expression_attribute_values.clone())) + .filter_expression(format!("{wid_filter} AND contains(:value, fruit)")) + .set_expression_attribute_values(Some(expr_vals)) .send() .await?; let attribute_values = scan_response.items.unwrap(); - // Validate only 1 item was returned: item1 + // Validate only 1 item was returned: item2 assert_eq!(attribute_values.len(), 1); - let returned_item = &attribute_values[0]; - // Validate the item has the expected attributes - assert_eq!(returned_item["work_id"], item2["work_id"]); + assert_eq!(attribute_values[0]["work_id"], item2["work_id"]); // 15. Test a Shared search. Select records where the dessert attribute matches the fruit attribute let scan_response = ddb .scan() .table_name(ddb_table_name) - .filter_expression("dessert = fruit") + .filter_expression(format!("{wid_filter} AND dessert = fruit")) + .set_expression_attribute_values(Some(wid_values.clone())) .send() .await?; let attribute_values = scan_response.items.unwrap(); - // Validate only 1 item was returned: item1 + // Validate only 1 item was returned: item2 assert_eq!(attribute_values.len(), 1); - let returned_item = &attribute_values[0]; - // Validate the item has the expected attributes - assert_eq!(returned_item["work_id"], item2["work_id"]); + assert_eq!(attribute_values[0]["work_id"], item2["work_id"]); - // 15. Test the AsSet attribute 'veggies' : + // 16. Test the AsSet attribute 'veggies' : // Select records where the veggies attribute holds a particular value - let expression_attribute_values = - HashMap::from([(":value".to_string(), AttributeValue::S("peas".to_string()))]); + let mut expr_vals = wid_values.clone(); + expr_vals.insert(":value".to_string(), AttributeValue::S("peas".to_string())); let scan_response = ddb .scan() .table_name(ddb_table_name) - .filter_expression("contains(veggies, :value)") - .set_expression_attribute_values(Some(expression_attribute_values.clone())) + .filter_expression(format!("{wid_filter} AND contains(veggies, :value)")) + .set_expression_attribute_values(Some(expr_vals)) .send() .await?; let attribute_values = scan_response.items.unwrap(); - // Validate only 1 item was returned: item1 + // Validate only 1 item was returned: item2 assert_eq!(attribute_values.len(), 1); - let returned_item = &attribute_values[0]; - // Validate the item has the expected attributes - assert_eq!(returned_item["work_id"], item2["work_id"]); + assert_eq!(attribute_values[0]["work_id"], item2["work_id"]); - // 16. Test the compound beacon 'work_unit' : - let expression_attribute_values = HashMap::from([( + // 17. Test the compound beacon 'work_unit' : + let mut expr_vals = wid_values.clone(); + expr_vals.insert( ":value".to_string(), - AttributeValue::S("I-1.T-small".to_string()), - )]); + AttributeValue::S(format!("I-{}.T-small", item1["work_id"].as_s().unwrap())), + ); let scan_response = ddb .scan() .table_name(ddb_table_name) - .filter_expression("work_unit = :value") - .set_expression_attribute_values(Some(expression_attribute_values.clone())) + .filter_expression(format!("{wid_filter} AND work_unit = :value")) + .set_expression_attribute_values(Some(expr_vals)) .send() .await?; let attribute_values = scan_response.items.unwrap(); // Validate only 1 item was returned: item1 assert_eq!(attribute_values.len(), 1); - let returned_item = &attribute_values[0]; - // Validate the item has the expected attributes - assert_eq!(returned_item["work_id"], item1["work_id"]); + assert_eq!(attribute_values[0]["work_id"], item1["work_id"]); - println!("beacon_styles_searchable_encryption successful."); Ok(()) } diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/searchableencryption/BeaconStylesSearchableEncryptionExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/searchableencryption/BeaconStylesSearchableEncryptionExample.java index 65714e0eba..c6a39493e2 100644 --- a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/searchableencryption/BeaconStylesSearchableEncryptionExample.java +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/searchableencryption/BeaconStylesSearchableEncryptionExample.java @@ -4,10 +4,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import javax.swing.text.html.Option; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest; import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; import software.amazon.awssdk.services.dynamodb.model.PutItemResponse; import software.amazon.awssdk.services.dynamodb.model.ScanRequest; @@ -274,9 +276,13 @@ public static void PutItemQueryItemWithBeaconStyles( .tableEncryptionConfigs(tableConfigs) .build(); + // Generate unique work_id values for this run to avoid collisions with stale items + final String workId1 = UUID.randomUUID().toString(); + final String workId2 = UUID.randomUUID().toString(); + // 8. Create item one, specifically with "dessert != fruit", and "fruit in basket". final HashMap item1 = new HashMap<>(); - item1.put("work_id", AttributeValue.builder().s("1").build()); + item1.put("work_id", AttributeValue.builder().s(workId1).build()); item1.put( "inspection_date", AttributeValue.builder().s("2023-06-13").build() @@ -297,7 +303,7 @@ public static void PutItemQueryItemWithBeaconStyles( // 9. Create item two, specifically with "dessert == fruit", and "fruit not in basket". final HashMap item2 = new HashMap<>(); - item2.put("work_id", AttributeValue.builder().s("2").build()); + item2.put("work_id", AttributeValue.builder().s(workId2).build()); item2.put( "inspection_date", AttributeValue.builder().s("2023-06-13").build() @@ -349,135 +355,151 @@ public static void PutItemQueryItemWithBeaconStyles( // Validate object put successfully assert 200 == putResponse.sdkHttpResponse().statusCode(); - // 13. Test the first type of Set operation : - // Select records where the basket attribute holds a particular value - Map expressionAttributeValues = new HashMap<>(); - expressionAttributeValues.put( - ":value", - AttributeValue.builder().s("banana").build() - ); - - ScanRequest scanRequest = ScanRequest - .builder() - .tableName(ddbTableName) - .filterExpression("contains(basket, :value)") - .expressionAttributeValues(expressionAttributeValues) - .build(); - - ScanResponse scanResponse = ddb.scan(scanRequest); - // Validate query was returned successfully - assert 200 == scanResponse.sdkHttpResponse().statusCode(); - - // Validate only 1 item was returned: item1 - assert scanResponse.items().size() == 1; - assert scanResponse.items().get(0).equals(item1); - - // 14. Test the second type of Set operation : - // Select records where the basket attribute holds the fruit attribute - scanRequest = - ScanRequest - .builder() - .tableName(ddbTableName) - .filterExpression("contains(basket, fruit)") - .build(); - - scanResponse = ddb.scan(scanRequest); - // Validate query was returned successfully - assert 200 == scanResponse.sdkHttpResponse().statusCode(); - - // Validate only 1 item was returned: item1 - assert scanResponse.items().size() == 1; - assert scanResponse.items().get(0).equals(item1); - - // 15. Test the third type of Set operation : - // Select records where the fruit attribute exists in a particular set - ArrayList basket3 = new ArrayList(); - basket3.add("boysenberry"); - basket3.add("grape"); - basket3.add("orange"); - expressionAttributeValues.put( - ":value", - AttributeValue.builder().ss(basket3).build() - ); - - scanRequest = - ScanRequest - .builder() - .tableName(ddbTableName) - .filterExpression("contains(:value, fruit)") - .expressionAttributeValues(expressionAttributeValues) - .build(); - - scanResponse = ddb.scan(scanRequest); - - // Validate query was returned successfully - assert 200 == scanResponse.sdkHttpResponse().statusCode(); - - // Validate only 1 item was returned: item2 - assert scanResponse.items().size() == 1; - assert scanResponse.items().get(0).equals(item2); - - // Test a Shared search. Select records where the dessert attribute matches the fruit attribute - scanRequest = - ScanRequest - .builder() - .tableName(ddbTableName) - .filterExpression("dessert = fruit") - .build(); - - scanResponse = ddb.scan(scanRequest); - - // Validate query was returned successfully - assert 200 == scanResponse.sdkHttpResponse().statusCode(); - - // Validate only 1 item was returned: item2 - assert scanResponse.items().size() == 1; - assert scanResponse.items().get(0).equals(item2); - - // 16. Test the AsSet attribute 'veggies' : - // Select records where the veggies attribute holds a particular value - expressionAttributeValues.put( - ":value", - AttributeValue.builder().s("peas").build() - ); + // These filters ensure scans only match items created by this test run. + final String widFilter = "work_id IN (:wid1, :wid2)"; + final Map widValues = new HashMap<>(); + widValues.put(":wid1", AttributeValue.builder().s(workId1).build()); + widValues.put(":wid2", AttributeValue.builder().s(workId2).build()); + + try { + // 13. Test the first type of Set operation : + // Select records where the basket attribute holds a particular value + Map expressionAttributeValues = new HashMap<>( + widValues + ); + expressionAttributeValues.put( + ":value", + AttributeValue.builder().s("banana").build() + ); - scanRequest = - ScanRequest + ScanRequest scanRequest = ScanRequest .builder() .tableName(ddbTableName) - .filterExpression("contains(veggies, :value)") + .filterExpression(widFilter + " AND contains(basket, :value)") .expressionAttributeValues(expressionAttributeValues) .build(); - scanResponse = ddb.scan(scanRequest); - // Validate query was returned successfully - assert 200 == scanResponse.sdkHttpResponse().statusCode(); + ScanResponse scanResponse = ddb.scan(scanRequest); + assert 200 == scanResponse.sdkHttpResponse().statusCode(); + assert scanResponse.items().size() == 1; + assert scanResponse.items().get(0).equals(item1); - // Validate only 1 item was returned: item1 - assert scanResponse.items().size() == 1; - assert scanResponse.items().get(0).equals(item2); - - // 17. Test the compound beacon 'work_unit' : - expressionAttributeValues.put( - ":value", - AttributeValue.builder().s("I-1.T-small").build() - ); + // 14. Test the second type of Set operation : + // Select records where the basket attribute holds the fruit attribute + scanRequest = + ScanRequest + .builder() + .tableName(ddbTableName) + .filterExpression(widFilter + " AND contains(basket, fruit)") + .expressionAttributeValues(widValues) + .build(); + + scanResponse = ddb.scan(scanRequest); + assert 200 == scanResponse.sdkHttpResponse().statusCode(); + assert scanResponse.items().size() == 1; + assert scanResponse.items().get(0).equals(item1); + + // 15. Test the third type of Set operation : + // Select records where the fruit attribute exists in a particular set + ArrayList basket3 = new ArrayList(); + basket3.add("boysenberry"); + basket3.add("grape"); + basket3.add("orange"); + expressionAttributeValues = new HashMap<>(widValues); + expressionAttributeValues.put( + ":value", + AttributeValue.builder().ss(basket3).build() + ); - scanRequest = - ScanRequest - .builder() - .tableName(ddbTableName) - .filterExpression("work_unit = :value") - .expressionAttributeValues(expressionAttributeValues) - .build(); + scanRequest = + ScanRequest + .builder() + .tableName(ddbTableName) + .filterExpression(widFilter + " AND contains(:value, fruit)") + .expressionAttributeValues(expressionAttributeValues) + .build(); + + scanResponse = ddb.scan(scanRequest); + assert 200 == scanResponse.sdkHttpResponse().statusCode(); + assert scanResponse.items().size() == 1; + assert scanResponse.items().get(0).equals(item2); + + // Test a Shared search. Select records where the dessert attribute matches the fruit attribute + scanRequest = + ScanRequest + .builder() + .tableName(ddbTableName) + .filterExpression(widFilter + " AND dessert = fruit") + .expressionAttributeValues(widValues) + .build(); + + scanResponse = ddb.scan(scanRequest); + assert 200 == scanResponse.sdkHttpResponse().statusCode(); + assert scanResponse.items().size() == 1; + assert scanResponse.items().get(0).equals(item2); + + // 16. Test the AsSet attribute 'veggies' : + // Select records where the veggies attribute holds a particular value + expressionAttributeValues = new HashMap<>(widValues); + expressionAttributeValues.put( + ":value", + AttributeValue.builder().s("peas").build() + ); - scanResponse = ddb.scan(scanRequest); - // Validate query was returned successfully - assert 200 == scanResponse.sdkHttpResponse().statusCode(); + scanRequest = + ScanRequest + .builder() + .tableName(ddbTableName) + .filterExpression(widFilter + " AND contains(veggies, :value)") + .expressionAttributeValues(expressionAttributeValues) + .build(); + + scanResponse = ddb.scan(scanRequest); + assert 200 == scanResponse.sdkHttpResponse().statusCode(); + assert scanResponse.items().size() == 1; + assert scanResponse.items().get(0).equals(item2); + + // 17. Test the compound beacon 'work_unit' : + expressionAttributeValues = new HashMap<>(widValues); + expressionAttributeValues.put( + ":value", + AttributeValue.builder().s("I-" + workId1 + ".T-small").build() + ); - // Validate only 1 item was returned: item1 - assert scanResponse.items().size() == 1; - assert scanResponse.items().get(0).equals(item1); + scanRequest = + ScanRequest + .builder() + .tableName(ddbTableName) + .filterExpression(widFilter + " AND work_unit = :value") + .expressionAttributeValues(expressionAttributeValues) + .build(); + + scanResponse = ddb.scan(scanRequest); + assert 200 == scanResponse.sdkHttpResponse().statusCode(); + assert scanResponse.items().size() == 1; + assert scanResponse.items().get(0).equals(item1); + } finally { + // Clean up: delete both items using a plain DDB client + final DynamoDbClient plainDdb = DynamoDbClient.create(); + HashMap key1 = new HashMap<>(); + key1.put("work_id", AttributeValue.builder().s(workId1).build()); + key1.put( + "inspection_date", + AttributeValue.builder().s("2023-06-13").build() + ); + plainDdb.deleteItem( + DeleteItemRequest.builder().tableName(ddbTableName).key(key1).build() + ); + HashMap key2 = new HashMap<>(); + key2.put("work_id", AttributeValue.builder().s(workId2).build()); + key2.put( + "inspection_date", + AttributeValue.builder().s("2023-06-13").build() + ); + plainDdb.deleteItem( + DeleteItemRequest.builder().tableName(ddbTableName).key(key2).build() + ); + } } public static void main(final String[] args) {