Source code for dynamodb_encryption_sdk.encrypted.table

# 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.
"""High-level helper class to provide a familiar interface to encrypted tables."""
from functools import partial

import attr
from boto3.dynamodb.table import BatchWriter
from boto3.resources.base import ServiceResource

from dynamodb_encryption_sdk.internal.utils import (
    crypto_config_from_kwargs,
    crypto_config_from_table_info,
    decrypt_get_item,
    decrypt_multi_get,
    encrypt_put_item,
)
from dynamodb_encryption_sdk.material_providers import CryptographicMaterialsProvider
from dynamodb_encryption_sdk.structures import AttributeActions, TableInfo

from .client import EncryptedClient
from .item import decrypt_python_item, encrypt_python_item

try:  # Python 3.5.0 and 3.5.1 have incompatible typing modules
    from typing import Optional  # noqa pylint: disable=unused-import
except ImportError:  # pragma: no cover
    # We only actually need these imports when running the mypy checks
    pass


__all__ = ("EncryptedTable",)


[docs]@attr.s(init=False) class EncryptedTable(object): # pylint: disable=too-few-public-methods,too-many-instance-attributes """High-level helper class to provide a familiar interface to encrypted tables. >>> import boto3 >>> from dynamodb_encryption_sdk.encrypted.table import EncryptedTable >>> from dynamodb_encryption_sdk.material_providers.aws_kms import AwsKmsCryptographicMaterialsProvider >>> table = boto3.resource('dynamodb').Table('my_table') >>> aws_kms_cmp = AwsKmsCryptographicMaterialsProvider('alias/MyKmsAlias') >>> encrypted_table = EncryptedTable( ... table=table, ... materials_provider=aws_kms_cmp ... ) .. note:: This class provides a superset of the boto3 DynamoDB Table API, so should work as a drop-in replacement once configured. https://boto3.readthedocs.io/en/latest/reference/services/dynamodb.html#table If you want to provide per-request cryptographic details, the ``put_item``, ``get_item``, ``query``, and ``scan`` methods will also accept a ``crypto_config`` parameter, defining a custom :class:`CryptoConfig` instance for this request. .. warning:: We do not currently support the ``update_item`` method. :param table: Pre-configured boto3 DynamoDB Table object :type table: boto3.resources.base.ServiceResource :param CryptographicMaterialsProvider materials_provider: Cryptographic materials provider to use :param TableInfo table_info: Information about the target DynamoDB table :param AttributeActions attribute_actions: Table-level configuration of how to encrypt/sign attributes :param bool auto_refresh_table_indexes: Should we attempt to refresh information about table indexes? Requires ``dynamodb:DescribeTable`` permissions on each table. (default: True) """ _table = attr.ib(validator=attr.validators.instance_of(ServiceResource)) _materials_provider = attr.ib(validator=attr.validators.instance_of(CryptographicMaterialsProvider)) _table_info = attr.ib(validator=attr.validators.optional(attr.validators.instance_of(TableInfo)), default=None) _attribute_actions = attr.ib( validator=attr.validators.instance_of(AttributeActions), default=attr.Factory(AttributeActions) ) _auto_refresh_table_indexes = attr.ib(validator=attr.validators.instance_of(bool), default=True) def __init__( self, table, # type: ServiceResource materials_provider, # type: CryptographicMaterialsProvider table_info=None, # type: Optional[TableInfo] attribute_actions=None, # type: Optional[AttributeActions] auto_refresh_table_indexes=True, # type: Optional[bool] ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. # https://github.com/python/mypy/issues/2088 # https://github.com/python-attrs/attrs/issues/215 if attribute_actions is None: attribute_actions = AttributeActions() self._table = table self._materials_provider = materials_provider self._table_info = table_info self._attribute_actions = attribute_actions self._auto_refresh_table_indexes = auto_refresh_table_indexes attr.validate(self) self.__attrs_post_init__() def __attrs_post_init__(self): """Prepare table info is it was not set and set up translation methods.""" if self._table_info is None: self._table_info = TableInfo(name=self._table.name) if self._auto_refresh_table_indexes: self._table_info.refresh_indexed_attributes(self._table.meta.client) # Clone the attribute actions before we modify them self._attribute_actions = self._attribute_actions.copy() self._attribute_actions.set_index_keys(*self._table_info.protected_index_keys()) self._crypto_config = partial( # attrs confuses pylint: disable=attribute-defined-outside-init crypto_config_from_kwargs, partial(crypto_config_from_table_info, self._materials_provider, self._attribute_actions, self._table_info), ) self.get_item = partial( # attrs confuses pylint: disable=attribute-defined-outside-init decrypt_get_item, decrypt_python_item, self._crypto_config, self._table.get_item ) self.put_item = partial( # attrs confuses pylint: disable=attribute-defined-outside-init encrypt_put_item, encrypt_python_item, self._crypto_config, self._table.put_item ) self.query = partial( # attrs confuses pylint: disable=attribute-defined-outside-init decrypt_multi_get, decrypt_python_item, self._crypto_config, self._table.query ) self.scan = partial( # attrs confuses pylint: disable=attribute-defined-outside-init decrypt_multi_get, decrypt_python_item, self._crypto_config, self._table.scan ) def __getattr__(self, name): """Catch any method/attribute lookups that are not defined in this class and try to find them on the provided bridge object. :param str name: Attribute name :returns: Result of asking the provided table object for that attribute name :raises AttributeError: if attribute is not found on provided bridge object """ return getattr(self._table, name)
[docs] def update_item(self, **kwargs): """Update item is not yet supported.""" raise NotImplementedError('"update_item" is not yet implemented')
[docs] def batch_writer(self, overwrite_by_pkeys=None): """Create a batch writer object. https://boto3.readthedocs.io/en/latest/reference/services/dynamodb.html#DynamoDB.Table.batch_writer :type overwrite_by_pkeys: list(string) :param overwrite_by_pkeys: De-duplicate request items in buffer if match new request item on specified primary keys. i.e ``["partition_key1", "sort_key2", "sort_key3"]`` """ encrypted_client = EncryptedClient( client=self._table.meta.client, materials_provider=self._materials_provider, attribute_actions=self._attribute_actions, auto_refresh_table_indexes=self._auto_refresh_table_indexes, expect_standard_dictionaries=True, ) return BatchWriter(table_name=self._table.name, client=encrypted_client, overwrite_by_pkeys=overwrite_by_pkeys)