Skip to content

Commit e9f917b

Browse files
author
Lucas McDonald
committed
sync
1 parent a9f0b12 commit e9f917b

File tree

2 files changed

+117
-80
lines changed

2 files changed

+117
-80
lines changed

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

Lines changed: 67 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,14 @@ class EncryptedClient(EncryptedBotoInterface):
6969
* ``transact_write_items``
7070
* ``delete_item``
7171
72+
Any calls to ``update_item`` can only update unsigned attributes. If an attribute to be updated is marked as signed,
73+
this operation will raise a ``DynamoDbEncryptionTransformsException``.
74+
7275
The following operations are not supported and will raise DynamoDbEncryptionTransformsException:
7376
7477
* ``execute_statement``
7578
* ``execute_transaction``
7679
* ``batch_execute_statement``
77-
* ``update_item``
7880
7981
Any other operations on this class will defer to the underlying boto3 DynamoDB client's implementation.
8082
@@ -383,6 +385,70 @@ def delete_item(self, **kwargs):
383385
output_item_to_ddb_transform_method=self._resource_to_client_shape_converter.delete_item_response,
384386
)
385387

388+
def update_item(self, **kwargs):
389+
"""
390+
Update an unsigned attribute in an item on a table.
391+
392+
If the attribute is signed, this operation will raise DynamoDbEncryptionTransformsException.
393+
394+
The input and output syntaxes match those for the boto3 DynamoDB client ``update_item`` API:
395+
396+
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/client/update_item.html
397+
398+
Args:
399+
**kwargs: Keyword arguments to pass to the operation. This matches the boto3 client ``update_item``
400+
request syntax.
401+
402+
Returns:
403+
dict: The response from DynamoDB. This matches the boto3 client ``update_item`` response syntax.
404+
405+
Raises:
406+
DynamoDbEncryptionTransformsException: If an attribute specified in the ``UpdateExpression`` is signed.
407+
408+
"""
409+
return self._client_operation_logic(
410+
operation_input=kwargs,
411+
input_item_to_ddb_transform_method=self._resource_to_client_shape_converter.update_item_request,
412+
input_item_to_dict_transform_method=self._client_to_resource_shape_converter.update_item_request,
413+
input_transform_method=self._transformer.update_item_input_transform,
414+
input_transform_shape=UpdateItemInputTransformInput,
415+
output_transform_method=self._transformer.update_item_output_transform,
416+
output_transform_shape=UpdateItemOutputTransformInput,
417+
client_method=self._client.update_item,
418+
output_item_to_dict_transform_method=self._client_to_resource_shape_converter.update_item_response,
419+
output_item_to_ddb_transform_method=self._resource_to_client_shape_converter.update_item_response,
420+
)
421+
422+
def get_paginator(self, operation_name: str) -> EncryptedPaginator | botocore.client.Paginator:
423+
"""
424+
Get a paginator from the underlying client.
425+
426+
If the paginator requested is for "scan" or "query", the paginator returned will
427+
transparently decrypt the returned items.
428+
429+
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html#paginators
430+
431+
Args:
432+
operation_name (str): Name of operation for which to get paginator
433+
434+
Returns:
435+
EncryptedPaginator | botocore.client.Paginator: An EncryptedPaginator that will transparently decrypt items
436+
for ``scan``/``query`` operations; for other operations, the standard paginator.
437+
438+
"""
439+
paginator = self._client.get_paginator(operation_name)
440+
441+
if operation_name in ("scan", "query"):
442+
return EncryptedPaginator(
443+
paginator=paginator,
444+
encryption_config=self._encryption_config,
445+
expect_standard_dictionaries=self._expect_standard_dictionaries,
446+
)
447+
else:
448+
# The paginator can still be used for list_backups, list_tables, and list_tags_of_resource,
449+
# but there is nothing to encrypt/decrypt in these operations.
450+
return paginator
451+
386452
def execute_statement(self, **kwargs):
387453
"""
388454
Not implemented. Raises DynamoDbEncryptionTransformsException.
@@ -431,30 +497,6 @@ def execute_transaction(self, **kwargs):
431497
output_item_to_ddb_transform_method=self._resource_to_client_shape_converter.execute_transaction_response,
432498
)
433499

434-
def update_item(self, **kwargs):
435-
"""
436-
Not implemented. Raises DynamoDbEncryptionTransformsException.
437-
438-
Args:
439-
**kwargs: Any arguments passed to this method
440-
441-
Raises:
442-
DynamoDbEncryptionTransformsException: This operation is not supported on encrypted tables.
443-
444-
"""
445-
return self._client_operation_logic(
446-
operation_input=kwargs,
447-
input_item_to_ddb_transform_method=self._resource_to_client_shape_converter.update_item_request,
448-
input_item_to_dict_transform_method=self._client_to_resource_shape_converter.update_item_request,
449-
input_transform_method=self._transformer.update_item_input_transform,
450-
input_transform_shape=UpdateItemInputTransformInput,
451-
output_transform_method=self._transformer.update_item_output_transform,
452-
output_transform_shape=UpdateItemOutputTransformInput,
453-
client_method=self._client.update_item,
454-
output_item_to_dict_transform_method=self._client_to_resource_shape_converter.update_item_response,
455-
output_item_to_ddb_transform_method=self._resource_to_client_shape_converter.update_item_response,
456-
)
457-
458500
def batch_execute_statement(self, **kwargs):
459501
"""
460502
Not implemented. Raises DynamoDbEncryptionTransformsException.
@@ -479,36 +521,6 @@ def batch_execute_statement(self, **kwargs):
479521
output_item_to_ddb_transform_method=self._resource_to_client_shape_converter.batch_execute_statement_response,
480522
)
481523

