#!/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']:
|
|
hashed = ldap3.utils.hashed.hashed(ldap3.HASHED_SALTED_SHA512, new_lilik_user['userPassword'])
|
|
modifiers[user_cn]['userPassword'] = [(ldap3.MODIFY_REPLACE, [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
|