auth.py 7.52 KB
Newer Older
1
import hmac, hashlib
2
import ssl
Robin Sonnabend's avatar
Robin Sonnabend committed
3
4
5
import ldap3
from ldap3.utils.dn import parse_dn
from datetime import datetime
Robin Sonnabend's avatar
Robin Sonnabend committed
6
import grp, pwd, pam
7
8

class User:
9
    def __init__(self, username, groups, timestamp=None, obsolete=False, 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):
20
        return "{}:{}:{}:{}:{}".format(self.username, ",".join(self.groups), str(self.timestamp.timestamp()), self.obsolete, self.permanent)
21
22
23

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

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

Robin Sonnabend's avatar
Robin Sonnabend committed
39
40
41
42
class UserManager:
    def __init__(self, backends):
        self.backends = backends

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

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


class LdapManager:
56
    def __init__(self, host, user_dn, group_dn, port=636, use_ssl=True, obsolete=False):
Robin Sonnabend's avatar
Robin Sonnabend committed
57
58
59
        self.server = ldap3.Server(host, port=port, use_ssl=use_ssl)
        self.user_dn = user_dn
        self.group_dn = group_dn
60
        self.obsolete = obsolete
Robin Sonnabend's avatar
Robin Sonnabend committed
61
62

    def authenticate(self, username, password):
63
64
65
66
67
        try:
            connection = ldap3.Connection(self.server, self.user_dn.format(username), password)
            return connection.bind()
        except ldap3.core.exceptions.LDAPSocketOpenError:
            return False
Robin Sonnabend's avatar
Robin Sonnabend committed
68
69
70
71
72

    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)
73
        username = username.lower()
Robin Sonnabend's avatar
Robin Sonnabend committed
74
75
76
77
78
79
80
81
82
83
84
85
        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

86

Robin Sonnabend's avatar
Robin Sonnabend committed
87
class ADManager:
88
    def __init__(self, host, domain, user_dn, group_dn,
89
        port=636, use_ssl=True, ca_cert=None, obsolete=False):
90
91
92
93
94
95
        tls_config = ldap3.Tls(validate=ssl.CERT_REQUIRED)
        if ca_cert is not None:
            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
96
97
98
        self.domain = domain
        self.user_dn = user_dn
        self.group_dn = group_dn
99
        self.obsolete = obsolete
Robin Sonnabend's avatar
Robin Sonnabend committed
100
101
102
103
104
105
106
107

    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):
108
109
110
111
        try:
            return self.prepare_connection(username, password).bind()
        except ldap3.core.exceptions.LDAPSocketOpenError:
            return False
Robin Sonnabend's avatar
Robin Sonnabend committed
112
113
114
115
116
117
118

    def groups(self, username, password):
        connection = self.prepare_connection(username, password)
        connection.bind()
        obj_def = ldap3.ObjectDef("user", connection)
        name_filter = "cn:={}".format(username)
        user_reader = ldap3.Reader(connection, obj_def, self.user_dn, name_filter)
119
120
121
122
123
124
125
        group_def = ldap3.ObjectDef("group", connection)
        def _yield_recursive_groups(group_dn):
            group_reader = ldap3.Reader(connection, group_def, group_dn, None)
            for entry in group_reader.search():
                yield entry.name.value
                for child in entry.memberOf:
                    yield from _yield_recursive_groups(child)
Robin Sonnabend's avatar
Robin Sonnabend committed
126
127
        for result in user_reader.search():
            for group_dn in result.memberOf:
128
129
                yield from _yield_recursive_groups(group_dn)

Robin Sonnabend's avatar
Robin Sonnabend committed
130
131
132
133
134
135
136
137
138

    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)
        for result in reader.search():
            yield result.name.value

139
140

class StaticUserManager:
Robin Sonnabend's avatar
Robin Sonnabend committed
141
    def __init__(self, users, obsolete=False):
142
143
144
145
146
147
148
149
        self.passwords = {
            username: password
            for (username, password, groups) in users
        }
        self.groups = {
            username: groups
            for (username, password, groups) in users
        }
Robin Sonnabend's avatar
Robin Sonnabend committed
150
        self.obsolete = obsolete
151
152
153
154
155
156
157
158
159
160

    def authenticate(self, username, password):
        return (username in self.passwords
            and self.passwords[username] == password)

    def groups(self, username, password=None):
        if username in self.groups:
            yield from self.groups[username]

    def all_groups(self):
Robin Sonnabend's avatar
Robin Sonnabend committed
161
162
        yield from list(set(group for group in groups.values()))

163

Robin Sonnabend's avatar
Robin Sonnabend committed
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
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():
            yield group.gr_name
182

183
class SecurityManager:
Robin Sonnabend's avatar
Robin Sonnabend committed
184
    def __init__(self, key, max_duration=300):
185
        self.maccer = hmac.new(key.encode("utf-8"), digestmod=hashlib.sha512)
Robin Sonnabend's avatar
Robin Sonnabend committed
186
        self.max_duration = max_duration
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201

    def hash_user(self, user):
        maccer = self.maccer.copy()
        summary = user.summarize()
        maccer.update(summary.encode("utf-8"))
        return "{}={}".format(summary, maccer.hexdigest())

    def check_user(self, string):
        parts = string.split("=", 1)
        if len(parts) != 2:
            # wrong format, expecting summary:hash
            return False
        summary, hash = map(lambda s: s.encode("utf-8"), parts)
        maccer = self.maccer.copy()
        maccer.update(summary)
202
203
204
205
        user = User.from_hashstring(string)
        if user is None:
            return False
        session_duration = datetime.now() - user.timestamp
Robin Sonnabend's avatar
Robin Sonnabend committed
206
207
        macs_equal = hmac.compare_digest(maccer.hexdigest().encode("utf-8"), hash)
        time_short = int(session_duration.total_seconds()) < self.max_duration 
208
        return macs_equal and (time_short or user.permanent)
Robin Sonnabend's avatar
Robin Sonnabend committed
209