Skip to content

Commit 63a3128

Browse files
committed
consolidate encrypt/decrypt layers across encrypted Client/Resource/Table
1 parent bb4b2d1 commit 63a3128

File tree

5 files changed

+270
-262
lines changed

5 files changed

+270
-262
lines changed

src/dynamodb_encryption_sdk/encrypted/__init__.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,3 @@ def copy(self):
8787
encryption_context=copy.copy(self.encryption_context),
8888
attribute_actions=self.attribute_actions
8989
)
90-
91-
92-
def validate_get_arguments(kwargs):
93-
# type: (Dict[Text, Any]) -> None
94-
"""Verify that attribute filtering parameters are not found in the request.
95-
96-
:raises InvalidArgumentError: if banned parameters are found
97-
"""
98-
for arg in ('AttributesToGet', 'ProjectionExpression'):
99-
if arg in kwargs:
100-
raise InvalidArgumentError('"{}" is not supported for this operation'.format(arg))
101-
102-
if kwargs.get('Select', None) in ('SPECIFIC_ATTRIBUTES', 'ALL_PROJECTED_ATTRIBUTES', 'SPECIFIC_ATTRIBUTES'):
103-
raise InvalidArgumentError('Scan "Select" value of "{}" is not supported'.format(kwargs['Select']))

src/dynamodb_encryption_sdk/encrypted/client.py

Lines changed: 61 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,26 @@
1111
# ANY KIND, either express or implied. See the License for the specific
1212
# language governing permissions and limitations under the License.
1313
"""High-level helper class to provide a familiar interface to encrypted tables."""
14+
from functools import partial
15+
1416
import attr
1517
import botocore.client
1618

17-
from dynamodb_encryption_sdk.internal.utils import TableInfoCache
19+
from dynamodb_encryption_sdk.internal.utils import (
20+
decrypt_batch_get_item, decrypt_get_item, decrypt_multi_get,
21+
encrypt_batch_write_item, encrypt_put_item, TableInfoCache
22+
)
1823
from dynamodb_encryption_sdk.material_providers import CryptographicMaterialsProvider
1924
from dynamodb_encryption_sdk.structures import AttributeActions, EncryptionContext
20-
from . import CryptoConfig, validate_get_arguments
25+
from . import CryptoConfig
2126
from .item import decrypt_dynamodb_item, encrypt_dynamodb_item
2227

2328
__all__ = ('EncryptedClient',)
2429

2530

2631
@attr.s
2732
class EncryptedClient(object):
33+
# pylint: disable=too-few-public-methods
2834
"""High-level helper class to provide a familiar interface to encrypted tables.
2935
3036
.. note::
@@ -57,11 +63,47 @@ class EncryptedClient(object):
5763
)
5864

5965
def __attrs_post_init__(self):
60-
"""Set up the table info cache."""
61-
self._table_info_cache = TableInfoCache(
66+
"""Set up the table info cache and translation methods."""
67+
self._table_info_cache = TableInfoCache( # attrs confuses pylint: disable=attribute-defined-outside-init
6268
client=self._client,
6369
auto_refresh_table_indexes=self._auto_refresh_table_indexes
6470
)
71+
self.get_item = partial( # attrs confuses pylint: disable=attribute-defined-outside-init
72+
decrypt_get_item,
73+
decrypt_method=decrypt_dynamodb_item,
74+
crypto_config_method=self._table_crypto_config,
75+
read_method=self._client.get_item
76+
)
77+
self.put_item = partial( # attrs confuses pylint: disable=attribute-defined-outside-init
78+
encrypt_put_item,
79+
encrypt_method=encrypt_dynamodb_item,
80+
crypto_config_method=self._table_crypto_config,
81+
write_method=self._client.put_item
82+
)
83+
self.query = partial( # attrs confuses pylint: disable=attribute-defined-outside-init
84+
decrypt_multi_get,
85+
decrypt_method=decrypt_dynamodb_item,
86+
crypto_config_method=self._table_crypto_config,
87+
read_method=self._client.query
88+
)
89+
self.scan = partial( # attrs confuses pylint: disable=attribute-defined-outside-init
90+
decrypt_multi_get,
91+
decrypt_method=decrypt_dynamodb_item,
92+
crypto_config_method=self._table_crypto_config,
93+
read_method=self._client.scan
94+
)
95+
self.batch_get_item = partial( # attrs confuses pylint: disable=attribute-defined-outside-init
96+
decrypt_batch_get_item,
97+
decrypt_method=decrypt_dynamodb_item,
98+
crypto_config_method=self._batch_crypto_config,
99+
read_method=self._client.batch_get_item
100+
)
101+
self.batch_write_item = partial( # attrs confuses pylint: disable=attribute-defined-outside-init
102+
encrypt_batch_write_item,
103+
encrypt_method=encrypt_dynamodb_item,
104+
crypto_config_method=self._batch_crypto_config,
105+
write_method=self._client.batch_write_item
106+
)
65107

66108
def __getattr__(self, name):
67109
"""Catch any method/attribute lookups that are not defined in this class and try
@@ -96,110 +138,24 @@ def _crypto_config(self, table_name, **kwargs):
96138
)
97139
return crypto_config, kwargs
98140

