summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Wagner <pwagner@pebble.com>2015-09-11 08:51:34 -0400
committerGiuseppe Lavagetto <lavagetto@gmail.com>2015-11-16 08:45:18 +0100
commit817adc5348a798d2981e6cc5b988373a0985cf54 (patch)
tree6f6d66cb7b0836e9da94f6da588f4bfe2325cd65
parent8e1e1ce3021031dd3487d94b7b4165908787b9f1 (diff)
User authentication initial
* Initial BASIC auth implementation + unit tests Checkpoint before integration tests.
-rw-r--r--src/etcd/client.py26
-rw-r--r--src/etcd/tests/integration/helpers.py6
-rw-r--r--src/etcd/tests/unit/test_client.py33
-rw-r--r--src/etcd/tests/unit/test_lock.py12
4 files changed, 68 insertions, 9 deletions
diff --git a/src/etcd/client.py b/src/etcd/client.py
index ab17acd..5bb69f4 100644
--- a/src/etcd/client.py
+++ b/src/etcd/client.py
@@ -57,6 +57,8 @@ class Client(object):
protocol='http',
cert=None,
ca_cert=None,
+ username=None,
+ password=None,
allow_reconnect=False,
use_proxies=False,
expected_cluster_id=None,
@@ -88,6 +90,10 @@ class Client(object):
ca_cert (str): The ca certificate. If pressent it will enable
validation.
+ username (str): username for etcd authentication.
+
+ password (str): password for etcd authentication.
+
allow_reconnect (bool): allow the client to reconnect to another
etcd server in the cluster in the case the
default one does not respond.
@@ -165,6 +171,16 @@ class Client(object):
kw['ca_certs'] = ca_cert
kw['cert_reqs'] = ssl.CERT_REQUIRED
+ self.username = None
+ self.password = None
+ if username and password:
+ self.username = username
+ self.password = password
+ elif username:
+ _log.warning('Username provided without password, both are required for authentication')
+ elif password:
+ _log.warning('Password provided without username, both are required for authentication')
+
self.http = urllib3.PoolManager(num_pools=10, **kw)
_log.debug("New etcd client created for %s", self.base_uri)
@@ -258,6 +274,7 @@ class Client(object):
response = self.http.request(
self._MGET,
uri,
+ headers=self._get_headers(),
timeout=self.read_timeout,
redirect=self.allow_redirect)
@@ -781,6 +798,7 @@ class Client(object):
timeout=timeout,
fields=params,
redirect=self.allow_redirect,
+ headers=self._get_headers(),
preload_content=False)
elif (method == self._MPUT) or (method == self._MPOST):
@@ -791,6 +809,7 @@ class Client(object):
timeout=timeout,
encode_multipart=False,
redirect=self.allow_redirect,
+ headers=self._get_headers(),
preload_content=False)
else:
raise etcd.EtcdException(
@@ -877,3 +896,10 @@ class Client(object):
r = {"message": "Bad response",
"cause": str(resp)}
etcd.EtcdError.handle(r)
+
+ def _get_headers(self):
+ if self.username and self.password:
+ credentials = ':'.join((self.username, self.password))
+ return urllib3.make_headers(basic_auth=credentials)
+ return {}
+
diff --git a/src/etcd/tests/integration/helpers.py b/src/etcd/tests/integration/helpers.py
index 6c7e21c..3314be9 100644
--- a/src/etcd/tests/integration/helpers.py
+++ b/src/etcd/tests/integration/helpers.py
@@ -77,12 +77,12 @@ class EtcdProcessHelper(object):
def kill_one(self, slot):
log = logging.getLogger()
- dir, process = self.processes.pop(slot)
+ data_dir, process = self.processes.pop(slot)
process.kill()
time.sleep(2)
log.debug('Killed etcd pid:%d', process.pid)
- shutil.rmtree(dir)
- log.debug('Removed directory %s' % dir)
+ shutil.rmtree(data_dir)
+ log.debug('Removed directory %s' % data_dir)
class TestingCA(object):
diff --git a/src/etcd/tests/unit/test_client.py b/src/etcd/tests/unit/test_client.py
index 4301732..bb05a66 100644
--- a/src/etcd/tests/unit/test_client.py
+++ b/src/etcd/tests/unit/test_client.py
@@ -45,6 +45,16 @@ class TestClient(unittest.TestCase):
client = etcd.Client()
assert client.allow_redirect
+ def test_default_username(self):
+ """ default username is None"""
+ client = etcd.Client()
+ assert client.username is None
+
+ def test_default_password(self):
+ """ default username is None"""
+ client = etcd.Client()
+ assert client.password is None
+
def test_set_host(self):
""" can change host """
client = etcd.Client(host='192.168.1.1')
@@ -92,6 +102,29 @@ class TestClient(unittest.TestCase):
client = etcd.Client(use_proxies = True)
assert client._use_proxies
+ def test_set_username_only(self):
+ client = etcd.Client(username='username')
+ assert client.username is None
+
+ def test_set_password_only(self):
+ client = etcd.Client(password='password')
+ assert client.password is None
+
+ def test_set_username_password(self):
+ client = etcd.Client(username='username', password='password')
+ assert client.username == 'username'
+ assert client.password == 'password'
+
+ def test_get_headers_with_auth(self):
+ client = etcd.Client(username='username', password='password')
+ assert client._get_headers() == {
+ 'authorization': 'Basic dXNlcm5hbWU6cGFzc3dvcmQ='
+ }
+
+ def test_get_headers_without_auth(self):
+ client = etcd.Client()
+ assert client._get_headers() == {}
+
def test_allow_reconnect(self):
""" Fails if allow_reconnect is false and a list of hosts is given"""
with self.assertRaises(etcd.EtcdException):
diff --git a/src/etcd/tests/unit/test_lock.py b/src/etcd/tests/unit/test_lock.py
index 6a41f13..1b374c8 100644
--- a/src/etcd/tests/unit/test_lock.py
+++ b/src/etcd/tests/unit/test_lock.py
@@ -40,8 +40,8 @@ class TestClientLock(TestClientApiBase):
Acquiring a precedingly inexistent lock works.
"""
l = etcd.Lock(self.client, 'test_lock')
- l._find_lock = mock.create_autospec(l._find_lock, return_value=False)
- l._acquired = mock.create_autospec(l._acquired, return_value=True)
+ l._find_lock = mock.MagicMock(spec=l._find_lock, return_value=False)
+ l._acquired = mock.MagicMock(spec=l._acquired, return_value=True)
# Mock the write
d = {
u'action': u'set',
@@ -90,8 +90,8 @@ class TestClientLock(TestClientApiBase):
"""
self.locker._sequence = '4'
retval = ('/_locks/test_lock/4', None)
- self.locker._get_locker = mock.create_autospec(
- self.locker._get_locker, return_value=retval)
+ self.locker._get_locker = mock.MagicMock(
+ spec=self.locker._get_locker, return_value=retval)
self.assertTrue(self.locker._acquired())
self.assertTrue(self.locker.is_taken)
retval = ('/_locks/test_lock/1', '/_locks/test_lock/4')
@@ -112,8 +112,8 @@ class TestClientLock(TestClientApiBase):
def side_effect():
return returns.pop()
- self.locker._get_locker = mock.create_autospec(
- self.locker._get_locker, side_effect=side_effect)
+ self.locker._get_locker = mock.MagicMock(
+ spec=self.locker._get_locker, side_effect=side_effect)
self.assertTrue(self.locker._acquired())
def test_lock_key(self):