From 6f2176741d91acb157fbb808914191c222aca1e3 Mon Sep 17 00:00:00 2001 From: Alan Hamlett Date: Fri, 5 May 2017 17:09:17 -0700 Subject: [PATCH] Ability to disable SSL cert verification Adds a --no-ssl-verify argument. Adds a no_ssl_verify boolean config option. --- tests/samples/configs/has_everything.cfg | 1 + ...onfig_file_not_passed_in_command_line_args | 2 +- .../output/configs_test_missing_config_file | 2 +- .../main_test_timeout_passed_via_command_line | 2 +- tests/samples/output/test_help_contents | 4 ++- tests/test_arguments.py | 26 +++++++++++++++++++ tests/test_configs.py | 26 +++++++++++++++++++ wakatime/arguments.py | 6 +++++ wakatime/main.py | 7 +++-- 9 files changed, 70 insertions(+), 6 deletions(-) diff --git a/tests/samples/configs/has_everything.cfg b/tests/samples/configs/has_everything.cfg index 2796a5c..74b70b0 100644 --- a/tests/samples/configs/has_everything.cfg +++ b/tests/samples/configs/has_everything.cfg @@ -12,6 +12,7 @@ include = .* offline = false proxy = https://user:pass@localhost:8080 +no_ssl_verify = false timeout = abc api_url = https://localhost:0/api/v1/heartbeats hostname = fromcfgfile diff --git a/tests/samples/output/configs_test_config_file_not_passed_in_command_line_args b/tests/samples/output/configs_test_config_file_not_passed_in_command_line_args index 412c3f6..843b9e3 100644 --- a/tests/samples/output/configs_test_config_file_not_passed_in_command_line_args +++ b/tests/samples/output/configs_test_config_file_not_passed_in_command_line_args @@ -1,6 +1,6 @@ usage: wakatime [-h] [--entity FILE] [--key KEY] [--write] [--plugin PLUGIN] [--time time] [--lineno LINENO] [--cursorpos CURSORPOS] - [--entity-type ENTITY_TYPE] [--proxy PROXY] + [--entity-type ENTITY_TYPE] [--proxy PROXY] [--no-ssl-verify] [--project PROJECT] [--alternate-project ALTERNATE_PROJECT] [--language LANGUAGE] [--hostname HOSTNAME] [--disableoffline] [--hidefilenames] [--exclude EXCLUDE] [--include INCLUDE] diff --git a/tests/samples/output/configs_test_missing_config_file b/tests/samples/output/configs_test_missing_config_file index 412c3f6..843b9e3 100644 --- a/tests/samples/output/configs_test_missing_config_file +++ b/tests/samples/output/configs_test_missing_config_file @@ -1,6 +1,6 @@ usage: wakatime [-h] [--entity FILE] [--key KEY] [--write] [--plugin PLUGIN] [--time time] [--lineno LINENO] [--cursorpos CURSORPOS] - [--entity-type ENTITY_TYPE] [--proxy PROXY] + [--entity-type ENTITY_TYPE] [--proxy PROXY] [--no-ssl-verify] [--project PROJECT] [--alternate-project ALTERNATE_PROJECT] [--language LANGUAGE] [--hostname HOSTNAME] [--disableoffline] [--hidefilenames] [--exclude EXCLUDE] [--include INCLUDE] diff --git a/tests/samples/output/main_test_timeout_passed_via_command_line b/tests/samples/output/main_test_timeout_passed_via_command_line index 9de38ab..67e012a 100644 --- a/tests/samples/output/main_test_timeout_passed_via_command_line +++ b/tests/samples/output/main_test_timeout_passed_via_command_line @@ -1,6 +1,6 @@ usage: wakatime [-h] [--entity FILE] [--key KEY] [--write] [--plugin PLUGIN] [--time time] [--lineno LINENO] [--cursorpos CURSORPOS] - [--entity-type ENTITY_TYPE] [--proxy PROXY] + [--entity-type ENTITY_TYPE] [--proxy PROXY] [--no-ssl-verify] [--project PROJECT] [--alternate-project ALTERNATE_PROJECT] [--language LANGUAGE] [--hostname HOSTNAME] [--disableoffline] [--hidefilenames] [--exclude EXCLUDE] [--include INCLUDE] diff --git a/tests/samples/output/test_help_contents b/tests/samples/output/test_help_contents index c9b997b..59419ea 100644 --- a/tests/samples/output/test_help_contents +++ b/tests/samples/output/test_help_contents @@ -1,6 +1,6 @@ usage: wakatime [-h] [--entity FILE] [--key KEY] [--write] [--plugin PLUGIN] [--time time] [--lineno LINENO] [--cursorpos CURSORPOS] - [--entity-type ENTITY_TYPE] [--proxy PROXY] + [--entity-type ENTITY_TYPE] [--proxy PROXY] [--no-ssl-verify] [--project PROJECT] [--alternate-project ALTERNATE_PROJECT] [--language LANGUAGE] [--hostname HOSTNAME] [--disableoffline] [--hidefilenames] [--exclude EXCLUDE] [--include INCLUDE] @@ -30,6 +30,8 @@ optional arguments: --proxy PROXY optional proxy configuration. Supports HTTPS and SOCKS proxies. For example: https://user:pass@host:port or socks5://user:pass@host:port or domain\user:pass + --no-ssl-verify disables SSL certificate verification for HTTPS + requests. By default, SSL certificates are verified. --project PROJECT optional project name --alternate-project ALTERNATE_PROJECT optional alternate project name; auto-discovered diff --git a/tests/test_arguments.py b/tests/test_arguments.py index 9fbd841..ab5c6a2 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -351,6 +351,32 @@ class MainTestCase(utils.TestCase): self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].assert_called_once_with(ANY, cert=None, proxies={'https': proxy}, stream=False, timeout=60, verify=True) + def test_disable_ssl_verify_argument(self): + response = Response() + response.status_code = 201 + self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = response + + with utils.TemporaryDirectory() as tempdir: + entity = 'tests/samples/codefiles/emptyfile.txt' + shutil.copy(entity, os.path.join(tempdir, 'emptyfile.txt')) + entity = os.path.realpath(os.path.join(tempdir, 'emptyfile.txt')) + + config = 'tests/samples/configs/good_config.cfg' + args = ['--file', entity, '--config', config, '--no-ssl-verify'] + retval = execute(args) + self.assertEquals(retval, SUCCESS) + self.assertEquals(sys.stdout.getvalue(), '') + self.assertEquals(sys.stderr.getvalue(), '') + + self.patched['wakatime.session_cache.SessionCache.get'].assert_called_once_with() + self.patched['wakatime.session_cache.SessionCache.delete'].assert_not_called() + self.patched['wakatime.session_cache.SessionCache.save'].assert_called_once_with(ANY) + + self.patched['wakatime.offlinequeue.Queue.push'].assert_not_called() + self.patched['wakatime.offlinequeue.Queue.pop'].assert_called_once_with() + + self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].assert_called_once_with(ANY, cert=None, proxies=ANY, stream=False, timeout=60, verify=False) + def test_write_argument(self): response = Response() response.status_code = 0 diff --git a/tests/test_configs.py b/tests/test_configs.py index b25cc10..3f724f9 100644 --- a/tests/test_configs.py +++ b/tests/test_configs.py @@ -551,3 +551,29 @@ class MainTestCase(utils.TestCase): headers = self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].call_args[0][0].headers self.assertEquals(headers.get('X-Machine-Name'), hostname.encode('utf-8') if is_py3 else hostname) + + def test_no_ssl_verify_from_config_file(self): + response = Response() + response.status_code = 201 + self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = response + + with utils.TemporaryDirectory() as tempdir: + entity = 'tests/samples/codefiles/emptyfile.txt' + shutil.copy(entity, os.path.join(tempdir, 'emptyfile.txt')) + entity = os.path.realpath(os.path.join(tempdir, 'emptyfile.txt')) + + config = 'tests/samples/configs/has_ssl_verify_disabled.cfg' + args = ['--file', entity, '--config', config, '--timeout', '15'] + retval = execute(args) + self.assertEquals(retval, SUCCESS) + self.assertEquals(sys.stdout.getvalue(), '') + self.assertEquals(sys.stderr.getvalue(), '') + + self.patched['wakatime.session_cache.SessionCache.get'].assert_called_once_with() + self.patched['wakatime.session_cache.SessionCache.delete'].assert_not_called() + self.patched['wakatime.session_cache.SessionCache.save'].assert_called_once_with(ANY) + + self.patched['wakatime.offlinequeue.Queue.push'].assert_not_called() + self.patched['wakatime.offlinequeue.Queue.pop'].assert_called_once_with() + + self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].assert_called_once_with(ANY, cert=None, proxies=ANY, stream=False, timeout=15, verify=False) diff --git a/wakatime/arguments.py b/wakatime/arguments.py index 0fbe7f0..94920f3 100644 --- a/wakatime/arguments.py +++ b/wakatime/arguments.py @@ -76,6 +76,10 @@ def parseArguments(): 'https://user:pass@host:port or '+ 'socks5://user:pass@host:port or ' + 'domain\\user:pass') + parser.add_argument('--no-ssl-verify', dest='nosslverify', + action='store_true', + help='disables SSL certificate verification for HTTPS '+ + 'requests. By default, SSL certificates are verified.') parser.add_argument('--project', dest='project', help='optional project name') parser.add_argument('--alternate-project', dest='alternate_project', @@ -214,6 +218,8 @@ def parseArguments(): 'https://user:pass@host:port or ' + 'socks5://user:pass@host:port or ' + 'domain\\user:pass.') + if configs.has_option('settings', 'no_ssl_verify'): + args.nosslverify = configs.getboolean('settings', 'no_ssl_verify') if not args.verbose and configs.has_option('settings', 'verbose'): args.verbose = configs.getboolean('settings', 'verbose') if not args.verbose and configs.has_option('settings', 'debug'): diff --git a/wakatime/main.py b/wakatime/main.py index e1b31a1..064fec9 100644 --- a/wakatime/main.py +++ b/wakatime/main.py @@ -62,7 +62,8 @@ from .packages import tzlocal def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None, entity=None, timestamp=None, is_write=None, plugin=None, offline=None, entity_type='file', hidefilenames=None, - proxy=None, api_url=None, timeout=None, **kwargs): + proxy=None, nosslverify=None, api_url=None, timeout=None, + **kwargs): """Sends heartbeat as POST request to WakaTime api server. Returns `SUCCESS` when heartbeat was sent, otherwise returns an @@ -151,7 +152,8 @@ def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None, response = None try: response = session.post(api_url, data=request_body, headers=headers, - proxies=proxies, timeout=timeout) + proxies=proxies, timeout=timeout, + verify=not nosslverify) except RequestException: exception_data = { sys.exc_info()[0].__name__: u(sys.exc_info()[1]), @@ -286,6 +288,7 @@ def process_heartbeat(args, configs, hostname, heartbeat): heartbeat['offline'] = args.offline heartbeat['hidefilenames'] = args.hidefilenames heartbeat['proxy'] = args.proxy + heartbeat['nosslverify'] = args.nosslverify heartbeat['api_url'] = args.api_url return send_heartbeat(**heartbeat)