Skip to content

deep type validators #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions src/dynamodb_encryption_sdk/internal/validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
"""Custom attrs validators."""


def dictionary_validator(key_type, value_type):
"""attrs validator that performs deep type checking of dictionaries."""

def _validate_dictionary(instance, attribute, value):
"""Validate that a dictionary is structured as expected.

:raises TypeError: if ``value`` is not a dictionary
:raises TypeError: if ``value`` keys are not all of ``key_type`` type
:raises TypeError: if ``value`` values are not all of ``value_type`` type
"""
if not isinstance(value, dict):
raise TypeError('"{}" must be a dictionary'.format(attribute.name))

for key, data in value.items():
if not isinstance(key, key_type):
raise TypeError('"{name}" dictionary keys must be of type "{type}"'.format(
name=attribute.name,
type=key_type
))

if not isinstance(data, value_type):
raise TypeError('"{name}" dictionary values must be of type "{type}"'.format(
name=attribute.name,
type=value_type
))

return _validate_dictionary


def iterable_validator(iterable_type, member_type):
"""attrs validator that performs deep type checking of iterables."""

def _validate_tuple(instance, attribute, value):
"""Validate that a dictionary is structured as expected.

:raises TypeError: if ``value`` is not of ``iterable_type`` type
:raises TypeError: if ``value`` members are not all of ``member_type`` type
"""
if not isinstance(value, iterable_type):
raise TypeError('"{name}" must be a {type}'.format(
name=attribute.name,
type=iterable_type
))

for member in value:
if not isinstance(member, member_type):
raise TypeError('"{name}" members must all be of type "{type}"'.format(
name=attribute.name,
type=member_type
))

return _validate_tuple
58 changes: 17 additions & 41 deletions src/dynamodb_encryption_sdk/material_providers/aws_kms.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
from dynamodb_encryption_sdk.identifiers import EncryptionKeyTypes, KeyEncodingType
from dynamodb_encryption_sdk.internal import dynamodb_types
from dynamodb_encryption_sdk.internal.identifiers import MaterialDescriptionKeys
from dynamodb_encryption_sdk.internal.str_ops import to_bytes, to_str
from dynamodb_encryption_sdk.internal.validators import dictionary_validator, iterable_validator
from dynamodb_encryption_sdk.materials.raw import RawDecryptionMaterials, RawEncryptionMaterials
from dynamodb_encryption_sdk.structures import EncryptionContext

Expand Down Expand Up @@ -115,44 +117,18 @@ class AwsKmsCryptographicMaterialsProvider(CryptographicMaterialsProvider):
validator=attr.validators.instance_of(botocore.session.Session),
default=attr.Factory(botocore.session.Session)
)
_grant_tokens = attr.ib(default=attr.Factory(tuple))

@_grant_tokens.validator
def _grant_tokens_validator(self, attribute, value):
"""Validate grant token values."""
if not isinstance(value, tuple):
raise TypeError('"grant_tokens" must be a tuple')

for token in value:
if not isinstance(token, six.string_types):
raise TypeError('"grant_tokens" must contain strings')

_material_description = attr.ib(default=attr.Factory(dict))

@_material_description.validator
def _material_description_validator(self, attribute, value):
"""Validate material description values."""
if not isinstance(value, dict):
raise TypeError('"material_description" must be a dictionary')

for key, data in value.items():
if not (isinstance(key, six.string_types) and isinstance(data, six.string_types)):
raise TypeError('"material_description" must be a string-string dictionary')

_regional_clients = attr.ib(default=attr.Factory(dict))

@_regional_clients.validator
def regional_clients_validator(self, attribute, value):
"""Validate regional clients values."""
if not isinstance(value, dict):
raise TypeError('"regional_clients" must be a dictionary')

for key, client in value.items():
if not isinstance(key, six.string_types):
raise TypeError('"regional_clients" region name must be a string')

if not isinstance(client, botocore.client.BaseClient):
raise TypeError('"regional_clients" client must be a botocore client')
_grant_tokens = attr.ib(
validator=iterable_validator(tuple, six.string_types),
default=attr.Factory(tuple)
)
_material_description = attr.ib(
validator=dictionary_validator(six.string_types, six.string_types),
default=attr.Factory(dict)
)
_regional_clients = attr.ib(
validator=dictionary_validator(six.string_types, botocore.client.BaseClient),
default=attr.Factory(dict)
)