482-
def get_paginator(self, operation_name: str) -> EncryptedPaginator | botocore.client.Paginator:
483-
"""
484-
Get a paginator from the underlying client.
485-
486-
If the paginator requested is for "scan" or "query", the paginator returned will
487-
transparently decrypt the returned items.
488-
489-
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html#paginators
490-
491-
Args:
492-
operation_name (str): Name of operation for which to get paginator
493-
494-
Returns:
495-
EncryptedPaginator | botocore.client.Paginator: An EncryptedPaginator that will transparently decrypt items
496-
for ``scan``/``query`` operations; for other operations, the standard paginator.
497-
498-
"""
499-
paginator = self._client.get_paginator(operation_name)
500-
501-
if operation_name in ("scan", "query"):
502-
return EncryptedPaginator(
503-
paginator=paginator,
504-
encryption_config=self._encryption_config,
505-
expect_standard_dictionaries=self._expect_standard_dictionaries,
506-
)
507-
else:
508-
# The paginator can still be used for list_backups, list_tables, and list_tags_of_resource,
509-
# but there is nothing to encrypt/decrypt in these operations.
510-
return paginator
511-
512524
def _client_operation_logic(
513525
self,
514526
*,

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

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
simple_key_dict,
2525
)
2626
from ...requests import (
27+
basic_batch_execute_statement_request,
2728
basic_batch_get_item_request_ddb,
2829
basic_batch_get_item_request_dict,
2930
basic_batch_write_item_delete_request_ddb,
@@ -32,6 +33,8 @@
3233
basic_batch_write_item_put_request_dict,
3334
basic_delete_item_request_ddb,
3435
basic_delete_item_request_dict,
36+
basic_execute_statement_request,
37+
basic_execute_transaction_request,
3538
basic_get_item_request_ddb,
3639
basic_get_item_request_dict,
3740
basic_put_item_request_ddb,
@@ -46,11 +49,10 @@
4649
basic_transact_write_item_delete_request_dict,
4750
basic_transact_write_item_put_request_ddb,
4851
basic_transact_write_item_put_request_dict,
49-
basic_update_item_request_ddb,
50-
basic_update_item_request_dict,
51-
basic_execute_statement_request,
52-
basic_execute_transaction_request,
53-
basic_batch_execute_statement_request,
52+
basic_update_item_request_ddb_signed_attribute,
53+
basic_update_item_request_ddb_unsigned_attribute,
54+
basic_update_item_request_dict_signed_attribute,
55+
basic_update_item_request_dict_unsigned_attribute,
5456
)
5557
from . import sort_dynamodb_json_lists
5658

@@ -381,14 +383,39 @@ def test_GIVEN_valid_transact_write_and_get_requests_WHEN_transact_write_and_get
381383

382384

383385
@pytest.fixture
384-
def update_item_request(expect_standard_dictionaries, test_item):
386+
def update_item_request_unsigned_attribute(expect_standard_dictionaries, test_item):
385387
if expect_standard_dictionaries:
386-
return {**basic_update_item_request_dict(test_item), "TableName": INTEG_TEST_DEFAULT_DYNAMODB_TABLE_NAME}
387-
return basic_update_item_request_ddb(test_item)
388+
return {
389+
**basic_update_item_request_dict_unsigned_attribute(test_item),
390+
"TableName": INTEG_TEST_DEFAULT_DYNAMODB_TABLE_NAME,
391+
}
392+
return basic_update_item_request_ddb_unsigned_attribute(test_item)
388393

389394

390-
def test_WHEN_update_item_THEN_raises_DynamoDbEncryptionTransformsException(
391-
client, update_item_request, encrypted,
395+
def test_WHEN_update_item_with_unsigned_attribute_THEN_passes(
396+
client, update_item_request_unsigned_attribute, encrypted, get_item_request
397+
):
398+
# Given: Valid update_item request
399+
# When: update_item
400+
update_response = client.update_item(**update_item_request_unsigned_attribute)
401+
# Then: update_item succeeds
402+
assert update_response["ResponseMetadata"]["HTTPStatusCode"] == 200
403+
404+
405+
@pytest.fixture
406+
def update_item_request_signed_attribute(expect_standard_dictionaries, test_item):
407+
if expect_standard_dictionaries:
408+
return {
409+
**basic_update_item_request_dict_signed_attribute(test_item),
410+
"TableName": INTEG_TEST_DEFAULT_DYNAMODB_TABLE_NAME,
411+
}
412+
return basic_update_item_request_ddb_signed_attribute(test_item)
413+
414+
415+
def test_WHEN_update_item_with_signed_attribute_THEN_raises_DynamoDbEncryptionTransformsException(
416+
client,
417+
update_item_request_signed_attribute,
418+
encrypted,
392419
):
393420
"""Test that update_item raises DynamoDbEncryptionTransformsException."""
394421
if not encrypted:
@@ -398,9 +425,7 @@ def test_WHEN_update_item_THEN_raises_DynamoDbEncryptionTransformsException(
398425
# Then: DynamoDbEncryptionTransformsException is raised
399426
with pytest.raises(DynamoDbEncryptionTransformsException):
400427
# When: Calling update_item
401-
client.update_item(
402-
**update_item_request
403-
)
428+
client.update_item(**update_item_request_signed_attribute)
404429

405430

406431
@pytest.fixture
@@ -409,7 +434,9 @@ def execute_statement_request():
409434

410435

411436
def test_WHEN_execute_statement_THEN_raises_DynamoDbEncryptionTransformsException(
412-
client, execute_statement_request, encrypted,
437+
client,
438+
execute_statement_request,
439+
encrypted,
413440
):
414441
"""Test that execute_statement raises DynamoDbEncryptionTransformsException."""
415442
if not encrypted:
@@ -419,9 +446,7 @@ def test_WHEN_execute_statement_THEN_raises_DynamoDbEncryptionTransformsExceptio
419446
# Then: DynamoDbEncryptionTransformsException is raised
420447
with pytest.raises(DynamoDbEncryptionTransformsException):
421448
# When: Calling update_item
422-
client.execute_statement(
423-
**execute_statement_request
424-
)
449+
client.execute_statement(**execute_statement_request)
425450

426451

427452
@pytest.fixture
@@ -430,7 +455,9 @@ def execute_transaction_request():
430455

431456

432457
def test_WHEN_execute_transaction_THEN_raises_DynamoDbEncryptionTransformsException(
433-
client, execute_transaction_request, encrypted,
458+
client,
459+
execute_transaction_request,
460+
encrypted,
434461
):
435462
"""Test that execute_transaction raises DynamoDbEncryptionTransformsException."""
436463
if not encrypted:
@@ -440,9 +467,7 @@ def test_WHEN_execute_transaction_THEN_raises_DynamoDbEncryptionTransformsExcept
440467
# Then: DynamoDbEncryptionTransformsException is raised
441468
with pytest.raises(DynamoDbEncryptionTransformsException):
442469
# When: Calling update_item
443-
client.execute_transaction(
444-
**execute_transaction_request
445-
)
470+
client.execute_transaction(**execute_transaction_request)
446471

447472

448473
@pytest.fixture
@@ -451,7 +476,9 @@ def batch_execute_statement_request():
451476

452477

453478
def test_WHEN_batch_execute_statement_THEN_raises_DynamoDbEncryptionTransformsException(
454-
client, batch_execute_statement_request, encrypted,
479+
client,
480+
batch_execute_statement_request,
481+
encrypted,
455482
):
456483
"""Test that batch_execute_statement raises DynamoDbEncryptionTransformsException."""
457484
if not encrypted:
@@ -461,9 +488,7 @@ def test_WHEN_batch_execute_statement_THEN_raises_DynamoDbEncryptionTransformsEx
461488
# Then: DynamoDbEncryptionTransformsException is raised
462489
with pytest.raises(DynamoDbEncryptionTransformsException):
463490
# When: Calling update_item
464-
client.batch_execute_statement(
465-
**batch_execute_statement_request
466-
)
491+
client.batch_execute_statement(**batch_execute_statement_request)
467492

468493

469494
def test_WHEN_get_paginator_THEN_correct_paginator_is_returned():

0 commit comments

Comments
 (0)