Skip to content

Commit 2ee8800

Browse files
authored
Merge pull request #5 from mattsb42-aws/validators-1
deep type validators
2 parents 131b251 + 25bb09d commit 2ee8800

File tree

5 files changed

+115
-51
lines changed

5 files changed

+115
-51
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You
4+
# may not use this file except in compliance with the License. A copy of
5+
# the License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is
10+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11+
# ANY KIND, either express or implied. See the License for the specific
12+
# language governing permissions and limitations under the License.
13+
"""Custom attrs validators."""
14+
15+
16+
def dictionary_validator(key_type, value_type):
17+
"""attrs validator that performs deep type checking of dictionaries."""
18+
19+
def _validate_dictionary(instance, attribute, value):
20+
"""Validate that a dictionary is structured as expected.
21+
22+
:raises TypeError: if ``value`` is not a dictionary
23+
:raises TypeError: if ``value`` keys are not all of ``key_type`` type
24+
:raises TypeError: if ``value`` values are not all of ``value_type`` type
25+
"""
26+
if not isinstance(value, dict):
27+
raise TypeError('"{}" must be a dictionary'.format(attribute.name))
28+
29+
for key, data in value.items():
30+
if not isinstance(key, key_type):
31+
raise TypeError('"{name}" dictionary keys must be of type "{type}"'.format(
32+
name=attribute.name,
33+
type=key_type
34+
))
35+
36+
if not isinstance(data, value_type):
37+
raise TypeError('"{name}" dictionary values must be of type "{type}"'.format(
38+
name=attribute.name,
39+
type=value_type
40+
))
41+
42+
return _validate_dictionary
43+
44+
45+
def iterable_validator(iterable_type, member_type):
46+
"""attrs validator that performs deep type checking of iterables."""
47+
48+
def _validate_tuple(instance, attribute, value):
49+
"""Validate that a dictionary is structured as expected.
50+
51+
:raises TypeError: if ``value`` is not of ``iterable_type`` type
52+
:raises TypeError: if ``value`` members are not all of ``member_type`` type
53+
"""
54+
if not isinstance(value, iterable_type):
55+
raise TypeError('"{name}" must be a {type}'.format(
56+
name=attribute.name,
57+
type=iterable_type
58+
))
59+
60+
for member in value:
61+
if not isinstance(member, member_type):
62+
raise TypeError('"{name}" members must all be of type "{type}"'.format(
63+
name=attribute.name,
64+
type=member_type
65+
))
66+
67+
return _validate_tuple

src/dynamodb_encryption_sdk/material_providers/aws_kms.py

Lines changed: 17 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
from dynamodb_encryption_sdk.identifiers import EncryptionKeyTypes, KeyEncodingType
3131
from dynamodb_encryption_sdk.internal import dynamodb_types
3232
from dynamodb_encryption_sdk.internal.identifiers import MaterialDescriptionKeys
33+
from dynamodb_encryption_sdk.internal.str_ops import to_bytes, to_str
34+
from dynamodb_encryption_sdk.internal.validators import dictionary_validator, iterable_validator
3335
from dynamodb_encryption_sdk.materials.raw import RawDecryptionMaterials, RawEncryptionMaterials
3436
from dynamodb_encryption_sdk.structures import EncryptionContext
3537

