diff --git a/dal/ungleich_ldap.py b/dal/ungleich_ldap.py new file mode 100644 index 0000000..7b8bb7a --- /dev/null +++ b/dal/ungleich_ldap.py @@ -0,0 +1,144 @@ +import base64 +import hashlib +import random + +import ldap3 +from django.conf import settings + + +class LdapManager: + __instance = None + def __new__(cls): + if LdapManager.__instance is None: + LdapManager.__instance = object.__new__(cls) + return LdapManager.__instance + + def __init__(self): + """ + Initialize the LDAP subsystem. + """ + self.rng = random.SystemRandom() + self.server = ldap3.Server(settings.AUTH_LDAP_SERVER) + + + def get_admin_conn(self): + """ + Return a bound :class:`ldap3.Connection` instance which has write + permissions on the dn in which the user accounts reside. + """ + conn = self.get_conn(user=settings.LDAP_ADMIN_DN, + password=settings.LDAP_ADMIN_PASSWORD, + raise_exceptions=True) + conn.bind() + return conn + + + def get_conn(self, **kwargs): + """ + Return an unbound :class:`ldap3.Connection` which talks to the configured + LDAP server. + + The *kwargs* are passed to the constructor of :class:`ldap3.Connection` and + can be used to set *user*, *password* and other useful arguments. + """ + return ldap3.Connection(self.server, **kwargs) + + + def _ssha_password(self, password): + """ + Apply the SSHA password hashing scheme to the given *password*. + *password* must be a :class:`bytes` object, containing the utf-8 + encoded password. + + Return a :class:`bytes` object containing ``ascii``-compatible data + which can be used as LDAP value, e.g. after armoring it once more using + base64 or decoding it to unicode from ``ascii``. + """ + SALT_BYTES = 15 + + sha1 = hashlib.sha1() + salt = self.rng.getrandbits(SALT_BYTES * 8).to_bytes(SALT_BYTES, + "little") + sha1.update(password) + sha1.update(salt) + + digest = sha1.digest() + passwd = b"{SSHA}" + base64.b64encode(digest + salt) + return passwd + + + def create_user(self, loginname, displayname, mail, password): + """ + Create a new user in the LDAP storage. + + *loginname* must be a unique, valid user id. It is generally safe to + pass lower-case ascii letters here. The *loginname* of an account + cannot be changed. + + *displayname* is the name which is shown to other users. This can be + changed in the future. + + *mail* is a valid mail address of the user. + + *password* is the initial plain text password for the user. + """ + + conn = self.get_admin_conn() + try: + conn.add( + ("uid={uid}," + settings.LDAP_CUSTOMER_DN).format(uid=loginname), + ["inetOrgPerson"], + { + "uid": [loginname], + "cn": [displayname], + "sn": ["XXX"], + "givenName": ["XXX"], + "mail": [mail], + "userpassword": [self._ssha_password( + password.encode("utf-8") + )] + } + ) + finally: + conn.unbind() + + + def change_password(self, user_dn, new_password): + """ + Changes the password of the user identified by user_dn + + :param user_dn: str The distinguished name for identifying the user + :param new_password: str The new password string + :return: True if password was changed successfully False otherwise + """ + conn = self.get_admin_conn() + return_val = conn.modify( + user_dn, + { + "userpassword": ( + ldap3.MODIFY_REPLACE, + [self._ssha_password(new_password.encode("utf-8"))] + ) + } + ) + conn.unbind() + return return_val + + def check_user_exists(self, uid, is_customer=True): + """ + Check if the user with the given uid exists in the customer group. + + :param uid: str representing the user + :param is_customer: bool representing whether the current user is a + customer. By default, the user is a customer (assume) + :return: True if the user exists otherwise return False + """ + conn = self.get_admin_conn() + try: + result = conn.search( + settings.LDAP_CUSTOMER_DN if is_customer else settings.LDAP_USERS_DN, + search_filter='(uid={uid})'.format(uid=uid) + ) + finally: + conn.unbind() + return result diff --git a/dal/views.py b/dal/views.py index 26f82f9..8020e44 100644 --- a/dal/views.py +++ b/dal/views.py @@ -342,11 +342,15 @@ class ResetRequest(View): if len(password1) < 8: return render(request, 'error.html', { 'service': service, 'error': 'The password is too short, please use a longer one. At least 8 characters.' } ) # everything checks out, now change the password - with get_pool().next() as rpc: - pwd = r'%s' % password1 - result = rpc.changepassword.change_password(user, pwd) - # password change successfull - if result == True: + + from .ungleich_ldap import LdapManager + ldap_manager = LdapManager() + result = ldap_manager.change_password( + ("uid={uid}," + settings.LDAP_CUSTOMER_DN).format(uid=user), + password1 + ) + # password change successful + if result: return render(request, 'changedpassword.html', { 'user': user } ) # Something went wrong while changing the password else: