diff --git a/aws_lambda_powertools/utilities/data_masking/base.py b/aws_lambda_powertools/utilities/data_masking/base.py index 8cb7eb4c9d9..356dee91c9f 100644 --- a/aws_lambda_powertools/utilities/data_masking/base.py +++ b/aws_lambda_powertools/utilities/data_masking/base.py @@ -98,9 +98,12 @@ def _apply_action_to_fields(self, data: Union[dict, str], fields, action, **prov ) for field in fields: + # prevent overriding loop variable + current_field = field if not isinstance(field, str): - field = json.dumps(field) - keys = field.split(".") + current_field = json.dumps(field) + + keys = current_field.split(".") curr_dict = my_dict_parsed for key in keys[:-1]: diff --git a/poetry.lock b/poetry.lock index 6d0c63e808e..c6a3e91bf19 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2374,6 +2374,20 @@ pytest = ">=5.0" [package.extras] dev = ["pre-commit", "pytest-asyncio", "tox"] +[[package]] +name = "pytest-socket" +version = "0.6.0" +description = "Pytest Plugin to disable socket calls during tests" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "pytest_socket-0.6.0-py3-none-any.whl", hash = "sha256:cca72f134ff01e0023c402e78d31b32e68da3efdf3493bf7788f8eba86a6824c"}, + {file = "pytest_socket-0.6.0.tar.gz", hash = "sha256:363c1d67228315d4fc7912f1aabfd570de29d0e3db6217d61db5728adacd7138"}, +] + +[package.dependencies] +pytest = ">=3.6.3" + [[package]] name = "pytest-xdist" version = "3.3.1" diff --git a/pyproject.toml b/pyproject.toml index 7eefa02a299..f8af23604cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,21 +4,31 @@ version = "2.23.1" description = "Powertools for AWS Lambda (Python) is a developer toolkit to implement Serverless best practices and increase developer velocity." authors = ["Amazon Web Services"] include = ["aws_lambda_powertools/py.typed", "THIRD-PARTY-LICENSES"] -classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT No Attribution License (MIT-0)", - "Natural Language :: English", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT No Attribution License (MIT-0)", + "Natural Language :: English", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", ] repository = "https://github.com/aws-powertools/powertools-lambda-python" documentation = "https://docs.powertools.aws.dev/lambda/python/" readme = "README.md" -keywords = ["aws_lambda_powertools", "aws", "tracing", "logging", "lambda", "powertools", "feature_flags", "idempotency", "middleware"] +keywords = [ + "aws_lambda_powertools", + "aws", + "tracing", + "logging", + "lambda", + "powertools", + "feature_flags", + "idempotency", + "middleware", +] # MIT-0 is not recognized as an existing license from poetry. # By using `MIT` as a license value, a `License :: OSI Approved :: MIT License` classifier is added to the classifiers list. license = "MIT" @@ -38,7 +48,7 @@ datadog-lambda = { version = "^4.77.0", optional = true } aws-encryption-sdk = { version = "^3.1.1", optional = true } [tool.poetry.dev-dependencies] -coverage = {extras = ["toml"], version = "^7.2"} +coverage = { extras = ["toml"], version = "^7.2" } pytest = "^7.4.1" black = "^23.3" boto3 = "^1.18" @@ -75,7 +85,7 @@ filelock = "^3.12.2" checksumdir = "^1.2.0" mypy-boto3-appconfigdata = "^1.28.36" ijson = "^3.2.2" -typed-ast = { version = "^1.5.5", python = "< 3.8"} +typed-ast = { version = "^1.5.5", python = "< 3.8" } hvac = "^1.1.1" aws-requests-auth = "^0.4.3" datadog-lambda = "^4.77.0" @@ -89,7 +99,7 @@ tracer = ["aws-xray-sdk"] all = ["pydantic", "aws-xray-sdk", "fastjsonschema", "aws-encryption-sdk"] # allow customers to run code locally without emulators (SAM CLI, etc.) aws-sdk = ["boto3"] -datadog=["datadog-lambda"] +datadog = ["datadog-lambda"] [tool.poetry.group.dev.dependencies] cfn-lint = "0.79.9" @@ -99,10 +109,16 @@ httpx = ">=0.23.3,<0.25.0" sentry-sdk = "^1.22.2" ruff = ">=0.0.272,<0.0.288" retry2 = "^0.9.5" +pytest-socket = "^0.6.0" [tool.coverage.run] source = ["aws_lambda_powertools"] -omit = ["tests/*", "aws_lambda_powertools/exceptions/*", "aws_lambda_powertools/utilities/parser/types.py", "aws_lambda_powertools/utilities/jmespath_utils/envelopes.py"] +omit = [ + "tests/*", + "aws_lambda_powertools/exceptions/*", + "aws_lambda_powertools/utilities/parser/types.py", + "aws_lambda_powertools/utilities/jmespath_utils/envelopes.py", +] branch = true [tool.coverage.html] @@ -112,26 +128,26 @@ title = "Powertools for AWS Lambda (Python) Test Coverage" [tool.coverage.report] fail_under = 90 exclude_lines = [ - # Have to re-enable the standard pragma - "pragma: no cover", + # Have to re-enable the standard pragma + "pragma: no cover", - # Don't complain about missing debug-only code: - "def __repr__", - "if self.debug", + # Don't complain about missing debug-only code: + "def __repr__", + "if self.debug", - # Don't complain if tests don't hit defensive assertion code: - "raise AssertionError", - "raise NotImplementedError", + # Don't complain if tests don't hit defensive assertion code: + "raise AssertionError", + "raise NotImplementedError", - # Don't complain if non-runnable code isn't run: - "if 0:", - "if __name__ == .__main__.:", + # Don't complain if non-runnable code isn't run: + "if 0:", + "if __name__ == .__main__.:", - # Ignore runtime type checking - "if TYPE_CHECKING:", + # Ignore runtime type checking + "if TYPE_CHECKING:", - # Ignore type function overload - "@overload", + # Ignore type function overload + "@overload", ] [tool.isort] @@ -164,16 +180,16 @@ minversion = "6.0" addopts = "-ra -vv" testpaths = "./tests" markers = [ - "perf: marks perf tests to be deselected (deselect with '-m \"not perf\"')", + "perf: marks perf tests to be deselected (deselect with '-m \"not perf\"')", ] # MAINTENANCE: Remove these lines when drop support to Pydantic v1 -filterwarnings=[ +filterwarnings = [ "ignore:.*The `parse_obj` method is deprecated*:DeprecationWarning", "ignore:.*The `parse_raw` method is deprecated*:DeprecationWarning", "ignore:.*load_str_bytes is deprecated*:DeprecationWarning", "ignore:.*The `dict` method is deprecated; use `model_dump` instead*:DeprecationWarning", - "ignore:.*Pydantic V1 style `@validator` validators are deprecated*:DeprecationWarning" + "ignore:.*Pydantic V1 style `@validator` validators are deprecated*:DeprecationWarning", ] [build-system] diff --git a/tests/functional/data_masking/conftest.py b/tests/functional/data_masking/conftest.py new file mode 100644 index 00000000000..6127858d6b3 --- /dev/null +++ b/tests/functional/data_masking/conftest.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from typing import Tuple + +from pytest_socket import disable_socket + + +def pytest_runtest_setup(): + """Disable Unix and TCP sockets for Data masking tests""" + disable_socket() + + +class FakeEncryptionClient: + ENCRYPTION_HEADER = "test" + + def encrypt(self, source: bytes | str, **kwargs) -> Tuple[bytes, str]: + if isinstance(source, str): + return source.encode(), self.ENCRYPTION_HEADER + + return source, self.ENCRYPTION_HEADER + + def decrypt(self, source: bytes, **kwargs) -> Tuple[bytes, str]: + return source, "dummy_decryption_header" diff --git a/tests/functional/data_masking/test_aws_encryption_sdk.py b/tests/functional/data_masking/test_aws_encryption_sdk.py index ed2a356f2f1..52d6b90654f 100644 --- a/tests/functional/data_masking/test_aws_encryption_sdk.py +++ b/tests/functional/data_masking/test_aws_encryption_sdk.py @@ -1,15 +1,32 @@ +from __future__ import annotations + import pytest from aws_lambda_powertools.utilities.data_masking.base import DataMasking -from aws_lambda_powertools.utilities.data_masking.providers.aws_encryption_sdk import AwsEncryptionSdkProvider -from tests.unit.data_masking.setup import * - -AWS_SDK_KEY = "arn:aws:kms:us-west-2:683517028648:key/269301eb-81eb-4067-ac72-98e8e49bf2b3" +from aws_lambda_powertools.utilities.data_masking.providers.aws_encryption_sdk import ( + AwsEncryptionSdkProvider, +) +from tests.functional.data_masking.conftest import FakeEncryptionClient +from tests.unit.data_masking.setup import ( + aws_encrypted_with_fields, + data_types, + data_types_and_masks, + dict_fields, + dictionaries, + fields_to_mask, + json_blob, + json_dict, + masked_with_fields, + python_dict, +) @pytest.fixture -def data_masker(): - return DataMasking(provider=AwsEncryptionSdkProvider(keys=[AWS_SDK_KEY])) +def data_masker() -> DataMasking: + """DataMasking using AWS Encryption SDK Provider with a fake client""" + fake_client = FakeEncryptionClient() + provider = AwsEncryptionSdkProvider(keys=["dummy"], client=fake_client) + return DataMasking(provider=provider) @pytest.mark.parametrize("value, value_masked", data_types_and_masks) @@ -36,7 +53,7 @@ def test_mask_with_fields(data_masker): @pytest.mark.parametrize("value", data_types) -def test_encrypt_decrypt(value, data_masker): +def test_encrypt_decrypt(value, data_masker: DataMasking): # GIVEN an instantiation of DataMasking with the AWS encryption provider # WHEN encrypting and then decrypting the encrypted data @@ -62,7 +79,4 @@ def test_encrypt_decrypt_with_fields(value, fields, data_masker): print("json blob!!!!") assert decrypted_data == value else: - print("json_blob_fields!!!!") - assert decrypted_data == str(value) - print("decrypted_data:", decrypted_data) - print("aws_encrypted_with_fields:", aws_encrypted_with_fields) + assert decrypted_data == aws_encrypted_with_fields diff --git a/tests/performance/test_data_masking.py b/tests/performance/test_data_masking.py index 33ebe42733f..9dcf041b81f 100644 --- a/tests/performance/test_data_masking.py +++ b/tests/performance/test_data_masking.py @@ -4,9 +4,6 @@ import pytest from aws_lambda_powertools.utilities.data_masking.base import DataMasking -from aws_lambda_powertools.utilities.data_masking.providers.itsdangerous import ( - ItsDangerousProvider, -) DATA_MASKING_PACKAGE = "aws_lambda_powertools.utilities.data_masking" DATA_MASKING_INIT_SLA: float = 0.002 @@ -58,16 +55,15 @@ def test_data_masking_init(benchmark): pytest.fail(f"High level imports should be below {DATA_MASKING_INIT_SLA}s: {stat}") -def encrypt_json_blob(): - data_masker = DataMasking(provider=ItsDangerousProvider("mykey")) - encrypted = data_masker.encrypt(json_blob, json_blob_fields) - data_masker.decrypt(encrypted, json_blob_fields) +def mask_json_blob(): + data_masker = DataMasking() + data_masker.mask(json_blob, json_blob_fields) @pytest.mark.perf @pytest.mark.benchmark(group="core", disable_gc=True, warmup=False) def test_data_masking_encrypt_with_json_blob(benchmark): - benchmark.pedantic(encrypt_json_blob) + benchmark.pedantic(mask_json_blob) stat = benchmark.stats.stats.max if stat > DATA_MASKING_NESTED_ENCRYPT_SLA: pytest.fail(f"High level imports should be below {DATA_MASKING_NESTED_ENCRYPT_SLA}s: {stat}")