LILiK login and user managment web interface
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

200 lines
8.8 KiB

  1. #!/bin/usr/env python3
  2. import ldap3
  3. import utils
  4. import config
  5. import time
  6. def check_result(conn):
  7. if conn.result['result'] != 0: #see https://www.ietf.org/rfc/rfc4511.txt 4.1.9
  8. raise RuntimeError(str(conn.result))
  9. def connection_decorator(f):
  10. with ldap3.Connection(config.SERVER, auto_bind=True, client_strategy=ldap3.STRATEGY_SYNC_RESTARTABLE) as conn:
  11. def wrapped_f(*args, **kwargs):
  12. return f(args[0], conn, *args[1:], **kwargs)
  13. return wrapped_f
  14. def admin_connection_decorator(f):
  15. with ldap3.Connection(config.SERVER, user=config.ADMIN_CN, password=config.ADMIN_PASSWORD, auto_bind=True, client_strategy=ldap3.STRATEGY_SYNC_RESTARTABLE) as conn:
  16. def wrapped_f(*args, **kwargs):
  17. return f(args[0], conn, *args[1:], **kwargs)
  18. return wrapped_f
  19. class LILiK_USER(object):
  20. _attributes = ['cn']
  21. _read_only_attributes = ['uid']
  22. _flags = {'mail': 'accountActive'}
  23. _hosts = ['ltsp',
  24. 'users']
  25. _groups = ['admin',
  26. 'wiki',
  27. 'lilik.it',
  28. 'cloud',
  29. 'projects',
  30. 'teambox',
  31. 'im']
  32. _posix_groups = ['users_sites']
  33. def __init__(self, entry, posix_groups):
  34. results = {}
  35. for attribute in self._attributes + self._read_only_attributes:
  36. self.__setattr__(attribute, str(entry.__getattr__(attribute)))
  37. services = {}
  38. for service, flag in self._flags.items():
  39. services[service] = flag in entry and str(entry[flag]) == 'TRUE'
  40. for host in self._hosts:
  41. services[host] = 'host' in entry and host in entry['host']
  42. for group in self._groups:
  43. services[group] = 'memberOf' in entry and utils.ldap_path("cn=%s"%utils.clean_value(group), config.GROUP, config.DOMAIN) in list(entry['memberOf'])
  44. for posix_group in self._posix_groups:
  45. services[posix_group] = posix_group in posix_groups and str(entry.uid) in list(posix_groups[posix_group])
  46. self.__setattr__('services', services)
  47. def to_json(self):
  48. return json.dumps(self, default=lambda o: o.__dict__,)
  49. def to_dict(self):
  50. return self.__dict__
  51. @admin_connection_decorator
  52. def diff(self, conn, new_lilik_user):
  53. user_cn = utils.ldap_path('uid=%s'%self.uid, config.PEOPLE, config.DOMAIN)
  54. return utils.DictDiffer(new_lilik_user, self.__dict__)
  55. @admin_connection_decorator
  56. def update(self, conn, new_lilik_user):
  57. user_cn = utils.ldap_path('uid=%s'%self.uid, config.PEOPLE, config.DOMAIN)
  58. diff = utils.DictDiffer(new_lilik_user, self.__dict__)
  59. modifiers = {user_cn: {}}
  60. if 'userPassword' in diff.added() and new_lilik_user['userPassword']:
  61. hashed = ldap3.utils.hashed.hashed(ldap3.HASHED_SALTED_SHA512, new_lilik_user['userPassword'])
  62. modifiers[user_cn]['userPassword'] = [(ldap3.MODIFY_REPLACE, [hashed])]
  63. for changed in diff.changed():
  64. if changed == 'services':
  65. services_diff = utils.DictDiffer(self.__dict__[changed], new_lilik_user[changed])
  66. for service_changed in services_diff.changed():
  67. if service_changed in self._flags:
  68. flag = self._flags[service_changed]
  69. modifiers[user_cn][flag] = [(ldap3.MODIFY_REPLACE, [str(new_lilik_user['services'][service_changed]).upper()])]
  70. elif service_changed in self._hosts:
  71. action = ldap3.MODIFY_ADD if new_lilik_user['services'][service_changed] else ldap3.MODIFY_DELETE
  72. modifiers[user_cn]['host'] = [(action, [service_changed])]
  73. elif service_changed in self._groups:
  74. group_cn = utils.ldap_path('cn=%s'%service_changed, config.GROUP, config.DOMAIN)
  75. action = ldap3.MODIFY_ADD if new_lilik_user['services'][service_changed] else ldap3.MODIFY_DELETE
  76. modifiers[group_cn] = {'member': [(action, [user_cn])]}
  77. elif service_changed in self._posix_groups:
  78. group_cn = utils.ldap_path('cn=%s'%service_changed, config.GROUP, config.DOMAIN)
  79. action = ldap3.MODIFY_ADD if new_lilik_user['services'][service_changed] else ldap3.MODIFY_DELETE
  80. modifiers[group_cn] = {'memberUid': [(action, [self.uid])]}
  81. else:
  82. raise NameError('Unknown user attribute: %s'%service_changed)
  83. elif changed in self._attributes:
  84. if changed == 'cn':
  85. try:
  86. firstname, surname = new_lilik_user[changed].strip().rsplit(' ', 1)
  87. except ValueError:
  88. firstname = new_lilik_user[changed].strip()
  89. surname = " "
  90. modifiers[user_cn]['sn'] = [(ldap3.MODIFY_REPLACE, [surname])]
  91. modifiers[user_cn]['givenname'] = [(ldap3.MODIFY_REPLACE, [firstname])]
  92. modifiers[user_cn][changed] = [(ldap3.MODIFY_REPLACE, [new_lilik_user[changed]])]
  93. for entry_cn, modifier in modifiers.items():
  94. print(modifier.keys())
  95. if modifier:
  96. if 'userPassword' in modifier.keys():
  97. print(entry_cn, list(modifier.keys())[0], '****** (HIDDEN)')
  98. else:
  99. print(entry_cn, modifier)
  100. conn.modify(entry_cn, modifier)
  101. check_result(conn)
  102. print(conn.result)
  103. if conn.result['result'] != 0:
  104. return False
  105. return True
  106. class LILiK_LDAP(object):
  107. def login(self, user_name, password):
  108. bind_dn = utils.ldap_path('uid=%s'%utils.clean_user_name(user_name), config.PEOPLE, config.DOMAIN)
  109. c = ldap3.Connection(config.SERVER, user=bind_dn, password=password)
  110. return c.bind()
  111. @admin_connection_decorator
  112. def get_users(self, conn):
  113. conn.search(utils.ldap_path(config.PEOPLE, config.DOMAIN), '(objectclass=posixAccount)', attributes=['uid'])
  114. check_result(conn)
  115. return [str(a.uid) for a in conn.entries]
  116. @admin_connection_decorator
  117. def get_user(self, conn, user_name):
  118. conn.search(utils.ldap_path(config.PEOPLE, config.DOMAIN), '(&(objectclass=posixAccount)(uid=%s))'%utils.clean_user_name(user_name), attributes=['*', 'memberOf'])
  119. check_result(conn)
  120. if len(conn.entries) == 0:
  121. raise IndexError("no user found for %s"%user_name)
  122. entry = conn.entries[0]
  123. return LILiK_USER(entry, self.get_posix_groups())
  124. @connection_decorator
  125. def get_max_uidnumber(self, conn):
  126. conn.search(utils.ldap_path(config.PEOPLE, config.DOMAIN),
  127. "(objectClass=posixAccount)",
  128. attributes=['uidnumber'])
  129. check_result(conn)
  130. max = 0
  131. for entry in conn.response:
  132. max = int(entry['attributes']['uidnumber'][0]) if int(entry['attributes']['uidnumber'][0]) > max else max
  133. return max
  134. @admin_connection_decorator
  135. def new_user(self, conn, user_dict):
  136. username = utils.clean_user_name(user_dict['uid'])
  137. user_dn = "uid=%s,o=People,dc=lilik,dc=it"%username
  138. mail_dn = "mail=%s,vd=lilik.it,o=hosting,dc=lilik,dc=it"%username
  139. # print("delete user")
  140. # conn.delete(user_dn)
  141. # print("delete mail")
  142. # conn.delete(mail_dn)
  143. print(" creating")
  144. conn.add(user_dn,
  145. ['top', 'inetOrgPerson', 'VirtualMailAccount', 'posixAccount', 'shadowAccount'],
  146. {'accountactive': "FALSE",
  147. 'cn': 'undefined',
  148. 'delete': "FALSE",
  149. 'gidnumber': 100,
  150. 'givenname': 'undefined',
  151. 'homedirectory': "/home/%s"%username,
  152. 'loginshell': '/bin/bash',
  153. 'mail': username,
  154. 'othertransport': 'phamm:',
  155. 'quota': 1024000,
  156. 'smtpauth': "FALSE",
  157. 'sn': 'undefined',
  158. 'uid': username,
  159. 'uidnumber': self.get_max_uidnumber() + 1,
  160. 'userpassword': '',
  161. 'vdhome': 'undefined',
  162. 'mailbox': 'undefined',
  163. 'lastChange': int(time.time())}
  164. )
  165. check_result(conn)
  166. print(conn.result)
  167. print("user created")
  168. conn.add(mail_dn,
  169. ['alias', 'extensibleObject'],
  170. {'aliasedobjectname': user_dn}
  171. )
  172. check_result(conn)
  173. print(conn.result)
  174. @connection_decorator
  175. def get_posix_groups(self, conn):
  176. conn.search(utils.ldap_path(config.GROUP, config.DOMAIN), '(objectclass=posixGroup)', attributes=['*'])
  177. check_result(conn)
  178. results = {}
  179. for group in conn.entries:
  180. results[str(group.cn)] = list(group.memberUid) if 'memberUid' in group else []
  181. return results