Skip to content

Commit a2bd9bb

Browse files
m
1 parent 1eff5a7 commit a2bd9bb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+291625
-26
lines changed

DynamoDbEncryption/dafny/DynamoDbEncryption/Model/DynamoDbEncryption.smithy

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use com.amazonaws.dynamodb#AttributeName
2626
use com.amazonaws.dynamodb#Key
2727
use com.amazonaws.dynamodb#AttributeNameList
2828
use com.amazonaws.dynamodb#KeySchemaAttributeName
29+
use com.amazonaws.dynamodb#ExpressionAttributeNameMap
2930

3031
use aws.cryptography.primitives#AwsCryptographicPrimitives
3132
use aws.cryptography.dbEncryptionSdk.structuredEncryption#StructuredEncryption
@@ -881,3 +882,15 @@ structure DynamoDbEncryptionException {
881882
@required
882883
message: String,
883884
}
885+
886+
structure ExtractAttrtibutesInput {
887+
@required
888+
s: string
889+
ex: ExpressionAttributeNameMap
890+
}
891+
892+
structure ExtractAttrtibutesOutput {
893+
@required
894+
s: string
895+
ex: ExpressionAttributeNameMap
896+
}

DynamoDbEncryption/runtimes/python/src/aws_database_encryption_sdk/__init__.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,32 @@
66

77
# Initialize externs
88
from .internaldafny import extern
9+
10+
"""
11+
boto3 uses Python's decimal library to deserialize numbers retrieved by resources
12+
(Tables, etc.) from strings to `decimal.Decimal`s.
13+
boto3 deserializes strings to Decimals according to its DYNAMODB_CONTEXT:
14+
https://github.com/boto/boto3/blob/develop/boto3/dynamodb/types.py#L37-L42
15+
16+
boto3 is configured to raise an exception if the deserialization is "Rounded": (`traps: [.. Rounded]`)
17+
https://docs.python.org/3/library/decimal.html#decimal.Rounded
18+
"Rounded" means some digits were discarded.
19+
However, those digits may have been 0, and no information is lost.
20+
21+
boto3 is also configured to raise an exception if the deserialization is "Inexact":
22+
https://docs.python.org/3/library/decimal.html#decimal.Inexact
23+
"Inexact" means non-zero digits are discarded, and the result is inexact.
24+
25+
Other DBESDK DynamoDB runtimes treat "Rounded" as acceptable, but "Inexact" as unacceptable.
26+
By default, boto3 will treat both "Rounded" and "Inexact" as unacceptable.
27+
28+
For DBESDK DynamoDB, change the DynamoDB context to treat "Rounded" as acceptable.
29+
"""
30+
import boto3.dynamodb.types
31+
from decimal import Rounded
32+
33+
old_context = boto3.dynamodb.types.DYNAMODB_CONTEXT
34+
old_traps = old_context.__getattribute__("traps")
35+
# traps structure: {k (trap class) : v (True if trap should raise Exception; False otherwise)}
36+
old_traps[Rounded] = False
37+
boto3.dynamodb.types.DYNAMODB_CONTEXT.__setattr__("traps", old_traps)

DynamoDbEncryption/runtimes/python/src/aws_database_encryption_sdk/encryptor/client.py

Lines changed: 122 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,14 @@ class EncryptedClient:
4444
_client: botocore.client.BaseClient
4545
_encryption_config: DynamoDbTablesEncryptionConfig
4646
_transformer: DynamoDbEncryptionTransforms
47+
_expect_standard_dictionaries: bool
4748

