添加自动安装脚本,方便快速部署上线。

调整部分代码,之前scheme是写死的https,考虑可能会有使用http,所以改成从request.scheme中取。
添加uwsgi启动脚本
This commit is contained in:
pwdselfservice
2020-04-01 17:28:51 +08:00
parent fa8beccc12
commit 1352586d70
33 changed files with 913 additions and 168 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

34
resetpwd/form.py Normal file
View File

@@ -0,0 +1,34 @@
from django.forms import fields as c_fields
from django import forms as c_forms
from django.core.exceptions import ValidationError
class CheckForm(c_forms.Form):
new_password = c_fields.RegexField(
'(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9]).{8,30}',
# 密码必须同时包含大写、小写、数字和特殊字符其中三项且至少8位
strip=True,
min_length=8,
max_length=30,
error_messages={'required': '新密码不能为空.',
'invalid': '密码必须包含数字,字母、特殊字符',
'min_length': "密码长度不能小于8个字符",
'max_length': "密码长度不能大于30个字符"}
)
old_password = c_fields.CharField(error_messages={'required': '确认密码不能为空'})
ensure_password = c_fields.CharField(error_messages={'required': '确认密码不能为空'})
user_email = c_fields.CharField(error_messages={'required': '邮箱不能为空', 'invalid': '邮箱格式错误'})
def clean(self):
pwd0 = self.cleaned_data.get('old_password')
pwd1 = self.cleaned_data.get('new_password')
pwd2 = self.cleaned_data.get('ensure_password')
if pwd1 == pwd2:
pass
elif pwd0 == pwd1:
# 这里异常模块导入要放在函数里面,放到文件开头有时会报错,找不到
from django.core.exceptions import ValidationError
raise ValidationError('新旧密码不能一样')
else:
from django.core.exceptions import ValidationError
raise ValidationError('新密码和确认密码输入不一致')

View File