@@ -115,44 +117,18 @@ class AwsKmsCryptographicMaterialsProvider(CryptographicMaterialsProvider):
115117
validator=attr.validators.instance_of(botocore.session.Session),
116118
default=attr.Factory(botocore.session.Session)
117119
)
118-
_grant_tokens = attr.ib(default=attr.Factory(tuple))
119-
120-
@_grant_tokens.validator
121-
def _grant_tokens_validator(self, attribute, value):
122-
"""Validate grant token values."""
123-
if not isinstance(value, tuple):
124-
raise TypeError('"grant_tokens" must be a tuple')
125-
126-
for token in value:
127-
if not isinstance(token, six.string_types):
128-
raise TypeError('"grant_tokens" must contain strings')
129-
130-
_material_description = attr.ib(default=attr.Factory(dict))
131-
132-
@_material_description.validator
133-
def _material_description_validator(self, attribute, value):
134-
"""Validate material description values."""
135-
if not isinstance(value, dict):
136-
raise TypeError('"material_description" must be a dictionary')
137-
138-
for key, data in value.items():
139-
if not (isinstance(key, six.string_types) and isinstance(data, six.string_types)):
140-
raise TypeError('"material_description" must be a string-string dictionary')
141-
142-
_regional_clients = attr.ib(default=attr.Factory(dict))
143-
144-
@_regional_clients.validator
145-
def regional_clients_validator(self, attribute, value):
146-
"""Validate regional clients values."""
147-
if not isinstance(value, dict):
148-
raise TypeError('"regional_clients" must be a dictionary')
149-
150-
for key, client in value.items():
151-
if not isinstance(key, six.string_types):
152-
raise TypeError('"regional_clients" region name must be a string')
153-
154-
if not isinstance(client, botocore.client.BaseClient):
155-
raise TypeError('"regional_clients" client must be a botocore client')
120+
_grant_tokens = attr.ib(
121+
validator=iterable_validator(tuple, six.string_types),
122+
default=attr.Factory(tuple)
123+
)
124+
_material_description = attr.ib(
125+
validator=dictionary_validator(six.string_types, six.string_types),
126+
default=attr.Factory(dict)
127+
)
128+
_regional_clients = attr.ib(
129+
validator=dictionary_validator(six.string_types, botocore.client.BaseClient),
130+
default=attr.Factory(dict)
131+
)
156132

157133
def __attrs_post_init__(self):
158134
# type: () -> None
@@ -330,9 +306,9 @@ def _decrypt_initial_material(self, encryption_context):
330306
MaterialDescriptionKeys.ITEM_SIGNATURE_ALGORITHM.value
331307
)
332308
)
333-
encrypted_initial_material = base64.b64decode(encryption_context.material_description.get(
309+
encrypted_initial_material = base64.b64decode(to_bytes(encryption_context.material_description.get(
334310
MaterialDescriptionKeys.WRAPPED_DATA_KEY.value
335-
))
311+
)))
336312
kms_params = dict(
337313
CiphertextBlob=encrypted_initial_material,
338314
EncryptionContext=kms_encryption_context
@@ -454,7 +430,7 @@ def encryption_materials(self, encryption_context):
454430
MaterialDescriptionKeys.CONTENT_KEY_WRAPPING_ALGORITHM.value: 'kms',
455431
MaterialDescriptionKeys.CONTENT_ENCRYPTION_ALGORITHM.value: self._content_key_info.description,
456432
MaterialDescriptionKeys.ITEM_SIGNATURE_ALGORITHM.value: self._signing_key_info.description,
457-
MaterialDescriptionKeys.WRAPPED_DATA_KEY.value: base64.b64encode(encrypted_initial_material)
433+
MaterialDescriptionKeys.WRAPPED_DATA_KEY.value: to_str(base64.b64encode(encrypted_initial_material))
458434
})
459435
return RawEncryptionMaterials(
460436
signing_key=self._mac_key(initial_material, self._signing_key_info),

src/dynamodb_encryption_sdk/materials/raw.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@
2525
import copy
2626

2727
import attr
28+
import six
2829

2930
from dynamodb_encryption_sdk.delegated_keys import DelegatedKey
31+
from dynamodb_encryption_sdk.internal.validators import dictionary_validator
3032
from dynamodb_encryption_sdk.materials import DecryptionMaterials, EncryptionMaterials
3133

3234

@@ -47,7 +49,7 @@ class RawEncryptionMaterials(EncryptionMaterials):
4749
_signing_key = attr.ib(validator=attr.validators.instance_of(DelegatedKey))
4850
_encryption_key = attr.ib(validator=attr.validators.instance_of(DelegatedKey))
4951
_material_description = attr.ib(
50-
validator=attr.validators.instance_of(dict),
52+
validator=dictionary_validator(six.string_types, six.string_types),
5153
converter=copy.deepcopy,
5254
default=attr.Factory(dict)
5355
)
@@ -107,7 +109,7 @@ class RawDecryptionMaterials(DecryptionMaterials):
107109
_verification_key = attr.ib(validator=attr.validators.instance_of(DelegatedKey))
108110
_decryption_key = attr.ib(validator=attr.validators.instance_of(DelegatedKey))
109111
_material_description = attr.ib(
110-
validator=attr.validators.instance_of(dict),
112+
validator=dictionary_validator(six.string_types, six.string_types),
111113
converter=copy.deepcopy,
112114
default=attr.Factory(dict)
113115
)

src/dynamodb_encryption_sdk/materials/wrapped.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616
import copy
1717

1818
import attr
19+
import six
1920

2021
from dynamodb_encryption_sdk.delegated_keys import DelegatedKey
2122
from dynamodb_encryption_sdk.delegated_keys.jce import JceNameLocalDelegatedKey
2223
from dynamodb_encryption_sdk.exceptions import UnwrappingError, WrappingError
2324
from dynamodb_encryption_sdk.identifiers import EncryptionKeyTypes
2425
from dynamodb_encryption_sdk.internal.identifiers import MaterialDescriptionKeys
26+
from dynamodb_encryption_sdk.internal.validators import dictionary_validator
2527
from dynamodb_encryption_sdk.materials import CryptographicMaterials
2628

2729
__all__ = ('WrappedRawCryptographicMaterials',)
@@ -65,7 +67,7 @@ class WrappedCryptographicMaterials(CryptographicMaterials):
6567
default=None
6668
)
6769
_material_description = attr.ib(
68-
validator=attr.validators.instance_of(dict),
70+
validator=dictionary_validator(six.string_types, six.string_types),
6971
converter=copy.deepcopy,
7072
default=attr.Factory(dict)
7173
)

