support for ntlm proxy

This commit is contained in:
Alan Hamlett 2017-02-15 15:02:05 -08:00
parent 5428942ccf
commit 1fd6169f53
31 changed files with 3029 additions and 58 deletions

View file

@ -1,3 +1,3 @@
[settings]
debug = true
api_key = 1234
api_key = 35fe2c34-252c-4760-bab9-ff85610e459d

View file

@ -1,3 +1,3 @@
[settings]
debug = false
api_key = 1234
api_key = 1090a6ae-855f-4be7-b8fb-3edbaf1aa3ec

View file

@ -1,6 +1,6 @@
[settings]
verbose = true
api_key = 1234567
api_key = d491a956-c8f2-44a9-98a7-987814bd71ba
logfile = /tmp/waka
hidefilenames = true
exclude =

View file

@ -1,13 +1,13 @@
[settings]
debug = false
api_key = 1234
ignore =
api_key = a77fdb4a-4bc9-40fe-baac-b369ca7de98e
ignore =
COMMIT_EDITMSG$
TAG_EDITMSG$
exclude =
exclude =
excludeme
\(invalid regex)
include =
include =
\(invalid regex)
includeme
offline = true

View file

@ -1,6 +1,6 @@
[settings]
debug = false
api_key = 1234
api_key = 033c47c9-0441-4eb5-8b3f-b51f27b31049
hidefilenames =
missingfile
twolinefile\.txt$

View file

@ -1,14 +1,14 @@
[settings]
debug = false
api_key = 1234
api_key = c21f8ebd-6a6a-48a0-900b-0870db3d7afe
api_url = https://api.wakatime.com/api/v1/heartbeats
ignore =
ignore =
COMMIT_EDITMSG$
TAG_EDITMSG$
exclude =
exclude =
excludeme
\(invalid regex)
include =
include =
\(invalid regex)
includeme
offline = true

View file

@ -1,6 +1,6 @@
[settings]
debug = false
api_key = 1234
api_key = ba50f683-bb6b-4f31-9c84-7c70412234f7
[projectmap]
samples/projects/proj.{3}_map/ = proj-map
samples/projects/project_map(\d+)/ = proj-map{0}

View file

