feat: 拆分 feishu lark

pull/12877/head
feng 2024-03-22 18:05:43 +08:00 committed by Bryan
parent ccd4f3ada4
commit 470a088a9f
34 changed files with 329 additions and 75 deletions

View File

@ -36,6 +36,7 @@ class AuthBackendLabelMapping(LazyObject):
backend_label_mapping[settings.AUTH_BACKEND_AUTH_TOKEN] = _("Auth Token")
backend_label_mapping[settings.AUTH_BACKEND_WECOM] = _("WeCom")
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_DINGTALK] = _("DingTalk")
backend_label_mapping[settings.AUTH_BACKEND_TEMP_TOKEN] = _("Temporary token")

View File

@ -6,6 +6,7 @@ from .common import *
from .confirm import *
from .connection_token import *
from .feishu import *
from .lark import *
from .login_confirm import *
from .mfa import *
from .password import *

View File

@ -12,7 +12,6 @@ from common.permissions import IsValidUser, OnlySuperUser
from common.utils import get_logger
from users.models import User
logger = get_logger(__file__)
@ -24,6 +23,7 @@ class QRUnBindBase(APIView):
'wecom': {'user_field': 'wecom_id', 'not_bind_err': errors.WeComNotBound},
'dingtalk': {'user_field': 'dingtalk_id', 'not_bind_err': errors.DingTalkNotBound},
'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},
}
user = self.user

View File

@ -0,0 +1,8 @@
from common.utils import get_logger
from .feishu import FeiShuEventSubscriptionCallback
logger = get_logger(__name__)
class LarkEventSubscriptionCallback(FeiShuEventSubscriptionCallback):
pass

View File

