diff --git a/src/currencycloud/errors/api.py b/src/currencycloud/errors/api.py index 27635ae..80c047a 100644 --- a/src/currencycloud/errors/api.py +++ b/src/currencycloud/errors/api.py @@ -32,6 +32,14 @@ def extract_error_messages(errors): return error_messages +REDACTED_STRING = "********" +VALUES_TO_REDACT = ["api_key"] + + +def redact_values(params): + return {i: REDACTED_STRING if i in VALUES_TO_REDACT else params[i] for i in params.keys()} + + class ApiError(Exception): class ApiErrorMessage: def __init__(self, field, error): @@ -79,7 +87,7 @@ def __str__(self): error_details = { 'platform': self.platform, 'request': { - 'parameters': self.params, + 'parameters': redact_values(self.params), 'verb': str( self.verb), 'url': self.route, diff --git a/tests/integration/test_errors.py b/tests/integration/test_errors.py index 6207a7a..f213014 100644 --- a/tests/integration/test_errors.py +++ b/tests/integration/test_errors.py @@ -3,6 +3,10 @@ from currencycloud import Client, Config from currencycloud.errors import AuthenticationError, BadRequestError, ForbiddenError, NotFoundError, TooManyRequestsError, ApiError from json import JSONDecodeError +from requests.models import Response +import datetime +from currencycloud.errors.api import REDACTED_STRING + class TestError: def setup_method(self, method): @@ -33,7 +37,7 @@ def test_error_contains_full_details_for_api_error(self): expected_error_fields = [ "login_id: non-existent-login-id", - "api_key: " + api_key, + "api_key: '" + REDACTED_STRING +"'", "verb: post", "url: https://devapi.currencycloud.com/v2/authenticate/api", "status_code: 400", @@ -220,6 +224,7 @@ def test_error_is_handled_different_json_format_2(self): assert error_message.message == "Unhandled Error occurred. Check params for details" assert error_message.params + def test_error_is_handled_missing_params_in_error_message(self): with Betamax(self.client.config.session) as betamax: betamax.use_cassette("errors/is_handled_missing_params_in_error_message") @@ -236,4 +241,24 @@ def test_error_is_handled_missing_params_in_error_message(self): assert error_message.field == "base" assert error_message.code == "No Code" assert error_message.message == "No Message" - assert not error_message.params \ No newline at end of file + assert not error_message.params + + def test_error_parameter_redaction(self): + api_key = "IDoNotWantToSeeThis" + login_id = "test@currencycloud.com" + params = {"api_key": api_key, "login_id": login_id} + url = "https://devapi.currencycloud.com/v2/authenticate/api" + verb = "post" + response = Response() + response.code = "unauthorized" + response.error_type = "unauthorized" + response.status_code = 401 + response._content = b'{"error_code": "auth_failed","error_messages": {"username": [{"code": "invalid_supplied_credentials","message": "Authentication failed with the supplied credentials","params": {}}]}}' + response.headers["Date"] = datetime.datetime(2023, 3, 20, 0, 0) + response.headers["x-request-id"] = "06ac2168-8d8f-4a0c-8033-e81a22a2feb5" + + error = AuthenticationError(verb, url, params, response) + s = str(error) + assert api_key not in s + assert REDACTED_STRING in s +