From 72fe535f3ca3260f1289d67fa295d5132c214490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=91=E4=B9=90=F0=9F=8C=8C?= Date: Sat, 22 May 2021 17:27:42 +0800 Subject: [PATCH] BUG FIX --- resetpwd/utils.py | 122 ++++++++++++++++++++++++--------------- resetpwd/views.py | 39 ++++++------- utils/ad_ops.py | 141 ++++++++++++++++++++++++++-------------------- 3 files changed, 173 insertions(+), 129 deletions(-) diff --git a/resetpwd/utils.py b/resetpwd/utils.py index 05622a4..aeee513 100644 --- a/resetpwd/utils.py +++ b/resetpwd/utils.py @@ -10,6 +10,7 @@ from django.shortcuts import render from django.http import HttpResponseRedirect import logging from utils.crypto_ops import Crypto +from ldap3.core.exceptions import LDAPInvalidCredentialsResult, LDAPOperationResult, LDAPExceptionError, LDAPException from django.conf import settings from pwdselfservice import crypto_key @@ -23,7 +24,10 @@ else: logger = logging.getLogger('django') -def code_2_user_id(ops, request, msg_template, home_url, code): +def code_2_user_info(ops, request, msg_template, home_url, code): + """ + 临时授权码换取userinfo + """ _status, user_id = ops.get_user_id_by_code(code) # 判断 user_id 在本企业钉钉/微信中是否存在 if not _status: @@ -45,6 +49,9 @@ def code_2_user_id(ops, request, msg_template, home_url, code): def crypto_id_2_user_info(ops, request, msg_template, home_url, scan_app_tag): + """ + 能过前端提交的加密的userid来获取用户信息 + """ try: crypto_tmp_id = request.COOKIES.get('tmpid') if not crypto_tmp_id: @@ -73,6 +80,9 @@ def crypto_id_2_user_info(ops, request, msg_template, home_url, scan_app_tag): def crypto_user_id_2_cookie(user_id): + """ + 加密userid,写入到cookie + """ crypto = Crypto(crypto_key) # 对user_id进行加密,因为user_id基本上固定不变的,为了防止user_id泄露而导致重复使用,进行加密后再传回。 _id_cryto = crypto.encrypt(user_id) @@ -83,6 +93,9 @@ def crypto_user_id_2_cookie(user_id): def crypto_id_2_user_id(request, msg_template, home_url): + """ + 前端提交的加密的userid,解密出真实的userid + """ try: crypto_tmp_id = request.COOKIES.get('tmpid') # 解密 @@ -100,55 +113,74 @@ def crypto_id_2_user_id(request, msg_template, home_url): def ops_account(ad_ops, request, msg_template, home_url, username, new_password): - if ad_ops.ad_ensure_user_by_account(username) is False: - context = { - 'msg': "账号[%s]在AD中不存在,请确认当前钉钉扫码账号绑定的邮箱是否和您正在使用的邮箱一致?或者该账号己被禁用!\n猜测:您的账号或邮箱是否是带有数字或其它字母区分?" % username, - 'button_click': "window.location.href='%s'" % home_url, - 'button_display': "返回主页" - } - return render(request, msg_template, context) + """ + ad 账号操作,判断账号状态,重置密码或解锁账号 + """ + try: + print("ops_account: {}" .format(username)) + _status, _account = ad_ops.ad_ensure_user_by_account(username=username) + if not _status: + context = { + 'msg': "账号[%s]在AD中不存在,请确认当前钉钉扫码账号绑定的邮箱是否和您正在使用的邮箱一致?或者该账号己被禁用!\n猜测:您的账号或邮箱是否是带有数字或其它字母区分?" % username, + 'button_click': "window.location.href='%s'" % home_url, + 'button_display': "返回主页" + } + return render(request, msg_template, context) - account_code = ad_ops.ad_get_user_status_by_account(username) - if account_code in settings.AD_ACCOUNT_DISABLE_CODE: - context = { - 'msg': "此账号状态为己禁用,请联系HR确认账号是否正确。", - 'button_click': "window.location.href='%s'" % home_url, - 'button_display': "返回主页" - } - return render(request, msg_template, context) + _status, account_code = ad_ops.ad_get_user_status_by_account(username) + if _status and account_code in settings.AD_ACCOUNT_DISABLE_CODE: + context = { + 'msg': "此账号状态为己禁用,请联系HR确认账号是否正确。", + 'button_click': "window.location.href='%s'" % home_url, + 'button_display': "返回主页" + } + return render(request, msg_template, context) + elif not _status: + context = { + 'msg': "错误:{}" .format(account_code), + 'button_click': "window.location.href='%s'" % home_url, + 'button_display': "返回主页" + } + return render(request, msg_template, context) - if new_password: - reset_status, result = ad_ops.ad_reset_user_pwd_by_account(username=username, new_password=new_password) - if reset_status: - # 重置密码并执行一次解锁,防止重置后账号还是锁定状态。 - unlock_status, result = ad_ops.ad_unlock_user_by_account(username) - if unlock_status: + if new_password: + reset_status, result = ad_ops.ad_reset_user_pwd_by_account(username=username, new_password=new_password) + if reset_status: + # 重置密码并执行一次解锁,防止重置后账号还是锁定状态。 + unlock_status, result = ad_ops.ad_unlock_user_by_account(username) + if unlock_status: + context = { + 'msg': "密码己修改/重置成功,请妥善保管。你可以点击返回主页或直接关闭此页面!", + 'button_click': "window.location.href='%s'" % home_url, + 'button_display': "返回主页" + } + return render(request, msg_template, context) + else: context = { - 'msg': "密码己修改/重置成功,请妥善保管。你可以点击返回主页或直接关闭此页面!", + 'msg': "密码未修改/重置成功,错误信息:{}".format(result), 'button_click': "window.location.href='%s'" % home_url, 'button_display': "返回主页" } return render(request, msg_template, context) else: - context = { - 'msg': "密码未修改/重置成功,错误信息:{}".format(result), - 'button_click': "window.location.href='%s'" % home_url, - 'button_display': "返回主页" - } - return render(request, msg_template, context) - else: - unlock_status, result = ad_ops.ad_unlock_user_by_account(username) - if unlock_status: - context = { - 'msg': "账号己解锁成功。你可以点击返回主页或直接关闭此页面!", - 'button_click': "window.location.href='%s'" % home_url, - 'button_display': "返回主页" - } - return render(request, msg_template, context) - else: - context = { - 'msg': "账号未能解锁,错误信息:{}".format(result), - 'button_click': "window.location.href='%s'" % home_url, - 'button_display': "返回主页" - } - return render(request, msg_template, context) + unlock_status, result = ad_ops.ad_unlock_user_by_account(username) + if unlock_status: + context = { + 'msg': "账号己解锁成功。你可以点击返回主页或直接关闭此页面!", + 'button_click': "window.location.href='%s'" % home_url, + 'button_display': "返回主页" + } + return render(request, msg_template, context) + else: + context = { + 'msg': "账号未能解锁,错误信息:{}".format(result), + 'button_click': "window.location.href='%s'" % home_url, + 'button_display': "返回主页" + } + return render(request, msg_template, context) + except LDAPInvalidCredentialsResult as lic_e: + raise LDAPOperationResult("LDAPInvalidCredentialsResult: " + str(lic_e.message)) + except LDAPOperationResult as lo_e: + raise LDAPOperationResult("LDAPOperationResult: " + str(lo_e.message)) + except LDAPException as l_e: + raise LDAPException("LDAPException: " + str(l_e)) diff --git a/resetpwd/views.py b/resetpwd/views.py index 62faf5d..240c2f7 100644 --- a/resetpwd/views.py +++ b/resetpwd/views.py @@ -2,9 +2,10 @@ import logging import os from django.shortcuts import render from utils.ad_ops import AdOps +from ldap3.core.exceptions import LDAPException from utils.format_username import format2username, get_user_is_active from .form import CheckForm -from .utils import code_2_user_id, crypto_id_2_user_info, ops_account, crypto_id_2_user_id, crypto_user_id_2_cookie +from .utils import code_2_user_info, crypto_id_2_user_info, ops_account, crypto_id_2_user_id, crypto_user_id_2_cookie APP_ENV = os.getenv('APP_ENV') if APP_ENV == 'dev': from conf.local_settings_dev import SCAN_CODE_TYPE, DING_MO_APP_ID, WEWORK_CORP_ID, WEWORK_AGENT_ID, HOME_URL @@ -39,13 +40,13 @@ class PARAMS(object): scan_params = PARAMS() _ops = scan_params.ops -try: - ad_ops = AdOps() - print("初始化Active Directory连接成功...") -except Exception as e: - ad_ops = None - print("初始化Active Directory连接失败...") - print(str(e)) +# try: +# AdOps() = AdOps() +# print("初始化Active Directory连接成功...") +# except Exception as e: +# AdOps() = LDAPException("连接域控制器失败,无法访问到LDAP") +# print("初始化Active Directory连接失败...") +# print(str(e)) def index(request): @@ -86,7 +87,7 @@ def index(request): # 格式化用户名 username = format2username(username) # 检测账号状态 - auth_status, auth_result = ad_ops.ad_auth_user(username=username, password=old_password) + auth_status, auth_result = AdOps().ad_auth_user(username=username, password=old_password) if not auth_status: context = { 'msg': str(auth_result), @@ -94,7 +95,7 @@ def index(request): 'button_display': "返回" } return render(request, msg_template, context) - return ops_account(ad_ops, request, msg_template, home_url, username, new_password) + return ops_account(AdOps(), request, msg_template, home_url, username, new_password) else: context = { 'msg': "请从主页进行修改密码操作或扫码验证用户信息。", @@ -124,7 +125,7 @@ def callback_check(request): return render(request, msg_template, context) print("code: {}" .format(code)) try: - _status, user_id, user_info = code_2_user_id(_ops, request, msg_template, home_url, code) + _status, user_id, user_info = code_2_user_info(_ops, request, msg_template, home_url, code) if not _status: return render(request, msg_template, user_id) # 账号是否是激活的 @@ -156,17 +157,9 @@ def reset_pwd_by_callback(request): home_url = '%s://%s' % (request.scheme, HOME_URL) # 从cookie中提取union_id,并解密,然后对当前union_id的用户进行重置密码 if request.method == 'GET': - _status, user_id = crypto_id_2_user_id(request, msg_template, home_url) + _status, user_info = crypto_id_2_user_info(_ops, request, msg_template, home_url, scan_params.SCAN_APP) if not _status: - return render(request, msg_template, user_id) - userid_status, user_info = _ops.get_user_detail_by_user_id(user_id) - if not userid_status: - context = { - 'msg': '获取{}用户信息失败,错误信息:{}'.format(user_info, scan_params.SCAN_APP), - 'button_click': "window.location.href='%s'" % home_url, - 'button_display': "返回主页" - } - return render(request, msg_template, context) + return render(request, msg_template, user_info) # 通过user_id拿到用户信息,并格式化为username username = format2username(user_info.get('email')) # 如果邮箱能提取到,则格式化之后,提取出账号提交到前端绑定 @@ -191,7 +184,7 @@ def reset_pwd_by_callback(request): if not _status: return render(request, msg_template, user_info) username = format2username(user_info.get('email')) - return ops_account(ad_ops, request, msg_template, home_url, username, _new_password) + return ops_account(ad_ops=AdOps(), request=request, msg_template=msg_template, home_url=home_url, username=username, new_password=_new_password) except Exception as reset_e: context = { 'msg': "错误[%s],请与管理员联系." % str(reset_e), @@ -231,7 +224,7 @@ def unlock_account(request): if not _status: return render(request, msg_template, user_info) username = format2username(user_info.get('email')) - return ops_account(ad_ops, request, msg_template, home_url, username, None) + return ops_account(AdOps(), request, msg_template, home_url, username, None) else: context = { 'msg': "请从主页开始进行操作。", diff --git a/utils/ad_ops.py b/utils/ad_ops.py index afa31d7..47d84a6 100644 --- a/utils/ad_ops.py +++ b/utils/ad_ops.py @@ -1,5 +1,6 @@ +import ldap3 from ldap3 import * -from ldap3.core.exceptions import LDAPInvalidCredentialsResult, LDAPOperationResult +from ldap3.core.exceptions import LDAPInvalidCredentialsResult, LDAPOperationResult, LDAPExceptionError,LDAPException from ldap3.core.results import * from ldap3.utils.dn import safe_dn import os @@ -54,10 +55,12 @@ class AdOps(object): try: self.conn = Connection(server, auto_bind=self.auto_bind, user=r'{}\{}'.format(self.domain, self.user), password=self.password, authentication=self.authentication, raise_exceptions=True) - except LDAPOperationResult as e: - raise LDAPOperationResult("LDAPOperationResult: " + str(e)) - except Exception as e: - raise Exception('LDAP Exception:无法连接到AD控制器 --- {}' .format(e)) + except LDAPInvalidCredentialsResult as lic_e: + raise LDAPOperationResult("LDAPInvalidCredentialsResult: " + str(lic_e.message)) + except LDAPOperationResult as lo_e: + raise LDAPOperationResult("LDAPOperationResult: " + str(lo_e.message)) + except LDAPException as l_e: + raise LDAPException("LDAPException: " + str(l_e)) def ad_auth_user(self, username, password): """ @@ -100,10 +103,10 @@ class AdOps(object): :param username: :return: True or False """ - base_dn = BASE_DN - condition = '(&(objectclass=user)(sAMAccountName={}))'.format(username) - attributes = ['sAMAccountName'] - return self.conn.search(base_dn, condition, attributes=attributes) + try: + return True, self.conn.search(BASE_DN, '(&(objectclass=user)(sAMAccountName={}))'.format(username), attributes=['sAMAccountName']) + except Exception as e: + return False, "AdOps Exception: {}" .format(e) def ad_get_user_displayname_by_account(self, username): """ @@ -111,11 +114,11 @@ class AdOps(object): :param username: :return: user_displayname """ - self.conn.search(BASE_DN, '(&(objectclass=user)(sAMAccountName={}))'.format(username), attributes=['name']) try: + self.conn.search(BASE_DN, '(&(objectclass=user)(sAMAccountName={}))'.format(username), attributes=['name']) return True, self.conn.entries[0]['name'] except Exception as e: - return False, str(e) + return False, "AdOps Exception: {}" .format(e) def ad_get_user_dn_by_account(self, username): """ @@ -123,8 +126,11 @@ class AdOps(object): :param username: :return: DN """ - self.conn.search(BASE_DN, '(&(objectclass=user)(sAMAccountName={}))'.format(username), attributes=['distinguishedName']) - return str(self.conn.entries[0]['distinguishedName']) + try: + self.conn.search(BASE_DN, '(&(objectclass=user)(sAMAccountName={}))'.format(username), attributes=['distinguishedName']) + return True, str(self.conn.entries[0]['distinguishedName']) + except Exception as e: + return False, "AdOps Exception: {}" .format(e) def ad_get_user_status_by_account(self, username): """ @@ -132,8 +138,11 @@ class AdOps(object): :param username: :return: user_account_control code """ - self.conn.search(BASE_DN, '(&(objectclass=user)(sAMAccountName={}))'.format(username), attributes=['userAccountControl']) - return self.conn.entries[0]['userAccountControl'] + try: + self.conn.search(BASE_DN, '(&(objectclass=user)(sAMAccountName={}))'.format(username), attributes=['userAccountControl']) + return True, self.conn.entries[0]['userAccountControl'] + except Exception as e: + return False, "AdOps Exception: {}" .format(e) def ad_unlock_user_by_account(self, username): """ @@ -141,11 +150,14 @@ class AdOps(object): :param username: :return: """ - user_dn = self.ad_get_user_dn_by_account(username) - try: - return True, self.conn.extend.microsoft.unlock_account(user='%s' % user_dn) - except Exception as e: - return False, str(e) + _status, user_dn = self.ad_get_user_dn_by_account(username) + if _status: + try: + return True, self.conn.extend.microsoft.unlock_account(user='%s' % user_dn) + except Exception as e: + return False, "AdOps Exception: {}".format(e) + else: + return False, user_dn def ad_reset_user_pwd_by_account(self, username, new_password): """ @@ -153,33 +165,35 @@ class AdOps(object): :param username: :return: """ - user_dn = self.ad_get_user_dn_by_account(username) - if self.conn.check_names: - user_dn = safe_dn(user_dn) - encoded_new_password = ('"%s"' % new_password).encode('utf-16-le') - result = self.conn.modify(user_dn, - {'unicodePwd': [(MODIFY_REPLACE, [encoded_new_password])]}, - ) - - if not self.conn.strategy.sync: - _, result = self.conn.get_response(result) - else: - if self.conn.strategy.thread_safe: - _, result, _, _ = result + _status, user_dn = self.ad_get_user_dn_by_account(username) + if _status: + if self.conn.check_names: + user_dn = safe_dn(user_dn) + encoded_new_password = ('"%s"' % new_password).encode('utf-16-le') + result = self.conn.modify(user_dn, + {'unicodePwd': [(MODIFY_REPLACE, [encoded_new_password])]}, + ) + if not self.conn.strategy.sync: + _, result = self.conn.get_response(result) else: - result = self.conn.result + if self.conn.strategy.thread_safe: + _, result, _, _ = result + else: + result = self.conn.result - # change successful, returns True - if result['result'] == RESULT_SUCCESS: - return True, '密码己修改成功,请妥善保管!' + # change successful, returns True + if result['result'] == RESULT_SUCCESS: + return True, '密码己修改成功,请妥善保管!' - # change was not successful, raises exception if raise_exception = True in connection or returns the operation result, error code is in result['result'] - if self.conn.raise_exceptions: - from ldap3.core.exceptions import LDAPOperationResult - _msg = LDAPOperationResult(result=result['result'], description=result['description'], dn=result['dn'], message=result['message'], - response_type=result['type']) - return False, _msg - return False, result['result'] + # change was not successful, raises exception if raise_exception = True in connection or returns the operation result, error code is in result['result'] + if self.conn.raise_exceptions: + from ldap3.core.exceptions import LDAPOperationResult + _msg = LDAPOperationResult(result=result['result'], description=result['description'], dn=result['dn'], message=result['message'], + response_type=result['type']) + return False, _msg + return False, result['result'] + else: + return False, user_dn def ad_get_user_locked_status_by_account(self, username): """ @@ -187,12 +201,15 @@ class AdOps(object): :param username: :return: 如果结果是1601-01-01说明账号未锁定,返回0 """ - self.conn.search(BASE_DN, '(&(objectclass=user)(sAMAccountName={}))'.format(username), attributes=['lockoutTime']) - locked_status = self.conn.entries[0]['lockoutTime'] - if '1601-01-01' in str(locked_status): - return 0 - else: - return locked_status + try: + self.conn.search(BASE_DN, '(&(objectclass=user)(sAMAccountName={}))'.format(username), attributes=['lockoutTime']) + locked_status = self.conn.entries[0]['lockoutTime'] + if '1601-01-01' in str(locked_status): + return True, 'unlocked' + else: + return False, locked_status + except Exception as e: + return False, "AdOps Exception: {}" .format(e) if __name__ == '__main__': @@ -204,14 +221,16 @@ if __name__ == '__main__': # print(conn.result) # conn = _ad_connect() - user = 'zhangsan' - old_password = 'K2dhhuT1Zf11111cnJ1ollC3y' - # old_password = 'L1qyrmZDUFeYW1OIualjlNhr4' - new_password = 'K2dhhuT1Zf11111cnJ1ollC3y' - ad_ops = AdOps() - # ad_ops = AdOps(user=user, password=old_password) - status, msg = ad_ops.ad_auth_user(username=user, password=old_password) - print(msg) - if status: - res = ad_ops.ad_reset_user_pwd_by_account(user, new_password) - print(res) + # user = 'zhangsan' + # old_password = 'K2dhhuT1Zf11111cnJ1ollC3y' + # # old_password = 'L1qyrmZDUFeYW1OIualjlNhr4' + # new_password = 'K2dhhuT1Zf11111cnJ1ollC3y' + # ad_ops = AdOps() + # # ad_ops = AdOps(user=user, password=old_password) + # status, msg = ad_ops.ad_auth_user(username=user, password=old_password) + # print(msg) + # if status: + # res = ad_ops.ad_reset_user_pwd_by_account(user, new_password) + # print(res) + _ad = AdOps() + print(_ad.ad_ensure_user_by_account('le.xiang')) \ No newline at end of file