### 本次升级、修复,请使用最新版:
+ 升级Python版本为3.8 + 升级Django到3.2 + 修复用户名中使用\被转义的问题 + 重写了dingding模块,因为dingding开发者平台接口鉴权的一些变动,之前的一些接口不能再使用,本次重写。 + 重写了ad模块,修改账号的一些判断逻辑。 + 重写了用户账号的格式兼容,现在用户账号可以兼容:username、DOMAIN\username、username@abc.com这三种格式。 + 优化了整体的代码逻辑,去掉一些冗余重复的代码。
This commit is contained in:
parent
d8ac7552a6
commit
bc04829070
35
Dockerfile
35
Dockerfile
|
@ -1,35 +0,0 @@
|
|||
# 编译代码
|
||||
FROM python:3.6.13-slim as stage-build
|
||||
MAINTAINER Xiangle0109@outlook.com
|
||||
ARG VERSION
|
||||
ENV VERSION=1.0
|
||||
|
||||
WORKDIR /opt/password-self-service
|
||||
ADD ./ad-password.tar.gz ./
|
||||
|
||||
ARG PIP_MIRROR=https://pypi.douban.com/simple
|
||||
ENV PIP_MIRROR=$PIP_MIRROR
|
||||
|
||||
WORKDIR /opt/password-self-service
|
||||
|
||||
|
||||
RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list \
|
||||
&& sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list \
|
||||
&& apt update \
|
||||
&& grep -v '^#' ./docker-src/deb_requirement | xargs apt -y install \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& localedef -c -f UTF-8 -i zh_CN zh_CN.UTF-8 \
|
||||
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
|
||||
|
||||
|
||||
RUN pip install --upgrade pip==20.2.4 setuptools==49.6.0 wheel==0.34.2 -i ${PIP_MIRROR} \
|
||||
&& pip config set global.index-url ${PIP_MIRROR} \
|
||||
&& pip install --no-cache-dir -r ./docker-src/requirement
|
||||
|
||||
VOLUME /opt/password-self-service/log
|
||||
|
||||
ENV LANG=zh_CN.UTF-8
|
||||
|
||||
EXPOSE 8070
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
|
@ -138,30 +138,31 @@ fi
|
|||
|
||||
##install python3
|
||||
##如果之前用此脚本安装过python3,后续就不会再次安装。
|
||||
if [[ -f "/usr/share/python-3.6.9/bin/python3" ]]
|
||||
python_ver='3.8.9'
|
||||
if [[ -f "/usr/share/python-${python_ver}/bin/python3" ]]
|
||||
then
|
||||
echo "己发现Python3,将不会安装。"
|
||||
else
|
||||
if [[ -f "Python-3.6.9.tar.xz" ]]
|
||||
if [[ -f "Python-${python_ver}.tar.xz" ]]
|
||||
then
|
||||
echo "将安装Python3.6.9"
|
||||
tar xf Python-3.6.9.tar.xz
|
||||
cd Python-3.6.9
|
||||
sudo ./configure --prefix=/usr/share/python-3.6.9 && make && make install
|
||||
echo "将安装Python${python_ver}"
|
||||
tar xf Python-${python_ver}.tar.xz
|
||||
cd Python-${python_ver}
|
||||
sudo ./configure --prefix=/usr/share/python-${python_ver} && make && make install
|
||||
else
|
||||
echo "脚本目录下没有发现Python3.6.9.tar.xz,将会下载python 3.6.9"
|
||||
sudo wget https://www.python.org/ftp/python/3.6.9/Python-3.6.9.tar.xz
|
||||
tar xf Python-3.6.9.tar.xz
|
||||
cd Python-3.6.9
|
||||
sudo ./configure --prefix=/usr/share/python-3.6.9 && make && make install
|
||||
echo "脚本目录下没有发现Python${python_ver}.tar.xz,将会下载python ${python_ver}"
|
||||
sudo wget https://www.python.org/ftp/python/${python_ver}/Python-${python_ver}.tar.xz
|
||||
tar xf Python-${python_ver}.tar.xz
|
||||
cd Python-${python_ver}
|
||||
sudo ./configure --prefix=/usr/share/python-${python_ver} && make && make install
|
||||
fi
|
||||
|
||||
if [[ $? -eq 0 ]]
|
||||
then
|
||||
echo "创建python3和pip3的软件链接"
|
||||
cd ${SHELL_FOLDER}
|
||||
sudo ln -svf /usr/share/python-3.6.9/bin/python3 /usr/bin/python3
|
||||
sudo ln -svf /usr/share/python-3.6.9/bin/pip3 /usr/bin/pip3
|
||||
sudo ln -svf /usr/share/python-${python_ver}/bin/python3 /usr/bin/python3
|
||||
sudo ln -svf /usr/share/python-${python_ver}/bin/pip3 /usr/bin/pip3
|
||||
echo "======================================================================="
|
||||
echo "Python3 安装成功!"
|
||||
echo "======================================================================="
|
||||
|
|
|
@ -1,255 +0,0 @@
|
|||
#!/bin/bash
|
||||
echo -e "此脚本为Docker快速部署脚本"
|
||||
|
||||
##Check IP
|
||||
function check_ip() {
|
||||
local IP=$1
|
||||
VALID_CHECK=$(echo $IP|awk -F. '$1<=255&&$2<=255&&$3<=255&&$4<=255{print "yes"}')
|
||||
if echo $IP|grep -E "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$" >/dev/null; then
|
||||
if [[ $VALID_CHECK == "yes" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
##Check domain
|
||||
function check_domain() {
|
||||
local DOMAIN=$1
|
||||
if echo $DOMAIN |grep -P "(?=^.{4,253}$)(^(?:[a-zA-Z0-9](?:(?:[a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$)" >/dev/null; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
##Check Port
|
||||
function check_port() {
|
||||
local PORT=$1
|
||||
VALID_CHECK=$(echo $PORT|awk '$1<=65535&&$1>=1{print "yes"}')
|
||||
if echo $PORT |grep -E "^([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]{1}|6553[0-5])$" >/dev/null; then
|
||||
if [[ $VALID_CHECK == "yes" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
while :; do echo
|
||||
echo "请确认你此台服务器是全新干净的,以防此脚本相关操作对正在运行的服务造成影响(不可逆)。"
|
||||
read -p "请确认是否继续执行,输入 [y/n]: " ensure_yn
|
||||
if [[ ! "${ensure_yn}" =~ ^[y,n]$ ]]; then
|
||||
echo "输入有误,请输入 y 或 n"
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "${ensure_yn}" = n ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "======================================================================="
|
||||
while :; do echo
|
||||
read -p "请输入密码自助平台使用的本机IP: " PWD_SELF_SERVICE_IP
|
||||
check_ip ${PWD_SELF_SERVICE_IP}
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "---输入的IP地址格式有误,请重新输入。"
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
echo "======================================================================="
|
||||
while :; do echo
|
||||
read -p "请输入密码自助平台使用的端口: " PWD_SELF_SERVICE_PORT
|
||||
check_port ${PWD_SELF_SERVICE_PORT}
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "---输入的端口有误,请重新输入。"
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
echo "======================================================================="
|
||||
while :; do echo
|
||||
read -p "请输入密码自助平台使用域名,例如:pwd.abc.com: " PWD_SELF_SERVICE_DOMAIN
|
||||
check_domain ${PWD_SELF_SERVICE_DOMAIN}
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "---输入的域名格式有误,请重新输入。"
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
##当前脚本的绝对路径
|
||||
SHELL_FOLDER=$(dirname $(readlink -f "$0"))
|
||||
|
||||
echo "关闭SELINUX"
|
||||
sudo setenforce 0
|
||||
sudo sed -i 's@SELINUX=*@SELINUX=disabled@g' /etc/selinux/config
|
||||
echo "DONE....."
|
||||
echo "关闭防火墙"
|
||||
sudo systemctl disable firewalld
|
||||
sudo systemctl stop firewalld
|
||||
echo "DONE....."
|
||||
|
||||
echo "初始化编译环境----------"
|
||||
sudo yum install gcc patch libffi-devel python-devel zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel wget psmisc -y
|
||||
echo "======================================================================="
|
||||
echo "初始化编译环境完成"
|
||||
echo "======================================================================="
|
||||
|
||||
##Quick install nginx
|
||||
echo "======================================================================="
|
||||
echo "安装 Nginx"
|
||||
sudo cat << EOF > /etc/yum.repos.d/nginx.repo
|
||||
[nginx-stable]
|
||||
name=nginx stable repo
|
||||
baseurl=http://nginx.org/packages/centos/7/\$basearch/
|
||||
gpgcheck=1
|
||||
enabled=1
|
||||
gpgkey=https://nginx.org/keys/nginx_signing.key
|
||||
module_hotfixes=true
|
||||
EOF
|
||||
|
||||
sudo yum makecache fast
|
||||
sudo yum install nginx -y
|
||||
|
||||
if [[ $? -eq 0 ]]
|
||||
then
|
||||
sudo systemctl enable nginx
|
||||
sudo systemctl start nginx
|
||||
echo "======================================================================="
|
||||
echo "nginx 安装成功!"
|
||||
echo "======================================================================="
|
||||
else
|
||||
echo "======================================================================="
|
||||
echo "nginx 安装失败!"
|
||||
echo "======================================================================="
|
||||
exit 1
|
||||
fi
|
||||
|
||||
##install python3
|
||||
##如果之前用此脚本安装过python3,后续就不会再次安装。
|
||||
if [[ -f "/usr/share/python-3.6.9/bin/python3" ]]
|
||||
then
|
||||
echo "己发现Python3,将不会安装。"
|
||||
else
|
||||
if [[ -f "Python-3.6.9.tar.xz" ]]
|
||||
then
|
||||
echo "将安装Python3.6.9"
|
||||
tar xf Python-3.6.9.tar.xz
|
||||
cd Python-3.6.9
|
||||
sudo ./configure --prefix=/usr/share/python-3.6.9 && make && make install
|
||||
else
|
||||
echo "脚本目录下没有发现Python3.6.9.tar.xz,将会下载python 3.6.9"
|
||||
sudo wget https://www.python.org/ftp/python/3.6.9/Python-3.6.9.tar.xz
|
||||
tar xf Python-3.6.9.tar.xz
|
||||
cd Python-3.6.9
|
||||
sudo ./configure --prefix=/usr/share/python-3.6.9 && make && make install
|
||||
fi
|
||||
|
||||
if [[ $? -eq 0 ]]
|
||||
then
|
||||
echo "创建python3和pip3的软件链接"
|
||||
cd ${SHELL_FOLDER}
|
||||
sudo ln -svf /usr/share/python-3.6.9/bin/python3 /usr/bin/python3
|
||||
sudo ln -svf /usr/share/python-3.6.9/bin/pip3 /usr/bin/pip3
|
||||
echo "======================================================================="
|
||||
echo "Python3 安装成功!"
|
||||
echo "======================================================================="
|
||||
else
|
||||
echo "======================================================================="
|
||||
echo "Python3 安装失败!"
|
||||
echo "======================================================================="
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
##修改PIP源为国内
|
||||
mkdir -p ~/.pip
|
||||
cat << EOF > ~/.pip/pip.conf
|
||||
[global]
|
||||
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
[install]
|
||||
trusted-host=pypi.tuna.tsinghua.edu.cn
|
||||
EOF
|
||||
|
||||
cd ${SHELL_FOLDER}
|
||||
echo "====升级pip================"
|
||||
/usr/bin/pip3 install --upgrade pip
|
||||
/usr/bin/pip3 install -r requestment
|
||||
|
||||
if [[ $? -eq 0 ]]
|
||||
then
|
||||
echo "======================================================================="
|
||||
echo "Pip3 requestment 安装成功!"
|
||||
echo "======================================================================="
|
||||
else
|
||||
echo "======================================================================="
|
||||
echo "Pip3 requestment 安装失败!"
|
||||
echo "======================================================================="
|
||||
exit 1
|
||||
fi
|
||||
|
||||
##处理配置文件
|
||||
echo "======================================================================="
|
||||
echo "处理uwsgi.ini配置文件"
|
||||
sed -i "s@PWD_SELF_SERVICE_HOME@${SHELL_FOLDER}@g" ${SHELL_FOLDER}/uwsgi.ini
|
||||
sed -i "s@PWD_SELF_SERVICE_IP@${PWD_SELF_SERVICE_IP}@g" ${SHELL_FOLDER}/uwsgi.ini
|
||||
sed -i "s@PWD_SELF_SERVICE_PORT@${PWD_SELF_SERVICE_PORT}@g" ${SHELL_FOLDER}/uwsgi.ini
|
||||
echo "处理uwsgi.ini配置文件完成"
|
||||
echo
|
||||
echo "处理uwsgiserver启动脚本"
|
||||
sed -i "s@PWD_SELF_SERVICE_HOME@${SHELL_FOLDER}@g" ${SHELL_FOLDER}/uwsgiserver
|
||||
alias cp='cp'
|
||||
cp -f ${SHELL_FOLDER}/uwsgiserver /etc/init.d/uwsgiserver
|
||||
chmod +x /etc/init.d/uwsgiserver
|
||||
chkconfig uwsgiserver on
|
||||
echo "处理uwsgiserver启动脚本完成"
|
||||
echo
|
||||
|
||||
sed -i "s@PWD_SELF_SERVICE_DOMAIN@${PWD_SELF_SERVICE_DOMAIN}@g" ${SHELL_FOLDER}/pwdselfservice/local_settings.py
|
||||
|
||||
##Nginx vhost配置
|
||||
cat << EOF > /etc/nginx/conf.d/pwdselfservice.conf
|
||||
server {
|
||||
listen 80;
|
||||
server_name ${PWD_SELF_SERVICE_DOMAIN} ${PWD_SELF_SERVICE_IP};
|
||||
|
||||
location / {
|
||||
proxy_pass http://${PWD_SELF_SERVICE_IP}:${PWD_SELF_SERVICE_PORT};
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
}
|
||||
access_log off;
|
||||
}
|
||||
EOF
|
||||
rm -f /etc/nginx/conf.d/default.conf
|
||||
systemctl restart nginx
|
||||
|
||||
echo
|
||||
echo "======================================================================="
|
||||
echo
|
||||
echo "密码自助服务平台的访问地址是:http://${PWD_SELF_SERVICE_DOMAIN}或http://${PWD_SELF_SERVICE_IP}"
|
||||
echo "请确保以上域名能正常解析,否则使用域名无法访问。"
|
||||
echo
|
||||
echo "Uwsgi启动:/etc/init.d/uwsgi start"
|
||||
echo "Uwsgi停止:/etc/init.d/uwsgi stop"
|
||||
echo "Uwsgi重启:/etc/init.d/uwsgi restart"
|
||||
echo
|
||||
echo
|
||||
echo "文件${SHELL_FOLDER}/pwdselfservice/local_setting.py中必要参数需要你自行修改"
|
||||
echo "此文件中有AD和钉钉的一些参数,按自己企业的修改"
|
||||
echo
|
||||
echo "======================================================================="
|
|
@ -1,23 +0,0 @@
|
|||
# common
|
||||
gcc
|
||||
cmake
|
||||
curl
|
||||
wget
|
||||
vim
|
||||
locales
|
||||
iputils-ping
|
||||
python3
|
||||
nginx
|
||||
|
||||
# mysql-client
|
||||
default-mysql-client
|
||||
default-libmysqlclient-dev
|
||||
|
||||
openssl
|
||||
libssl-dev
|
||||
libldap2-dev
|
||||
libsasl2-dev
|
||||
libkrb5-dev
|
||||
sqlite
|
||||
|
||||
sshpass
|
|
@ -1,28 +0,0 @@
|
|||
[uwsgi]
|
||||
http-socket = PWD_SELF_SERVICE_IP:PWD_SELF_SERVICE_PORT
|
||||
|
||||
chdir = PWD_SELF_SERVICE_HOME
|
||||
|
||||
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
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Startup script for the uwsgi server
|
||||
# chkconfig: - 85 15
|
||||
# description: uwsgi server is Web Server
|
||||
# HTML files and CGI.
|
||||
# processname: uwsgiserver
|
||||
|
||||
INI="/opt/password-self-service/uwsgi.ini"
|
||||
UWSGI="/usr/local/bin/uwsgi"
|
||||
PSID=$(ps -ef | grep "password-self-service-uwsgi uWSGI master" | grep -v grep | awk '{print $2}')
|
||||
|
||||
if [ ! -n "$1" ]
|
||||
then
|
||||
content="Usages: $0 [start|stop|restart|status]"
|
||||
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 $INI
|
||||
content="Start uWsgi Service [OK]"
|
||||
echo -e "\033[32m $content \033[0m"
|
||||
fi
|
||||
|
||||
elif [ $1 = stop ];then
|
||||
kill -9 $PSID > /dev/null 2>&1
|
||||
content="Stop uWsgi Service [OK]"
|
||||
echo -e "\033[32m $content \033[0m"
|
||||
|
||||
elif [ $1 = restart ];then
|
||||
kill -9 $PSID > /dev/null 2>&1
|
||||
echo "Pls wait...."
|
||||
sleep 3s
|
||||
$UWSGI --ini $INI
|
||||
content="Restart uWsgi Service [OK]"
|
||||
echo -e "\033[32m $content \033[0m"
|
||||
|
||||
elif [ $1 = status ];then
|
||||
ps -ef | grep "password-self-service-uwsgi" | grep -v "grep"
|
||||
else
|
||||
content="Usages: $0 [start|stop|restart|status]"
|
||||
echo -e "\033[31m $content \033[0m"
|
||||
fi
|
33488
log/log.log
33488
log/log.log
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,7 @@
|
|||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
from utils.ad_ops import AdOps
|
||||
|
||||
if __name__ == '__main__':
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pwdselfservice.settings')
|
||||
|
@ -12,4 +13,11 @@ if __name__ == '__main__':
|
|||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
|
||||
try:
|
||||
AdOps()
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
print("未能连接到AD,先决条件未满足,Django不会运行..")
|
||||
sys.exit(1)
|
||||
execute_from_command_line(sys.argv)
|
||||
|
|
|
@ -1,32 +1,42 @@
|
|||
# AD配置,修改为自己的
|
||||
# ########## AD配置,修改为自己的
|
||||
# AD主机,可以是IP或主机域名,例如可以是: abc.com或172.16.122.1
|
||||
AD_HOST = '修改为自己的'
|
||||
AD_HOST = r'修改成自己的'
|
||||
|
||||
# 用于登录AD做用户信息验证的账号, 需要有修改用户账号密码的权限。
|
||||
# 账号格式使用DOMAIN\USERNAME,例如:abc\pwdadmin
|
||||
AD_LOGIN_USER = '修改为自己的'
|
||||
# AD域控的DOMAIN名,例如:abc、abc.com
|
||||
AD_DOMAIN = r'修改成自己的'
|
||||
|
||||
# 用于登录AD做用户信息处理的账号,需要有修改用户账号密码或信息的权限。
|
||||
# AD账号,例如:pwdadmin
|
||||
AD_LOGIN_USER = r'修改成自己的'
|
||||
# 密码
|
||||
AD_LOGIN_USER_PWD = '修改为自己的'
|
||||
AD_LOGIN_USER_PWD = r'修改为自己的'
|
||||
|
||||
# BASE DN,账号的查找DN路径,例如:'DC=abc,DC=com',可以指定到OU之下,例如:'OU=RD,DC=abc,DC=com'。
|
||||
BASE_DN = '修改为自己的'
|
||||
BASE_DN = r'修改成自己的'
|
||||
|
||||
# 是否启用SSL,
|
||||
# 注意:AD必须使用SSL才能修改密码(这里被坑了N久...),自行部署下AD的证书服务,并颁发CA证书,重启服务器生效。具体教程百度一下,有很多。
|
||||
AD_USE_SSL = True
|
||||
# 连接的端口,如果启用SSL默认是636,否则就是389
|
||||
AD_CONN_PORT = 636
|
||||
|
||||
|
||||
# ########## 钉钉
|
||||
# 钉钉配置
|
||||
# 钉钉接口地址,不可修改
|
||||
DING_URL = "https://oapi.dingtalk.com/sns"
|
||||
# 钉钉接口主地址,不可修改
|
||||
DING_URL = r'https://oapi.dingtalk.com'
|
||||
|
||||
# 钉钉企业ID,修改为自己的
|
||||
# 钉钉企业ID <CorpId>,修改为自己的
|
||||
DING_CORP_ID = '修改为自己的'
|
||||
|
||||
# 钉钉E应用,修改为自己的
|
||||
DING_AGENT_ID = '修改为自己的'
|
||||
DING_APP_KEY = '修改为自己的'
|
||||
DING_APP_SECRET = '修改为自己的'
|
||||
# 钉钉企业内部开发,内部H5微应用或小程序,用于读取企业内部用户信息
|
||||
DING_AGENT_ID = r'修改为自己的'
|
||||
DING_APP_KEY = r'修改为自己的'
|
||||
DING_APP_SECRET = r'修改为自己的'
|
||||
|
||||
# 钉钉移动应用接入,修改为自己的
|
||||
DING_SELF_APP_ID = '修改为自己的'
|
||||
DING_SELF_APP_SECRET = '修改为自己的'
|
||||
# 移动应用接入 主要为了实现通过扫码拿到用户的unioid
|
||||
DING_MO_APP_ID = r'修改为自己的'
|
||||
DING_MO_APP_SECRET = r'修改为自己的'
|
||||
|
||||
# 执行:python3 ./resetpwd/utils/crypto.py 生成
|
||||
# 可自行生成后替换
|
||||
|
@ -35,6 +45,5 @@ CRYPTO_KEY = b'dp8U9y7NAhCD3MoNwPzPBhBtTZ1uI_WWSdpNs6wUDgs='
|
|||
# COOKIE 超时单位是秒,可不用修改
|
||||
TMPID_COOKIE_AGE = 300
|
||||
|
||||
|
||||
# 主页域名,index.html中的钉钉跳转等需要指定域名。
|
||||
# 主页域名,钉钉跳转等需要指定域名,格式:pwd.abc.com。
|
||||
HOME_URL = 'PWD_SELF_SERVICE_DOMAIN'
|
|
@ -15,15 +15,11 @@ import os
|
|||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'nxnm3#&2tat_c2i6%$y74a)t$(3irh^gpwaleoja1kdv30fmcm'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = False
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
|
@ -35,7 +31,8 @@ if not os.path.isdir(LOG_PATH):
|
|||
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,#此选项开启表示禁用部分日志,不建议设置为True
|
||||
# 此选项开启表示禁用部分日志,不建议设置为True
|
||||
'disable_existing_loggers': False,
|
||||
'formatters': {
|
||||
'verbose': {
|
||||
'format': '%(asctime)s %(levelname)s %(pathname)s %(module)s.%(funcName)s %(lineno)d: %(message)s'
|
||||
|
@ -47,7 +44,8 @@ LOGGING = {
|
|||
},
|
||||
'filters': {
|
||||
'require_debug_true': {
|
||||
'()': 'django.utils.log.RequireDebugTrue',#过滤器,只有当setting的DEBUG = True时生效
|
||||
# 过滤器,只有当setting的DEBUG = True时生效
|
||||
'()': 'django.utils.log.RequireDebugTrue',
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
|
@ -57,15 +55,18 @@ LOGGING = {
|
|||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'verbose'
|
||||
},
|
||||
'file': {#重点配置部分
|
||||
'file': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.FileHandler',
|
||||
'filename': '%s/log.log' % LOG_PATH,#日志保存文件
|
||||
'formatter': 'verbose'#日志格式,与上边的设置对应选择
|
||||
# 日志保存文件
|
||||
'filename': '%s/log.log' % LOG_PATH,
|
||||
# 日志格式,与上边的设置对应选择
|
||||
'formatter': 'verbose'
|
||||
}
|
||||
},
|
||||
'loggers': {
|
||||
'django': {#日志记录器
|
||||
'django': {
|
||||
# 日志记录器
|
||||
'handlers': ['file'],
|
||||
'level': 'DEBUG',
|
||||
'propagate': True,
|
||||
|
@ -85,8 +86,6 @@ SESSION_EXPIRE_AT_BROWSER_CLOSE = True
|
|||
# SESSION_COOKIE_HTTPONLY= True
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
# 'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
|
@ -140,9 +139,6 @@ WSGI_APPLICATION = 'pwdselfservice.wsgi.application'
|
|||
# }
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
|
@ -158,10 +154,6 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/2.1/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'zh-hans'
|
||||
|
||||
TIME_ZONE = 'Asia/Shanghai'
|
||||
|
@ -170,15 +162,10 @@ USE_I18N = True
|
|||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = False
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/2.1/howto/static-files/
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
USE_TZ = True
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = 'static'
|
||||
# STATIC_ROOT = 'static'
|
||||
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(BASE_DIR, 'static'),
|
||||
|
|
|
@ -4,9 +4,9 @@ import resetpwd.views
|
|||
|
||||
urlpatterns = {
|
||||
path("favicon.ico", RedirectView.as_view(url='static/img/favicon.ico')),
|
||||
path('', resetpwd.views.resetpwd_index, name='index'),
|
||||
path('resetcheck', resetpwd.views.resetpwd_check_userinfo, name='resetcheck'),
|
||||
path('resetpwd', resetpwd.views.resetpwd_reset, name='resetpwd'),
|
||||
path('resetunlock', resetpwd.views.resetpwd_unlock, name='resetunlock'),
|
||||
path('resetmsg', resetpwd.views.reset_msg, name='resetmsg'),
|
||||
path('', resetpwd.views.index, name='index'),
|
||||
path('callbackCheck', resetpwd.views.callback_check, name='callbackCheck'),
|
||||
path('resetPassword', resetpwd.views.reset_pwd_by_ding_callback, name='resetPassword'),
|
||||
path('unlockAccount', resetpwd.views.unlock_account, name='unlockAccount'),
|
||||
path('messages', resetpwd.views.messages, name='messages'),
|
||||
}
|
||||
|
|
129
readme.md
129
readme.md
|
@ -1,57 +1,61 @@
|
|||
# 初学Django时碰到的一个需求,因为公司中很多员工在修改密码之后,有一些关联的客户端或网页中的旧密码没有更新,导致密码在尝试多次之后账号被锁,为了减少这种让人头疼的重置解锁密码的操蛋工作,自己做了一个自助修改小平台。
|
||||
## 水平有限,代码写得不好,但是能用,有需要的可以直接拿去用。
|
||||
### 初学Django时碰到的一个需求,因为公司中很多员工在修改密码之后,有一些关联的客户端或网页中的旧密码没有更新,导致密码在尝试多次之后账号被锁,为了减少这种让人头疼的重置解锁密码的操蛋工作,自己做了一个自助修改小平台。
|
||||
### 水平有限,代码写得不好,但是能用,有需要的可以直接拿去用。
|
||||
#### 场景说明:
|
||||
因为本公司AD是早期已经在用,用户的个人信息不是十分全面,例如:用户手机号。
|
||||
钉钉是后来才开始使用,钉钉默认是使用手机号登录。
|
||||
这样就造成如果通过手机号来进行钉钉与AD之间的验证视乎行不通。
|
||||
在这里我就使用了通过扫码后,提取钉钉账号的邮箱信息,再将邮箱在AD中进行比对来验证用户(邮箱)是否同时在企业的钉钉和企业AD中同时存在,并账号状态是激活的。
|
||||
用户自行重置密码时如果通过手机号来进行钉钉与AD之间的验证就行不通了。
|
||||
|
||||
此处的配置可按自己的实际情况修改。
|
||||
### 新版本逻辑:
|
||||
>用户扫码通过之后,通过临时授权码,提取用户的unionid,再通过unionid判断用户在本企业中是否存在。如果存在,提取用户钉钉账号的邮箱,通过邮箱转成账号,将账号拿到AD中进行比对来验证账号在AD中是否存在并账号状态是激活的。满足以上条件的账号就会视为可自行重置密码。
|
||||
|
||||
整个验证逻辑写在resetpwd/views.py
|
||||
|
||||
提示:
|
||||
如果使用中提示无法连接到域控,可以修改下resetpwd\utils\ad.py文件:
|
||||
```python
|
||||
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:
|
||||
conn = Connection(server, auto_bind=True, user=username, password=AD_LOGIN_USER_PWD, authentication='NTLM')
|
||||
return conn
|
||||
except Exception:
|
||||
raise Exception('Server Error. Could not connect to Domain Controller')
|
||||
### 提示:
|
||||
```
|
||||
AD必须使用SSL才能修改密码(这里被坑了N久...)
|
||||
自行部署下AD的证书服务,并颁发CA证书,重启服务器生效。
|
||||
具体教程百度一下,有很多。
|
||||
```
|
||||
|
||||
把上面代码中的use_ssl=True改成use_ssl=False
|
||||
### 本次升级、修复,请使用最新版:
|
||||
+ 升级Python版本为3.8
|
||||
+ 升级Django到3.2
|
||||
+ 修复用户名中使用\被转义的问题
|
||||
+ 重写了dingding模块,因为dingding开发者平台接口鉴权的一些变动,之前的一些接口不能再使用,本次重写。
|
||||
+ 重写了ad模块,修改账号的一些判断逻辑。
|
||||
+ 重写了用户账号的格式兼容,现在用户账号可以兼容:username、DOMAIN\username、username@abc.com这三种格式。
|
||||
+ 优化了整体的代码逻辑,去掉一些冗余重复的代码。
|
||||
|
||||
## 线上环境需要的基础环境:
|
||||
+ Python 3.8.9 (可自行下载源码包放到项目目录下,使用一键安装)
|
||||
+ Nginx
|
||||
+ Uwsgi
|
||||
|
||||
## 截图
|
||||
|
||||

|
||||

|
||||
|
||||
## 线上环境需要的基础环境:
|
||||
+ Python 3.6.x
|
||||
* Nginx
|
||||
* Uwsgi
|
||||
|
||||
## 钉钉必要条件:
|
||||
#### E应用配置
|
||||
* 在钉钉工作台中通过“自建应用”创建应用,选择“企业内部自主开发”,在应用首页中获取应用的AgentId、AppKey、AppSecret。
|
||||
#### 创建企业内部应用
|
||||
* 在钉钉工作台中通过“自建应用”创建应用,选择“企业内部开发”,创建H5微应用或小程序,在应用首页中获取应用的:AgentId、AppKey、AppSecret。
|
||||
* 应用需要权限:身份验证、消息通知、通讯录只读权限、手机号码信息、邮箱等个人信息、智能人事,范围是全部员工或自行选择
|
||||
* 应用安全域名和IP一定要配置,否则无法返回接口数据。
|
||||
|
||||
#### 移动接入应用:
|
||||
* 登录中开启扫码登录,配置回调域名:“https://pwd.abc.com/resetcheck”
|
||||
其中pwd.abc.com请按自己实际域名来,并记录相关的appId、appSecret。
|
||||
参考截图配置:
|
||||

|
||||

|
||||

|
||||
|
||||
#### 移动接入应用--登录权限:
|
||||
>登录中开启扫码登录,配置回调域名:“https://pwd.abc.com/callbackCheck”
|
||||
其中pwd.abc.com请按自己实际域名来,并记录相关的:appId、appSecret。
|
||||
|
||||
参考截图配置:
|
||||

|
||||
|
||||
|
||||
# 使用脚本自动快速部署,只适合Centos,其它发行版本的Linux请自行修改相关命令。
|
||||
## 我添加了一个快速自动部署脚本,可快速自动部署完成当前项目上线。
|
||||
### 使用脚本自动快速部署,只适合Centos,其它发行版本的Linux请自行修改相关命令。
|
||||
#### 我添加了一个快速自动部署脚本,可快速自动部署完成当前项目上线。
|
||||
把整个项目目录上传到新的服务器上
|
||||
```shell
|
||||
chmod +x auto-install.sh
|
||||
|
@ -113,51 +117,60 @@ HOME_URL = 'PWD_SELF_SERVICE_DOMAIN'
|
|||
修改pwdselfservice/local_settings.py中的参数,按自己的实际参数修改
|
||||
|
||||
```` python
|
||||
# AD配置,修改为自己的
|
||||
# ########## AD配置,修改为自己的
|
||||
# AD主机,可以是IP或主机域名,例如可以是: abc.com或172.16.122.1
|
||||
AD_HOST = '修改为自己的'
|
||||
AD_HOST = r'修改成自己的'
|
||||
|
||||
# 用于登录AD做用户信息验证的账号, 需要有修改用户账号密码的权限。
|
||||
# 账号格式使用DOMAIN\USERNAME,例如:abc\pwdadmin
|
||||
AD_LOGIN_USER = '修改为自己的'
|
||||
# AD域控的DOMAIN名,例如:abc、abc.com
|
||||
AD_DOMAIN = r'修改成自己的'
|
||||
|
||||
# 用于登录AD做用户信息处理的账号,需要有修改用户账号密码或信息的权限。
|
||||
# AD账号,例如:pwdadmin
|
||||
AD_LOGIN_USER = r'修改成自己的'
|
||||
# 密码
|
||||
AD_LOGIN_USER_PWD = '修改为自己的'
|
||||
AD_LOGIN_USER_PWD = r'修改为自己的'
|
||||
|
||||
# BASE DN,账号的查找DN路径,例如:'DC=abc,DC=com',可以指定到OU之下,例如:'OU=RD,DC=abc,DC=com'。
|
||||
BASE_DN = '修改为自己的'
|
||||
BASE_DN = r'修改成自己的'
|
||||
|
||||
# 是否启用SSL,
|
||||
# 注意:AD必须使用SSL才能修改密码(这里被坑了N久...),自行部署下AD的证书服务,并颁发CA证书,重启服务器生效。具体教程百度一下,有很多。
|
||||
AD_USE_SSL = True
|
||||
# 连接的端口,如果启用SSL默认是636,否则就是389
|
||||
AD_CONN_PORT = 636
|
||||
|
||||
|
||||
# ########## 钉钉
|
||||
# 钉钉配置
|
||||
# 钉钉接口地址,不可修改
|
||||
DING_URL = "https://oapi.dingtalk.com/sns"
|
||||
# 钉钉接口主地址,不可修改
|
||||
DING_URL = r'https://oapi.dingtalk.com'
|
||||
|
||||
# 钉钉企业ID,修改为自己的
|
||||
# 钉钉企业ID <CorpId>,修改为自己的
|
||||
DING_CORP_ID = '修改为自己的'
|
||||
|
||||
# 钉钉E应用,修改为自己的
|
||||
DING_AGENT_ID = '修改为自己的'
|
||||
DING_APP_KEY = '修改为自己的'
|
||||
DING_APP_SECRET = '修改为自己的'
|
||||
# 钉钉企业内部开发,内部H5微应用或小程序,用于读取企业内部用户信息
|
||||
DING_AGENT_ID = r'修改为自己的'
|
||||
DING_APP_KEY = r'修改为自己的'
|
||||
DING_APP_SECRET = r'修改为自己的'
|
||||
|
||||
# 钉钉移动应用接入,修改为自己的
|
||||
DING_SELF_APP_ID = '修改为自己的'
|
||||
DING_SELF_APP_SECRET = '修改为自己的'
|
||||
# 移动应用接入 主要为了实现通过扫码拿到用户的unioid
|
||||
DING_MO_APP_ID = r'修改为自己的'
|
||||
DING_MO_APP_SECRET = r'修改为自己的'
|
||||
|
||||
# Crypty key 通过generate_key生成,可不用修改
|
||||
# 执行:python3 ./resetpwd/utils/crypto.py 生成
|
||||
# 可自行生成后替换
|
||||
CRYPTO_KEY = b'dp8U9y7NAhCD3MoNwPzPBhBtTZ1uI_WWSdpNs6wUDgs='
|
||||
|
||||
# COOKIE 超时单位是秒,可不用修改
|
||||
TMPID_COOKIE_AGE = 300
|
||||
|
||||
|
||||
# 主页域名,index.html中的钉钉跳转等需要指定域名。
|
||||
# 主页域名,钉钉跳转等需要指定域名,格式:pwd.abc.com。
|
||||
HOME_URL = 'PWD_SELF_SERVICE_DOMAIN'
|
||||
|
||||
````
|
||||
|
||||
|
||||
### 自行安装完python3之后,使用python3目录下的pip3进行安装依赖:
|
||||
### 我自行安装的Python路径为/usr/local/python3
|
||||
# 手动部署
|
||||
#### 自行安装完python3之后,使用python3目录下的pip3进行安装依赖:
|
||||
#### 我自行安装的Python路径为/usr/local/python3
|
||||
|
||||
项目目录下的requestment文件里记录了所依赖的相关python模块,安装方法:
|
||||
* /usr/local/python3/bin/pip3 install -r requestment
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
Django==2.1.8
|
||||
dingtalk-sdk>=1.2.2
|
||||
pycrypto>=2.6
|
||||
cryptography
|
||||
ldap3
|
||||
Django==3.2
|
||||
dingtalk-sdk==1.3.8
|
||||
cryptography==3.4.7
|
||||
ldap3==2.9
|
||||
requests
|
||||
uwsgi
|
|
@ -1,3 +0,0 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
|
@ -17,7 +17,7 @@ class CheckForm(c_forms.Form):
|
|||
)
|
||||
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': '邮箱格式错误'})
|
||||
username = c_fields.CharField(error_messages={'required': '账号不能为空', 'invalid': '账号格式错误'})
|
||||
|
||||
def clean(self):
|
||||
pwd0 = self.cleaned_data.get('old_password')
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
from django.db import models
|
||||
from django import forms
|
||||
from django.contrib import auth
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
from ldap3 import *
|
||||
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:
|
||||
conn = Connection(server, auto_bind=True, user=username, password=AD_LOGIN_USER_PWD, authentication='NTLM')
|
||||
return conn
|
||||
except Exception:
|
||||
raise Exception('Server Error. Could not connect to Domain Controller')
|
||||
|
||||
|
||||
def ad_ensure_user_by_sam(username):
|
||||
"""
|
||||
通过sAMAccountName查询某个用户是否在AD中
|
||||
:param username: 除去@domain.com 的部分
|
||||
:return: True or False
|
||||
"""
|
||||
conn = __ad_connect()
|
||||
base_dn = BASE_DN
|
||||
condition = '(&(objectclass=person)(mail=' + username + '))'
|
||||
attributes = ['sAMAccountName']
|
||||
result = conn.search(base_dn, condition, attributes=attributes)
|
||||
conn.unbind()
|
||||
return result
|
||||
|
||||
|
||||
def ad_ensure_user_by_mail(user_mail_addr):
|
||||
"""
|
||||
通过mail查询某个用户是否在AD中
|
||||
:param user_mail_addr:
|
||||
:return: True or False
|
||||
"""
|
||||
conn = __ad_connect()
|
||||
base_dn = BASE_DN
|
||||
condition = '(&(objectclass=person)(mail=' + user_mail_addr + '))'
|
||||
attributes = ['mail']
|
||||
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'])
|
||||
user_displayname = conn.entries[0]['displayName']
|
||||
conn.unbind()
|
||||
return user_displayname
|
||||
|
||||
|
||||
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)
|
||||
conn.unbind()
|
||||
return result
|
||||
|
||||
|
||||
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)
|
||||
conn.unbind()
|
||||
return result
|
||||
|
||||
|
||||
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,
|
||||
old_password="%s" % old_password)
|
||||
conn.unbind()
|
||||
return result
|
||||
|
||||
|
||||
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']
|
||||
conn.unbind()
|
||||
if '1601-01-01' in str(locked_status):
|
||||
return 0
|
||||
else:
|
||||
return locked_status
|
|
@ -1,34 +0,0 @@
|
|||
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('新密码和确认密码输入不一致')
|
|
@ -1,30 +0,0 @@
|
|||
from django.shortcuts import render, reverse, HttpResponsePermanentRedirect, redirect
|
||||
from django.http import *
|
||||
from django.contrib import messages
|
||||
from dingtalk import *
|
||||
from resetpwd.models import *
|
||||
from .crypto import Crypto
|
||||
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 resetpwd.form import *
|
||||
|
||||
|
||||
class CustomPasswortValidator(object):
|
||||
|
||||
def __init__(self, min_length=1, max_length=30):
|
||||
self.min_length = min_length
|
||||
|
||||
def validate(self, password):
|
||||
special_characters = "[~\!@#\$%\^&\*\(\)_\+{}\":;'\[\]]"
|
||||
if not any(char.isdigit() for char in password):
|
||||
raise ValidationError(_('Password must contain at least %(min_length)d digit.') % {'min_length': self.min_length})
|
||||
if not any(char.isalpha() for char in password):
|
||||
raise ValidationError(_('Password must contain at least %(min_length)d letter.') % {'min_length': self.min_length})
|
||||
if not any(char in special_characters for char in password):
|
||||
raise ValidationError(_('Password must contain at least %(min_length)d special character.') % {'min_length': self.min_length})
|
||||
|
||||
def get_help_text(self):
|
||||
return ""
|
|
@ -1,63 +1,67 @@
|
|||
from django.shortcuts import render
|
||||
from django.http import *
|
||||
from resetpwd.utils.crypto import Crypto
|
||||
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 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 .form import CheckForm
|
||||
import logging
|
||||
import sys
|
||||
|
||||
msg_template = 'msg.html'
|
||||
from django.http import *
|
||||
from django.shortcuts import render
|
||||
|
||||
from utils.ad_ops import *
|
||||
from utils.crypto import Crypto
|
||||
from utils.dingding_ops import *
|
||||
from utils.format_username import format2username
|
||||
from .form import CheckForm
|
||||
|
||||
msg_template = 'messages.html'
|
||||
logger = logging.getLogger('django')
|
||||
|
||||
ad_ops = AdOps()
|
||||
ding_ops = DingDingOps()
|
||||
|
||||
def resetpwd_index(request):
|
||||
|
||||
def index(request):
|
||||
"""
|
||||
用户修改密码
|
||||
用户自行修改密码
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
home_url = '%s://%s' % (request.scheme, HOME_URL)
|
||||
app_id = DING_SELF_APP_ID
|
||||
app_id = DING_MO_APP_ID
|
||||
if request.method == 'GET':
|
||||
return render(request, 'index.html', locals())
|
||||
else:
|
||||
logger.error('[异常] 请求方法:%s,请求路径:%s' % (request.method, request.path))
|
||||
|
||||
if request.method == 'POST':
|
||||
# 对前端提交的数据进行二次验证,防止恶意提交简单密码或串改账号。
|
||||
# 对前端提交的数据进行二次验证,防止恶意提交简单密码或篡改账号。
|
||||
check_form = CheckForm(request.POST)
|
||||
if check_form.is_valid():
|
||||
form_obj = check_form.cleaned_data
|
||||
user_email = form_obj.get("user_email")
|
||||
username = form_obj.get("username")
|
||||
old_password = form_obj.get("old_password")
|
||||
new_password = form_obj.get("new_password")
|
||||
else:
|
||||
msg = check_form.as_p().errors
|
||||
logger.error('[异常] 请求方法:%s,请求路径:%s,错误信息:%s' % (request.method, request.path, msg))
|
||||
_msg = check_form.as_p().errors
|
||||
logger.error('[异常] 请求方法:%s,请求路径:%s,错误信息:%s' % (request.method, request.path, _msg))
|
||||
context = {
|
||||
'msg': msg,
|
||||
'msg': _msg,
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
'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:
|
||||
# 格式化用户名
|
||||
username = format2username(username)
|
||||
# 检测账号状态
|
||||
auth_status, auth_result = ad_ops.ad_auth_user(username=username, password=old_password)
|
||||
if not auth_status:
|
||||
context = {
|
||||
'msg': "此账号己被锁定,请先解锁账号。",
|
||||
'msg': str(auth_result),
|
||||
'button_click': "window.history.back()",
|
||||
'button_display': "返回"
|
||||
}
|
||||
return render(request, msg_template, context)
|
||||
|
||||
# 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:
|
||||
# 可能不是太准确
|
||||
if ad_ops.ad_get_user_status_by_account(username) == 514 or ad_ops.ad_get_user_status_by_account(username) == 66050:
|
||||
context = {
|
||||
'msg': "此账号状态为己禁用,请联系HR确认账号是否正确。",
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
|
@ -65,65 +69,21 @@ def resetpwd_index(request):
|
|||
}
|
||||
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:
|
||||
reset_status, reset_result = ad_ops.ad_reset_user_pwd_by_account(username=username, new_password=new_password)
|
||||
if reset_status:
|
||||
context = {
|
||||
'msg': "密码己修改成功,请妥善保管密码。你可直接关闭此页面!",
|
||||
'msg': "密码己修改成功,新密码稍后生效,请妥善保管。您可直接关闭此页面!",
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
'button_display': "返回主页"
|
||||
}
|
||||
return render(request, msg_template, context)
|
||||
|
||||
else:
|
||||
context = {
|
||||
'msg': "密码未修改成功,请确认旧密码是否正确。",
|
||||
'msg': "密码未修改成功,原因:{}" .format(reset_result),
|
||||
'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': "请确认邮箱账号[%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)
|
||||
|
||||
else:
|
||||
context = {
|
||||
'msg': "用户名、旧密码、新密码参数不正确,请重新确认后输入。",
|
||||
'button_click': "window.history.back()",
|
||||
'button_display': "返回"
|
||||
}
|
||||
return render(request, msg_template, context)
|
||||
|
||||
else:
|
||||
context = {
|
||||
'msg': "请从主页进行修改密码操作或扫码验证用户信息。",
|
||||
|
@ -133,10 +93,9 @@ def resetpwd_index(request):
|
|||
return render(request, msg_template, context)
|
||||
|
||||
|
||||
def resetpwd_check_userinfo(request):
|
||||
def callback_check(request):
|
||||
"""
|
||||
钉钉扫码回调数据对用户在AD中进行验证
|
||||
扫码之后从钉钉中取出用户的unionid
|
||||
钉钉扫码回调数据之后,将用户账号在AD中进行验证,如果通过,则返回钉钉中取出用户的union_id
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
|
@ -147,45 +106,48 @@ def resetpwd_check_userinfo(request):
|
|||
else:
|
||||
logger.error('[异常] 请求方法:%s,请求路径:%s,未能拿到CODE。' % (request.method, request.path))
|
||||
try:
|
||||
unionid = ding_get_persistent_code(code, ding_get_access_token())
|
||||
# 判断 unionid 在本企业钉钉中是否存在
|
||||
if not unionid:
|
||||
logger.error('[异常] 请求方法:%s,请求路径:%s,未能拿到unionid。' % (request.method, request.path))
|
||||
union_status, union_id = ding_ops.ding_get_union_id_by_code(code)
|
||||
# 判断 union_id 在本企业钉钉中是否存在
|
||||
if not union_status:
|
||||
logger.error('[异常] 请求方法:%s,请求路径:%s,未能拿到union_id。' % (request.method, request.path))
|
||||
context = {
|
||||
'msg': '未能在钉钉企业通讯录中检索到相关信息,请确认当前登录钉钉的账号已在企业中注册!',
|
||||
'msg': '未能在企业钉钉中检索到用户信息,错误信息:{}' .format(union_id),
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
'button_display': "返回主页"
|
||||
}
|
||||
return render(request, msg_template, context)
|
||||
userid_status, user_result = ding_ops.ding_get_userid_by_union_id(union_id)
|
||||
if not userid_status:
|
||||
context = {
|
||||
'msg': '获取钉钉userid失败,错误信息:{}'.format(user_result),
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
'button_display': "返回主页"
|
||||
}
|
||||
return render(request, msg_template, context)
|
||||
detail_status, ding_user_info = ding_ops.ding_get_userinfo_detail(user_result)
|
||||
if not detail_status:
|
||||
context = {
|
||||
'msg': '获取钉钉用户信息失败,错误信息:{}'.format(ding_user_info),
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
'button_display': "返回主页"
|
||||
}
|
||||
return render(request, msg_template, context)
|
||||
|
||||
ding_user_info = ding_get_userinfo_detail(ding_get_userid_by_unionid(unionid))
|
||||
try:
|
||||
# 钉钉中此账号是否可用
|
||||
if ding_user_info['active']:
|
||||
crypto = Crypto(CRYPTO_KEY)
|
||||
# 对unionid进行加密,因为unionid基本上固定不变的,为了防止unionid泄露而导致重复使用,进行加密后再传回。
|
||||
unionid_cryto = crypto.encrypt(unionid)
|
||||
# 配置cookie,通过cookie把加密后的用户unionid传到重置密码页面,并重定向到重置密码页面。
|
||||
set_cookie = HttpResponseRedirect('resetpwd')
|
||||
set_cookie.set_cookie('tmpid', unionid_cryto, expires=TMPID_COOKIE_AGE)
|
||||
# 对union_id进行加密,因为union_id基本上固定不变的,为了防止union_id泄露而导致重复使用,进行加密后再传回。
|
||||
union_id_cryto = crypto.encrypt(union_id)
|
||||
# 配置cookie,通过cookie把加密后的用户union_id传到重置密码页面,并重定向到重置密码页面。
|
||||
set_cookie = HttpResponseRedirect('resetPassword')
|
||||
set_cookie.set_cookie('tmpid', union_id_cryto, expires=TMPID_COOKIE_AGE)
|
||||
return set_cookie
|
||||
else:
|
||||
context = {
|
||||
'msg': '邮箱是[%s]的用户在钉钉中未激活或可能己离职' % ding_user_info['email'],
|
||||
'msg': '[%s]在钉钉中未激活或可能己离职' % format2username(ding_user_info['name']),
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
'button_display': "返回主页"
|
||||
}
|
||||
return render(request, msg_template, context)
|
||||
except IndexError:
|
||||
context = {
|
||||
'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))
|
||||
|
||||
except KeyError:
|
||||
context = {
|
||||
'msg': "错误,钉钉临时Code己失效,请从主页重新扫码。",
|
||||
|
@ -205,21 +167,22 @@ def resetpwd_check_userinfo(request):
|
|||
return render(request, msg_template, context)
|
||||
|
||||
|
||||
def resetpwd_reset(request):
|
||||
def reset_pwd_by_ding_callback(request):
|
||||
"""
|
||||
钉钉扫码并验证信息之后,在重置密码页面将用户邮箱进行绑定
|
||||
钉钉扫码并验证信息通过之后,在重置密码页面将用户账号进行绑定
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
global unionid_crypto
|
||||
global union_id_crypto
|
||||
home_url = '%s://%s' % (request.scheme, HOME_URL)
|
||||
# 从cookie中提取unionid,并解密,然后对当前unionid的用户进行重置密码
|
||||
# 从cookie中提取union_id,并解密,然后对当前union_id的用户进行重置密码
|
||||
if request.method == 'GET':
|
||||
try:
|
||||
unionid_crypto = request.COOKIES.get('tmpid')
|
||||
union_id_crypto = request.COOKIES.get('tmpid')
|
||||
except Exception as e:
|
||||
union_id_crypto = None
|
||||
logger.error('[异常] :%s' % str(e))
|
||||
if not unionid_crypto:
|
||||
if not union_id_crypto:
|
||||
logger.error('[异常] 请求方法:%s,请求路径:%s,未能拿到CODE或CODE己超时。' % (request.method, request.path))
|
||||
context = {
|
||||
'msg': "会话己超时,请重新扫码验证用户信息。",
|
||||
|
@ -229,20 +192,35 @@ def resetpwd_reset(request):
|
|||
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:
|
||||
union_id = crypto.decrypt(union_id_crypto)
|
||||
# 通过union_id在钉钉中拿到用户的邮箱,并格式化为username
|
||||
userid_status, user_result = ding_ops.ding_get_userid_by_union_id(union_id)
|
||||
if not userid_status:
|
||||
context = {
|
||||
'user_email': user_email,
|
||||
'msg': '获取钉钉userid失败,错误信息:{}'.format(user_result),
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
'button_display': "返回主页"
|
||||
}
|
||||
return render(request, 'resetpwd.html', context)
|
||||
return render(request, msg_template, context)
|
||||
detail_status, ding_user_info = ding_ops.ding_get_userinfo_detail(user_result)
|
||||
if not detail_status:
|
||||
context = {
|
||||
'msg': '获取钉钉用户信息失败,错误信息:{}'.format(ding_user_info),
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
'button_display': "返回主页"
|
||||
}
|
||||
return render(request, msg_template, context)
|
||||
username = format2username(ding_user_info['email'])
|
||||
# 如果邮箱在钉钉中能提取到,则格式化之后,提取出账号提交到前端绑定
|
||||
if username:
|
||||
context = {
|
||||
'username': username,
|
||||
}
|
||||
return render(request, 'resetPassword.html', context)
|
||||
# 否则就是钉钉中此用户未配置邮箱,返回相关提示
|
||||
else:
|
||||
context = {
|
||||
'msg': "%s 您好,企业钉钉中未能找到您账号的邮箱配置,请联系HR完善信息。" % ding_get_userinfo_detail(ding_get_userid_by_unionid(
|
||||
unionid))['name'],
|
||||
'msg': "%s,您好,企业钉钉中未能找到您账号的邮箱配置,请联系HR完善信息。" % ding_user_info['name'],
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
'button_display': "返回主页"
|
||||
}
|
||||
|
@ -250,10 +228,14 @@ def resetpwd_reset(request):
|
|||
|
||||
# 重置密码页面,输入新密码后点击提交
|
||||
elif request.method == 'POST':
|
||||
new_password = request.POST.get('new_password').strip()
|
||||
unionid_crypto = request.COOKIES.get('tmpid')
|
||||
# 对cookie中的unionid进行超时验证,如果页面超时就不再做处理。
|
||||
if not unionid_crypto:
|
||||
_new_password = request.POST.get('new_password').strip()
|
||||
try:
|
||||
union_id_crypto = request.COOKIES.get('tmpid')
|
||||
except Exception as e:
|
||||
union_id_crypto = None
|
||||
logger.error('[异常] :%s' % str(e))
|
||||
if not union_id_crypto:
|
||||
logger.error('[异常] 请求方法:%s,请求路径:%s,未能拿到CODE或CODE己超时。' % (request.method, request.path))
|
||||
context = {
|
||||
'msg': "会话己超时,请重新扫码验证用户信息。",
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
|
@ -261,17 +243,32 @@ def resetpwd_reset(request):
|
|||
}
|
||||
return render(request, msg_template, context)
|
||||
crypto = Crypto(CRYPTO_KEY)
|
||||
unionid = crypto.decrypt(unionid_crypto)
|
||||
user_email = ding_get_userinfo_detail(ding_get_userid_by_unionid(unionid))['email']
|
||||
if ad_ensure_user_by_mail(user_mail_addr=user_email) is False:
|
||||
union_id = crypto.decrypt(union_id_crypto)
|
||||
userid_status, user_result = ding_ops.ding_get_userid_by_union_id(union_id)
|
||||
if not userid_status:
|
||||
context = {
|
||||
'msg': "账号[%s]在AD中不存在,请确认当前钉钉扫码账号绑定的邮箱是否和您正在使用的邮箱一致?或者该邮箱账号己被禁用!\n猜测:您的邮箱是否是带有数字或其它字母区分?" % user_email,
|
||||
'msg': '获取钉钉userid失败,错误信息:{}'.format(user_result),
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
'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:
|
||||
detail_status, ding_user_info = ding_ops.ding_get_userinfo_detail(user_result)
|
||||
if not detail_status:
|
||||
context = {
|
||||
'msg': '获取钉钉用户信息失败,错误信息:{}'.format(ding_user_info),
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
'button_display': "返回主页"
|
||||
}
|
||||
return render(request, msg_template, context)
|
||||
username = format2username(ding_user_info['email'])
|
||||
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)
|
||||
if ad_ops.ad_get_user_status_by_account(username) == 514 or ad_ops.ad_get_user_status_by_account(username) == 66050:
|
||||
context = {
|
||||
'msg': "此账号状态为己禁用,请联系HR确认账号是否正确。",
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
|
@ -279,11 +276,11 @@ def resetpwd_reset(request):
|
|||
}
|
||||
return render(request, msg_template, context)
|
||||
|
||||
try:
|
||||
result = ad_reset_user_pwd_by_mail(user_mail_addr=user_email, new_password=new_password)
|
||||
if result:
|
||||
reset_status, result = ad_ops.ad_reset_user_pwd_by_account(username=username, new_password=_new_password)
|
||||
if reset_status:
|
||||
# 重置密码并执行一次解锁,防止重置后账号还是锁定状态。
|
||||
ad_unlock_user_by_mail(user_email)
|
||||
unlock_status, result = ad_ops.ad_unlock_user_by_account(username)
|
||||
if unlock_status:
|
||||
context = {
|
||||
'msg': "密码己重置成功,请妥善保管。你可以点击返回主页或直接关闭此页面!",
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
|
@ -292,21 +289,7 @@ def resetpwd_reset(request):
|
|||
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': "请确认邮箱账号[%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),
|
||||
'msg': "密码未重置成功,错误信息:{}" .format(result),
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
'button_display': "返回主页"
|
||||
}
|
||||
|
@ -320,7 +303,7 @@ def resetpwd_reset(request):
|
|||
return render(request, msg_template, context)
|
||||
|
||||
|
||||
def resetpwd_unlock(request):
|
||||
def unlock_account(request):
|
||||
"""
|
||||
解锁账号
|
||||
:param request:
|
||||
|
@ -328,8 +311,8 @@ def resetpwd_unlock(request):
|
|||
"""
|
||||
home_url = '%s://%s' % (request.scheme, HOME_URL)
|
||||
if request.method == 'GET':
|
||||
unionid_crypto = request.COOKIES.get('tmpid')
|
||||
if not unionid_crypto:
|
||||
_union_id_crypto = request.COOKIES.get('tmpid')
|
||||
if not _union_id_crypto:
|
||||
context = {
|
||||
'msg': "会话己超时,请重新扫码验证用户信息。",
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
|
@ -337,16 +320,32 @@ def resetpwd_unlock(request):
|
|||
}
|
||||
return render(request, msg_template, context)
|
||||
crypto = Crypto(CRYPTO_KEY)
|
||||
unionid = crypto.decrypt(unionid_crypto)
|
||||
user_email = ding_get_userinfo_detail(ding_get_userid_by_unionid(unionid))['email']
|
||||
union_id = crypto.decrypt(_union_id_crypto)
|
||||
userid_status, user_result = ding_ops.ding_get_userid_by_union_id(union_id)
|
||||
if not userid_status:
|
||||
context = {
|
||||
'user_email': user_email,
|
||||
'msg': '获取钉钉userid失败,错误信息:{}'.format(user_result),
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
'button_display': "返回主页"
|
||||
}
|
||||
return render(request, 'resetpwd.html', context)
|
||||
return render(request, msg_template, context)
|
||||
detail_status, ding_user_info = ding_ops.ding_get_userinfo_detail(user_result)
|
||||
if not detail_status:
|
||||
context = {
|
||||
'msg': '获取钉钉用户信息失败,错误信息:{}'.format(ding_user_info),
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
'button_display': "返回主页"
|
||||
}
|
||||
return render(request, msg_template, context)
|
||||
username = format2username(ding_user_info['email'])
|
||||
context = {
|
||||
'username': username,
|
||||
}
|
||||
return render(request, 'resetPassword.html', context)
|
||||
|
||||
elif request.method == 'POST':
|
||||
unionid_crypto = request.COOKIES.get('tmpid')
|
||||
if not unionid_crypto:
|
||||
_union_id_crypto = request.COOKIES.get('tmpid')
|
||||
if not _union_id_crypto:
|
||||
context = {
|
||||
'msg': "会话己超时,请重新扫码验证用户信息。",
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
|
@ -354,20 +353,35 @@ def resetpwd_unlock(request):
|
|||
}
|
||||
return render(request, msg_template, context)
|
||||
crypto = Crypto(CRYPTO_KEY)
|
||||
unionid = crypto.decrypt(unionid_crypto)
|
||||
user_email = ding_get_userinfo_detail(ding_get_userid_by_unionid(unionid))['email']
|
||||
if ad_ensure_user_by_mail(user_mail_addr=user_email) is False:
|
||||
union_id = crypto.decrypt(_union_id_crypto)
|
||||
userid_status, user_result = ding_ops.ding_get_userid_by_union_id(union_id)
|
||||
if not userid_status:
|
||||
context = {
|
||||
'msg': "账号[%s]在AD中未能正确检索到,请确认当前钉钉扫码账号绑定的邮箱是否和您正在使用的邮箱一致?或者该邮箱账号己被禁用!\n猜测:您的邮箱是否是带有数字或其它字母区分?" %
|
||||
user_email,
|
||||
'msg': '获取钉钉userid失败,错误信息:{}'.format(user_result),
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
'button_display': "返回主页"
|
||||
}
|
||||
return render(request, msg_template, context)
|
||||
detail_status, ding_user_info = ding_ops.ding_get_userinfo_detail(user_result)
|
||||
if not detail_status:
|
||||
context = {
|
||||
'msg': '获取钉钉用户信息失败,错误信息:{}'.format(ding_user_info),
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
'button_display': "返回主页"
|
||||
}
|
||||
return render(request, msg_template, context)
|
||||
username = format2username(ding_user_info['email'])
|
||||
if ad_ops.ad_ensure_user_by_account(username=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)
|
||||
else:
|
||||
try:
|
||||
result = ad_unlock_user_by_mail(user_email)
|
||||
if result:
|
||||
unlock_status, result = ad_ops.ad_unlock_user_by_account(username)
|
||||
if unlock_status:
|
||||
context = {
|
||||
'msg': "账号己解锁成功。你可以点击返回主页或直接关闭此页面!",
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
|
@ -376,21 +390,7 @@ def resetpwd_unlock(request):
|
|||
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': "请确认邮箱账号[%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),
|
||||
'msg': "账号未能解锁,错误信息:{}" .format(result),
|
||||
'button_click': "window.location.href='%s'" % home_url,
|
||||
'button_display': "返回主页"
|
||||
}
|
||||
|
@ -404,12 +404,12 @@ def resetpwd_unlock(request):
|
|||
return render(request, msg_template, context)
|
||||
|
||||
|
||||
def reset_msg(request):
|
||||
msg = request.GET.get('msg')
|
||||
def messages(request):
|
||||
_msg = request.GET.get('msg')
|
||||
button_click = request.GET.get('button_click')
|
||||
button_display = request.GET.get('button_display')
|
||||
context = {
|
||||
'msg': msg,
|
||||
'msg': _msg,
|
||||
'button_click': button_click,
|
||||
'button_display': button_display
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 111 KiB |
Binary file not shown.
After Width: | Height: | Size: 268 KiB |
Binary file not shown.
After Width: | Height: | Size: 168 KiB |
Binary file not shown.
After Width: | Height: | Size: 153 KiB |
|
@ -22,13 +22,14 @@
|
|||
// (?=.*[0-9])(?=.*[a-zA-Z]).{8,30} 大小写字母+数字
|
||||
regex_mail = new RegExp('^\\w+((-\\w+)|(\\.\\w+))*\\@[A-Za-z0-9]+((\\.|-)[A-Za-z0-9]+)*\\.[A-Za-z0-9]+$')
|
||||
regex_pwd = new RegExp('(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9]).{8,30}');
|
||||
if ($.trim($('#user_email').val()) === '') {
|
||||
alert('请输入邮箱账号');
|
||||
return false;
|
||||
} else if (!regex_mail.test($.trim($('#user_email').val()))) {
|
||||
alert('请输入正确的邮箱账号。\n');
|
||||
return false;
|
||||
} else if ($.trim($('#old_password').val()) === '') {
|
||||
//if ($.trim($('#user_email').val()) === '') {
|
||||
// alert('请输入邮箱账号');
|
||||
// return false;
|
||||
//} else if (!regex_mail.test($.trim($('#user_email').val()))) {
|
||||
// alert('请输入正确的邮箱账号。\n');
|
||||
// return false;
|
||||
//} else
|
||||
if ($.trim($('#old_password').val()) === '') {
|
||||
alert('请输入旧密码');
|
||||
return false;
|
||||
} else if ($.trim($('#new_password').val()) === '') {
|
||||
|
@ -57,13 +58,14 @@
|
|||
$('#btn_reset').click(function () {
|
||||
let regex_mail = new RegExp('^\\w+((-\\w+)|(\\.\\w+))*\\@[A-Za-z0-9]+((\\.|-)[A-Za-z0-9]+)*\\.[A-Za-z0-9]+$')
|
||||
let regex_pwd = new RegExp('(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9]).{8,30}');
|
||||
if ($.trim($('#user_email').val()) === '') {
|
||||
alert('请输入邮箱账号');
|
||||
return false;
|
||||
} else if (!regex_mail.test($.trim($('#user_email').val()))) {
|
||||
alert('请输入正确的邮箱账号。\n');
|
||||
return false;
|
||||
} else if ($.trim($('#new_password').val()) === '') {
|
||||
//if ($.trim($('#user_email').val()) === '') {
|
||||
//alert('请输入邮箱账号');
|
||||
// return false;
|
||||
//} else if (!regex_mail.test($.trim($('#user_email').val()))) {
|
||||
// alert('请输入正确的邮箱账号。\n');
|
||||
// return false;
|
||||
//} else
|
||||
if ($.trim($('#new_password').val()) === '') {
|
||||
alert('请输入密码');
|
||||
return false;
|
||||
} else if ($.trim($('#ensure_password').val()) === '') {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>密码自助服务</title>
|
||||
|
@ -9,7 +9,7 @@
|
|||
</head>
|
||||
<body style="overflow: hidden">
|
||||
|
||||
<form name="resetcheck" method="post" action="resetpwd" autocomplete="off">
|
||||
<form name="callbackCheck" method="post" action="resetPassword" autocomplete="off">
|
||||
{% csrf_token %}
|
||||
</form>
|
||||
</body></html>
|
|
@ -1,11 +1,11 @@
|
|||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>密码自助服务</title>
|
||||
<link type="text/css" rel="stylesheet" href="{% static 'css/login.css' %}">
|
||||
<script type="text/javascript" src="{% static 'js/jquery-1.8.3.min.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'js/check.js' %}"></script>
|
||||
<script src="//g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js"></script>
|
||||
<script src="https://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="pagewrap">
|
||||
|
@ -16,7 +16,7 @@
|
|||
<div class="content">
|
||||
<div class="con_left" >
|
||||
<div style="margin: 0 auto; width:100%; height: 200px; line-height: 200px;" align="center">
|
||||
<p style="margin: 0 auto; color: #fdfdfe; font-size: 36px; width:100%; ">「域账号/邮箱」<small>密码自助平台</small></p>
|
||||
<p style="margin: 0 auto; color: #fdfdfe; font-size: 36px; width:100%; ">「域账号或邮箱」<small>密码自助平台</small></p>
|
||||
</div>
|
||||
<div style="margin: 0 auto; width:400px; height: 240px;">
|
||||
<p style="margin: 0 auto; color: #fdfdfe; font-size: 16px; width:100%;
|
||||
|
@ -36,7 +36,7 @@
|
|||
{% csrf_token %}
|
||||
<div class="user">
|
||||
<div><span class="user-icon"></span>
|
||||
<input type="text" id="user_email" name="user_email" placeholder=" 输入邮箱(例如:user@abc.com)" value="">
|
||||
<input type="text" id="username" name="username" placeholder="格式:abc\lisi、lisi、lisi@abc.com" value="">
|
||||
</div>
|
||||
<div><span class="mima-icon"></span>
|
||||
<input type="password" id="old_password" name="old_password"
|
||||
|
@ -59,12 +59,16 @@
|
|||
<div style="margin-top: -30px" class="erweima">
|
||||
<div style="width: 300px; height: 300px; margin: 0 auto" id="ding_code"></div>
|
||||
<script type="text/javascript">
|
||||
// 构造钉钉登录
|
||||
// 扫描之后需要跳转的域名,填写自己的修改密码的域名地址http或https
|
||||
var home_url = "{{ home_url }}";
|
||||
// 钉钉移动应用接入ID
|
||||
var appid = "{{ app_id }}";
|
||||
var url = encodeURIComponent(home_url + '/resetcheck');
|
||||
var goto = encodeURIComponent('https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=' + appid + '&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=' + url);
|
||||
var app_id = "{{ app_id }}";
|
||||
var redirect_url = encodeURIComponent(home_url + '/callbackCheck');
|
||||
var goto = encodeURIComponent('https://oapi.dingtalk.com/connect/qrconnect?appid='
|
||||
+ app_id
|
||||
+ '&response_type=code&scope=snsapi_login&state=STATE&redirect_uri='
|
||||
+ redirect_url);
|
||||
var obj = DDLogin({
|
||||
id: "ding_code",
|
||||
goto: goto,
|
||||
|
@ -73,15 +77,20 @@
|
|||
height: "300"
|
||||
});
|
||||
// 获取loginTmpCode
|
||||
const hanndleMessage = function (event) {
|
||||
let origin = event.origin;
|
||||
var hanndleMessage = function (event) {
|
||||
var origin = event.origin;
|
||||
console.log("origin", event.origin)
|
||||
//判断是否来自ddLogin扫码事件。
|
||||
if (origin === "https://login.dingtalk.com") {
|
||||
let loginTmpCode = event.data;
|
||||
var loginTmpCode = event.data;
|
||||
console.log("loginTmpCode", loginTmpCode);
|
||||
if (loginTmpCode) {
|
||||
location.href = "https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=" + appid + "&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=" + url + "&loginTmpCode=" + loginTmpCode;
|
||||
//拿到loginTmpCode后就可以在这里构造跳转链接进行跳转了
|
||||
location.href = 'https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid='
|
||||
+ app_id
|
||||
+ '&response_type=code&scope=snsapi_login&state=STATE&redirect_uri='
|
||||
+ redirect_url
|
||||
+ '&loginTmpCode=' + loginTmpCode;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>密码自助服务</title>
|
||||
<link type="text/css" rel="stylesheet" href="{% static 'css/login.css' %}">
|
||||
<script type="text/javascript" src="{% static 'js/jquery-1.8.3.min.js' %}"></script>
|
||||
|
@ -13,8 +13,7 @@
|
|||
<div class="content">
|
||||
<div class="con_left" >
|
||||
<div style="margin: 0 auto; width:100%; height: 200px; line-height: 200px;" align="center">
|
||||
<p style="margin: 0 auto; color: #fdfdfe; font-size: 36px; width:100%; ">「域账号/邮箱」
|
||||
<small>密码自助平台</small></p>
|
||||
<p style="margin: 0 auto; color: #fdfdfe; font-size: 36px; width:100%; ">「域账号或邮箱」<small>密码自助平台</small></p>
|
||||
</div>
|
||||
<div style="margin: 0 auto; width:400px; height: 240px;">
|
||||
<p style="margin: 0 auto; color: #fdfdfe; font-size: 16px; width:100%;
|
|
@ -1,4 +1,4 @@
|
|||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>密码自助服务</title>
|
||||
|
@ -14,7 +14,7 @@
|
|||
<div class="content">
|
||||
<div class="con_left" >
|
||||
<div style="margin: 0 auto; width:100%; height: 200px; line-height: 200px;" align="center" >
|
||||
<p style="margin: 0 auto; color: #fdfdfe; font-size: 36px; width:100%; ">「域账号/邮箱」
|
||||
<p style="margin: 0 auto; color: #fdfdfe; font-size: 36px; width:100%; ">「域账号或邮箱」
|
||||
<small>密码自助平台</small></p>
|
||||
</div>
|
||||
<div style="margin: 0 auto; width:400px; height: 240px;">
|
||||
|
@ -35,7 +35,7 @@
|
|||
{% csrf_token %}
|
||||
<div class="user">
|
||||
<div><span class="user-icon"></span>
|
||||
<input type="text" id="user_email" name="user_email" readonly placeholder="{{ user_email }}" value="{{ user_email }}">
|
||||
<input type="text" id="username" name="username" readonly placeholder="{{ username }}" value="{{ username }}">
|
||||
</div>
|
||||
<div><span class="mima-icon"></span>
|
||||
<input type="password" id="new_password" name="new_password"
|
||||
|
@ -55,7 +55,7 @@
|
|||
{% csrf_token %}
|
||||
<div class="user" style="height: 168px">
|
||||
<div><span class="user-icon"></span>
|
||||
<input type="text" id="user_email" name="user_email" readonly placeholder="{{ user_email }}" value="{{ user_email }}">
|
||||
<input type="text" id="username" name="username" readonly placeholder="{{ username }}" value="{{ username }}">
|
||||
</div>
|
||||
<span class="msgs"></span>
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
from ldap3 import *
|
||||
from ldap3.core.exceptions import LDAPInvalidCredentialsResult, LDAPOperationResult
|
||||
from ldap3.core.results import *
|
||||
from ldap3.utils.dn import safe_dn
|
||||
|
||||
from pwdselfservice.local_settings import *
|
||||
|
||||
"""
|
||||
根据以下网站的说明:
|
||||
https://docs.microsoft.com/zh-cn/troubleshoot/windows/win32/change-windows-active-directory-user-password
|
||||
密码存储在 unicodePwd 属性中的用户对象的 AD 和 LDS 数据库中。 此属性可以在受限条件下写入,但无法读取。 只能修改属性;无法在对象创建时或由搜索查询时添加它。
|
||||
为了修改此属性,客户端必须具有到服务器的 128 位传输层安全性 (TLS) /Secure Socket Layer (SSL) 连接。
|
||||
使用 SSP 创建的会话密钥(使用 NTLM 或 Kerberos)的加密会话也可接受,只要达到最小密钥长度。
|
||||
若要使用 TLS/SSL 实现此连接:
|
||||
服务器必须拥有 128 位 RSA 连接的服务器证书。
|
||||
客户端必须信任生成服务器证书 (CA) 证书颁发机构。
|
||||
客户端和服务器都必须能够进行 128 位加密。
|
||||
|
||||
unicodePwd 属性的语法为 octet-string;但是,目录服务预期八进制字符串将包含 UNICODE 字符串 (,因为属性的名称指示) 。
|
||||
这意味着在 LDAP 中传递的此属性的任何值都必须是 BER 编码的 UNICODE 字符串 (基本编码规则) 八进制字符串。
|
||||
此外,UNICODE 字符串必须以引号开头和结尾,这些引号不是所需密码的一部分。
|
||||
|
||||
可通过两种方法修改 unicodePwd 属性。 第一种操作类似于正常的 用户更改密码 操作。
|
||||
在这种情况下,修改请求必须同时包含删除和添加操作。 删除操作必须包含当前密码,并包含其周围的引号。
|
||||
添加操作必须包含所需的新密码,其周围必须有引号。
|
||||
|
||||
修改此属性的第二种方法类似于管理员重置用户密码。 为此,客户端必须以具有修改其他用户密码的足够权限的用户进行绑定。
|
||||
此修改请求应包含单个替换操作,其中包含用引号括起的新所需密码。 如果客户端具有足够的权限,则无论旧密码是什么,此密码都将变为新密码。
|
||||
"""
|
||||
|
||||
|
||||
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, password=AD_LOGIN_USER_PWD,
|
||||
authentication=NTLM):
|
||||
"""
|
||||
AD连接器 authentication [SIMPLE, ANONYMOUS, SASL, NTLM]
|
||||
:return:
|
||||
|
||||
"""
|
||||
self.use_ssl = use_ssl
|
||||
self.port = port
|
||||
self.domain = domain
|
||||
self.user = user
|
||||
self.password = password
|
||||
self.authentication = authentication
|
||||
self.auto_bind = auto_bind
|
||||
|
||||
server = Server(host='%s' % AD_HOST, use_ssl=self.use_ssl, port=port, get_info=ALL)
|
||||
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:
|
||||
raise Exception('出现错误:无法连接到AD控制器。')
|
||||
|
||||
def ad_auth_user(self, username, password):
|
||||
"""
|
||||
验证账号
|
||||
:param username:
|
||||
:param password:
|
||||
:return: True or False
|
||||
"""
|
||||
try:
|
||||
server = Server(host='%s' % AD_HOST, use_ssl=self.use_ssl, port=self.port, get_info=ALL)
|
||||
c_auth = Connection(server=server, user=r'{}\{}'.format(self.domain, username), password=password, auto_bind=True, raise_exceptions=True)
|
||||
c_auth.unbind()
|
||||
return True, '旧密码验证通过。'
|
||||
except LDAPInvalidCredentialsResult as e:
|
||||
if '52e' in e.message:
|
||||
return False, u'账号或旧密码不正确!'
|
||||
elif '775' in e.message:
|
||||
return False, u'账号已锁定,请自行扫码解锁!'
|
||||
elif '533' in e.message:
|
||||
return False, u'账号已禁用!'
|
||||
elif '525' in e.message:
|
||||
return False, u'账号不存在!'
|
||||
elif '532' in e.message:
|
||||
return False, u'密码己过期!'
|
||||
elif '701' in e.message:
|
||||
return False, u'账号己过期!'
|
||||
elif '773' in e.message:
|
||||
# 如果仅仅使用普通凭据来绑定ldap用途,请返回False, 让用户通过其他途径修改密码后再来验证登陆
|
||||
# return False, '用户登陆前必须修改密码!'
|
||||
# 设置该账号下次登陆不需要更改密码,再验证一次
|
||||
self.conn.search(search_base=BASE_DN, search_filter='(sAMAccountName={}))'.format(username), attributes=['pwdLastSet'])
|
||||
self.conn.modify(self.conn.entries[0].entry_dn, {'pwdLastSet': [(MODIFY_REPLACE, ['-1'])]})
|
||||
return self.ad_auth_user(username, password)
|
||||
else:
|
||||
return False, u'旧密码认证失败,请确认账号的旧密码是否正确或使用重置密码功能。'
|
||||
|
||||
def ad_ensure_user_by_account(self, username):
|
||||
"""
|
||||
通过username查询某个用户是否在AD中
|
||||
: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)
|
||||
|
||||
def ad_get_user_displayname_by_account(self, username):
|
||||
"""
|
||||
通过username查询某个用户的显示名
|
||||
:param username:
|
||||
:return: user_displayname
|
||||
"""
|
||||
self.conn.search(BASE_DN, '(&(objectclass=user)(sAMAccountName={}))'.format(username), attributes=['name'])
|
||||
try:
|
||||
return True, self.conn.entries[0]['name']
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
def ad_get_user_dn_by_account(self, username):
|
||||
"""
|
||||
通过mail查询某个用户的完整DN
|
||||
:param username:
|
||||
:return: DN
|
||||
"""
|
||||
self.conn.search(BASE_DN, '(&(objectclass=user)(sAMAccountName={}))'.format(username), attributes=['distinguishedName'])
|
||||
return str(self.conn.entries[0]['distinguishedName'])
|
||||
|
||||
def ad_get_user_status_by_account(self, username):
|
||||
"""
|
||||
通过username查询某个用户的账号状态
|
||||
: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']
|
||||
|
||||
def ad_unlock_user_by_account(self, username):
|
||||
"""
|
||||
通过username解锁某个用户
|
||||
: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)
|
||||
|
||||
def ad_reset_user_pwd_by_account(self, username, new_password):
|
||||
"""
|
||||
重置某个用户的密码
|
||||
: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
|
||||
else:
|
||||
result = self.conn.result
|
||||
|
||||
# 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']
|
||||
|
||||
def ad_get_user_locked_status_by_account(self, username):
|
||||
"""
|
||||
通过mail获取某个用户账号是否被锁定
|
||||
: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
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# server = Server(host='%s' % AD_HOST, use_ssl=AD_USE_SSL, port=AD_CONN_PORT, get_info=ALL)
|
||||
# conn = Connection(server, auto_bind=True, user=str(AD_LOGIN_USER).lower(), password=AD_LOGIN_USER_PWD, authentication=SIMPLE)
|
||||
# # conn.bind()
|
||||
# # conn.search(BASE_DN, '(&(objectclass=user)(sAMAccountName=xiangle))', attributes=['name'])
|
||||
# # print(conn.entries[0])
|
||||
# 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)
|
|
@ -0,0 +1,114 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import base64
|
||||
import hmac
|
||||
import time
|
||||
from hashlib import sha256
|
||||
from urllib.parse import quote
|
||||
|
||||
import requests
|
||||
from dingtalk.client import AppKeyClient
|
||||
|
||||
from pwdselfservice.local_settings import DING_APP_KEY, DING_APP_SECRET, DING_CORP_ID, DING_URL, DING_MO_APP_ID, DING_MO_APP_SECRET
|
||||
|
||||
|
||||
class DingDingOps(object):
|
||||
def __init__(self, corp_id=DING_CORP_ID, app_key=DING_APP_KEY, app_secret=DING_APP_SECRET, mo_app_id=DING_MO_APP_ID, mo_app_secret=DING_MO_APP_SECRET):
|
||||
self.corp_id = corp_id
|
||||
self.app_key = app_key
|
||||
self.app_secret = app_secret
|
||||
self.mo_app_id = mo_app_id
|
||||
self.mo_app_secret = mo_app_secret
|
||||
|
||||
@property
|
||||
def ding_client_connect(self):
|
||||
"""
|
||||
钉钉连接器
|
||||
:return:
|
||||
"""
|
||||
return AppKeyClient(corp_id=self.corp_id, app_key=self.app_key, app_secret=self.app_secret)
|
||||
|
||||
@property
|
||||
def ding_get_access_token(self):
|
||||
"""
|
||||
获取企业内部应用的access_token
|
||||
:return:
|
||||
"""
|
||||
return self.ding_client_connect.access_token
|
||||
|
||||
def ding_get_dept_user_list_detail(self, dept_id, offset, size):
|
||||
"""
|
||||
获取部门中的用户列表详细清单
|
||||
:param dept_id: 部门ID
|
||||
:param offset: 偏移量(可理解为步进量)
|
||||
:param size: 一次查询多少个
|
||||
:return:
|
||||
"""
|
||||
return self.ding_client_connect.user.list(department_id=dept_id, offset=offset, size=size)
|
||||
|
||||
def ding_get_union_id_by_code(self, code):
|
||||
"""
|
||||
通过移动应用接入扫码返回的临时授权码,获取用户的unionid
|
||||
:param code:
|
||||
:return:
|
||||
"""
|
||||
# token = self.ding_get_access_token
|
||||
time_stamp = int(round(time.time() * 1000))
|
||||
# 时间戳
|
||||
# 通过appSecret计算出来的签名值,该参数值在HTTP请求参数中需要urlEncode(因为签名中可能包含特殊字符+)。
|
||||
signature = quote(base64.b64encode(hmac.new(
|
||||
self.mo_app_secret.encode('utf-8'),
|
||||
str(time_stamp).encode('utf-8'),
|
||||
digestmod=sha256).digest()).decode("utf-8"))
|
||||
# accessKey 是 登录开发者后台,选择应用开发 > 移动接入应用 > 登录所看到应用的appId。
|
||||
url = '{}/sns/getuserinfo_bycode?accessKey={}&signature={}×tamp={}'.format(DING_URL, self.mo_app_id, signature, time_stamp)
|
||||
resp = requests.post(
|
||||
url=url,
|
||||
json=dict(tmp_auth_code=code),
|
||||
)
|
||||
resp = resp.json()
|
||||
try:
|
||||
return True, resp["user_info"]["unionid"]
|
||||
except Exception as e:
|
||||
return False, 'ding_get_union_id_by_code: {}' .format(e)
|
||||
|
||||
def ding_get_userid_by_union_id(self, union_id):
|
||||
"""
|
||||
通过unionid获取用户的userid
|
||||
:param union_id: 用户在当前钉钉开放平台账号范围内的唯一标识
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
return True, self.ding_client_connect.user.get_userid_by_unionid(union_id)['userid']
|
||||
except Exception as e:
|
||||
return False, 'ding_get_userid_by_union_id: {}' .format(e)
|
||||
|
||||
@property
|
||||
def ding_get_org_user_count(self):
|
||||
"""
|
||||
企业员工数量
|
||||
only_active – 是否包含未激活钉钉的人员数量
|
||||
:return:
|
||||
"""
|
||||
return self.ding_client_connect.user.get_org_user_count('only_active')
|
||||
|
||||
def ding_get_userinfo_detail(self, user_id):
|
||||
"""
|
||||
通过user_id 获取用户详细信息
|
||||
user_id – 用户ID
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
return True, self.ding_client_connect.user.get(user_id)
|
||||
except Exception as e:
|
||||
return False, 'ding_get_userinfo_detail: {}' .format(e)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
start = time.time()
|
||||
d = DingDingOps()
|
||||
print(d.ding_get_access_token)
|
||||
# print(d.user.getuserinfo('2ecebee187863a8ea2863a7a2fa17b49'))
|
||||
end = time.time()
|
||||
print("running:" + str(round((end - start), 3)))
|
|
@ -0,0 +1,21 @@
|
|||
"""
|
||||
Created on 2018-9-17
|
||||
|
||||
@author: xiaoxuan.lp
|
||||
"""
|
||||
|
||||
|
||||
class appinfo(object):
|
||||
def __init__(self, appkey, secret):
|
||||
self.appkey = appkey
|
||||
self.secret = secret
|
||||
|
||||
|
||||
def getDefaultAppInfo():
|
||||
pass
|
||||
|
||||
|
||||
def setDefaultAppInfo(appkey, secret):
|
||||
default = appinfo(appkey, secret)
|
||||
global getDefaultAppInfo
|
||||
getDefaultAppInfo = lambda: default
|
|
@ -0,0 +1,2 @@
|
|||
from api.rest import *
|
||||
from api.base import FileItem
|
|
@ -0,0 +1,332 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2018-9-17
|
||||
|
||||
@author: xiaoxuan.lp
|
||||
"""
|
||||
|
||||
try:
|
||||
import http.client
|
||||
except ImportError:
|
||||
import http.client as httplib
|
||||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
import itertools
|
||||
import json
|
||||
import mimetypes
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
'''
|
||||
定义一些系统变量
|
||||
'''
|
||||
|
||||
SYSTEM_GENERATE_VERSION = "taobao-sdk-python-dynamicVersionNo"
|
||||
|
||||
P_APPKEY = "app_key"
|
||||
P_API = "method"
|
||||
P_ACCESS_TOKEN = "access_token"
|
||||
P_VERSION = "v"
|
||||
P_FORMAT = "format"
|
||||
P_TIMESTAMP = "timestamp"
|
||||
P_SIGN = "sign"
|
||||
P_SIGN_METHOD = "sign_method"
|
||||
P_PARTNER_ID = "partner_id"
|
||||
|
||||
P_CODE = 'errcode'
|
||||
P_MSG = 'errmsg'
|
||||
|
||||
|
||||
def sign(secret, parameters):
|
||||
# ===========================================================================
|
||||
# '''签名方法
|
||||
# @param secret: 签名需要的密钥
|
||||
# @param parameters: 支持字典和string两种
|
||||
# '''
|
||||
# ===========================================================================
|
||||
# 如果parameters 是字典类的话
|
||||
if hasattr(parameters, "items"):
|
||||
keys = list(parameters.keys())
|
||||
keys.sort()
|
||||
|
||||
parameters = "%s%s%s" % (secret,
|
||||
str().join('%s%s' % (key, parameters[key]) for key in keys),
|
||||
secret)
|
||||
sign = hashlib.md5(parameters.encode("utf-8")).hexdigest().upper()
|
||||
return sign
|
||||
|
||||
|
||||
def mixStr(pstr):
|
||||
if isinstance(pstr, str):
|
||||
return pstr
|
||||
elif isinstance(pstr, str):
|
||||
return pstr.encode('utf-8')
|
||||
else:
|
||||
return str(pstr)
|
||||
|
||||
|
||||
class FileItem(object):
|
||||
def __init__(self, filename=None, content=None):
|
||||
self.filename = filename
|
||||
self.content = content
|
||||
|
||||
|
||||
class MultiPartForm(object):
|
||||
"""Accumulate the data to be used when posting a form."""
|
||||
|
||||
def __init__(self):
|
||||
self.form_fields = []
|
||||
self.files = []
|
||||
self.boundary = "PYTHON_SDK_BOUNDARY"
|
||||
return
|
||||
|
||||
def get_content_type(self):
|
||||
return 'multipart/form-data;charset=UTF-8; boundary=%s' % self.boundary
|
||||
|
||||
def add_field(self, name, value):
|
||||
"""Add a simple field to the form data."""
|
||||
self.form_fields.append((name, str(value)))
|
||||
return
|
||||
|
||||
def add_file(self, fieldname, filename, fileHandle, mimetype=None):
|
||||
"""Add a file to be uploaded."""
|
||||
body = fileHandle.read()
|
||||
if mimetype is None:
|
||||
mimetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
|
||||
self.files.append((mixStr(fieldname), mixStr(filename), mixStr(mimetype), mixStr(body)))
|
||||
return
|
||||
|
||||
def __str__(self):
|
||||
"""Return a string representing the form data, including attached files."""
|
||||
# Build a list of lists, each containing "lines" of the
|
||||
# request. Each part is separated by a boundary string.
|
||||
# Once the list is built, return a string where each
|
||||
# line is separated by '\r\n'.
|
||||
parts = []
|
||||
part_boundary = '--' + self.boundary
|
||||
|
||||
# Add the form fields
|
||||
parts.extend(
|
||||
[part_boundary,
|
||||
'Content-Disposition: form-data; name="%s"' % name,
|
||||
'Content-Type: text/plain; charset=UTF-8',
|
||||
'',
|
||||
value,
|
||||
]
|
||||
for name, value in self.form_fields
|
||||
)
|
||||
|
||||
# Add the files to upload
|
||||
parts.extend(
|
||||
[part_boundary,
|
||||
'Content-Disposition: form-data; name="%s"; filename="%s"' % (field_name, filename),
|
||||
'Content-Type: %s' % content_type,
|
||||
'Content-Transfer-Encoding: binary',
|
||||
'',
|
||||
body,
|
||||
]
|
||||
for field_name, filename, content_type, body in self.files
|
||||
)
|
||||
|
||||
# Flatten the list and add closing boundary marker,
|
||||
# then return CR+LF separated data
|
||||
flattened = list(itertools.chain(*parts))
|
||||
flattened.append('--' + self.boundary + '--')
|
||||
flattened.append('')
|
||||
return '\r\n'.join(flattened)
|
||||
|
||||
|
||||
class TopException(Exception):
|
||||
# ===========================================================================
|
||||
# 业务异常类
|
||||
# ===========================================================================
|
||||
def __init__(self):
|
||||
self.errcode = None
|
||||
self.errmsg = None
|
||||
self.application_host = None
|
||||
self.service_host = None
|
||||
|
||||
def __str__(self, *args, **kwargs):
|
||||
sb = "errcode=" + mixStr(self.errcode) + \
|
||||
" errmsg=" + mixStr(self.errmsg) + \
|
||||
" application_host=" + mixStr(self.application_host) + \
|
||||
" service_host=" + mixStr(self.service_host)
|
||||
return sb
|
||||
|
||||
|
||||
class RequestException(Exception):
|
||||
# ===========================================================================
|
||||
# 请求连接异常类
|
||||
# ===========================================================================
|
||||
pass
|
||||
|
||||
|
||||
class RestApi(object):
|
||||
# ===========================================================================
|
||||
# Rest api的基类
|
||||
# ===========================================================================
|
||||
|
||||
def __init__(self, url=None):
|
||||
# =======================================================================
|
||||
# 初始化基类
|
||||
# Args @param domain: 请求的域名或者ip
|
||||
# @param port: 请求的端口
|
||||
# =======================================================================
|
||||
if url is None:
|
||||
raise RequestException("domain must not be empty.")
|
||||
if url.find('http://') >= 0:
|
||||
self.__port = 80
|
||||
pathUrl = url.replace('http://', '')
|
||||
elif url.find('https://') >= 0:
|
||||
self.__port = 443
|
||||
pathUrl = url.replace('https://', '')
|
||||
else:
|
||||
raise RequestException("http protocol is not validate.")
|
||||
|
||||
index = pathUrl.find('/')
|
||||
if index > 0:
|
||||
self.__domain = pathUrl[0:index]
|
||||
self.__path = pathUrl[index:]
|
||||
else:
|
||||
self.__domain = pathUrl
|
||||
self.__path = ''
|
||||
|
||||
# print("domain:" + self.__domain + ",path:" + self.__path + ",port:" + str(self.__port))
|
||||
|
||||
def get_request_header(self):
|
||||
return {
|
||||
'Content-type': 'application/json;charset=UTF-8',
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "Keep-Alive",
|
||||
}
|
||||
|
||||
def getHttpMethod(self):
|
||||
return "GET"
|
||||
|
||||
def getapiname(self):
|
||||
return ""
|
||||
|
||||
def getMultipartParas(self):
|
||||
return []
|
||||
|
||||
def getTranslateParas(self):
|
||||
return {}
|
||||
|
||||
def _check_requst(self):
|
||||
pass
|
||||
|
||||
def getResponse(self, authrize='', accessKey='', accessSecret='', suiteTicket='', corpId='', timeout=30):
|
||||
# =======================================================================
|
||||
# 获取response结果
|
||||
# =======================================================================
|
||||
if self.__port == 443:
|
||||
connection = http.client.HTTPSConnection(self.__domain, self.__port, None, None, timeout)
|
||||
else:
|
||||
connection = http.client.HTTPConnection(self.__domain, self.__port, timeout)
|
||||
sys_parameters = {
|
||||
P_PARTNER_ID: SYSTEM_GENERATE_VERSION,
|
||||
}
|
||||
if authrize is not None:
|
||||
sys_parameters[P_ACCESS_TOKEN] = authrize
|
||||
application_parameter = self.getApplicationParameters()
|
||||
sign_parameter = sys_parameters.copy()
|
||||
sign_parameter.update(application_parameter)
|
||||
|
||||
header = self.get_request_header()
|
||||
if self.getMultipartParas():
|
||||
form = MultiPartForm()
|
||||
for key, value in list(application_parameter.items()):
|
||||
form.add_field(key, value)
|
||||
for key in self.getMultipartParas():
|
||||
fileitem = getattr(self, key)
|
||||
if fileitem and isinstance(fileitem, FileItem):
|
||||
form.add_file(key, fileitem.filename, fileitem.content)
|
||||
body = str(form)
|
||||
header['Content-type'] = form.get_content_type()
|
||||
else:
|
||||
body = urllib.parse.urlencode(application_parameter)
|
||||
|
||||
if accessKey != '':
|
||||
timestamp = str(int(round(time.time()))) + '000'
|
||||
print(("timestamp:" + timestamp))
|
||||
canonicalString = self.getCanonicalStringForIsv(timestamp, suiteTicket)
|
||||
print(("canonicalString:" + canonicalString))
|
||||
print(("accessSecret:" + accessSecret))
|
||||
signature = self.computeSignature(accessSecret, canonicalString)
|
||||
print(("signature:" + signature))
|
||||
ps = {}
|
||||
ps["accessKey"] = accessKey
|
||||
ps["signature"] = signature
|
||||
ps["timestamp"] = timestamp
|
||||
if suiteTicket != '':
|
||||
ps["suiteTicket"] = suiteTicket
|
||||
if corpId != '':
|
||||
ps["corpId"] = corpId
|
||||
queryStr = urllib.parse.urlencode(ps)
|
||||
if self.__path.find("?") > 0:
|
||||
fullPath = self.__path + "&" + queryStr
|
||||
else:
|
||||
fullPath = self.__path + "?" + queryStr
|
||||
print(("fullPath:" + fullPath))
|
||||
else:
|
||||
if self.__path.find("?") > 0:
|
||||
fullPath = (self.__path + "&access_token=" + str(authrize)) if len(str(authrize)) > 0 else self.__path
|
||||
else:
|
||||
fullPath = (self.__path + "?access_token=" + str(authrize)) if len(str(authrize)) > 0 else self.__path
|
||||
|
||||
if self.getHttpMethod() == "GET":
|
||||
if fullPath.find("?") > 0:
|
||||
fullPath = fullPath + "&" + body
|
||||
else:
|
||||
fullPath = fullPath + "?" + body
|
||||
connection.request(self.getHttpMethod(), fullPath, headers=header)
|
||||
else:
|
||||
if self.getMultipartParas():
|
||||
body = body
|
||||
else:
|
||||
body = json.dumps(application_parameter)
|
||||
connection.request(self.getHttpMethod(), fullPath, body=body, headers=header)
|
||||
response = connection.getresponse()
|
||||
if response.status != 200:
|
||||
raise RequestException('invalid http status ' + str(response.status) + ',detail body:' + str(response.read()))
|
||||
result = response.read()
|
||||
# print("result:" + result)
|
||||
jsonobj = json.loads(result)
|
||||
if P_CODE in jsonobj and jsonobj[P_CODE] != 0:
|
||||
error = TopException()
|
||||
error.errcode = jsonobj[P_CODE]
|
||||
error.errmsg = jsonobj[P_MSG]
|
||||
error.application_host = response.getheader("Application-Host", "")
|
||||
error.service_host = response.getheader("Location-Host", "")
|
||||
raise error
|
||||
return jsonobj
|
||||
|
||||
def getCanonicalStringForIsv(self, timestamp, suiteTicket):
|
||||
if suiteTicket != '':
|
||||
return timestamp + '\n' + suiteTicket
|
||||
else:
|
||||
return timestamp
|
||||
|
||||
def computeSignature(self, secret, canonicalString):
|
||||
message = canonicalString.encode(encoding="utf-8")
|
||||
sec = secret.encode(encoding="utf-8")
|
||||
return str(base64.b64encode(hmac.new(sec, message, digestmod=hashlib.sha256).digest()))
|
||||
|
||||
def getApplicationParameters(self):
|
||||
application_parameter = {}
|
||||
for key, value in self.__dict__.items():
|
||||
if not key.startswith("__") and not key in self.getMultipartParas() and not key.startswith("_RestApi__") and value is not None:
|
||||
if key.startswith("_"):
|
||||
application_parameter[key[1:]] = value
|
||||
else:
|
||||
application_parameter[key] = value
|
||||
# 查询翻译字典来规避一些关键字属性
|
||||
translate_parameter = self.getTranslateParas()
|
||||
for key, value in application_parameter.items():
|
||||
if key in translate_parameter:
|
||||
application_parameter[translate_parameter[key]] = application_parameter[key]
|
||||
del application_parameter[key]
|
||||
return application_parameter
|
|
@ -0,0 +1,17 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CcoserviceServicegroupAddmemberRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.open_group_id = None
|
||||
self.userid = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.ccoservice.servicegroup.addmember'
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CcoserviceServicegroupGetRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.open_group_id = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.ccoservice.servicegroup.get'
|
|
@ -0,0 +1,15 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpBlazersGetbinddataRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.blazers.getbinddata'
|
|
@ -0,0 +1,15 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpBlazersGetbizidRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.blazers.getbizid'
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Created by auto_sdk on 2018.07.25
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpBlazersRemovemappingRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.biz_id = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.blazers.removemapping'
|
|
@ -0,0 +1,15 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpBlazersUnbindRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.blazers.unbind'
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpCalendarCreateRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.create_vo = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.calendar.create'
|
|
@ -0,0 +1,19 @@
|
|||
"""
|
||||
Created by auto_sdk on 2020.09.18
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpChatbotAddchatbotinstanceRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.chatbot_id = None
|
||||
self.icon_media_id = None
|
||||
self.name = None
|
||||
self.open_conversation_id = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.chatbot.addchatbotinstance'
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Created by auto_sdk on 2018.07.25
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpChatbotCreateorgbotRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.create_chat_bot_model = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.chatbot.createorgbot'
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Created by auto_sdk on 2020.08.17
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpChatbotInstallRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.chatbot_vo = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.chatbot.install'
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Created by auto_sdk on 2020.09.18
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpChatbotListbychatbotidsRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.chatbot_ids = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.chatbot.listbychatbotids'
|
|
@ -0,0 +1,17 @@
|
|||
"""
|
||||
Created by auto_sdk on 2018.07.25
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpChatbotListorgbotRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.agent_id = None
|
||||
self.type = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.chatbot.listorgbot'
|
|
@ -0,0 +1,17 @@
|
|||
"""
|
||||
Created by auto_sdk on 2020.09.18
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpChatbotListorgbotbytypeandbottypeRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.bot_type = None
|
||||
self.type = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.chatbot.listorgbotbytypeandbottype'
|
|
@ -0,0 +1,22 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpChatbotUpdatebychatbotidRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.breif = None
|
||||
self.chatbot_id = None
|
||||
self.description = None
|
||||
self.icon = None
|
||||
self.name = None
|
||||
self.preview_media_id = None
|
||||
self.update_type = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.chatbot.updatebychatbotid'
|
|
@ -0,0 +1,18 @@
|
|||
"""
|
||||
Created by auto_sdk on 2018.07.25
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpChatbotUpdateorgbotRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.chatbot_id = None
|
||||
self.icon = None
|
||||
self.name = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.chatbot.updateorgbot'
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Created by auto_sdk on 2020.09.21
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpConversationCorpconversionGetconversationRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.open_conversation_id = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.conversation.corpconversion.getconversation'
|
|
@ -0,0 +1,18 @@
|
|||
"""
|
||||
Created by auto_sdk on 2020.09.21
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpConversationCorpconversionListmemberRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.count = None
|
||||
self.offset = None
|
||||
self.open_conversation_id = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.conversation.corpconversion.listmember'
|
|
@ -0,0 +1,17 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpDeptgroupSyncuserRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.dept_id = None
|
||||
self.userid = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.deptgroup.syncuser'
|
|
@ -0,0 +1,17 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpDeviceManageGetRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.device_id = None
|
||||
self.device_service_id = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.device.manage.get'
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpDeviceManageHasbinddeviceRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.device_service_id = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.device.manage.hasbinddevice'
|
|
@ -0,0 +1,18 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.08.14
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpDeviceManageQuerylistRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.cursor = None
|
||||
self.device_service_id = None
|
||||
self.size = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.device.manage.querylist'
|
|
@ -0,0 +1,17 @@
|
|||
"""
|
||||
Created by auto_sdk on 2018.07.25
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpDeviceManageUnbindRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.device_id = None
|
||||
self.device_service_id = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.device.manage.unbind'
|
|
@ -0,0 +1,18 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpDeviceNickUpdateRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.device_id = None
|
||||
self.device_service_id = None
|
||||
self.new_nick = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.device.nick.update'
|
|
@ -0,0 +1,21 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpDingCreateRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.attachment = None
|
||||
self.creator_userid = None
|
||||
self.receiver_userids = None
|
||||
self.remind_time = None
|
||||
self.remind_type = None
|
||||
self.text_content = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.ding.create'
|
|
@ -0,0 +1,19 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpDingReceiverstatusListRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.confirmed_status = None
|
||||
self.ding_id = None
|
||||
self.page_no = None
|
||||
self.page_size = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.ding.receiverstatus.list'
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Created by auto_sdk on 2018.07.25
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpDingTaskCreateRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.task_send_v_o = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.ding.task.create'
|
|
@ -0,0 +1,18 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpEmpSearchRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.keyword = None
|
||||
self.offset = None
|
||||
self.size = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.emp.search'
|
|
@ -0,0 +1,15 @@
|
|||
"""
|
||||
Created by auto_sdk on 2018.07.25
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpEncryptionKeyListRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.encryption.key.list'
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpExtAddRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.contact = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.ext.add'
|
|
@ -0,0 +1,17 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpExtListRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.offset = None
|
||||
self.size = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.ext.list'
|
|
@ -0,0 +1,17 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpExtListlabelgroupsRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.offset = None
|
||||
self.size = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.ext.listlabelgroups'
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpExtUpdateRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.contact = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.ext.update'
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpExtcontactCreateRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.contact = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.extcontact.create'
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Created by auto_sdk on 2018.07.25
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpExtcontactDeleteRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.userid = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.extcontact.delete'
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpExtcontactGetRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.user_id = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.extcontact.get'
|
|
@ -0,0 +1,17 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpExtcontactListRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.offset = None
|
||||
self.size = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.extcontact.list'
|
|
@ -0,0 +1,17 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpExtcontactListlabelgroupsRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.offset = None
|
||||
self.size = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.extcontact.listlabelgroups'
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Created by auto_sdk on 2018.07.25
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpExtcontactUpdateRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.contact = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.extcontact.update'
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpHealthStepinfoGetuserstatusRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.userid = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.health.stepinfo.getuserstatus'
|
|
@ -0,0 +1,18 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpHealthStepinfoListRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.object_id = None
|
||||
self.stat_dates = None
|
||||
self.type = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.health.stepinfo.list'
|
|
@ -0,0 +1,17 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpHealthStepinfoListbyuseridRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.stat_date = None
|
||||
self.userids = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.health.stepinfo.listbyuserid'
|
|
@ -0,0 +1,23 @@
|
|||
"""
|
||||
Created by auto_sdk on 2018.07.25
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpHrmEmployeeAddresumerecordRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.content = None
|
||||
self.k_v_content = None
|
||||
self.pc_url = None
|
||||
self.phone_url = None
|
||||
self.record_time_stamp = None
|
||||
self.title = None
|
||||
self.userid = None
|
||||
self.web_url = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.hrm.employee.addresumerecord'
|
|
@ -0,0 +1,17 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpHrmEmployeeDelemployeedismissionandhandoverRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.dismission_info_with_hand_over = None
|
||||
self.op_userid = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.hrm.employee.delemployeedismissionandhandover'
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpHrmEmployeeGetRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.userid = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.hrm.employee.get'
|
|
@ -0,0 +1,18 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpHrmEmployeeGetdismissionlistRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.current = None
|
||||
self.op_userid = None
|
||||
self.page_size = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.hrm.employee.getdismissionlist'
|
|
@ -0,0 +1,17 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpHrmEmployeeModjobinfoRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.hrm_api_job_model = None
|
||||
self.op_userid = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.hrm.employee.modjobinfo'
|
|
@ -0,0 +1,17 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpHrmEmployeeSetuserworkdataRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.hrm_api_user_data_model = None
|
||||
self.op_userid = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.hrm.employee.setuserworkdata'
|
|
@ -0,0 +1,15 @@
|
|||
"""
|
||||
Created by auto_sdk on 2018.07.25
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpInvoiceGettitleRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.invoice.gettitle'
|
|
@ -0,0 +1,15 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpLivenessGetRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.liveness.get'
|
|
@ -0,0 +1,21 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpMessageCorpconversationAsyncsendRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.agent_id = None
|
||||
self.dept_id_list = None
|
||||
self.msgcontent = None
|
||||
self.msgtype = None
|
||||
self.to_all_user = None
|
||||
self.userid_list = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.message.corpconversation.asyncsend'
|
|
@ -0,0 +1,22 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpMessageCorpconversationAsyncsendbycodeRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.agent_id = None
|
||||
self.code = None
|
||||
self.dept_id_list = None
|
||||
self.msgcontent = None
|
||||
self.msgtype = None
|
||||
self.to_all_user = None
|
||||
self.user_id_list = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.message.corpconversation.asyncsendbycode'
|
|
@ -0,0 +1,17 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpMessageCorpconversationGetsendprogressRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.agent_id = None
|
||||
self.task_id = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.message.corpconversation.getsendprogress'
|
|
@ -0,0 +1,17 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpMessageCorpconversationGetsendresultRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.agent_id = None
|
||||
self.task_id = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.message.corpconversation.getsendresult'
|
|
@ -0,0 +1,20 @@
|
|||
"""
|
||||
Created by auto_sdk on 2018.07.25
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpMessageCorpconversationSendmockRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.message = None
|
||||
self.message_type = None
|
||||
self.microapp_agent_id = None
|
||||
self.to_party = None
|
||||
self.to_user = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.message.corpconversation.sendmock'
|
|
@ -0,0 +1,21 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpReportListRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.cursor = None
|
||||
self.end_time = None
|
||||
self.size = None
|
||||
self.start_time = None
|
||||
self.template_name = None
|
||||
self.userid = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.report.list'
|
|
@ -0,0 +1,17 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpRoleAddrolesforempsRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.rolelid_list = None
|
||||
self.userid_list = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.role.addrolesforemps'
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpRoleDeleteroleRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.role_id = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.role.deleterole'
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpRoleGetrolegroupRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.group_id = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.role.getrolegroup'
|
|
@ -0,0 +1,17 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpRoleListRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.offset = None
|
||||
self.size = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.role.list'
|
|
@ -0,0 +1,17 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpRoleRemoverolesforempsRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.roleid_list = None
|
||||
self.userid_list = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.role.removerolesforemps'
|
|
@ -0,0 +1,18 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpRoleSimplelistRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.offset = None
|
||||
self.role_id = None
|
||||
self.size = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.role.simplelist'
|
|
@ -0,0 +1,18 @@
|
|||
"""
|
||||
Created by auto_sdk on 2019.07.03
|
||||
"""
|
||||
from api.base import RestApi
|
||||
|
||||
|
||||
class CorpSearchCorpcontactBaseinfoRequest(RestApi):
|
||||
def __init__(self, url=None):
|
||||
RestApi.__init__(self, url)
|
||||
self.offset = None
|
||||
self.query = None
|
||||
self.size = None
|
||||
|
||||
def getHttpMethod(self):
|
||||
return 'POST'
|
||||
|
||||
def getapiname(self):
|
||||
return 'dingtalk.corp.search.corpcontact.baseinfo'
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue