3434from dynamodb_encryption_sdk .encrypted .table import EncryptedTable
3535from dynamodb_encryption_sdk .identifiers import CryptoAction
3636from dynamodb_encryption_sdk .internal .identifiers import ReservedAttributes
37+ from dynamodb_encryption_sdk .material_providers import CryptographicMaterialsProvider
3738from dynamodb_encryption_sdk .material_providers .most_recent import MostRecentProvider
3839from dynamodb_encryption_sdk .material_providers .static import StaticCryptographicMaterialsProvider
3940from dynamodb_encryption_sdk .material_providers .store .meta import MetaStore
4041from dynamodb_encryption_sdk .material_providers .wrapped import WrappedCryptographicMaterialsProvider
42+ from dynamodb_encryption_sdk .materials import CryptographicMaterials
4143from dynamodb_encryption_sdk .materials .raw import RawDecryptionMaterials , RawEncryptionMaterials
42- from dynamodb_encryption_sdk .structures import AttributeActions
44+ from dynamodb_encryption_sdk .structures import AttributeActions , EncryptionContext
4345from dynamodb_encryption_sdk .transform import ddb_to_dict , dict_to_ddb
4446
4547RUNNING_IN_TRAVIS = "TRAVIS" in os .environ
@@ -164,6 +166,38 @@ def table_with_global_secondary_indexes():
164166 mock_dynamodb2 ().stop ()
165167
166168
169+ class PassThroughCryptographicMaterialsProviderThatRequiresAttributes (CryptographicMaterialsProvider ):
170+ """Cryptographic materials provider that passes through to another, but requires that attributes are set.
171+
172+ If the EncryptionContext passed to decryption_materials or encryption_materials
173+ ever does not have attributes set,
174+ a ValueError is raised.
175+ Otherwise, it passes through to the passthrough CMP normally.
176+ """
177+
178+ def __init__ (self , passthrough_cmp ):
179+ self ._passthrough_cmp = passthrough_cmp
180+
181+ def _assert_attributes_set (self , encryption_context ):
182+ # type: (EncryptionContext) -> None
183+ if not encryption_context .attributes :
184+ raise ValueError ("Encryption context attributes MUST be set!" )
185+
186+ def decryption_materials (self , encryption_context ):
187+ # type: (EncryptionContext) -> CryptographicMaterials
188+ self ._assert_attributes_set (encryption_context )
189+ return self ._passthrough_cmp .decryption_materials (encryption_context )
190+
191+ def encryption_materials (self , encryption_context ):
192+ # type: (EncryptionContext) -> CryptographicMaterials
193+ self ._assert_attributes_set (encryption_context )
194+ return self ._passthrough_cmp .encryption_materials (encryption_context )
195+
196+ def refresh (self ):
197+ # type: () -> None
198+ self ._passthrough_cmp .refresh ()
199+
200+
167201def _get_from_cache (dk_class , algorithm , key_length ):
168202 """Don't generate new keys every time. All we care about is that they are valid keys, not that they are unique."""
169203 try :
@@ -221,8 +255,15 @@ def _some_algorithm_pairs():
221255_cmp_builders = {"static" : build_static_jce_cmp , "wrapped" : _build_wrapped_jce_cmp }
222256
223257
224- def _all_possible_cmps (algorithm_generator ):
225- """Generate all possible cryptographic materials providers based on the supplied generator."""
258+ def _all_possible_cmps (algorithm_generator , require_attributes ):
259+ """Generate all possible cryptographic materials providers based on the supplied generator.
260+
261+ require_attributes determines whether the CMP will be wrapped in
262+ PassThroughCryptographicMaterialsProviderThatRequiresAttributes
263+ to require that attributes are set on every request.
264+ This should ONLY be disabled on the item encryptor tests.
265+ All high-level helper clients MUST set the attributes before passing the encryption context down.
266+ """
226267 # The AES combinations do the same thing, but this makes sure that the AESWrap name works as expected.
227268 yield _build_wrapped_jce_cmp ("AESWrap" , 256 , "HmacSHA256" , 256 )
228269
@@ -242,17 +283,28 @@ def _all_possible_cmps(algorithm_generator):
242283 sig_key_length = signing_key_length ,
243284 )
244285
245- yield pytest .param (
246- builder_func (encryption_algorithm , encryption_key_length , signing_algorithm , signing_key_length ),
247- id = id_string ,
248- )
286+ inner_cmp = builder_func (encryption_algorithm , encryption_key_length , signing_algorithm , signing_key_length )
287+
288+ if require_attributes :
289+ outer_cmp = PassThroughCryptographicMaterialsProviderThatRequiresAttributes (inner_cmp )
290+ else :
291+ outer_cmp = inner_cmp
292+
293+ yield pytest .param (outer_cmp , id = id_string )
249294
250295
251- def set_parametrized_cmp (metafunc ):
252- """Set paramatrized values for cryptographic materials providers."""
296+ def set_parametrized_cmp (metafunc , require_attributes = True ):
297+ """Set paramatrized values for cryptographic materials providers.
298+
299+ require_attributes determines whether the CMP will be wrapped in
300+ PassThroughCryptographicMaterialsProviderThatRequiresAttributes
301+ to require that attributes are set on every request.
302+ This should ONLY be disabled on the item encryptor tests.
303+ All high-level helper clients MUST set the attributes before passing the encryption context down.
304+ """
253305 for name , algorithm_generator in (("all_the_cmps" , _all_algorithm_pairs ), ("some_cmps" , _some_algorithm_pairs )):
254306 if name in metafunc .fixturenames :
255- metafunc .parametrize (name , _all_possible_cmps (algorithm_generator ))
307+ metafunc .parametrize (name , _all_possible_cmps (algorithm_generator , require_attributes ))
256308
257309
258310_ACTIONS = {
@@ -437,30 +489,34 @@ def cycle_batch_item_check(
437489 check_attribute_actions = initial_actions .copy ()
438490 check_attribute_actions .set_index_keys (* list (TEST_KEY .keys ()))
439491 items = _generate_items (initial_item , write_transformer )
492+ items_in_table = len (items )
440493
441494 _put_result = encrypted .batch_write_item ( # noqa
442495 RequestItems = {table_name : [{"PutRequest" : {"Item" : _item }} for _item in items ]}
443496 )
444497
445- ddb_keys = [write_transformer (key ) for key in TEST_BATCH_KEYS ]
446- encrypted_result = raw .batch_get_item (RequestItems = {table_name : {"Keys" : ddb_keys }})
447- check_many_encrypted_items (
448- actual = encrypted_result ["Responses" ][table_name ],
449- expected = items ,
450- attribute_actions = check_attribute_actions ,
451- transformer = read_transformer ,
452- )
453-
454- decrypted_result = encrypted .batch_get_item (RequestItems = {table_name : {"Keys" : ddb_keys }})
455- assert_equal_lists_of_items (
456- actual = decrypted_result ["Responses" ][table_name ], expected = items , transformer = read_transformer
457- )
498+ try :
499+ ddb_keys = [write_transformer (key ) for key in TEST_BATCH_KEYS ]
500+ encrypted_result = raw .batch_get_item (RequestItems = {table_name : {"Keys" : ddb_keys }})
501+ check_many_encrypted_items (
502+ actual = encrypted_result ["Responses" ][table_name ],
503+ expected = items ,
504+ attribute_actions = check_attribute_actions ,
505+ transformer = read_transformer ,
506+ )
458507
459- if delete_items :
460- _cleanup_items (encrypted , write_transformer , table_name )
508+ decrypted_result = encrypted .batch_get_item (RequestItems = {table_name : {"Keys" : ddb_keys }})
509+ assert_equal_lists_of_items (
510+ actual = decrypted_result ["Responses" ][table_name ], expected = items , transformer = read_transformer
511+ )
512+ finally :
513+ if delete_items :
514+ _cleanup_items (encrypted , write_transformer , table_name )
515+ items_in_table = 0
461516
462517 del check_attribute_actions
463518 del items
519+ return items_in_table
464520
465521
466522def cycle_batch_writer_check (raw_table , encrypted_table , initial_actions , initial_item ):
@@ -692,16 +748,23 @@ def client_batch_items_unprocessed_check(
692748 )
693749
694750
695- def client_cycle_batch_items_check_paginators (
751+ def client_cycle_batch_items_check_scan_paginator (
696752 materials_provider , initial_actions , initial_item , table_name , region_name = None
697753):
754+ """Helper function for testing the "scan" paginator.
755+
756+ Populate the specified table with encrypted items,
757+ scan the table with raw client paginator to get encrypted items,
758+ scan the table with encrypted client paginator to get decrypted items,
759+ then verify that all items appear to have been encrypted correctly.
760+ """
698761 kwargs = {}
699762 if region_name is not None :
700763 kwargs ["region_name" ] = region_name
701764 client = boto3 .client ("dynamodb" , ** kwargs )
702765 e_client = EncryptedClient (client = client , materials_provider = materials_provider , attribute_actions = initial_actions )
703766
704- cycle_batch_item_check (
767+ items_in_table = cycle_batch_item_check (
705768 raw = client ,
706769 encrypted = e_client ,
707770 initial_actions = initial_actions ,
@@ -712,29 +775,31 @@ def client_cycle_batch_items_check_paginators(
712775 delete_items = False ,
713776 )
714777
715- encrypted_items = []
716- raw_paginator = client .get_paginator ("scan" )
717- for page in raw_paginator .paginate (TableName = table_name , ConsistentRead = True ):
718- encrypted_items .extend (page ["Items" ])
719-
720- decrypted_items = []
721- encrypted_paginator = e_client .get_paginator ("scan" )
722- for page in encrypted_paginator .paginate (TableName = table_name , ConsistentRead = True ):
723- decrypted_items .extend (page ["Items" ])
724-
725- print (encrypted_items )
726- print (decrypted_items )
727-
728- check_attribute_actions = initial_actions .copy ()
729- check_attribute_actions .set_index_keys (* list (TEST_KEY .keys ()))
730- check_many_encrypted_items (
731- actual = encrypted_items ,
732- expected = decrypted_items ,
733- attribute_actions = check_attribute_actions ,
734- transformer = ddb_to_dict ,
735- )
778+ try :
779+ encrypted_items = []
780+ raw_paginator = client .get_paginator ("scan" )
781+ for page in raw_paginator .paginate (TableName = table_name , ConsistentRead = True ):
782+ encrypted_items .extend (page ["Items" ])
783+
784+ decrypted_items = []
785+ encrypted_paginator = e_client .get_paginator ("scan" )
786+ for page in encrypted_paginator .paginate (TableName = table_name , ConsistentRead = True ):
787+ decrypted_items .extend (page ["Items" ])
788+
789+ assert encrypted_items and decrypted_items
790+ assert len (encrypted_items ) == len (decrypted_items ) == items_in_table
791+
792+ check_attribute_actions = initial_actions .copy ()
793+ check_attribute_actions .set_index_keys (* list (TEST_KEY .keys ()))
794+ check_many_encrypted_items (
795+ actual = encrypted_items ,
796+ expected = decrypted_items ,
797+ attribute_actions = check_attribute_actions ,
798+ transformer = ddb_to_dict ,
799+ )
736800
737- _cleanup_items (encrypted = e_client , write_transformer = dict_to_ddb , table_name = table_name )
801+ finally :
802+ _cleanup_items (encrypted = e_client , write_transformer = dict_to_ddb , table_name = table_name )
738803
739804 raw_scan_result = client .scan (TableName = table_name , ConsistentRead = True )
740805 e_scan_result = e_client .scan (TableName = table_name , ConsistentRead = True )
0 commit comments