diff options
author | Peter Wagner <pwagner@pebble.com> | 2015-09-11 08:51:34 -0400 |
---|---|---|
committer | Giuseppe Lavagetto <lavagetto@gmail.com> | 2015-11-16 08:45:18 +0100 |
commit | 817adc5348a798d2981e6cc5b988373a0985cf54 (patch) | |
tree | 6f6d66cb7b0836e9da94f6da588f4bfe2325cd65 | |
parent | 8e1e1ce3021031dd3487d94b7b4165908787b9f1 (diff) |
User authentication initial
* Initial BASIC auth implementation + unit tests
Checkpoint before integration tests.
-rw-r--r-- | src/etcd/client.py | 26 | ||||
-rw-r--r-- | src/etcd/tests/integration/helpers.py | 6 | ||||
-rw-r--r-- | src/etcd/tests/unit/test_client.py | 33 | ||||
-rw-r--r-- | src/etcd/tests/unit/test_lock.py | 12 |
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): |