99-
def update_item(self, **kwargs):
100-
"""Update item is not yet supported."""
101-
raise NotImplementedError('"update_item" is not yet implemented')
102-
103-
def get_item(self, **kwargs):
104-
"""Transparently decrypt an item after getting it from the table.
105-
106-
https://boto3.readthedocs.io/en/latest/reference/services/dynamodb.html#DynamoDB.Client.get_item
107-
"""
108-
validate_get_arguments(kwargs)
109-
crypto_config, ddb_kwargs = self._crypto_config(kwargs['TableName'], **kwargs)
110-
response = self._client.get_item(**ddb_kwargs)
111-
if 'Item' in response:
112-
response['Item'] = decrypt_dynamodb_item(
113-
item=response['Item'],
114-
crypto_config=crypto_config
115-
)
116-
return response
117-
118-
def put_item(self, **kwargs):
119-
"""Transparently encrypt an item before putting it to the table.
120-
121-
https://boto3.readthedocs.io/en/latest/reference/services/dynamodb.html#DynamoDB.Client.put_item
122-
"""
123-
crypto_config, ddb_kwargs = self._crypto_config(kwargs['TableName'], **kwargs)
124-
ddb_kwargs['Item'] = encrypt_dynamodb_item(
125-
item=ddb_kwargs['Item'],
126-
crypto_config=crypto_config
127-
)
128-
return self._client.put_item(**ddb_kwargs)
129-
130-
def _encrypted_multi_get_single_table(self, method, **kwargs):
131-
"""Transparently decrypt multiple items after getting them from the table.
132-
133-
:param method: Method from underlying DynamoDB client object to use
134-
:type method: callable
135-
"""
136-
validate_get_arguments(kwargs)
137-
crypto_config, ddb_kwargs = self._crypto_config(kwargs['TableName'], **kwargs)
138-
response = method(**ddb_kwargs)
139-
for pos in range(len(response['Items'])):
140-
response['Items'][pos] = decrypt_dynamodb_item(
141-
item=response['Items'][pos],
142-
crypto_config=crypto_config
143-
)
144-
return response
145-
146-
def query(self, **kwargs):
147-
"""Transparently decrypt multiple items after getting them from a query request to the table.
148-
149-
https://boto3.readthedocs.io/en/latest/reference/services/dynamodb.html#DynamoDB.Client.query
150-
"""
151-
return self._encrypted_multi_get_single_table(self._client.query, **kwargs)
152-
153-
def scan(self, **kwargs):
154-
"""Transparently decrypt multiple items after getting them from a scan request to the table.
141+
def _table_crypto_config(self, **kwargs):
142+
"""Pull all encryption-specific parameters from the request and use them to build
143+
a crypto config for a single-table operation.
155144
156-
https://boto3.readthedocs.io/en/latest/reference/services/dynamodb.html#DynamoDB.Client.scan
145+
:returns: crypto config and updated kwargs
146+
:rtype: dynamodb_encryption_sdk.encrypted.CryptoConfig and dict
157147
"""
158-
return self._encrypted_multi_get_single_table(self._client.scan, **kwargs)
148+
return self._crypto_config(kwargs['TableName'], **kwargs)
159149

160-
def batch_get_item(self, **kwargs):
161-
"""Transparently decrypt multiple items after getting them from a batch get item request.
150+
def _batch_crypto_config(self, table_name):
151+
"""Build a crypto config for a specific table.
162152
163-
https://boto3.readthedocs.io/en/latest/reference/services/dynamodb.html#DynamoDB.Client.batch_get_item
153+
:param str table_name: Table for which to build crypto config
154+
:returns: crypto config
155+
:rtype: dynamodb_encryption_sdk.encrypted.CryptoConfig
164156
"""
165-
for _table_name, table_kwargs in kwargs['RequestItems'].items():
166-
validate_get_arguments(table_kwargs)
167-
168-
request_crypto_config = kwargs.pop('crypto_config', None)
157+
return self._crypto_config(table_name)[0]
169158

170-
response = self._client.batch_get_item(**kwargs)
171-
for table_name, items in response['Responses'].items():
172-
if request_crypto_config is not None:
173-
crypto_config = request_crypto_config
174-
else:
175-
crypto_config = self._crypto_config(table_name)[0]
176-
177-
for pos in range(len(items)):
178-
items[pos] = decrypt_dynamodb_item(
179-
item=items[pos],
180-
crypto_config=crypto_config
181-
)
182-
return response
183-
184-
def batch_write_item(self, **kwargs):
185-
"""Transparently encrypt multiple items before writing them with a batch write item request.
186-
187-
https://boto3.readthedocs.io/en/latest/reference/services/dynamodb.html#DynamoDB.Client.batch_write_item
188-
"""
189-
request_crypto_config = kwargs.pop('crypto_config', None)
190-
191-
for table_name, items in kwargs['RequestItems'].items():
192-
if request_crypto_config is not None:
193-
crypto_config = request_crypto_config
194-
else:
195-
crypto_config = self._crypto_config(table_name)[0]
196-
197-
for pos in range(len(items)):
198-
for request_type, item in items[pos].items():
199-
# We don't encrypt primary indexes, so we can ignore DeleteItem requests
200-
if request_type == 'PutRequest':
201-
items[pos][request_type]['Item'] = encrypt_dynamodb_item(
202-
item=item['Item'],
203-
crypto_config=crypto_config
204-
)
205-
return self._client.batch_write_item(**kwargs)
159+
def update_item(self, **kwargs):
160+
"""Update item is not yet supported."""
161+
raise NotImplementedError('"update_item" is not yet implemented')

0 commit comments

Comments
 (0)