Merge pull request #28 from capricornxl/feature/update_to_layui

应用内授权页面独立
This commit is contained in:
Yadeno 2023-01-16 16:07:24 +08:00 committed by GitHub
commit 2ae4acbffc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 305829 additions and 338 deletions

View File

@ -181,21 +181,6 @@ else
fi fi
fi fi
yum install -y redis
if [[ $? -eq 0 ]]; then
gen_password=$(echo "$(hostname)$(date)" |base64)
sed -i 's@^requirepass.*@@g' /etc/redis.conf
sed -i "/# requirepass foobared/a requirepass ${gen_password}" /etc/redis.conf
systemctl restart redis
sed -i "s@REDIS_PASSWORD.*@REDIS_PASSWORD = r'${gen_password}'@g" ${SHELL_FOLDER}/conf/local_settings.py
echo "安装 redis-server 成功"
echo "Redis Server密码是${gen_password},可在/etc/redis.conf中查到"
else
echo "安装 redis-server 失败,请重新运行本脚本再试"
fi
##修改PIP源为国内 ##修改PIP源为国内
mkdir -p ~/.pip mkdir -p ~/.pip
cat << EOF > ~/.pip/pip.conf cat << EOF > ~/.pip/pip.conf

View File

@ -5,26 +5,33 @@
# ########## AD配置修改为自己的 # ########## AD配置修改为自己的
# AD主机可以是IP或主机域名例如可以是: abc.com或172.16.122.1 # AD主机可以是IP或主机域名例如可以是: abc.com或172.16.122.1
AD_HOST = r'修改成自己的' LDAP_HOST = r'修改成自己的'
# AD域控的DOMAIN例如比如你的域名是abc.com那么这里的AD_DOMAIN就是abc # AD域控的DOMAIN例如比如你的域名是abc.com那么这里的LDAP_DOMAIN就是abc
# NTLM认证必须是domain\username # NTLM认证必须是domain\username
AD_DOMAIN = r'修改成自己的' LDAP_DOMAIN = r'修改成自己的'
# 用于登录AD做用户信息处理的账号需要有修改用户账号密码或信息的权限。 # 用于登录AD做用户信息处理的账号需要有修改用户账号密码或信息的权限。
# AD账号例如pwdadmin # AD账号例如pwdadmin
AD_LOGIN_USER = r'修改成自己的' LDAP_LOGIN_USER = r'修改成自己的'
# 密码 # 密码
AD_LOGIN_USER_PWD = r'修改为自己的' LDAP_LOGIN_USER_PWD = r'修改为自己的'
# BASE DN账号的查找DN路径例如'DC=abc,DC=com'可以指定到OU之下例如'OU=RD,DC=abc,DC=com'。 # BASE DN账号的查找DN路径例如'DC=abc,DC=com'可以指定到OU之下例如'OU=RD,DC=abc,DC=com'。
BASE_DN = r'修改成自己的' BASE_DN = r'修改成自己的'
# ldap的search_filter如果需要修改请保持用户账号部分为 点位符{} (代码中通过占位符引入账号)
# 例如AD的用户账号属性是sAMAccountName那么匹配的账号请配置成sAMAccountName={}
# LDAP中用户账号属性可能是uuid那么匹配的账号请配置成uuid={}
# 默认配置是AD环境的
SEARCH_FILTER = r'(&(objectclass=user)(sAMAccountName={}))'
# 是否启用SSL, # 是否启用SSL,
# 注意AD必须使用SSL才能修改密码这里被坑了N久...,自行部署下AD的证书服务并颁发CA证书重启服务器生效。具体教程百度一下有很多。 # 注意AD中必须使用SSL才能修改密码这里被坑了N久...,自行部署下AD的证书服务并颁发CA证书重启服务器生效。具体教程百度一下有很多。
AD_USE_SSL = True # 如果使用Openldap这里根据实际情况调整
LDAP_USE_SSL = True
# 连接的端口如果启用SSL默认是636否则就是389 # 连接的端口如果启用SSL默认是636否则就是389
AD_CONN_PORT = 636 LDAP_CONN_PORT = 636
# 验证的类型 # 验证的类型
# 钉钉 / 企业微信,自行修改 # 钉钉 / 企业微信,自行修改
@ -53,14 +60,8 @@ WEWORK_AGENT_ID = r'修改为自己的'
# 应用的Secret # 应用的Secret
WEWORK_AGNET_SECRET = r'修改为自己的' WEWORK_AGNET_SECRET = r'修改为自己的'
# Redis配置
# redis的连接地址redis://<Ip/Host>:<Port>
REDIS_LOCATION = r'redis://127.0.0.1:6379'
REDIS_PASSWORD = r'修改为自己的'
# 主页域名钉钉跳转等需要指定域名格式pwd.abc.com。 # 主页域名钉钉跳转等需要指定域名格式pwd.abc.com。
# 如果是自定义安装,请修改成自己的域名 # 如果是自定义安装,请修改成自己的域名
HOME_URL = 'PWD_SELF_SERVICE_DOMAIN' HOME_URL = 'PWD_SELF_SERVICE_DOMAIN'
# 标题 # 平台显示的标题
TITLE = 'Self-Service' TITLE = 'Self-Service'

