diff --git a/src/memberdb/account_backend.py b/src/memberdb/account_backend.py index 5e7482bd025d51265b822c409d851ed1b072efd4..ba9f2d6e16951601092884f9a8b0c1f968000c47 100644 --- a/src/memberdb/account_backend.py +++ b/src/memberdb/account_backend.py @@ -40,99 +40,99 @@ else: #initalise ldap instace _ldap_inst = Connection( - Server(getattr(settings, 'AUTH_LDAP_SERVER_URI')), - user = getattr(settings, 'AUTH_LDAP_BIND_DN'), - password = getattr(settings, 'AUTH_LDAP_BIND_PASSWORD'), - client_strategy='SYNC', - raise_exceptions= True, - ) + Server(getattr(settings, 'AUTH_LDAP_SERVER_URI')), + user = getattr(settings, 'AUTH_LDAP_BIND_DN'), + password = getattr(settings, 'AUTH_LDAP_BIND_PASSWORD'), + client_strategy='SYNC', + raise_exceptions= True, + ) # get the ldap instance and bind if required def get_ldap_instance(): - if not _ldap_inst.bound: - try: - _ldap_inst.bind() - except LDAPInvalidCredentialsResult: - log.error("LDAP: Invalid bind credentials") - raise - return _ldap_inst + if not _ldap_inst.bound: + try: + _ldap_inst.bind() + except LDAPInvalidCredentialsResult: + log.error("LDAP: Invalid bind credentials") + raise + return _ldap_inst def get_ldap_attrs(dn, filter, limit, attrs): - ld = get_ldap_instance() - - ld.search(dn, filter, size_limit=limit, attributes=attrs) - result = ld.result - # fetch matched objects on success - if (result['result'] == RESULT_SUCCESS): - entries = ld.entries - else: - # otherwise raise an exception - raise LDAPOperationResult( - result=result['result'], - description=result['description'], - dn=result['dn'], - message=result['message'], - response_type=result['type']) - - if len(entries) == 0: - raise LDAPNoSuchObjectResult() - - return entries; + ld = get_ldap_instance() + + ld.search(dn, filter, size_limit=limit, attributes=attrs) + result = ld.result + # fetch matched objects on success + if (result['result'] == RESULT_SUCCESS): + entries = ld.entries + else: + # otherwise raise an exception + raise LDAPOperationResult( + result=result['result'], + description=result['description'], + dn=result['dn'], + message=result['message'], + response_type=result['type']) + + if len(entries) == 0: + raise LDAPNoSuchObjectResult() + + return entries; def get_user_attrs(username, attrs): - ld = get_ldap_instance() - # find the user - filter = '(cn=%s)' % username + ld = get_ldap_instance() + # find the user + filter = '(cn=%s)' % username - result = get_ldap_attrs(ldap_user_dn, filter, 1, attrs) + result = get_ldap_attrs(ldap_user_dn, filter, 1, attrs) - return result[0]; + return result[0]; def get_maxuid(): - ld = get_ldap_instance() - filter = "(cn=*)" - attrs = ['msSFU30MaxUidNumber'] - result = get_ldap_attrs(maxuid_dn, filter, 1, attrs) + ld = get_ldap_instance() + filter = "(cn=*)" + attrs = ['msSFU30MaxUidNumber'] + result = get_ldap_attrs(maxuid_dn, filter, 1, attrs) - return result[0] + return result[0] def get_account_lock_status(username): - ld = get_ldap_instance() - try: - result = get_user_attrs(username, ['userAccountControl']) - # user does not exist - except LDAPNoSuchObjectResult: - return None - # return UAC flag 0x002 ('ACCOUNT_DISABLE') - return bool(result[1]['userAccountControl'] & 0x002) + ld = get_ldap_instance() + try: + result = get_user_attrs(username, ['userAccountControl']) + # user does not exist + except LDAPNoSuchObjectResult: + return None + # return UAC flag 0x002 ('ACCOUNT_DISABLE') + return bool(result[1]['userAccountControl'] & 0x002) def validate_username(value : str): - # note: slug validator ensures that username only contains [a-z0-9_-] - # usernames can't begin with a numeric - if not value[0].isalpha(): - raise ValidationError(_('Username must begin with a letter')) - # ensure username is lowercase - if not value.islower(): - raise ValidationError(_('Username cannot contain uppercase characters')) - # check if the user exists, this test should catch *most* cases - if subprocess.call(["id", value], stderr=subprocess.DEVNULL) == 0: - raise ValidationError(_('Username already taken (passwd)')) - - # usernames cannot conflict with hostnames - try: - socket.gethostbyname(value) - raise ValidationError( - _('Username already taken (CNAME)') - ) - except socket.gaierror: - pass - # lookup user in ldap, required because not all users are mapped to *nix users - try: - get_user_attrs(value, None) - except LDAPNoSuchObjectResult: - pass - else: - raise ValidationError(_('Username already taken (AD)')) + # note: slug validator ensures that username only contains [a-z0-9_-] + # usernames can't begin with a numeric + if not value[0].isalpha(): + raise ValidationError(_('Username must begin with a letter')) + # ensure username is lowercase + if not value.islower(): + raise ValidationError(_('Username cannot contain uppercase characters')) + # check if the user exists, this test should catch *most* cases + if subprocess.call(["id", value], stderr=subprocess.DEVNULL) == 0: + raise ValidationError(_('Username already taken (passwd)')) + + # usernames cannot conflict with hostnames + try: + socket.gethostbyname(value) + raise ValidationError( + _('Username already taken (CNAME)') + ) + except socket.gaierror: + pass + # lookup user in ldap, required because not all users are mapped to *nix users + try: + get_user_attrs(value, None) + except LDAPNoSuchObjectResult: + pass + else: + raise ValidationError(_('Username already taken (AD)')) # locks the specified User Account by performing the following actions: @@ -140,160 +140,160 @@ def validate_username(value : str): # 2. set user shell to `/etc/locked20xx` via ldap # 3. do `dispense user type disabled <username> <reason>` def lock_account(username): - # TODO: error handling - ld = get_ldap_instance() - today = date.today() - try: - # fetch current uac - result = get_user_attrs(username, ['userAccountControl']) - - dn = result.entry_dn - uac = result['userAccountControl'] | 0x002 # set ACCOUNTDISABLE - actions = { - "userAccountControl": [(MODIFY_REPLACE,[uac])], - "userShell": [(MODIFY_REPLACE,["/etc/locked"+str(today.year)])] - } - # write record - ld.modify(dn, actions) - except LDAPOperationResult: - raise - finally: - ld.unbind() - - reason = "account locked by uccportal on %s" % str(today) - dispense.set_dispense_flag(username, 'disabled', reason) + # TODO: error handling + ld = get_ldap_instance() + today = date.today() + try: + # fetch current uac + result = get_user_attrs(username, ['userAccountControl']) + + dn = result.entry_dn + uac = result['userAccountControl'] | 0x002 # set ACCOUNTDISABLE + actions = { + "userAccountControl": [(MODIFY_REPLACE,[uac])], + "userShell": [(MODIFY_REPLACE,["/etc/locked"+str(today.year)])] + } + # write record + ld.modify(dn, actions) + except LDAPOperationResult: + raise + finally: + ld.unbind() + + reason = "account locked by uccportal on %s" % str(today) + dispense.set_dispense_flag(username, 'disabled', reason) def unlock_account(username): - # TODO: error handling - ld = get_ldap_instance() - today = date.today() - try: - # fetch current uac - result = get_user_attrs(username, ['userAccountControl']) - - dn = result[0] - uac = result[1]['userAccountControl'] & ~0x002 # clear ACCOUNTDISABLE - actions = { - "userAccountControl": [(MODIFY_REPLACE,[uac])], - "userShell": [(MODIFY_REPLACE,["/bin/zsh"])] - } - # write record - ld.modify(dn, actions) - except LDAPOperationResult: - raise - finally: - ld.unbind() - reason = "account unlocked by uccportal on %s" % str(today) - dispense.set_dispense_flag(username, '!disabled', reason) + # TODO: error handling + ld = get_ldap_instance() + today = date.today() + try: + # fetch current uac + result = get_user_attrs(username, ['userAccountControl']) + + dn = result[0] + uac = result[1]['userAccountControl'] & ~0x002 # clear ACCOUNTDISABLE + actions = { + "userAccountControl": [(MODIFY_REPLACE,[uac])], + "userShell": [(MODIFY_REPLACE,["/bin/zsh"])] + } + # write record + ld.modify(dn, actions) + except LDAPOperationResult: + raise + finally: + ld.unbind() + reason = "account unlocked by uccportal on %s" % str(today) + dispense.set_dispense_flag(username, '!disabled', reason) # Account creation def create_ad_user(form_data, member): - log.info("I: creating new account for %s (%s)") - - # store user details - # TODO add overides - username=form_data['username'] - displayName = member.first_name + ' ' + member.last_name - dn = 'CN=' + username +','+ ldap_user_dn - - # enclose password in quotes and convert to utf16 as required: - # https://msdn.microsoft.com/en-us/library/cc223248.aspx - quotedpass = '"'+ form_data['password']+'"' - utf16pass = quotedpass.encode('utf-16-le') - - # generate uid - try: - result = get_maxuid() - except: - log.error("LDAP: cannot find base uid") - raise - - - maxuid = int(result.msSFU30MaxUidNumber.value) - - # gets all uids >= maxuid - # this is done so that we don't encounter the 1000 item limit to ad queries - entries = get_ldap_attrs(ldap_user_dn,"(uidNumber>=%s)" % maxuid, 100, ['uidNumber']) - - # generate a new uid - uids = [] - for user in entries: - uids.append(int(user.uidNumber.value)) - - uids.sort() - # use max uid if it is free - if uids[0] != maxuid: - newuid = str(maxuid) - else: - prev = uids[0] - for uid in uids: - if uid - prev > 1: - newuid = uid + 1 - break; - prev = uid - #increment uid - newuid = str(prev + 1) - - # sanity check: make sure the uid is free - if subprocess.call(["id", newuid], stderr=subprocess.DEVNULL) == 0: - log.error("LDAP: UID Conflict UID %s already assigned" % newuid) - raise ValueError - - # create the new user struct - objclass = ['top','posixAccount','person','organizationalPerson','user'] - attrs = { - 'cn' : username, - 'sAMAccountName' : username, - 'givenName' : member.first_name, - 'sn': member.last_name, - 'displayName': displayName, - 'userAccountControl' : '512', - 'unixHomeDirectory' : "/home/ucc/" + username, - 'loginShell' : '/bin/zsh', - 'gidNumber' : '20021', - 'uidNumber' : newuid, - 'gecos' : displayName, - 'mail' : username + '@ucc.gu.uwa.edu.au', - 'unicodePwd': utf16pass - } - - # commit the new user to AD - ld = get_ldap_instance() - result = ld.add(dn, objclass, attrs) - if not result: - log.error("LDAP: user add failed") - raise LDAPOperationsErrorResult - - # set maxuid - result = ld.modify(maxuid_dn, {'msSFU30MaxUidNumber': [(MODIFY_REPLACE, newuid)]}) - if not result: - log.warning("LDAP: user created but msSFU30MaxUidNumber not updated") - - ld.unbind(); - return True; + log.info("I: creating new account for %s (%s)") + + # store user details + # TODO add overides + username=form_data['username'] + displayName = member.first_name + ' ' + member.last_name + dn = 'CN=' + username +','+ ldap_user_dn + + # enclose password in quotes and convert to utf16 as required: + # https://msdn.microsoft.com/en-us/library/cc223248.aspx + quotedpass = '"'+ form_data['password']+'"' + utf16pass = quotedpass.encode('utf-16-le') + + # generate uid + try: + result = get_maxuid() + except: + log.error("LDAP: cannot find base uid") + raise + + + maxuid = int(result.msSFU30MaxUidNumber.value) + + # gets all uids >= maxuid + # this is done so that we don't encounter the 1000 item limit to ad queries + entries = get_ldap_attrs(ldap_user_dn,"(uidNumber>=%s)" % maxuid, 100, ['uidNumber']) + + # generate a new uid + uids = [] + for user in entries: + uids.append(int(user.uidNumber.value)) + + uids.sort() + # use max uid if it is free + if uids[0] != maxuid: + newuid = str(maxuid) + else: + prev = uids[0] + for uid in uids: + if uid - prev > 1: + newuid = uid + 1 + break; + prev = uid + #increment uid + newuid = str(prev + 1) + + # sanity check: make sure the uid is free + if subprocess.call(["id", newuid], stderr=subprocess.DEVNULL) == 0: + log.error("LDAP: UID Conflict UID %s already assigned" % newuid) + raise ValueError + + # create the new user struct + objclass = ['top','posixAccount','person','organizationalPerson','user'] + attrs = { + 'cn' : username, + 'sAMAccountName' : username, + 'givenName' : member.first_name, + 'sn': member.last_name, + 'displayName': displayName, + 'userAccountControl' : '512', + 'unixHomeDirectory' : "/home/ucc/" + username, + 'loginShell' : '/bin/zsh', + 'gidNumber' : '20021', + 'uidNumber' : newuid, + 'gecos' : displayName, + 'mail' : username + '@ucc.gu.uwa.edu.au', + 'unicodePwd': utf16pass + } + + # commit the new user to AD + ld = get_ldap_instance() + result = ld.add(dn, objclass, attrs) + if not result: + log.error("LDAP: user add failed") + raise LDAPOperationsErrorResult + + # set maxuid + result = ld.modify(maxuid_dn, {'msSFU30MaxUidNumber': [(MODIFY_REPLACE, newuid)]}) + if not result: + log.warning("LDAP: user created but msSFU30MaxUidNumber not updated") + + ld.unbind(); + return True; def make_home(formdata, member): - user = member.username - mail = formdata['email_address'] if formdata['forward'] else "" + user = member.username + mail = formdata['email_address'] if formdata['forward'] else "" result = subprocess.run(make_home_cmd + [user, mail]) if result == 0: - return True - else: - raise CalledProcessError + return True + else: + raise CalledProcessError def subscribe_to_list(member): # TODO: when (if) we upgrade to mailman 3 we should switch to the new # mailman REST API. # aweful hack - result = os.system(make_mail_cmd % (make_mail_key, member.username)) - if result == 0: - return True - else: - raise CalledProcessError + result = os.system(make_mail_cmd % (make_mail_key, member.username)) + if result == 0: + return True + else: + raise CalledProcessError def set_pin(member, pin): - return + return