__init__.py 7.73 KB
Newer Older
1
2
3
4
5
6
# -*- coding: utf-8 -*-

import logging

import vispa
from vispa.models.user import User
Gero Müller's avatar
Gero Müller committed
7
from vispa.models.group import Group
8
9
10
11
12
13
from vispa.server import AbstractExtension

from sqlalchemy.orm import scoped_session, sessionmaker

import ldap3
from ldap3.utils.log import set_library_log_detail_level, OFF, BASIC, NETWORK, EXTENDED
14
from ldap3.core.exceptions import LDAPNoSuchObjectResult, LDAPAttributeOrValueExistsResult, LDAPEntryAlreadyExistsResult, LDAPNoSuchAttributeResult
15
16
17

logger = logging.getLogger(__name__)

18

Gero Müller's avatar
Gero Müller committed
19
20
def _r(val):
    return [(ldap3.MODIFY_REPLACE, [val])]
21
22


23
class LDAPExport(object):
24

25
26
27
28
29
30
31
    def __init__(self):
        self.user_base = vispa.config("ldap-export", "user_base")
        self.group_base = vispa.config("ldap-export", "group_base")
        self.uid_offset = vispa.config("ldap-export", "uid_offset", 10000)
        self.private_group = vispa.config("ldap-export", "private_group", True)
        self.gid_offset = vispa.config("ldap-export", "gid_offset", 100000)
        set_library_log_detail_level(BASIC)
32

33
    def connect(self):
34
35
36
37
        url = vispa.config("ldap-export", "url")
        user = vispa.config("ldap-export", "user")
        password = vispa.config("ldap-export", "password")

38
39
        if not url:
            return
40

41
        self.connection = ldap3.Connection(url, user, password, auto_bind=True, raise_exceptions=True)
42

43
        return self.connection != None
44

45
    def delete_unknown(self, db):
46
        try:
47
48
            active_users = [unicode(u.name) for u in db.query(User, User.name).filter_by(status=User.ACTIVE)]
            groups = [unicode(g.name) for g in db.query(Group, Group.name)]
49
50
51
52
53
54
55
56
57

            self.connection.search(self.user_base, "(objectClass=posixAccount)", attributes=['uid'])
            for ldap_user in self.connection.entries:
                if ldap_user.uid not in active_users:
                    logger.info("Deleting unknown ldap user: %s", ldap_user.entry_get_dn())
                    self.connection.delete(ldap_user.entry_get_dn())

            self.connection.search(self.group_base, "(objectClass=posixGroup)", attributes=['cn'])
            for ldap_group in self.connection.entries:
58
59
60
                private_group = self.private_group and ldap_group.cn in active_users
                public_group = ldap_group.cn in groups
                if not private_group and not public_group:
61
62
                    logger.info("Deleting unknown ldap group: %s", ldap_group.entry_get_dn())
                    self.connection.delete(ldap_group.entry_get_dn())
63

64
65
66
        except:
            logger.exception("remove_invalid_ldap_entries")
        finally:
67
            db.remove()
68

69
    def sync_all_users(self, db):
70
        try:
71
            users = db.query(User).filter_by(status=User.ACTIVE)
72
73
            for user in users:
                try:
74
                    self.user_add(unicode(user.name), user.id, user.password)
75
                except:
Gero Müller's avatar
Gero Müller committed
76
                    logger.exception("sync_all_users")
77
78
79
        except:
            logger.exception("sync_all_users")
        finally:
80
            db.remove()
81

82
83
84
85
86
87
    def sync_all_groups(self, db):
        try:
            groups = db.query(Group)
            for group in groups:
                try:
                    self.group_add(unicode(group.name), group.id + self.gid_offset)
Martin Urban's avatar
Martin Urban committed
88
89
                    for assoc in group.get_users():
                        self.group_add_member(unicode(group.name), unicode(assoc.user.name))
90
91
92
93
94
95
                except:
                    logger.exception("sync_all_groups")
        except:
            logger.exception("sync_all_groups")
        finally:
            db.remove()
96

97
98
    def user_delete(self, name):
        dn = 'cn=%s,%s' % (name, self.user_base)
99
100
101
        logger.info("Delete user: %s", dn)
        self.connection.delete(dn)

102
        dn = 'cn=%s,%s' % (name, self.group_base)
103
104
105
        logger.info("Delete group: %s", dn)
        self.connection.delete(dn)

106
107
108
    def user_add(self, name, uid, password):
        username = name
        dn = 'cn=%s,%s' % (name, self.user_base)
