auth.py 8.85 KB
Newer Older
Robin Sonnabend's avatar
Robin Sonnabend committed
1
2
import hmac
import hashlib
3
import ssl
Robin Sonnabend's avatar
Robin Sonnabend committed
4
from datetime import datetime
Robin Sonnabend's avatar
Robin Sonnabend committed
5

6
7

class User:
8
9
    def __init__(self, username, groups, all_groups, timestamp=None,
                 obsolete=False, permanent=False):
10
11
        self.username = username
        self.groups = groups
12
        self.all_groups = all_groups
Robin Sonnabend's avatar
Robin Sonnabend committed
13
14
15
16
        if timestamp is not None:
            self.timestamp = timestamp
        else:
            self.timestamp = datetime.now()
17
        self.obsolete = obsolete
18
        self.permanent = permanent
19
20

    def summarize(self):
Robin Sonnabend's avatar
Robin Sonnabend committed
21
        return ":".join((
22
            self.username, ",".join(self.groups), ",".join(self.all_groups),
23
24
            str(self.timestamp.timestamp()), str(self.obsolete),
            str(self.permanent)))
25
26
27

    @staticmethod
    def from_summary(summary):
28
29
        parts = summary.split(":", 5)
        if len(parts) != 6:
Robin Sonnabend's avatar
Robin Sonnabend committed
30
            return None
31
32
        (name, group_str, all_group_str, timestamp_str, obsolete_str,
            permanent_str) = parts
33
34
35
        timestamp = datetime.fromtimestamp(float(timestamp_str))
        obsolete = obsolete_str == "True"
        groups = group_str.split(",")
36
        all_groups = group_str.split(",")
37
        permanent = permanent_str == "True"
38
        return User(name, groups, all_groups, timestamp, obsolete, permanent)
39
40
41
42
43
44

    @staticmethod
    def from_hashstring(secure_string):
        summary, hash = secure_string.split("=", 1)
        return User.from_summary(summary)

Robin Sonnabend's avatar
Robin Sonnabend committed
45

Robin Sonnabend's avatar
Robin Sonnabend committed
46
47
48
49
class UserManager:
    def __init__(self, backends):
        self.backends = backends

50
    def login(self, username, password, permanent=False):
Robin Sonnabend's avatar
Robin Sonnabend committed
51
52
        for backend in self.backends:
            if backend.authenticate(username, password):
53
                groups = sorted(list(set(backend.groups(username, password))))
54
55
                all_groups = sorted(list(set(backend.all_groups(
                    username, password))))
Robin Sonnabend's avatar
Robin Sonnabend committed
56
                return User(
57
                    username, groups, all_groups, obsolete=backend.obsolete,
Robin Sonnabend's avatar
Robin Sonnabend committed
58
                    permanent=permanent)
Robin Sonnabend's avatar
Robin Sonnabend committed
59
60
61
        return None


Robin Sonnabend's avatar
Robin Sonnabend committed
62
63
64
65
class SecurityManager:
    def __init__(self, key, max_duration=300):
        self.maccer = hmac.new(key.encode("utf-8"), digestmod=hashlib.sha512)
        self.max_duration = max_duration
Robin Sonnabend's avatar
Robin Sonnabend committed
66

Robin Sonnabend's avatar
Robin Sonnabend committed
67
68
69
70
71
    def hash_user(self, user):
        maccer = self.maccer.copy()
        summary = user.summarize()
        maccer.update(summary.encode("utf-8"))
        return "{}={}".format(summary, maccer.hexdigest())
Robin Sonnabend's avatar
Robin Sonnabend committed
72

Robin Sonnabend's avatar
Robin Sonnabend committed
73
74
75
76
    def check_user(self, string):
        parts = string.split("=", 1)
        if len(parts) != 2:
            # wrong format, expecting summary:hash
77
            return False
Robin Sonnabend's avatar
Robin Sonnabend committed
78
79
80
81
82
83
84
        summary, hash = map(lambda s: s.encode("utf-8"), parts)
        maccer = self.maccer.copy()
        maccer.update(summary)
        user = User.from_hashstring(string)
        if user is None:
            return False
        session_duration = datetime.now() - user.timestamp