4849
def __init__(
4950
self,
5051
*,
5152
client: botocore.client.BaseClient,
5253
encryption_config: DynamoDbTablesEncryptionConfig,
54+
expect_standard_dictionaries: Optional[bool] = False,
5355
):
5456
"""
5557
Parameters:
@@ -61,6 +63,87 @@ def __init__(
6163
self._transformer = DynamoDbEncryptionTransforms(
6264
config = encryption_config
6365
)
66+
self._expect_standard_dictionaries = expect_standard_dictionaries
67+
68+
def _maybe_transform_request_to_dynamodb_item(self, item_key, **kwargs):
69+
if self._expect_standard_dictionaries:
70+
dynamodb_item = dict_to_ddb(kwargs[item_key])
71+
dynamodb_input = kwargs
72+
dynamodb_input[item_key] = dynamodb_item
73+
else:
74+
dynamodb_input = kwargs
75+
return dynamodb_input
76+
77+
def _maybe_transform_batch_request_to_dynamodb_item(self, **kwargs):
78+
if not self._expect_standard_dictionaries:
79+
return kwargs
80+
81+
output_requests = []
82+
83+
tables = kwargs["RequestItems"]
84+
table_names = list(tables.keys())
85+
if len(tables.keys()) > 1:
86+
raise ValueError("Table batch_write_item only supports 1 table in a request.")
87+
table_name = table_names[0]
88+
requests = tables[table_names[0]]
89+
for request in requests:
90+
request_name_list = list(request.keys())
91+
if len(request_name_list) > 1:
92+
raise ValueError("Cannot send more than one request in a single request")
93+
request_name = request_name_list[0]
94+
if request_name == "PutRequest":
95+
dict_request = dict_to_ddb(request[request_name]["Item"])
96+
boto3_request = {"Item": dict_request}
97+
elif request_name == "DeleteRequest":
98+
dict_request = dict_to_ddb(request[request_name]["Key"])
99+
boto3_request = {"Key": dict_request}
100+
output_requests.append({request_name: boto3_request})
101+
else:
102+
raise ValueError(f"Unknown batch_write_items method key: {request_name}")
103+
output_requests.append({request_name: boto3_request})
104+
kwargs["RequestItems"][table_name] = output_requests
105+
return kwargs
106+
107+
def _maybe_transform_batch_request_to_dict_item(self, **kwargs):
108+
# TODO separate standard dictionaries for this one (internal client expects dicts)
109+
if not self._expect_standard_dictionaries:
110+
return kwargs
111+
112+
output_requests = []
113+
114+
tables = kwargs["RequestItems"]
115+
table_names = list(tables.keys())
116+
if len(tables.keys()) > 1:
117+
raise ValueError("Table batch_write_item only supports 1 table in a request.")
118+
table_name = table_names[0]
119+
requests = tables[table_names[0]]
120+
for request in requests:
121+
request_name_list = list(request.keys())
122+
if len(request_name_list) > 1:
123+
raise ValueError("Cannot send more than one request in a single request")
124+
request_name = request_name_list[0]
125+
if request_name == "PutRequest":
126+
dict_request = ddb_to_dict(request[request_name]["Item"])
127+
boto3_request = {"Item": dict_request}
128+
elif request_name == "DeleteRequest":
129+
dict_request = ddb_to_dict(request[request_name]["Key"])
130+
boto3_request = {"Key": dict_request}
131+
output_requests.append({request_name: boto3_request})
132+
else:
133+
raise ValueError(f"Unknown batch_write_items method key: {request_name}")
134+
output_requests.append({request_name: boto3_request})
135+
kwargs["RequestItems"][table_name] = output_requests
136+
return kwargs
137+
138+
def _maybe_transform_response_to_python_dict(self, response):
139+
if self._expect_standard_dictionaries:
140+
if hasattr(response, "Item"):
141+
response["Item"] = ddb_to_dict(response["Item"])
142+
143+
def _maybe_transform_batch_response_to_python_dict(self, response):
144+
if self._expect_standard_dictionaries:
145+
if hasattr(response, "Item"):
146+
response["Item"] = ddb_to_dict(response["Item"])
64147

65148
def _copy_sdk_response_to_dbesdk_response(self, sdk_response, dbesdk_response):
66149
for sdk_response_key, sdk_response_value in sdk_response.items():
@@ -75,131 +158,154 @@ def _get_protected_methods(self):
75158
]
76159

77160
def put_item(self, **kwargs):
161+
sdk_input = self._maybe_transform_request_to_dynamodb_item(item_key = "Item", **kwargs)
78162
transformed_request = self._transformer.put_item_input_transform(
79163
PutItemInputTransformInput(
80-
sdk_input = kwargs
164+
sdk_input = sdk_input
81165
)
82166
).transformed_input
83167
sdk_response = self._client.put_item(**transformed_request)
84168
dbesdk_response = self._transformer.put_item_output_transform(
85169
PutItemOutputTransformInput(
86-
original_input = kwargs,
170+
original_input = sdk_input,
87171
sdk_output = sdk_response,
88172
)
89173
).transformed_output
90174
self._copy_sdk_response_to_dbesdk_response(sdk_response, dbesdk_response)
175+
self._maybe_transform_response_to_python_dict(dbesdk_response)
91176
return dbesdk_response
92177

93178
def get_item(self, **kwargs):
179+
sdk_input = self._maybe_transform_request_to_dynamodb_item(item_key = "Key", **kwargs)
94180
transformed_request = self._transformer.get_item_input_transform(
95181
GetItemInputTransformInput(
96-
sdk_input = kwargs
182+
sdk_input = sdk_input
97183
)
98184
).transformed_input
99185
sdk_response = self._client.get_item(**transformed_request)
100186
dbesdk_response = self._transformer.get_item_output_transform(
101187
GetItemOutputTransformInput(
102-
original_input = kwargs,
188+
original_input = sdk_input,
103189
sdk_output = sdk_response,
104190
)
105191
).transformed_output
106192
self._copy_sdk_response_to_dbesdk_response(sdk_response, dbesdk_response)
193+
self._maybe_transform_response_to_python_dict(dbesdk_response)
107194
return dbesdk_response
108195

109196
def batch_write_item(self, **kwargs):
197+
# print(f"batch_write_item pre {kwargs=}")
198+
sdk_input = self._maybe_transform_batch_request_to_dynamodb_item(**kwargs)
199+
# print(f"batch_write_item post {kwargs=}")
110200
transformed_request = self._transformer.batch_write_item_input_transform(
111201
BatchWriteItemInputTransformInput(
112-
sdk_input = kwargs
202+
sdk_input = sdk_input
113203
)
114204
).transformed_input
205+
# print(f"{self._client=}")
206+
# print(f"{transformed_request=}")
207+
transformed_request = self._maybe_transform_batch_request_to_dict_item(**transformed_request)
115208
sdk_response = self._client.batch_write_item(**transformed_request)
116209
dbesdk_response = self._transformer.batch_write_item_output_transform(
117210
BatchWriteItemOutputTransformInput(
118-
original_input = kwargs,
211+
original_input = sdk_input,
119212
sdk_output = sdk_response,
120213
)
121214
).transformed_output
215+
print(f'{sdk_response=}')
122216
self._copy_sdk_response_to_dbesdk_response(sdk_response, dbesdk_response)
217+
self._maybe_transform_response_to_python_dict(dbesdk_response)
218+
print(f'{dbesdk_response=}')
123219
return dbesdk_response
124220

125221
def batch_get_item(self, **kwargs):
222+
sdk_input = self._maybe_transform_request_to_dynamodb_item(item_key = None, **kwargs)
126223
transformed_request = self._transformer.batch_get_item_input_transform(
127224
BatchGetItemInputTransformInput(
128-
sdk_input = kwargs
225+
sdk_input = sdk_input
129226
)
130227
).transformed_input
131228
sdk_response = self._client.batch_get_item(**transformed_request)
132229
dbesdk_response = self._transformer.batch_get_item_output_transform(
133230
BatchGetItemOutputTransformInput(
134-
original_input = kwargs,
231+
original_input = sdk_input,
135232
sdk_output = sdk_response,
136233
)
137234
).transformed_output
138235
self._copy_sdk_response_to_dbesdk_response(sdk_response, dbesdk_response)
236+
self._maybe_transform_response_to_python_dict(dbesdk_response)
139237
return dbesdk_response
140238

141239
def scan(self, **kwargs):
240+
sdk_input = self._maybe_transform_request_to_dynamodb_item(item_key = "Key", **kwargs)
142241
transformed_request = self._transformer.scan_input_transform(
143242
ScanInputTransformInput(
144-
sdk_input = kwargs
243+
sdk_input = sdk_input
145244
)
146245
).transformed_input
147246
sdk_response = self._client.scan(**transformed_request)
148247
dbesdk_response = self._transformer.scan_output_transform(
149248
ScanOutputTransformInput(
150-
original_input = kwargs,
249+
original_input = sdk_input,
151250
sdk_output = sdk_response,
152251
)
153252
).transformed_output
154253
self._copy_sdk_response_to_dbesdk_response(sdk_response, dbesdk_response)
254+
self._maybe_transform_response_to_python_dict(dbesdk_response)
155255
return dbesdk_response
156256

157257
def transact_get_items(self, **kwargs):
258+
sdk_input = self._maybe_transform_request_to_dynamodb_item(item_key = "Key", **kwargs)
158259
transformed_request = self._transformer.transact_get_items_input_transform(
159260
TransactGetItemsInputTransformInput(
160-
sdk_input = kwargs
261+
sdk_input = sdk_input
161262
)
162263
).transformed_input
163264
sdk_response = self._client.transact_get_items(**transformed_request)
164265
dbesdk_response = self._transformer.transact_get_items_output_transform(
165266
TransactGetItemsOutputTransformInput(
166-
original_input = kwargs,
267+
original_input = sdk_input,
167268
sdk_output = sdk_response,
168269
)
169270
).transformed_output
170271
self._copy_sdk_response_to_dbesdk_response(sdk_response, dbesdk_response)
272+
self._maybe_transform_response_to_python_dict(dbesdk_response)
171273
return dbesdk_response
172274

173275
def transact_write_items(self, **kwargs):
276+
sdk_input = self._maybe_transform_request_to_dynamodb_item(item_key = "Key", **kwargs)
174277
transformed_request = self._transformer.transact_write_items_input_transform(
175278
TransactWriteItemsInputTransformInput(
176-
sdk_input = kwargs
279+
sdk_input = sdk_input
177280
)
178281
).transformed_input
179282
sdk_response = self._client.transact_write_items(**transformed_request)
180283
dbesdk_response = self._transformer.transact_write_items_output_transform(
181284
TransactWriteItemsOutputTransformInput(
182-
original_input = kwargs,
285+
original_input = sdk_input,
183286
sdk_output = sdk_response,
184287
)
185288
).transformed_output
186289
self._copy_sdk_response_to_dbesdk_response(sdk_response, dbesdk_response)
290+
self._maybe_transform_response_to_python_dict(dbesdk_response)
187291
return dbesdk_response
188292

189293
def query(self, **kwargs):
294+
sdk_input = self._maybe_transform_request_to_dynamodb_item(item_key = "Key", **kwargs)
190295
transformed_request = self._transformer.query_input_transform(
191296
QueryInputTransformInput(
192-
sdk_input = kwargs
297+
sdk_input = sdk_input
193298
)
194299
).transformed_input
195300
sdk_response = self._client.query(**transformed_request)
196301
dbesdk_response = self._transformer.query_output_transform(
197302
QueryOutputTransformInput(
198-
original_input = kwargs,
303+
original_input = sdk_input,
199304
sdk_output = sdk_response,
200305
)
201306
).transformed_output
202307
self._copy_sdk_response_to_dbesdk_response(sdk_response, dbesdk_response)
308+
self._maybe_transform_response_to_python_dict(dbesdk_response)
203309
return dbesdk_response
204310

205311
def __getattr__(self, name):

DynamoDbEncryption/runtimes/python/src/aws_database_encryption_sdk/encryptor/item.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,13 @@ class ItemEncryptor:
2727

2828
def __init__(
2929
self,
30-
*,
31-
item_encryptor_config: DynamoDbItemEncryptorConfig | None,
30+
item_encryptor_config: DynamoDbItemEncryptorConfig,
3231
):
3332
"""
3433
Create an ItemEncryptor.
35-
Must provide exactly one of `item_encryptor_config` or `initialized_client`.
3634
3735
Parameters:
38-
item_encryptor_config (Optional[DynamoDbItemEncryptorConfig]): Encryption configuration object.
39-
initialized_client (Optional[DynamoDbItemEncryptor]): Initialized DynamoDbItemEncryptor.
40-
36+
item_encryptor_config (DynamoDbItemEncryptorConfig): Encryption configuration object.
4137
"""
4238
self._internal_client = DynamoDbItemEncryptor(config = item_encryptor_config)
4339

@@ -109,7 +105,7 @@ def encrypt_dynamodb_item(
109105
"""
110106
return self.encrypt_item(
111107
EncryptItemInput(
112-
plaintext_item = plaintext_ddb_item
108+
plaintext_item = plaintext_dynamodb_item
113109
)
114110
)
115111

0 commit comments

Comments
 (0)