Aufgrund einer Störung des s3 Storage, könnten in nächster Zeit folgende GitLab Funktionen nicht zur Verfügung stehen: LFS, Container Registry, Job Artifacs, Uploads (Wiki, Bilder, Projekt-Exporte). Wir bitten um Verständnis. Es wird mit Hochdruck an der Behebung des Problems gearbeitet. Weitere Informationen zur Störung des Object Storage finden Sie hier: https://maintenance.itc.rwth-aachen.de/ticket/status/messages/59-object-storage-pilot

__init__.py 7.71 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

            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:
53 54
                    logger.info("Deleting unknown ldap user: %s", ldap_user.entry_dn)
                    self.connection.delete(ldap_user.entry_dn)
55 56 57

            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_dn)
                    self.connection.delete(ldap_group.entry_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))