From 7c2b53ca13be22612118087d488bfe9c2e2c719c Mon Sep 17 00:00:00 2001 From: marysieek Date: Mon, 9 Nov 2020 10:38:57 +0100 Subject: [PATCH 1/5] Add configurable logger to configuration --- castle/configuration.py | 51 ++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/castle/configuration.py b/castle/configuration.py index aa176e6..476623a 100644 --- a/castle/configuration.py +++ b/castle/configuration.py @@ -55,17 +55,29 @@ def __init__(self): self.trusted_proxies = [] self.trust_proxy_chain = False self.trusted_proxy_depth = None + self.logger = None def isValid(self): return self.api_secret and self.base_url.hostname @property - def api_secret(self): - return self.__api_secret + def request_timeout(self): + return self.__request_timeout - @api_secret.setter - def api_secret(self, value): - self.__api_secret = value + @request_timeout.setter + def request_timeout(self, value): + self.__request_timeout = value + + @property + def failover_strategy(self): + return self.__failover_strategy + + @failover_strategy.setter + def failover_strategy(self, value): + if value in FAILOVER_STRATEGIES: + self.__failover_strategy = value + else: + raise ConfigurationError @property def base_url(self): @@ -101,23 +113,12 @@ def denylisted(self, value): self.__denylisted = [] @property - def request_timeout(self): - return self.__request_timeout - - @request_timeout.setter - def request_timeout(self, value): - self.__request_timeout = value - - @property - def failover_strategy(self): - return self.__failover_strategy + def api_secret(self): + return self.__api_secret - @failover_strategy.setter - def failover_strategy(self, value): - if value in FAILOVER_STRATEGIES: - self.__failover_strategy = value - else: - raise ConfigurationError + @api_secret.setter + def api_secret(self, value): + self.__api_secret = value @property def ip_headers(self): @@ -163,6 +164,14 @@ def trusted_proxy_depth(self, value): else: raise ConfigurationError + @property + def logger(self): + return self.__logger + + @logger.setter + def logger(self, value): + self.__logger = value + # pylint: disable=invalid-name configuration = Configuration() From 5660fde32f81a1c066567821c98795c8af7050e4 Mon Sep 17 00:00:00 2001 From: marysieek Date: Mon, 9 Nov 2020 10:57:23 +0100 Subject: [PATCH 2/5] Add basic logger class --- castle/logger.py | 20 ++++++++++++++++++++ castle/utils/merge.py | 6 +++--- 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 castle/logger.py diff --git a/castle/logger.py b/castle/logger.py new file mode 100644 index 0000000..a6c2f87 --- /dev/null +++ b/castle/logger.py @@ -0,0 +1,20 @@ +from castle.configuration import configuration + + +class Logger(object): + + @staticmethod + def call(message, data=None): + """ + Log the message with optionally data using preconfigured logger + + :param message: The base logger message. + :param data: Additional data passed optionally. + """ + logger = configuration.logger + + if not logger: + return None + + msg = "[CASTLE] #{} #{}".format(message, data).strip() + return logger.info(msg) diff --git a/castle/utils/merge.py b/castle/utils/merge.py index 2cd0720..6beec67 100644 --- a/castle/utils/merge.py +++ b/castle/utils/merge.py @@ -1,7 +1,7 @@ class UtilsMerge(object): - @staticmethod - def call(base, extra): + @classmethod + def call(cls, base, extra): """ Deeply merge two dictionaries, overriding existing keys in the base. @@ -18,7 +18,7 @@ def call(base, extra): del base[key] # If the key represents a dict on both given dicts, merge the sub-dicts elif isinstance(base.get(key), dict) and isinstance(value, dict): - __class__.call(base[key], value) + cls.call(base[key], value) else: # Otherwise, set the key on the base to be the value of the extra. base[key] = value From 4b13d39c779cdfec57f465a2a8697f292f4cc6e3 Mon Sep 17 00:00:00 2001 From: marysieek Date: Mon, 9 Nov 2020 11:33:55 +0100 Subject: [PATCH 3/5] Update send_request.py --- castle/core/send_request.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/castle/core/send_request.py b/castle/core/send_request.py index bd46c30..ea337f8 100644 --- a/castle/core/send_request.py +++ b/castle/core/send_request.py @@ -1,5 +1,6 @@ import json from castle.configuration import configuration +from castle.logger import Logger from castle.session import Session HTTPS_SCHEME = 'https' @@ -12,14 +13,21 @@ def __init__(self, headers=None): self.session = Session() def build_query(self, method, path, params): + url = self.build_url(path) + request_data = { + "auth": ('', configuration.api_secret), + "timeout": configuration.request_timeout / 1000.0, + "headers": self.headers, + "verify": CoreSendRequest.verify(), + "data": None if params is None else json.dumps(params) + } + + Logger.call("#{}:".format(url), request_data.get("data")) + return self.session.get().request( method, - self.build_url(path), - auth=('', configuration.api_secret), - timeout=configuration.request_timeout / 1000.0, - headers=self.headers, - verify=CoreSendRequest.verify(), - data=None if params is None else json.dumps(params) + url, + **request_data ) def build_url(self, path): From 16f39390d0eb84b07d92a652b9b43049ea4d000f Mon Sep 17 00:00:00 2001 From: marysieek Date: Mon, 9 Nov 2020 12:25:51 +0100 Subject: [PATCH 4/5] Add logger config --- castle/core/send_request.py | 2 +- castle/logger.py | 4 ++-- castle/test/__init__.py | 1 + castle/test/logger_test.py | 21 +++++++++++++++++++++ 4 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 castle/test/logger_test.py diff --git a/castle/core/send_request.py b/castle/core/send_request.py index ea337f8..68cd60e 100644 --- a/castle/core/send_request.py +++ b/castle/core/send_request.py @@ -22,7 +22,7 @@ def build_query(self, method, path, params): "data": None if params is None else json.dumps(params) } - Logger.call("#{}:".format(url), request_data.get("data")) + Logger.call("{}:".format(url), request_data.get("data")) return self.session.get().request( method, diff --git a/castle/logger.py b/castle/logger.py index a6c2f87..0401e17 100644 --- a/castle/logger.py +++ b/castle/logger.py @@ -4,7 +4,7 @@ class Logger(object): @staticmethod - def call(message, data=None): + def call(message, data=""): """ Log the message with optionally data using preconfigured logger @@ -16,5 +16,5 @@ def call(message, data=None): if not logger: return None - msg = "[CASTLE] #{} #{}".format(message, data).strip() + msg = "[CASTLE] {} {}".format(message, data).strip() return logger.info(msg) diff --git a/castle/test/__init__.py b/castle/test/__init__.py index 945091e..87d3578 100644 --- a/castle/test/__init__.py +++ b/castle/test/__init__.py @@ -26,6 +26,7 @@ 'castle.test.headers.filter_test', 'castle.test.headers.format_test', 'castle.test.ip.extract_test', + 'castle.test.logger_test', 'castle.test.secure_mode_test', 'castle.test.session_test', 'castle.test.utils.clone_test', diff --git a/castle/test/logger_test.py b/castle/test/logger_test.py new file mode 100644 index 0000000..6ae5dcf --- /dev/null +++ b/castle/test/logger_test.py @@ -0,0 +1,21 @@ +from castle.test import unittest +from castle.logger import Logger +from castle.configuration import configuration + + +class TmpLogger(object): + + @staticmethod + def info(message): + return message + + +class LoggerTestCase(unittest.TestCase): + + def test_without_logger(self): + configuration.logger = None + self.assertEqual(Logger.call("Test"), None) + + def test_with_logger(self): + configuration.logger = TmpLogger + self.assertEqual(Logger.call("Test"), "[CASTLE] Test") From bcf9eb8b9fe46da08053996726107b52505c0b0a Mon Sep 17 00:00:00 2001 From: marysieek Date: Mon, 9 Nov 2020 13:27:14 +0100 Subject: [PATCH 5/5] Add logger to README.rst --- README.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.rst b/README.rst index 7fb8fa1..d368972 100644 --- a/README.rst +++ b/README.rst @@ -35,6 +35,9 @@ import and configure the library with your Castle API secret. # Base Castle API url # configuration.base_url = "https://api.castle.io/v1" + # Logger (need to respond to info method) - logs Castle API requests and responses + # configuration.logger = logging.getLogger() + # Allowlisted and Denylisted headers are case insensitive # and allow to use _ and - as a separator, http prefixes are removed # By default all headers are passed, but some are automatically scrubbed.