@@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -3,6 +3,10 @@ from pwdselfservice.local_settings import *
def __ad_connect():
"""
AD连接器
:return:
"""
username = str(AD_LOGIN_USER).lower()
server = Server(host=AD_HOST, use_ssl=True, port=636, get_info='ALL')
try:
@@ -22,7 +26,9 @@ def ad_ensure_user_by_sam(username):
base_dn = BASE_DN
condition = '(&(objectclass=person)(mail=' + username + '))'
attributes = ['sAMAccountName']
return conn.search(base_dn, condition, attributes=attributes)
result = conn.search(base_dn, condition, attributes=attributes)
conn.unbind()
return result
def ad_ensure_user_by_mail(user_mail_addr):
@@ -35,10 +41,17 @@ def ad_ensure_user_by_mail(user_mail_addr):
base_dn = BASE_DN
condition = '(&(objectclass=person)(mail=' + user_mail_addr + '))'
attributes = ['mail']
return conn.search(base_dn, condition, attributes=attributes)
result = conn.search(base_dn, condition, attributes=attributes)
conn.unbind()
return result
def ad_get_user_displayname_by_mail(user_mail_addr):
"""
通过mail查询某个用户的显示名
:param user_mail_addr:
:return: user_displayname
"""
conn = __ad_connect()
conn.search(BASE_DN, '(&(objectclass=person)(mail=' + user_mail_addr + '))', attributes=[
'displayName'])
@@ -48,22 +61,39 @@ def ad_get_user_displayname_by_mail(user_mail_addr):
def ad_get_user_dn_by_mail(user_mail_addr):
"""
通过mail查询某个用户的完整DN
:param user_mail_addr:
:return: DN
"""
conn = __ad_connect()
conn.search(BASE_DN,
'(&(objectclass=person)(mail=' + user_mail_addr + '))', attributes=['distinguishedName'])
user_dn = conn.entries[0]['distinguishedName']
conn.unbind()
return user_dn
def ad_get_user_status_by_mail(user_mail_addr):
"""
通过mail查询某个用户的账号状态
:param user_mail_addr:
:return: user_account_control code
"""
conn = __ad_connect()
conn.search(BASE_DN,
'(&(objectclass=person)(mail=' + user_mail_addr + '))', attributes=['userAccountControl'])
user_account_control = conn.entries[0]['userAccountControl']
conn.unbind()
return user_account_control
def ad_unlock_user_by_mail(user_mail_addr):
"""
通过mail解锁某个用户
:param user_mail_addr:
:return:
"""
conn = __ad_connect()
user_dn = ad_get_user_dn_by_mail(user_mail_addr)
result = conn.extend.microsoft.unlock_account(user="%s" % user_dn)
@@ -72,6 +102,11 @@ def ad_unlock_user_by_mail(user_mail_addr):
def ad_reset_user_pwd_by_mail(user_mail_addr, new_password):
"""
通过mail重置某个用户的密码
:param user_mail_addr:
:return:
"""
conn = __ad_connect()
user_dn = ad_get_user_dn_by_mail(user_mail_addr)
result = conn.extend.microsoft.modify_password(user="%s" % user_dn, new_password="%s" % new_password)
@@ -80,6 +115,11 @@ def ad_reset_user_pwd_by_mail(user_mail_addr, new_password):
def ad_modify_user_pwd_by_mail(user_mail_addr, old_password, new_password):
"""
通过mail修改某个用户的密码
:param user_mail_addr:
:return:
"""
conn = __ad_connect()
user_dn = ad_get_user_dn_by_mail(user_mail_addr)
result = conn.extend.microsoft.modify_password(user="%s" % user_dn, new_password="%s" % new_password,
@@ -89,10 +129,15 @@ def ad_modify_user_pwd_by_mail(user_mail_addr, old_password, new_password):
def ad_get_user_locked_status_by_mail(user_mail_addr):
"""
通过mail获取某个用户账号是否被锁定
:param user_mail_addr:
:return: 如果结果是1601-01-01说明账号未锁定返回0
"""
conn = __ad_connect()
conn.search(BASE_DN, '(&(objectclass=person)(mail=' + user_mail_addr + '))', attributes=['lockoutTime'])
locked_status = conn.entries[0]['lockoutTime']
print(locked_status)
conn.unbind()
if '1601-01-01' in str(locked_status):
return 0
else:

View File

@@ -4,6 +4,10 @@ from pwdselfservice.local_settings import *
def ding_get_access_token():
"""
获取钉钉access token
:return:
"""
resp = requests.get(
url=DING_URL + "/gettoken",
params=dict(appid=DING_SELF_APP_ID, appsecret=DING_SELF_APP_SECRET)
@@ -16,6 +20,10 @@ def ding_get_access_token():
def ding_get_persistent_code(code, token):
"""
获取钉钉当前用户的unionid
:return:
"""
resp = requests.post(
url="%s/get_persistent_code?access_token=%s" % (DING_URL, token),
json=dict(tmp_auth_code=code),
@@ -28,11 +36,20 @@ def ding_get_persistent_code(code, token):
def ding_client_connect():
"""
钉钉连接器
:return:
"""
client = AppKeyClient(corp_id=DING_CORP_ID, app_key=DING_APP_KEY, app_secret=DING_APP_SECRET)
return client
def ding_get_dept_user_list_detail(dept_id, offset, size):
"""
获取部门中的用户列表详细清单
:param code:
:return:
"""
client = ding_client_connect()
result = client.user.list(department_id=dept_id, offset=offset, size=size)
return result

View File

@@ -4,12 +4,12 @@ from django.contrib import messages
from dingtalk import *
from resetpwd.models import *
from .crypto import Crypto
from .ad import ad_get_user_locked_status_by_mail, ad_unlock_user_by_mail, ad_reset_user_pwd_by_mail, \
from resetpwd.utils.ad import ad_get_user_locked_status_by_mail, ad_unlock_user_by_mail, ad_reset_user_pwd_by_mail, \
ad_get_user_status_by_mail, ad_ensure_user_by_mail, ad_modify_user_pwd_by_mail
from .dingding import ding_get_userinfo_detail, ding_get_userid_by_unionid, ding_get_userinfo_by_code, \
ding_get_persistent_code, ding_get_access_token
from pwdselfservice.local_settings import *
from .form import *
from resetpwd.form import *
class CustomPasswortValidator(object):

View File

@@ -6,17 +6,20 @@ from resetpwd.utils.ad import ad_get_user_locked_status_by_mail, ad_unlock_user_
from resetpwd.utils.dingding import ding_get_userinfo_detail, ding_get_userid_by_unionid, \
ding_get_persistent_code, ding_get_access_token
from pwdselfservice.local_settings import *
from resetpwd.utils.form import CheckForm
from .form import CheckForm
import logging
msg_template = 'msg.html'
home_url = HOME_URL
logger = logging.getLogger('django')
def resetpwd_index(request):
home_url = HOME_URL
"""
用户修改密码
:param request:
:return:
"""
home_url = '%s://%s' % (request.scheme, HOME_URL)
app_id = DING_SELF_APP_ID
if request.method == 'GET':
return render(request, 'index.html', locals())
@@ -24,8 +27,8 @@ def resetpwd_index(request):
logger.error('[异常] 请求方法:%s,请求路径:%s' % (request.method, request.path))
if request.method == 'POST':
# 对前端提交的数据进行二次验证,防止恶意提交简单密码或串改账号。
check_form = CheckForm(request.POST)
# 对前端提交的用户名、密码进行二次验证防止有人恶意修改前端JS提交简单密码或提交非法用户
if check_form.is_valid():
form_obj = check_form.cleaned_data
user_email = form_obj.get("user_email")
@@ -41,55 +44,81 @@ def resetpwd_index(request):
}
return render(request, msg_template, context)
try:
# 判断账号是否被锁定
if ad_get_user_locked_status_by_mail(user_mail_addr=user_email) is not 0:
context = {
'msg': "此账号己被锁定,请先解锁账号。",
'button_click': "window.history.back()",
'button_display': "返回"
}
return render(request, msg_template, context)
if user_email and old_password and new_password:
try:
# 判断账号是否被锁定
if ad_get_user_locked_status_by_mail(user_mail_addr=user_email) is not 0:
context = {
'msg': "此账号己被锁定,请先解锁账号。",
'button_click': "window.history.back()",
'button_display': "返回"
}
return render(request, msg_template, context)
# 判断账号状态是否禁用或锁定
if ad_get_user_status_by_mail(user_mail_addr=user_email) == 514 or ad_get_user_status_by_mail(
user_mail_addr=user_email) == 66050:
# 514 66050是AD中账号被禁用的特定代码这个可以在微软官网查到。
if ad_get_user_status_by_mail(user_mail_addr=user_email) == 514 or ad_get_user_status_by_mail(
user_mail_addr=user_email) == 66050:
context = {
'msg': "此账号状态为己禁用请联系HR确认账号是否正确。",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
try:
result = ad_modify_user_pwd_by_mail(user_mail_addr=user_email, old_password=old_password,
new_password=new_password)
if result:
context = {
'msg': "密码己修改成功,请妥善保管密码。你可直接关闭此页面!",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
else:
context = {
'msg': "密码未修改成功,请确认旧密码是否正确。",
'button_click': "window.history.back()",
'button_display': "返回"
}
return render(request, msg_template, context)
except IndexError:
context = {
'msg': "请确认邮箱账号[%s]是否正确未能在Active Directory中检索到相关信息。" % user_email,
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
except Exception as e:
context = {
'msg': "出现未预期的错误[%s],请与管理员联系~" % str(e),
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
except IndexError:
context = {
'msg': "此账号状态为己禁用请联系HR确认账号是否正确。",
'msg': "请确认邮箱账号[%s]是否正确未能在Active Directory中检索到相关信息。" % user_email,
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
except IndexError:
context = {
'msg': "请确认邮箱账号[%s]是否正确未能在Active Directory中检索到相关信息。" % user_email,
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
except Exception as e:
context = {
'msg': "出现未预期的错误[%s],请与管理员联系~" % str(e),
'button_click': "window.history.back()",
'button_display': "返回"
}
return render(request, msg_template, context)
# 修改密码
result = ad_modify_user_pwd_by_mail(user_mail_addr=user_email, old_password=old_password,
new_password=new_password)
if result is True:
context = {
'msg': "密码己修改成功,请妥善保管密码。你可直接关闭此页面!",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
except Exception as e:
context = {
'msg': "出现未预期的错误[%s],请与管理员联系~" % str(e),
'button_click': "window.history.back()",
'button_display': "返回"
}
return render(request, msg_template, context)
else:
context = {
'msg': "密码未修改成功,请确认旧密码是否正确",
'msg': "用户名、旧密码、新密码参数不正确,请重新确认后输入",
'button_click': "window.history.back()",
'button_display': "返回"
}
@@ -105,6 +134,13 @@ def resetpwd_index(request):
def resetpwd_check_userinfo(request):
"""
钉钉扫码回调数据对用户在AD中进行验证
扫码之后从钉钉中取出用户的unionid
:param request:
:return:
"""
home_url = '%s://%s' % (request.scheme, HOME_URL)
code = request.GET.get('code')
if code:
logger.info('[成功] 请求方法:%s,请求路径:%sCODE%s' % (request.method, request.path, code))
@@ -112,7 +148,7 @@ def resetpwd_check_userinfo(request):
logger.error('[异常] 请求方法:%s,请求路径:%s未能拿到CODE。' % (request.method, request.path))
try:
unionid = ding_get_persistent_code(code, ding_get_access_token())
# unionid 在钉钉企业中是否存在
# 判断 unionid 在本企业钉钉中是否存在
if not unionid:
logger.error('[异常] 请求方法:%s,请求路径:%s未能拿到unionid。' % (request.method, request.path))
context = {
@@ -127,8 +163,9 @@ def resetpwd_check_userinfo(request):
# 钉钉中此账号是否可用
if ding_user_info['active']:
crypto = Crypto(CRYPTO_KEY)
# 对unionid进行加密因为unionid基本上固定不变的为了防止unionid泄露而导致重复使用进行加密后再传回。
unionid_cryto = crypto.encrypt(unionid)
# 配置cookie并重定向到重置密码页面。
# 配置cookie通过cookie把加密后的用户unionid传到重置密码页面并重定向到重置密码页面。
set_cookie = HttpResponseRedirect('resetpwd')
set_cookie.set_cookie('tmpid', unionid_cryto, expires=TMPID_COOKIE_AGE)
return set_cookie
@@ -144,7 +181,7 @@ def resetpwd_check_userinfo(request):
'msg': "用户不存在或己离职",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
}
return render(request, msg_template, context)
except Exception as e:
logger.error('[异常] %s' % str(e))
@@ -169,7 +206,14 @@ def resetpwd_check_userinfo(request):
def resetpwd_reset(request):
"""
钉钉扫码并验证信息之后,在重置密码页面将用户邮箱进行绑定
:param request:
:return:
"""
global unionid_crypto
home_url = '%s://%s' % (request.scheme, HOME_URL)
# 从cookie中提取unionid并解密然后对当前unionid的用户进行重置密码
if request.method == 'GET':
try:
unionid_crypto = request.COOKIES.get('tmpid')
@@ -183,14 +227,18 @@ def resetpwd_reset(request):
'button_display': "返回主页"
}
return render(request, msg_template, context)
# 解密
crypto = Crypto(CRYPTO_KEY)
unionid = crypto.decrypt(unionid_crypto)
# 通过unionid在钉钉中拿到用户的邮箱
user_email = ding_get_userinfo_detail(ding_get_userid_by_unionid(unionid))['email']
# 如果邮箱在钉钉中能提取,则提交到前端绑定
if user_email:
context = {
'user_email': user_email,
}
return render(request, 'resetpwd.html', context)
# 否则就是钉钉中此用户未配置邮箱,返回相关提示
else:
context = {
'msg': "%s 您好企业钉钉中未能找到您账号的邮箱配置请联系HR完善信息。" % ding_get_userinfo_detail(ding_get_userid_by_unionid(
@@ -200,9 +248,11 @@ def resetpwd_reset(request):
}
return render(request, msg_template, context)
# 重置密码页面,输入新密码后点击提交
elif request.method == 'POST':
new_password = request.POST.get('new_password').strip()
unionid_crypto = request.COOKIES.get('tmpid')
# 对cookie中的unionid进行超时验证如果页面超时就不再做处理。
if not unionid_crypto:
context = {
'msg': "会话己超时,请重新扫码验证用户信息。",
@@ -271,6 +321,12 @@ def resetpwd_reset(request):
def resetpwd_unlock(request):
"""
解锁账号
:param request:
:return:
"""
home_url = '%s://%s' % (request.scheme, HOME_URL)
if request.method == 'GET':
unionid_crypto = request.COOKIES.get('tmpid')
if not unionid_crypto:
@@ -308,37 +364,37 @@ def resetpwd_unlock(request):
'button_display': "返回主页"
}
return render(request, msg_template, context)
try:
result = ad_unlock_user_by_mail(user_email)
if result:
else:
try:
result = ad_unlock_user_by_mail(user_email)
if result:
context = {
'msg': "账号己解锁成功。你可以点击返回主页或直接关闭此页面!",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
else:
context = {
'msg': "账号未能解锁请联系管理员确认该账号在AD的是否己禁用。",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
except IndexError:
context = {
'msg': "账号己解锁成功。你可以点击返回主页或直接关闭此页面!",
'msg': "请确认邮箱账号[%s]是否正确未能在AD中检索到相关信息。" % user_email,
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
else:
except Exception as e:
context = {
'msg': "账号未能解锁请联系管理员确认该账号在AD的是否己禁用。",
'msg': "出现未预期的错误[%s],请与管理员联系~" % str(e),
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
except IndexError:
context = {
'msg': "请确认邮箱账号[%s]是否正确未能在AD中检索到相关信息。" % user_email,
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
except Exception as e:
context = {
'msg': "出现未预期的错误[%s],请与管理员联系~" % str(e),
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
else:
context = {
'msg': "请从主页开始进行操作。",