修改钉钉/企业微信直接使用内部应用免密登录的方式来验证,不再支持扫码。

由于一些API的权限发生变化,导致一些关键信息无法获取,所以做以上改变。
删除了无用的代码,其它没啥变化,没太多时间重写,先就这么着吧。
This commit is contained in:
Leven 2022-12-20 13:20:40 +08:00
parent 2e886dc6e8
commit c5bc154924
11 changed files with 69 additions and 205 deletions

View File

@ -31,10 +31,7 @@ AD_CONN_PORT = 636
# 值是DING / WEWORK
AUTH_CODE_TYPE = 'DING'
# ########## 钉钉 《如果不使用钉钉扫码,可不用配置》##########
# 钉钉接口主地址,不可修改
DING_URL = r'https://oapi.dingtalk.com'
# ########## 钉钉 《如果不使用钉钉,可不用配置》##########
# 钉钉企业ID <CorpId>,修改为自己的
DING_CORP_ID = '修改为自己的'
@ -48,7 +45,7 @@ DING_MO_APP_ID = r'修改为自己的'
DING_MO_APP_SECRET = r'修改为自己的'
# ####### 企业微信《如果不使用企业微信扫码,可不用配置》 ##########
# ####### 企业微信《如果不使用企业微信,可不用配置》 ##########
# 企业微信的企业ID
WEWORK_CORP_ID = r'修改为自己的'
# 应用的AgentId
@ -58,11 +55,9 @@ WEWORK_AGNET_SECRET = r'修改为自己的'
# Redis配置
# redis的连接地址redis://<Ip/Host>:<Port>/<数据库>
REDIS_LOCATION = r'redis://127.0.0.1:6379/1'
REDIS_PASSWORD = r'12345678'
REDIS_LOCATION = r'redis://127.0.0.1:6379'
REDIS_PASSWORD = r'修改为自己的'
# COOKIE超时时间单位是秒可不用修改
TMPID_COOKIE_AGE = 300
# 主页域名钉钉跳转等需要指定域名格式pwd.abc.com。
# 如果是自定义安装,请修改成自己的域名

View File

@ -1,19 +1,17 @@
import datetime
from cryptography.fernet import Fernet
import sys
from django_redis import get_redis_connection
from utils.storage.kvstorage import KvStorage
from utils.storage.memorystorage import MemoryStorage
import datetime
from traceback import format_exc
try:
redis_conn = get_redis_connection()
cache_storage = KvStorage(redis_conn)
cache_storage.set('redis_connection', str(datetime.datetime.now()))
redis_get = cache_storage.get('redis_connection')
# print("Redis连接成功set/get测试通过--{}Token缓存将使用Redis处理".format(redis_get))
except Exception as e:
cache_storage = MemoryStorage()
print("Redis无法连接Token缓存将使用MemoryStorage处理")
print("如果确定需要使用Redis作为缓存请排查Redis配置错误信息如下")
print("Redis Exception: {}".format(e))
crypto_key = Fernet.generate_key()
print("请排查Redis配置错误信息如下")
print("Redis Exception: {}".format(format_exc()))
sys.exit(1)

View File

@ -8,7 +8,6 @@ else:
from conf.local_settings import REDIS_PASSWORD, REDIS_LOCATION
DEBUG = False
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@ -80,8 +79,10 @@ SESSION_SAVE_EVERY_REQUEST = True
SESSION_COOKIE_AGE = 300
# False 会话cookie可以在用户浏览器中保持有效期。True关闭浏览器则Cookie失效。
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
# 建议配置,阻止 javascript 对会话数据的访问,提高安全性。
# SESSION_COOKIE_HTTPONLY= True
# session使用的存储方式
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
# 指明使用哪一个库保存session数据
SESSION_CACHE_ALIAS = "session"
INSTALLED_APPS = [
@ -132,11 +133,19 @@ AD_ACCOUNT_DISABLE_CODE = [514, 66050]
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": REDIS_LOCATION,
"LOCATION": "{}/1".format(REDIS_LOCATION),
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"PASSWORD": REDIS_PASSWORD,
"IGNORE_EXCEPTIONS": True,
}
},
"session": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "{}/3".format(REDIS_LOCATION), # 指明使用redis的3号数据库
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"PASSWORD": REDIS_PASSWORD,
"COMPRESSOR": "django_redis.compressors.zlib.ZlibCompressor",
"IGNORE_EXCEPTIONS": True,
}
}

