Skip to content

Commit

Permalink
feature: 企业微信用户同步 TencentBlueKing#118
Browse files Browse the repository at this point in the history
  • Loading branch information
richardlu committed Nov 2, 2021
1 parent 8c633e0 commit b23c281
Show file tree
Hide file tree
Showing 12 changed files with 982 additions and 0 deletions.
23 changes: 23 additions & 0 deletions src/api/bkuser_core/categories/plugins/wecom/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# 创建插件目录
本地创建插件
对应的插件名称为 wecom
domian 根据实际需要设置
```bash
python manage.py create_pluggable_category --name 企业微信 --domain some-domain.com --plugin wecom
```
默认地,以上步骤会创建一个 key 为 `wecom``Setting` 绑定到该目录。


# DB 初始化
配置同步所需的corpId和Secret
category_id: step1创建的目录的ID
settingmeta_wecom_corpid_id: settingmeta 表中key为wecom_corpid 的ID
settingmeta_wecom_secret_id: settingmeta 表中key为wecom_secret 的ID
```bash
insert into user_settings_setting(create_time,update_time,value,enabled,category_id,meta_id) values(now(),now(),"",0,{category_id},${settingmeta_wecom_corpid_id});
insert into user_settings_setting(create_time,update_time,value,enabled,category_id,meta_id) values(now(),now(),"",0,{category_id},${settingmeta_wecom_secret_id});
```

# 其他
企业微信接口频率限制
https://work.weixin.qq.com/api/doc/90000/90139/90312
22 changes: 22 additions & 0 deletions src/api/bkuser_core/categories/plugins/wecom/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available.
Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
"""
from bkuser_core.categories.plugins.plugin import DataSourcePlugin

from .login import LoginHandler
from .syncer import WeComSyncer

DataSourcePlugin(
name="wecom",
syncer_cls=WeComSyncer,
login_handler_cls=LoginHandler,
allow_client_write=True,
category_type="pluggable",
).register()
130 changes: 130 additions & 0 deletions src/api/bkuser_core/categories/plugins/wecom/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available.
Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
"""

import requests
import json
import logging

from dataclasses import dataclass
from typing import TYPE_CHECKING

from . import exceptions as local_exceptions
from .constants import ACCESS_TOKEN_URL, DEPARTMENT_LIST_URL, USER_LIST_DETAIL


if TYPE_CHECKING:
from bkuser_core.user_settings.loader import ConfigProvider


logger = logging.getLogger(__name__)


@dataclass
class WeComClient:

config_provider: "ConfigProvider"

def __post_init__(self):
self.corpid = self.config_provider.get("wecom_corpid").get("wecom_corpid")
self.secret = self.config_provider.get("wecom_secret").get("wecom_secret")
self.access_token = ""

@staticmethod
def __request_get(url: str) -> dict:
"""
request get 请求
:param url:
:return:
"""
try:
response = requests.get(url)
except Exception as e:
logger.exception("request wecom api failed,url={}, error={}".format(url, e))
raise local_exceptions.WeComAPIRequestFailed
if response.status_code != 200:
logger.error("request wecom api failed,url={},status_code={}".format(url, response.status_code))
raise local_exceptions.WeComAPIRequestStatusCodeError
else:
try:
result = json.loads(response.content)
except Exception as e:
logger.exception("json loads wecom api return fail, error={}".format(e))
raise local_exceptions.WeComAPIRequestJsonLoadError
return result

def __get_access_token(self) -> str:
"""
获取访问企业微信的access_token
:return:
"""
if self.access_token:
return self.access_token
url = ACCESS_TOKEN_URL.format(self.corpid, self.secret)
res = self.__request_get(url)
if res.get('errcode') == 0:
access_token = res.get('access_token', '')
else:
logger.error("get access_token failed, res={}".format(res))
raise local_exceptions.WeComAPIGetAccessTokenError
self.access_token = access_token
return access_token

def get_departments(self) -> list:
"""
获取部门列表
:return:
"""
url = DEPARTMENT_LIST_URL.format(self.__get_access_token())
res = self.__request_get(url)
if res.get('errcode') == 0:
departments = res.get('department', [])
else:
logger.error("get departments failed, res={}".format(res))
raise local_exceptions.WeComAPIGetDepartmentError
return departments

def get_user_info(self, departments: list) -> list:
"""
获取用户列表
:param departments: 部门列表
:return:
"""
user_list = []
for department in departments:
url = USER_LIST_DETAIL.format(self.__get_access_token(), department['id'], 0)
res = self.__request_get(url)
if res.get('errcode') == 0:
user_list.extend(res.get('userlist', []))
else:
logger.error("get user list failed, res={}".format(res))
raise local_exceptions.WeComAPIGetDepartmentError
# 去重
new_user_list = self.__uniq_user_info(user_list)
return new_user_list

@staticmethod
def __uniq_user_info(user_list: list) -> list:
"""
将从企业微信获取的用户去重
:param user_list:
:return:
"""
user_ids = []
new_user_list = []
for user in user_list:
if user.get("userid") not in user_ids:
user_ids.append(user.get("userid"))
new_user_list.append(user)
return new_user_list

def check(self, corpid, secret):
# todo check corpid and secret
pass
30 changes: 30 additions & 0 deletions src/api/bkuser_core/categories/plugins/wecom/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available.
Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
"""
ACCESS_TOKEN_URL = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={}&corpsecret={}"
DEPARTMENT_LIST_URL = "https://qyapi.weixin.qq.com/cgi-bin/department/list?access_token={}"
USER_LIST_URL = "https://qyapi.weixin.qq.com/cgi-bin/user/simplelist?access_token={}&department_id={}&fetch_child=1"
USER_LIST_DETAIL = "https://qyapi.weixin.qq.com/cgi-bin/user/list?access_token={}&department_id={}&fetch_child={}"
# USER_LIST_DETAIL = "https://qyapi.weixin.qq.com/cgi-bin/user/list?access_token={}&department_id={}&fetch_child=1"


class WeComStatus:
"""WeCom用户状态类型
1=已激活,2=已禁用,4=未激活,5=退出企业。
"""
ACTIVE = 1
DISABLED = 2
INACTIVE = 4
EXIT_ENTERPRISE = 5


class WeComEnabled:
ENABLE = 1
DISABLE = 0
46 changes: 46 additions & 0 deletions src/api/bkuser_core/categories/plugins/wecom/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available.
Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
"""


class NoKeyItemAvailable(Exception):
"""没有可找到的对象"""


class CustomAPIRequestFailed(Exception):
"""拉取自定义 API 失败"""


class WeComAPIRequestFailed(Exception):
"""WeCom API 失败"""


class WeComAPIRequestStatusCodeError(Exception):
"""WeCom API 返回码非200"""


class WeComAPIRequestJsonLoadError(Exception):
"""WeCom API json load 失败"""


class WeComAPIGetAccessTokenError(Exception):
"""WeCom API 获取 accesstoken 失败"""


class WeComAPIGetDepartmentError(Exception):
"""WeCom API 获取部门信息失败"""


class WeComAPIGetUserListError(Exception):
"""WeCom API 获取用户信息失败"""


class WeComLoginError(Exception):
"""WeCom 登录直接返回"""
20 changes: 20 additions & 0 deletions src/api/bkuser_core/categories/plugins/wecom/login.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available.
Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
"""

from dataclasses import dataclass

from bkuser_core.categories.plugins.wecom.exceptions import WeComLoginError


@dataclass
class LoginHandler:
def check(self, profile, password):
raise WeComLoginError
Loading

0 comments on commit b23c281

Please sign in to comment.