@ -1,5 +1,5 @@
[settings]
debug = false
api_key = 1234
api_key = 3ac03597-a707-4dd2-95a1-25430f9486ba
[projectmap]
invalid[({regex = proj-map

View file

@ -1,5 +1,5 @@
[settings]
debug = false
api_key = 1234
api_key = 4b59ee97-144f-46d0-a2c0-167e61a9c518
[projectmap]
samples/projects/project_map(\d+)/ = proj-map{3}

View file

@ -1,13 +1,13 @@
[settings]
debug = false
apikey = 1234
ignore =
apikey = b5acbb37-8558-4383-8ea4-581837c7371c
ignore =
COMMIT_EDITMSG$
TAG_EDITMSG$
exclude =
exclude =
excludeme
\(invalid regex)
include =
include =
\(invalid regex)
includeme
offline = true

View file

@ -30,7 +30,7 @@ optional arguments:
"domain", or "app"; defaults to file.
--proxy PROXY optional proxy configuration. Supports HTTPS and SOCKS
proxies. For example: https://user:pass@host:port or
socks5://user:pass@host:port
socks5://user:pass@host:port or domain\user:pass
--project PROJECT optional project name
--alternate-project ALTERNATE_PROJECT
optional alternate project name; auto-discovered

View file

@ -10,6 +10,7 @@ import time
import re
import shutil
import sys
import uuid
from testfixtures import log_capture
from wakatime.compat import u, is_py3
from wakatime.constants import (
@ -68,9 +69,10 @@ class MainTestCase(utils.TestCase):
entity = 'tests/samples/codefiles/twolinefile.txt'
shutil.copy(entity, os.path.join(tempdir, 'twolinefile.txt'))
entity = os.path.realpath(os.path.join(tempdir, 'twolinefile.txt'))
key = str(uuid.uuid4())
config = 'tests/samples/configs/good_config.cfg'
args = ['--file', entity, '--key', '123', '--config', config]
args = ['--file', entity, '--key', key, '--config', config]
retval = execute(args)
self.assertEquals(retval, SUCCESS)
@ -192,7 +194,11 @@ class MainTestCase(utils.TestCase):
self.patched['wakatime.offlinequeue.Queue.push'].assert_not_called()
self.patched['wakatime.offlinequeue.Queue.pop'].assert_called_once_with()
def test_api_key_without_underscore_accepted(self):
def test_api_key_setting_without_underscore_accepted(self):
"""Api key in wakatime.cfg should also work without an underscore:
apikey = XXX
"""
response = Response()
response.status_code = 201
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = response
@ -288,11 +294,11 @@ class MainTestCase(utils.TestCase):
entity = 'tests/samples/codefiles/twolinefile.txt'
shutil.copy(entity, os.path.join(tempdir, 'twolinefile.txt'))
entity = os.path.realpath(os.path.join(tempdir, 'twolinefile.txt'))
now = u(int(time.time()))
config = 'tests/samples/configs/good_config.cfg'
key = str(uuid.uuid4())
args = ['--file', entity, '--key', '123', '--config', config, '--time', now]
args = ['--file', entity, '--key', key, '--config', config, '--time', now]
retval = execute(args)
self.assertEquals(retval, API_ERROR)
@ -334,11 +340,11 @@ class MainTestCase(utils.TestCase):
entity = 'tests/samples/codefiles/twolinefile.txt'
shutil.copy(entity, os.path.join(tempdir, 'twolinefile.txt'))
entity = os.path.realpath(os.path.join(tempdir, 'twolinefile.txt'))
now = u(int(time.time()))
config = 'tests/samples/configs/paranoid.cfg'
key = str(uuid.uuid4())
args = ['--file', entity, '--key', '123', '--config', config, '--time', now]
args = ['--file', entity, '--key', key, '--config', config, '--time', now]
retval = execute(args)
self.assertEquals(retval, API_ERROR)
@ -380,11 +386,11 @@ class MainTestCase(utils.TestCase):
entity = 'tests/samples/codefiles/twolinefile.txt'
shutil.copy(entity, os.path.join(tempdir, 'twolinefile.txt'))
entity = os.path.realpath(os.path.join(tempdir, 'twolinefile.txt'))
now = u(int(time.time()))
config = 'tests/samples/configs/hide_file_names.cfg'
key = str(uuid.uuid4())
args = ['--file', entity, '--key', '123', '--config', config, '--time', now]
args = ['--file', entity, '--key', key, '--config', config, '--time', now]
retval = execute(args)
self.assertEquals(retval, API_ERROR)
@ -426,11 +432,11 @@ class MainTestCase(utils.TestCase):
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'))
now = u(int(time.time()))
config = 'tests/samples/configs/hide_file_names.cfg'
key = str(uuid.uuid4())
args = ['--file', entity, '--key', '123', '--config', config, '--time', now]
args = ['--file', entity, '--key', key, '--config', config, '--time', now]
retval = execute(args)
self.assertEquals(retval, API_ERROR)
@ -472,9 +478,9 @@ class MainTestCase(utils.TestCase):
entity = 'tests/samples/codefiles/twolinefile.txt'
shutil.copy(entity, os.path.join(tempdir, 'twolinefile.txt'))
entity = os.path.realpath(os.path.join(tempdir, 'twolinefile.txt'))
config = 'tests/samples/configs/good_config.cfg'
args = ['--file', entity, '--key', '123', '--config', config, '--timeout', 'abc']
key = str(uuid.uuid4())
args = ['--file', entity, '--key', key, '--config', config, '--timeout', 'abc']
with self.assertRaises(SystemExit) as e:
execute(args)
@ -500,8 +506,8 @@ class MainTestCase(utils.TestCase):
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, '--exclude', 'empty', '--verbose']
retval = execute(args)
self.assertEquals(retval, SUCCESS)
@ -529,10 +535,10 @@ class MainTestCase(utils.TestCase):
entity = 'tests/samples/codefiles/twolinefile.txt'
shutil.copy(entity, os.path.join(tempdir, 'twolinefile.txt'))
entity = os.path.realpath(os.path.join(tempdir, 'twolinefile.txt'))
now = u(int(time.time()))
key = str(uuid.uuid4())
args = ['--file', entity, '--key', '123',
args = ['--file', entity, '--key', key,
'--config', 'tests/samples/configs/paranoid.cfg', '--time', now]
retval = execute(args)
@ -575,10 +581,10 @@ class MainTestCase(utils.TestCase):
entity = 'tests/samples/codefiles/twolinefile.txt'
shutil.copy(entity, os.path.join(tempdir, 'twolinefile.txt'))
entity = os.path.realpath(os.path.join(tempdir, 'twolinefile.txt'))
now = u(int(time.time()))
key = str(uuid.uuid4())
args = ['--file', entity, '--key', '123',
args = ['--file', entity, '--key', key,
'--config', 'tests/samples/configs/paranoid.cfg', '--time', now]
retval = execute(args)
@ -602,10 +608,10 @@ class MainTestCase(utils.TestCase):
entity = 'tests/samples/codefiles/twolinefile.txt'
shutil.copy(entity, os.path.join(tempdir, 'twolinefile.txt'))
entity = os.path.realpath(os.path.join(tempdir, 'twolinefile.txt'))
now = u(int(time.time()))
key = str(uuid.uuid4())
args = ['--file', entity, '--key', '123',
args = ['--file', entity, '--key', key,
'--config', 'tests/samples/configs/paranoid.cfg', '--time', now]
retval = execute(args)
@ -654,10 +660,10 @@ class MainTestCase(utils.TestCase):
entity = 'tests/samples/codefiles/twolinefile.txt'
shutil.copy(entity, os.path.join(tempdir, 'twolinefile.txt'))
entity = os.path.realpath(os.path.join(tempdir, 'twolinefile.txt'))
now = u(int(time.time()))
key = str(uuid.uuid4())
args = ['--file', entity, '--key', '123', '--disableoffline',
args = ['--file', entity, '--key', key, '--disableoffline',
'--config', 'tests/samples/configs/good_config.cfg', '--time', now]
retval = execute(args)
@ -692,10 +698,10 @@ class MainTestCase(utils.TestCase):
entity = 'tests/samples/codefiles/twolinefile.txt'
shutil.copy(entity, os.path.join(tempdir, 'twolinefile.txt'))
entity = os.path.realpath(os.path.join(tempdir, 'twolinefile.txt'))
now = u(int(time.time()))
key = str(uuid.uuid4())
args = ['--file', entity, '--key', '123', '--verbose',
args = ['--file', entity, '--key', key, '--verbose',
'--config', 'tests/samples/configs/good_config.cfg', '--time', now]
retval = execute(args)
@ -749,10 +755,10 @@ class MainTestCase(utils.TestCase):
entity = 'tests/samples/codefiles/twolinefile.txt'
shutil.copy(entity, os.path.join(tempdir, 'twolinefile.txt'))
entity = os.path.realpath(os.path.join(tempdir, 'twolinefile.txt'))
now = u(int(time.time()))
key = str(uuid.uuid4())
args = ['--file', entity, '--key', '123', '--disableoffline',
args = ['--file', entity, '--key', key, '--disableoffline',
'--config', 'tests/samples/configs/good_config.cfg', '--time', now]
retval = execute(args)
@ -847,7 +853,37 @@ class MainTestCase(utils.TestCase):
self.assertEquals(int(str(e.exception)), AUTH_ERROR)
self.assertEquals(sys.stdout.getvalue(), '')
expected = 'error: Missing api key'
expected = 'error: Missing api key. Find your api key from wakatime.com/settings.'
self.assertIn(expected, sys.stderr.getvalue())
log_output = u("\n").join([u(' ').join(x) for x in logs.actual()])
expected = ''
self.assertEquals(log_output, expected)
self.patched['wakatime.session_cache.SessionCache.get'].assert_not_called()
self.patched['wakatime.session_cache.SessionCache.delete'].assert_not_called()
self.patched['wakatime.session_cache.SessionCache.save'].assert_not_called()
self.patched['wakatime.offlinequeue.Queue.push'].assert_not_called()
self.patched['wakatime.offlinequeue.Queue.pop'].assert_not_called()
@log_capture()
def test_invalid_api_key(self, logs):
logging.disable(logging.NOTSET)
response = Response()
response.status_code = 201
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = response
config = 'tests/samples/configs/missing_api_key.cfg'
args = ['--config', config, '--key', 'invalid-api-key']
with self.assertRaises(SystemExit) as e:
execute(args)
self.assertEquals(int(str(e.exception)), AUTH_ERROR)
self.assertEquals(sys.stdout.getvalue(), '')
expected = 'error: Invalid api key. Find your api key from wakatime.com/settings.'
self.assertIn(expected, sys.stderr.getvalue())
log_output = u("\n").join([u(' ').join(x) for x in logs.actual()])
@ -870,9 +906,10 @@ class MainTestCase(utils.TestCase):
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'))
proxy = 'localhost:1337'
config = 'tests/samples/configs/good_config.cfg'
args = ['--file', entity, '--config', config, '--proxy', 'localhost:1234']
args = ['--file', entity, '--config', config, '--proxy', proxy]
retval = execute(args)
self.assertEquals(retval, SUCCESS)
self.assertEquals(sys.stdout.getvalue(), '')
@ -885,7 +922,7 @@ class MainTestCase(utils.TestCase):
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={'https': 'localhost:1234'}, stream=False, timeout=60, verify=True)
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_write_argument(self):
response = Response()
@ -897,8 +934,9 @@ class MainTestCase(utils.TestCase):
shutil.copy(entity, os.path.join(tempdir, 'emptyfile.txt'))
entity = os.path.realpath(os.path.join(tempdir, 'emptyfile.txt'))
now = u(int(time.time()))
key = str(uuid.uuid4())
args = ['--file', entity, '--key', '123', '--write', '--verbose',
args = ['--file', entity, '--key', key, '--write', '--verbose',
'--config', 'tests/samples/configs/good_config.cfg', '--time', now]
retval = execute(args)
@ -1292,11 +1330,11 @@ class MainTestCase(utils.TestCase):
entity = os.path.join('tests/samples/codefiles/unicode', filename)
shutil.copy(entity, os.path.join(tempdir, filename))
entity = os.path.realpath(os.path.join(tempdir, filename))
now = u(int(time.time()))
config = 'tests/samples/configs/good_config.cfg'
key = str(uuid.uuid4())
args = ['--file', entity, '--key', '123', '--config', config, '--time', now]
args = ['--file', entity, '--key', key, '--config', config, '--time', now]
retval = execute(args)
self.assertEquals(retval, API_ERROR)
@ -1342,7 +1380,9 @@ class MainTestCase(utils.TestCase):
entity = 'tests/samples/codefiles/twolinefile.txt'
config = 'tests/samples/configs/good_config.cfg'
args = ['--entity', entity, '--key', '123', '--config', config]
key = str(uuid.uuid4())
args = ['--entity', entity, '--key', key, '--config', config]
execute(args)

176
tests/test_proxy.py Normal file
View file

@ -0,0 +1,176 @@
# -*- coding: utf-8 -*-
from wakatime.main import execute
from wakatime.packages import requests
import logging
import os
import shutil
import sys
from testfixtures import log_capture
from wakatime.compat import u
from wakatime.constants import SUCCESS
from wakatime.packages.requests.models import Response
from . import utils
try:
from mock import ANY
except ImportError:
from unittest.mock import ANY
class MainTestCase(utils.TestCase):
patch_these = [
'wakatime.packages.requests.adapters.HTTPAdapter.send',
'wakatime.offlinequeue.Queue.push',
['wakatime.offlinequeue.Queue.pop', None],
['wakatime.offlinequeue.Queue.connect', None],
'wakatime.session_cache.SessionCache.save',
'wakatime.session_cache.SessionCache.delete',
['wakatime.session_cache.SessionCache.get', requests.session],
['wakatime.session_cache.SessionCache.connect', None],
]
def test_proxy_without_protocol(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'))
proxy = 'user:pass@localhost:12345'
config = 'tests/samples/configs/good_config.cfg'
args = ['--file', entity, '--config', config, '--proxy', proxy]
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={'https': proxy}, stream=False, timeout=60, verify=True)
def test_https_proxy(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'))
proxy = 'https://user:pass@localhost:12345'
config = 'tests/samples/configs/good_config.cfg'
args = ['--file', entity, '--config', config, '--proxy', proxy]
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={'https': proxy}, stream=False, timeout=60, verify=True)
def test_socks_proxy(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'))
proxy = 'socks5://user:pass@localhost:12345'
config = 'tests/samples/configs/good_config.cfg'
args = ['--file', entity, '--config', config, '--proxy', proxy]
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={'https': proxy}, stream=False, timeout=60, verify=True)
def test_ntlm_proxy(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'))
proxy = 'domain\\user:pass'
config = 'tests/samples/configs/good_config.cfg'
args = ['--file', entity, '--config', config, '--proxy', proxy]
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={}, stream=False, timeout=60, verify=True)
@log_capture()
def test_invalid_proxy(self, logs):
logging.disable(logging.NOTSET)
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'))
proxy = 'invaliddd:proxyarg'
config = 'tests/samples/configs/good_config.cfg'
args = ['--file', entity, '--config', config, '--proxy', proxy]
with self.assertRaises(SystemExit) as e:
execute(args)
self.assertEquals(int(str(e.exception)), 2)
self.assertEquals(sys.stdout.getvalue(), '')
expected = 'error: Invalid proxy. Must be in format https://user:pass@host:port or socks5://user:pass@host:port or domain\user:pass.'
self.assertIn(expected, sys.stderr.getvalue())
log_output = u("\n").join([u(' ').join(x) for x in logs.actual()])
expected = ''
self.assertEquals(log_output, expected)
self.patched['wakatime.session_cache.SessionCache.get'].assert_not_called()
self.patched['wakatime.session_cache.SessionCache.delete'].assert_not_called()
self.patched['wakatime.session_cache.SessionCache.save'].assert_not_called()
self.patched['wakatime.offlinequeue.Queue.push'].assert_not_called()
self.patched['wakatime.offlinequeue.Queue.pop'].assert_not_called()