mirror of https://github.com/jumpserver/jumpserver
feat: 拆分 feishu lark
parent
ccd4f3ada4
commit
470a088a9f
|
@ -36,6 +36,7 @@ class AuthBackendLabelMapping(LazyObject):
|
||||||
backend_label_mapping[settings.AUTH_BACKEND_AUTH_TOKEN] = _("Auth Token")
|
backend_label_mapping[settings.AUTH_BACKEND_AUTH_TOKEN] = _("Auth Token")
|
||||||
backend_label_mapping[settings.AUTH_BACKEND_WECOM] = _("WeCom")
|
backend_label_mapping[settings.AUTH_BACKEND_WECOM] = _("WeCom")
|
||||||
backend_label_mapping[settings.AUTH_BACKEND_FEISHU] = _("FeiShu")
|
backend_label_mapping[settings.AUTH_BACKEND_FEISHU] = _("FeiShu")
|
||||||
|
backend_label_mapping[settings.AUTH_BACKEND_LARK] = 'Lark'
|
||||||
backend_label_mapping[settings.AUTH_BACKEND_SLACK] = _("Slack")
|
backend_label_mapping[settings.AUTH_BACKEND_SLACK] = _("Slack")
|
||||||
backend_label_mapping[settings.AUTH_BACKEND_DINGTALK] = _("DingTalk")
|
backend_label_mapping[settings.AUTH_BACKEND_DINGTALK] = _("DingTalk")
|
||||||
backend_label_mapping[settings.AUTH_BACKEND_TEMP_TOKEN] = _("Temporary token")
|
backend_label_mapping[settings.AUTH_BACKEND_TEMP_TOKEN] = _("Temporary token")
|
||||||
|
|
|
@ -6,6 +6,7 @@ from .common import *
|
||||||
from .confirm import *
|
from .confirm import *
|
||||||
from .connection_token import *
|
from .connection_token import *
|
||||||
from .feishu import *
|
from .feishu import *
|
||||||
|
from .lark import *
|
||||||
from .login_confirm import *
|
from .login_confirm import *
|
||||||
from .mfa import *
|
from .mfa import *
|
||||||
from .password import *
|
from .password import *
|
||||||
|
|
|
@ -12,7 +12,6 @@ from common.permissions import IsValidUser, OnlySuperUser
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from users.models import User
|
from users.models import User
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,6 +23,7 @@ class QRUnBindBase(APIView):
|
||||||
'wecom': {'user_field': 'wecom_id', 'not_bind_err': errors.WeComNotBound},
|
'wecom': {'user_field': 'wecom_id', 'not_bind_err': errors.WeComNotBound},
|
||||||
'dingtalk': {'user_field': 'dingtalk_id', 'not_bind_err': errors.DingTalkNotBound},
|
'dingtalk': {'user_field': 'dingtalk_id', 'not_bind_err': errors.DingTalkNotBound},
|
||||||
'feishu': {'user_field': 'feishu_id', 'not_bind_err': errors.FeiShuNotBound},
|
'feishu': {'user_field': 'feishu_id', 'not_bind_err': errors.FeiShuNotBound},
|
||||||
|
'lark': {'user_field': 'lark_id', 'not_bind_err': errors.LarkNotBound},
|
||||||
'slack': {'user_field': 'slack_id', 'not_bind_err': errors.SlackNotBound},
|
'slack': {'user_field': 'slack_id', 'not_bind_err': errors.SlackNotBound},
|
||||||
}
|
}
|
||||||
user = self.user
|
user = self.user
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
from common.utils import get_logger
|
||||||
|
from .feishu import FeiShuEventSubscriptionCallback
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class LarkEventSubscriptionCallback(FeiShuEventSubscriptionCallback):
|
||||||
|
pass
|
|
@ -55,6 +55,12 @@ class FeiShuAuthentication(JMSModelBackend):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LarkAuthentication(FeiShuAuthentication):
|
||||||
|
@staticmethod
|
||||||
|
def is_enabled():
|
||||||
|
return settings.AUTH_LARK
|
||||||
|
|
||||||
|
|
||||||
class SlackAuthentication(JMSModelBackend):
|
class SlackAuthentication(JMSModelBackend):
|
||||||
"""
|
"""
|
||||||
什么也不做呀😺
|
什么也不做呀😺
|
||||||
|
@ -72,5 +78,6 @@ class AuthorizationTokenAuthentication(JMSModelBackend):
|
||||||
"""
|
"""
|
||||||
什么也不做呀😺
|
什么也不做呀😺
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def authenticate(self, request, **kwargs):
|
def authenticate(self, request, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -33,6 +33,11 @@ class FeiShuNotBound(JMSException):
|
||||||
default_detail = _('FeiShu is not bound')
|
default_detail = _('FeiShu is not bound')
|
||||||
|
|
||||||
|
|
||||||
|
class LarkNotBound(JMSException):
|
||||||
|
default_code = 'lark_not_bound'
|
||||||
|
default_detail = _('Lark is not bound')
|
||||||
|
|
||||||
|
|
||||||
class SlackNotBound(JMSException):
|
class SlackNotBound(JMSException):
|
||||||
default_code = 'slack_not_bound'
|
default_code = 'slack_not_bound'
|
||||||
default_detail = _('Slack is not bound')
|
default_detail = _('Slack is not bound')
|
||||||
|
|
|
@ -22,6 +22,9 @@ urlpatterns = [
|
||||||
path('feishu/event/subscription/callback/', api.FeiShuEventSubscriptionCallback.as_view(),
|
path('feishu/event/subscription/callback/', api.FeiShuEventSubscriptionCallback.as_view(),
|
||||||
name='feishu-event-subscription-callback'),
|
name='feishu-event-subscription-callback'),
|
||||||
|
|
||||||
|
path('lark/event/subscription/callback/', api.LarkEventSubscriptionCallback.as_view(),
|
||||||
|
name='lark-event-subscription-callback'),
|
||||||
|
|
||||||
path('auth/', api.TokenCreateApi.as_view(), name='user-auth'),
|
path('auth/', api.TokenCreateApi.as_view(), name='user-auth'),
|
||||||
path('confirm-oauth/', api.ConfirmBindORUNBindOAuth.as_view(), name='confirm-oauth'),
|
path('confirm-oauth/', api.ConfirmBindORUNBindOAuth.as_view(), name='confirm-oauth'),
|
||||||
path('tokens/', api.TokenCreateApi.as_view(), name='auth-token'),
|
path('tokens/', api.TokenCreateApi.as_view(), name='auth-token'),
|
||||||
|
|
|
@ -49,6 +49,12 @@ urlpatterns = [
|
||||||
path('feishu/qr/bind/callback/', views.FeiShuQRBindCallbackView.as_view(), name='feishu-qr-bind-callback'),
|
path('feishu/qr/bind/callback/', views.FeiShuQRBindCallbackView.as_view(), name='feishu-qr-bind-callback'),
|
||||||
path('feishu/qr/login/callback/', views.FeiShuQRLoginCallbackView.as_view(), name='feishu-qr-login-callback'),
|
path('feishu/qr/login/callback/', views.FeiShuQRLoginCallbackView.as_view(), name='feishu-qr-login-callback'),
|
||||||
|
|
||||||
|
path('lark/bind/start/', views.LarkEnableStartView.as_view(), name='lark-bind-start'),
|
||||||
|
path('lark/qr/bind/', views.LarkQRBindView.as_view(), name='lark-qr-bind'),
|
||||||
|
path('lark/qr/login/', views.LarkQRLoginView.as_view(), name='lark-qr-login'),
|
||||||
|
path('lark/qr/bind/callback/', views.LarkQRBindCallbackView.as_view(), name='lark-qr-bind-callback'),
|
||||||
|
path('lark/qr/login/callback/', views.LarkQRLoginCallbackView.as_view(), name='lark-qr-login-callback'),
|
||||||
|
|
||||||
path('slack/bind/start/', views.SlackEnableStartView.as_view(), name='slack-bind-start'),
|
path('slack/bind/start/', views.SlackEnableStartView.as_view(), name='slack-bind-start'),
|
||||||
path('slack/qr/bind/', views.SlackQRBindView.as_view(), name='slack-qr-bind'),
|
path('slack/qr/bind/', views.SlackQRBindView.as_view(), name='slack-qr-bind'),
|
||||||
path('slack/qr/login/', views.SlackQRLoginView.as_view(), name='slack-qr-login'),
|
path('slack/qr/login/', views.SlackQRLoginView.as_view(), name='slack-qr-login'),
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from .login import *
|
|
||||||
from .mfa import *
|
|
||||||
from .wecom import *
|
|
||||||
from .dingtalk import *
|
from .dingtalk import *
|
||||||
from .feishu import *
|
from .feishu import *
|
||||||
|
from .lark import *
|
||||||
|
from .login import *
|
||||||
|
from .mfa import *
|
||||||
from .slack import *
|
from .slack import *
|
||||||
|
from .wecom import *
|
||||||
|
|
|
@ -21,24 +21,45 @@ from .mixins import FlashMessageMixin
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
FEISHU_STATE_SESSION_KEY = '_feishu_state'
|
|
||||||
|
class FeiShuEnableStartView(UserVerifyPasswordView):
|
||||||
|
category = 'feishu'
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
referer = self.request.META.get('HTTP_REFERER')
|
||||||
|
redirect_url = self.request.GET.get("redirect_url")
|
||||||
|
|
||||||
|
success_url = reverse(f'authentication:{self.category}-qr-bind')
|
||||||
|
|
||||||
|
success_url += '?' + urlencode({
|
||||||
|
'redirect_url': redirect_url or referer
|
||||||
|
})
|
||||||
|
|
||||||
|
return success_url
|
||||||
|
|
||||||
|
|
||||||
class FeiShuQRMixin(UserConfirmRequiredExceptionMixin, PermissionsMixin, FlashMessageMixin, View):
|
class FeiShuQRMixin(UserConfirmRequiredExceptionMixin, PermissionsMixin, FlashMessageMixin, View):
|
||||||
|
category = 'feishu'
|
||||||
|
error = _('FeiShu Error')
|
||||||
|
error_msg = _('FeiShu is already bound')
|
||||||
|
state_session_key = f'_{category}_state'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_object(self):
|
||||||
|
return URL()
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
except APIException as e:
|
except APIException as e:
|
||||||
msg = str(e.detail)
|
msg = str(e.detail)
|
||||||
return self.get_failed_response(
|
return self.get_failed_response(
|
||||||
'/',
|
'/', self.error, msg
|
||||||
_('FeiShu Error'),
|
|
||||||
msg
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def verify_state(self):
|
def verify_state(self):
|
||||||
state = self.request.GET.get('state')
|
state = self.request.GET.get('state')
|
||||||
session_state = self.request.session.get(FEISHU_STATE_SESSION_KEY)
|
session_state = self.request.session.get(self.state_session_key)
|
||||||
if state != session_state:
|
if state != session_state:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
@ -49,19 +70,18 @@ class FeiShuQRMixin(UserConfirmRequiredExceptionMixin, PermissionsMixin, FlashMe
|
||||||
|
|
||||||
def get_qr_url(self, redirect_uri):
|
def get_qr_url(self, redirect_uri):
|
||||||
state = random_string(16)
|
state = random_string(16)
|
||||||
self.request.session[FEISHU_STATE_SESSION_KEY] = state
|
self.request.session[self.state_session_key] = state
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
'app_id': settings.FEISHU_APP_ID,
|
'app_id': getattr(settings, f'{self.category}_APP_ID'.upper()),
|
||||||
'state': state,
|
'state': state,
|
||||||
'redirect_uri': redirect_uri,
|
'redirect_uri': redirect_uri,
|
||||||
}
|
}
|
||||||
url = URL().authen + '?' + urlencode(params)
|
url = self.url_object.authen + '?' + urlencode(params)
|
||||||
return url
|
return url
|
||||||
|
|
||||||
def get_already_bound_response(self, redirect_url):
|
def get_already_bound_response(self, redirect_url):
|
||||||
msg = _('FeiShu is already bound')
|
response = self.get_failed_response(redirect_url, self.error_msg, self.error_msg)
|
||||||
response = self.get_failed_response(redirect_url, msg, msg)
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@ -71,7 +91,7 @@ class FeiShuQRBindView(FeiShuQRMixin, View):
|
||||||
def get(self, request: HttpRequest):
|
def get(self, request: HttpRequest):
|
||||||
redirect_url = request.GET.get('redirect_url')
|
redirect_url = request.GET.get('redirect_url')
|
||||||
|
|
||||||
redirect_uri = reverse('authentication:feishu-qr-bind-callback', external=True)
|
redirect_uri = reverse(f'authentication:{self.category}-qr-bind-callback', external=True)
|
||||||
redirect_uri += '?' + urlencode({'redirect_url': redirect_url})
|
redirect_uri += '?' + urlencode({'redirect_url': redirect_url})
|
||||||
|
|
||||||
url = self.get_qr_url(redirect_uri)
|
url = self.get_qr_url(redirect_uri)
|
||||||
|
@ -81,25 +101,16 @@ class FeiShuQRBindView(FeiShuQRMixin, View):
|
||||||
class FeiShuQRBindCallbackView(FeiShuQRMixin, BaseBindCallbackView):
|
class FeiShuQRBindCallbackView(FeiShuQRMixin, BaseBindCallbackView):
|
||||||
permission_classes = (IsAuthenticated,)
|
permission_classes = (IsAuthenticated,)
|
||||||
|
|
||||||
client_type_path = 'common.sdk.im.feishu.FeiShu'
|
|
||||||
client_auth_params = {'app_id': 'FEISHU_APP_ID', 'app_secret': 'FEISHU_APP_SECRET'}
|
|
||||||
auth_type = 'feishu'
|
auth_type = 'feishu'
|
||||||
auth_type_label = _('FeiShu')
|
auth_type_label = _('FeiShu')
|
||||||
|
client_type_path = f'common.sdk.im.{auth_type}.FeiShu'
|
||||||
|
|
||||||
|
@property
|
||||||
class FeiShuEnableStartView(UserVerifyPasswordView):
|
def client_auth_params(self):
|
||||||
|
return {
|
||||||
def get_success_url(self):
|
'app_id': f'{self.auth_type}_APP_ID'.upper(),
|
||||||
referer = self.request.META.get('HTTP_REFERER')
|
'app_secret': f'{self.auth_type}_APP_SECRET'.upper()
|
||||||
redirect_url = self.request.GET.get("redirect_url")
|
}
|
||||||
|
|
||||||
success_url = reverse('authentication:feishu-qr-bind')
|
|
||||||
|
|
||||||
success_url += '?' + urlencode({
|
|
||||||
'redirect_url': redirect_url or referer
|
|
||||||
})
|
|
||||||
|
|
||||||
return success_url
|
|
||||||
|
|
||||||
|
|
||||||
class FeiShuQRLoginView(FeiShuQRMixin, View):
|
class FeiShuQRLoginView(FeiShuQRMixin, View):
|
||||||
|
@ -107,7 +118,7 @@ class FeiShuQRLoginView(FeiShuQRMixin, View):
|
||||||
|
|
||||||
def get(self, request: HttpRequest):
|
def get(self, request: HttpRequest):
|
||||||
redirect_url = request.GET.get('redirect_url') or reverse('index')
|
redirect_url = request.GET.get('redirect_url') or reverse('index')
|
||||||
redirect_uri = reverse('authentication:feishu-qr-login-callback', external=True)
|
redirect_uri = reverse(f'authentication:{self.category}-qr-login-callback', external=True)
|
||||||
redirect_uri += '?' + urlencode({
|
redirect_uri += '?' + urlencode({
|
||||||
'redirect_url': redirect_url,
|
'redirect_url': redirect_url,
|
||||||
})
|
})
|
||||||
|
@ -119,11 +130,19 @@ class FeiShuQRLoginView(FeiShuQRMixin, View):
|
||||||
class FeiShuQRLoginCallbackView(FeiShuQRMixin, BaseLoginCallbackView):
|
class FeiShuQRLoginCallbackView(FeiShuQRMixin, BaseLoginCallbackView):
|
||||||
permission_classes = (AllowAny,)
|
permission_classes = (AllowAny,)
|
||||||
|
|
||||||
client_type_path = 'common.sdk.im.feishu.FeiShu'
|
|
||||||
client_auth_params = {'app_id': 'FEISHU_APP_ID', 'app_secret': 'FEISHU_APP_SECRET'}
|
|
||||||
user_type = 'feishu'
|
user_type = 'feishu'
|
||||||
auth_backend = 'AUTH_BACKEND_FEISHU'
|
auth_type = user_type
|
||||||
|
client_type_path = f'common.sdk.im.{auth_type}.FeiShu'
|
||||||
|
|
||||||
msg_client_err = _('FeiShu Error')
|
msg_client_err = _('FeiShu Error')
|
||||||
msg_user_not_bound_err = _('FeiShu is not bound')
|
msg_user_not_bound_err = _('FeiShu is not bound')
|
||||||
msg_not_found_user_from_client_err = _('Failed to get user from FeiShu')
|
msg_not_found_user_from_client_err = _('Failed to get user from FeiShu')
|
||||||
|
|
||||||
|
auth_backend = f'AUTH_BACKEND_{auth_type}'.upper()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def client_auth_params(self):
|
||||||
|
return {
|
||||||
|
'app_id': f'{self.auth_type}_APP_ID'.upper(),
|
||||||
|
'app_secret': f'{self.auth_type}_APP_SECRET'.upper()
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from common.sdk.im.lark import URL
|
||||||
|
from common.utils import get_logger
|
||||||
|
from .feishu import (
|
||||||
|
FeiShuEnableStartView, FeiShuQRBindView, FeiShuQRBindCallbackView,
|
||||||
|
FeiShuQRLoginView, FeiShuQRLoginCallbackView
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
class LarkEnableStartView(FeiShuEnableStartView):
|
||||||
|
category = 'lark'
|
||||||
|
|
||||||
|
|
||||||
|
class BaseLarkQRMixin:
|
||||||
|
category = 'lark'
|
||||||
|
error = _('Lark Error')
|
||||||
|
error_msg = _('Lark is already bound')
|
||||||
|
state_session_key = f'_{category}_state'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_object(self):
|
||||||
|
return URL()
|
||||||
|
|
||||||
|
|
||||||
|
class LarkQRBindView(BaseLarkQRMixin, FeiShuQRBindView):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LarkQRBindCallbackView(BaseLarkQRMixin, FeiShuQRBindCallbackView):
|
||||||
|
auth_type = 'lark'
|
||||||
|
auth_type_label = auth_type.capitalize()
|
||||||
|
client_type_path = f'common.sdk.im.{auth_type}.Lark'
|
||||||
|
|
||||||
|
|
||||||
|
class LarkQRLoginView(BaseLarkQRMixin, FeiShuQRLoginView):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LarkQRLoginCallbackView(BaseLarkQRMixin, FeiShuQRLoginCallbackView):
|
||||||
|
user_type = 'lark'
|
||||||
|
auth_type = user_type
|
||||||
|
client_type_path = f'common.sdk.im.{auth_type}.Lark'
|
||||||
|
|
||||||
|
msg_client_err = _('Lark Error')
|
||||||
|
msg_user_not_bound_err = _('Lark is not bound')
|
||||||
|
msg_not_found_user_from_client_err = _('Failed to get user from Lark')
|
|
@ -91,6 +91,12 @@ class UserLoginContextMixin:
|
||||||
'url': reverse('authentication:feishu-qr-login'),
|
'url': reverse('authentication:feishu-qr-login'),
|
||||||
'logo': static('img/login_feishu_logo.png')
|
'logo': static('img/login_feishu_logo.png')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'Lark',
|
||||||
|
'enabled': settings.AUTH_LARK,
|
||||||
|
'url': reverse('authentication:lark-qr-login'),
|
||||||
|
'logo': static('img/login_feishu_logo.png')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'name': _('Slack'),
|
'name': _('Slack'),
|
||||||
'enabled': settings.AUTH_SLACK,
|
'enabled': settings.AUTH_SLACK,
|
||||||
|
|
|
@ -2,24 +2,18 @@ import json
|
||||||
|
|
||||||
from rest_framework.exceptions import APIException
|
from rest_framework.exceptions import APIException
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from users.utils import construct_user_email
|
|
||||||
from common.utils.common import get_logger
|
|
||||||
from common.sdk.im.utils import digest
|
|
||||||
from common.sdk.im.mixin import RequestMixin, BaseRequest
|
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
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class URL:
|
class URL:
|
||||||
# https://open.feishu.cn/document/ukTMukTMukTM/uEDO4UjLxgDO14SM4gTN
|
# https://open.feishu.cn/document/ukTMukTMukTM/uEDO4UjLxgDO14SM4gTN
|
||||||
@property
|
|
||||||
def host(self):
|
host = 'https://open.feishu.cn'
|
||||||
if settings.FEISHU_VERSION == 'feishu':
|
|
||||||
h = 'https://open.feishu.cn'
|
|
||||||
else:
|
|
||||||
h = 'https://open.larksuite.com'
|
|
||||||
return h
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def authen(self):
|
def authen(self):
|
||||||
|
@ -87,12 +81,13 @@ class FeiShu(RequestMixin):
|
||||||
"""
|
"""
|
||||||
非业务数据导致的错误直接抛异常,说明是系统配置错误,业务代码不用理会
|
非业务数据导致的错误直接抛异常,说明是系统配置错误,业务代码不用理会
|
||||||
"""
|
"""
|
||||||
|
requests_cls = FeishuRequests
|
||||||
|
|
||||||
def __init__(self, app_id, app_secret, timeout=None):
|
def __init__(self, app_id, app_secret, timeout=None):
|
||||||
self._app_id = app_id or ''
|
self._app_id = app_id or ''
|
||||||
self._app_secret = app_secret or ''
|
self._app_secret = app_secret or ''
|
||||||
|
|
||||||
self._requests = FeishuRequests(
|
self._requests = self.requests_cls(
|
||||||
app_id=app_id,
|
app_id=app_id,
|
||||||
app_secret=app_secret,
|
app_secret=app_secret,
|
||||||
timeout=timeout
|
timeout=timeout
|
||||||
|
@ -130,7 +125,7 @@ class FeiShu(RequestMixin):
|
||||||
body['receive_id'] = user_id
|
body['receive_id'] = user_id
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.info(f'Feishu send text: user_ids={user_ids} msg={msg}')
|
logger.info(f'{self.__class__.__name__} send text: user_ids={user_ids} msg={msg}')
|
||||||
self._requests.post(URL().send_message, params=params, json=body)
|
self._requests.post(URL().send_message, params=params, json=body)
|
||||||
except APIException as e:
|
except APIException as e:
|
||||||
# 只处理可预知的错误
|
# 只处理可预知的错误
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
from common.utils.common import get_logger
|
||||||
|
from ..feishu import URL as FeiShuURL, FeishuRequests, FeiShu
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class URL(FeiShuURL):
|
||||||
|
host = 'https://open.larksuite.com'
|
||||||
|
|
||||||
|
|
||||||
|
class LarkRequests(FeishuRequests):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Lark(FeiShu):
|
||||||
|
requests_cls = LarkRequests
|
|
@ -407,7 +407,11 @@ class Config(dict):
|
||||||
'AUTH_FEISHU': False,
|
'AUTH_FEISHU': False,
|
||||||
'FEISHU_APP_ID': '',
|
'FEISHU_APP_ID': '',
|
||||||
'FEISHU_APP_SECRET': '',
|
'FEISHU_APP_SECRET': '',
|
||||||
'FEISHU_VERSION': 'feishu',
|
|
||||||
|
# Lark
|
||||||
|
'AUTH_LARK': False,
|
||||||
|
'LARK_APP_ID': '',
|
||||||
|
'LARK_APP_SECRET': '',
|
||||||
|
|
||||||
# Slack
|
# Slack
|
||||||
'AUTH_SLACK': False,
|
'AUTH_SLACK': False,
|
||||||
|
|
|
@ -141,7 +141,10 @@ DINGTALK_APPSECRET = CONFIG.DINGTALK_APPSECRET
|
||||||
AUTH_FEISHU = CONFIG.AUTH_FEISHU
|
AUTH_FEISHU = CONFIG.AUTH_FEISHU
|
||||||
FEISHU_APP_ID = CONFIG.FEISHU_APP_ID
|
FEISHU_APP_ID = CONFIG.FEISHU_APP_ID
|
||||||
FEISHU_APP_SECRET = CONFIG.FEISHU_APP_SECRET
|
FEISHU_APP_SECRET = CONFIG.FEISHU_APP_SECRET
|
||||||
FEISHU_VERSION = CONFIG.FEISHU_VERSION
|
|
||||||
|
AUTH_LARK = CONFIG.AUTH_LARK
|
||||||
|
LARK_APP_ID = CONFIG.LARK_APP_ID
|
||||||
|
LARK_APP_SECRET = CONFIG.LARK_APP_SECRET
|
||||||
|
|
||||||
# Slack auth
|
# Slack auth
|
||||||
AUTH_SLACK = CONFIG.AUTH_SLACK
|
AUTH_SLACK = CONFIG.AUTH_SLACK
|
||||||
|
@ -212,6 +215,7 @@ AUTH_BACKEND_SSO = 'authentication.backends.sso.SSOAuthentication'
|
||||||
AUTH_BACKEND_WECOM = 'authentication.backends.sso.WeComAuthentication'
|
AUTH_BACKEND_WECOM = 'authentication.backends.sso.WeComAuthentication'
|
||||||
AUTH_BACKEND_DINGTALK = 'authentication.backends.sso.DingTalkAuthentication'
|
AUTH_BACKEND_DINGTALK = 'authentication.backends.sso.DingTalkAuthentication'
|
||||||
AUTH_BACKEND_FEISHU = 'authentication.backends.sso.FeiShuAuthentication'
|
AUTH_BACKEND_FEISHU = 'authentication.backends.sso.FeiShuAuthentication'
|
||||||
|
AUTH_BACKEND_LARK = 'authentication.backends.sso.LarkAuthentication'
|
||||||
AUTH_BACKEND_SLACK = 'authentication.backends.sso.SlackAuthentication'
|
AUTH_BACKEND_SLACK = 'authentication.backends.sso.SlackAuthentication'
|
||||||
AUTH_BACKEND_AUTH_TOKEN = 'authentication.backends.sso.AuthorizationTokenAuthentication'
|
AUTH_BACKEND_AUTH_TOKEN = 'authentication.backends.sso.AuthorizationTokenAuthentication'
|
||||||
AUTH_BACKEND_SAML2 = 'authentication.backends.saml2.SAML2Backend'
|
AUTH_BACKEND_SAML2 = 'authentication.backends.saml2.SAML2Backend'
|
||||||
|
@ -228,7 +232,7 @@ AUTHENTICATION_BACKENDS = [
|
||||||
AUTH_BACKEND_CAS, AUTH_BACKEND_OIDC_PASSWORD, AUTH_BACKEND_OIDC_CODE, AUTH_BACKEND_SAML2,
|
AUTH_BACKEND_CAS, AUTH_BACKEND_OIDC_PASSWORD, AUTH_BACKEND_OIDC_CODE, AUTH_BACKEND_SAML2,
|
||||||
AUTH_BACKEND_OAUTH2,
|
AUTH_BACKEND_OAUTH2,
|
||||||
# 扫码模式
|
# 扫码模式
|
||||||
AUTH_BACKEND_WECOM, AUTH_BACKEND_DINGTALK, AUTH_BACKEND_FEISHU, AUTH_BACKEND_SLACK,
|
AUTH_BACKEND_WECOM, AUTH_BACKEND_DINGTALK, AUTH_BACKEND_FEISHU, AUTH_BACKEND_LARK, AUTH_BACKEND_SLACK,
|
||||||
# Token模式
|
# Token模式
|
||||||
AUTH_BACKEND_AUTH_TOKEN, AUTH_BACKEND_SSO, AUTH_BACKEND_TEMP_TOKEN,
|
AUTH_BACKEND_AUTH_TOKEN, AUTH_BACKEND_SSO, AUTH_BACKEND_TEMP_TOKEN,
|
||||||
AUTH_BACKEND_PASSKEY
|
AUTH_BACKEND_PASSKEY
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
client_name_mapper = {}
|
client_name_mapper = {}
|
||||||
|
|
||||||
|
@ -12,7 +12,9 @@ class BACKEND(models.TextChoices):
|
||||||
DINGTALK = 'dingtalk', _('DingTalk')
|
DINGTALK = 'dingtalk', _('DingTalk')
|
||||||
SITE_MSG = 'site_msg', _('Site message')
|
SITE_MSG = 'site_msg', _('Site message')
|
||||||
FEISHU = 'feishu', _('FeiShu')
|
FEISHU = 'feishu', _('FeiShu')
|
||||||
|
LARK = 'lark', 'Lark'
|
||||||
SLACK = 'slack', _('Slack')
|
SLACK = 'slack', _('Slack')
|
||||||
|
|
||||||
# SMS = 'sms', _('SMS')
|
# SMS = 'sms', _('SMS')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from common.sdk.im.lark import Lark as Client
|
||||||
|
from .base import BackendBase
|
||||||
|
|
||||||
|
|
||||||
|
class Lark(BackendBase):
|
||||||
|
account_field = 'lark_id'
|
||||||
|
is_enable_field_in_settings = 'AUTH_LARK'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.client = Client(
|
||||||
|
app_id=settings.LARK_APP_ID,
|
||||||
|
app_secret=settings.LARK_APP_SECRET
|
||||||
|
)
|
||||||
|
|
||||||
|
def send_msg(self, users, message, subject=None):
|
||||||
|
accounts, __, __ = self.get_accounts(users)
|
||||||
|
print('lark', message)
|
||||||
|
return self.client.send_text(accounts, message)
|
||||||
|
|
||||||
|
|
||||||
|
backend = Lark
|
|
@ -196,6 +196,9 @@ class Message(metaclass=MessageType):
|
||||||
def get_feishu_msg(self) -> dict:
|
def get_feishu_msg(self) -> dict:
|
||||||
return self.markdown_msg
|
return self.markdown_msg
|
||||||
|
|
||||||
|
def get_lark_msg(self) -> dict:
|
||||||
|
return self.markdown_msg
|
||||||
|
|
||||||
def get_email_msg(self) -> dict:
|
def get_email_msg(self) -> dict:
|
||||||
return self.html_msg_with_sign
|
return self.html_msg_with_sign
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ from .chat import *
|
||||||
from .dingtalk import *
|
from .dingtalk import *
|
||||||
from .email import *
|
from .email import *
|
||||||
from .feishu import *
|
from .feishu import *
|
||||||
|
from .lark import *
|
||||||
from .ldap import *
|
from .ldap import *
|
||||||
from .public import *
|
from .public import *
|
||||||
from .security import *
|
from .security import *
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
from rest_framework.views import Response
|
|
||||||
from rest_framework.generics import GenericAPIView
|
|
||||||
from rest_framework.exceptions import APIException
|
|
||||||
from rest_framework import status
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.exceptions import APIException
|
||||||
|
from rest_framework.generics import GenericAPIView
|
||||||
|
from rest_framework.views import Response
|
||||||
|
|
||||||
from settings.models import Setting
|
|
||||||
from common.sdk.im.feishu import FeiShu
|
from common.sdk.im.feishu import FeiShu
|
||||||
|
from settings.models import Setting
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
|
|
||||||
|
|
||||||
class FeiShuTestingAPI(GenericAPIView):
|
class FeiShuTestingAPI(GenericAPIView):
|
||||||
|
category = 'FEISHU'
|
||||||
serializer_class = serializers.FeiShuSettingSerializer
|
serializer_class = serializers.FeiShuSettingSerializer
|
||||||
rbac_perms = {
|
rbac_perms = {
|
||||||
'POST': 'settings.change_auth'
|
'POST': 'settings.change_auth'
|
||||||
|
@ -20,11 +20,11 @@ class FeiShuTestingAPI(GenericAPIView):
|
||||||
serializer = self.serializer_class(data=request.data)
|
serializer = self.serializer_class(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
app_id = serializer.validated_data['FEISHU_APP_ID']
|
app_id = serializer.validated_data[f'{self.category}_APP_ID']
|
||||||
app_secret = serializer.validated_data.get('FEISHU_APP_SECRET')
|
app_secret = serializer.validated_data.get(f'{self.category}_APP_SECRET')
|
||||||
|
|
||||||
if not app_secret:
|
if not app_secret:
|
||||||
secret = Setting.objects.filter(name='FEISHU_APP_SECRET').first()
|
secret = Setting.objects.filter(name=f'{self.category}_APP_SECRET').first()
|
||||||
if secret:
|
if secret:
|
||||||
app_secret = secret.cleaned_value
|
app_secret = secret.cleaned_value
|
||||||
|
|
||||||
|
@ -40,3 +40,8 @@ class FeiShuTestingAPI(GenericAPIView):
|
||||||
except:
|
except:
|
||||||
error = e.detail
|
error = e.detail
|
||||||
return Response(status=status.HTTP_400_BAD_REQUEST, data={'error': error})
|
return Response(status=status.HTTP_400_BAD_REQUEST, data={'error': error})
|
||||||
|
|
||||||
|
|
||||||
|
class LarkTestingAPI(FeiShuTestingAPI):
|
||||||
|
category = 'LARK'
|
||||||
|
serializer_class = serializers.LarkSettingSerializer
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
from .feishu import FeiShuTestingAPI
|
||||||
|
from .. import serializers
|
||||||
|
|
||||||
|
|
||||||
|
class LarkTestingAPI(FeiShuTestingAPI):
|
||||||
|
category = 'LARK'
|
||||||
|
serializer_class = serializers.LarkSettingSerializer
|
|
@ -39,6 +39,7 @@ class SettingsApi(generics.RetrieveUpdateAPIView):
|
||||||
'wecom': serializers.WeComSettingSerializer,
|
'wecom': serializers.WeComSettingSerializer,
|
||||||
'dingtalk': serializers.DingTalkSettingSerializer,
|
'dingtalk': serializers.DingTalkSettingSerializer,
|
||||||
'feishu': serializers.FeiShuSettingSerializer,
|
'feishu': serializers.FeiShuSettingSerializer,
|
||||||
|
'lark': serializers.LarkSettingSerializer,
|
||||||
'slack': serializers.SlackSettingSerializer,
|
'slack': serializers.SlackSettingSerializer,
|
||||||
'auth': serializers.AuthSettingSerializer,
|
'auth': serializers.AuthSettingSerializer,
|
||||||
'oidc': serializers.OIDCSettingSerializer,
|
'oidc': serializers.OIDCSettingSerializer,
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Generated by Django 4.1.13 on 2024-03-26 07:31
|
||||||
|
import json
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from django.db.models import F
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_feishu_to_lark(apps, schema_editor):
|
||||||
|
setting_model = apps.get_model("settings", "Setting")
|
||||||
|
user_model = apps.get_model("users", "User")
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
|
||||||
|
feishu_version_instance = setting_model.objects.using(db_alias).filter(name='FEISHU_VERSION').first()
|
||||||
|
if not feishu_version_instance or json.loads(feishu_version_instance.value) == 'feishu':
|
||||||
|
return
|
||||||
|
feishu_version_instance.delete()
|
||||||
|
|
||||||
|
user_model.objects.using(db_alias).filter(feishu_id__isnull=False).update(lark_id=F('feishu_id'))
|
||||||
|
user_model.objects.filter(feishu_id__isnull=False).update(lark_id=F('feishu_id'))
|
||||||
|
user_model.objects.filter(feishu_id__isnull=False).update(feishu_id=None)
|
||||||
|
|
||||||
|
settings_to_update = [
|
||||||
|
('AUTH_FEISHU', 'AUTH_LARK'),
|
||||||
|
('FEISHU_APP_ID', 'LARK_APP_ID'),
|
||||||
|
('FEISHU_APP_SECRET', 'LARK_APP_SECRET'),
|
||||||
|
]
|
||||||
|
|
||||||
|
for old_name, new_name in settings_to_update:
|
||||||
|
setting_model.objects.using(db_alias).filter(
|
||||||
|
name=old_name
|
||||||
|
).update(name=new_name)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('settings', '0012_alter_setting_options'),
|
||||||
|
('users', '0050_user_lark_id_alter_user_source'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(migrate_feishu_to_lark),
|
||||||
|
]
|
|
@ -2,13 +2,14 @@ from .base import *
|
||||||
from .cas import *
|
from .cas import *
|
||||||
from .dingtalk import *
|
from .dingtalk import *
|
||||||
from .feishu import *
|
from .feishu import *
|
||||||
|
from .lark import *
|
||||||
from .ldap import *
|
from .ldap import *
|
||||||
from .oauth2 import *
|
from .oauth2 import *
|
||||||
from .oidc import *
|
from .oidc import *
|
||||||
from .passkey import *
|
from .passkey import *
|
||||||
from .radius import *
|
from .radius import *
|
||||||
from .saml2 import *
|
from .saml2 import *
|
||||||
|
from .slack import *
|
||||||
from .sms import *
|
from .sms import *
|
||||||
from .sso import *
|
from .sso import *
|
||||||
from .wecom import *
|
from .wecom import *
|
||||||
from .slack import *
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ class AuthSettingSerializer(serializers.Serializer):
|
||||||
AUTH_RADIUS = serializers.BooleanField(required=False, label=_('RADIUS Auth'))
|
AUTH_RADIUS = serializers.BooleanField(required=False, label=_('RADIUS Auth'))
|
||||||
AUTH_DINGTALK = serializers.BooleanField(default=False, label=_('DingTalk Auth'))
|
AUTH_DINGTALK = serializers.BooleanField(default=False, label=_('DingTalk Auth'))
|
||||||
AUTH_FEISHU = serializers.BooleanField(default=False, label=_('FeiShu Auth'))
|
AUTH_FEISHU = serializers.BooleanField(default=False, label=_('FeiShu Auth'))
|
||||||
|
AUTH_LARK = serializers.BooleanField(default=False, label=_('Lark Auth'))
|
||||||
AUTH_WECOM = serializers.BooleanField(default=False, label=_('Slack Auth'))
|
AUTH_WECOM = serializers.BooleanField(default=False, label=_('Slack Auth'))
|
||||||
AUTH_SLACK = serializers.BooleanField(default=False, label=_('WeCom Auth'))
|
AUTH_SLACK = serializers.BooleanField(default=False, label=_('WeCom Auth'))
|
||||||
AUTH_SSO = serializers.BooleanField(default=False, label=_("SSO Auth"))
|
AUTH_SSO = serializers.BooleanField(default=False, label=_("SSO Auth"))
|
||||||
|
|
|
@ -9,13 +9,6 @@ __all__ = ['FeiShuSettingSerializer']
|
||||||
class FeiShuSettingSerializer(serializers.Serializer):
|
class FeiShuSettingSerializer(serializers.Serializer):
|
||||||
PREFIX_TITLE = _('FeiShu')
|
PREFIX_TITLE = _('FeiShu')
|
||||||
|
|
||||||
VERSION_CHOICES = (
|
|
||||||
('feishu', _('FeiShu')),
|
|
||||||
('lark', 'Lark')
|
|
||||||
)
|
|
||||||
AUTH_FEISHU = serializers.BooleanField(default=False, label=_('Enable FeiShu Auth'))
|
AUTH_FEISHU = serializers.BooleanField(default=False, label=_('Enable FeiShu Auth'))
|
||||||
FEISHU_APP_ID = serializers.CharField(max_length=256, required=True, label='App ID')
|
FEISHU_APP_ID = serializers.CharField(max_length=256, required=True, label='App ID')
|
||||||
FEISHU_APP_SECRET = EncryptedField(max_length=256, required=False, label='App Secret')
|
FEISHU_APP_SECRET = EncryptedField(max_length=256, required=False, label='App Secret')
|
||||||
FEISHU_VERSION = serializers.ChoiceField(
|
|
||||||
choices=VERSION_CHOICES, default='feishu', label=_('Version')
|
|
||||||
)
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from common.serializers.fields import EncryptedField
|
||||||
|
|
||||||
|
__all__ = ['LarkSettingSerializer']
|
||||||
|
|
||||||
|
|
||||||
|
class LarkSettingSerializer(serializers.Serializer):
|
||||||
|
PREFIX_TITLE = 'Lark'
|
||||||
|
|
||||||
|
AUTH_LARK = serializers.BooleanField(default=False, label=_('Enable Lark Auth'))
|
||||||
|
LARK_APP_ID = serializers.CharField(max_length=256, required=True, label='App ID')
|
||||||
|
LARK_APP_SECRET = EncryptedField(max_length=256, required=False, label='App Secret')
|
|
@ -40,6 +40,7 @@ class PrivateSettingSerializer(PublicSettingSerializer):
|
||||||
AUTH_WECOM = serializers.BooleanField()
|
AUTH_WECOM = serializers.BooleanField()
|
||||||
AUTH_DINGTALK = serializers.BooleanField()
|
AUTH_DINGTALK = serializers.BooleanField()
|
||||||
AUTH_FEISHU = serializers.BooleanField()
|
AUTH_FEISHU = serializers.BooleanField()
|
||||||
|
AUTH_LARK = serializers.BooleanField()
|
||||||
AUTH_TEMP_TOKEN = serializers.BooleanField()
|
AUTH_TEMP_TOKEN = serializers.BooleanField()
|
||||||
|
|
||||||
TERMINAL_RAZOR_ENABLED = serializers.BooleanField()
|
TERMINAL_RAZOR_ENABLED = serializers.BooleanField()
|
||||||
|
|
|
@ -7,9 +7,9 @@ from common.utils import i18n_fmt
|
||||||
from .auth import (
|
from .auth import (
|
||||||
LDAPSettingSerializer, OIDCSettingSerializer, KeycloakSettingSerializer,
|
LDAPSettingSerializer, OIDCSettingSerializer, KeycloakSettingSerializer,
|
||||||
CASSettingSerializer, RadiusSettingSerializer, FeiShuSettingSerializer,
|
CASSettingSerializer, RadiusSettingSerializer, FeiShuSettingSerializer,
|
||||||
WeComSettingSerializer, DingTalkSettingSerializer, AlibabaSMSSettingSerializer,
|
LarkSettingSerializer, WeComSettingSerializer, DingTalkSettingSerializer,
|
||||||
TencentSMSSettingSerializer, CMPP2SMSSettingSerializer, AuthSettingSerializer,
|
AlibabaSMSSettingSerializer, TencentSMSSettingSerializer, CMPP2SMSSettingSerializer,
|
||||||
SAML2SettingSerializer, OAuth2SettingSerializer, PasskeySettingSerializer,
|
AuthSettingSerializer, SAML2SettingSerializer, OAuth2SettingSerializer, PasskeySettingSerializer,
|
||||||
CustomSMSSettingSerializer,
|
CustomSMSSettingSerializer,
|
||||||
)
|
)
|
||||||
from .basic import BasicSettingSerializer
|
from .basic import BasicSettingSerializer
|
||||||
|
@ -75,6 +75,7 @@ class SettingsSerializer(
|
||||||
WeComSettingSerializer,
|
WeComSettingSerializer,
|
||||||
DingTalkSettingSerializer,
|
DingTalkSettingSerializer,
|
||||||
FeiShuSettingSerializer,
|
FeiShuSettingSerializer,
|
||||||
|
LarkSettingSerializer,
|
||||||
EmailSettingSerializer,
|
EmailSettingSerializer,
|
||||||
EmailContentSettingSerializer,
|
EmailContentSettingSerializer,
|
||||||
OtherSettingSerializer,
|
OtherSettingSerializer,
|
||||||
|
|
|
@ -15,6 +15,7 @@ urlpatterns = [
|
||||||
path('wecom/testing/', api.WeComTestingAPI.as_view(), name='wecom-testing'),
|
path('wecom/testing/', api.WeComTestingAPI.as_view(), name='wecom-testing'),
|
||||||
path('dingtalk/testing/', api.DingTalkTestingAPI.as_view(), name='dingtalk-testing'),
|
path('dingtalk/testing/', api.DingTalkTestingAPI.as_view(), name='dingtalk-testing'),
|
||||||
path('feishu/testing/', api.FeiShuTestingAPI.as_view(), name='feishu-testing'),
|
path('feishu/testing/', api.FeiShuTestingAPI.as_view(), name='feishu-testing'),
|
||||||
|
path('lark/testing/', api.LarkTestingAPI.as_view(), name='lark-testing'),
|
||||||
path('slack/testing/', api.SlackTestingAPI.as_view(), name='slack-testing'),
|
path('slack/testing/', api.SlackTestingAPI.as_view(), name='slack-testing'),
|
||||||
path('sms/<str:backend>/testing/', api.SMSTestingAPI.as_view(), name='sms-testing'),
|
path('sms/<str:backend>/testing/', api.SMSTestingAPI.as_view(), name='sms-testing'),
|
||||||
path('sms/backend/', api.SMSBackendAPI.as_view(), name='sms-backend'),
|
path('sms/backend/', api.SMSBackendAPI.as_view(), name='sms-backend'),
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Generated by Django 4.1.13 on 2024-03-25 08:55
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0049_alter_user_unique_together_user_slack_id_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='lark_id',
|
||||||
|
field=models.CharField(default=None, max_length=128, null=True, verbose_name='Lark'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='user',
|
||||||
|
name='source',
|
||||||
|
field=models.CharField(choices=[('local', 'Local'), ('ldap', 'LDAP/AD'), ('openid', 'OpenID'), ('radius', 'Radius'), ('cas', 'CAS'), ('saml2', 'SAML2'), ('oauth2', 'OAuth2'), ('wecom', 'WeCom'), ('dingtalk', 'DingTalk'), ('feishu', 'FeiShu'), ('lark', 'Lark'), ('slack', 'Slack'), ('custom', 'Custom')], default='local', max_length=30, verbose_name='Source'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='user',
|
||||||
|
unique_together={('wecom_id',), ('slack_id',), ('dingtalk_id',), ('lark_id',), ('feishu_id',)},
|
||||||
|
),
|
||||||
|
]
|
|
@ -749,6 +749,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, LabeledMixin, JSONFilterM
|
||||||
wecom = 'wecom', _('WeCom')
|
wecom = 'wecom', _('WeCom')
|
||||||
dingtalk = 'dingtalk', _('DingTalk')
|
dingtalk = 'dingtalk', _('DingTalk')
|
||||||
feishu = 'feishu', _('FeiShu')
|
feishu = 'feishu', _('FeiShu')
|
||||||
|
lark = 'lark', _('Lark')
|
||||||
slack = 'slack', _('Slack')
|
slack = 'slack', _('Slack')
|
||||||
custom = 'custom', 'Custom'
|
custom = 'custom', 'Custom'
|
||||||
|
|
||||||
|
@ -782,6 +783,9 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, LabeledMixin, JSONFilterM
|
||||||
Source.feishu: [
|
Source.feishu: [
|
||||||
settings.AUTH_BACKEND_FEISHU
|
settings.AUTH_BACKEND_FEISHU
|
||||||
],
|
],
|
||||||
|
Source.lark: [
|
||||||
|
settings.AUTH_BACKEND_LARK
|
||||||
|
],
|
||||||
Source.slack: [
|
Source.slack: [
|
||||||
settings.AUTH_BACKEND_SLACK
|
settings.AUTH_BACKEND_SLACK
|
||||||
],
|
],
|
||||||
|
@ -855,6 +859,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, LabeledMixin, JSONFilterM
|
||||||
wecom_id = models.CharField(null=True, default=None, max_length=128, verbose_name=_('WeCom'))
|
wecom_id = models.CharField(null=True, default=None, max_length=128, verbose_name=_('WeCom'))
|
||||||
dingtalk_id = models.CharField(null=True, default=None, max_length=128, verbose_name=_('DingTalk'))
|
dingtalk_id = models.CharField(null=True, default=None, max_length=128, verbose_name=_('DingTalk'))
|
||||||
feishu_id = models.CharField(null=True, default=None, max_length=128, verbose_name=_('FeiShu'))
|
feishu_id = models.CharField(null=True, default=None, max_length=128, verbose_name=_('FeiShu'))
|
||||||
|
lark_id = models.CharField(null=True, default=None, max_length=128, verbose_name='Lark')
|
||||||
slack_id = models.CharField(null=True, default=None, max_length=128, verbose_name=_('Slack'))
|
slack_id = models.CharField(null=True, default=None, max_length=128, verbose_name=_('Slack'))
|
||||||
|
|
||||||
DATE_EXPIRED_WARNING_DAYS = 5
|
DATE_EXPIRED_WARNING_DAYS = 5
|
||||||
|
@ -1006,6 +1011,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, LabeledMixin, JSONFilterM
|
||||||
('dingtalk_id',),
|
('dingtalk_id',),
|
||||||
('wecom_id',),
|
('wecom_id',),
|
||||||
('feishu_id',),
|
('feishu_id',),
|
||||||
|
('lark_id',),
|
||||||
('slack_id',),
|
('slack_id',),
|
||||||
)
|
)
|
||||||
permissions = [
|
permissions = [
|
||||||
|
|
|
@ -123,8 +123,8 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, ResourceLa
|
||||||
# small 指的是 不需要计算的直接能从一张表中获取到的数据
|
# small 指的是 不需要计算的直接能从一张表中获取到的数据
|
||||||
fields_small = fields_mini + fields_write_only + [
|
fields_small = fields_mini + fields_write_only + [
|
||||||
"email", "wechat", "phone", "mfa_level", "source",
|
"email", "wechat", "phone", "mfa_level", "source",
|
||||||
"wecom_id", "dingtalk_id", "feishu_id", "slack_id",
|
"wecom_id", "dingtalk_id", "feishu_id", "lark_id",
|
||||||
"created_by", "updated_by", "comment", # 通用字段
|
"slack_id", "created_by", "updated_by", "comment", # 通用字段
|
||||||
]
|
]
|
||||||
fields_date = [
|
fields_date = [
|
||||||
"date_expired", "date_joined", "last_login",
|
"date_expired", "date_joined", "last_login",
|
||||||
|
@ -154,7 +154,7 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, ResourceLa
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
"date_joined", "last_login", "created_by",
|
"date_joined", "last_login", "created_by",
|
||||||
"is_first_login", "wecom_id", "dingtalk_id",
|
"is_first_login", "wecom_id", "dingtalk_id",
|
||||||
"feishu_id", "date_api_key_last_used",
|
"feishu_id", "lark_id", "date_api_key_last_used",
|
||||||
]
|
]
|
||||||
disallow_self_update_fields = ["is_active", "system_roles", "org_roles"]
|
disallow_self_update_fields = ["is_active", "system_roles", "org_roles"]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
|
|
Loading…
Reference in New Issue