@ -55,6 +55,12 @@ class FeiShuAuthentication(JMSModelBackend):
pass
class LarkAuthentication(FeiShuAuthentication):
@staticmethod
def is_enabled():
return settings.AUTH_LARK
class SlackAuthentication(JMSModelBackend):
"""
什么也不做呀😺
@ -72,5 +78,6 @@ class AuthorizationTokenAuthentication(JMSModelBackend):
"""
什么也不做呀😺
"""
def authenticate(self, request, **kwargs):
pass

View File

@ -33,6 +33,11 @@ class FeiShuNotBound(JMSException):
default_detail = _('FeiShu is not bound')
class LarkNotBound(JMSException):
default_code = 'lark_not_bound'
default_detail = _('Lark is not bound')
class SlackNotBound(JMSException):
default_code = 'slack_not_bound'
default_detail = _('Slack is not bound')

View File

@ -22,6 +22,9 @@ urlpatterns = [
path('feishu/event/subscription/callback/', api.FeiShuEventSubscriptionCallback.as_view(),
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('confirm-oauth/', api.ConfirmBindORUNBindOAuth.as_view(), name='confirm-oauth'),
path('tokens/', api.TokenCreateApi.as_view(), name='auth-token'),

View File

@ -49,6 +49,12 @@ urlpatterns = [
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('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/qr/bind/', views.SlackQRBindView.as_view(), name='slack-qr-bind'),
path('slack/qr/login/', views.SlackQRLoginView.as_view(), name='slack-qr-login'),

View File

@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
#
from .login import *
from .mfa import *
from .wecom import *
from .dingtalk import *
from .feishu import *
from .lark import *
from .login import *
from .mfa import *
from .slack import *
from .wecom import *

View File

@ -21,24 +21,45 @@ from .mixins import FlashMessageMixin
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):
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):
try:
return super().dispatch(request, *args, **kwargs)
except APIException as e:
msg = str(e.detail)
return self.get_failed_response(
'/',
_('FeiShu Error'),
msg
'/', self.error, msg
)
def verify_state(self):
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:
return False
return True
@ -49,19 +70,18 @@ class FeiShuQRMixin(UserConfirmRequiredExceptionMixin, PermissionsMixin, FlashMe
def get_qr_url(self, redirect_uri):
state = random_string(16)
self.request.session[FEISHU_STATE_SESSION_KEY] = state
self.request.session[self.state_session_key] = state
params = {
'app_id': settings.FEISHU_APP_ID,
'app_id': getattr(settings, f'{self.category}_APP_ID'.upper()),
'state': state,
'redirect_uri': redirect_uri,
}
url = URL().authen + '?' + urlencode(params)
url = self.url_object.authen + '?' + urlencode(params)
return url
def get_already_bound_response(self, redirect_url):
msg = _('FeiShu is already bound')
response = self.get_failed_response(redirect_url, msg, msg)
response = self.get_failed_response(redirect_url, self.error_msg, self.error_msg)
return response
@ -71,7 +91,7 @@ class FeiShuQRBindView(FeiShuQRMixin, View):
def get(self, request: HttpRequest):
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})
url = self.get_qr_url(redirect_uri)
@ -81,25 +101,16 @@ class FeiShuQRBindView(FeiShuQRMixin, View):
class FeiShuQRBindCallbackView(FeiShuQRMixin, BaseBindCallbackView):
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_label = _('FeiShu')
client_type_path = f'common.sdk.im.{auth_type}.FeiShu'
class FeiShuEnableStartView(UserVerifyPasswordView):
def get_success_url(self):
referer = self.request.META.get('HTTP_REFERER')
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
@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()
}
class FeiShuQRLoginView(FeiShuQRMixin, View):
@ -107,7 +118,7 @@ class FeiShuQRLoginView(FeiShuQRMixin, View):
def get(self, request: HttpRequest):
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_url': redirect_url,
})
@ -119,11 +130,19 @@ class FeiShuQRLoginView(FeiShuQRMixin, View):
class FeiShuQRLoginCallbackView(FeiShuQRMixin, BaseLoginCallbackView):
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'
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_user_not_bound_err = _('FeiShu is not bound')
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()
}

View File

@ -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')

View File

@ -91,6 +91,12 @@ class UserLoginContextMixin:
'url': reverse('authentication:feishu-qr-login'),
'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'),
'enabled': settings.AUTH_SLACK,

View File

@ -2,24 +2,18 @@ import json
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.utils import digest
from common.utils.common import get_logger
from users.utils import construct_user_email
logger = get_logger(__name__)
class URL:
# https://open.feishu.cn/document/ukTMukTMukTM/uEDO4UjLxgDO14SM4gTN
@property
def host(self):
if settings.FEISHU_VERSION == 'feishu':
h = 'https://open.feishu.cn'
else:
h = 'https://open.larksuite.com'
return h
host = 'https://open.feishu.cn'
@property
def authen(self):
@ -87,12 +81,13 @@ class FeiShu(RequestMixin):
"""
非业务数据导致的错误直接抛异常说明是系统配置错误业务代码不用理会
"""
requests_cls = FeishuRequests
def __init__(self, app_id, app_secret, timeout=None):
self._app_id = app_id or ''
self._app_secret = app_secret or ''
self._requests = FeishuRequests(
self._requests = self.requests_cls(
app_id=app_id,
app_secret=app_secret,
timeout=timeout
@ -130,7 +125,7 @@ class FeiShu(RequestMixin):
body['receive_id'] = user_id
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)
except APIException as e:
# 只处理可预知的错误

View File

@ -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

View File

@ -407,7 +407,11 @@ class Config(dict):
'AUTH_FEISHU': False,
'FEISHU_APP_ID': '',
'FEISHU_APP_SECRET': '',
'FEISHU_VERSION': 'feishu',
# Lark
'AUTH_LARK': False,
'LARK_APP_ID': '',
'LARK_APP_SECRET': '',
# Slack
'AUTH_SLACK': False,

View File

@ -141,7 +141,10 @@ DINGTALK_APPSECRET = CONFIG.DINGTALK_APPSECRET
AUTH_FEISHU = CONFIG.AUTH_FEISHU
FEISHU_APP_ID = CONFIG.FEISHU_APP_ID
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
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_DINGTALK = 'authentication.backends.sso.DingTalkAuthentication'
AUTH_BACKEND_FEISHU = 'authentication.backends.sso.FeiShuAuthentication'
AUTH_BACKEND_LARK = 'authentication.backends.sso.LarkAuthentication'
AUTH_BACKEND_SLACK = 'authentication.backends.sso.SlackAuthentication'
AUTH_BACKEND_AUTH_TOKEN = 'authentication.backends.sso.AuthorizationTokenAuthentication'
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_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模式
AUTH_BACKEND_AUTH_TOKEN, AUTH_BACKEND_SSO, AUTH_BACKEND_TEMP_TOKEN,
AUTH_BACKEND_PASSKEY

View File

@ -1,7 +1,7 @@
import importlib
from django.utils.translation import gettext_lazy as _
from django.db import models
from django.utils.translation import gettext_lazy as _
client_name_mapper = {}
@ -12,7 +12,9 @@ class BACKEND(models.TextChoices):
DINGTALK = 'dingtalk', _('DingTalk')
SITE_MSG = 'site_msg', _('Site message')
FEISHU = 'feishu', _('FeiShu')
LARK = 'lark', 'Lark'
SLACK = 'slack', _('Slack')
# SMS = 'sms', _('SMS')
@property

View File

@ -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

View File

@ -196,6 +196,9 @@ class Message(metaclass=MessageType):
def get_feishu_msg(self) -> dict:
return self.markdown_msg
def get_lark_msg(self) -> dict:
return self.markdown_msg
def get_email_msg(self) -> dict:
return self.html_msg_with_sign

View File

@ -2,6 +2,7 @@ from .chat import *
from .dingtalk import *
from .email import *
from .feishu import *
from .lark import *
from .ldap import *
from .public import *
from .security import *

View File

@ -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 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 settings.models import Setting
from .. import serializers
class FeiShuTestingAPI(GenericAPIView):
category = 'FEISHU'
serializer_class = serializers.FeiShuSettingSerializer
rbac_perms = {
'POST': 'settings.change_auth'
@ -20,11 +20,11 @@ class FeiShuTestingAPI(GenericAPIView):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
app_id = serializer.validated_data['FEISHU_APP_ID']
app_secret = serializer.validated_data.get('FEISHU_APP_SECRET')
app_id = serializer.validated_data[f'{self.category}_APP_ID']
app_secret = serializer.validated_data.get(f'{self.category}_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:
app_secret = secret.cleaned_value
@ -40,3 +40,8 @@ class FeiShuTestingAPI(GenericAPIView):
except:
error = e.detail
return Response(status=status.HTTP_400_BAD_REQUEST, data={'error': error})
class LarkTestingAPI(FeiShuTestingAPI):
category = 'LARK'
serializer_class = serializers.LarkSettingSerializer

View File

@ -0,0 +1,7 @@
from .feishu import FeiShuTestingAPI
from .. import serializers
class LarkTestingAPI(FeiShuTestingAPI):
category = 'LARK'
serializer_class = serializers.LarkSettingSerializer

View File

@ -39,6 +39,7 @@ class SettingsApi(generics.RetrieveUpdateAPIView):
'wecom': serializers.WeComSettingSerializer,
'dingtalk': serializers.DingTalkSettingSerializer,
'feishu': serializers.FeiShuSettingSerializer,
'lark': serializers.LarkSettingSerializer,
'slack': serializers.SlackSettingSerializer,
'auth': serializers.AuthSettingSerializer,
'oidc': serializers.OIDCSettingSerializer,

View File

@ -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),
]

View File

@ -2,13 +2,14 @@ from .base import *
from .cas import *
from .dingtalk import *
from .feishu import *
from .lark import *
from .ldap import *
from .oauth2 import *
from .oidc import *
from .passkey import *
from .radius import *
from .saml2 import *
from .slack import *
from .sms import *
from .sso import *
from .wecom import *
from .slack import *

View File

@ -17,6 +17,7 @@ class AuthSettingSerializer(serializers.Serializer):
AUTH_RADIUS = serializers.BooleanField(required=False, label=_('RADIUS Auth'))
AUTH_DINGTALK = serializers.BooleanField(default=False, label=_('DingTalk 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_SLACK = serializers.BooleanField(default=False, label=_('WeCom Auth'))
AUTH_SSO = serializers.BooleanField(default=False, label=_("SSO Auth"))

View File

@ -9,13 +9,6 @@ __all__ = ['FeiShuSettingSerializer']
class FeiShuSettingSerializer(serializers.Serializer):
PREFIX_TITLE = _('FeiShu')
VERSION_CHOICES = (
('feishu', _('FeiShu')),
('lark', 'Lark')
)
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_SECRET = EncryptedField(max_length=256, required=False, label='App Secret')
FEISHU_VERSION = serializers.ChoiceField(
choices=VERSION_CHOICES, default='feishu', label=_('Version')
)

View File

@ -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')

View File

@ -40,6 +40,7 @@ class PrivateSettingSerializer(PublicSettingSerializer):
AUTH_WECOM = serializers.BooleanField()
AUTH_DINGTALK = serializers.BooleanField()
AUTH_FEISHU = serializers.BooleanField()
AUTH_LARK = serializers.BooleanField()
AUTH_TEMP_TOKEN = serializers.BooleanField()
TERMINAL_RAZOR_ENABLED = serializers.BooleanField()

View File

@ -7,9 +7,9 @@ from common.utils import i18n_fmt
from .auth import (
LDAPSettingSerializer, OIDCSettingSerializer, KeycloakSettingSerializer,
CASSettingSerializer, RadiusSettingSerializer, FeiShuSettingSerializer,
WeComSettingSerializer, DingTalkSettingSerializer, AlibabaSMSSettingSerializer,
TencentSMSSettingSerializer, CMPP2SMSSettingSerializer, AuthSettingSerializer,
SAML2SettingSerializer, OAuth2SettingSerializer, PasskeySettingSerializer,
LarkSettingSerializer, WeComSettingSerializer, DingTalkSettingSerializer,
AlibabaSMSSettingSerializer, TencentSMSSettingSerializer, CMPP2SMSSettingSerializer,
AuthSettingSerializer, SAML2SettingSerializer, OAuth2SettingSerializer, PasskeySettingSerializer,
CustomSMSSettingSerializer,
)
from .basic import BasicSettingSerializer
@ -75,6 +75,7 @@ class SettingsSerializer(
WeComSettingSerializer,
DingTalkSettingSerializer,
FeiShuSettingSerializer,
LarkSettingSerializer,
EmailSettingSerializer,
EmailContentSettingSerializer,
OtherSettingSerializer,

View File

@ -15,6 +15,7 @@ urlpatterns = [
path('wecom/testing/', api.WeComTestingAPI.as_view(), name='wecom-testing'),
path('dingtalk/testing/', api.DingTalkTestingAPI.as_view(), name='dingtalk-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('sms/<str:backend>/testing/', api.SMSTestingAPI.as_view(), name='sms-testing'),
path('sms/backend/', api.SMSBackendAPI.as_view(), name='sms-backend'),

View File

@ -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',)},
),
]

View File

@ -749,6 +749,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, LabeledMixin, JSONFilterM
wecom = 'wecom', _('WeCom')
dingtalk = 'dingtalk', _('DingTalk')
feishu = 'feishu', _('FeiShu')
lark = 'lark', _('Lark')
slack = 'slack', _('Slack')
custom = 'custom', 'Custom'
@ -782,6 +783,9 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, LabeledMixin, JSONFilterM
Source.feishu: [
settings.AUTH_BACKEND_FEISHU
],
Source.lark: [
settings.AUTH_BACKEND_LARK
],
Source.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'))
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'))
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'))
DATE_EXPIRED_WARNING_DAYS = 5
@ -1006,6 +1011,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, LabeledMixin, JSONFilterM
('dingtalk_id',),
('wecom_id',),
('feishu_id',),
('lark_id',),
('slack_id',),
)
permissions = [

View File

@ -123,8 +123,8 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, ResourceLa
# small 指的是 不需要计算的直接能从一张表中获取到的数据
fields_small = fields_mini + fields_write_only + [
"email", "wechat", "phone", "mfa_level", "source",
"wecom_id", "dingtalk_id", "feishu_id", "slack_id",
"created_by", "updated_by", "comment", # 通用字段
"wecom_id", "dingtalk_id", "feishu_id", "lark_id",
"slack_id", "created_by", "updated_by", "comment", # 通用字段
]
fields_date = [
"date_expired", "date_joined", "last_login",
@ -154,7 +154,7 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, ResourceLa
read_only_fields = [
"date_joined", "last_login", "created_by",
"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"]
extra_kwargs = {