From 063f3ed388c96959f284f6e2b9e08a0328481197 Mon Sep 17 00:00:00 2001
From: Steve Milner <smilner@redhat.com>
Date: Fri, 18 Nov 2016 10:12:58 -0500
Subject: [PATCH] Removed the new auth module.
The new auth module doesn't support etcd 2.3+ yet. Since it is a net new
module in this version it has been disabled until an update becomes
available.
See: https://github.com/jplana/python-etcd/issues/210
---
src/etcd/auth.py | 255 --------------------------------------------
src/etcd/tests/test_auth.py | 161 ----------------------------
2 files changed, 416 deletions(-)
delete mode 100644 src/etcd/auth.py
delete mode 100644 src/etcd/tests/test_auth.py
diff --git a/src/etcd/auth.py b/src/etcd/auth.py
deleted file mode 100644
index 796772d..0000000
--- a/src/etcd/auth.py
+++ /dev/null
@@ -1,255 +0,0 @@
-import json
-
-import logging
-import etcd
-
-_log = logging.getLogger(__name__)
-
-
-class EtcdAuthBase(object):
- entity = 'example'
-
- def __init__(self, client, name):
- self.client = client
- self.name = name
- self.uri = "{}/auth/{}s/{}".format(self.client.version_prefix,
- self.entity, self.name)
-
- @property
- def names(self):
- key = "{}s".format(self.entity)
- uri = "{}/auth/{}".format(self.client.version_prefix, key)
- response = self.client.api_execute(uri, self.client._MGET)
- return json.loads(response.data.decode('utf-8'))[key]
-
- def read(self):
- try:
- response = self.client.api_execute(self.uri, self.client._MGET)
- except etcd.EtcdInsufficientPermissions as e:
- _log.error("Any action on the authorization requires the root role")
- raise
- except etcd.EtcdKeyNotFound:
- _log.info("%s '%s' not found", self.entity, self.name)
- raise
- except Exception as e:
- _log.error("Failed to fetch %s in %s%s: %r",
- self.entity, self.client._base_uri,
- self.client.version_prefix, e)
- raise etcd.EtcdException(
- "Could not fetch {} '{}'".format(self.entity, self.name))
-
- self._from_net(response.data)
-
- def write(self):
- try:
- r = self.__class__(self.client, self.name)
- r.read()
- except etcd.EtcdKeyNotFound:
- r = None
- try:
- for payload in self._to_net(r):
- response = self.client.api_execute_json(self.uri,
- self.client._MPUT,
- params=payload)
- # This will fail if the response is an error
- self._from_net(response.data)
- except etcd.EtcdInsufficientPermissions as e:
- _log.error("Any action on the authorization requires the root role")
- raise
- except Exception as e:
- _log.error("Failed to write %s '%s'", self.entity, self.name)
- # TODO: fine-grained exception handling
- raise etcd.EtcdException(
- "Could not write {} '{}': {}".format(self.entity,
- self.name, e))
-
- def delete(self):
- try:
- _ = self.client.api_execute(self.uri, self.client._MDELETE)
- except etcd.EtcdInsufficientPermissions as e:
- _log.error("Any action on the authorization requires the root role")
- raise
- except etcd.EtcdKeyNotFound:
- _log.info("%s '%s' not found", self.entity, self.name)
- raise
- except Exception as e:
- _log.error("Failed to delete %s in %s%s: %r",
- self.entity, self._base_uri, self.version_prefix, e)
- raise etcd.EtcdException(
- "Could not delete {} '{}'".format(self.entity, self.name))
-
- def _from_net(self, data):
- raise NotImplementedError()
-
- def _to_net(self, old=None):
- raise NotImplementedError()
-
- @classmethod
- def new(cls, client, data):
- c = cls(client, data[cls.entity])
- c._from_net(data)
- return c
-
-
-class EtcdUser(EtcdAuthBase):
- """Class to manage in a orm-like way etcd users"""
- entity = 'user'
-
- def __init__(self, client, name):
- super(EtcdUser, self).__init__(client, name)
- self._roles = set()
- self._password = None
-
- def _from_net(self, data):
- d = json.loads(data.decode('utf-8'))
- self.roles = d.get('roles', [])
- self.name = d.get('user')
-
- def _to_net(self, prevobj=None):
- if prevobj is None:
- retval = [{"user": self.name, "password": self._password,
- "roles": list(self.roles)}]
- else:
- retval = []
- if self._password:
- retval.append({"user": self.name, "password": self._password})
- to_grant = list(self.roles - prevobj.roles)
- to_revoke = list(prevobj.roles - self.roles)
- if to_grant:
- retval.append({"user": self.name, "grant": to_grant})
- if to_revoke:
- retval.append({"user": self.name, "revoke": to_revoke})
- # Let's blank the password now
- # Even if the user can't be written we don't want it to leak anymore.
- self._password = None
- return retval
-
- @property
- def roles(self):
- return self._roles
-
- @roles.setter
- def roles(self, val):
- self._roles = set(val)
-
- @property
- def password(self):
- """Empty property for password."""
- return None
-
- @password.setter
- def password(self, new_password):
- """Change user's password."""
- self._password = new_password
-
- def __str__(self):
- return json.dumps(self._to_net()[0])
-
-
-
-class EtcdRole(EtcdAuthBase):
- entity = 'role'
-
- def __init__(self, client, name):
- super(EtcdRole, self).__init__(client, name)
- self._read_paths = set()
- self._write_paths = set()
-
- def _from_net(self, data):
- d = json.loads(data.decode('utf-8'))
- self.name = d.get('role')
-
- try:
- kv = d["permissions"]["kv"]
- except:
- self._read_paths = set()
- self._write_paths = set()
- return
-
- self._read_paths = set(kv.get('read', []))
- self._write_paths = set(kv.get('write', []))
-
- def _to_net(self, prevobj=None):
- retval = []
- if prevobj is None:
- retval.append({
- "role": self.name,
- "permissions":
- {
- "kv":
- {
- "read": list(self._read_paths),
- "write": list(self._write_paths)
- }
- }
- })
- else:
- to_grant = {
- 'read': list(self._read_paths - prevobj._read_paths),
- 'write': list(self._write_paths - prevobj._write_paths)
- }
- to_revoke = {
- 'read': list(prevobj._read_paths - self._read_paths),
- 'write': list(prevobj._write_paths - self._write_paths)
- }
- if [path for sublist in to_revoke.values() for path in sublist]:
- retval.append({'role': self.name, 'revoke': {'kv': to_revoke}})
- if [path for sublist in to_grant.values() for path in sublist]:
- retval.append({'role': self.name, 'grant': {'kv': to_grant}})
- return retval
-
- def grant(self, path, permission):
- if permission.upper().find('R') >= 0:
- self._read_paths.add(path)
- if permission.upper().find('W') >= 0:
- self._write_paths.add(path)
-
- def revoke(self, path, permission):
- if permission.upper().find('R') >= 0 and \
- path in self._read_paths:
- self._read_paths.remove(path)
- if permission.upper().find('W') >= 0 and \
- path in self._write_paths:
- self._write_paths.remove(path)
-
- @property
- def acls(self):
- perms = {}
- try:
- for path in self._read_paths:
- perms[path] = 'R'
- for path in self._write_paths:
- if path in perms:
- perms[path] += 'W'
- else:
- perms[path] = 'W'
- except:
- pass
- return perms
-
- @acls.setter
- def acls(self, acls):
- self._read_paths = set()
- self._write_paths = set()
- for path, permission in acls.items():
- self.grant(path, permission)
-
- def __str__(self):
- return json.dumps({"role": self.name, 'acls': self.acls})
-
-
-class Auth(object):
- def __init__(self, client):
- self.client = client
- self.uri = "{}/auth/enable".format(self.client.version_prefix)
-
- @property
- def active(self):
- resp = self.client.api_execute(self.uri, self.client._MGET)
- return json.loads(resp.data.decode('utf-8'))['enabled']
-
- @active.setter
- def active(self, value):
- if value != self.active:
- method = value and self.client._MPUT or self.client._MDELETE
- self.client.api_execute(self.uri, method)
diff --git a/src/etcd/tests/test_auth.py b/src/etcd/tests/test_auth.py
deleted file mode 100644
index fc6ce70..0000000
--- a/src/etcd/tests/test_auth.py
+++ /dev/null
@@ -1,161 +0,0 @@
-from etcd.tests.integration.test_simple import EtcdIntegrationTest
-from etcd import auth
-import etcd
-
-
-class TestEtcdAuthBase(EtcdIntegrationTest):
- cl_size = 1
-
- def setUp(self):
- # Sets up the root user, toggles auth
- u = auth.EtcdUser(self.client, 'root')
- u.password = 'testpass'
- u.write()
- self.client = etcd.Client(port=6001, username='root',
- password='testpass')
- self.unauth_client = etcd.Client(port=6001)
- a = auth.Auth(self.client)
- a.active = True
-
- def tearDown(self):
- u = auth.EtcdUser(self.client, 'test_user')
- r = auth.EtcdRole(self.client, 'test_role')
- try:
- u.delete()
- except:
- pass
- try:
- r.delete()
- except:
- pass
- a = auth.Auth(self.client)
- a.active = False
-
-
-class EtcdUserTest(TestEtcdAuthBase):
- def test_names(self):
- u = auth.EtcdUser(self.client, 'test_user')
- self.assertEquals(u.names, ['root'])
-
- def test_read(self):
- u = auth.EtcdUser(self.client, 'root')
- # Reading an existing user succeeds
- try:
- u.read()
- except Exception:
- self.fail("reading the root user raised an exception")
-
- # roles for said user are fetched
- self.assertEquals(u.roles, set(['root']))
-
- # The user is correctly rendered out
- self.assertEquals(u._to_net(), [{'user': 'root', 'password': None,
- 'roles': ['root']}])
-
- # An inexistent user raises the appropriate exception
- u = auth.EtcdUser(self.client, 'user.does.not.exist')
- self.assertRaises(etcd.EtcdKeyNotFound, u.read)
-
- # Reading with an unauthenticated client raises an exception
- u = auth.EtcdUser(self.unauth_client, 'root')
- self.assertRaises(etcd.EtcdInsufficientPermissions, u.read)
-
- # Generic errors are caught
- c = etcd.Client(port=9999)
- u = auth.EtcdUser(c, 'root')
- self.assertRaises(etcd.EtcdException, u.read)
-
- def test_write_and_delete(self):
- # Create an user
- u = auth.EtcdUser(self.client, 'test_user')
- u.roles.add('guest')
- u.roles.add('root')
- # directly from my suitcase
- u.password = '123456'
- try:
- u.write()
- except:
- self.fail("creating a user doesn't work")
- # Password gets wiped
- self.assertEquals(u.password, None)
- u.read()
- # Verify we can log in as this user and access the auth (it has the
- # root role)
- cl = etcd.Client(port=6001, username='test_user',
- password='123456')
- ul = auth.EtcdUser(cl, 'root')
- try:
- ul.read()
- except etcd.EtcdInsufficientPermissions:
- self.fail("Reading auth with the new user is not possible")
-
- self.assertEquals(u.name, "test_user")
- self.assertEquals(u.roles, set(['guest', 'root']))
- # set roles as a list, it works!
- u.roles = ['guest', 'test_group']
- try:
- u.write()
- except:
- self.fail("updating a user you previously created fails")
- u.read()
- self.assertIn('test_group', u.roles)
-
- # Unauthorized access is properly handled
- ua = auth.EtcdUser(self.unauth_client, 'test_user')
- self.assertRaises(etcd.EtcdInsufficientPermissions, ua.write)
-
- # now let's test deletion
- du = auth.EtcdUser(self.client, 'user.does.not.exist')
- self.assertRaises(etcd.EtcdKeyNotFound, du.delete)
-
- # Delete test_user
- u.delete()
- self.assertRaises(etcd.EtcdKeyNotFound, u.read)
- # Permissions are properly handled
- self.assertRaises(etcd.EtcdInsufficientPermissions, ua.delete)
-
-
-class EtcdRoleTest(TestEtcdAuthBase):
- def test_names(self):
- r = auth.EtcdRole(self.client, 'guest')
- self.assertListEqual(r.names, [u'guest', u'root'])
-
- def test_read(self):
- r = auth.EtcdRole(self.client, 'guest')
- try:
- r.read()
- except:
- self.fail('Reading an existing role failed')
-
- self.assertEquals(r.acls, {'*': 'RW'})
- # We can actually skip most other read tests as they are common
- # with EtcdUser
-
- def test_write_and_delete(self):
- r = auth.EtcdRole(self.client, 'test_role')
- r.acls = {'*': 'R', '/test/*': 'RW'}
- try:
- r.write()
- except:
- self.fail("Writing a simple groups should not fail")
-
- r1 = auth.EtcdRole(self.client, 'test_role')
- r1.read()
- self.assertEquals(r1.acls, r.acls)
- r.revoke('/test/*', 'W')
- r.write()
- r1.read()
- self.assertEquals(r1.acls, {'*': 'R', '/test/*': 'R'})
- r.grant('/pub/*', 'RW')
- r.write()
- r1.read()
- self.assertEquals(r1.acls['/pub/*'], 'RW')
- # All other exceptions are tested by the user tests
- r1.name = None
- self.assertRaises(etcd.EtcdException, r1.write)
- # ditto for delete
- try:
- r.delete()
- except:
- self.fail("A normal delete should not fail")
- self.assertRaises(etcd.EtcdKeyNotFound, r.read)
--
2.7.4