__init__.py 7.3 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
Gero Müller's avatar
Gero Müller committed
14
from ldap3.core.exceptions import LDAPNoSuchObjectResult, LDAPAttributeOrValueExistsResult, LDAPEntryAlreadyExistsResult
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
88
89
90
91
92
93
94
95
    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)
                    for member in group.users:
                        self.group_add_member(unicode(group.name), unicode(member.name))
                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,
Gero Müller's avatar
Gero Müller 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
127
            logger.info(" -> updated")
            changes = {
                'uid': _r(username),
                'cn': _r(username),
128
129
130
                'userPassword': _r('{CRYPT}' + password),
                'uidNumber': _r(uid + self.uid_offset),
                'gidNumber': _r(uid + self.uid_offset),
Gero Müller's avatar
Gero Müller committed
131
132
133
                'homeDirectory': _r('/home/%s/' % username)
            }
            self.connection.modify(dn, changes)
134

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


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

Gero Müller's avatar
Gero Müller committed
158
159
160
161
162
    def group_add_member(self, groupname, username):
        dn = 'cn=%s,%s' % (groupname, self.group_base)
        change = {
            'memberUid': [(ldap3.MODIFY_ADD, [username])],
        }
Gero Müller's avatar
Gero Müller committed
163
164
        try:
            self.connection.modify(dn, change)
Gero Müller's avatar
Gero Müller committed
165
        except LDAPAttributeOrValueExistsResult:
166
            pass
167

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

176

177

178
179
180
class LDAPExportExtension(AbstractExtension):

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

    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)
196
                self.sync_all_groups(session)
197

198
199
200
201
202
            vispa.register_callback("user.activate", self.on_activate)
            vispa.register_callback("user.set_password", self.on_set_password)

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

204
205
    def on_set_password(self, user):
        self.ldapexport.user_set_password(unicode(user.name), user.password)