src/dynamodb_encryption_sdk/structures.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@
1717
import six
1818

1919
from .identifiers import ItemAction
20+
from dynamodb_encryption_sdk.internal.validators import dictionary_validator, iterable_validator
21+
22+
23+
def _validate_attribute_values_are_ddb_items(instance, attribute, value):
24+
"""Validate that dictionary values in ``value`` match the structure of DynamoDB JSON
25+
items.
26+
27+
.. note::
28+
29+
We are not trying to validate the full structure of the item with this validator.
30+
This is just meant to verify that the values roughly match the correct format.
31+
"""
32+
for data in value.values():
33+
if len(list(data.values())) != 1:
34+
raise TypeError('"{}" values do not look like DynamoDB items'.format(attribute.name))
2035

2136

2237
@attr.s(hash=False)
@@ -41,13 +56,15 @@ class EncryptionContext(object):
4156
validator=attr.validators.optional(attr.validators.instance_of(six.string_types)),
4257
default=None
4358
)
44-
# TODO: converter to make sure that attributes are in DDB form
4559
attributes = attr.ib(
46-
validator=attr.validators.optional(attr.validators.instance_of(dict)),
60+
validator=(
61+
dictionary_validator(six.string_types, dict),
62+
_validate_attribute_values_are_ddb_items
63+
),
4764
default=attr.Factory(dict)
4865
)
4966
material_description = attr.ib(
50-
validator=attr.validators.instance_of(dict),
67+
validator=dictionary_validator(six.string_types, six.string_types),
5168
converter=copy.deepcopy,
5269
default=attr.Factory(dict)
5370
)
@@ -66,7 +83,7 @@ class AttributeActions(object):
6683
default=ItemAction.ENCRYPT_AND_SIGN
6784
)
6885
attribute_actions = attr.ib(
69-
validator=attr.validators.instance_of(dict),
86+
validator=dictionary_validator(six.string_types, ItemAction),
7087
default=attr.Factory(dict)
7188
)
7289

@@ -127,8 +144,8 @@ class TableIndex(object):
127144
"""
128145
partition = attr.ib(validator=attr.validators.instance_of(six.string_types))
129146
sort = attr.ib(
130-
default=None,
131-
validator=attr.validators.optional(attr.validators.instance_of(six.string_types))
147+
validator=attr.validators.optional(attr.validators.instance_of(six.string_types)),
148+
default=None
132149
)
133150

134151
def __attrs_post_init__(self):
@@ -159,7 +176,7 @@ class TableInfo(object):
159176
default=None
160177
)
161178
_indexed_attributes = attr.ib(
162-
validator=attr.validators.optional(attr.validators.instance_of(set)),
179+
validator=attr.validators.optional(iterable_validator(set, six.string_types)),
163180
default=None
164181
)
165182

0 commit comments

Comments
 (0)