View File

@ -159,14 +159,6 @@ WEWORK_AGNET_SECRET = r'修改为自己的'
REDIS_LOCATION = r'redis://127.0.0.1:6379/1'
REDIS_PASSWORD = r'12345678'
# ##########################
# 执行python3 ./utils/crypto.py 生成
# 可自行生成后替换
CRYPTO_KEY = b'dp8U9y7NAhCD3MoNwPzPBhBtTZ1uI_WWSdpNs6wUDgs='
# COOKIE 超时单位是秒,可不用修改
TMPID_COOKIE_AGE = 300
# 主页域名钉钉跳转等需要指定域名格式pwd.abc.com。
# 如果是自定义安装,请修改成自己的域名
HOME_URL = 'PWD_SELF_SERVICE_DOMAIN'
@ -257,9 +249,6 @@ REDIS_PASSWORD = r'12345678'
# 可自行生成后替换
CRYPTO_KEY = b'dp8U9y7NAhCD3MoNwPzPBhBtTZ1uI_WWSdpNs6wUDgs='
# COOKIE 超时单位是秒,可不用修改
TMPID_COOKIE_AGE = 300
# 主页域名钉钉跳转等需要指定域名格式pwd.abc.com。
# 如果是自定义安装,请修改成自己的域名
HOME_URL = 'PWD_SELF_SERVICE_DOMAIN'

View File

@ -2,10 +2,10 @@ Django==3.2
pycryptodome==3.10.1
attrs==21.2.0
python-dateutil==2.8.1
alibabacloud_dingtalk==1.4.96
dingtalk-sdk==1.3.8
cryptography==3.4.7
ldap3==2.9
django-redis==4.12.1
feishu-python-sdk==0.1.4
requests
uwsgi
django-redis==4.12.1
requests==2.28.1
uwsgi==2.0.21

View File

