### 本次升级、修复,请使用最新版:
+ 升级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
|
##install python3
|
||||||
##如果之前用此脚本安装过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
|
then
|
||||||
echo "己发现Python3,将不会安装。"
|
echo "己发现Python3,将不会安装。"
|
||||||
else
|
else
|
||||||
if [[ -f "Python-3.6.9.tar.xz" ]]
|
if [[ -f "Python-${python_ver}.tar.xz" ]]
|
||||||
then
|
then
|
||||||
echo "将安装Python3.6.9"
|
echo "将安装Python${python_ver}"
|
||||||
tar xf Python-3.6.9.tar.xz
|
tar xf Python-${python_ver}.tar.xz
|
||||||
cd Python-3.6.9
|
cd Python-${python_ver}
|
||||||
sudo ./configure --prefix=/usr/share/python-3.6.9 && make && make install
|
sudo ./configure --prefix=/usr/share/python-${python_ver} && make && make install
|
||||||
else
|
else
|
||||||
echo "脚本目录下没有发现Python3.6.9.tar.xz,将会下载python 3.6.9"
|
echo "脚本目录下没有发现Python${python_ver}.tar.xz,将会下载python ${python_ver}"
|
||||||
sudo wget https://www.python.org/ftp/python/3.6.9/Python-3.6.9.tar.xz
|
sudo wget https://www.python.org/ftp/python/${python_ver}/Python-${python_ver}.tar.xz
|
||||||
tar xf Python-3.6.9.tar.xz
|
tar xf Python-${python_ver}.tar.xz
|
||||||
cd Python-3.6.9
|
cd Python-${python_ver}
|
||||||
sudo ./configure --prefix=/usr/share/python-3.6.9 && make && make install
|
sudo ./configure --prefix=/usr/share/python-${python_ver} && make && make install
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $? -eq 0 ]]
|
if [[ $? -eq 0 ]]
|
||||||
then
|
then
|
||||||
echo "创建python3和pip3的软件链接"
|
echo "创建python3和pip3的软件链接"
|
||||||
cd ${SHELL_FOLDER}
|
cd ${SHELL_FOLDER}
|
||||||
sudo ln -svf /usr/share/python-3.6.9/bin/python3 /usr/bin/python3
|
sudo ln -svf /usr/share/python-${python_ver}/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/pip3 /usr/bin/pip3
|
||||||
echo "======================================================================="
|
echo "======================================================================="
|
||||||
echo "Python3 安装成功!"
|
echo "Python3 安装成功!"
|
||||||
echo "======================================================================="
|
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
|
#!/usr/bin/env python
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from utils.ad_ops import AdOps
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pwdselfservice.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pwdselfservice.settings')
|
||||||
|
@ -12,4 +13,11 @@ if __name__ == '__main__':
|
||||||
"available on your PYTHONPATH environment variable? Did you "
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
"forget to activate a virtual environment?"
|
"forget to activate a virtual environment?"
|
||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
|
try:
|
||||||
|
AdOps()
|
||||||
|
except Exception as e:
|
||||||
|
print(str(e))
|
||||||
|
print("未能连接到AD,先决条件未满足,Django不会运行..")
|
||||||
|
sys.exit(1)
|
||||||
execute_from_command_line(sys.argv)
|
execute_from_command_line(sys.argv)
|
||||||
|
|
|
@ -1,32 +1,42 @@
|
||||||
# AD配置,修改为自己的
|
# ########## AD配置,修改为自己的
|
||||||
# AD主机,可以是IP或主机域名,例如可以是:abc.com或172.16.122.1
|
# AD主机,可以是IP或主机域名,例如可以是: abc.com或172.16.122.1
|
||||||
AD_HOST = '修改为自己的'
|
AD_HOST = r'修改成自己的'
|
||||||
|
|
||||||
# 用于登录AD做用户信息验证的账号, 需要有修改用户账号密码的权限。
|
# AD域控的DOMAIN名,例如:abc、abc.com
|
||||||
# 账号格式使用DOMAIN\USERNAME,例如:abc\pwdadmin
|
AD_DOMAIN = r'修改成自己的'
|
||||||
AD_LOGIN_USER = '修改为自己的'
|
|
||||||
|
|
||||||
|
# 用于登录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,账号的查找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 = '修改为自己的'
|
DING_CORP_ID = '修改为自己的'
|
||||||
|
|
||||||
# 钉钉E应用,修改为自己的
|
# 钉钉企业内部开发,内部H5微应用或小程序,用于读取企业内部用户信息
|
||||||
DING_AGENT_ID = '修改为自己的'
|
DING_AGENT_ID = r'修改为自己的'
|
||||||
DING_APP_KEY = '修改为自己的'
|
DING_APP_KEY = r'修改为自己的'
|
||||||
DING_APP_SECRET = '修改为自己的'
|
DING_APP_SECRET = r'修改为自己的'
|
||||||
|
|
||||||
# 钉钉移动应用接入,修改为自己的
|
# 移动应用接入 主要为了实现通过扫码拿到用户的unioid
|
||||||
DING_SELF_APP_ID = '修改为自己的'
|
DING_MO_APP_ID = r'修改为自己的'
|
||||||
DING_SELF_APP_SECRET = '修改为自己的'
|
DING_MO_APP_SECRET = r'修改为自己的'
|
||||||
|
|
||||||
# 执行:python3 ./resetpwd/utils/crypto.py 生成
|
# 执行:python3 ./resetpwd/utils/crypto.py 生成
|
||||||
# 可自行生成后替换
|
# 可自行生成后替换
|
||||||
|
@ -35,6 +45,5 @@ CRYPTO_KEY = b'dp8U9y7NAhCD3MoNwPzPBhBtTZ1uI_WWSdpNs6wUDgs='
|
||||||
# COOKIE 超时单位是秒,可不用修改
|
# COOKIE 超时单位是秒,可不用修改
|
||||||
TMPID_COOKIE_AGE = 300
|
TMPID_COOKIE_AGE = 300
|
||||||
|
|
||||||
|
# 主页域名,钉钉跳转等需要指定域名,格式:pwd.abc.com。
|
||||||
# 主页域名,index.html中的钉钉跳转等需要指定域名。
|
|
||||||
HOME_URL = 'PWD_SELF_SERVICE_DOMAIN'
|
HOME_URL = 'PWD_SELF_SERVICE_DOMAIN'
|
|
@ -15,15 +15,11 @@ import os
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
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!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
SECRET_KEY = 'nxnm3#&2tat_c2i6%$y74a)t$(3irh^gpwaleoja1kdv30fmcm'
|
SECRET_KEY = 'nxnm3#&2tat_c2i6%$y74a)t$(3irh^gpwaleoja1kdv30fmcm'
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = False
|
DEBUG = True
|
||||||
|
|
||||||
ALLOWED_HOSTS = ['*']
|
ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
|
@ -35,11 +31,12 @@ if not os.path.isdir(LOG_PATH):
|
||||||
|
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
'version': 1,
|
'version': 1,
|
||||||
'disable_existing_loggers': False,#此选项开启表示禁用部分日志,不建议设置为True
|
# 此选项开启表示禁用部分日志,不建议设置为True
|
||||||
|
'disable_existing_loggers': False,
|
||||||
'formatters': {
|
'formatters': {
|
||||||
'verbose': {
|
'verbose': {
|
||||||
'format': '%(asctime)s %(levelname)s %(pathname)s %(module)s.%(funcName)s %(lineno)d: %(message)s'
|
'format': '%(asctime)s %(levelname)s %(pathname)s %(module)s.%(funcName)s %(lineno)d: %(message)s'
|
||||||
#日志格式
|
# 日志格式
|
||||||
},
|
},
|
||||||
'simple': {
|
'simple': {
|
||||||
'format': '%(asctime)s %(levelname)s %(pathname)s %(module)s.%(funcName)s %(lineno)d: %(message)s'
|
'format': '%(asctime)s %(levelname)s %(pathname)s %(module)s.%(funcName)s %(lineno)d: %(message)s'
|
||||||
|
@ -47,7 +44,8 @@ LOGGING = {
|
||||||
},
|
},
|
||||||
'filters': {
|
'filters': {
|
||||||
'require_debug_true': {
|
'require_debug_true': {
|
||||||
'()': 'django.utils.log.RequireDebugTrue',#过滤器,只有当setting的DEBUG = True时生效
|
# 过滤器,只有当setting的DEBUG = True时生效
|
||||||
|
'()': 'django.utils.log.RequireDebugTrue',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'handlers': {
|
'handlers': {
|
||||||
|
@ -57,15 +55,18 @@ LOGGING = {
|
||||||
'class': 'logging.StreamHandler',
|
'class': 'logging.StreamHandler',
|
||||||
'formatter': 'verbose'
|
'formatter': 'verbose'
|
||||||
},
|
},
|
||||||
'file': {#重点配置部分
|
'file': {
|
||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'class': 'logging.FileHandler',
|
'class': 'logging.FileHandler',
|
||||||
'filename': '%s/log.log' % LOG_PATH,#日志保存文件
|
# 日志保存文件
|
||||||
'formatter': 'verbose'#日志格式,与上边的设置对应选择
|
'filename': '%s/log.log' % LOG_PATH,
|
||||||
|
# 日志格式,与上边的设置对应选择
|
||||||
|
'formatter': 'verbose'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'loggers': {
|
'loggers': {
|
||||||
'django': {#日志记录器
|
'django': {
|
||||||
|
# 日志记录器
|
||||||
'handlers': ['file'],
|
'handlers': ['file'],
|
||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'propagate': True,
|
'propagate': True,
|
||||||
|
@ -85,8 +86,6 @@ SESSION_EXPIRE_AT_BROWSER_CLOSE = True
|
||||||
# SESSION_COOKIE_HTTPONLY= True
|
# SESSION_COOKIE_HTTPONLY= True
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
# 'django.contrib.admin',
|
# 'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'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 = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
{
|
{
|
||||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
'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'
|
LANGUAGE_CODE = 'zh-hans'
|
||||||
|
|
||||||
TIME_ZONE = 'Asia/Shanghai'
|
TIME_ZONE = 'Asia/Shanghai'
|
||||||
|
@ -170,15 +162,10 @@ USE_I18N = True
|
||||||
|
|
||||||
USE_L10N = True
|
USE_L10N = True
|
||||||
|
|
||||||
USE_TZ = False
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
# 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__)))
|
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
STATIC_ROOT = 'static'
|
# STATIC_ROOT = 'static'
|
||||||
|
|
||||||
STATICFILES_DIRS = [
|
STATICFILES_DIRS = [
|
||||||
os.path.join(BASE_DIR, 'static'),
|
os.path.join(BASE_DIR, 'static'),
|
||||||
|
|
|
@ -4,9 +4,9 @@ import resetpwd.views
|
||||||
|
|
||||||
urlpatterns = {
|
urlpatterns = {
|
||||||
path("favicon.ico", RedirectView.as_view(url='static/img/favicon.ico')),
|
path("favicon.ico", RedirectView.as_view(url='static/img/favicon.ico')),
|
||||||
path('', resetpwd.views.resetpwd_index, name='index'),
|
path('', resetpwd.views.index, name='index'),
|
||||||
path('resetcheck', resetpwd.views.resetpwd_check_userinfo, name='resetcheck'),
|
path('callbackCheck', resetpwd.views.callback_check, name='callbackCheck'),
|
||||||
path('resetpwd', resetpwd.views.resetpwd_reset, name='resetpwd'),
|
path('resetPassword', resetpwd.views.reset_pwd_by_ding_callback, name='resetPassword'),
|
||||||
path('resetunlock', resetpwd.views.resetpwd_unlock, name='resetunlock'),
|
path('unlockAccount', resetpwd.views.unlock_account, name='unlockAccount'),
|
||||||
path('resetmsg', resetpwd.views.reset_msg, name='resetmsg'),
|
path('messages', resetpwd.views.messages, name='messages'),
|
||||||
}
|
}
|
||||||
|
|
135
readme.md
135
readme.md
|
@ -1,57 +1,61 @@
|
||||||
# 初学Django时碰到的一个需求,因为公司中很多员工在修改密码之后,有一些关联的客户端或网页中的旧密码没有更新,导致密码在尝试多次之后账号被锁,为了减少这种让人头疼的重置解锁密码的操蛋工作,自己做了一个自助修改小平台。
|
### 初学Django时碰到的一个需求,因为公司中很多员工在修改密码之后,有一些关联的客户端或网页中的旧密码没有更新,导致密码在尝试多次之后账号被锁,为了减少这种让人头疼的重置解锁密码的操蛋工作,自己做了一个自助修改小平台。
|
||||||
## 水平有限,代码写得不好,但是能用,有需要的可以直接拿去用。
|
### 水平有限,代码写得不好,但是能用,有需要的可以直接拿去用。
|
||||||
#### 场景说明:
|
#### 场景说明:
|
||||||
因为本公司AD是早期已经在用,用户的个人信息不是十分全面,例如:用户手机号。
|
因为本公司AD是早期已经在用,用户的个人信息不是十分全面,例如:用户手机号。
|
||||||
钉钉是后来才开始使用,钉钉默认是使用手机号登录。
|
钉钉是后来才开始使用,钉钉默认是使用手机号登录。
|
||||||
这样就造成如果通过手机号来进行钉钉与AD之间的验证视乎行不通。
|
用户自行重置密码时如果通过手机号来进行钉钉与AD之间的验证就行不通了。
|
||||||
在这里我就使用了通过扫码后,提取钉钉账号的邮箱信息,再将邮箱在AD中进行比对来验证用户(邮箱)是否同时在企业的钉钉和企业AD中同时存在,并账号状态是激活的。
|
|
||||||
|
### 新版本逻辑:
|
||||||
|
>用户扫码通过之后,通过临时授权码,提取用户的unionid,再通过unionid判断用户在本企业中是否存在。如果存在,提取用户钉钉账号的邮箱,通过邮箱转成账号,将账号拿到AD中进行比对来验证账号在AD中是否存在并账号状态是激活的。满足以上条件的账号就会视为可自行重置密码。
|
||||||
|
|
||||||
此处的配置可按自己的实际情况修改。
|
|
||||||
|
|
||||||
整个验证逻辑写在resetpwd/views.py
|
### 提示:
|
||||||
|
```
|
||||||
提示:
|
AD必须使用SSL才能修改密码(这里被坑了N久...)
|
||||||
如果使用中提示无法连接到域控,可以修改下resetpwd\utils\ad.py文件:
|
自行部署下AD的证书服务,并颁发CA证书,重启服务器生效。
|
||||||
```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')
|
|
||||||
```
|
```
|
||||||
|
|
||||||
把上面代码中的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一定要配置,否则无法返回接口数据。
|
* 应用安全域名和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
|
```shell
|
||||||
chmod +x auto-install.sh
|
chmod +x auto-install.sh
|
||||||
|
@ -113,51 +117,60 @@ HOME_URL = 'PWD_SELF_SERVICE_DOMAIN'
|
||||||
修改pwdselfservice/local_settings.py中的参数,按自己的实际参数修改
|
修改pwdselfservice/local_settings.py中的参数,按自己的实际参数修改
|
||||||
|
|
||||||
```` python
|
```` python
|
||||||
# AD配置,修改为自己的
|
# ########## AD配置,修改为自己的
|
||||||
# AD主机,可以是IP或主机域名,例如可以是:abc.com或172.16.122.1
|
# AD主机,可以是IP或主机域名,例如可以是: abc.com或172.16.122.1
|
||||||
AD_HOST = '修改为自己的'
|
AD_HOST = r'修改成自己的'
|
||||||
|
|
||||||
# 用于登录AD做用户信息验证的账号, 需要有修改用户账号密码的权限。
|
# AD域控的DOMAIN名,例如:abc、abc.com
|
||||||
# 账号格式使用DOMAIN\USERNAME,例如:abc\pwdadmin
|
AD_DOMAIN = r'修改成自己的'
|
||||||
AD_LOGIN_USER = '修改为自己的'
|
|
||||||
|
|
||||||
|
# 用于登录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,账号的查找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 = '修改为自己的'
|
DING_CORP_ID = '修改为自己的'
|
||||||
|
|
||||||
# 钉钉E应用,修改为自己的
|
# 钉钉企业内部开发,内部H5微应用或小程序,用于读取企业内部用户信息
|
||||||
DING_AGENT_ID = '修改为自己的'
|
DING_AGENT_ID = r'修改为自己的'
|
||||||
DING_APP_KEY = '修改为自己的'
|
DING_APP_KEY = r'修改为自己的'
|
||||||
DING_APP_SECRET = '修改为自己的'
|
DING_APP_SECRET = r'修改为自己的'
|
||||||
|
|
||||||
# 钉钉移动应用接入,修改为自己的
|
# 移动应用接入 主要为了实现通过扫码拿到用户的unioid
|
||||||
DING_SELF_APP_ID = '修改为自己的'
|
DING_MO_APP_ID = r'修改为自己的'
|
||||||
DING_SELF_APP_SECRET = '修改为自己的'
|
DING_MO_APP_SECRET = r'修改为自己的'
|
||||||
|
|
||||||
# Crypty key 通过generate_key生成,可不用修改
|
# 执行:python3 ./resetpwd/utils/crypto.py 生成
|
||||||
|
# 可自行生成后替换
|
||||||
CRYPTO_KEY = b'dp8U9y7NAhCD3MoNwPzPBhBtTZ1uI_WWSdpNs6wUDgs='
|
CRYPTO_KEY = b'dp8U9y7NAhCD3MoNwPzPBhBtTZ1uI_WWSdpNs6wUDgs='
|
||||||
|
|
||||||
# COOKIE 超时单位是秒,可不用修改
|
# COOKIE 超时单位是秒,可不用修改
|
||||||
TMPID_COOKIE_AGE = 300
|
TMPID_COOKIE_AGE = 300
|
||||||
|
|
||||||
|
# 主页域名,钉钉跳转等需要指定域名,格式:pwd.abc.com。
|
||||||
# 主页域名,index.html中的钉钉跳转等需要指定域名。
|
|
||||||
HOME_URL = 'PWD_SELF_SERVICE_DOMAIN'
|
HOME_URL = 'PWD_SELF_SERVICE_DOMAIN'
|
||||||
|
|
||||||
````
|
````
|
||||||
|
|
||||||
|
# 手动部署
|
||||||
### 自行安装完python3之后,使用python3目录下的pip3进行安装依赖:
|
#### 自行安装完python3之后,使用python3目录下的pip3进行安装依赖:
|
||||||
### 我自行安装的Python路径为/usr/local/python3
|
#### 我自行安装的Python路径为/usr/local/python3
|
||||||
|
|
||||||
项目目录下的requestment文件里记录了所依赖的相关python模块,安装方法:
|
项目目录下的requestment文件里记录了所依赖的相关python模块,安装方法:
|
||||||
* /usr/local/python3/bin/pip3 install -r requestment
|
* /usr/local/python3/bin/pip3 install -r requestment
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
Django==2.1.8
|
Django==3.2
|
||||||
dingtalk-sdk>=1.2.2
|
dingtalk-sdk==1.3.8
|
||||||
pycrypto>=2.6
|
cryptography==3.4.7
|
||||||
cryptography
|
ldap3==2.9
|
||||||
ldap3
|
|
||||||
requests
|
requests
|
||||||
uwsgi
|
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': '确认密码不能为空'})
|
old_password = c_fields.CharField(error_messages={'required': '确认密码不能为空'})
|
||||||
ensure_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):
|
def clean(self):
|
||||||
pwd0 = self.cleaned_data.get('old_password')
|
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,129 +1,89 @@
|
||||||
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 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')
|
logger = logging.getLogger('django')
|
||||||
|
|
||||||
|
ad_ops = AdOps()
|
||||||
|
ding_ops = DingDingOps()
|
||||||
|
|
||||||
def resetpwd_index(request):
|
|
||||||
|
def index(request):
|
||||||
"""
|
"""
|
||||||
用户修改密码
|
用户自行修改密码
|
||||||
:param request:
|
:param request:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
home_url = '%s://%s' % (request.scheme, HOME_URL)
|
home_url = '%s://%s' % (request.scheme, HOME_URL)
|
||||||
app_id = DING_SELF_APP_ID
|
app_id = DING_MO_APP_ID
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
return render(request, 'index.html', locals())
|
return render(request, 'index.html', locals())
|
||||||
else:
|
else:
|
||||||
logger.error('[异常] 请求方法:%s,请求路径:%s' % (request.method, request.path))
|
logger.error('[异常] 请求方法:%s,请求路径:%s' % (request.method, request.path))
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
# 对前端提交的数据进行二次验证,防止恶意提交简单密码或串改账号。
|
# 对前端提交的数据进行二次验证,防止恶意提交简单密码或篡改账号。
|
||||||
check_form = CheckForm(request.POST)
|
check_form = CheckForm(request.POST)
|
||||||
if check_form.is_valid():
|
if check_form.is_valid():
|
||||||
form_obj = check_form.cleaned_data
|
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")
|
old_password = form_obj.get("old_password")
|
||||||
new_password = form_obj.get("new_password")
|
new_password = form_obj.get("new_password")
|
||||||
else:
|
else:
|
||||||
msg = check_form.as_p().errors
|
_msg = check_form.as_p().errors
|
||||||
logger.error('[异常] 请求方法:%s,请求路径:%s,错误信息:%s' % (request.method, request.path, msg))
|
logger.error('[异常] 请求方法:%s,请求路径:%s,错误信息:%s' % (request.method, request.path, _msg))
|
||||||
context = {
|
context = {
|
||||||
'msg': msg,
|
'msg': _msg,
|
||||||
'button_click': "window.location.href='%s'" % home_url,
|
'button_click': "window.location.href='%s'" % home_url,
|
||||||
'button_display': "返回主页"
|
'button_display': "返回主页"
|
||||||
}
|
}
|
||||||
return render(request, msg_template, context)
|
return render(request, msg_template, context)
|
||||||
|
# 格式化用户名
|
||||||
if user_email and old_password and new_password:
|
username = format2username(username)
|
||||||
try:
|
# 检测账号状态
|
||||||
# 判断账号是否被锁定
|
auth_status, auth_result = ad_ops.ad_auth_user(username=username, password=old_password)
|
||||||
if ad_get_user_locked_status_by_mail(user_mail_addr=user_email) is not 0:
|
if not auth_status:
|
||||||
context = {
|
|
||||||
'msg': "此账号己被锁定,请先解锁账号。",
|
|
||||||
'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:
|
|
||||||
context = {
|
|
||||||
'msg': "此账号状态为己禁用,请联系HR确认账号是否正确。",
|
|
||||||
'button_click': "window.location.href='%s'" % home_url,
|
|
||||||
'button_display': "返回主页"
|
|
||||||
}
|
|
||||||
return render(request, msg_template, context)
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = ad_modify_user_pwd_by_mail(user_mail_addr=user_email, old_password=old_password,
|
|
||||||
new_password=new_password)
|
|
||||||
if result:
|
|
||||||
context = {
|
|
||||||
'msg': "密码己修改成功,请妥善保管密码。你可直接关闭此页面!",
|
|
||||||
'button_click': "window.location.href='%s'" % home_url,
|
|
||||||
'button_display': "返回主页"
|
|
||||||
}
|
|
||||||
return render(request, msg_template, context)
|
|
||||||
|
|
||||||
else:
|
|
||||||
context = {
|
|
||||||
'msg': "密码未修改成功,请确认旧密码是否正确。",
|
|
||||||
'button_click': "window.history.back()",
|
|
||||||
'button_display': "返回"
|
|
||||||
}
|
|
||||||
return render(request, msg_template, context)
|
|
||||||
|
|
||||||
except IndexError:
|
|
||||||
context = {
|
|
||||||
'msg': "请确认邮箱账号[%s]是否正确?未能在Active Directory中检索到相关信息。" % user_email,
|
|
||||||
'button_click': "window.location.href='%s'" % home_url,
|
|
||||||
'button_display': "返回主页"
|
|
||||||
}
|
|
||||||
return render(request, msg_template, context)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
context = {
|
|
||||||
'msg': "出现未预期的错误[%s],请与管理员联系~" % str(e),
|
|
||||||
'button_click': "window.location.href='%s'" % home_url,
|
|
||||||
'button_display': "返回主页"
|
|
||||||
}
|
|
||||||
return render(request, msg_template, context)
|
|
||||||
|
|
||||||
except IndexError:
|
|
||||||
context = {
|
|
||||||
'msg': "请确认邮箱账号[%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 = {
|
context = {
|
||||||
'msg': "用户名、旧密码、新密码参数不正确,请重新确认后输入。",
|
'msg': str(auth_result),
|
||||||
'button_click': "window.history.back()",
|
'button_click': "window.history.back()",
|
||||||
'button_display': "返回"
|
'button_display': "返回"
|
||||||
}
|
}
|
||||||
return render(request, msg_template, context)
|
return render(request, msg_template, context)
|
||||||
|
|
||||||
|
# 514 66050是AD中账号被禁用的特定代码,这个可以在微软官网查到。
|
||||||
|
# 可能不是太准确
|
||||||
|
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,
|
||||||
|
'button_display': "返回主页"
|
||||||
|
}
|
||||||
|
return render(request, msg_template, context)
|
||||||
|
|
||||||
|
reset_status, reset_result = ad_ops.ad_reset_user_pwd_by_account(username=username, new_password=new_password)
|
||||||
|
if reset_status:
|
||||||
|
context = {
|
||||||
|
'msg': "密码己修改成功,新密码稍后生效,请妥善保管。您可直接关闭此页面!",
|
||||||
|
'button_click': "window.location.href='%s'" % home_url,
|
||||||
|
'button_display': "返回主页"
|
||||||
|
}
|
||||||
|
return render(request, msg_template, context)
|
||||||
|
else:
|
||||||
|
context = {
|
||||||
|
'msg': "密码未修改成功,原因:{}" .format(reset_result),
|
||||||
|
'button_click': "window.history.back()",
|
||||||
|
'button_display': "返回"
|
||||||
|
}
|
||||||
|
return render(request, msg_template, context)
|
||||||
else:
|
else:
|
||||||
context = {
|
context = {
|
||||||
'msg': "请从主页进行修改密码操作或扫码验证用户信息。",
|
'msg': "请从主页进行修改密码操作或扫码验证用户信息。",
|
||||||
|
@ -133,10 +93,9 @@ def resetpwd_index(request):
|
||||||
return render(request, msg_template, context)
|
return render(request, msg_template, context)
|
||||||
|
|
||||||
|
|
||||||
def resetpwd_check_userinfo(request):
|
def callback_check(request):
|
||||||
"""
|
"""
|
||||||
钉钉扫码回调数据对用户在AD中进行验证
|
钉钉扫码回调数据之后,将用户账号在AD中进行验证,如果通过,则返回钉钉中取出用户的union_id
|
||||||
扫码之后从钉钉中取出用户的unionid
|
|
||||||
:param request:
|
:param request:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
@ -147,45 +106,48 @@ def resetpwd_check_userinfo(request):
|
||||||
else:
|
else:
|
||||||
logger.error('[异常] 请求方法:%s,请求路径:%s,未能拿到CODE。' % (request.method, request.path))
|
logger.error('[异常] 请求方法:%s,请求路径:%s,未能拿到CODE。' % (request.method, request.path))
|
||||||
try:
|
try:
|
||||||
unionid = ding_get_persistent_code(code, ding_get_access_token())
|
union_status, union_id = ding_ops.ding_get_union_id_by_code(code)
|
||||||
# 判断 unionid 在本企业钉钉中是否存在
|
# 判断 union_id 在本企业钉钉中是否存在
|
||||||
if not unionid:
|
if not union_status:
|
||||||
logger.error('[异常] 请求方法:%s,请求路径:%s,未能拿到unionid。' % (request.method, request.path))
|
logger.error('[异常] 请求方法:%s,请求路径:%s,未能拿到union_id。' % (request.method, request.path))
|
||||||
context = {
|
context = {
|
||||||
'msg': '未能在钉钉企业通讯录中检索到相关信息,请确认当前登录钉钉的账号已在企业中注册!',
|
'msg': '未能在企业钉钉中检索到用户信息,错误信息:{}' .format(union_id),
|
||||||
'button_click': "window.location.href='%s'" % home_url,
|
'button_click': "window.location.href='%s'" % home_url,
|
||||||
'button_display': "返回主页"
|
'button_display': "返回主页"
|
||||||
}
|
}
|
||||||
return render(request, msg_template, context)
|
return render(request, msg_template, context)
|
||||||
|
userid_status, user_result = ding_ops.ding_get_userid_by_union_id(union_id)
|
||||||
ding_user_info = ding_get_userinfo_detail(ding_get_userid_by_unionid(unionid))
|
if not userid_status:
|
||||||
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)
|
|
||||||
return set_cookie
|
|
||||||
else:
|
|
||||||
context = {
|
|
||||||
'msg': '邮箱是[%s]的用户在钉钉中未激活或可能己离职' % ding_user_info['email'],
|
|
||||||
'button_click': "window.location.href='%s'" % home_url,
|
|
||||||
'button_display': "返回主页"
|
|
||||||
}
|
|
||||||
return render(request, msg_template, context)
|
|
||||||
except IndexError:
|
|
||||||
context = {
|
context = {
|
||||||
'msg': "用户不存在或己离职",
|
'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)
|
||||||
|
# 钉钉中此账号是否可用
|
||||||
|
if ding_user_info['active']:
|
||||||
|
crypto = Crypto(CRYPTO_KEY)
|
||||||
|
# 对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]在钉钉中未激活或可能己离职' % format2username(ding_user_info['name']),
|
||||||
'button_click': "window.location.href='%s'" % home_url,
|
'button_click': "window.location.href='%s'" % home_url,
|
||||||
'button_display': "返回主页"
|
'button_display': "返回主页"
|
||||||
}
|
}
|
||||||
return render(request, msg_template, context)
|
return render(request, msg_template, context)
|
||||||
except Exception as e:
|
|
||||||
logger.error('[异常] :%s' % str(e))
|
|
||||||
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
context = {
|
context = {
|
||||||
'msg': "错误,钉钉临时Code己失效,请从主页重新扫码。",
|
'msg': "错误,钉钉临时Code己失效,请从主页重新扫码。",
|
||||||
|
@ -205,21 +167,22 @@ def resetpwd_check_userinfo(request):
|
||||||
return render(request, msg_template, context)
|
return render(request, msg_template, context)
|
||||||
|
|
||||||
|
|
||||||
def resetpwd_reset(request):
|
def reset_pwd_by_ding_callback(request):
|
||||||
"""
|
"""
|
||||||
钉钉扫码并验证信息之后,在重置密码页面将用户邮箱进行绑定
|
钉钉扫码并验证信息通过之后,在重置密码页面将用户账号进行绑定
|
||||||
:param request:
|
:param request:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
global unionid_crypto
|
global union_id_crypto
|
||||||
home_url = '%s://%s' % (request.scheme, HOME_URL)
|
home_url = '%s://%s' % (request.scheme, HOME_URL)
|
||||||
# 从cookie中提取unionid,并解密,然后对当前unionid的用户进行重置密码
|
# 从cookie中提取union_id,并解密,然后对当前union_id的用户进行重置密码
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
try:
|
try:
|
||||||
unionid_crypto = request.COOKIES.get('tmpid')
|
union_id_crypto = request.COOKIES.get('tmpid')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
union_id_crypto = None
|
||||||
logger.error('[异常] :%s' % str(e))
|
logger.error('[异常] :%s' % str(e))
|
||||||
if not unionid_crypto:
|
if not union_id_crypto:
|
||||||
logger.error('[异常] 请求方法:%s,请求路径:%s,未能拿到CODE或CODE己超时。' % (request.method, request.path))
|
logger.error('[异常] 请求方法:%s,请求路径:%s,未能拿到CODE或CODE己超时。' % (request.method, request.path))
|
||||||
context = {
|
context = {
|
||||||
'msg': "会话己超时,请重新扫码验证用户信息。",
|
'msg': "会话己超时,请重新扫码验证用户信息。",
|
||||||
|
@ -229,20 +192,35 @@ def resetpwd_reset(request):
|
||||||
return render(request, msg_template, context)
|
return render(request, msg_template, context)
|
||||||
# 解密
|
# 解密
|
||||||
crypto = Crypto(CRYPTO_KEY)
|
crypto = Crypto(CRYPTO_KEY)
|
||||||
unionid = crypto.decrypt(unionid_crypto)
|
union_id = crypto.decrypt(union_id_crypto)
|
||||||
# 通过unionid在钉钉中拿到用户的邮箱
|
# 通过union_id在钉钉中拿到用户的邮箱,并格式化为username
|
||||||
user_email = ding_get_userinfo_detail(ding_get_userid_by_unionid(unionid))['email']
|
userid_status, user_result = ding_ops.ding_get_userid_by_union_id(union_id)
|
||||||
# 如果邮箱在钉钉中能提取,则提交到前端绑定
|
if not userid_status:
|
||||||
if user_email:
|
|
||||||
context = {
|
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:
|
else:
|
||||||
context = {
|
context = {
|
||||||
'msg': "%s 您好,企业钉钉中未能找到您账号的邮箱配置,请联系HR完善信息。" % ding_get_userinfo_detail(ding_get_userid_by_unionid(
|
'msg': "%s,您好,企业钉钉中未能找到您账号的邮箱配置,请联系HR完善信息。" % ding_user_info['name'],
|
||||||
unionid))['name'],
|
|
||||||
'button_click': "window.location.href='%s'" % home_url,
|
'button_click': "window.location.href='%s'" % home_url,
|
||||||
'button_display': "返回主页"
|
'button_display': "返回主页"
|
||||||
}
|
}
|
||||||
|
@ -250,10 +228,14 @@ def resetpwd_reset(request):
|
||||||
|
|
||||||
# 重置密码页面,输入新密码后点击提交
|
# 重置密码页面,输入新密码后点击提交
|
||||||
elif request.method == 'POST':
|
elif request.method == 'POST':
|
||||||
new_password = request.POST.get('new_password').strip()
|
_new_password = request.POST.get('new_password').strip()
|
||||||
unionid_crypto = request.COOKIES.get('tmpid')
|
try:
|
||||||
# 对cookie中的unionid进行超时验证,如果页面超时就不再做处理。
|
union_id_crypto = request.COOKIES.get('tmpid')
|
||||||
if not unionid_crypto:
|
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 = {
|
context = {
|
||||||
'msg': "会话己超时,请重新扫码验证用户信息。",
|
'msg': "会话己超时,请重新扫码验证用户信息。",
|
||||||
'button_click': "window.location.href='%s'" % home_url,
|
'button_click': "window.location.href='%s'" % home_url,
|
||||||
|
@ -261,17 +243,32 @@ def resetpwd_reset(request):
|
||||||
}
|
}
|
||||||
return render(request, msg_template, context)
|
return render(request, msg_template, context)
|
||||||
crypto = Crypto(CRYPTO_KEY)
|
crypto = Crypto(CRYPTO_KEY)
|
||||||
unionid = crypto.decrypt(unionid_crypto)
|
union_id = crypto.decrypt(union_id_crypto)
|
||||||
user_email = ding_get_userinfo_detail(ding_get_userid_by_unionid(unionid))['email']
|
userid_status, user_result = ding_ops.ding_get_userid_by_union_id(union_id)
|
||||||
if ad_ensure_user_by_mail(user_mail_addr=user_email) is False:
|
if not userid_status:
|
||||||
context = {
|
context = {
|
||||||
'msg': "账号[%s]在AD中不存在,请确认当前钉钉扫码账号绑定的邮箱是否和您正在使用的邮箱一致?或者该邮箱账号己被禁用!\n猜测:您的邮箱是否是带有数字或其它字母区分?" % user_email,
|
'msg': '获取钉钉userid失败,错误信息:{}'.format(user_result),
|
||||||
'button_click': "window.location.href='%s'" % home_url,
|
'button_click': "window.location.href='%s'" % home_url,
|
||||||
'button_display': "返回主页"
|
'button_display': "返回主页"
|
||||||
}
|
}
|
||||||
return render(request, msg_template, context)
|
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(
|
detail_status, ding_user_info = ding_ops.ding_get_userinfo_detail(user_result)
|
||||||
user_mail_addr=user_email) == 66050:
|
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 = {
|
context = {
|
||||||
'msg': "此账号状态为己禁用,请联系HR确认账号是否正确。",
|
'msg': "此账号状态为己禁用,请联系HR确认账号是否正确。",
|
||||||
'button_click': "window.location.href='%s'" % home_url,
|
'button_click': "window.location.href='%s'" % home_url,
|
||||||
|
@ -279,34 +276,20 @@ def resetpwd_reset(request):
|
||||||
}
|
}
|
||||||
return render(request, msg_template, context)
|
return render(request, msg_template, context)
|
||||||
|
|
||||||
try:
|
reset_status, result = ad_ops.ad_reset_user_pwd_by_account(username=username, new_password=_new_password)
|
||||||
result = ad_reset_user_pwd_by_mail(user_mail_addr=user_email, new_password=new_password)
|
if reset_status:
|
||||||
if result:
|
# 重置密码并执行一次解锁,防止重置后账号还是锁定状态。
|
||||||
# 重置密码并执行一次解锁,防止重置后账号还是锁定状态。
|
unlock_status, result = ad_ops.ad_unlock_user_by_account(username)
|
||||||
ad_unlock_user_by_mail(user_email)
|
if unlock_status:
|
||||||
context = {
|
context = {
|
||||||
'msg': "密码己重置成功,请妥善保管。你可以点击返回主页或直接关闭此页面!",
|
'msg': "密码己重置成功,请妥善保管。你可以点击返回主页或直接关闭此页面!",
|
||||||
'button_click': "window.location.href='%s'" % home_url,
|
'button_click': "window.location.href='%s'" % home_url,
|
||||||
'button_display': "返回主页"
|
'button_display': "返回主页"
|
||||||
}
|
}
|
||||||
return render(request, msg_template, context)
|
return render(request, msg_template, context)
|
||||||
else:
|
else:
|
||||||
context = {
|
|
||||||
'msg': "密码未重置成功,确认密码是否满足AD的复杂性要求。",
|
|
||||||
'button_click': "window.location.href='%s'" % home_url,
|
|
||||||
'button_display': "返回主页"
|
|
||||||
}
|
|
||||||
return render(request, msg_template, context)
|
|
||||||
except IndexError:
|
|
||||||
context = {
|
context = {
|
||||||
'msg': "请确认邮箱账号[%s]是否正确?未能在AD中检索到相关信息。" % user_email,
|
'msg': "密码未重置成功,错误信息:{}" .format(result),
|
||||||
'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_click': "window.location.href='%s'" % home_url,
|
||||||
'button_display': "返回主页"
|
'button_display': "返回主页"
|
||||||
}
|
}
|
||||||
|
@ -320,7 +303,7 @@ def resetpwd_reset(request):
|
||||||
return render(request, msg_template, context)
|
return render(request, msg_template, context)
|
||||||
|
|
||||||
|
|
||||||
def resetpwd_unlock(request):
|
def unlock_account(request):
|
||||||
"""
|
"""
|
||||||
解锁账号
|
解锁账号
|
||||||
:param request:
|
:param request:
|
||||||
|
@ -328,8 +311,8 @@ def resetpwd_unlock(request):
|
||||||
"""
|
"""
|
||||||
home_url = '%s://%s' % (request.scheme, HOME_URL)
|
home_url = '%s://%s' % (request.scheme, HOME_URL)
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
unionid_crypto = request.COOKIES.get('tmpid')
|
_union_id_crypto = request.COOKIES.get('tmpid')
|
||||||
if not unionid_crypto:
|
if not _union_id_crypto:
|
||||||
context = {
|
context = {
|
||||||
'msg': "会话己超时,请重新扫码验证用户信息。",
|
'msg': "会话己超时,请重新扫码验证用户信息。",
|
||||||
'button_click': "window.location.href='%s'" % home_url,
|
'button_click': "window.location.href='%s'" % home_url,
|
||||||
|
@ -337,16 +320,32 @@ def resetpwd_unlock(request):
|
||||||
}
|
}
|
||||||
return render(request, msg_template, context)
|
return render(request, msg_template, context)
|
||||||
crypto = Crypto(CRYPTO_KEY)
|
crypto = Crypto(CRYPTO_KEY)
|
||||||
unionid = crypto.decrypt(unionid_crypto)
|
union_id = crypto.decrypt(_union_id_crypto)
|
||||||
user_email = ding_get_userinfo_detail(ding_get_userid_by_unionid(unionid))['email']
|
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)
|
||||||
|
username = format2username(ding_user_info['email'])
|
||||||
context = {
|
context = {
|
||||||
'user_email': user_email,
|
'username': username,
|
||||||
}
|
}
|
||||||
return render(request, 'resetpwd.html', context)
|
return render(request, 'resetPassword.html', context)
|
||||||
|
|
||||||
elif request.method == 'POST':
|
elif request.method == 'POST':
|
||||||
unionid_crypto = request.COOKIES.get('tmpid')
|
_union_id_crypto = request.COOKIES.get('tmpid')
|
||||||
if not unionid_crypto:
|
if not _union_id_crypto:
|
||||||
context = {
|
context = {
|
||||||
'msg': "会话己超时,请重新扫码验证用户信息。",
|
'msg': "会话己超时,请重新扫码验证用户信息。",
|
||||||
'button_click': "window.location.href='%s'" % home_url,
|
'button_click': "window.location.href='%s'" % home_url,
|
||||||
|
@ -354,43 +353,44 @@ def resetpwd_unlock(request):
|
||||||
}
|
}
|
||||||
return render(request, msg_template, context)
|
return render(request, msg_template, context)
|
||||||
crypto = Crypto(CRYPTO_KEY)
|
crypto = Crypto(CRYPTO_KEY)
|
||||||
unionid = crypto.decrypt(unionid_crypto)
|
union_id = crypto.decrypt(_union_id_crypto)
|
||||||
user_email = ding_get_userinfo_detail(ding_get_userid_by_unionid(unionid))['email']
|
userid_status, user_result = ding_ops.ding_get_userid_by_union_id(union_id)
|
||||||
if ad_ensure_user_by_mail(user_mail_addr=user_email) is False:
|
if not userid_status:
|
||||||
context = {
|
context = {
|
||||||
'msg': "账号[%s]在AD中未能正确检索到,请确认当前钉钉扫码账号绑定的邮箱是否和您正在使用的邮箱一致?或者该邮箱账号己被禁用!\n猜测:您的邮箱是否是带有数字或其它字母区分?" %
|
'msg': '获取钉钉userid失败,错误信息:{}'.format(user_result),
|
||||||
user_email,
|
'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_click': "window.location.href='%s'" % home_url,
|
||||||
'button_display': "返回主页"
|
'button_display': "返回主页"
|
||||||
}
|
}
|
||||||
return render(request, msg_template, context)
|
return render(request, msg_template, context)
|
||||||
else:
|
else:
|
||||||
try:
|
unlock_status, result = ad_ops.ad_unlock_user_by_account(username)
|
||||||
result = ad_unlock_user_by_mail(user_email)
|
if unlock_status:
|
||||||
if result:
|
|
||||||
context = {
|
|
||||||
'msg': "账号己解锁成功。你可以点击返回主页或直接关闭此页面!",
|
|
||||||
'button_click': "window.location.href='%s'" % home_url,
|
|
||||||
'button_display': "返回主页"
|
|
||||||
}
|
|
||||||
return render(request, msg_template, context)
|
|
||||||
else:
|
|
||||||
context = {
|
|
||||||
'msg': "账号未能解锁,请联系管理员确认该账号在AD的是否己禁用。",
|
|
||||||
'button_click': "window.location.href='%s'" % home_url,
|
|
||||||
'button_display': "返回主页"
|
|
||||||
}
|
|
||||||
return render(request, msg_template, context)
|
|
||||||
except IndexError:
|
|
||||||
context = {
|
context = {
|
||||||
'msg': "请确认邮箱账号[%s]是否正确?未能在AD中检索到相关信息。" % user_email,
|
'msg': "账号己解锁成功。你可以点击返回主页或直接关闭此页面!",
|
||||||
'button_click': "window.location.href='%s'" % home_url,
|
'button_click': "window.location.href='%s'" % home_url,
|
||||||
'button_display': "返回主页"
|
'button_display': "返回主页"
|
||||||
}
|
}
|
||||||
return render(request, msg_template, context)
|
return render(request, msg_template, context)
|
||||||
except Exception as e:
|
else:
|
||||||
context = {
|
context = {
|
||||||
'msg': "出现未预期的错误[%s],请与管理员联系~" % str(e),
|
'msg': "账号未能解锁,错误信息:{}" .format(result),
|
||||||
'button_click': "window.location.href='%s'" % home_url,
|
'button_click': "window.location.href='%s'" % home_url,
|
||||||
'button_display': "返回主页"
|
'button_display': "返回主页"
|
||||||
}
|
}
|
||||||
|
@ -404,12 +404,12 @@ def resetpwd_unlock(request):
|
||||||
return render(request, msg_template, context)
|
return render(request, msg_template, context)
|
||||||
|
|
||||||
|
|
||||||
def reset_msg(request):
|
def messages(request):
|
||||||
msg = request.GET.get('msg')
|
_msg = request.GET.get('msg')
|
||||||
button_click = request.GET.get('button_click')
|
button_click = request.GET.get('button_click')
|
||||||
button_display = request.GET.get('button_display')
|
button_display = request.GET.get('button_display')
|
||||||
context = {
|
context = {
|
||||||
'msg': msg,
|
'msg': _msg,
|
||||||
'button_click': button_click,
|
'button_click': button_click,
|
||||||
'button_display': button_display
|
'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} 大小写字母+数字
|
// (?=.*[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_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}');
|
regex_pwd = new RegExp('(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9]).{8,30}');
|
||||||
if ($.trim($('#user_email').val()) === '') {
|
//if ($.trim($('#user_email').val()) === '') {
|
||||||
alert('请输入邮箱账号');
|
// alert('请输入邮箱账号');
|
||||||
return false;
|
// return false;
|
||||||
} else if (!regex_mail.test($.trim($('#user_email').val()))) {
|
//} else if (!regex_mail.test($.trim($('#user_email').val()))) {
|
||||||
alert('请输入正确的邮箱账号。\n');
|
// alert('请输入正确的邮箱账号。\n');
|
||||||
return false;
|
// return false;
|
||||||
} else if ($.trim($('#old_password').val()) === '') {
|
//} else
|
||||||
|
if ($.trim($('#old_password').val()) === '') {
|
||||||
alert('请输入旧密码');
|
alert('请输入旧密码');
|
||||||
return false;
|
return false;
|
||||||
} else if ($.trim($('#new_password').val()) === '') {
|
} else if ($.trim($('#new_password').val()) === '') {
|
||||||
|
@ -57,13 +58,14 @@
|
||||||
$('#btn_reset').click(function () {
|
$('#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_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}');
|
let regex_pwd = new RegExp('(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9]).{8,30}');
|
||||||
if ($.trim($('#user_email').val()) === '') {
|
//if ($.trim($('#user_email').val()) === '') {
|
||||||
alert('请输入邮箱账号');
|
//alert('请输入邮箱账号');
|
||||||
return false;
|
// return false;
|
||||||
} else if (!regex_mail.test($.trim($('#user_email').val()))) {
|
//} else if (!regex_mail.test($.trim($('#user_email').val()))) {
|
||||||
alert('请输入正确的邮箱账号。\n');
|
// alert('请输入正确的邮箱账号。\n');
|
||||||
return false;
|
// return false;
|
||||||
} else if ($.trim($('#new_password').val()) === '') {
|
//} else
|
||||||
|
if ($.trim($('#new_password').val()) === '') {
|
||||||
alert('请输入密码');
|
alert('请输入密码');
|
||||||
return false;
|
return false;
|
||||||
} else if ($.trim($('#ensure_password').val()) === '') {
|
} else if ($.trim($('#ensure_password').val()) === '') {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% load staticfiles %}
|
{% load static %}
|
||||||
<!DOCTYPE html>
|
<!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"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
<title>密码自助服务</title>
|
<title>密码自助服务</title>
|
||||||
|
@ -9,7 +9,7 @@
|
||||||
</head>
|
</head>
|
||||||
<body style="overflow: hidden">
|
<body style="overflow: hidden">
|
||||||
|
|
||||||
<form name="resetcheck" method="post" action="resetpwd" autocomplete="off">
|
<form name="callbackCheck" method="post" action="resetPassword" autocomplete="off">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
</form>
|
</form>
|
||||||
</body></html>
|
</body></html>
|
|
@ -1,11 +1,11 @@
|
||||||
{% load staticfiles %}
|
{% load static %}
|
||||||
<!DOCTYPE html>
|
<!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"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
<title>密码自助服务</title>
|
<title>密码自助服务</title>
|
||||||
<link type="text/css" rel="stylesheet" href="{% static 'css/login.css' %}">
|
<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/jquery-1.8.3.min.js' %}"></script>
|
||||||
<script type="text/javascript" src="{% static 'js/check.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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="pagewrap">
|
<div class="pagewrap">
|
||||||
|
@ -15,8 +15,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="con_left" >
|
<div class="con_left" >
|
||||||
<div style="margin: 0 auto; width:100%; height: 200px; line-height: 200px;" align="center" >
|
<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>
|
||||||
<div style="margin: 0 auto; width:400px; height: 240px;">
|
<div style="margin: 0 auto; width:400px; height: 240px;">
|
||||||
<p style="margin: 0 auto; color: #fdfdfe; font-size: 16px; width:100%;
|
<p style="margin: 0 auto; color: #fdfdfe; font-size: 16px; width:100%;
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="user">
|
<div class="user">
|
||||||
<div><span class="user-icon"></span>
|
<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>
|
||||||
<div><span class="mima-icon"></span>
|
<div><span class="mima-icon"></span>
|
||||||
<input type="password" id="old_password" name="old_password"
|
<input type="password" id="old_password" name="old_password"
|
||||||
|
@ -59,12 +59,16 @@
|
||||||
<div style="margin-top: -30px" class="erweima">
|
<div style="margin-top: -30px" class="erweima">
|
||||||
<div style="width: 300px; height: 300px; margin: 0 auto" id="ding_code"></div>
|
<div style="width: 300px; height: 300px; margin: 0 auto" id="ding_code"></div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
// 构造钉钉登录
|
||||||
// 扫描之后需要跳转的域名,填写自己的修改密码的域名地址http或https
|
// 扫描之后需要跳转的域名,填写自己的修改密码的域名地址http或https
|
||||||
var home_url = "{{ home_url }}";
|
var home_url = "{{ home_url }}";
|
||||||
// 钉钉移动应用接入ID
|
// 钉钉移动应用接入ID
|
||||||
var appid = "{{ app_id }}";
|
var app_id = "{{ app_id }}";
|
||||||
var url = encodeURIComponent(home_url + '/resetcheck');
|
var redirect_url = encodeURIComponent(home_url + '/callbackCheck');
|
||||||
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 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({
|
var obj = DDLogin({
|
||||||
id: "ding_code",
|
id: "ding_code",
|
||||||
goto: goto,
|
goto: goto,
|
||||||
|
@ -73,15 +77,20 @@
|
||||||
height: "300"
|
height: "300"
|
||||||
});
|
});
|
||||||
// 获取loginTmpCode
|
// 获取loginTmpCode
|
||||||
const hanndleMessage = function (event) {
|
var hanndleMessage = function (event) {
|
||||||
let origin = event.origin;
|
var origin = event.origin;
|
||||||
console.log("origin", event.origin)
|
console.log("origin", event.origin)
|
||||||
//判断是否来自ddLogin扫码事件。
|
//判断是否来自ddLogin扫码事件。
|
||||||
if (origin === "https://login.dingtalk.com") {
|
if (origin === "https://login.dingtalk.com") {
|
||||||
let loginTmpCode = event.data;
|
var loginTmpCode = event.data;
|
||||||
console.log("loginTmpCode", loginTmpCode);
|
console.log("loginTmpCode", loginTmpCode);
|
||||||
if (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>
|
<!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>
|
<title>密码自助服务</title>
|
||||||
<link type="text/css" rel="stylesheet" href="{% static 'css/login.css' %}">
|
<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/jquery-1.8.3.min.js' %}"></script>
|
||||||
|
@ -12,9 +12,8 @@
|
||||||
<div class="header"></div>
|
<div class="header"></div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="con_left" >
|
<div class="con_left" >
|
||||||
<div style="margin: 0 auto; width:100%; height: 200px; line-height: 200px;" align="center" >
|
<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>
|
||||||
<small>密码自助平台</small></p>
|
|
||||||
</div>
|
</div>
|
||||||
<div style="margin: 0 auto; width:400px; height: 240px;">
|
<div style="margin: 0 auto; width:400px; height: 240px;">
|
||||||
<p style="margin: 0 auto; color: #fdfdfe; font-size: 16px; width:100%;
|
<p style="margin: 0 auto; color: #fdfdfe; font-size: 16px; width:100%;
|
|
@ -1,4 +1,4 @@
|
||||||
{% load staticfiles %}
|
{% load static %}
|
||||||
<!DOCTYPE html>
|
<!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"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
<title>密码自助服务</title>
|
<title>密码自助服务</title>
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="con_left" >
|
<div class="con_left" >
|
||||||
<div style="margin: 0 auto; width:100%; height: 200px; line-height: 200px;" align="center" >
|
<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>
|
<small>密码自助平台</small></p>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin: 0 auto; width:400px; height: 240px;">
|
<div style="margin: 0 auto; width:400px; height: 240px;">
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="user">
|
<div class="user">
|
||||||
<div><span class="user-icon"></span>
|
<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>
|
||||||
<div><span class="mima-icon"></span>
|
<div><span class="mima-icon"></span>
|
||||||
<input type="password" id="new_password" name="new_password"
|
<input type="password" id="new_password" name="new_password"
|
||||||
|
@ -55,7 +55,7 @@
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="user" style="height: 168px">
|
<div class="user" style="height: 168px">
|
||||||
<div><span class="user-icon"></span>
|
<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="msgs"></span>
|
<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