305668
log/log.log

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@ import resetpwd.views
urlpatterns = { urlpatterns = {
path("favicon.ico", RedirectView.as_view(url='static/img/favicon.ico')), path("favicon.ico", RedirectView.as_view(url='static/img/favicon.ico')),
path('', resetpwd.views.index, name='index'), path('', resetpwd.views.index, name='index'),
path('auth', resetpwd.views.auth, name='auth'),
path('resetPassword', resetpwd.views.reset_password, name='resetPassword'), path('resetPassword', resetpwd.views.reset_password, name='resetPassword'),
path('unlockAccount', resetpwd.views.unlock_account, name='unlockAccount'), path('unlockAccount', resetpwd.views.unlock_account, name='unlockAccount'),
path('messages', resetpwd.views.messages, name='messages'), path('messages', resetpwd.views.messages, name='messages'),

243
readme.md
View File

@ -1,5 +1,5 @@
### 初学Django时碰到的一个需求因为公司中很多员工在修改密码之后有一些关联的客户端或网页中的旧密码没有更新导致密码在尝试多次之后账号被锁为了减少这种让人头疼的重置解锁密码的操蛋工作自己做了一个自助修改小平台。 ### 初学Django时碰到的一个需求因为公司中很多员工在修改密码之后有一些关联的客户端或网页中的旧密码没有更新导致密码在尝试多次之后账号被锁为了减少这种让人头疼的重置解锁密码的操蛋工作自己做了一个自助修改小平台。
### 水平有限,代码写得不好,但是能用,有需要的可以直接拿去用。 ### 代码结构不咋样,但是能用,有需要的可以直接拿去用。
#### 场景说明: #### 场景说明:
因为本公司AD是早期已经在用用户的个人信息不是十分全面例如:用户手机号。 因为本公司AD是早期已经在用用户的个人信息不是十分全面例如:用户手机号。
钉钉是后来才开始使用,钉钉默认是使用手机号登录。 钉钉是后来才开始使用,钉钉默认是使用手机号登录。
@ -14,11 +14,6 @@
如果您的场景不是这样,请按自己的需求修改源代码适配。 如果您的场景不是这样,请按自己的需求修改源代码适配。
### 代码提交到:
```
master
```
### 提示: ### 提示:
``` ```
AD必须使用SSL才能修改密码这里被坑了N久... AD必须使用SSL才能修改密码这里被坑了N久...
@ -35,26 +30,31 @@ AD必须使用SSL才能修改密码这里被坑了N久...
+ 重写了用户账号的格式兼容现在用户账号可以兼容username、DOMAIN\username、username@abc.com这三种格式。 + 重写了用户账号的格式兼容现在用户账号可以兼容username、DOMAIN\username、username@abc.com这三种格式。
+ 优化了整体的代码逻辑,去掉一些冗余重复的代码。 + 优化了整体的代码逻辑,去掉一些冗余重复的代码。
### 2022/12/16 -- 更新: ### 2022/12/16 -- 更新:
+ 修改钉钉、企业微信直接通过企业内部免密登录授权或验证的方式实现用户信息的获取直接通过软件内部工作平台打开废弃扫码方式由于API接口的权限问题一些关键数据已经不再支持通过扫码获取 + 修改钉钉、企业微信直接通过企业内部免密登录授权或验证的方式实现用户信息的获取直接通过软件内部工作平台打开废弃扫码方式由于API接口的权限问题一些关键数据已经不再支持通过扫码获取
其它没变化,只修复了这个问题~~~~ ### 2023/01/15 -- 更新:
+ 兼容PC与移动端的显示使用Layui
+ 修复一些BUG
## 线上环境需要的基础环境: ## 线上环境需要的基础环境:
+ Python 3.8.9 (可自行下载源码包放到项目目录下,使用一键安装) + Python 3.8.9 (可自行下载源码包放到项目目录下,使用一键安装)
+ Nginx + Nginx
+ Uwsgi + Uwsgi
### 钉钉 ### 界面效果
![截图2](screenshot/创建H5微应用06.png)
### 微信 <img alt="截图10" width="500" src="screenshot/QQ截图20230116152954.png">
![截图11](screenshot/微信小应用04.png)
<img alt="截图10" width="500" src="screenshot/212473880-4a59c535-85bb-42d2-a99a-899265c83136.png">
#### 授权或验证成功之后: <img alt="截图10" width="500" src="screenshot/212474222-e1c13e1b-bb6f-4523-b040-24a65055d681.png">
![截图15](screenshot/扫码成功.png)
### 移动端
<img alt="截图10" width="500" src="screenshot/212474177-dd68b0c9-81cc-4eb0-9196-e760784e3f69.jpg">
<img alt="截图10" width="500" src="screenshot/212474293-0cd60898-22c3-4258-ac4c-dfee52a6cf1e.png">
## 钉钉必要条件: ## 钉钉必要条件:
#### 创建企业内部应用 #### 创建企业内部应用
@ -62,6 +62,8 @@ AD必须使用SSL才能修改密码这里被坑了N久...
* 应用需要权限:通讯录只读权限、邮箱等个人信息,范围是全部员工或自行选择 * 应用需要权限:通讯录只读权限、邮箱等个人信息,范围是全部员工或自行选择
* 应用安全域名和IP一定要配置否则无法返回接口数据。 * 应用安全域名和IP一定要配置否则无法返回接口数据。
> **如果想实现进入应用就自动授权跳转重置页面可将回调域名指定向pwd.abc.com/auth**
参考截图配置: 参考截图配置:
![截图3](screenshot/h5微应用.png) ![截图3](screenshot/h5微应用.png)
@ -74,10 +76,12 @@ AD必须使用SSL才能修改密码这里被坑了N久...
> 废弃,已经不再需要,如果之前有配置,可以删除!! > 废弃,已经不再需要,如果之前有配置,可以删除!!
## 企业微信必要条件: ## 企业微信必要条件:
* 创建应用记录下企业的CorpId应用的ID和Secret。 * 创建应用记录下企业的CorpId应用的ID和Secret。
> **如果想实现进入应用就自动授权跳转重置页面可将回调域名指定向pwd.abc.com/auth**
参考截图: 参考截图:
![截图7](screenshot/微扫码13.png) ![截图7](screenshot/微扫码13.png)
@ -100,70 +104,11 @@ AD必须使用SSL才能修改密码这里被坑了N久...
## 使用脚本自动部署: ## 使用脚本自动部署:
使用脚本自动快速部署只适合Centos其它发行版本的Linux请自行修改相关命令。 使用脚本自动快速部署只适合Centos其它发行版本的Linux请自行修改相关命令。
### 把整个项目目录上传到新的服务器上 ### 把整个项目目录上传到新的服务器上
#### 先修改配置文件,按自己实际的配置修改项目配置文件: #### 先修改配置文件,按自己实际的配置修改项目配置文件:
修改conf/local_settings.py中的参数按自己的实际参数修改 修改conf/local_settings.py中的参数按自己的实际参数修改
```` python
# ########## AD配置修改为自己的
# AD主机可以是IP或主机域名例如可以是: abc.com或172.16.122.1
AD_HOST = r'修改成自己的'
# AD域控的DOMAIN名例如abc
AD_DOMAIN = r'修改成自己的'
# 用于登录AD做用户信息处理的账号需要有修改用户账号密码或信息的权限。
# AD账号例如pwdadmin
AD_LOGIN_USER = r'修改成自己的'
# 密码
AD_LOGIN_USER_PWD = r'修改为自己的'
# BASE DN账号的查找DN路径例如'DC=abc,DC=com'可以指定到OU之下例如'OU=RD,DC=abc,DC=com'。
BASE_DN = r'修改成自己的'
# 是否启用SSL,
# 注意AD必须使用SSL才能修改密码这里被坑了N久...,自行部署下AD的证书服务并颁发CA证书重启服务器生效。具体教程百度一下有很多。
AD_USE_SSL = True
# 连接的端口如果启用SSL默认是636否则就是389
AD_CONN_PORT = 636
# 扫码验证的类型
# 钉钉 / 企业微信,自行修改
# 值是DING / WEWORK
AUTH_CODE_TYPE = 'DING'
# ########## 钉钉 《如果不使用钉钉扫码,可不用配置》##########
# 钉钉企业ID <CorpId>,修改为自己的
DING_CORP_ID = '修改为自己的'
# 钉钉企业内部开发内部H5微应用或小程序用于读取企业内部用户信息
DING_AGENT_ID = r'修改为自己的'
DING_APP_KEY = r'修改为自己的'
DING_APP_SECRET = r'修改为自己的'
# 移动应用接入 主要为了实现通过扫码拿到用户的unionid
DING_MO_APP_ID = r'修改为自己的'
DING_MO_APP_SECRET = r'修改为自己的'
# ####### 企业微信《如果不使用企业微信扫码,可不用配置》 ##########
# 企业微信的企业ID
WEWORK_CORP_ID = r'修改为自己的'
# 应用的AgentId
WEWORK_AGENT_ID = r'修改为自己的'
# 应用的Secret
WEWORK_AGNET_SECRET = r'修改为自己的'
# Redis配置
# redis的连接地址redis://<Ip/Host>:<Port>
REDIS_LOCATION = r'redis://127.0.0.1:6379'
REDIS_PASSWORD = r'12345678'
# 主页域名钉钉跳转等需要指定域名格式pwd.abc.com。
# 如果是自定义安装,请修改成自己的域名
HOME_URL = 'PWD_SELF_SERVICE_DOMAIN'
````
### 执行部署脚本 ### 执行部署脚本
```shell ```shell
chmod +x auto-install.sh chmod +x auto-install.sh
@ -180,72 +125,13 @@ chmod +x auto-install.sh
项目目录下的requestment文件里记录了所依赖的相关python模块安装方法 项目目录下的requestment文件里记录了所依赖的相关python模块安装方法
>/usr/local/python3/bin/pip3 install -r requestment >/usr/local/python3/bin/pip3 install -r requestment
>
等待所有模块安装完成之后进行下一步。
等待所有模块安装完成之后进行下一步。
### 按自己实际的配置修改项目配置参数: ### 按自己实际的配置修改项目配置参数:
修改conf/local_settings.py中的参数按自己的实际参数修改 修改conf/local_settings.py中的参数按自己的实际参数修改
```` python ```
# ########## AD配置修改为自己的
# AD主机可以是IP或主机域名例如可以是: abc.com或172.16.122.1
AD_HOST = r'修改成自己的'
# AD域控的DOMAIN名例如abc
AD_DOMAIN = r'修改成自己的'
# 用于登录AD做用户信息处理的账号需要有修改用户账号密码或信息的权限。
# AD账号例如pwdadmin
AD_LOGIN_USER = r'修改成自己的'
# 密码
AD_LOGIN_USER_PWD = r'修改为自己的'
# BASE DN账号的查找DN路径例如'DC=abc,DC=com'可以指定到OU之下例如'OU=RD,DC=abc,DC=com'。
BASE_DN = r'修改成自己的'
# 是否启用SSL,
# 注意AD必须使用SSL才能修改密码这里被坑了N久...,自行部署下AD的证书服务并颁发CA证书重启服务器生效。具体教程百度一下有很多。
AD_USE_SSL = True
# 连接的端口如果启用SSL默认是636否则就是389
AD_CONN_PORT = 636
# 扫码验证的类型
# 钉钉 / 企业微信,自行修改
# 值是DING / WEWORK
AUTH_CODE_TYPE = 'DING'
# ########## 钉钉 《如果不使用钉钉扫码,可不用配置》##########
# 钉钉企业ID <CorpId>,修改为自己的
DING_CORP_ID = '修改为自己的'
# 钉钉企业内部开发内部H5微应用或小程序用于读取企业内部用户信息
DING_AGENT_ID = r'修改为自己的'
DING_APP_KEY = r'修改为自己的'
DING_APP_SECRET = r'修改为自己的'
# 移动应用接入 主要为了实现通过扫码拿到用户的unionid
DING_MO_APP_ID = r'修改为自己的'
DING_MO_APP_SECRET = r'修改为自己的'
# ####### 企业微信《如果不使用企业微信扫码,可不用配置》 ##########
# 企业微信的企业ID
WEWORK_CORP_ID = r'修改为自己的'
# 应用的AgentId
WEWORK_AGENT_ID = r'修改为自己的'
# 应用的Secret
WEWORK_AGNET_SECRET = r'修改为自己的'
# Redis配置
# redis的连接地址redis://<Ip/Host>:<Port>
REDIS_LOCATION = r'redis://127.0.0.1:6379'
REDIS_PASSWORD = r'12345678'
# 主页域名钉钉跳转等需要指定域名格式pwd.abc.com。
# 如果是自定义安装,请修改成自己的域名
HOME_URL = 'PWD_SELF_SERVICE_DOMAIN'
````
安装完依赖后,直接执行 安装完依赖后,直接执行
/usr/local/python3/bin/python3 manager.py runserver x.x.x.x:8000 /usr/local/python3/bin/python3 manager.py runserver x.x.x.x:8000
@ -254,97 +140,22 @@ HOME_URL = 'PWD_SELF_SERVICE_DOMAIN'
## 修改uwsig.ini配置: ## 修改uwsig.ini配置:
IP和路径按自己实际路径修改 IP和路径按自己实际路径修改
````ini
[uwsgi]
http-socket = PWD_SELF_SERVICE_IP:PWD_SELF_SERVICE_PORT
chdir = PWD_SELF_SERVICE_HOME ```
env=DJANGO_SETTINGS_MODULE=pwdselfservice.settings
module = pwdselfservice.wsgi:application
master = true
processes = 4
threads = 4
max-requests = 2000
chmod-socket = 755
vacuum = true
#设置缓冲
post-buffering = 4096
#设置静态文件
static-map = /static=PWD_SELF_SERVICE_HOME/static
#设置日志目录
daemonize = PWD_SELF_SERVICE_HOME/log/uwsgi.log
````
## 通过uwsgiserver启动 ## 通过uwsgiserver启动
其中PWD_SELF_SERVICE_HOME是你自己的服务器当前项目的目录请自行修改
将以下脚本修改完之后,复制到/etc/init.d/,给予执行权限。
uwsgiserver:
```shell ```shell
#!/bin/sh 请自行修改将脚本修改完之后
复制到/etc/init.d/,给予执行权限。
INI="PWD_SELF_SERVICE_HOME/uwsgi.ini" 执行/etc/init.d/uwsigserver start 启动
UWSGI="/usr/share/python-3.6.9/bin/uwsgi" ```
PSID=`ps aux | grep "uwsgi"| grep -v "grep" | wc -l`
if [ ! -n "$1" ]
then
content="Usages: sh uwsgiserver [start|stop|restart]"
echo -e "\033[31m $content \033[0m"
exit 0
fi
if [ $1 = start ]
then
if [ `eval $PSID` -gt 4 ]
then
content="uwsgi is running!"
echo -e "\033[32m $content \033[0m"
exit 0
else
$UWSGI $INI
content="Start uwsgi service [OK]"
echo -e "\033[32m $content \033[0m"
fi
elif [ $1 = stop ];then
if [ `eval $PSID` -gt 4 ];then
killall -9 uwsgi
fi
content="Stop uwsgi service [OK]"
echo -e "\033[32m $content \033[0m"
elif [ $1 = restart ];then
if [ `eval $PSID` -gt 4 ];then
killall -9 uwsgi
fi
$UWSGI --ini $INI
content="Restart uwsgi service [OK]"
echo -e "\033[32m $content \033[0m"
else
content="Usages: sh uwsgiserver [start|stop|restart]"
echo -e "\033[31m $content \033[0m"
fi
````
脚本内的路径按自己实际情况修改
## 自行部署Nginx然后添加Nginx配置 ## 自行部署Nginx然后添加Nginx配置
#### Nginx配置 #### Nginx配置
Nginx Server配置 Nginx Server配置
* proxy_pass的IP地址改成自己的服务器IP * proxy_pass的IP地址改成自己的服务器IP
* 配置可自己写一个vhost或直接加在nginx.conf中 * 配置可自己写一个vhost或直接加在nginx.conf中
```` nginx ``` nginx
server { server {
listen 80; listen 80;
server_name pwd.abc.com; server_name pwd.abc.com;
@ -358,5 +169,5 @@ server {
} }
access_log off; access_log off;
} }
```` ```
- 执行Nginx reload操作重新加载配置 - 执行Nginx reload操作重新加载配置

View File

@ -91,7 +91,7 @@ def ops_account(ad_ops, request, msg_template, home_url, username, new_password)
unlock_status, result = ad_ops.ad_unlock_user_by_account(username) unlock_status, result = ad_ops.ad_unlock_user_by_account(username)
if unlock_status: if unlock_status:
context = {'global_title': TITLE, context = {'global_title': TITLE,
'msg': "密码己修改成功,请妥善保管。你可以点击返回主页或直接关闭此页面!", 'msg': "密码己修改成功,请妥善保管。你可以点击修改密码或直接关闭此页面!",
'button_click': "window.location.href='%s'" % home_url, 'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页" 'button_display': "返回主页"
} }
@ -99,8 +99,8 @@ def ops_account(ad_ops, request, msg_template, home_url, username, new_password)
else: else:
context = {'global_title': TITLE, context = {'global_title': TITLE,
'msg': "密码未修改/重置成功,错误信息:{}".format(result), 'msg': "密码未修改/重置成功,错误信息:{}".format(result),
'button_click': "window.location.href='%s'" % home_url, 'button_click': "window.location.href='%s'" % '/auth',
'button_display': "返回主页" 'button_display': "重新认证授权"
} }
return render(request, msg_template, context) return render(request, msg_template, context)
else: else:
@ -115,14 +115,14 @@ def ops_account(ad_ops, request, msg_template, home_url, username, new_password)
else: else:
context = {'global_title': TITLE, context = {'global_title': TITLE,
'msg': "账号未能解锁,错误信息:{}".format(result), 'msg': "账号未能解锁,错误信息:{}".format(result),
'button_click': "window.location.href='%s'" % home_url, 'button_click': "window.location.href='%s'" % '/auth',
'button_display': "返回主页" 'button_display': "重新认证授权"
} }
return render(request, msg_template, context) return render(request, msg_template, context)
except LDAPException as l_e: except LDAPException as l_e:
context = {'global_title': TITLE, context = {'global_title': TITLE,
'msg': "账号未能解锁,错误信息:{}".format(l_e), 'msg': "账号未能解锁,错误信息:{}".format(l_e),
'button_click': "window.location.href='%s'" % home_url, 'button_click': "window.location.href='%s'" % '/auth',
'button_display': "返回主页" 'button_display': "重新认证授权"
} }
return render(request, msg_template, context) return render(request, msg_template, context)

View File

@ -43,10 +43,7 @@ scan_params = PARAMS()
_ops = scan_params.ops _ops = scan_params.ops
def index(request): def auth(request):
"""
用户自行修改密码/首页
"""
home_url = '%s://%s' % (request.scheme, HOME_URL) home_url = '%s://%s' % (request.scheme, HOME_URL)
corp_id = scan_params.corp_id corp_id = scan_params.corp_id
app_id = scan_params.app_id app_id = scan_params.app_id
@ -57,6 +54,29 @@ def index(request):
app_type = INTEGRATION_APP_TYPE app_type = INTEGRATION_APP_TYPE
global_title = TITLE global_title = TITLE
if request.method == 'GET':
code = request.GET.get('code', None)
username = request.GET.get('username', None)
# 如果满足,说明已经授权免密登录
if username and code and request.session.get(username) == code:
context = {'global_title': TITLE,
'username': username,
'code': code,
}
return render(request, 'reset_password.html', context)
return render(request, 'auth.html', locals())
else:
logger.error('[异常] 请求方法:%s,请求路径%s' % (request.method, request.path))
def index(request):
"""
用户自行修改密码/首页
"""
home_url = '%s://%s' % (request.scheme, HOME_URL)
scan_app = scan_params.AUTH_APP
global_title = TITLE
if request.method == 'GET': if request.method == 'GET':
return render(request, 'index.html', locals()) return render(request, 'index.html', locals())
else: else:
@ -75,8 +95,8 @@ def index(request):
logger.error('[异常] 请求方法:%s,请求路径:%s,错误信息:%s' % (request.method, request.path, _msg)) logger.error('[异常] 请求方法:%s,请求路径:%s,错误信息:%s' % (request.method, request.path, _msg))
context = {'global_title': TITLE, context = {'global_title': TITLE,
'msg': _msg, 'msg': _msg,
'button_click': "window.location.href='%s'" % home_url, 'button_click': "window.location.href='%s'" % '/auth',
'button_display': "返回主页" 'button_display': "重新认证授权"
} }
return render(request, msg_template, context) return render(request, msg_template, context)
# 格式化用户名 # 格式化用户名
@ -84,8 +104,8 @@ def index(request):
if _ is False: if _ is False:
context = {'global_title': TITLE, context = {'global_title': TITLE,
'msg': username, 'msg': username,
'button_click': "window.location.href='%s'" % home_url, 'button_click': "window.location.href='%s'" % '/auth',
'button_display': "返回主页" 'button_display': "重新认证授权"
} }
return render(request, msg_template, context) return render(request, msg_template, context)
# 检测账号状态 # 检测账号状态
@ -93,16 +113,16 @@ def index(request):
if not auth_status: if not auth_status:
context = {'global_title': TITLE, context = {'global_title': TITLE,
'msg': str(auth_result), 'msg': str(auth_result),
'button_click': "window.location.href='%s'" % home_url, 'button_click': "window.location.href='%s'" % '/auth',
'button_display': "返回主页" 'button_display': "重新认证授权"
} }
return render(request, msg_template, context) return render(request, msg_template, context)
return ops_account(AdOps(), request, msg_template, home_url, username, new_password) return ops_account(AdOps(), request, msg_template, home_url, username, new_password)
else: else:
context = {'global_title': TITLE, context = {'global_title': TITLE,
'msg': "请从主页进行修改密码操作或扫码验证用户信息", 'msg': "不被接受的认证信息,请重新尝试认证授权",
'button_click': "window.location.href='%s'" % home_url, 'button_click': "window.location.href='%s'" % '/auth',
'button_display': "返回主页" 'button_display': "重新认证授权"
} }
return render(request, msg_template, context) return render(request, msg_template, context)
@ -130,9 +150,9 @@ def reset_password(request):
else: else:
logger.error('[异常] 请求方法:%s,请求路径:%s未能拿到Code。' % (request.method, request.path)) logger.error('[异常] 请求方法:%s,请求路径:%s未能拿到Code。' % (request.method, request.path))
context = {'global_title': TITLE, context = {'global_title': TITLE,
'msg': "错误,临时授权码己失效,请从主页重新开始登录授权..", 'msg': "错误,临时授权码己失效,请尝试重新认证授权..",
'button_click': "window.location.href='%s'" % home_url, 'button_click': "window.location.href='%s'" % '/auth',
'button_display': "返回主页" 'button_display': "重新认证授权"
} }
return render(request, msg_template, context) return render(request, msg_template, context)
try: try:
@ -163,8 +183,8 @@ def reset_password(request):
if not _: if not _:
context = {'global_title': TITLE, context = {'global_title': TITLE,
'msg': email, 'msg': email,
'button_click': "window.location.href='%s'" % home_url, 'button_click': "window.location.href='%s'" % '/auth',
'button_display': "返回主页" 'button_display': "重新认证授权"
} }
return render(request, msg_template, context) return render(request, msg_template, context)
@ -172,8 +192,8 @@ def reset_password(request):
if _ is False: if _ is False:
context = {'global_title': TITLE, context = {'global_title': TITLE,
'msg': username, 'msg': username,
'button_click': "window.location.href='%s'" % home_url, 'button_click': "window.location.href='%s'" % '/auth',
'button_display': "返回主页" 'button_display': "重新认证授权"
} }
return render(request, msg_template, context) return render(request, msg_template, context)
@ -189,8 +209,8 @@ def reset_password(request):
context = {'global_title': TITLE, context = {'global_title': TITLE,
'msg': "{},您好,企业{}中未能找到您账号的邮箱配置请联系HR完善信息。".format( 'msg': "{},您好,企业{}中未能找到您账号的邮箱配置请联系HR完善信息。".format(
user_info.get('name'), scan_params.AUTH_APP), user_info.get('name'), scan_params.AUTH_APP),
'button_click': "window.location.href='%s'" % home_url, 'button_click': "window.location.href='%s'" % '/auth',
'button_display': "返回主页" 'button_display': "重新认证授权"
} }
return render(request, msg_template, context) return render(request, msg_template, context)
@ -215,9 +235,9 @@ def reset_password(request):
del request.session[username] del request.session[username]
else: else:
context = {'global_title': TITLE, context = {'global_title': TITLE,
'msg': "认证已经失效,请从主页重新进行操作", 'msg': "认证已经失效,可尝试从重新认证授权",
'button_click': "window.location.href='%s'" % home_url, 'button_click': "window.location.href='%s'" % '/auth',
'button_display': "返回主页" 'button_display': "重新认证授权"
} }
return render(request, msg_template, context) return render(request, msg_template, context)
@ -242,8 +262,8 @@ def unlock_account(request):
else: else:
context = {'global_title': TITLE, context = {'global_title': TITLE,
'msg': "{},您好,当前会话可能已经过期,请再试一次吧。".format(username), 'msg': "{},您好,当前会话可能已经过期,请再试一次吧。".format(username),
'button_click': "window.location.href='%s'" % home_url, 'button_click': "window.location.href='%s'" % '/auth',
'button_display': "返回主页" 'button_display': "重新认证授权"
} }
return render(request, msg_template, context) return render(request, msg_template, context)
@ -265,9 +285,9 @@ def unlock_account(request):
del request.session[username] del request.session[username]
else: else:
context = {'global_title': TITLE, context = {'global_title': TITLE,
'msg': "认证已经失效,请从主页重新进行操作", 'msg': "认证已经失效,请尝试从重新进行认证授权",
'button_click': "window.location.href='%s'" % home_url, 'button_click': "window.location.href='%s'" % '/auth',
'button_display': "返回主页" 'button_display': "重新认证授权"
} }
return render(request, msg_template, context) return render(request, msg_template, context)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 868 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 219 KiB

41
templates/auth.html Normal file
View File

@ -0,0 +1,41 @@
{% extends 'base.html' %}
{% load static %}
{% block headerjs %}<script type="text/javascript" src="{% static 'js/dingtalk.open.js' %}"></script>{% endblock %}
{% block paneltitle %}请稍后,授权信息认证中{% endblock %}
{% block middleblock %}
{% endblock %}
{% block footerjs %}
<script src="{% static 'layui/layui.js' %}"></script>
<script>
layui.use(['form', 'jquery', 'layer'], function () {
let layer = layui.layer,
$ = layui.jquery;
let re_url= ""
let index_load = layer.load(1, {shade: 0.4});
{% if app_type == 'DING' %}
dd.ready(() => {
dd.runtime.permission.requestAuthCode({corpId: '{{ corp_id }}'}).then((result) => {
re_url = '/resetPassword?code=' + result.code
window.parent.parent.location.href=re_url;
}).catch(err => {
layer.close(index_load)
layer.open({
title : '出错啦!'
,content: err
,btn: '关闭'
,btnAlign: 'c'
,yes: function(){
layer.closeAll();
}
});
});
});
{% elif app_type == 'WEWORK' %}
$(function () {
re_url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid={{ app_id }}&agentid={{ agent_id }}&redirect_uri={{ redirect_url }}&response_type=code&scope=snsapi_privateinfo&state=#wechat_redirect"
window.parent.parent.location.href=re_url;
})
{% endif %}
});
</script>
{% endblock %}

View File

@ -34,7 +34,7 @@
</div> </div>
<div class="layui-form-item a-middle-text"> <div class="layui-form-item a-middle-text">
<span class="layui-breadcrumb"> <span class="layui-breadcrumb">
<a class="layui-text" id="redirect_url" href=""><i class="layui-icon layui-icon-refresh-3"></i> 重置/解锁账号</a> <a class="layui-text" id="redirect_url" href="/auth"><i class="layui-icon layui-icon-refresh-3"></i> 重置/解锁账号</a>
</span> </span>
</div> </div>
</form> </form>
@ -53,39 +53,7 @@
<script> <script>
layui.use(['form', 'jquery', 'layer'], function () { layui.use(['form', 'jquery', 'layer'], function () {
let form = layui.form, let form = layui.form,
layer = layui.layer,
$ = layui.jquery; $ = layui.jquery;
if ('{{ app_type }}' === 'DING') {
let re_url= ""
window.onload = function () {
if (dd.env.platform !== 'notInDingTalk') {
dd.ready(() => {
dd.runtime.permission.requestAuthCode({corpId: '{{ corp_id }}'}).then((result) => {
re_url = '/resetPassword?code=' + result.code
}).catch(err => {
console.log(err);
}).finally(() => {
document.getElementById("redirect_url").setAttribute("href", re_url)
})
});
} else {
layer.open({
title : '出错啦!'
,content: '请在钉钉中打开本页面~~'
,btn: '关闭'
,btnAlign: 'c'
,yes: function(){
layer.closeAll();
}
});
}
}
} else if ('{{ app_type }}' === 'WEWORK') {
let re_url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid={{ app_id }}&agentid={{ agent_id }}&redirect_uri={{ redirect_url }}&response_type=code&scope=snsapi_privateinfo&state=#wechat_redirect"
window.onload = function () {
document.getElementById("redirect_url").setAttribute("href", re_url)
}
}
form.verify({ form.verify({
pass: [ pass: [
/^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9]).{8,30}$/, /^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9]).{8,30}$/,
@ -94,14 +62,11 @@ layui.use(['form', 'jquery', 'layer'], function () {
repass: function (value,item) { repass: function (value,item) {
if ($('#ensure_password').val() !== $('#new_password').val()) { if ($('#ensure_password').val() !== $('#new_password').val()) {
return '两次输入密码不一致!'; return '两次输入密码不一致!';
} }},
},
newpass: function (value,item) { newpass: function (value,item) {
if ($('#old_password').val() === $('#password').val()) { if ($('#old_password').val() === $('#password').val()) {
return '新旧密码不能重复使用,请修正!'; return '新旧密码不能重复使用,请修正!';
} }}});
}
});
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -28,7 +28,7 @@
</div> </div>
<div class="layui-form-item a-middle-text"> <div class="layui-form-item a-middle-text">
<span class="layui-breadcrumb"> <span class="layui-breadcrumb">
<a class="layui-text" href="/"><i class="layui-icon layui-icon-prev"></i> 返回主页</a> <a class="layui-text" href="/"><i class="layui-icon layui-icon-prev"></i> 修改密码</a>
<a class="layui-text" id="redirect_url" href="/unlockAccount?code={{ code }}&username={{ username }}"><i class="layui-icon layui-icon-password"></i> 解锁账号</a> <a class="layui-text" id="redirect_url" href="/unlockAccount?code={{ code }}&username={{ username }}"><i class="layui-icon layui-icon-password"></i> 解锁账号</a>
</span> </span>
</div> </div>

View File

@ -16,7 +16,7 @@
</div> </div>
<div class="layui-form-item a-middle-text"> <div class="layui-form-item a-middle-text">
<span class="layui-breadcrumb"> <span class="layui-breadcrumb">
<a class="layui-text" href="/"><i class="layui-icon layui-icon-prev"></i> 返回主页</a> <a class="layui-text" href="/"><i class="layui-icon layui-icon-prev"></i> 修改密码</a>
<a class="layui-text" id="redirect_url" href="/resetPassword?code={{ code }}&username={{ username }}"><i class="layui-icon layui-icon-refresh-1"></i> 重置密码</a> <a class="layui-text" id="redirect_url" href="/resetPassword?code={{ code }}&username={{ username }}"><i class="layui-icon layui-icon-refresh-1"></i> 重置密码</a>
</span> </span>
</div> </div>

View File

@ -1,4 +1,3 @@
import ldap3
from ldap3 import * from ldap3 import *
from ldap3.core.exceptions import LDAPInvalidCredentialsResult, LDAPOperationResult, LDAPExceptionError, LDAPException, \ from ldap3.core.exceptions import LDAPInvalidCredentialsResult, LDAPOperationResult, LDAPExceptionError, LDAPException, \
LDAPSocketOpenError LDAPSocketOpenError
@ -38,8 +37,8 @@ unicodePwd 属性的语法为 octet-string;但是,目录服务预期八进制
class AdOps(object): class AdOps(object):
def __init__(self, auto_bind=True, use_ssl=AD_USE_SSL, port=AD_CONN_PORT, domain=AD_DOMAIN, user=AD_LOGIN_USER, def __init__(self, auto_bind=True, use_ssl=LDAP_USE_SSL, port=LDAP_CONN_PORT, domain=LDAP_DOMAIN, user=LDAP_LOGIN_USER,
password=AD_LOGIN_USER_PWD, password=LDAP_LOGIN_USER_PWD,
authentication=NTLM): authentication=NTLM):
""" """
AD连接器 authentication [SIMPLE, ANONYMOUS, SASL, NTLM] AD连接器 authentication [SIMPLE, ANONYMOUS, SASL, NTLM]
@ -60,7 +59,7 @@ class AdOps(object):
def __server(self): def __server(self):
if self.server is None: if self.server is None:
try: try:
self.server = Server(host='%s' % AD_HOST, connect_timeout=1, use_ssl=self.use_ssl, port=self.port, self.server = Server(host='%s' % LDAP_HOST, connect_timeout=1, use_ssl=self.use_ssl, port=self.port,
get_info=ALL) get_info=ALL)
except LDAPInvalidCredentialsResult as lic_e: except LDAPInvalidCredentialsResult as lic_e:
return False, LDAPOperationResult("LDAPInvalidCredentialsResult: " + str(lic_e.message)) return False, LDAPOperationResult("LDAPInvalidCredentialsResult: " + str(lic_e.message))
@ -118,7 +117,7 @@ class AdOps(object):
# return False, '用户登陆前必须修改密码!' # return False, '用户登陆前必须修改密码!'
# 设置该账号下次登陆不需要更改密码,再验证一次 # 设置该账号下次登陆不需要更改密码,再验证一次
self.__conn() self.__conn()
self.conn.search(search_base=BASE_DN, search_filter='(sAMAccountName={}))'.format(username), self.conn.search(search_base=BASE_DN, search_filter=SEARCH_FILTER.format(username),
attributes=['pwdLastSet']) attributes=['pwdLastSet'])
self.conn.modify(self.conn.entries[0].entry_dn, {'pwdLastSet': [(MODIFY_REPLACE, ['-1'])]}) self.conn.modify(self.conn.entries[0].entry_dn, {'pwdLastSet': [(MODIFY_REPLACE, ['-1'])]})
return True, self.ad_auth_user(username, password) return True, self.ad_auth_user(username, password)
@ -135,7 +134,7 @@ class AdOps(object):
""" """
try: try:
self.__conn() self.__conn()
return True, self.conn.search(BASE_DN, '(&(objectclass=user)(sAMAccountName={}))'.format(username), return True, self.conn.search(BASE_DN, SEARCH_FILTER.format(username),
attributes=['sAMAccountName']) attributes=['sAMAccountName'])
except Exception as e: except Exception as e:
return False, "AdOps Exception: {}".format(e) return False, "AdOps Exception: {}".format(e)
@ -148,7 +147,7 @@ class AdOps(object):
""" """
try: try:
self.__conn() self.__conn()
self.conn.search(BASE_DN, '(&(objectclass=user)(sAMAccountName={}))'.format(username), attributes=['name']) self.conn.search(BASE_DN, SEARCH_FILTER.format(username), attributes=['name'])
return True, self.conn.entries[0]['name'] return True, self.conn.entries[0]['name']
except Exception as e: except Exception as e:
return False, "AdOps Exception: {}".format(e) return False, "AdOps Exception: {}".format(e)
@ -161,7 +160,7 @@ class AdOps(object):
""" """
try: try:
self.__conn() self.__conn()
self.conn.search(BASE_DN, '(&(objectclass=user)(sAMAccountName={}))'.format(username), self.conn.search(BASE_DN, SEARCH_FILTER.format(username),
attributes=['distinguishedName']) attributes=['distinguishedName'])
return True, str(self.conn.entries[0]['distinguishedName']) return True, str(self.conn.entries[0]['distinguishedName'])
except Exception as e: except Exception as e:
@ -175,7 +174,7 @@ class AdOps(object):
""" """
try: try:
self.__conn() self.__conn()
self.conn.search(BASE_DN, '(&(objectclass=user)(sAMAccountName={}))'.format(username), self.conn.search(BASE_DN, SEARCH_FILTER.format(username),
attributes=['userAccountControl']) attributes=['userAccountControl'])
return True, self.conn.entries[0]['userAccountControl'] return True, self.conn.entries[0]['userAccountControl']
except Exception as e: except Exception as e:
@ -241,7 +240,7 @@ class AdOps(object):
""" """
try: try:
self.__conn() self.__conn()
self.conn.search(BASE_DN, '(&(objectclass=user)(sAMAccountName={}))'.format(username), self.conn.search(BASE_DN, SEARCH_FILTER.format(username),
attributes=['lockoutTime']) attributes=['lockoutTime'])
locked_status = self.conn.entries[0]['lockoutTime'] locked_status = self.conn.entries[0]['lockoutTime']
if '1601-01-01' in str(locked_status): if '1601-01-01' in str(locked_status):

View File

@ -163,8 +163,8 @@ class WeWorkOps(AbstractApi):
if not _status: if not _status:
context = {'global_title': TITLE, context = {'global_title': TITLE,
'msg': '获取userid失败错误信息{}'.format(ticket_data), 'msg': '获取userid失败错误信息{}'.format(ticket_data),
'button_click': "window.location.href='%s'" % home_url, 'button_click': "window.location.href='%s'" % '/auth',
'button_display': "返回主页" 'button_display': "重新认证授权"
} }
return False, context, ticket_data return False, context, ticket_data
@ -173,7 +173,7 @@ class WeWorkOps(AbstractApi):
context = {'global_title': TITLE, context = {'global_title': TITLE,
'msg': '获取用户Ticket失败当前扫码用户[{}]可能未加入企业!'.format(user_id), 'msg': '获取用户Ticket失败当前扫码用户[{}]可能未加入企业!'.format(user_id),
'button_click': "window.location.href='%s'" % home_url, 'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页" 'button_display': "返回修改密码"
} }
return False, context, user_id return False, context, user_id
@ -183,8 +183,8 @@ class WeWorkOps(AbstractApi):
if not detail_status: if not detail_status:
context = {'global_title': TITLE, context = {'global_title': TITLE,
'msg': '获取用户信息失败,错误信息:{}'.format(user_id), 'msg': '获取用户信息失败,错误信息:{}'.format(user_id),
'button_click': "window.location.href='%s'" % home_url, 'button_click': "window.location.href='%s'" % '/auth',
'button_display': "返回主页" 'button_display': "重新认证授权"
} }
return False, context return False, context
return True, user_id, user_info return True, user_id, user_info