Robin Sonnabend's avatar
Robin Sonnabend committed
85
86
        macs_equal = hmac.compare_digest(
            maccer.hexdigest().encode("utf-8"), hash)
Robin Sonnabend's avatar
Robin Sonnabend committed
87
88
        time_short = int(session_duration.total_seconds()) < self.max_duration
        return macs_equal and (time_short or user.permanent)
Robin Sonnabend's avatar
Robin Sonnabend committed
89

90
91

class StaticUserManager:
Robin Sonnabend's avatar
Robin Sonnabend committed
92
    def __init__(self, users, obsolete=False):
93
94
95
96
        self.passwords = {
            username: password
            for (username, password, groups) in users
        }
Robin Sonnabend's avatar
Robin Sonnabend committed
97
        self.group_map = {
98
99
100
            username: groups
            for (username, password, groups) in users
        }
Robin Sonnabend's avatar
Robin Sonnabend committed
101
        self.obsolete = obsolete
102
103
104

    def authenticate(self, username, password):
        return (username in self.passwords
Robin Sonnabend's avatar
Robin Sonnabend committed
105
                and self.passwords[username] == password)
106
107

    def groups(self, username, password=None):
Robin Sonnabend's avatar
Robin Sonnabend committed
108
109
        if username in self.group_map:
            yield from self.group_map[username]
110

111
    def all_groups(self, username, password):
Robin Sonnabend's avatar
Robin Sonnabend committed
112
        yield from list(set(group for group in self.group_map.values()))
Robin Sonnabend's avatar
Robin Sonnabend committed
113

114

Robin Sonnabend's avatar
Robin Sonnabend committed
115
116
117
118
119
try:
    import ldap3

    class LdapManager:
        def __init__(self, host, user_dn, group_dn, port=636, use_ssl=True,
Robin Sonnabend's avatar
Robin Sonnabend committed
120
                     obsolete=False):
Robin Sonnabend's avatar
Robin Sonnabend committed
121
122
123
124
125
126
127
            self.server = ldap3.Server(host, port=port, use_ssl=use_ssl)
            self.user_dn = user_dn
            self.group_dn = group_dn
            self.obsolete = obsolete

        def authenticate(self, username, password):
            try:
Robin Sonnabend's avatar
Robin Sonnabend committed
128
129
                connection = ldap3.Connection(
                    self.server, self.user_dn.format(username), password)
Robin Sonnabend's avatar
Robin Sonnabend committed
130
131
132
133
134
135
136
137
138
139
140
141
142
143
                return connection.bind()
            except ldap3.core.exceptions.LDAPSocketOpenError:
                return False

        def groups(self, username, password=None):
            connection = ldap3.Connection(self.server)
            obj_def = ldap3.ObjectDef("posixgroup", connection)
            group_reader = ldap3.Reader(connection, obj_def, self.group_dn)
            username = username.lower()
            for group in group_reader.search():
                members = group.memberUid.value
                if members is not None and username in members:
                    yield group.cn.value

144
        def all_groups(self, username, password):
Robin Sonnabend's avatar
Robin Sonnabend committed
145
146
147
148
149
            connection = ldap3.Connection(self.server)
            obj_def = ldap3.ObjectDef("posixgroup", connection)
            group_reader = ldap3.Reader(connection, obj_def, self.group_dn)
            for group in group_reader.search():
                yield group.cn.value
Robin Sonnabend's avatar
Robin Sonnabend committed
150

Robin Sonnabend's avatar
Robin Sonnabend committed
151
152
    class ADManager:
        def __init__(self, host, domain, user_dn, group_dn,
Robin Sonnabend's avatar
Robin Sonnabend committed
153
                     port=636, use_ssl=True, ca_cert=None, obsolete=False):
Robin Sonnabend's avatar
Robin Sonnabend committed
154
155
            tls_config = ldap3.Tls(validate=ssl.CERT_REQUIRED)
            if ca_cert is not None:
Robin Sonnabend's avatar
Robin Sonnabend committed
156
157
158
159
                tls_config = ldap3.Tls(
                    validate=ssl.CERT_REQUIRED, ca_certs_file=ca_cert)
            self.server = ldap3.Server(
                host, port=port, use_ssl=use_ssl, tls=tls_config)