@ -13,7 +13,6 @@ 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
import os
APP_ENV = os.getenv('APP_ENV')
if APP_ENV == 'dev':
@ -56,70 +55,6 @@ def code_2_user_info_with_oauth2(ops, request, msg_template, home_url, code):
return True, user_id, user_info
def crypto_id_2_user_info(ops, request, msg_template, home_url, scan_app_tag):
"""
能过前端提交的加密的userid来获取用户信息<userinfo>
"""
try:
crypto_tmp_id = request.COOKIES.get('tmpid')
if not crypto_tmp_id:
logger.error('[异常] 请求方法:%s,请求路径:%s未能拿到TmpID或会话己超时。' % (request.method, request.path))
context = {
'msg': "会话己超时,请重新验证用户信息。",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return False, context
# 解密
crypto = Crypto(crypto_key)
user_id = crypto.decrypt(crypto_tmp_id)
# 通过user_id拿到用户的邮箱并格式化为username
userid_status, user_info = ops.get_user_detail_by_user_id(user_id)
if not userid_status:
context = {
'msg': '获取{}用户信息失败,错误信息:{}'.format(user_info, scan_app_tag),
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return False, context
return True, user_info
except Exception as e:
return False, str(e)
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)
# 配置cookie通过cookie把加密后的用户user_id传到重置密码页面并重定向到重置密码页面。
set_cookie = HttpResponseRedirect('resetPassword')
set_cookie.set_cookie('tmpid', _id_cryto, expires=TMPID_COOKIE_AGE)
return set_cookie
def crypto_id_2_user_id(request, msg_template, home_url):
"""
前端提交的加密的userid解密出真实的userid
"""
try:
crypto_tmp_id = request.COOKIES.get('tmpid')
# 解密
crypto = Crypto(crypto_key)
return True, crypto.decrypt(crypto_tmp_id)
except Exception as e:
logger.error('[异常] %s' % str(e))
logger.error('[异常] 请求方法:%s,请求路径:%s未能拿到TmpID或会话己超时。' % (request.method, request.path))
context = {
'msg': "会话己超时,请重新扫码验证用户信息。",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return False, context
def ops_account(ad_ops, request, msg_template, home_url, username, new_password):
"""
ad 账号操作判断账号状态重置密码或解锁账号

View File

@ -7,7 +7,7 @@ from ldap3.core.exceptions import LDAPException
import urllib.parse as url_encode
from utils.format_username import format2username, get_user_is_active, get_email_from_userinfo
from .form import CheckForm
from .utils import code_2_user_detail, crypto_id_2_user_info, ops_account
from .utils import code_2_user_detail, ops_account
from django.conf import settings
APP_ENV = os.getenv('APP_ENV')
if APP_ENV == 'dev':
@ -34,13 +34,6 @@ class PARAMS(object):
AUTH_APP = '微信'
from utils.wework_ops import WeWorkOps
ops = WeWorkOps()
else:
corp_id = None
app_id = WEWORK_CORP_ID
agent_id = WEWORK_AGENT_ID
AUTH_APP = '微信'
from utils.wework_ops import WeWorkOps
ops = WeWorkOps()
scan_params = PARAMS()
@ -63,8 +56,6 @@ def index(request):
return render(request, 'ding_index.v1.html', locals())
elif request.method == 'GET' and AUTH_CODE_TYPE == 'WEWORK':
return render(request, 'we_index.v1.html', locals())
elif request.method == 'GET' and AUTH_CODE_TYPE == 'FEISHU':
return render(request, 'index.v1.html', locals())
else:
logger.error('[异常] 请求方法:%s,请求路径%s' % (request.method, request.path))
#
@ -123,7 +114,6 @@ def reset_password(request):
:return:
"""
home_url = '%s://%s' % (request.scheme, HOME_URL)
# 从cookie中提取union_id并解密然后对当前union_id的用户进行重置密码
if request.method == 'GET':
code = request.GET.get('code')
if code:
@ -138,13 +128,13 @@ def reset_password(request):
return render(request, msg_template, context)
try:
# 用code换取用户基本信息
_status, user_id, user_info = code_2_user_detail(_ops, home_url, code)
if not _status:
return render(request, msg_template, user_id)
# 账号是否是激活的
# 账号在企业微信或钉钉中是否是激活的
_, res = get_user_is_active(user_info)
if not _:
# 否则账号不存在或未激活
context = {
'msg': '当前扫码的用户未激活或可能己离职,用户信息如下:%s' % user_info,
'button_click': "window.location.href='%s'" % home_url,
@ -160,7 +150,7 @@ def reset_password(request):
logger.error('[异常] %s' % str(callback_e))
return render(request, msg_template, context)
# 通过user_info拿到用户信息并格式化为username
# 通过user_info拿到用户邮箱并格式化为username
_, email = get_email_from_userinfo(user_info)
if not _:
context = {
@ -181,8 +171,10 @@ def reset_password(request):
# 如果邮箱能提取到,则格式化之后,提取出账号提交到前端绑定
if username:
request.session[username] = code
context = {
'username': username,
'code': code,
}
return render(request, 'resetPassword.v1.html', context)
else:
@ -195,43 +187,25 @@ def reset_password(request):
# 重置密码页面,输入新密码后点击提交
elif request.method == 'POST':
try:
username = request.POST.get('username')
code = request.POST.get('code')
if request.session.get(username) and request.session.get(username) == code:
_new_password = request.POST.get('new_password').strip()
_status, user_info = crypto_id_2_user_info(_ops, request, msg_template, home_url, scan_params.AUTH_APP)
if not _status:
return render(request, msg_template, user_info)
# 通过user_info拿到用户信息并格式化为username
_, email = get_email_from_userinfo(user_info)
if not _:
try:
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': email,
'msg': "错误[%s],请与管理员联系." % str(reset_e),
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
logger.error('[异常] %s' % str(reset_e))
return render(request, msg_template, context)
# 格式化用户名
_, username = format2username(email)
if _ is False:
context = {
'msg': username,
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
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),
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
logger.error('[异常] %s' % str(reset_e))
return render(request, msg_template, context)
finally:
del request.session[username]
else:
context = {
'msg': "请从主页开始进行操作。",
'msg': "认证已经失效,请从主页重新进行操作。",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
@ -245,62 +219,26 @@ def unlock_account(request):
:return:
"""
home_url = '%s://%s' % (request.scheme, HOME_URL)
if request.method == 'GET':
_status, user_info = crypto_id_2_user_info(_ops, request, msg_template, home_url, scan_params.AUTH_APP)
if not _status:
return render(request, msg_template, user_info)
# 通过user_info拿到用户信息并格式化为username
_, email = get_email_from_userinfo(user_info)
if not _:
context = {
'msg': email,
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
_, username = format2username(email)
if _ is False:
context = {
'msg': username,
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
context = {
'username': username,
}
return render(request, 'resetPassword.v1.html', context)
elif request.method == 'POST':
_status, user_info = crypto_id_2_user_info(_ops, request, msg_template, home_url, scan_params.AUTH_APP)
if not _status:
return render(request, msg_template, user_info)
# 通过user_info拿到用户信息并格式化为username
_, email = get_email_from_userinfo(user_info)
if not _:
context = {
'msg': email,
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
# 格式化用户名
_, username = format2username(email)
if _ is False:
context = {
'msg': username,
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
return ops_account(AdOps(), request, msg_template, home_url, username, None)
if request.method == 'POST':
username = request.POST.get('username')
code = request.POST.get('code')
if request.session.get(username) and request.session.get(username) == code:
try:
return ops_account(AdOps(), request, msg_template, home_url, username, None)
except Exception as reset_e:
context = {
'msg': "错误[%s],请与管理员联系." % str(reset_e),
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
logger.error('[异常] %s' % str(reset_e))
return render(request, msg_template, context)
finally:
del request.session[username]
else:
context = {
'msg': "请从主页开始进行操作。",
'msg': "认证已经失效,请从主页重新进行操作。",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}

View File

@ -1,4 +1,4 @@
a, body, button, dd, div, dl, dt, h1, h2, h3, h4, h5, h6, input, li, ol, p, td, textarea, ul { margin: 0; padding: 0; }
a, body, button, dd, div, dl, dt, h1, h2, h3, h4, h5, h6, input, li, ol, p, td, textarea, ul { margin: 0; padding: 0; }
body, button, input, select, textarea { font: 9pt/1.5 tahoma,arial,Hiragino Sans GB,\5b8b\4f53,sans-serif; }
button, h1, h2, h3, h4, h5, h6, input, select, textarea { font-size: 100%; }
/*background-image: linear-gradient(160deg, #2f548e 20%,#043559 80%);*/

View File

@ -1,4 +1,4 @@

function BtnClick(btn, type, unsecpwd) {
$(btn).click(function () {
// ^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[!@#$\%\^\&\*\(\)])[0-9a-zA-Z!@#$\%\^\&\*\(\)]{8,32}$ 要求密码了里面包含字母、数字、特殊字符。

View File

@ -1,4 +1,4 @@
{% load static %}
{% load static %}
<!DOCTYPE html>
<html lang="zh-CN">
<head>

View File

@ -6,9 +6,7 @@
<title>自助密码平台</title>
<link rel="stylesheet" href="{% static 'css/dmaku.css' %}">
<script type="text/javascript" src="{% static 'js/jquery-1.8.3.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/alert.js' %}"></script>
<script type="text/javascript" src="{% static 'js/check.js' %}"></script>
<script type="text/javascript" src="{% static 'js/ddLogin-0.0.5.js' %}"></script>
</head>
<body>
<div class="head-container" id="head-container">
@ -22,6 +20,7 @@
{% csrf_token %}
<h1>重置</h1>
<input type="text" id="username" name="username" readonly placeholder="{{ username }}" value="{{ username }}">
<input type="hidden" id="code" name="code" readonly value="{{ code }}">
<p></p>
<p></p>
<button id="btn_unlock" type="submit">解锁账号</button>
@ -34,6 +33,7 @@
<h1>重置</h1>
<span>新密码8至30位长度要求包含大小写字母及数字。</span>
<input type="text" id="username" name="username" readonly placeholder="{{ username }}" value="{{ username }}">
<input type="hidden" id="code" name="code" readonly value="{{ code }}">
<input type="password" id="new_password" name="new_password" placeholder="新密码">
<input type="password" id="ensure_password" name="ensure_password" placeholder="再次确认新密码">
<p></p>