Skip to content

Commit c85fcc0

Browse files
author
Lucas McDonald
committed
sync
1 parent 2d3b6ab commit c85fcc0

File tree

3 files changed

+52
-57
lines changed

3 files changed

+52
-57
lines changed

DynamoDbEncryption/runtimes/python/src/aws_dbesdk_dynamodb/encrypted/table.py

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,21 @@ class EncryptedTable(EncryptedBotoInterface):
3737
drop-in replacement that transparently handles encryption and decryption of items.
3838
3939
The API matches the standard boto3 DynamoDB table interface:
40+
4041
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/table.html
4142
4243
This class will encrypt/decrypt items for the following operations:
43-
* put_item
44-
* get_item
45-
* query
46-
* scan
44+
* ``put_item``
45+
* ``get_item``
46+
* ``query``
47+
* ``scan``
48+
49+
The ``update_item`` operation is not currently supported. Calling this operation will raise ``NotImplementedError``.
4750
48-
Calling batch_writer() will return a BatchWriter that transparently encrypts batch write requests.
51+
Calling ``batch_writer()`` will return a ``BatchWriter`` that transparently encrypts batch write requests.
4952
5053
Any other operations on this class will defer to the underlying boto3 DynamoDB Table's implementation
5154
and will not be encrypted/decrypted.
52-
53-
Note: The update_item operation is not currently supported. Calling this operation will raise NotImplementedError.
5455
"""
5556

5657
def __init__(
@@ -60,7 +61,7 @@ def __init__(
6061
encryption_config: DynamoDbTablesEncryptionConfig,
6162
):
6263
"""
63-
Create an EncryptedTable object.
64+
Create an ``EncryptedTable`` object.
6465
6566
Args:
6667
table (ServiceResource): Initialized boto3 DynamoDB table
@@ -79,15 +80,16 @@ def put_item(self, **kwargs) -> dict[str, Any]:
7980
"""
8081
Put a single item to the table. Encrypts the item before writing to DynamoDB.
8182
82-
The parameters and return value match the boto3 DynamoDB table put_item API:
83+
The input and output syntaxes match those for the boto3 DynamoDB table ``put_item`` API:
84+
8385
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/table/put_item.html
8486
8587
Args:
86-
**kwargs: Keyword arguments to pass to the operation. These match the boto3 put_item API parameters.
87-
The "Item" field will be encrypted locally before being written to DynamoDB.
88+
**kwargs: Keyword arguments to pass to the operation. This matches the boto3 Table ``put_item`` request
89+
syntax. The value in ``"Item"`` will be encrypted locally before being written to DynamoDB.
8890
8991
Returns:
90-
dict: The response from DynamoDB. This matches the boto3 put_item API response.
92+
dict: The response from DynamoDB. This matches the boto3 ``put_item`` response syntax.
9193
9294
"""
9395
return self._table_operation_logic(
@@ -107,15 +109,17 @@ def get_item(self, **kwargs) -> dict[str, Any]:
107109
"""
108110
Get a single item from the table. Decrypts the item after reading from DynamoDB.
109111
110-
The parameters and return value match the boto3 DynamoDB table get_item API:
112+
The input and output syntaxes match those for the boto3 DynamoDB table ``get_item`` API:
113+
111114
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/table/get_item.html
112115
113116
Args:
114-
**kwargs: Keyword arguments to pass to the operation. These match the boto3 get_item API parameters.
117+
**kwargs: Keyword arguments to pass to the operation. This matches the boto3 Table ``get_item`` request
118+
syntax.
115119
116120
Returns:
117-
dict: The response from DynamoDB. This matches the boto3 get_item API response.
118-
The "Item" field will be decrypted locally after being read from DynamoDB.
121+
dict: The response from DynamoDB. This matches the boto3 Table ``get_item`` response syntax.
122+
The value in ``"Item"`` will be decrypted locally after being read from DynamoDB.
119123
120124
"""
121125
return self._table_operation_logic(
@@ -135,15 +139,17 @@ def query(self, **kwargs) -> dict[str, Any]:
135139
"""
136140
Query items from the table or index. Decrypts any returned items.
137141
138-
The parameters and return value match the boto3 DynamoDB table query API:
142+
The input and output syntaxes match those for the boto3 DynamoDB table ``query`` API:
143+
139144
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/table/query.html
140145
141146
Args:
142-
**kwargs: Keyword arguments to pass to the operation. These match the boto3 query API parameters.
147+
**kwargs: Keyword arguments to pass to the operation. This matches the boto3 Table ``query`` request
148+
syntax.
143149
144150
Returns:
145-
dict: The response from DynamoDB. This matches the boto3 query API response.
146-
The "Items" field will be decrypted locally after being read from DynamoDB.
151+
dict: The response from DynamoDB. This matches the boto3 Table ``query`` response syntax.
152+
The value in ``"Items"`` will be decrypted locally after being read from DynamoDB.
147153
148154
"""
149155
return self._table_operation_logic(
@@ -163,15 +169,17 @@ def scan(self, **kwargs) -> dict[str, Any]:
163169
"""
164170
Scan the entire table or index. Decrypts any returned items.
165171
166-
The parameters and return value match the boto3 DynamoDB table scan API:
172+
The input and output syntaxes match those for the boto3 DynamoDB table ``scan`` API:
173+
167174
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/table/scan.html
168175
169176
Args:
170-
**kwargs: Keyword arguments to pass to the operation. These match the boto3 scan API parameters.
177+
**kwargs: Keyword arguments to pass to the operation. This matches the boto3 Table ``scan`` request
178+
syntax.
171179
172180
Returns:
173-
dict: The response from DynamoDB. This matches the boto3 scan API response.
174-
The "Items" field will be decrypted locally after being read from DynamoDB.
181+
dict: The response from DynamoDB. This matches the boto3 Table ``scan`` response syntax.
182+
The value in ``"Items"`` will be decrypted locally after being read from DynamoDB.
175183
176184
"""
177185
return self._table_operation_logic(
@@ -189,7 +197,7 @@ def scan(self, **kwargs) -> dict[str, Any]:
189197

190198
def update_item(self, **kwargs):
191199
"""
192-
Not implemented. Raises NotImplementedError.
200+
Not implemented. Raises ``NotImplementedError``.
193201
194202
Args:
195203
**kwargs: Any arguments passed to this method

DynamoDbEncryption/runtimes/python/test/integ/encrypted/test_table.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ def plaintext_table():
3535
return table
3636

3737

38+
# Creates a matrix of tests for each value in param,
39+
# with a user-friendly string for test output:
40+
# encrypted = True -> "encrypted"
41+
# encrypted = False -> "plaintext"
3842
@pytest.fixture(params=[True, False], ids=["encrypted", "plaintext"])
3943
def encrypted(request):
4044
return request.param
@@ -52,6 +56,10 @@ def table(encrypted):
5256
return plaintext_table()
5357

5458

59+
# Creates a matrix of tests for each value in param,
60+
# with a user-friendly string for test output:
61+
# use_complex_item = True -> "complex_item"
62+
# use_complex_item = False -> "simple_item"
5563
@pytest.fixture(params=[simple_item_dict, complex_item_dict], ids=["simple_item", "complex_item"])
5664
def test_item(request):
5765
return request.param
@@ -135,24 +143,13 @@ def test_GIVEN_items_in_table_WHEN_query_THEN_items_are_decrypted_correctly(tabl
135143
assert len(query_response["Items"]) == 1
136144
assert query_response["Items"][0] == put_item_request_dict["Item"]
137145

138-
# Scans work, but the test items are not found because
139-
# DDB only returns the first 1MB of data, and the test items
140-
# are not in the first 1MB sometimes. We probably need a new table.
141-
# TODO: Add a new table for these tests, enable tests.
142-
# # When: Scanning with filter that matches only our test items
143-
# scan_response = encrypted_table.scan(**scan_request_dict)
144-
# # Then: Scan returns both test items
145-
# assert scan_response["ResponseMetadata"]["HTTPStatusCode"] == 200
146-
# assert len(scan_response["Items"]) == 2
147-
# # Check each test item is found in scan results
148-
# found_items = scan_response["Items"]
149-
# assert all(any(found_item == item for found_item in found_items) for item in items)
150-
151146

152147
@pytest.fixture
153148
def scan_request(encrypted, test_item):
154149
if encrypted:
155150
request = basic_scan_request_dict(test_item)
151+
# If the encrypted scan encounters a plaintext item, the scan will fail.
152+
# To avoid this, encrypted scans add a filter expression that matches only encrypted items.
156153
request["FilterExpression"] = request["FilterExpression"] + " AND attribute_exists (#sig)"
157154
request["ExpressionAttributeNames"] = {}
158155
request["ExpressionAttributeNames"]["#sig"] = "amzn-ddb-map-sig"

TestVectors/runtimes/python/src/aws_dbesdk_dynamodb_test_vectors/internaldafny/extern/CreateInterceptedDDBTable.py

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,22 @@
1212
from smithy_dafny_standard_library.internaldafny.generated import Wrappers
1313
from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb.errors import _smithy_error_to_dafny_error
1414
from aws_dbesdk_dynamodb_test_vectors.waiting_boto3_ddb_client import WaitingLocalDynamoClient
15-
from aws_dbesdk_dynamodb.transform import (
16-
dict_to_ddb,
17-
ddb_to_dict,
18-
)
19-
from aws_dbesdk_dynamodb.internal import client_to_resource
20-
21-
from boto3.dynamodb.conditions import Key, Attr, And, Or, Not, Contains
22-
from boto3.dynamodb.types import TypeDeserializer
2315

24-
# from .....test.resource_formatted_queries import (queries, complex_queries)
16+
from boto3.dynamodb.conditions import Key, Attr
2517

2618
# When querying, DBESDK DDB TestVectors will pass the Table the query as a string.
2719
# The Table could accept this string as-is and process it correctly.
2820
# However, EncryptedTables have extra logic to process boto3 Conditions.
29-
# I want to test this extra logic as much as possible.
21+
# This extra logic should be tested as much as possible.
3022
# This map converts some known query strings to equivalent Conditions.
3123
# TestVectors will pass the query string (map key) to the Table;
3224
# the Table's internal logic will look up the query string in this map:
3325
# - Entry found: Query with replaced Condition
3426
# - Not found: Query with original string. Table accepts strings.
3527
# This map contains all query strings in the TestVectors' data.json as of commit
3628
# 4f18689f79243c9a5ab0f3a23108671defddeac4
37-
# If any query strings are added to TestVectors, they COULD be added here,
38-
# but do not need to be added.
29+
# If any query strings are added to TestVectors, they COULD be added here;
30+
# if they are not added, the Table will accept the string as-is.
3931
known_query_string_to_condition_map = {
4032
# "Basic" queries
4133
"RecNum = :zero": Key("RecNum").eq(":zero"),
@@ -130,9 +122,9 @@ def get_item(self, **kwargs):
130122
return client_output
131123

132124
def batch_write_item(self, **kwargs):
133-
# There isn't a resource shape for this;
125+
# The table doesn't support batch_write_item, but supports batch_writer.
126+
# Translate the batch_write_item request to batch_writer requests.
134127
table_input = self._client_shape_to_resource_shape_converter.batch_write_item_request(kwargs)
135-
# table_output = self._table.batch_write_item(**table_input)
136128
with self._table.batch_writer() as batch_writer:
137129
for _, items in table_input["RequestItems"].items():
138130
for item in items:
@@ -142,7 +134,7 @@ def batch_write_item(self, **kwargs):
142134
batch_writer.delete_item(item["DeleteRequest"]["Key"])
143135
else:
144136
raise ValueError(f"Unknown request type: {item}")
145-
# There isn't a shape for the output, but luckily the output can be an empty dict:
137+
# An empty dict is valid output:
146138
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/client/batch_write_item.html
147139
client_output = {}
148140
return client_output
@@ -158,8 +150,7 @@ def scan(self, **kwargs):
158150
if "KeyConditionExpression" in table_input:
159151
if table_input["KeyConditionExpression"] in known_query_string_to_condition_map:
160152
# Turn the query into the resource-formatted query
161-
query = known_query_string_to_condition_map[table_input["KeyConditionExpression"]]
162-
table_input["KeyConditionExpression"] = query
153+
table_input["KeyConditionExpression"] = known_query_string_to_condition_map[table_input["KeyConditionExpression"]]
163154
if "FilterExpression" in table_input:
164155
if table_input["FilterExpression"] in known_query_string_to_condition_map:
165156
# Turn the query into the resource-formatted query
@@ -182,8 +173,7 @@ def query(self, **kwargs):
182173
if "KeyConditionExpression" in table_input:
183174
if table_input["KeyConditionExpression"] in known_query_string_to_condition_map:
184175
# Turn the query into the resource-formatted query
185-
query = known_query_string_to_condition_map[table_input["KeyConditionExpression"]]
186-
table_input["KeyConditionExpression"] = query
176+
table_input["KeyConditionExpression"] = known_query_string_to_condition_map[table_input["KeyConditionExpression"]]
187177
if "FilterExpression" in table_input:
188178
if table_input["FilterExpression"] in known_query_string_to_condition_map:
189179
# Turn the query into the resource-formatted query

0 commit comments

Comments
 (0)