Browse Source

add new user api, fix previous api

master
Andrea Cimbalo 7 years ago
parent
commit
df67f7b1ef
3 changed files with 139 additions and 26 deletions
  1. +79
    -15
      lilikusers.py
  2. +54
    -10
      server.py
  3. +6
    -1
      utils.py

+ 79
- 15
lilikusers.py View File

@ -2,21 +2,27 @@
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) as conn:
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) as conn:
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 = {'uid': [], 'cn': ['givenName']}
_attributes = ['cn']
_read_only_attributes = ['uid']
_flags = {'mail': 'accountActive'}
_hosts = ['ltsp',
'users']
@ -30,12 +36,12 @@ class LILiK_USER(object):
_posix_groups = ['users_sites']
def __init__(self, entry, posix_groups):
results = {}
for attribute in self._attributes.keys():
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 bool(entry[flag])
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:
@ -63,7 +69,7 @@ class LILiK_USER(object):
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, [new_lilik_user['services'][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
@ -79,14 +85,19 @@ class LILiK_USER(object):
action = ldap3.MODIFY_ADD if new_lilik_user['services'][service_changed] else ldap3.MODIFY_DELETE
modifiers[group_cn] = {'memberUid': [(action, [self.uid])]}
else:
raise Exception('Unknown user attribute')
else:
for alias in self._attributes[changed]:
modifiers[user_cn][alias] = [(ldap3.MODIFY_REPLACE, [new_lilik_user[changed]])]
raise NameError('Unknown user attribute: %s'%service_changed)
elif changed in self._attributes:
if changed == 'cn':
firstname, surname = new_lilik_user[changed].rsplit(' ', 1)
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():
if modifier:
print(entry_cn, modifier)
conn.modify(entry_cn, modifier)
check_result(conn)
print(conn.result)
if conn.result['result'] != 0:
return False
return True
@ -98,24 +109,77 @@ class LILiK_LDAP(object):
c = ldap3.Connection(config.SERVER, user=bind_dn, password=password)
return c.bind()
@connection_decorator
@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]
@connection_decorator
@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'])
# return clean_user_name(user_name)
check_result(conn)
if len(conn.entries) == 0:
return None
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=['*'])
# return clean_user_name(user_name)
check_result(conn)
results = {}
for group in conn.entries:
results[str(group.cn)] = list(group.memberUid) if 'memberUid' in group else []


+ 54
- 10
server.py View File

@ -13,41 +13,85 @@ def check_auth(user_name, password):
"""This function is called to check if a username /
password combination is valid.
"""
return lilik_ldap.login(user_name, password)
if lilik_ldap.login(user_name, password):
return lilik_ldap.get_user(user_name)
raise ValueError
def authenticate():
"""Sends a 401 response that enables basic auth"""
return Response(
'Could not verify your access level for that URL.\n'
'You have to login with proper credentials', 401,
'You have to login with proper credentials\n', 401,
{'WWW-Authenticate': 'Basic realm="Login Required"'})
def admin_required():
"""Sends a 401 response that enables basic auth"""
return Response(
'Could not verify your access level for that URL.\n'
'You have to login with admin rights\n', 401,
{'WWW-Authenticate': 'Basic realm="Admin login Required"'})
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
if not auth:
return authenticate()
return f(*args, **kwargs)
try:
user = check_auth(auth.username, auth.password)
except ValueError:
app.logger.warning("Authentication failed for %s" % auth.username)
return authenticate()
return f(user, *args, **kwargs)
return decorated
def requires_admin_auth(f):
@wraps(f)
def decorated(user, *args, **kwargs):
if not user.services['admin']:
app.logger.warning("Admin privilege required for user %s" % user.uid)
return admin_required()
return f(user, *args, **kwargs)
return decorated
def requires_same_user_or_admin_auth(f):
@wraps(f)
def decorated(user, user_name, *args, **kwargs):
if not user.services['admin'] and user.uid != user_name:
app.logger.warning("Admin privilege required for user %s" % user.uid)
return admin_required()
return f(user, user_name, *args, **kwargs)
return decorated
@app.route('/api/users', methods=['GET'])
@requires_auth
def get_users():
@requires_admin_auth
def get_users(user):
''' return the list of users'''
return jsonify(lilik_ldap.get_users())
@app.route('/api/user/<user_name>', methods=['GET'])
@app.route('/api/users/<user_name>', methods=['GET'])
@requires_auth
def get_user(user_name):
@requires_same_user_or_admin_auth
def get_user(user, user_name):
''' return the list of users'''
return jsonify(lilik_ldap.get_user(user_name).to_dict())
@app.route('/api/user/<user_name>', methods=['POST'])
@app.route('/api/users/<user_name>', methods=['PUT'])
@requires_auth
@requires_admin_auth
def update_user(user, user_name):
new_lilik_user = request.get_json()
lilik_ldap.get_user(user_name).update(new_lilik_user)
return jsonify(lilik_ldap.get_user(user_name).to_dict())
@app.route('/api/users', methods=['POST'])
@requires_auth
def post_user(self, user_name):
@requires_admin_auth
def new_user(user):
new_lilik_user = request.get_json()
return lilik_ldap.get_user(user_name).update(new_lilik_user)
print(lilik_ldap.new_user(new_lilik_user))
return jsonify(lilik_ldap.get_user(new_lilik_user['uid']).to_dict())
if __name__ == '__main__':
app.run(debug=True)

+ 6
- 1
utils.py View File

@ -1,3 +1,8 @@
def rremove(origin, match):
if origin.endswith(match):
return origin[:-len(match)]
return origin
def ldap_path(entry, *arg):
if arg:
return "%s,%s"%(ldap_path(entry), ldap_path(arg))
@ -10,7 +15,7 @@ def ldap_path(entry, *arg):
return entry
def clean_user_name(user_name):
return clean_value(user_name.rstrip('lilik.it'))
return clean_value(rremove(user_name, '@lilik.it'))
def clean_value(value):
'''


Loading…
Cancel
Save