Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add default ACL support to KazooClient

  • Loading branch information...
commit 33cd1523110660872caccf33de80628bdd1467fc 1 parent 99a96d7
@labisso labisso authored
View
64 kazoo/client.py
@@ -1,8 +1,9 @@
import logging
from os.path import split
+import hashlib
from kazoo.zkclient import ZooKeeperClient, WatchedEvent, KeeperState,\
- EventType, NodeExistsException, NoNodeException
+ EventType, NodeExistsException, NoNodeException, AclPermission
from kazoo.retry import KazooRetry
log = logging.getLogger(__name__)
@@ -25,13 +26,43 @@ class KazooState(object):
LOST = "LOST"
+def make_digest_acl_credential(username, password):
+ credential = "%s:%s" % (username, password)
+ cred_hash = hashlib.sha1(credential).digest().encode('base64').strip()
+ return "%s:%s" % (username, cred_hash)
+
+def make_acl(scheme, credential, read=False, write=False,
+ create=False, delete=False, admin=False, all=False):
+ if all:
+ permissions = AclPermission.ALL
+ else:
+ permissions = 0
+ if read:
+ permissions |= AclPermission.READ
+ if write:
+ permissions |= AclPermission.WRITE
+ if create:
+ permissions |= AclPermission.CREATE
+ if delete:
+ permissions |= AclPermission.DELETE
+ if admin:
+ permissions |= AclPermission.ADMIN
+
+ return dict(scheme=scheme, id=credential, perms=permissions)
+
+def make_digest_acl(username, password, read=False, write=False,
+ create=False, delete=False, admin=False, all=False):
+ cred = make_digest_acl_credential(username, password)
+ return make_acl("digest", cred, read=read, write=write, create=create,
+ delete=delete, admin=admin, all=all)
+
class KazooClient(object):
"""Higher-level ZooKeeper client.
Supports retries, namespacing, easier state monitoring; saves kittens.
"""
- def __init__(self, hosts, namespace=None, timeout=10.0, max_retries=None):
+ def __init__(self, hosts, namespace=None, timeout=10.0, max_retries=None, default_acl=None):
# remove any trailing slashes
if namespace:
namespace = namespace.rstrip('/')
@@ -48,6 +79,8 @@ def __init__(self, hosts, namespace=None, timeout=10.0, max_retries=None):
self.state = KazooState.LOST
self.state_listeners = set()
+ self.default_acl = default_acl
+
def _session_watcher(self, event):
"""called by the underlying ZK client when the connection state changes
"""
@@ -77,9 +110,9 @@ def _make_state_change(self, state):
except Exception:
log.exception("Error in connection state listener")
- def _assure_namespace(self):
+ def _assure_namespace(self, acl=None):
if self._needs_ensure_path:
- self.ensure_path('/')
+ self.ensure_path('/', acl=acl)
self._needs_ensure_path = False
def add_listener(self, listener):
@@ -130,9 +163,13 @@ def create(self, path, value, acl=None, ephemeral=False, sequence=False,
@param makepath: boolean indicating whether to create path if it doesn't exist
@return: real path of the new node
"""
- self._assure_namespace()
+ self._assure_namespace(acl=acl)
path = self.namespace_path(path)
+
+ if acl is None and self.default_acl:
+ acl = self.default_acl
+
try:
realpath = self.zk.create(path, value, acl=acl,
ephemeral=ephemeral, sequence=sequence)
@@ -147,7 +184,7 @@ def create(self, path, value, acl=None, ephemeral=False, sequence=False,
parent, _ = split(path)
# using the inner call directly because path is already namespaced
- self._inner_ensure_path(parent)
+ self._inner_ensure_path(parent, acl)
# now retry
realpath = self.zk.create(path, value, acl=acl,
@@ -217,27 +254,28 @@ def delete(self, path, version=-1):
@param path: path of node to delete
@param version: version of node to delete, or -1 for any
"""
- self._assure_namespace()
-
path = self.namespace_path(path)
return self.zk.delete(path, version)
- def ensure_path(self, path):
+ def ensure_path(self, path, acl=None):
"""Recursively create a path if it doesn't exist
"""
path = self.namespace_path(path)
- self._inner_ensure_path(path)
+ self._inner_ensure_path(path, acl)
- def _inner_ensure_path(self, path):
+ def _inner_ensure_path(self, path, acl):
if self.zk.exists(path):
return
+ if acl is None and self.default_acl:
+ acl = self.default_acl
+
parent, node = split(path)
if parent != "/":
- self._inner_ensure_path(parent)
+ self._inner_ensure_path(parent, acl)
try:
- self.zk.create(path, "")
+ self.zk.create(path, "", acl=acl)
except NodeExistsException:
# someone else created the node. how sweet!
pass
View
5 kazoo/test/__init__.py
@@ -40,7 +40,10 @@ def setUp(self):
self.hosts = get_hosts_or_skip()
self.namespace = "/kazootests" + uuid.uuid4().hex
- self.client = KazooClient(self.hosts, namespace=self.namespace)
+ self.client = self._get_client()
+
+ def _get_client(self):
+ return KazooClient(self.hosts, namespace=self.namespace)
def tearDown(self):
if self.client.state == KazooState.LOST:
View
33 kazoo/test/test_client.py
@@ -1,11 +1,12 @@
import threading
+import uuid
-from kazoo.client import KazooState
+from kazoo.client import KazooState, make_digest_acl
from kazoo.zkclient import EventType
from kazoo.test import KazooTestCase
-from kazoo.exceptions import NoNodeException
+from kazoo.exceptions import NoNodeException, NoAuthException
-class ZooKeeperClientTests(KazooTestCase):
+class KazooClientTests(KazooTestCase):
def test_namespace(self):
namespace = self.namespace
@@ -120,3 +121,29 @@ def test_create_makepath(self):
data, stat = self.client.get("/1/2/3/4/5")
self.assertEqual(data, "val2")
+ def test_auth(self):
+
+ self.client.connect()
+
+ username = uuid.uuid4().hex
+ password = uuid.uuid4().hex
+
+ digest_auth = "%s:%s" % (username, password)
+ acl = make_digest_acl(username, password, all=True)
+
+ self.client.add_auth("digest", digest_auth)
+
+ self.client.default_acl = (acl,)
+
+ self.client.create("/1", "")
+ self.client.create("/1/2", "")
+
+ eve = self._get_client()
+ eve.connect()
+
+ self.assertRaises(NoAuthException, eve.get, "/1/2")
+
+ # try again with the wrong auth token
+ eve.add_auth("digest", "badbad:bad")
+
+ self.assertRaises(NoAuthException, eve.get, "/1/2")
View
5 kazoo/test/test_zkclient.py
@@ -1,7 +1,6 @@
import uuid
import threading
-from kazoo.zkclient import ZooKeeperClient
from kazoo.test import KazooTestCase
class ZooKeeperClientTests(KazooTestCase):
@@ -89,7 +88,7 @@ def w(watch_event):
self.assertEqual(watch_event.path, nodepath)
event.set()
- raise Exception("this is really bad")
+ raise Exception("test exception in callback")
exists = self.zk.exists(nodepath, watch=w)
self.assertIsNone(exists)
@@ -113,4 +112,4 @@ def test_create_delete(self):
self.assertIsNone(exists)
-
+
View
10 kazoo/zkclient.py
@@ -18,6 +18,16 @@
ZK_OPEN_ACL_UNSAFE = {"perms": zookeeper.PERM_ALL, "scheme": "world",
"id": "anyone"}
+
+class AclPermission(object):
+ READ = zookeeper.PERM_READ
+ WRITE = zookeeper.PERM_WRITE
+ CREATE = zookeeper.PERM_CREATE
+ DELETE = zookeeper.PERM_DELETE
+ ADMIN = zookeeper.PERM_ADMIN
+ ALL = zookeeper.PERM_ALL
+
+
class KeeperState(object):
ASSOCIATING = zookeeper.ASSOCIATING_STATE
AUTH_FAILED = zookeeper.AUTH_FAILED_STATE
Please sign in to comment.
Something went wrong with that request. Please try again.