Robin Sonnabend's avatar
Robin Sonnabend committed
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
            self.domain = domain
            self.user_dn = user_dn
            self.group_dn = group_dn
            self.obsolete = obsolete

        def prepare_connection(self, username=None, password=None):
            if username is not None and password is not None:
                ad_user = "{}\\{}".format(self.domain, username)
                return ldap3.Connection(self.server, ad_user, password)
            return ldap3.Connection(self.server)

        def authenticate(self, username, password):
            try:
                return self.prepare_connection(username, password).bind()
            except ldap3.core.exceptions.LDAPSocketOpenError:
                return False

        def groups(self, username, password):
            connection = self.prepare_connection(username, password)
179
180
            if not connection.bind():
                return
Robin Sonnabend's avatar
Robin Sonnabend committed
181
182
            obj_def = ldap3.ObjectDef("user", connection)
            name_filter = "cn:={}".format(username)
Robin Sonnabend's avatar
Robin Sonnabend committed
183
184
            user_reader = ldap3.Reader(
                connection, obj_def, self.user_dn, name_filter)
Robin Sonnabend's avatar
Robin Sonnabend committed
185
            group_def = ldap3.ObjectDef("group", connection)
Robin Sonnabend's avatar
Robin Sonnabend committed
186

187
188
189
190
191
192
193
            all_group_reader = ldap3.Reader(
                connection, group_def, self.group_dn)
            all_groups = {
                group.primaryGroupToken.value: group
                for group in all_group_reader.search()
            }

Robin Sonnabend's avatar
Robin Sonnabend committed
194
            def _yield_recursive_groups(group_dn):
Robin Sonnabend's avatar
Robin Sonnabend committed
195
                group_reader = ldap3.Reader(
196
                    connection, group_def, group_dn)
Robin Sonnabend's avatar
Robin Sonnabend committed
197
198
199
200
201
                for entry in group_reader.search():
                    yield entry.name.value
                    for child in entry.memberOf:
                        yield from _yield_recursive_groups(child)
            for result in user_reader.search():
202
203
204
                yield from _yield_recursive_groups(
                    all_groups[result.primaryGroupID.value]
                    .distinguishedName.value)
Robin Sonnabend's avatar
Robin Sonnabend committed
205
206
207
                for group_dn in result.memberOf:
                    yield from _yield_recursive_groups(group_dn)

208
209
210
211
        def all_groups(self, username, password):
            connection = self.prepare_connection(username, password)
            if not connection.bind():
                return
Robin Sonnabend's avatar
Robin Sonnabend committed
212
213
            obj_def = ldap3.ObjectDef("group", connection)
            group_reader = ldap3.Reader(connection, obj_def, self.group_dn)
Robin Sonnabend's avatar
Robin Sonnabend committed
214
            for result in group_reader.search():
Robin Sonnabend's avatar
Robin Sonnabend committed
215
                yield result.name.value
Robin Sonnabend's avatar
Robin Sonnabend committed
216

217
except ImportError:
Robin Sonnabend's avatar
Robin Sonnabend committed
218
219
220
221
    pass


try:
Robin Sonnabend's avatar
Robin Sonnabend committed
222
223
224
    import grp
    import pwd
    import pam
Robin Sonnabend's avatar
Robin Sonnabend committed
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239

    class PAMManager:
        def __init__(self, obsolete=False):
            self.pam = pam.pam()
            self.obsolete = obsolete

        def authenticate(self, username, password):
            return self.pam.authenticate(username, password)

        def groups(self, username, password=None):
            yield grp.getgrgid(pwd.getpwnam(username).pw_gid).gr_name
            for group in grp.getgrall():
                if username in group.gr_mem:
                    yield group.gr_name

240
        def all_groups(self, username, password):
Robin Sonnabend's avatar
Robin Sonnabend committed
241
            for group in grp.getgrall():
Robin Sonnabend's avatar
Robin Sonnabend committed
242
                yield group.gr_name
243
except ImportError:
Robin Sonnabend's avatar
Robin Sonnabend committed
244
    pass