def __attrs_post_init__(self):
# type: () -> None
Expand Down Expand Up @@ -330,9 +306,9 @@ def _decrypt_initial_material(self, encryption_context):
MaterialDescriptionKeys.ITEM_SIGNATURE_ALGORITHM.value
)
)
encrypted_initial_material = base64.b64decode(encryption_context.material_description.get(
encrypted_initial_material = base64.b64decode(to_bytes(encryption_context.material_description.get(
MaterialDescriptionKeys.WRAPPED_DATA_KEY.value
))
)))
kms_params = dict(
CiphertextBlob=encrypted_initial_material,
EncryptionContext=kms_encryption_context
Expand Down Expand Up @@ -454,7 +430,7 @@ def encryption_materials(self, encryption_context):
MaterialDescriptionKeys.CONTENT_KEY_WRAPPING_ALGORITHM.value: 'kms',
MaterialDescriptionKeys.CONTENT_ENCRYPTION_ALGORITHM.value: self._content_key_info.description,
MaterialDescriptionKeys.ITEM_SIGNATURE_ALGORITHM.value: self._signing_key_info.description,
MaterialDescriptionKeys.WRAPPED_DATA_KEY.value: base64.b64encode(encrypted_initial_material)
MaterialDescriptionKeys.WRAPPED_DATA_KEY.value: to_str(base64.b64encode(encrypted_initial_material))
})
return RawEncryptionMaterials(
signing_key=self._mac_key(initial_material, self._signing_key_info),
Expand Down
6 changes: 4 additions & 2 deletions src/dynamodb_encryption_sdk/materials/raw.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@
import copy

import attr
import six

from dynamodb_encryption_sdk.delegated_keys import DelegatedKey
from dynamodb_encryption_sdk.internal.validators import dictionary_validator
from dynamodb_encryption_sdk.materials import DecryptionMaterials, EncryptionMaterials


Expand All @@ -47,7 +49,7 @@ class RawEncryptionMaterials(EncryptionMaterials):
_signing_key = attr.ib(validator=attr.validators.instance_of(DelegatedKey))
_encryption_key = attr.ib(validator=attr.validators.instance_of(DelegatedKey))
_material_description = attr.ib(
validator=attr.validators.instance_of(dict),
validator=dictionary_validator(six.string_types, six.string_types),
converter=copy.deepcopy,
default=attr.Factory(dict)
)
Expand Down Expand Up @@ -107,7 +109,7 @@ class RawDecryptionMaterials(DecryptionMaterials):
_verification_key = attr.ib(validator=attr.validators.instance_of(DelegatedKey))
_decryption_key = attr.ib(validator=attr.validators.instance_of(DelegatedKey))
_material_description = attr.ib(
validator=attr.validators.instance_of(dict),
validator=dictionary_validator(six.string_types, six.string_types),
converter=copy.deepcopy,
default=attr.Factory(dict)
)
Expand Down
4 changes: 3 additions & 1 deletion src/dynamodb_encryption_sdk/materials/wrapped.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@
import copy

import attr
import six

from dynamodb_encryption_sdk.delegated_keys import DelegatedKey
from dynamodb_encryption_sdk.delegated_keys.jce import JceNameLocalDelegatedKey
from dynamodb_encryption_sdk.exceptions import UnwrappingError, WrappingError
from dynamodb_encryption_sdk.identifiers import EncryptionKeyTypes
from dynamodb_encryption_sdk.internal.identifiers import MaterialDescriptionKeys
from dynamodb_encryption_sdk.internal.validators import dictionary_validator
from dynamodb_encryption_sdk.materials import CryptographicMaterials

__all__ = ('WrappedRawCryptographicMaterials',)
Expand Down Expand Up @@ -65,7 +67,7 @@ class WrappedCryptographicMaterials(CryptographicMaterials):
default=None
)
_material_description = attr.ib(
validator=attr.validators.instance_of(dict),
validator=dictionary_validator(six.string_types, six.string_types),
converter=copy.deepcopy,
default=attr.Factory(dict)
)
Expand Down
31 changes: 24 additions & 7 deletions src/dynamodb_encryption_sdk/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@
import six

from .identifiers import ItemAction
from dynamodb_encryption_sdk.internal.validators import dictionary_validator, iterable_validator


def _validate_attribute_values_are_ddb_items(instance, attribute, value):
"""Validate that dictionary values in ``value`` match the structure of DynamoDB JSON
items.

.. note::

We are not trying to validate the full structure of the item with this validator.
This is just meant to verify that the values roughly match the correct format.
"""
for data in value.values():
if len(list(data.values())) != 1:
raise TypeError('"{}" values do not look like DynamoDB items'.format(attribute.name))


@attr.s(hash=False)
Expand All @@ -41,13 +56,15 @@ class EncryptionContext(object):
validator=attr.validators.optional(attr.validators.instance_of(six.string_types)),
default=None
)
# TODO: converter to make sure that attributes are in DDB form
attributes = attr.ib(
validator=attr.validators.optional(attr.validators.instance_of(dict)),
validator=(
dictionary_validator(six.string_types, dict),
_validate_attribute_values_are_ddb_items
),
default=attr.Factory(dict)
)
material_description = attr.ib(
validator=attr.validators.instance_of(dict),
validator=dictionary_validator(six.string_types, six.string_types),
converter=copy.deepcopy,
default=attr.Factory(dict)
)
Expand All @@ -66,7 +83,7 @@ class AttributeActions(object):
default=ItemAction.ENCRYPT_AND_SIGN
)
attribute_actions = attr.ib(
validator=attr.validators.instance_of(dict),
validator=dictionary_validator(six.string_types, ItemAction),
default=attr.Factory(dict)
)

Expand Down Expand Up @@ -127,8 +144,8 @@ class TableIndex(object):
"""
partition = attr.ib(validator=attr.validators.instance_of(six.string_types))
sort = attr.ib(
default=None,
validator=attr.validators.optional(attr.validators.instance_of(six.string_types))
validator=attr.validators.optional(attr.validators.instance_of(six.string_types)),
default=None
)

def __attrs_post_init__(self):
Expand Down Expand Up @@ -159,7 +176,7 @@ class TableInfo(object):
default=None
)
_indexed_attributes = attr.ib(
validator=attr.validators.optional(attr.validators.instance_of(set)),
validator=attr.validators.optional(iterable_validator(set, six.string_types)),
default=None
)

Expand Down