#!/bin/usr/env python3 import ldap3 import utils import config import time def check_result(conn): if conn.result['result'] != 0: #see https://www.ietf.org/rfc/rfc4511.txt 4.1.9 raise RuntimeError(str(conn.result)) def connection_decorator(f): with ldap3.Connection(config.SERVER, auto_bind=True, client_strategy=ldap3.STRATEGY_SYNC_RESTARTABLE) as conn: def wrapped_f(*args, **kwargs): return f(args[0], conn, *args[1:], **kwargs) return wrapped_f def admin_connection_decorator(f): with ldap3.Connection(config.SERVER, user=config.ADMIN_CN, password=config.ADMIN_PASSWORD, auto_bind=True, client_strategy=ldap3.STRATEGY_SYNC_RESTARTABLE) as conn: def wrapped_f(*args, **kwargs): return f(args[0], conn, *args[1:], **kwargs) return wrapped_f class LILiK_USER(object): _attributes = ['cn'] _read_only_attributes = ['uid'] _flags = {'mail': 'accountActive'} _hosts = ['ltsp', 'users'] _groups = ['admin', 'wiki', 'lilik.it', 'cloud', 'projects', 'teambox', 'im'] _posix_groups = ['users_sites'] def __init__(self, entry, posix_groups): results = {} for attribute in self._attributes + self._read_only_attributes: self.__setattr__(attribute, str(entry.__getattr__(attribute))) services = {} for service, flag in self._flags.items(): services[service] = flag in entry and str(entry[flag]) == 'TRUE' for host in self._hosts: services[host] = 'host' in entry and host in entry['host'] for group in self._groups: services[group] = 'memberOf' in entry and utils.ldap_path("cn=%s"%utils.clean_value(group), config.GROUP, config.DOMAIN) in list(entry['memberOf']) for posix_group in self._posix_groups: services[posix_group] = posix_group in posix_groups and str(entry.uid) in list(posix_groups[posix_group]) self.__setattr__('services', services) def to_json(self): return json.dumps(self, default=lambda o: o.__dict__,) def to_dict(self): return self.__dict__ @admin_connection_decorator def diff(self, conn, new_lilik_user): user_cn = utils.ldap_path('uid=%s'%self.uid, config.PEOPLE, config.DOMAIN) return utils.DictDiffer(new_lilik_user, self.__dict__) @admin_connection_decorator def update(self, conn, new_lilik_user): user_cn = utils.ldap_path('uid=%s'%self.uid, config.PEOPLE, config.DOMAIN) diff = utils.DictDiffer(new_lilik_user, self.__dict__) modifiers = {user_cn: {}} if 'userPassword' in diff.added() and new_lilik_user['userPassword']: action = ldap3.MODIFY_REPLACE hashed = ldap3.utils.hashed(HASHED_SALTED_SHA, new_lilik_user['userPassword']) modifiers[user_cn]['userPassword'] = [(action, [hashed])] for changed in diff.changed(): if changed == 'services': services_diff = utils.DictDiffer(self.__dict__[changed], new_lilik_user[changed]) for service_changed in services_diff.changed(): if service_changed in self._flags: flag = self._flags[service_changed] modifiers[user_cn][flag] = [(ldap3.MODIFY_REPLACE, [str(new_lilik_user['services'][service_changed]).upper()])] elif service_changed in self._hosts: action = ldap3.MODIFY_ADD if new_lilik_user['services'][service_changed] else ldap3.MODIFY_DELETE modifiers[user_cn]['host'] = [(action, [service_changed])] elif service_changed in self._groups: group_cn = utils.ldap_path('cn=%s'%service_changed, config.GROUP, config.DOMAIN) action = ldap3.MODIFY_ADD if new_lilik_user['services'][service_changed] else ldap3.MODIFY_DELETE modifiers[group_cn] = {'member': [(action, [user_cn])]} elif service_changed in self._posix_groups: group_cn = utils.ldap_path('cn=%s'%service_changed, config.GROUP, config.DOMAIN) action = ldap3.MODIFY_ADD if new_lilik_user['services'][service_changed] else ldap3.MODIFY_DELETE modifiers[group_cn] = {'memberUid': [(action, [self.uid])]} else: raise NameError('Unknown user attribute: %s'%service_changed) elif changed in self._attributes: if changed == 'cn': try: firstname, surname = new_lilik_user[changed].strip().rsplit(' ', 1) except ValueError: firstname = new_lilik_user[changed].strip() surname = " " modifiers[user_cn]['sn'] = [(ldap3.MODIFY_REPLACE, [surname])] modifiers[user_cn]['givenname'] = [(ldap3.MODIFY_REPLACE, [firstname])] modifiers[user_cn][changed] = [(ldap3.MODIFY_REPLACE, [new_lilik_user[changed]])] for entry_cn, modifier in modifiers.items(): print(modifier.keys()) if modifier: if 'userPassword' in modifier.keys(): print(entry_cn, list(modifier.keys())[0], '****** (HIDDEN)') else: print(entry_cn, modifier) conn.modify(entry_cn, modifier) check_result(conn) print(conn.result) if conn.result['result'] != 0: return False return True class LILiK_LDAP(object): def login(self, user_name, password): bind_dn = utils.ldap_path('uid=%s'%utils.clean_user_name(user_name), config.PEOPLE, config.DOMAIN) c = ldap3.Connection(config.SERVER, user=bind_dn, password=password) return c.bind() @admin_connection_decorator def get_users(self, conn): conn.search(utils.ldap_path(config.PEOPLE, config.DOMAIN), '(objectclass=posixAccount)', attributes=['uid']) check_result(conn) return [str(a.uid) for a in conn.entries] @admin_connection_decorator def get_user(self, conn, user_name): conn.search(utils.ldap_path(config.PEOPLE, config.DOMAIN), '(&(objectclass=posixAccount)(uid=%s))'%utils.clean_user_name(user_name), attributes=['*', 'memberOf']) check_result(conn) if len(conn.entries) == 0: raise IndexError("no user found for %s"%user_name) entry = conn.entries[0] return LILiK_USER(entry, self.get_posix_groups()) @connection_decorator def get_max_uidnumber(self, conn): conn.search(utils.ldap_path(config.PEOPLE, config.DOMAIN), "(objectClass=posixAccount)", attributes=['uidnumber']) check_result(conn) max = 0 for entry in conn.response: max = int(entry['attributes']['uidnumber'][0]) if int(entry['attributes']['uidnumber'][0]) > max else max return max @admin_connection_decorator def new_user(self, conn, user_dict): username = utils.clean_user_name(user_dict['uid']) user_dn = "uid=%s,o=People,dc=lilik,dc=it"%username mail_dn = "mail=%s,vd=lilik.it,o=hosting,dc=lilik,dc=it"%username # print("delete user") # conn.delete(user_dn) # print("delete mail") # conn.delete(mail_dn) print(" creating") conn.add(user_dn, ['top', 'inetOrgPerson', 'VirtualMailAccount', 'posixAccount', 'shadowAccount'], {'accountactive': "FALSE", 'cn': 'undefined', 'delete': "FALSE", 'gidnumber': 100, 'givenname': 'undefined', 'homedirectory': "/home/%s"%username, 'loginshell': '/bin/bash', 'mail': username, 'othertransport': 'phamm:', 'quota': 1024000, 'smtpauth': "FALSE", 'sn': 'undefined', 'uid': username, 'uidnumber': self.get_max_uidnumber() + 1, 'userpassword': '', 'vdhome': 'undefined', 'mailbox': 'undefined', 'lastChange': int(time.time())} ) check_result(conn) print(conn.result) print("user created") conn.add(mail_dn, ['alias', 'extensibleObject'], {'aliasedobjectname': user_dn} ) check_result(conn) print(conn.result) @connection_decorator def get_posix_groups(self, conn): conn.search(utils.ldap_path(config.GROUP, config.DOMAIN), '(objectclass=posixGroup)', attributes=['*']) check_result(conn) results = {} for group in conn.entries: results[str(group.cn)] = list(group.memberUid) if 'memberUid' in group else [] return results