109
110
        classes = ['top', 'person', 'organizationalPerson', 'inetOrgPerson', 'posixAccount', 'shadowAccount']
        attributes = {
Gero Müller's avatar
Gero Müller committed
111
112
113
            'uid': username,
            'cn': username,
            'sn': username,
114
            'userPassword': '{CRYPT}' + password,
115
            'loginShell': '/bin/bash',
116
117
            'uidNumber': uid + self.uid_offset,
            'gidNumber': uid + self.uid_offset,
Benjamin Fischer's avatar
Benjamin Fischer committed
118
            'homeDirectory': '/home/%s' % username
119
120
        }
        logger.info("Add user: %s, %s, %s", dn, classes, attributes)
Gero Müller's avatar
Gero Müller committed
121
122
        try:
            self.connection.add(dn, classes, attributes)
Gero Müller's avatar
Gero Müller committed
123
        except LDAPEntryAlreadyExistsResult:
Gero Müller's avatar
Gero Müller committed
124
125
126
            logger.info(" -> updated")
            changes = {
                'uid': _r(username),
127
128
129
                'userPassword': _r('{CRYPT}' + password),
                'uidNumber': _r(uid + self.uid_offset),
                'gidNumber': _r(uid + self.uid_offset),
Benjamin Fischer's avatar
Benjamin Fischer committed
130
                'homeDirectory': _r('/home/%s' % username)
Gero Müller's avatar
Gero Müller committed
131
132
            }
            self.connection.modify(dn, changes)
133

Gero Müller's avatar
Gero Müller committed
134
        if self.private_group:
135
            self.group_add(username, uid + self.uid_offset)
Gero Müller's avatar
Gero Müller committed
136
            self.group_add_member(username, username)
137
138


139
    def group_add(self, name, gid):
Gero Müller's avatar
Gero Müller committed
140
        dn = 'cn=%s,%s' % (name, self.group_base)
141
142
        classes = ['top', 'posixGroup']
        attributes = {
Gero Müller's avatar
Gero Müller committed
143
            'cn': name,
144
            'gidNumber': gid,
145
146
        }
        logger.info("Add group: %s, %s, %s", dn, classes, attributes)
Gero Müller's avatar
Gero Müller committed
147
148
        try:
            self.connection.add(dn, classes, attributes)
Gero Müller's avatar
Gero Müller committed
149
        except LDAPEntryAlreadyExistsResult:
Gero Müller's avatar
Gero Müller committed
150
151
            logger.info(" -> updated")
            changes = {
152
                'gidNumber': _r(gid),
Gero Müller's avatar
Gero Müller committed
153
154
            }
            self.connection.modify(dn, changes)
155

Gero Müller's avatar
Gero Müller committed
156
    def group_add_member(self, groupname, username):
157
158
159
        self.user_set_membership(username, groupname, True)

    def user_set_membership(self, username, groupname, active):
Gero Müller's avatar
Gero Müller committed
160
        try:
161
162
163
164
165
166
            self.connection.modify('cn=%s,%s' % (groupname, self.group_base), {
                'memberUid': [
                    (ldap3.MODIFY_ADD if active else ldap3.MODIFY_DELETE, [username])
                ],
            })
        except (LDAPAttributeOrValueExistsResult if active else LDAPNoSuchAttributeResult):
167
            pass
168

169
170
    def user_set_password(self, name, password):
        dn = 'cn=%s,%s' % (name, self.user_base)
171
        changes = {
172
            'userPassword': (2, '{CRYPT}' + password)
173
174
175
176
        }
        logger.info("Change password: %s, %s", dn, changes)
        self.connection.modify(dn, changes)

177

178

179
class LDAPExportExtension(AbstractExtension):
180
    server_only = True
181
182

    def name(self):
183
        return "ldap_export"
184
185
186
187
188
189
190
191
192
193
194
195
196
197

    def dependencies(self):
        return []

    def setup(self):
        self.ldapexport = LDAPExport()
        if self.ldapexport.connect():

            if vispa.config("ldap-export", "sync_on_startup", False):
                session = scoped_session(sessionmaker(autoflush=True, autocommit=False))
                session.configure(bind=self.server._engine)
                if vispa.config("ldap-export", "delete_unknown", False):
                    self.delete_unknown(session)
                self.sync_all_users(session)
198
                self.sync_all_groups(session)
199

200
201
            vispa.register_callback("user.activate", self.on_activate)
            vispa.register_callback("user.set_password", self.on_set_password)
202
            vispa.register_callback("user.group.membership", self.on_set_membership)
203
204
205

    def on_activate(self, user):
        self.ldapexport.user_add(unicode(user.name), user.id, user.password)
206

207
208
    def on_set_password(self, user):
        self.ldapexport.user_set_password(unicode(user.name), user.password)
209
210
211

    def on_set_membership(self, user, group, active):
        self.ldapexport.user_set_membership(unicode(user.name), unicode(group.name), bool(active))