__init__.py 6.43 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
14
15
16
17
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
from ldap3.core.exceptions import LDAPNoSuchObjectResult

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
32
33
    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)
    
    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
42
43
44
45
        self.connection = ldap3.Connection(url, user, password, auto_bind=True, raise_exceptions=True)
        
        return self.connection != None
        
    def delete_unknown(self, db):
46
        try:
47
            active_users = db.query(User, User.name).filter_by(status=User.ACTIVE).all()
48
49
50
51
52
53
54
55
56

            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:
Gero Müller's avatar
Gero Müller committed
57
                if ldap_group.cn not in active_users or not self.private_group:
58
59
60
61
62
63
                    logger.info("Deleting unknown ldap group: %s", ldap_group.entry_get_dn())
                    self.connection.delete(ldap_group.entry_get_dn())
                
        except:
            logger.exception("remove_invalid_ldap_entries")
        finally:
64
            db.remove()
65

66
    def sync_all_users(self, db):
67
        try:
68
            users = db.query(User).filter_by(status=User.ACTIVE)
69
70
71
72
            for user in users:
                try:
                    self.user_add(user)
                except:
Gero Müller's avatar
Gero Müller committed
73
                    logger.exception("sync_all_users")
74
75
76
        except:
            logger.exception("sync_all_users")
        finally:
77
            db.remove()
78
79
80
81
82
83
84
85
86
87
88

    def user_delete(self, user):
        dn = 'cn=%s,%s' % (unicode(user.name), self.user_base)
        logger.info("Delete user: %s", dn)
        self.connection.delete(dn)

        dn = 'cn=%s,%s' % (unicode(user.name), self.group_base)
        logger.info("Delete group: %s", dn)
        self.connection.delete(dn)

    def user_add(self, user):
Gero Müller's avatar
Gero Müller committed
89
        username = unicode(user.name)
90
91
92
        dn = 'cn=%s,%s' % (unicode(user.name), self.user_base)
        classes = ['top', 'person', 'organizationalPerson', 'inetOrgPerson', 'posixAccount', 'shadowAccount']
        attributes = {
Gero Müller's avatar
Gero Müller committed
93
94
95
            'uid': username,
            'cn': username,
            'sn': username,
96
97
98
            'userPassword': '{CRYPT}' + unicode(user.password),
            'loginShell': '/bin/bash',
            'uidNumber': user.id + self.uid_offset,
Gero Müller's avatar
Gero Müller committed
99
100
            'gidNumber': user.id + self.uid_offset,
            'homeDirectory': '/home/%s/' % username
101
102
        }
        logger.info("Add user: %s, %s, %s", dn, classes, attributes)
Gero Müller's avatar
Gero Müller committed
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
        try:
            self.connection.add(dn, classes, attributes)
        except ldap3.LDAPEntryAlreadyExistsResult:
            logger.info(" -> updated")
            changes = {
                'uid': _r(username),
                'cn': _r(username),
                'userPassword': _r('{CRYPT}' + unicode(user.password)),
                'uidNumber': _r(user.id + self.uid_offset),
                'gidNumber': _r(user.id + self.uid_offset),
                'homeDirectory': _r('/home/%s/' % username)
            }
            self.connection.modify(dn, changes)
        
        if self.private_group:
            self.group_add(username, user.id + self.uid_offset)
            self.group_add_member(username, username)
120
121


122
    def group_add(self, name, gid):
Gero Müller's avatar
Gero Müller committed
123
        dn = 'cn=%s,%s' % (name, self.group_base)
124
125
        classes = ['top', 'posixGroup']
        attributes = {
Gero Müller's avatar
Gero Müller committed
126
            'cn': name,
127
            'gidNumber': gid,
128
129
        }
        logger.info("Add group: %s, %s, %s", dn, classes, attributes)
Gero Müller's avatar
Gero Müller committed
130
131
132
133
134
135
        try:
            self.connection.add(dn, classes, attributes)
        except ldap3.LDAPEntryAlreadyExistsResult:
            logger.info(" -> updated")
            changes = {
                'cn': _r(name),
136
                'gidNumber': _r(gid),
Gero Müller's avatar
Gero Müller committed
137
138
139
140
141
142
143
144
            }
            self.connection.modify(dn, changes)
    
    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
145
146
147
148
        try:
            self.connection.modify(dn, change)
        except ldap3.LDAPAttributeOrValueExistsResult:
            pass            
149
150
151
152
153
154
155
156
157

    def user_set_password(self, user):
        dn = 'cn=%s,%s' % (unicode(user.name), self.user_base)
        changes = {
            'userPassword': (2, '{CRYPT}' + unicode(user.password))
        }
        logger.info("Change password: %s, %s", dn, changes)
        self.connection.modify(dn, changes)

158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182

        
class LDAPExportExtension(AbstractExtension):

    def name(self):
        return "ldap-export"

    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)
            
            vispa.register_callback("user.activate", self.ldapexport.user_add)
            vispa.register_callback("user.set_password", self.ldapexport.user_set_password)