|
11 | 11 | # ANY KIND, either express or implied. See the License for the specific
|
12 | 12 | # language governing permissions and limitations under the License.
|
13 | 13 | """High-level helper class to provide a familiar interface to encrypted tables."""
|
| 14 | +from functools import partial |
| 15 | + |
14 | 16 | import attr
|
15 | 17 | import botocore.client
|
16 | 18 |
|
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 | +) |
18 | 23 | from dynamodb_encryption_sdk.material_providers import CryptographicMaterialsProvider
|
19 | 24 | from dynamodb_encryption_sdk.structures import AttributeActions, EncryptionContext
|
20 |
| -from . import CryptoConfig, validate_get_arguments |
| 25 | +from . import CryptoConfig |
21 | 26 | from .item import decrypt_dynamodb_item, encrypt_dynamodb_item
|
22 | 27 |
|
23 | 28 | __all__ = ('EncryptedClient',)
|
24 | 29 |
|
25 | 30 |
|
26 | 31 | @attr.s
|
27 | 32 | class EncryptedClient(object):
|
| 33 | + # pylint: disable=too-few-public-methods |
28 | 34 | """High-level helper class to provide a familiar interface to encrypted tables.
|
29 | 35 |
|
30 | 36 | .. note::
|
@@ -57,11 +63,47 @@ class EncryptedClient(object):
|
57 | 63 | )
|
58 | 64 |
|
59 | 65 | 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 |
62 | 68 | client=self._client,
|
63 | 69 | auto_refresh_table_indexes=self._auto_refresh_table_indexes
|
64 | 70 | )
|
| 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 | + ) |
65 | 107 |
|
66 | 108 | def __getattr__(self, name):
|
67 | 109 | """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):
|
96 | 138 | )
|
97 | 139 | return crypto_config, kwargs
|
98 | 140 |
|
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. |
155 | 144 |
|
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 |
157 | 147 | """
|
158 |
| - return self._encrypted_multi_get_single_table(self._client.scan, **kwargs) |
| 148 | + return self._crypto_config(kwargs['TableName'], **kwargs) |
159 | 149 |
|
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. |
162 | 152 |
|
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 |
164 | 156 | """
|
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] |
169 | 158 |
|
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