diff --git a/apps/common/sdk/im/dingtalk/__init__.py b/apps/common/sdk/im/dingtalk/__init__.py index 37a3c2dad..2f23419b8 100644 --- a/apps/common/sdk/im/dingtalk/__init__.py +++ b/apps/common/sdk/im/dingtalk/__init__.py @@ -2,16 +2,17 @@ import base64 import hmac import time +from django.conf import settings + from common.sdk.im.mixin import BaseRequest from common.sdk.im.utils import digest, as_request from common.utils import get_logger -from users.utils import construct_user_email +from users.utils import construct_user_email, flatten_dict, map_attributes logger = get_logger(__file__) def sign(secret, data): - digest = hmac.HMAC( key=secret.encode('utf8'), msg=data.encode('utf8'), @@ -115,6 +116,8 @@ class DingTalkRequests(BaseRequest): class DingTalk: + attributes = settings.DINGTALK_RENAME_ATTRIBUTES + def __init__(self, appid, appsecret, agentid, timeout=None): self._appid = appid or '' self._appsecret = appsecret or '' @@ -206,17 +209,24 @@ class DingTalk: data = self._request.post(URL.GET_SEND_MSG_PROGRESS, json=body, with_token=True) return data - def get_user_detail(self, user_id, **kwargs): - # https://open.dingtalk.com/document/orgapp/query-user-details - body = {'userid': user_id} - data = self._request.post( - URL.GET_USER_INFO_BY_USER_ID, json=body, with_token=True - ) - data = data['result'] - username = user_id + @staticmethod + def default_user_detail(data): + username = data['user_id'] name = data.get('name', username) email = data.get('email') or data.get('org_email') email = construct_user_email(username, email) return { 'username': username, 'name': name, 'email': email } + + def get_user_detail(self, user_id, **kwargs): + # https://open.dingtalk.com/document/orgapp/query-user-details + data = self._request.post( + URL.GET_USER_INFO_BY_USER_ID, json={'userid': user_id}, with_token=True + ) + data = data['result'] + data['user_id'] = user_id + info = flatten_dict(data) + default_detail = self.default_user_detail(data) + detail = map_attributes(default_detail, info, self.attributes) + return detail diff --git a/apps/common/sdk/im/feishu/__init__.py b/apps/common/sdk/im/feishu/__init__.py index 199658c6b..da0d12ce2 100644 --- a/apps/common/sdk/im/feishu/__init__.py +++ b/apps/common/sdk/im/feishu/__init__.py @@ -6,7 +6,7 @@ from rest_framework.exceptions import APIException from common.sdk.im.mixin import RequestMixin, BaseRequest from common.sdk.im.utils import digest from common.utils.common import get_logger -from users.utils import construct_user_email +from users.utils import construct_user_email, flatten_dict, map_attributes logger = get_logger(__name__) @@ -84,7 +84,7 @@ class FeiShu(RequestMixin): 非业务数据导致的错误直接抛异常,说明是系统配置错误,业务代码不用理会 """ requests_cls = FeishuRequests - attributes = settings.LARK_RENAME_ATTRIBUTES + attributes = settings.FEISHU_RENAME_ATTRIBUTES def __init__(self, app_id, app_secret, timeout=None): self._app_id = app_id or '' @@ -151,11 +151,7 @@ class FeiShu(RequestMixin): # get_user_id_by_code 已经返回个人信息,这里直接解析 data = kwargs['other_info'] data['user_id'] = user_id - detail = self.default_user_detail(data) - - for local_name, remote_name in self.attributes.items(): - value = data.get(remote_name) - if not value: - continue - detail[local_name] = value + info = flatten_dict(data) + default_detail = self.default_user_detail(data) + detail = map_attributes(default_detail, info, self.attributes) return detail diff --git a/apps/common/sdk/im/slack/__init__.py b/apps/common/sdk/im/slack/__init__.py index 6ba895617..8b520f3e4 100644 --- a/apps/common/sdk/im/slack/__init__.py +++ b/apps/common/sdk/im/slack/__init__.py @@ -1,11 +1,12 @@ import mistune import requests +from django.conf import settings from django.utils.translation import gettext_lazy as _ from rest_framework.exceptions import APIException from common.utils.common import get_logger from jumpserver.utils import get_current_request -from users.utils import construct_user_email +from users.utils import construct_user_email, flatten_dict, map_attributes logger = get_logger(__name__) @@ -93,6 +94,8 @@ class SlackRequests: class Slack: + attributes = settings.SLACK_RENAME_ATTRIBUTES + def __init__(self, client_id=None, client_secret=None, bot_token=None, **kwargs): self._client = SlackRequests( client_id=client_id, client_secret=client_secret, bot_token=bot_token @@ -138,13 +141,22 @@ class Slack: logger.exception(e) @staticmethod - def get_user_detail(user_id, **kwargs): - # get_user_id_by_code 已经返回个人信息,这里直接解析 - user_info = kwargs['other_info'] - username = user_info.get('name') or user_id - name = user_info.get('real_name', username) - email = user_info.get('profile', {}).get('email') + def default_user_detail(data): + username = data['user_id'] + username = data.get('name', username) + name = data.get('real_name', username) + email = data.get('profile.email') email = construct_user_email(username, email) return { 'username': username, 'name': name, 'email': email } + + def get_user_detail(self, user_id, **kwargs): + # https://api.slack.com/methods/users.info + # get_user_id_by_code 已经返回个人信息,这里直接解析 + data = kwargs['other_info'] + data['user_id'] = user_id + info = flatten_dict(data) + default_detail = self.default_user_detail(data) + detail = map_attributes(default_detail, info, self.attributes) + return detail diff --git a/apps/common/sdk/im/wecom/__init__.py b/apps/common/sdk/im/wecom/__init__.py index 8262cbeb5..344bce4aa 100644 --- a/apps/common/sdk/im/wecom/__init__.py +++ b/apps/common/sdk/im/wecom/__init__.py @@ -1,12 +1,13 @@ from typing import Iterable, AnyStr +from django.conf import settings from django.utils.translation import gettext_lazy as _ from rest_framework.exceptions import APIException from common.sdk.im.mixin import RequestMixin, BaseRequest from common.sdk.im.utils import digest, update_values from common.utils.common import get_logger -from users.utils import construct_user_email +from users.utils import construct_user_email, flatten_dict, map_attributes logger = get_logger(__name__) @@ -79,6 +80,7 @@ class WeCom(RequestMixin): """ 非业务数据导致的错误直接抛异常,说明是系统配置错误,业务代码不用理会 """ + attributes = settings.WECOM_RENAME_ATTRIBUTES def __init__(self, corpid, corpsecret, agentid, timeout=None): self._corpid = corpid or '' @@ -173,10 +175,8 @@ class WeCom(RequestMixin): logger.error(f'WeCom response 200 but get field from json error: fields=UserId|OpenId') raise WeComError - def get_user_detail(self, user_id, **kwargs): - # https://open.work.weixin.qq.com/api/doc/90000/90135/90196 - params = {'userid': user_id} - data = self._requests.get(URL.GET_USER_DETAIL, params) + @staticmethod + def default_user_detail(data): username = data.get('userid') name = data.get('name', username) email = data.get('email') or data.get('biz_mail') @@ -184,3 +184,11 @@ class WeCom(RequestMixin): return { 'username': username, 'name': name, 'email': email } + + def get_user_detail(self, user_id, **kwargs): + # https://open.work.weixin.qq.com/api/doc/90000/90135/90196 + data = self._requests.get(URL.GET_USER_DETAIL, {'userid': user_id}) + info = flatten_dict(data) + default_detail = self.default_user_detail(data) + detail = map_attributes(default_detail, info, self.attributes) + return detail diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 21c58bf9c..1a95fb531 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -397,12 +397,22 @@ class Config(dict): 'WECOM_CORPID': '', 'WECOM_AGENTID': '', 'WECOM_SECRET': '', + 'WECOM_RENAME_ATTRIBUTES': { + 'name': 'name', + 'username': 'userid', + 'email': 'email' + }, # 钉钉 'AUTH_DINGTALK': False, 'DINGTALK_AGENTID': '', 'DINGTALK_APPKEY': '', 'DINGTALK_APPSECRET': '', + 'DINGTALK_RENAME_ATTRIBUTES': { + 'name': 'name', + 'username': 'user_id', + 'email': 'email' + }, # 飞书 'AUTH_FEISHU': False, @@ -429,6 +439,11 @@ class Config(dict): 'SLACK_CLIENT_ID': '', 'SLACK_CLIENT_SECRET': '', 'SLACK_BOT_TOKEN': '', + 'SLACK_RENAME_ATTRIBUTES': { + 'name': 'real_name', + 'username': 'name', + 'email': 'profile.email' + }, 'LOGIN_REDIRECT_TO_BACKEND': '', # 'OPENID / CAS / SAML2 'LOGIN_REDIRECT_MSG_ENABLED': True, diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py index d765ff108..c75a2f275 100644 --- a/apps/jumpserver/settings/auth.py +++ b/apps/jumpserver/settings/auth.py @@ -130,12 +130,14 @@ AUTH_WECOM = CONFIG.AUTH_WECOM WECOM_CORPID = CONFIG.WECOM_CORPID WECOM_AGENTID = CONFIG.WECOM_AGENTID WECOM_SECRET = CONFIG.WECOM_SECRET +WECOM_RENAME_ATTRIBUTES = CONFIG.WECOM_RENAME_ATTRIBUTES # DingDing auth AUTH_DINGTALK = CONFIG.AUTH_DINGTALK DINGTALK_AGENTID = CONFIG.DINGTALK_AGENTID DINGTALK_APPKEY = CONFIG.DINGTALK_APPKEY DINGTALK_APPSECRET = CONFIG.DINGTALK_APPSECRET +DINGTALK_RENAME_ATTRIBUTES = CONFIG.DINGTALK_RENAME_ATTRIBUTES # FeiShu auth AUTH_FEISHU = CONFIG.AUTH_FEISHU @@ -153,6 +155,7 @@ AUTH_SLACK = CONFIG.AUTH_SLACK SLACK_CLIENT_ID = CONFIG.SLACK_CLIENT_ID SLACK_CLIENT_SECRET = CONFIG.SLACK_CLIENT_SECRET SLACK_BOT_TOKEN = CONFIG.SLACK_BOT_TOKEN +SLACK_RENAME_ATTRIBUTES = CONFIG.SLACK_RENAME_ATTRIBUTES # Saml2 auth AUTH_SAML2 = CONFIG.AUTH_SAML2 diff --git a/apps/settings/serializers/auth/dingtalk.py b/apps/settings/serializers/auth/dingtalk.py index d4d4b34d7..0c6a57e51 100644 --- a/apps/settings/serializers/auth/dingtalk.py +++ b/apps/settings/serializers/auth/dingtalk.py @@ -13,3 +13,10 @@ class DingTalkSettingSerializer(serializers.Serializer): DINGTALK_APPKEY = serializers.CharField(max_length=256, required=True, label='App Key') DINGTALK_APPSECRET = EncryptedField(max_length=256, required=False, label='App Secret') AUTH_DINGTALK = serializers.BooleanField(default=False, label=_('Dingtalk')) + DINGTALK_RENAME_ATTRIBUTES = serializers.JSONField( + required=False, label=_('User attribute'), + help_text=_( + 'User attribute mapping, where the `key` is the JumpServer user attribute name and the ' + '`value` is the DingTalk service user attribute name' + ) + ) diff --git a/apps/settings/serializers/auth/feishu.py b/apps/settings/serializers/auth/feishu.py index 81f3f7ee9..e457055c7 100644 --- a/apps/settings/serializers/auth/feishu.py +++ b/apps/settings/serializers/auth/feishu.py @@ -15,7 +15,7 @@ class FeiShuSettingSerializer(serializers.Serializer): FEISHU_RENAME_ATTRIBUTES = serializers.JSONField( required=False, label=_('User attribute'), help_text=_( - "User attribute mapping, where the `key` is the CAS service user attribute name " - "and the `value` is the JumpServer user attribute name" + 'User attribute mapping, where the `key` is the JumpServer user attribute name and the ' + '`value` is the FeiShu service user attribute name' ) - ) \ No newline at end of file + ) diff --git a/apps/settings/serializers/auth/lark.py b/apps/settings/serializers/auth/lark.py index 0726ac334..df12e2c0c 100644 --- a/apps/settings/serializers/auth/lark.py +++ b/apps/settings/serializers/auth/lark.py @@ -15,7 +15,7 @@ class LarkSettingSerializer(serializers.Serializer): LARK_RENAME_ATTRIBUTES = serializers.JSONField( required=False, label=_('User attribute'), help_text=_( - "User attribute mapping, where the `key` is the CAS service user attribute name " - "and the `value` is the JumpServer user attribute name" + 'User attribute mapping, where the `key` is the JumpServer user attribute name and the ' + '`value` is the Lark service user attribute name' ) - ) \ No newline at end of file + ) diff --git a/apps/settings/serializers/auth/slack.py b/apps/settings/serializers/auth/slack.py index 244fd2cbd..c67cb16ea 100644 --- a/apps/settings/serializers/auth/slack.py +++ b/apps/settings/serializers/auth/slack.py @@ -13,3 +13,10 @@ class SlackSettingSerializer(serializers.Serializer): SLACK_CLIENT_ID = serializers.CharField(max_length=256, required=True, label='Client ID') SLACK_CLIENT_SECRET = EncryptedField(max_length=256, required=False, label='Client Secret') SLACK_BOT_TOKEN = EncryptedField(max_length=256, required=False, label='Client bot Token') + SLACK_RENAME_ATTRIBUTES = serializers.JSONField( + required=False, label=_('User attribute'), + help_text=_( + 'User attribute mapping, where the `key` is the JumpServer user attribute name and the ' + '`value` is the Slack service user attribute name' + ) + ) diff --git a/apps/settings/serializers/auth/wecom.py b/apps/settings/serializers/auth/wecom.py index 72c315c24..3e382d16d 100644 --- a/apps/settings/serializers/auth/wecom.py +++ b/apps/settings/serializers/auth/wecom.py @@ -13,3 +13,10 @@ class WeComSettingSerializer(serializers.Serializer): WECOM_AGENTID = serializers.CharField(max_length=256, required=True, label='App Agent ID') WECOM_SECRET = EncryptedField(max_length=256, required=False, label='App Secret') AUTH_WECOM = serializers.BooleanField(default=False, label=_('WeCom')) + WECOM_RENAME_ATTRIBUTES = serializers.JSONField( + required=False, label=_('User attribute'), + help_text=_( + 'User attribute mapping, where the `key` is the JumpServer user attribute name and the ' + '`value` is the WeCom service user attribute name' + ) + ) diff --git a/apps/users/utils.py b/apps/users/utils.py index bb9877a70..ab3886551 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -245,6 +245,26 @@ def construct_user_email(username, email, email_suffix=''): return email or default +def flatten_dict(d, parent_key='', sep='.'): + items = {} + for k, v in d.items(): + new_key = f"{parent_key}{sep}{k}" if parent_key else k + if isinstance(v, dict): + items.update(flatten_dict(v, new_key, sep=sep)) + else: + items[new_key] = v + return items + + +def map_attributes(default_profile, profile, attributes): + detail = default_profile + for local_name, remote_name in attributes.items(): + value = profile.get(remote_name) + if value: + detail[local_name] = value + return detail + + def get_current_org_members(): from orgs.utils import current_org return current_org.get_members()