auth.py 8.15 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:
Robin Sonnabend's avatar
Robin Sonnabend committed
8
    def __init__(self, username, groups, timestamp=None, obsolete=False,
Robin Sonnabend's avatar
Robin Sonnabend committed
9
                 permanent=False):
10
11
        self.username = username
        self.groups = groups
Robin Sonnabend's avatar
Robin Sonnabend committed
12
13
14
15
        if timestamp is not None:
            self.timestamp = timestamp
        else:
            self.timestamp = datetime.now()
16
        self.obsolete = obsolete
17
        self.permanent = permanent
18
19

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

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

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

Robin Sonnabend's avatar
Robin Sonnabend committed
41

Robin Sonnabend's avatar
Robin Sonnabend committed
42
43
44
45
class UserManager:
    def __init__(self, backends):
        self.backends = backends

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

    def all_groups(self):
        for backend in self.backends:
            yield from backend.all_groups()


Robin Sonnabend's avatar
Robin Sonnabend committed
60
61
62
63
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
64

Robin Sonnabend's avatar
Robin Sonnabend committed
65
66
67
68
69
    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
70

Robin Sonnabend's avatar
Robin Sonnabend committed
71
72
73
74
    def check_user(self, string):
        parts = string.split("=", 1)
        if len(parts) != 2:
            # wrong format, expecting summary:hash
75
            return False
Robin Sonnabend's avatar
Robin Sonnabend committed
76
77
78
79
80
81
82
        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
83
84
        macs_equal = hmac.compare_digest(
            maccer.hexdigest().encode("utf-8"), hash)
Robin Sonnabend's avatar
Robin Sonnabend committed
85
86
        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
87

88
89

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

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

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

    def all_groups(self):
Robin Sonnabend's avatar
Robin Sonnabend committed
110
        yield from list(set(group for group in self.group_map.values()))
Robin Sonnabend's avatar
Robin Sonnabend committed
111

112

Robin Sonnabend's avatar
Robin Sonnabend committed
113
114
115
116
117
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
118
                     obsolete=False):
Robin Sonnabend's avatar
Robin Sonnabend committed
119
120
121
122
123
124
125
            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
126
127
                connection = ldap3.Connection(
                    self.server, self.user_dn.format(username), password)
Robin Sonnabend's avatar
Robin Sonnabend committed
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
                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

        def all_groups(self):
            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
148

Robin Sonnabend's avatar
Robin Sonnabend committed
149
150
    class ADManager:
        def __init__(self, host, domain, user_dn, group_dn,
Robin Sonnabend's avatar
Robin Sonnabend committed
151
                     port=636, use_ssl=True, ca_cert=None, obsolete=False):
Robin Sonnabend's avatar
Robin Sonnabend committed
152
153
            tls_config = ldap3.Tls(validate=ssl.CERT_REQUIRED)
            if ca_cert is not None:
Robin Sonnabend's avatar
Robin Sonnabend committed
154
155
156
157
                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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
            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)
            connection.bind()
            obj_def = ldap3.ObjectDef("user", connection)
            name_filter = "cn:={}".format(username)
Robin Sonnabend's avatar
Robin Sonnabend committed
180
181
            user_reader = ldap3.Reader(
                connection, obj_def, self.user_dn, name_filter)
Robin Sonnabend's avatar
Robin Sonnabend committed
182
            group_def = ldap3.ObjectDef("group", connection)
Robin Sonnabend's avatar
Robin Sonnabend committed
183

Robin Sonnabend's avatar
Robin Sonnabend committed
184
            def _yield_recursive_groups(group_dn):
Robin Sonnabend's avatar
Robin Sonnabend committed
185
186
                group_reader = ldap3.Reader(
                    connection, group_def, group_dn, None)
Robin Sonnabend's avatar
Robin Sonnabend committed
187
188
189
190
191
192
193
194
195
196
197
198
199
                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():
                for group_dn in result.memberOf:
                    yield from _yield_recursive_groups(group_dn)

        def all_groups(self):
            connection = self.prepare_connection()
            connection.bind()
            obj_def = ldap3.ObjectDef("group", connection)
            group_reader = ldap3.Reader(connection, obj_def, self.group_dn)
Robin Sonnabend's avatar
Robin Sonnabend committed
200
            for result in group_reader.search():
Robin Sonnabend's avatar
Robin Sonnabend committed
201
                yield result.name.value
Robin Sonnabend's avatar
Robin Sonnabend committed
202

Robin Sonnabend's avatar
Robin Sonnabend committed
203
204
205
206
207
except ModuleNotFoundError:
    pass


try:
Robin Sonnabend's avatar
Robin Sonnabend committed
208
209
210
    import grp
    import pwd
    import pam
Robin Sonnabend's avatar
Robin Sonnabend committed
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228

    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):
            print(username)
            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

        def all_groups(self):
            for group in grp.getgrall():
Robin Sonnabend's avatar
Robin Sonnabend committed
229
                yield group.gr_name
Robin Sonnabend's avatar
Robin Sonnabend committed
230
231
except ModuleNotFoundError:
    pass