diff --git a/uffd/default_config.cfg b/uffd/default_config.cfg index 1c441271c0ccd0fc20171ed11d4bb7cf64faa7da..229b95a62fbb218285c7d02a2410d11dd62b6387 100644 --- a/uffd/default_config.cfg +++ b/uffd/default_config.cfg @@ -3,3 +3,7 @@ LDAP_BASE_GROUPS="ou=groups,dc=example,dc=com" LDAP_SERVICE_BIND_DN="" LDAP_SERVICE_BIND_PASSWORD="" LDAP_SERVICE_URL="ldapi:///" +LDAP_USER_OBJECTCLASSES=["vmailUser", "top", "inetOrgPerson", "organizationalPerson", "person", "posixAccount"] +LDAP_USER_GID=20001 +LDAP_USER_MIN_UID=10000 +LDAP_USER_MAX_UID=18999 diff --git a/uffd/group/models.py b/uffd/group/models.py index 23843df3bbe94fc44785c3bd29a96cdccfe77d73..98b60453878aab5e494f898e029d9779e995350d 100644 --- a/uffd/group/models.py +++ b/uffd/group/models.py @@ -26,7 +26,6 @@ class Group(): @classmethod def from_ldap_dn(cls, dn): conn = ldap.service_conn() - print(dn) conn.search(dn, '(objectClass=groupOfUniqueNames)') if not len(conn.entries) == 1: return None diff --git a/uffd/ldap/__init__.py b/uffd/ldap/__init__.py index e1f09d811d8260c7bb2a43f5219aef9bbfa43746..61ffb9e0dde2158e82c80db24f77de409468ad95 100644 --- a/uffd/ldap/__init__.py +++ b/uffd/ldap/__init__.py @@ -1,4 +1,4 @@ from .ldap import bp as ldap_bp -from .ldap import service_conn, user_conn, escape_filter_chars +from .ldap import service_conn, user_conn, escape_filter_chars, uid_to_dn, loginname_to_dn, get_next_uid bp = [ldap_bp] diff --git a/uffd/ldap/ldap.py b/uffd/ldap/ldap.py index b5d8b68a06b6cc2344a265e4bd11517ce2a41bf8..a9068cc7c1d913b8584cd795c2a4bfdda9887cfc 100644 --- a/uffd/ldap/ldap.py +++ b/uffd/ldap/ldap.py @@ -20,3 +20,31 @@ def service_conn(): def user_conn(): pass + +def uid_to_dn(uid): + conn = service_conn() + conn.search(current_app.config["LDAP_BASE_USER"], '(&(objectclass=person)(uidNumber={}))'.format(escape_filter_chars(uid))) + if not len(conn.entries) == 1: + return None + else: + return conn.entries[0].entry_dn + +def loginname_to_dn(loginname): + return 'uid={},{}'.format(escape_filter_chars(loginname), current_app.config["LDAP_BASE_USER"]) + +def get_next_uid(): + conn = service_conn() + conn.search(current_app.config["LDAP_BASE_USER"], '(objectclass=person)') + max_uid = current_app.config["LDAP_USER_MIN_UID"] + for i in conn.entries: + # skip out of range entries + if i['uidNumber'].value > current_app.config["LDAP_USER_MAX_UID"]: + continue + if i['uidNumber'].value < current_app.config["LDAP_USER_MIN_UID"]: + continue + max_uid = max(i['uidNumber'].value, max_uid) + next_uid = max_uid + 1 + if uid_to_dn(next_uid): + raise Exception('No free uid found') + else: + return next_uid diff --git a/uffd/user/models.py b/uffd/user/models.py index 734d07c23896226d7080fd7c736205e6fc9de646..a7ca635803d6b838764d3892a8b60aa664be5f29 100644 --- a/uffd/user/models.py +++ b/uffd/user/models.py @@ -1,4 +1,8 @@ import string + +from ldap3 import MODIFY_REPLACE, HASHED_SALTED_SHA512 +from flask import current_app + from uffd import ldap class User(): @@ -6,6 +10,7 @@ class User(): loginname = None displayname = None mail = None + newpassword = None def __init__(self, uid=None, loginname='', displayname='', mail='', groups=None): self.uid = uid @@ -14,7 +19,7 @@ class User(): self.mail = mail if isinstance(groups, str): groups = [groups] - self.groups_ldap = groups + self.groups_ldap = groups or [] self._groups = None @classmethod @@ -36,7 +41,35 @@ class User(): return User.from_ldap(conn.entries[0]) def to_ldap(self, new): - pass + conn = ldap.service_conn() + if new: + attributes= { + 'uidNumber': ldap.get_next_uid(), + 'gidNumber': current_app.config['LDAP_USER_GID'], + 'homeDirectory': '/home/'+self.loginname, + 'sn': ' ', + # same as for update + 'givenName': self.displayname, + 'displayName': self.displayname, + 'cn': self.displayname, + 'mail': self.mail, + } + dn = ldap.loginname_to_dn(self.loginname) + result = conn.add(dn, current_app.config['LDAP_USER_OBJECTCLASSES'], attributes) + else: + attributes = { + 'givenName': [(MODIFY_REPLACE, [self.displayname])], + 'displayName': [(MODIFY_REPLACE, [self.displayname])], + 'cn': [(MODIFY_REPLACE, [self.displayname])], + 'mail': [(MODIFY_REPLACE, [self.mail])], + } + dn = ldap.uid_to_dn(self.uid) + result = conn.modify(dn, attributes) + if result: + if self.newpassword: + print(self.newpassword) + conn.extend.standard.modify_password(user=dn, old_password=None, new_password=self.newpassword, hash_algorithm=HASHED_SALTED_SHA512) + return result def get_groups(self): from uffd.group.models import Group @@ -66,4 +99,4 @@ class User(): return True def set_password(self, value): - raise Exception('TODO: user want to change passwords') + self.newpassword = value diff --git a/uffd/user/templates/user.html b/uffd/user/templates/user.html index 4818822bf100b4064b330f748b2b3eb3144ac037..e25d37baccf2bc8c1cc494a277b9254e3102abdd 100644 --- a/uffd/user/templates/user.html +++ b/uffd/user/templates/user.html @@ -1,7 +1,7 @@ {% extends 'base.html' %} {% block body %} -<form action="{{ url_for(".user_update", id_=user.uid) }}" method="POST"> +<form action="{{ url_for(".user_update", uid=user.uid) }}" method="POST"> <div class="align-self-center"> <div class="form-group col"> <label for="user-uid">uid</label> diff --git a/uffd/user/templates/user_list.html b/uffd/user/templates/user_list.html index 54a9326ceeca4cd1e000a8090520d13314063eec..6635b97a88292e4cb9940e2c885fda8fcb30f581 100644 --- a/uffd/user/templates/user_list.html +++ b/uffd/user/templates/user_list.html @@ -8,7 +8,7 @@ <tr> <th scope="col">uid</th> <th scope="col">login name</th> - <th scope="col">given name</th> + <th scope="col">display name</th> <th scope="col"> <a type="button" class="btn btn-primary" href="{{ url_for(".user_show") }}"> <i class="fa fa-plus" aria-hidden="true"></i> New @@ -28,7 +28,7 @@ </a> </td> <td> - {{ user.givenname }} + {{ user.displayname }} </td> <td> <a href="{{ url_for(".user_show", uid=user.uid) }}" class="btn btn-primary"> diff --git a/uffd/user/views.py b/uffd/user/views.py index 80115181bd41994f3c7d7466a035043c1152d8dc..b6633bbb83cc0ea1dd03894547c14d1a40aeb352 100644 --- a/uffd/user/views.py +++ b/uffd/user/views.py @@ -23,16 +23,18 @@ def user_list(): def user_show(uid=None): if not uid: user = User() + ldif = '<none yet>' else: conn = service_conn() conn.search(current_app.config["LDAP_BASE_USER"], '(&(objectclass=person)(uidNumber={}))'.format((escape_filter_chars(uid)))) assert len(conn.entries) == 1 user = User.from_ldap(conn.entries[0]) - return render_template('user.html', user=user, user_ldif=conn.entries[0].entry_to_ldif()) + ldif = conn.entries[0].entry_to_ldif() + return render_template('user.html', user=user, user_ldif=ldif) @bp.route("/<int:uid>/update", methods=['POST']) @bp.route("/new", methods=['POST']) -def user_update(uid=None): +def user_update(uid=False): conn = service_conn() if uid: conn.search(current_app.config["LDAP_BASE_USER"], '(&(objectclass=person)(uidNumber={}))'.format((escape_filter_chars(uid)))) @@ -51,7 +53,7 @@ def user_update(uid=None): new_password = request.form.get('password') if new_password: user.set_password(new_password) - if user.to_ldap(conn, new=bool(uid)): + if user.to_ldap(new=(not uid)): flash('User updated') else: flash('Error updating user: {}'.format(conn.result['message']))