-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathresource.py
More file actions
314 lines (240 loc) · 13.7 KB
/
Copy pathresource.py
File metadata and controls
314 lines (240 loc) · 13.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
"""High-level helper classes to provide encrypting wrappers for boto3 DynamoDB resources."""
from collections.abc import Callable, Generator
from copy import deepcopy
from typing import Any
from boto3.resources.base import ServiceResource
from boto3.resources.collection import CollectionManager
from aws_dbesdk_dynamodb.encrypted.boto3_interface import EncryptedBotoInterface
from aws_dbesdk_dynamodb.encrypted.table import EncryptedTable
from aws_dbesdk_dynamodb.internal.client_to_resource import ClientShapeToResourceShapeConverter
from aws_dbesdk_dynamodb.internal.resource_to_client import ResourceShapeToClientShapeConverter
from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb.models import (
DynamoDbTablesEncryptionConfig,
)
from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb_transforms.client import (
DynamoDbEncryptionTransforms,
)
from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb_transforms.models import (
BatchGetItemInputTransformInput,
BatchGetItemOutputTransformInput,
BatchWriteItemInputTransformInput,
BatchWriteItemOutputTransformInput,
)
class EncryptedTablesCollectionManager(EncryptedBotoInterface):
"""
Collection manager that yields EncryptedTable objects.
The API matches boto3's tables collection manager interface:
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/service-resource/tables.html
All operations on this class will yield ``EncryptedTable`` objects.
"""
def __init__(
self,
*,
collection: CollectionManager,
encryption_config: DynamoDbTablesEncryptionConfig,
):
"""
Create an ``EncryptedTablesCollectionManager`` object.
Args:
collection (CollectionManager): Pre-configured boto3 DynamoDB table collection manager
encryption_config (DynamoDbTablesEncryptionConfig): Initialized DynamoDbTablesEncryptionConfig
"""
self._collection = collection
self._encryption_config = encryption_config
def all(self) -> Generator[EncryptedTable, None, None]:
"""
Create an iterable of all EncryptedTable resources in the collection.
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/service-resource/tables.html#DynamoDB.ServiceResource.all
Returns:
Generator[EncryptedTable, None, None]: An iterable of EncryptedTable objects
"""
yield from self._transform_table(self._collection.all)
def filter(self, **kwargs) -> Generator[EncryptedTable, None, None]:
"""
Create an iterable of all EncryptedTable resources in the collection filtered by kwargs passed to method.
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/service-resource/tables.html#filter
Returns:
Generator[EncryptedTable, None, None]: An iterable of EncryptedTable objects
"""
yield from self._transform_table(self._collection.filter, **kwargs)
def limit(self, **kwargs) -> Generator[EncryptedTable, None, None]:
"""
Create an iterable of all EncryptedTable resources in the collection filtered by kwargs passed to method.
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/service-resource/tables.html#limit
Returns:
Generator[EncryptedTable, None, None]: An iterable of EncryptedTable objects
"""
yield from self._transform_table(self._collection.limit, **kwargs)
def page_size(self, **kwargs) -> Generator[EncryptedTable, None, None]:
"""
Create an iterable of all EncryptedTable resources in the collection.
This limits the number of items returned by each service call by the specified amount.
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/service-resource/tables.html#page_size
Returns:
Generator[EncryptedTable, None, None]: An iterable of EncryptedTable objects
"""
yield from self._transform_table(self._collection.page_size, **kwargs)
def _transform_table(
self,
method: Callable,
**kwargs,
) -> Generator[EncryptedTable, None, None]:
for table in method(**kwargs):
yield EncryptedTable(table=table, encryption_config=self._encryption_config)
@property
def _boto_client_attr_name(self) -> str:
"""
Name of the attribute containing the underlying boto3 client.
Returns:
str: '_collection'
"""
return "_collection"
class EncryptedResource(EncryptedBotoInterface):
"""
Wrapper for a boto3 DynamoDB resource.
This class implements the complete boto3 DynamoDB resource API, allowing it to serve as a
drop-in replacement that transparently handles encryption and decryption of items.
The API matches the standard boto3 DynamoDB resource interface:
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/service-resource/index.html
This class will encrypt/decrypt items for the following operations:
* ``batch_get_item``
* ``batch_write_item``
Calling ``Table()`` will return an ``EncryptedTable`` object.
Any other operations on this class will defer to the underlying boto3 DynamoDB resource's implementation
and will not be encrypted/decrypted.
"""
def __init__(
self,
*,
resource: ServiceResource,
encryption_config: DynamoDbTablesEncryptionConfig,
):
"""
Create an ``EncryptedResource`` object.
Args:
resource (ServiceResource): Initialized boto3 DynamoDB resource
encryption_config (DynamoDbTablesEncryptionConfig): Initialized DynamoDbTablesEncryptionConfig
"""
self._resource = resource
self._encryption_config = encryption_config
self._transformer = DynamoDbEncryptionTransforms(config=encryption_config)
self._client_shape_to_resource_shape_converter = ClientShapeToResourceShapeConverter()
self._resource_shape_to_client_shape_converter = ResourceShapeToClientShapeConverter()
self.tables = EncryptedTablesCollectionManager(
collection=self._resource.tables, encryption_config=self._encryption_config
)
def Table(self, name):
"""
Create an ``EncryptedTable`` resource.
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/service-resource/Table.html
Args:
name (str): The EncryptedTable's name identifier. This must be set.
Returns:
EncryptedTable: An ``EncryptedTable`` resource
"""
return EncryptedTable(table=self._resource.Table(name), encryption_config=self._encryption_config)
def batch_get_item(self, **kwargs):
"""
Get multiple items from one or more tables. Decrypts any returned items.
The input and output syntaxes match those for the boto3 DynamoDB resource ``batch_get_item`` API:
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/service-resource/batch_get_item.html
Args:
**kwargs: Keyword arguments to pass to the operation. These match the boto3 resource ``batch_get_item``
request syntax.
Returns:
dict: The response from DynamoDB. This matches the boto3 resource ``batch_get_item`` response syntax.
The ``"Responses"`` field will be decrypted locally after being read from DynamoDB.
"""
return self._resource_operation_logic(
operation_input=kwargs,
input_resource_to_client_shape_transform_method=self._resource_shape_to_client_shape_converter.batch_get_item_request,
input_client_to_resource_shape_transform_method=self._client_shape_to_resource_shape_converter.batch_get_item_request,
input_encryption_transform_method=self._transformer.batch_get_item_input_transform,
input_encryption_transform_shape=BatchGetItemInputTransformInput,
output_encryption_transform_method=self._transformer.batch_get_item_output_transform,
output_encryption_transform_shape=BatchGetItemOutputTransformInput,
output_resource_to_client_shape_transform_method=self._resource_shape_to_client_shape_converter.batch_get_item_response,
output_client_to_resource_shape_transform_method=self._client_shape_to_resource_shape_converter.batch_get_item_response,
resource_method=self._resource.batch_get_item,
)
def batch_write_item(self, **kwargs):
"""
Put or delete multiple items in one or more tables.
For put operations, encrypts items before writing.
The input and output syntaxes match those for the boto3 DynamoDB resource ``batch_write_item`` API:
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/service-resource/batch_write_item.html
Args:
**kwargs: Keyword arguments to pass to the operation. These match the boto3 resource
``batch_write_item`` request syntax. Any ``"PutRequest"`` values in the ``"RequestItems"``
argument will be encrypted locally before being written to DynamoDB.
Returns:
dict: The response from DynamoDB. This matches the boto3 resource ``batch_write_item`` response syntax.
"""
return self._resource_operation_logic(
operation_input=kwargs,
input_resource_to_client_shape_transform_method=self._resource_shape_to_client_shape_converter.batch_write_item_request,
input_client_to_resource_shape_transform_method=self._client_shape_to_resource_shape_converter.batch_write_item_request,
input_encryption_transform_method=self._transformer.batch_write_item_input_transform,
input_encryption_transform_shape=BatchWriteItemInputTransformInput,
output_encryption_transform_method=self._transformer.batch_write_item_output_transform,
output_encryption_transform_shape=BatchWriteItemOutputTransformInput,
output_resource_to_client_shape_transform_method=self._resource_shape_to_client_shape_converter.batch_write_item_response,
output_client_to_resource_shape_transform_method=self._client_shape_to_resource_shape_converter.batch_write_item_response,
resource_method=self._resource.batch_write_item,
)
def _resource_operation_logic(
self,
*,
operation_input: dict[str, Any],
input_resource_to_client_shape_transform_method: Callable,
input_client_to_resource_shape_transform_method: Callable,
input_encryption_transform_method: Callable,
input_encryption_transform_shape: Any,
output_encryption_transform_method: Callable,
output_encryption_transform_shape: Any,
output_resource_to_client_shape_transform_method: Callable,
output_client_to_resource_shape_transform_method: Callable,
resource_method: Callable,
):
operation_input = deepcopy(operation_input)
# Table inputs are formatted as Python dictionary JSON, but encryption transformers expect DynamoDB JSON.
# `input_resource_to_client_shape_transform_method` formats the supplied Python dictionary as DynamoDB JSON.
input_transform_input = input_resource_to_client_shape_transform_method(operation_input)
# Apply encryption transformation to the user-supplied input
input_transform_output = input_encryption_transform_method(
input_encryption_transform_shape(sdk_input=input_transform_input)
).transformed_input
# The encryption transformation result is formatted in DynamoDB JSON,
# but the underlying boto3 table expects Python dictionary JSON.
# `input_client_to_resource_shape_transform_method` formats the transformation as Python dictionary JSON.
sdk_input = input_client_to_resource_shape_transform_method(input_transform_output)
# Call boto3 Table method with Python-dictionary-JSON-formatted, encryption-transformed input,
# and receive Python-dictionary-JSON-formatted boto3 output.
sdk_output = resource_method(**sdk_input)
# Format Python dictionary JSON-formatted SDK output as DynamoDB JSON for encryption transformer
output_transform_input = output_resource_to_client_shape_transform_method(sdk_output)
# Apply encryption transformer to boto3 output
output_transform_output = output_encryption_transform_method(
output_encryption_transform_shape(
original_input=input_transform_input,
sdk_output=output_transform_input,
)
).transformed_output
# Format DynamoDB JSON-formatted encryption transformation result as Python dictionary JSON
dbesdk_response = output_client_to_resource_shape_transform_method(output_transform_output)
# Copy any missing fields from the SDK output to the response
# (e.g. `ConsumedCapacity`)
dbesdk_response = self._copy_sdk_response_to_dbesdk_response(sdk_output, dbesdk_response)
# Clean up the expression builder for the next operation
self._resource_shape_to_client_shape_converter.expression_builder.reset()
return dbesdk_response
@property
def _boto_client_attr_name(self) -> str:
"""
Name of the attribute containing the underlying boto3 client.
Returns:
str: '_resource'
"""
return "_resource"