From 4f9ec68c34a51496f9ca2d86dfb9be2254dddc0f Mon Sep 17 00:00:00 2001 From: bartes Date: Thu, 10 Mar 2022 11:47:27 +0100 Subject: [PATCH 01/15] removed validations --- castle/commands/filter.py | 2 -- castle/commands/log.py | 2 -- castle/commands/risk.py | 2 -- 3 files changed, 6 deletions(-) diff --git a/castle/commands/filter.py b/castle/commands/filter.py index 9717eeb..4e7c03b 100644 --- a/castle/commands/filter.py +++ b/castle/commands/filter.py @@ -2,7 +2,6 @@ from castle.utils.timestamp import UtilsTimestamp as generate_timestamp from castle.context.merge import ContextMerge from castle.context.sanitize import ContextSanitize -from castle.validators.present import ValidatorsPresent class CommandsFilter(object): @@ -10,7 +9,6 @@ def __init__(self, context): self.context = context def call(self, options): - ValidatorsPresent.call(options, 'event') context = ContextMerge.call(self.context, options.get('context')) context = ContextSanitize.call(context) if context: diff --git a/castle/commands/log.py b/castle/commands/log.py index 05df3ab..a8bdb61 100644 --- a/castle/commands/log.py +++ b/castle/commands/log.py @@ -2,7 +2,6 @@ from castle.utils.timestamp import UtilsTimestamp as generate_timestamp from castle.context.merge import ContextMerge from castle.context.sanitize import ContextSanitize -from castle.validators.present import ValidatorsPresent class CommandsLog(object): @@ -10,7 +9,6 @@ def __init__(self, context): self.context = context def call(self, options): - ValidatorsPresent.call(options, 'event') context = ContextMerge.call(self.context, options.get('context')) context = ContextSanitize.call(context) if context: diff --git a/castle/commands/risk.py b/castle/commands/risk.py index 85a1238..6e0601a 100644 --- a/castle/commands/risk.py +++ b/castle/commands/risk.py @@ -2,7 +2,6 @@ from castle.utils.timestamp import UtilsTimestamp as generate_timestamp from castle.context.merge import ContextMerge from castle.context.sanitize import ContextSanitize -from castle.validators.present import ValidatorsPresent class CommandsRisk(object): @@ -10,7 +9,6 @@ def __init__(self, context): self.context = context def call(self, options): - ValidatorsPresent.call(options, 'event') context = ContextMerge.call(self.context, options.get('context')) context = ContextSanitize.call(context) if context: From 656aee2ca4a4d1c91815931a7cfde1d7dbc8ab3f Mon Sep 17 00:00:00 2001 From: bartes Date: Thu, 10 Mar 2022 12:09:06 +0100 Subject: [PATCH 02/15] added invalid request token error --- castle/core/process_response.py | 20 +++++++++++++++++--- castle/errors.py | 4 ++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/castle/core/process_response.py b/castle/core/process_response.py index 899beb1..4a4432d 100644 --- a/castle/core/process_response.py +++ b/castle/core/process_response.py @@ -1,5 +1,7 @@ +from json import JSONDecodeError from castle.errors import BadRequestError, UnauthorizedError, ForbiddenError, NotFoundError, \ - UserUnauthorizedError, InvalidParametersError, APIError, InternalServerError + UserUnauthorizedError, InvalidParametersError, APIError, InternalServerError, \ + InvalidRequestTokenError from castle.logger import Logger RESPONSE_ERRORS = { @@ -7,8 +9,7 @@ 401: UnauthorizedError, 403: ForbiddenError, 404: NotFoundError, - 419: UserUnauthorizedError, - 422: InvalidParametersError + 419: UserUnauthorizedError } @@ -32,5 +33,18 @@ def verify(self): if self.response.status_code >= 500 and self.response.status_code <= 599: raise InternalServerError + + if self.response.status_code == 422: + # attempt to unpack the error type from the response body + try: + body = self.response.json() + if isinstance(body, dict) and 'type' in body: + if body['type'] == 'invalid_request_token': + raise InvalidRequestTokenError(body['message']) + else: + raise InvalidParametersError(body['message']) + except JSONDecodeError: + raise InvalidParametersError(self.response.text) + error = RESPONSE_ERRORS.get(self.response.status_code, APIError) raise error(self.response.text) diff --git a/castle/errors.py b/castle/errors.py index 2991059..62646b6 100644 --- a/castle/errors.py +++ b/castle/errors.py @@ -27,6 +27,10 @@ class InvalidParametersError(APIError): pass +class InvalidRequestTokenError(APIError): + pass + + class BadRequestError(APIError): pass From 46bb3cdc4ae418b1f5dd241380ddedab43950528 Mon Sep 17 00:00:00 2001 From: bartes Date: Thu, 10 Mar 2022 14:04:42 +0100 Subject: [PATCH 03/15] fixed specs --- castle/test/__init__.py | 3 +++ castle/test/commands/filter_test.py | 36 ++++++++--------------------- castle/test/commands/log_test.py | 36 ++++++++--------------------- castle/test/commands/risk_test.py | 36 ++++++++--------------------- 4 files changed, 30 insertions(+), 81 deletions(-) diff --git a/castle/test/__init__.py b/castle/test/__init__.py index df17350..0de82f1 100644 --- a/castle/test/__init__.py +++ b/castle/test/__init__.py @@ -16,9 +16,12 @@ 'castle.test.commands.approve_device_test', 'castle.test.commands.authenticate_test', 'castle.test.commands.end_impersonation_test', + 'castle.test.commands.filter_test', 'castle.test.commands.get_device_test', 'castle.test.commands.get_devices_for_user_test', + 'castle.test.commands.log_test', 'castle.test.commands.report_device_test', + 'castle.test.commands.risk_test', 'castle.test.commands.start_impersonation_test', 'castle.test.commands.track_test', 'castle.test.configuration_test', diff --git a/castle/test/commands/filter_test.py b/castle/test/commands/filter_test.py index 3912068..6236c49 100644 --- a/castle/test/commands/filter_test.py +++ b/castle/test/commands/filter_test.py @@ -1,8 +1,7 @@ from castle.test import mock, unittest from castle.command import Command from castle.commands.filter import CommandsFilter -from castle.exceptions import InvalidParametersError -from castle.utils import clone +from castle.utils.clone import UtilsClone def default_options(): @@ -38,7 +37,7 @@ class CommandsFilterTestCase(unittest.TestCase): def setUp(self): # patch timestamp to return a known value timestamp_patcher = mock.patch( - 'castle.commands.filter.timestamp') + 'castle.commands.filter.generate_timestamp.call') self.mock_timestamp = timestamp_patcher.start() self.mock_timestamp.return_value = mock.sentinel.timestamp self.addCleanup(timestamp_patcher.stop) @@ -48,36 +47,19 @@ def test_init(self): obj = CommandsFilter(context) self.assertEqual(obj.context, context) - def test_build(self): + def test_call(self): context = {'test': '1'} options = default_options_plus(context={'spam': True}) # expect the original context to have been merged with the context specified in the options - expected_data = clone(options) + expected_data = UtilsClone.call(options) expected_data.update(context={'test': '1', 'spam': True}) expected = default_command_with_data(**expected_data) self.assertEqual(CommandsFilter( - context).build(options), expected) + context).call(options), expected) - def test_build_no_event(self): - context = {} - options = default_options() - options.pop('event') - - with self.assertRaises(InvalidParametersError): - CommandsFilter(context).build(options) - - def test_build_no_user_id(self): - context = {} - options = default_options() - options.pop('user_id') - - expected = default_command_with_data(**options) - - self.assertEqual(CommandsFilter(context).build(options), expected) - - def test_build_properties_allowed(self): + def test_call_properties_allowed(self): context = {} options = default_options_plus(properties={'test': '1'}) options.update({'context': context}) @@ -85,9 +67,9 @@ def test_build_properties_allowed(self): expected = default_command_with_data(**options) self.assertEqual(CommandsFilter( - context).build(options), expected) + context).call(options), expected) - def test_build_user_traits_allowed(self): + def test_call_user_traits_allowed(self): context = {} options = default_options_plus(user_traits={'email': 'a@b.com'}) options.update({'context': context}) @@ -95,4 +77,4 @@ def test_build_user_traits_allowed(self): expected = default_command_with_data(**options) self.assertEqual(CommandsFilter( - context).build(options), expected) + context).call(options), expected) diff --git a/castle/test/commands/log_test.py b/castle/test/commands/log_test.py index ec1f518..8965d00 100644 --- a/castle/test/commands/log_test.py +++ b/castle/test/commands/log_test.py @@ -1,8 +1,7 @@ from castle.test import mock, unittest from castle.command import Command from castle.commands.log import CommandsLog -from castle.exceptions import InvalidParametersError -from castle.utils import clone +from castle.utils.clone import UtilsClone def default_options(): @@ -38,7 +37,7 @@ class CommandsLogTestCase(unittest.TestCase): def setUp(self): # patch timestamp to return a known value timestamp_patcher = mock.patch( - 'castle.commands.log.timestamp') + 'castle.commands.log.generate_timestamp.call') self.mock_timestamp = timestamp_patcher.start() self.mock_timestamp.return_value = mock.sentinel.timestamp self.addCleanup(timestamp_patcher.stop) @@ -48,36 +47,19 @@ def test_init(self): obj = CommandsLog(context) self.assertEqual(obj.context, context) - def test_build(self): + def test_call(self): context = {'test': '1'} options = default_options_plus(context={'spam': True}) # expect the original context to have been merged with the context specified in the options - expected_data = clone(options) + expected_data = UtilsClone.call(options) expected_data.update(context={'test': '1', 'spam': True}) expected = default_command_with_data(**expected_data) self.assertEqual(CommandsLog( - context).build(options), expected) + context).call(options), expected) - def test_build_no_event(self): - context = {} - options = default_options() - options.pop('event') - - with self.assertRaises(InvalidParametersError): - CommandsLog(context).build(options) - - def test_build_no_user_id(self): - context = {} - options = default_options() - options.pop('user_id') - - expected = default_command_with_data(**options) - - self.assertEqual(CommandsLog(context).build(options), expected) - - def test_build_properties_allowed(self): + def test_call_properties_allowed(self): context = {} options = default_options_plus(properties={'test': '1'}) options.update({'context': context}) @@ -85,9 +67,9 @@ def test_build_properties_allowed(self): expected = default_command_with_data(**options) self.assertEqual(CommandsLog( - context).build(options), expected) + context).call(options), expected) - def test_build_user_traits_allowed(self): + def test_call_user_traits_allowed(self): context = {} options = default_options_plus(user_traits={'email': 'a@b.com'}) options.update({'context': context}) @@ -95,4 +77,4 @@ def test_build_user_traits_allowed(self): expected = default_command_with_data(**options) self.assertEqual(CommandsLog( - context).build(options), expected) + context).call(options), expected) diff --git a/castle/test/commands/risk_test.py b/castle/test/commands/risk_test.py index 7a3c67d..42483bc 100644 --- a/castle/test/commands/risk_test.py +++ b/castle/test/commands/risk_test.py @@ -1,8 +1,7 @@ from castle.test import mock, unittest from castle.command import Command from castle.commands.risk import CommandsRisk -from castle.exceptions import InvalidParametersError -from castle.utils import clone +from castle.utils.clone import UtilsClone def default_options(): @@ -38,7 +37,7 @@ class CommandsRiskTestCase(unittest.TestCase): def setUp(self): # patch timestamp to return a known value timestamp_patcher = mock.patch( - 'castle.commands.risk.timestamp') + 'castle.commands.risk.generate_timestamp.call') self.mock_timestamp = timestamp_patcher.start() self.mock_timestamp.return_value = mock.sentinel.timestamp self.addCleanup(timestamp_patcher.stop) @@ -48,36 +47,19 @@ def test_init(self): obj = CommandsRisk(context) self.assertEqual(obj.context, context) - def test_build(self): + def test_call(self): context = {'test': '1'} options = default_options_plus(context={'spam': True}) # expect the original context to have been merged with the context specified in the options - expected_data = clone(options) + expected_data = UtilsClone.call(options) expected_data.update(context={'test': '1', 'spam': True}) expected = default_command_with_data(**expected_data) self.assertEqual(CommandsRisk( - context).build(options), expected) + context).call(options), expected) - def test_build_no_event(self): - context = {} - options = default_options() - options.pop('event') - - with self.assertRaises(InvalidParametersError): - CommandsRisk(context).build(options) - - def test_build_no_user_id(self): - context = {} - options = default_options() - options.pop('user_id') - - expected = default_command_with_data(**options) - - self.assertEqual(CommandsRisk(context).build(options), expected) - - def test_build_properties_allowed(self): + def test_call_properties_allowed(self): context = {} options = default_options_plus(properties={'test': '1'}) options.update({'context': context}) @@ -85,9 +67,9 @@ def test_build_properties_allowed(self): expected = default_command_with_data(**options) self.assertEqual(CommandsRisk( - context).build(options), expected) + context).call(options), expected) - def test_build_user_traits_allowed(self): + def test_call_user_traits_allowed(self): context = {} options = default_options_plus(user_traits={'email': 'a@b.com'}) options.update({'context': context}) @@ -95,4 +77,4 @@ def test_build_user_traits_allowed(self): expected = default_command_with_data(**options) self.assertEqual(CommandsRisk( - context).build(options), expected) + context).call(options), expected) From ccfd68db64a9cfe55e019c3837811de3489030fc Mon Sep 17 00:00:00 2001 From: bartes Date: Thu, 10 Mar 2022 14:05:41 +0100 Subject: [PATCH 04/15] linter --- castle/core/process_response.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/castle/core/process_response.py b/castle/core/process_response.py index 4a4432d..0f3b02e 100644 --- a/castle/core/process_response.py +++ b/castle/core/process_response.py @@ -33,16 +33,15 @@ def verify(self): if self.response.status_code >= 500 and self.response.status_code <= 599: raise InternalServerError - if self.response.status_code == 422: # attempt to unpack the error type from the response body try: body = self.response.json() if isinstance(body, dict) and 'type' in body: if body['type'] == 'invalid_request_token': - raise InvalidRequestTokenError(body['message']) + raise InvalidRequestTokenError(body['message']) else: - raise InvalidParametersError(body['message']) + raise InvalidParametersError(body['message']) except JSONDecodeError: raise InvalidParametersError(self.response.text) From 6168ed14f625d9947504bb8cad472986b9e3e2de Mon Sep 17 00:00:00 2001 From: bartes Date: Thu, 10 Mar 2022 14:08:09 +0100 Subject: [PATCH 05/15] linter --- castle/ips/extract.py | 2 +- castle/test/api/approve_device_test.py | 2 +- castle/test/api/get_device_test.py | 2 +- castle/test/api/get_devices_for_user_test.py | 2 +- castle/test/api/report_device_test.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/castle/ips/extract.py b/castle/ips/extract.py index 0df38da..fb78384 100644 --- a/castle/ips/extract.py +++ b/castle/ips/extract.py @@ -57,6 +57,6 @@ def _ips_from(self, header): def _limit_proxy_depth(self, ips, ip_header): if ip_header in DEPTH_RELATED: - ips = ips[:len(ips)-self.trusted_proxy_depth] + ips = ips[:len(ips) - self.trusted_proxy_depth] return ips diff --git a/castle/test/api/approve_device_test.py b/castle/test/api/approve_device_test.py index 6bf15b2..de69e7d 100644 --- a/castle/test/api/approve_device_test.py +++ b/castle/test/api/approve_device_test.py @@ -16,7 +16,7 @@ def tearDown(self): @responses.activate def test_call(self): # pylint: disable=line-too-long - response_text = "{\"token\":\"token\",\"created_at\":\"2020-08-13T11:26:47.401Z\",\"last_seen_at\":\"2020-10-18T18:37:22.855Z\",\"user_id\":\"4\",\"approved_at\":\"2020-11-18T12:48:41.112Z\",\"escalated_at\":null,\"mitigated_at\":null,\"context\":{\"ip\":\"127.0.0.1\",\"location\":{\"country_code\":\"PL\",\"country\":\"Poland\"},\"user_agent\":{\"raw\":\"Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/86.0.4240.75 Safari\/537.36\",\"browser\":\"Chrome\",\"version\":\"86.0.4240\",\"os\":\"Mac OS X 10.15.6\",\"mobile\":false,\"platform\":\"Mac OS X\",\"device\":\"Mac\",\"family\":\"Chrome\"},\"properties\":{},\"type\":\"desktop\"},\"is_current_device\":false}" + response_text = "{\"token\":\"token\",\"created_at\":\"2020-08-13T11:26:47.401Z\",\"last_seen_at\":\"2020-10-18T18:37:22.855Z\",\"user_id\":\"4\",\"approved_at\":\"2020-11-18T12:48:41.112Z\",\"escalated_at\":null,\"mitigated_at\":null,\"context\":{\"ip\":\"127.0.0.1\",\"location\":{\"country_code\":\"PL\",\"country\":\"Poland\"},\"user_agent\":{\"raw\":\"Mozilla\\/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit\\/537.36 (KHTML, like Gecko) Chrome\\/86.0.4240.75 Safari\\/537.36\",\"browser\":\"Chrome\",\"version\":\"86.0.4240\",\"os\":\"Mac OS X 10.15.6\",\"mobile\":false,\"platform\":\"Mac OS X\",\"device\":\"Mac\",\"family\":\"Chrome\"},\"properties\":{},\"type\":\"desktop\"},\"is_current_device\":false}" responses.add( responses.PUT, 'https://api.castle.io/v1/devices/1234/approve', diff --git a/castle/test/api/get_device_test.py b/castle/test/api/get_device_test.py index c6fa4ee..9eaaf8e 100644 --- a/castle/test/api/get_device_test.py +++ b/castle/test/api/get_device_test.py @@ -16,7 +16,7 @@ def tearDown(self): @responses.activate def test_call(self): # pylint: disable=line-too-long - response_text = "{\"token\":\"token\",\"created_at\":\"2020-08-13T11:26:47.401Z\",\"last_seen_at\":\"2020-10-18T18:37:22.855Z\",\"user_id\":\"4\",\"approved_at\":null,\"escalated_at\":null,\"mitigated_at\":null,\"context\":{\"ip\":\"127.0.0.1\",\"location\":{\"country_code\":\"PL\",\"country\":\"Poland\"},\"user_agent\":{\"raw\":\"Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/86.0.4240.75 Safari\/537.36\",\"browser\":\"Chrome\",\"version\":\"86.0.4240\",\"os\":\"Mac OS X 10.15.6\",\"mobile\":false,\"platform\":\"Mac OS X\",\"device\":\"Mac\",\"family\":\"Chrome\"},\"properties\":{},\"type\":\"desktop\"},\"is_current_device\":false}" + response_text = "{\"token\":\"token\",\"created_at\":\"2020-08-13T11:26:47.401Z\",\"last_seen_at\":\"2020-10-18T18:37:22.855Z\",\"user_id\":\"4\",\"approved_at\":null,\"escalated_at\":null,\"mitigated_at\":null,\"context\":{\"ip\":\"127.0.0.1\",\"location\":{\"country_code\":\"PL\",\"country\":\"Poland\"},\"user_agent\":{\"raw\":\"Mozilla\\/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit\\/537.36 (KHTML, like Gecko) Chrome\\/86.0.4240.75 Safari\\/537.36\",\"browser\":\"Chrome\",\"version\":\"86.0.4240\",\"os\":\"Mac OS X 10.15.6\",\"mobile\":false,\"platform\":\"Mac OS X\",\"device\":\"Mac\",\"family\":\"Chrome\"},\"properties\":{},\"type\":\"desktop\"},\"is_current_device\":false}" responses.add( responses.GET, 'https://api.castle.io/v1/devices/1234', diff --git a/castle/test/api/get_devices_for_user_test.py b/castle/test/api/get_devices_for_user_test.py index f1dec5b..03d5029 100644 --- a/castle/test/api/get_devices_for_user_test.py +++ b/castle/test/api/get_devices_for_user_test.py @@ -16,7 +16,7 @@ def tearDown(self): @responses.activate def test_call(self): # pylint: disable=line-too-long - response_text = "{\"total_count\":2,\"data\":[{\"token\":\"token\",\"created_at\":\"2020-08-01T18:55:45.352Z\",\"last_seen_at\":\"2020-10-18T21:11:57.476Z\",\"user_id\":\"4\",\"approved_at\":\"2020-08-13T09:55:19.286Z\",\"escalated_at\":null,\"mitigated_at\":null,\"context\":{\"ip\":\"127.0.0.1\",\"location\":{\"country_code\":\"PL\",\"country\":\"Poland\"},\"user_agent\":{\"raw\":\"Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/86.0.4240.75 Safari\/537.36\",\"browser\":\"Chrome\",\"version\":\"86.0.4240\",\"os\":\"Mac OS X 10.15.6\",\"mobile\":false,\"platform\":\"Mac OS X\",\"device\":\"Mac\",\"family\":\"Chrome\"},\"properties\":{},\"type\":\"desktop\"},\"is_current_device\":false},{\"token\":\"token2\",\"created_at\":\"2020-08-13T11:26:47.401Z\",\"last_seen_at\":\"2020-10-18T18:37:22.855Z\",\"user_id\":\"4\",\"approved_at\":null,\"escalated_at\":null,\"mitigated_at\":null,\"context\":{\"ip\":\"127.0.0.1\",\"location\":{\"country_code\":\"PL\",\"country\":\"Poland\"},\"user_agent\":{\"raw\":\"Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/86.0.4240.75 Safari\/537.36\",\"browser\":\"Chrome\",\"version\":\"86.0.4240\",\"os\":\"Mac OS X 10.15.6\",\"mobile\":false,\"platform\":\"Mac OS X\",\"device\":\"Mac\",\"family\":\"Chrome\"},\"properties\":{},\"type\":\"desktop\"},\"is_current_device\":false}]}" + response_text = "{\"total_count\":2,\"data\":[{\"token\":\"token\",\"created_at\":\"2020-08-01T18:55:45.352Z\",\"last_seen_at\":\"2020-10-18T21:11:57.476Z\",\"user_id\":\"4\",\"approved_at\":\"2020-08-13T09:55:19.286Z\",\"escalated_at\":null,\"mitigated_at\":null,\"context\":{\"ip\":\"127.0.0.1\",\"location\":{\"country_code\":\"PL\",\"country\":\"Poland\"},\"user_agent\":{\"raw\":\"Mozilla\\/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit\\/537.36 (KHTML, like Gecko) Chrome\\/86.0.4240.75 Safari\\/537.36\",\"browser\":\"Chrome\",\"version\":\"86.0.4240\",\"os\":\"Mac OS X 10.15.6\",\"mobile\":false,\"platform\":\"Mac OS X\",\"device\":\"Mac\",\"family\":\"Chrome\"},\"properties\":{},\"type\":\"desktop\"},\"is_current_device\":false},{\"token\":\"token2\",\"created_at\":\"2020-08-13T11:26:47.401Z\",\"last_seen_at\":\"2020-10-18T18:37:22.855Z\",\"user_id\":\"4\",\"approved_at\":null,\"escalated_at\":null,\"mitigated_at\":null,\"context\":{\"ip\":\"127.0.0.1\",\"location\":{\"country_code\":\"PL\",\"country\":\"Poland\"},\"user_agent\":{\"raw\":\"Mozilla\\/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit\\/537.36 (KHTML, like Gecko) Chrome\\/86.0.4240.75 Safari\\/537.36\",\"browser\":\"Chrome\",\"version\":\"86.0.4240\",\"os\":\"Mac OS X 10.15.6\",\"mobile\":false,\"platform\":\"Mac OS X\",\"device\":\"Mac\",\"family\":\"Chrome\"},\"properties\":{},\"type\":\"desktop\"},\"is_current_device\":false}]}" responses.add( responses.GET, 'https://api.castle.io/v1/users/1234/devices', diff --git a/castle/test/api/report_device_test.py b/castle/test/api/report_device_test.py index 2dcffa3..8e96c91 100644 --- a/castle/test/api/report_device_test.py +++ b/castle/test/api/report_device_test.py @@ -16,7 +16,7 @@ def tearDown(self): @responses.activate def test_call(self): # pylint: disable=line-too-long - response_text = "{\"token\":\"token\",\"created_at\":\"2020-08-13T11:26:47.401Z\",\"last_seen_at\":\"2020-10-18T18:37:22.855Z\",\"user_id\":\"4\",\"approved_at\":\"2020-11-18T12:48:41.112Z\",\"escalated_at\":null,\"mitigated_at\":null,\"context\":{\"ip\":\"127.0.0.1\",\"location\":{\"country_code\":\"PL\",\"country\":\"Poland\"},\"user_agent\":{\"raw\":\"Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/86.0.4240.75 Safari\/537.36\",\"browser\":\"Chrome\",\"version\":\"86.0.4240\",\"os\":\"Mac OS X 10.15.6\",\"mobile\":false,\"platform\":\"Mac OS X\",\"device\":\"Mac\",\"family\":\"Chrome\"},\"properties\":{},\"type\":\"desktop\"},\"is_current_device\":false}" + response_text = "{\"token\":\"token\",\"created_at\":\"2020-08-13T11:26:47.401Z\",\"last_seen_at\":\"2020-10-18T18:37:22.855Z\",\"user_id\":\"4\",\"approved_at\":\"2020-11-18T12:48:41.112Z\",\"escalated_at\":null,\"mitigated_at\":null,\"context\":{\"ip\":\"127.0.0.1\",\"location\":{\"country_code\":\"PL\",\"country\":\"Poland\"},\"user_agent\":{\"raw\":\"Mozilla\\/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit\\/537.36 (KHTML, like Gecko) Chrome\\/86.0.4240.75 Safari\\/537.36\",\"browser\":\"Chrome\",\"version\":\"86.0.4240\",\"os\":\"Mac OS X 10.15.6\",\"mobile\":false,\"platform\":\"Mac OS X\",\"device\":\"Mac\",\"family\":\"Chrome\"},\"properties\":{},\"type\":\"desktop\"},\"is_current_device\":false}" responses.add( responses.PUT, 'https://api.castle.io/v1/devices/1234/report', From 1d554c271de4992a926a380aa4f477bab2775401 Mon Sep 17 00:00:00 2001 From: bartes Date: Thu, 10 Mar 2022 14:15:59 +0100 Subject: [PATCH 06/15] handled linters --- castle/core/process_response.py | 3 +-- pylintrc | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/castle/core/process_response.py b/castle/core/process_response.py index 0f3b02e..193db7e 100644 --- a/castle/core/process_response.py +++ b/castle/core/process_response.py @@ -40,8 +40,7 @@ def verify(self): if isinstance(body, dict) and 'type' in body: if body['type'] == 'invalid_request_token': raise InvalidRequestTokenError(body['message']) - else: - raise InvalidParametersError(body['message']) + raise InvalidParametersError(body['message']) except JSONDecodeError: raise InvalidParametersError(self.response.text) diff --git a/pylintrc b/pylintrc index 0c98018..eceeb25 100644 --- a/pylintrc +++ b/pylintrc @@ -8,4 +8,4 @@ class-rgx=[A-Z_][_a-zA-Z0-9]+$ ignored-modules = responses,requests [MESSAGES CONTROL] -disable=missing-docstring,too-many-instance-attributes,attribute-defined-outside-init,too-few-public-methods,dangerous-default-value,duplicate-code,bad-continuation,useless-object-inheritance,too-many-public-methods +disable=missing-docstring,too-many-instance-attributes,attribute-defined-outside-init,too-few-public-methods,dangerous-default-value,duplicate-code,bad-continuation,useless-object-inheritance,too-many-public-methods,R1735,C0209,W0707 From e75d9481cde140e1b2442e15f38326d38c8bf9e5 Mon Sep 17 00:00:00 2001 From: bartes Date: Thu, 10 Mar 2022 14:21:36 +0100 Subject: [PATCH 07/15] restored --- castle/test/api/approve_device_test.py | 2 +- castle/test/api/get_device_test.py | 2 +- castle/test/api/report_device_test.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/castle/test/api/approve_device_test.py b/castle/test/api/approve_device_test.py index de69e7d..6bf15b2 100644 --- a/castle/test/api/approve_device_test.py +++ b/castle/test/api/approve_device_test.py @@ -16,7 +16,7 @@ def tearDown(self): @responses.activate def test_call(self): # pylint: disable=line-too-long - response_text = "{\"token\":\"token\",\"created_at\":\"2020-08-13T11:26:47.401Z\",\"last_seen_at\":\"2020-10-18T18:37:22.855Z\",\"user_id\":\"4\",\"approved_at\":\"2020-11-18T12:48:41.112Z\",\"escalated_at\":null,\"mitigated_at\":null,\"context\":{\"ip\":\"127.0.0.1\",\"location\":{\"country_code\":\"PL\",\"country\":\"Poland\"},\"user_agent\":{\"raw\":\"Mozilla\\/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit\\/537.36 (KHTML, like Gecko) Chrome\\/86.0.4240.75 Safari\\/537.36\",\"browser\":\"Chrome\",\"version\":\"86.0.4240\",\"os\":\"Mac OS X 10.15.6\",\"mobile\":false,\"platform\":\"Mac OS X\",\"device\":\"Mac\",\"family\":\"Chrome\"},\"properties\":{},\"type\":\"desktop\"},\"is_current_device\":false}" + response_text = "{\"token\":\"token\",\"created_at\":\"2020-08-13T11:26:47.401Z\",\"last_seen_at\":\"2020-10-18T18:37:22.855Z\",\"user_id\":\"4\",\"approved_at\":\"2020-11-18T12:48:41.112Z\",\"escalated_at\":null,\"mitigated_at\":null,\"context\":{\"ip\":\"127.0.0.1\",\"location\":{\"country_code\":\"PL\",\"country\":\"Poland\"},\"user_agent\":{\"raw\":\"Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/86.0.4240.75 Safari\/537.36\",\"browser\":\"Chrome\",\"version\":\"86.0.4240\",\"os\":\"Mac OS X 10.15.6\",\"mobile\":false,\"platform\":\"Mac OS X\",\"device\":\"Mac\",\"family\":\"Chrome\"},\"properties\":{},\"type\":\"desktop\"},\"is_current_device\":false}" responses.add( responses.PUT, 'https://api.castle.io/v1/devices/1234/approve', diff --git a/castle/test/api/get_device_test.py b/castle/test/api/get_device_test.py index 9eaaf8e..c6fa4ee 100644 --- a/castle/test/api/get_device_test.py +++ b/castle/test/api/get_device_test.py @@ -16,7 +16,7 @@ def tearDown(self): @responses.activate def test_call(self): # pylint: disable=line-too-long - response_text = "{\"token\":\"token\",\"created_at\":\"2020-08-13T11:26:47.401Z\",\"last_seen_at\":\"2020-10-18T18:37:22.855Z\",\"user_id\":\"4\",\"approved_at\":null,\"escalated_at\":null,\"mitigated_at\":null,\"context\":{\"ip\":\"127.0.0.1\",\"location\":{\"country_code\":\"PL\",\"country\":\"Poland\"},\"user_agent\":{\"raw\":\"Mozilla\\/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit\\/537.36 (KHTML, like Gecko) Chrome\\/86.0.4240.75 Safari\\/537.36\",\"browser\":\"Chrome\",\"version\":\"86.0.4240\",\"os\":\"Mac OS X 10.15.6\",\"mobile\":false,\"platform\":\"Mac OS X\",\"device\":\"Mac\",\"family\":\"Chrome\"},\"properties\":{},\"type\":\"desktop\"},\"is_current_device\":false}" + response_text = "{\"token\":\"token\",\"created_at\":\"2020-08-13T11:26:47.401Z\",\"last_seen_at\":\"2020-10-18T18:37:22.855Z\",\"user_id\":\"4\",\"approved_at\":null,\"escalated_at\":null,\"mitigated_at\":null,\"context\":{\"ip\":\"127.0.0.1\",\"location\":{\"country_code\":\"PL\",\"country\":\"Poland\"},\"user_agent\":{\"raw\":\"Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/86.0.4240.75 Safari\/537.36\",\"browser\":\"Chrome\",\"version\":\"86.0.4240\",\"os\":\"Mac OS X 10.15.6\",\"mobile\":false,\"platform\":\"Mac OS X\",\"device\":\"Mac\",\"family\":\"Chrome\"},\"properties\":{},\"type\":\"desktop\"},\"is_current_device\":false}" responses.add( responses.GET, 'https://api.castle.io/v1/devices/1234', diff --git a/castle/test/api/report_device_test.py b/castle/test/api/report_device_test.py index 8e96c91..2dcffa3 100644 --- a/castle/test/api/report_device_test.py +++ b/castle/test/api/report_device_test.py @@ -16,7 +16,7 @@ def tearDown(self): @responses.activate def test_call(self): # pylint: disable=line-too-long - response_text = "{\"token\":\"token\",\"created_at\":\"2020-08-13T11:26:47.401Z\",\"last_seen_at\":\"2020-10-18T18:37:22.855Z\",\"user_id\":\"4\",\"approved_at\":\"2020-11-18T12:48:41.112Z\",\"escalated_at\":null,\"mitigated_at\":null,\"context\":{\"ip\":\"127.0.0.1\",\"location\":{\"country_code\":\"PL\",\"country\":\"Poland\"},\"user_agent\":{\"raw\":\"Mozilla\\/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit\\/537.36 (KHTML, like Gecko) Chrome\\/86.0.4240.75 Safari\\/537.36\",\"browser\":\"Chrome\",\"version\":\"86.0.4240\",\"os\":\"Mac OS X 10.15.6\",\"mobile\":false,\"platform\":\"Mac OS X\",\"device\":\"Mac\",\"family\":\"Chrome\"},\"properties\":{},\"type\":\"desktop\"},\"is_current_device\":false}" + response_text = "{\"token\":\"token\",\"created_at\":\"2020-08-13T11:26:47.401Z\",\"last_seen_at\":\"2020-10-18T18:37:22.855Z\",\"user_id\":\"4\",\"approved_at\":\"2020-11-18T12:48:41.112Z\",\"escalated_at\":null,\"mitigated_at\":null,\"context\":{\"ip\":\"127.0.0.1\",\"location\":{\"country_code\":\"PL\",\"country\":\"Poland\"},\"user_agent\":{\"raw\":\"Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/86.0.4240.75 Safari\/537.36\",\"browser\":\"Chrome\",\"version\":\"86.0.4240\",\"os\":\"Mac OS X 10.15.6\",\"mobile\":false,\"platform\":\"Mac OS X\",\"device\":\"Mac\",\"family\":\"Chrome\"},\"properties\":{},\"type\":\"desktop\"},\"is_current_device\":false}" responses.add( responses.PUT, 'https://api.castle.io/v1/devices/1234/report', From 03646b162eb5b7a98f6e49025c8de70c6f3aa279 Mon Sep 17 00:00:00 2001 From: bartes Date: Fri, 11 Mar 2022 15:16:22 +0100 Subject: [PATCH 08/15] updated python list --- .circleci/config.yml | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bd09816..90066b5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,19 +17,9 @@ aliases: jobs: lint-sdk: docker: - - image: circleci/python:3.9 + - image: circleci/python:3.10 <<: *with-requests <<: *lint - python-3_4: - docker: - - image: circleci/python:3.4 - <<: *with-requests - <<: *job-defaults - python-3_5: - docker: - - image: circleci/python:3.5 - <<: *with-requests - <<: *job-defaults python-3_6: docker: - image: circleci/python:3.6 @@ -50,7 +40,11 @@ jobs: - image: circleci/python:3.9 <<: *with-requests <<: *job-defaults - + python-3_10: + docker: + - image: circleci/python:3.10 + <<: *with-requests + <<: *job-defaults workflows: main: jobs: From 02c9681cbdfcb34924433f035f2e6b70abbdd36b Mon Sep 17 00:00:00 2001 From: bartes Date: Fri, 11 Mar 2022 15:33:03 +0100 Subject: [PATCH 09/15] 3.10 --- .circleci/config.yml | 3 +-- .python-version | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 90066b5..9e23bfc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -49,9 +49,8 @@ workflows: main: jobs: - lint-sdk - - python-3_4 - - python-3_5 - python-3_6 - python-3_7 - python-3_8 - python-3_9 + - python-3_10 diff --git a/.python-version b/.python-version index a5c4c76..7b59a5c 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.9.0 +3.10.2 From 583b1a7adb1e486cdb5c4661946feebca118879f Mon Sep 17 00:00:00 2001 From: bartes Date: Fri, 11 Mar 2022 15:33:12 +0100 Subject: [PATCH 10/15] spec --- castle/test/core/process_response_test.py | 11 ++++++++++- castle/test/utils/merge_test.py | 6 ++++++ castle/utils/merge.py | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/castle/test/core/process_response_test.py b/castle/test/core/process_response_test.py index 41375ff..2c8421e 100644 --- a/castle/test/core/process_response_test.py +++ b/castle/test/core/process_response_test.py @@ -4,7 +4,7 @@ from castle.test import unittest from castle.core.process_response import CoreProcessResponse from castle.errors import BadRequestError, UnauthorizedError, ForbiddenError, NotFoundError, \ - UserUnauthorizedError, InvalidParametersError, InternalServerError + UserUnauthorizedError, InvalidParametersError, InternalServerError, InvalidRequestTokenError def response(status_code=200, body=None): @@ -79,6 +79,15 @@ def test_verify_422(self): with self.assertRaises(InvalidParametersError): CoreProcessResponse(response(status_code=422)).verify() + def test_verify_422_record_invalid(self): + with self.assertRaises(InvalidParametersError): + CoreProcessResponse(response(status_code=422, body=b'{"type":"record_invalid","message":"validation failed"}')).verify() + + def test_verify_422_invalid_request_token(self): + with self.assertRaises(InvalidRequestTokenError): + CoreProcessResponse(response(status_code=422, body=b'{"type":"invalid_request_token","message":"token invalid"}')).verify() + + def test_verify_500(self): with self.assertRaises(InternalServerError): CoreProcessResponse(response(status_code=500)).verify() diff --git a/castle/test/utils/merge_test.py b/castle/test/utils/merge_test.py index c8d1416..8c0cd4b 100644 --- a/castle/test/utils/merge_test.py +++ b/castle/test/utils/merge_test.py @@ -55,6 +55,12 @@ def test_merge_when_no_extra(self): UtilsMerge.call(a, b) self.assertEqual(a, {'key': 'value'}) + def test_merge_when_no_base(self): + a = None + b = {'key': 'value'} + UtilsMerge.call(a, b) + self.assertEqual(a, None) + def test_merge_none_deletes_from_base(self): a = {'key': 'value', 'other': 'value'} b = {'other': None} diff --git a/castle/utils/merge.py b/castle/utils/merge.py index 6beec67..2e62eaa 100644 --- a/castle/utils/merge.py +++ b/castle/utils/merge.py @@ -9,7 +9,7 @@ def call(cls, base, extra): :param extra: The dictionary to merge into the base. Keys from this dictionary will take precedence. """ - if extra is None: + if extra is None or base is None: return for key, value in extra.items(): From 2f3a93a44a4659fe27c7f034446f535008a5d55b Mon Sep 17 00:00:00 2001 From: bartes Date: Fri, 11 Mar 2022 15:50:16 +0100 Subject: [PATCH 11/15] linter --- castle/test/core/process_response_test.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/castle/test/core/process_response_test.py b/castle/test/core/process_response_test.py index 2c8421e..d8b8614 100644 --- a/castle/test/core/process_response_test.py +++ b/castle/test/core/process_response_test.py @@ -81,12 +81,13 @@ def test_verify_422(self): def test_verify_422_record_invalid(self): with self.assertRaises(InvalidParametersError): - CoreProcessResponse(response(status_code=422, body=b'{"type":"record_invalid","message":"validation failed"}')).verify() + CoreProcessResponse( + response(status_code=422, body=b'{"type":"record_invalid","message":"validation failed"}')).verify() def test_verify_422_invalid_request_token(self): with self.assertRaises(InvalidRequestTokenError): - CoreProcessResponse(response(status_code=422, body=b'{"type":"invalid_request_token","message":"token invalid"}')).verify() - + CoreProcessResponse(response( + status_code=422, body=b'{"type":"invalid_request_token","message":"token invalid"}')).verify() def test_verify_500(self): with self.assertRaises(InternalServerError): From c62ab84a376bb881e8dde05a54d1b83f2364f0b6 Mon Sep 17 00:00:00 2001 From: bartes Date: Fri, 11 Mar 2022 16:11:18 +0100 Subject: [PATCH 12/15] fixed context merge --- castle/context/merge.py | 2 +- castle/test/context/merge_test.py | 7 +++++++ castle/test/utils/merge_test.py | 6 ------ castle/utils/merge.py | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/castle/context/merge.py b/castle/context/merge.py index 85595fe..98fc3ad 100644 --- a/castle/context/merge.py +++ b/castle/context/merge.py @@ -6,6 +6,6 @@ class ContextMerge(object): @staticmethod def call(initial_context, request_context): - source_copy = UtilsClone.call(initial_context) + source_copy = UtilsClone.call(initial_context or {}) UtilsMerge.call(source_copy, request_context) return source_copy diff --git a/castle/test/context/merge_test.py b/castle/test/context/merge_test.py index 024177d..7c575e8 100644 --- a/castle/test/context/merge_test.py +++ b/castle/test/context/merge_test.py @@ -11,3 +11,10 @@ def test_call(self): params, {'foo': {'foo': 'foo'}, 'to_remove': None}), {'foo': {'foo': 'foo', 'nonfoo': 'nonbar'}} ) + + def test_call_no_base(self): + params = {'foo': {'foo': 'bar', 'nonfoo': 'nonbar'}} + self.assertEqual( + ContextMerge.call(None, params), + {'foo': {'foo': 'bar', 'nonfoo': 'nonbar'}} + ) diff --git a/castle/test/utils/merge_test.py b/castle/test/utils/merge_test.py index 8c0cd4b..c8d1416 100644 --- a/castle/test/utils/merge_test.py +++ b/castle/test/utils/merge_test.py @@ -55,12 +55,6 @@ def test_merge_when_no_extra(self): UtilsMerge.call(a, b) self.assertEqual(a, {'key': 'value'}) - def test_merge_when_no_base(self): - a = None - b = {'key': 'value'} - UtilsMerge.call(a, b) - self.assertEqual(a, None) - def test_merge_none_deletes_from_base(self): a = {'key': 'value', 'other': 'value'} b = {'other': None} diff --git a/castle/utils/merge.py b/castle/utils/merge.py index 2e62eaa..6beec67 100644 --- a/castle/utils/merge.py +++ b/castle/utils/merge.py @@ -9,7 +9,7 @@ def call(cls, base, extra): :param extra: The dictionary to merge into the base. Keys from this dictionary will take precedence. """ - if extra is None or base is None: + if extra is None: return for key, value in extra.items(): From a01689af44868c58bee42c393a26b141479df057 Mon Sep 17 00:00:00 2001 From: bartes Date: Mon, 14 Mar 2022 11:10:33 +0100 Subject: [PATCH 13/15] restored ci for 3.5 --- .circleci/config.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9e23bfc..33fe235 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -20,6 +20,11 @@ jobs: - image: circleci/python:3.10 <<: *with-requests <<: *lint + python-3_5: + docker: + - image: circleci/python:3.5 + <<: *with-requests + <<: *job-defaults python-3_6: docker: - image: circleci/python:3.6 @@ -49,6 +54,7 @@ workflows: main: jobs: - lint-sdk + - python-3_5 - python-3_6 - python-3_7 - python-3_8 From ff92a9429eb8712bb23f7521ad191aa77aa0bfcf Mon Sep 17 00:00:00 2001 From: bartes Date: Mon, 14 Mar 2022 12:34:20 +0100 Subject: [PATCH 14/15] fixed from_request method --- castle/client.py | 3 ++- castle/context/merge.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/castle/client.py b/castle/client.py index 11eea88..e7dc93f 100644 --- a/castle/client.py +++ b/castle/client.py @@ -20,7 +20,8 @@ def from_request(cls, request, options=None): if options is None: options = {} - options.setdefault('context', ContextPrepare.call(request, options)) + options = options.copy() + options['context'] = ContextPrepare.call(request, options) return cls(options) @staticmethod diff --git a/castle/context/merge.py b/castle/context/merge.py index 98fc3ad..994b036 100644 --- a/castle/context/merge.py +++ b/castle/context/merge.py @@ -6,6 +6,8 @@ class ContextMerge(object): @staticmethod def call(initial_context, request_context): - source_copy = UtilsClone.call(initial_context or {}) + if initial_context is None: + initial_context = {} + source_copy = UtilsClone.call(initial_context) UtilsMerge.call(source_copy, request_context) return source_copy From 19bdd77560700eed0770ad983122c9fc790b3c2b Mon Sep 17 00:00:00 2001 From: bartes Date: Mon, 14 Mar 2022 12:47:49 +0100 Subject: [PATCH 15/15] removed X-Castle-Client-Id from allowlist --- castle/configuration.py | 1 - 1 file changed, 1 deletion(-) diff --git a/castle/configuration.py b/castle/configuration.py index 5206407..cea334d 100644 --- a/castle/configuration.py +++ b/castle/configuration.py @@ -25,7 +25,6 @@ "TE", "Upgrade-Insecure-Requests", "User-Agent", - "X-Castle-Client-Id", "X-Requested-With", ]