summaryrefslogtreecommitdiff
path: root/kazoo/security.py
blob: e473360c8d2199b473fca55510da2a7c9d7ac283 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
"""Kazoo Security"""
from base64 import b64encode
from collections import namedtuple
import hashlib


# Represents a Zookeeper ID and ACL object
Id = namedtuple('Id', 'scheme id')


class ACL(namedtuple('ACL', 'perms id')):
    """An ACL for a Zookeeper Node

    An ACL object is created by using an :class:`Id` object along with
    a :class:`Permissions` setting. For convenience,
    :meth:`make_digest_acl` should be used to create an ACL object with
    the desired scheme, id, and permissions.
    """
    @property
    def acl_list(self):
        perms = []
        if self.perms & Permissions.ALL == Permissions.ALL:
            perms.append('ALL')
            return perms
        if self.perms & Permissions.READ == Permissions.READ:
            perms.append('READ')
        if self.perms & Permissions.WRITE == Permissions.WRITE:
            perms.append('WRITE')
        if self.perms & Permissions.CREATE == Permissions.CREATE:
            perms.append('CREATE')
        if self.perms & Permissions.DELETE == Permissions.DELETE:
            perms.append('DELETE')
        if self.perms & Permissions.ADMIN == Permissions.ADMIN:
            perms.append('ADMIN')
        return perms

    def __repr__(self):
        return 'ACL(perms=%r, acl_list=%s, id=%r)' % (
            self.perms, self.acl_list, self.id)


class Permissions(object):
    READ = 1
    WRITE = 2
    CREATE = 4
    DELETE = 8
    ADMIN = 16
    ALL = 31


# Shortcuts for common Ids
ANYONE_ID_UNSAFE = Id('world', 'anyone')
AUTH_IDS = Id('auth', '')

# Shortcuts for common ACLs
OPEN_ACL_UNSAFE = [ACL(Permissions.ALL, ANYONE_ID_UNSAFE)]
CREATOR_ALL_ACL = [ACL(Permissions.ALL, AUTH_IDS)]
READ_ACL_UNSAFE = [ACL(Permissions.READ, ANYONE_ID_UNSAFE)]


def make_digest_acl_credential(username, password):
    """Create a SHA1 digest credential.

    .. note::

        This function uses UTF-8 to encode non-ASCII codepoints,
        whereas ZooKeeper uses the "default locale" for decoding.  It
        may be a good idea to start the JVM with `-Dfile.encoding=UTF-8`
        in non-UTF-8 locales.
        See: https://github.com/python-zk/kazoo/pull/584

    """
    credential = username.encode('utf-8') + b":" + password.encode('utf-8')
    cred_hash = b64encode(hashlib.sha1(credential).digest()).strip()
    return username + ":" + cred_hash.decode('utf-8')


def make_acl(scheme, credential, read=False, write=False,
             create=False, delete=False, admin=False, all=False):
    """Given a scheme and credential, return an :class:`ACL` object
    appropriate for use with Kazoo.

    :param scheme: The scheme to use. I.e. `digest`.
    :param credential:
        A colon separated username, password. The password should be
        hashed with the `scheme` specified. The
        :meth:`make_digest_acl_credential` method will create and
        return a credential appropriate for use with the `digest`
        scheme.
    :param write: Write permission.
    :type write: bool
    :param create: Create permission.
    :type create: bool
    :param delete: Delete permission.
    :type delete: bool
    :param admin: Admin permission.
    :type admin: bool
    :param all: All permissions.
    :type all: bool

    :rtype: :class:`ACL`

    """
    if all:
        permissions = Permissions.ALL
    else:
        permissions = 0
        if read:
            permissions |= Permissions.READ
        if write:
            permissions |= Permissions.WRITE
        if create:
            permissions |= Permissions.CREATE
        if delete:
            permissions |= Permissions.DELETE
        if admin:
            permissions |= Permissions.ADMIN
    return ACL(permissions, Id(scheme, credential))


def make_digest_acl(username, password, read=False, write=False,
                    create=False, delete=False, admin=False, all=False):
    """Create a digest ACL for Zookeeper with the given permissions

    This method combines :meth:`make_digest_acl_credential` and
    :meth:`make_acl` to create an :class:`ACL` object appropriate for
    use with Kazoo's ACL methods.

    :param username: Username to use for the ACL.
    :param password: A plain-text password to hash.
    :param write: Write permission.
    :type write: bool
    :param create: Create permission.
    :type create: bool
    :param delete: Delete permission.
    :type delete: bool
    :param admin: Admin permission.
    :type admin: bool
    :param all: All permissions.
    :type all: bool

    :rtype: :class:`ACL`

    """
    cred = make_digest_acl_credential(username, password)
    return make_acl("digest", cred, read=read, write=write, create=create,
                    delete=delete, admin=admin, all=all)