feat: 支持钉钉、飞书、企业微信扫码登录无用户时自动创建用户

This commit is contained in:
jiangweidong 2023-04-28 14:01:44 +08:00 committed by Jiangjie.Bai
parent 7ff22cbc34
commit bda748d547
13 changed files with 221 additions and 135 deletions

View File

@ -23,7 +23,7 @@ from common.utils.random import random_string
from users.models import User from users.models import User
from users.views import UserVerifyPasswordView from users.views import UserVerifyPasswordView
from .mixins import METAMixin from .mixins import METAMixin, QRLoginCallbackMixin
logger = get_logger(__file__) logger = get_logger(__file__)
@ -158,7 +158,7 @@ class DingTalkQRBindCallbackView(DingTalkQRMixin, View):
appsecret=settings.DINGTALK_APPSECRET, appsecret=settings.DINGTALK_APPSECRET,
agentid=settings.DINGTALK_AGENTID agentid=settings.DINGTALK_AGENTID
) )
userid = dingtalk.get_userid_by_code(code) userid, __ = dingtalk.get_user_id_by_code(code)
if not userid: if not userid:
msg = _('DingTalk query user failed') msg = _('DingTalk query user failed')
@ -214,45 +214,20 @@ class DingTalkQRLoginView(DingTalkQRMixin, METAMixin, View):
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
class DingTalkQRLoginCallbackView(AuthMixin, DingTalkQRMixin, View): class DingTalkQRLoginCallbackView(QRLoginCallbackMixin, AuthMixin, DingTalkQRMixin, View):
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
def get(self, request: HttpRequest): CLIENT_INFO = (
code = request.GET.get('code') DingTalk, {'appid': 'DINGTALK_APPKEY', 'appsecret': 'DINGTALK_APPSECRET', 'agentid': 'DINGTALK_AGENTID'}
redirect_url = request.GET.get('redirect_url')
login_url = reverse('authentication:login')
if not self.verify_state():
return self.get_verify_state_failed_response(redirect_url)
dingtalk = DingTalk(
appid=settings.DINGTALK_APPKEY,
appsecret=settings.DINGTALK_APPSECRET,
agentid=settings.DINGTALK_AGENTID
) )
userid = dingtalk.get_userid_by_code(code) USER_TYPE = 'dingtalk'
if not userid: AUTH_BACKEND = 'AUTH_BACKEND_DINGTALK'
# 正常流程不会出这个错误hack 行为 CREATE_USER_IF_NOT_EXIST = 'DINGTALK_CREATE_USER_IF_NOT_EXIST'
msg = _('Failed to get user from DingTalk')
response = self.get_failed_response(login_url, title=msg, msg=msg)
return response
user = get_object_or_none(User, dingtalk_id=userid) MSG_CLIENT_ERR = _('DingTalk Error')
if user is None: MSG_USER_NOT_BOUND_ERR = _('DingTalk is not bound')
title = _('DingTalk is not bound') MSG_USER_NEED_BOUND_WARNING = _('Please login with a password and then bind the DingTalk')
msg = _('Please login with a password and then bind the DingTalk') MSG_NOT_FOUND_USER_FROM_CLIENT_ERR = _('Failed to get user from DingTalk')
response = self.get_failed_response(login_url, title=title, msg=msg)
return response
try:
self.check_oauth2_auth(user, settings.AUTH_BACKEND_DINGTALK)
except errors.AuthFailedError as e:
self.set_login_failed_mark()
msg = e.msg
response = self.get_failed_response(login_url, title=msg, msg=msg)
return response
return self.redirect_to_guard_view()
class DingTalkOAuthLoginView(DingTalkOAuthMixin, View): class DingTalkOAuthLoginView(DingTalkOAuthMixin, View):
@ -284,7 +259,7 @@ class DingTalkOAuthLoginCallbackView(AuthMixin, DingTalkOAuthMixin, View):
appsecret=settings.DINGTALK_APPSECRET, appsecret=settings.DINGTALK_APPSECRET,
agentid=settings.DINGTALK_AGENTID agentid=settings.DINGTALK_AGENTID
) )
userid = dingtalk.get_userid_by_code(code) userid, __ = dingtalk.get_user_id_by_code(code)
if not userid: if not userid:
# 正常流程不会出这个错误hack 行为 # 正常流程不会出这个错误hack 行为
msg = _('Failed to get user from DingTalk') msg = _('Failed to get user from DingTalk')

View File

@ -9,7 +9,6 @@ from django.views import View
from rest_framework.exceptions import APIException from rest_framework.exceptions import APIException
from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.permissions import AllowAny, IsAuthenticated
from authentication import errors
from authentication.const import ConfirmType from authentication.const import ConfirmType
from authentication.mixins import AuthMixin from authentication.mixins import AuthMixin
from authentication.notifications import OAuthBindMessage from authentication.notifications import OAuthBindMessage
@ -18,11 +17,12 @@ from common.permissions import UserConfirmation
from common.sdk.im.feishu import URL, FeiShu from common.sdk.im.feishu import URL, FeiShu
from common.utils import FlashMessageUtil, get_logger from common.utils import FlashMessageUtil, get_logger
from common.utils.common import get_request_ip from common.utils.common import get_request_ip
from common.utils.django import get_object_or_none, reverse from common.utils.django import reverse
from common.utils.random import random_string from common.utils.random import random_string
from users.models import User
from users.views import UserVerifyPasswordView from users.views import UserVerifyPasswordView
from .mixins import QRLoginCallbackMixin
logger = get_logger(__file__) logger = get_logger(__file__)
FEISHU_STATE_SESSION_KEY = '_feishu_state' FEISHU_STATE_SESSION_KEY = '_feishu_state'
@ -123,7 +123,7 @@ class FeiShuQRBindCallbackView(FeiShuQRMixin, View):
app_id=settings.FEISHU_APP_ID, app_id=settings.FEISHU_APP_ID,
app_secret=settings.FEISHU_APP_SECRET app_secret=settings.FEISHU_APP_SECRET
) )
user_id = feishu.get_user_id_by_code(code) user_id, __ = feishu.get_user_id_by_code(code)
if not user_id: if not user_id:
msg = _('FeiShu query user failed') msg = _('FeiShu query user failed')
@ -176,41 +176,17 @@ class FeiShuQRLoginView(FeiShuQRMixin, View):
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
class FeiShuQRLoginCallbackView(AuthMixin, FeiShuQRMixin, View): class FeiShuQRLoginCallbackView(QRLoginCallbackMixin, AuthMixin, FeiShuQRMixin, View):
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
def get(self, request: HttpRequest): CLIENT_INFO = (
code = request.GET.get('code') FeiShu, {'app_id': 'FEISHU_APP_ID', 'app_secret': 'FEISHU_APP_SECRET'}
redirect_url = request.GET.get('redirect_url')
login_url = reverse('authentication:login')
if not self.verify_state():
return self.get_verify_state_failed_response(redirect_url)
feishu = FeiShu(
app_id=settings.FEISHU_APP_ID,
app_secret=settings.FEISHU_APP_SECRET
) )
user_id = feishu.get_user_id_by_code(code) USER_TYPE = 'feishu'
if not user_id: AUTH_BACKEND = 'AUTH_BACKEND_FEISHU'
# 正常流程不会出这个错误hack 行为 CREATE_USER_IF_NOT_EXIST = 'FEISHU_CREATE_USER_IF_NOT_EXIST'
msg = _('Failed to get user from FeiShu')
response = self.get_failed_response(login_url, title=msg, msg=msg)
return response
user = get_object_or_none(User, feishu_id=user_id) MSG_CLIENT_ERR = _('FeiShu Error')
if user is None: MSG_USER_NOT_BOUND_ERR = _('FeiShu is not bound')
title = _('FeiShu is not bound') MSG_USER_NEED_BOUND_WARNING = _('Please login with a password and then bind the FeiShu')
msg = _('Please login with a password and then bind the FeiShu') MSG_NOT_FOUND_USER_FROM_CLIENT_ERR = _('Failed to get user from FeiShu')
response = self.get_failed_response(login_url, title=title, msg=msg)
return response
try:
self.check_oauth2_auth(user, settings.AUTH_BACKEND_FEISHU)
except errors.AuthFailedError as e:
self.set_login_failed_mark()
msg = e.msg
response = self.get_failed_response(login_url, title=msg, msg=msg)
return response
return self.redirect_to_guard_view()

View File

@ -1,5 +1,20 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from functools import lru_cache
from rest_framework.request import Request
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django.db.utils import IntegrityError
from authentication import errors
from users.models import User
from common.utils.django import reverse, get_object_or_none
from common.utils import get_logger
logger = get_logger(__file__)
class METAMixin: class METAMixin:
def get_next_url_from_meta(self): def get_next_url_from_meta(self):
@ -10,3 +25,92 @@ class METAMixin:
if len(next_url_item) > 1: if len(next_url_item) > 1:
next_url = next_url_item[-1] next_url = next_url_item[-1]
return next_url return next_url
class Client:
get_user_id_by_code: callable
get_user_detail: callable
class QRLoginCallbackMixin:
verify_state: callable
get_verify_state_failed_response: callable
get_failed_response: callable
check_oauth2_auth: callable
set_login_failed_mark: callable
redirect_to_guard_view: callable
# 属性
_client: Client
CLIENT_INFO: tuple
USER_TYPE: str
AUTH_BACKEND: str
CREATE_USER_IF_NOT_EXIST: str
# 提示信息
MSG_CLIENT_ERR: str
MSG_USER_NOT_BOUND_ERR: str
MSG_USER_NEED_BOUND_WARNING: str
MSG_NOT_FOUND_USER_FROM_CLIENT_ERR: str
@property
@lru_cache(maxsize=1)
def client(self):
client_type, client_init = self.CLIENT_INFO
client_init = {k: getattr(settings, v) for k, v in client_init.items()}
return client_type(**client_init)
def create_user_if_not_exist(self, user_id, **kwargs):
user = None
if not getattr(settings, self.CREATE_USER_IF_NOT_EXIST):
title = self.MSG_CLIENT_ERR
msg = self.MSG_USER_NEED_BOUND_WARNING
return user, (title, msg)
user_attr = self.client.get_user_detail(user_id, **kwargs)
try:
user, create = User.objects.get_or_create(
username=user_attr['username'], defaults=user_attr
)
setattr(user, f'{self.USER_TYPE}_id', user_id)
if create:
setattr(user, 'source', self.USER_TYPE)
user.save()
except IntegrityError as err:
logger.error(f'{self.MSG_CLIENT_ERR}: create user error: {err}')
if user is None:
title = self.MSG_CLIENT_ERR
msg = _('If you have any question, please contact the administrator')
return user, (title, msg)
return user, None
def get(self, request: Request):
code = request.GET.get('code')
redirect_url = request.GET.get('redirect_url')
login_url = reverse('authentication:login')
if not self.verify_state():
return self.get_verify_state_failed_response(redirect_url)
user_id, other_info = self.client.get_user_id_by_code(code)
if not user_id:
# 正常流程不会出这个错误hack 行为
err = self.MSG_NOT_FOUND_USER_FROM_CLIENT_ERR
response = self.get_failed_response(login_url, title=err, msg=err)
return response
user = get_object_or_none(User, **{f'{self.USER_TYPE}_id': user_id})
if user is None:
user, err = self.create_user_if_not_exist(user_id, other_info=other_info)
if err is not None:
response = self.get_failed_response(login_url, title=err[0], msg=err[1])
return response
try:
self.check_oauth2_auth(user, getattr(settings, self.AUTH_BACKEND))
except errors.AuthFailedError as e:
self.set_login_failed_mark()
msg = e.msg
response = self.get_failed_response(login_url, title=msg, msg=msg)
return response
return self.redirect_to_guard_view()

View File

@ -22,7 +22,8 @@ from authentication import errors
from authentication.mixins import AuthMixin from authentication.mixins import AuthMixin
from authentication.const import ConfirmType from authentication.const import ConfirmType
from authentication.notifications import OAuthBindMessage from authentication.notifications import OAuthBindMessage
from .mixins import METAMixin
from .mixins import METAMixin, QRLoginCallbackMixin
logger = get_logger(__file__) logger = get_logger(__file__)
@ -208,45 +209,20 @@ class WeComQRLoginView(WeComQRMixin, METAMixin, View):
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
class WeComQRLoginCallbackView(AuthMixin, WeComQRMixin, View): class WeComQRLoginCallbackView(QRLoginCallbackMixin, AuthMixin, WeComQRMixin, View):
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
def get(self, request: HttpRequest): CLIENT_INFO = (
code = request.GET.get('code') WeCom, {'corpid': 'WECOM_CORPID', 'corpsecret': 'WECOM_SECRET', 'agentid': 'WECOM_AGENTID'}
redirect_url = request.GET.get('redirect_url')
login_url = reverse('authentication:login')
if not self.verify_state():
return self.get_verify_state_failed_response(redirect_url)
wecom = WeCom(
corpid=settings.WECOM_CORPID,
corpsecret=settings.WECOM_SECRET,
agentid=settings.WECOM_AGENTID
) )
wecom_userid, __ = wecom.get_user_id_by_code(code) USER_TYPE = 'wecom'
if not wecom_userid: AUTH_BACKEND = 'AUTH_BACKEND_WECOM'
# 正常流程不会出这个错误hack 行为 CREATE_USER_IF_NOT_EXIST = 'WECOM_CREATE_USER_IF_NOT_EXIST'
msg = _('Failed to get user from WeCom')
response = self.get_failed_response(login_url, title=msg, msg=msg)
return response
user = get_object_or_none(User, wecom_id=wecom_userid) MSG_CLIENT_ERR = _('WeCom Error')
if user is None: MSG_USER_NOT_BOUND_ERR = _('WeCom is not bound')
title = _('WeCom is not bound') MSG_USER_NEED_BOUND_WARNING = _('Please login with a password and then bind the WeCom')
msg = _('Please login with a password and then bind the WeCom') MSG_NOT_FOUND_USER_FROM_CLIENT_ERR = _('Failed to get user from WeCom')
response = self.get_failed_response(login_url, title=title, msg=msg)
return response
try:
self.check_oauth2_auth(user, settings.AUTH_BACKEND_WECOM)
except errors.AuthFailedError as e:
self.set_login_failed_mark()
msg = e.msg
response = self.get_failed_response(login_url, title=msg, msg=msg)
return response
return self.redirect_to_guard_view()
class WeComOAuthLoginView(WeComOAuthMixin, View): class WeComOAuthLoginView(WeComOAuthMixin, View):

View File

@ -5,6 +5,7 @@ import base64
from common.utils import get_logger from common.utils import get_logger
from common.sdk.im.utils import digest, as_request from common.sdk.im.utils import digest, as_request
from common.sdk.im.mixin import BaseRequest from common.sdk.im.mixin import BaseRequest
from users.utils import construct_user_email
logger = get_logger(__file__) logger = get_logger(__file__)
@ -35,6 +36,7 @@ class URL:
SEND_MESSAGE = 'https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2' SEND_MESSAGE = 'https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2'
GET_SEND_MSG_PROGRESS = 'https://oapi.dingtalk.com/topapi/message/corpconversation/getsendprogress' GET_SEND_MSG_PROGRESS = 'https://oapi.dingtalk.com/topapi/message/corpconversation/getsendprogress'
GET_USERID_BY_UNIONID = 'https://oapi.dingtalk.com/topapi/user/getbyunionid' GET_USERID_BY_UNIONID = 'https://oapi.dingtalk.com/topapi/user/getbyunionid'
GET_USER_INFO_BY_USER_ID = 'https://oapi.dingtalk.com/topapi/v2/user/get'
class DingTalkRequests(BaseRequest): class DingTalkRequests(BaseRequest):
@ -129,11 +131,11 @@ class DingTalk:
data = self._request.post(URL.GET_USER_INFO_BY_CODE, json=body, with_sign=True) data = self._request.post(URL.GET_USER_INFO_BY_CODE, json=body, with_sign=True)
return data['user_info'] return data['user_info']
def get_userid_by_code(self, code): def get_user_id_by_code(self, code):
user_info = self.get_userinfo_bycode(code) user_info = self.get_userinfo_bycode(code)
unionid = user_info['unionid'] unionid = user_info['unionid']
userid = self.get_userid_by_unionid(unionid) userid = self.get_userid_by_unionid(unionid)
return userid return userid, None
def get_userid_by_unionid(self, unionid): def get_userid_by_unionid(self, unionid):
body = { body = {
@ -195,3 +197,18 @@ class DingTalk:
data = self._request.post(URL.GET_SEND_MSG_PROGRESS, json=body, with_token=True) data = self._request.post(URL.GET_SEND_MSG_PROGRESS, json=body, with_token=True)
return data 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
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
}

View File

@ -1,9 +1,9 @@
import json import json
from django.utils.translation import ugettext_lazy as _
from rest_framework.exceptions import APIException from rest_framework.exceptions import APIException
from django.conf import settings from django.conf import settings
from users.utils import construct_user_email
from common.utils.common import get_logger from common.utils.common import get_logger
from common.sdk.im.utils import digest from common.sdk.im.utils import digest
from common.sdk.im.mixin import RequestMixin, BaseRequest from common.sdk.im.mixin import RequestMixin, BaseRequest
@ -30,13 +30,16 @@ class URL:
return f'{self.host}/open-apis/auth/v3/tenant_access_token/internal/' return f'{self.host}/open-apis/auth/v3/tenant_access_token/internal/'
@property @property
def get_user_info_by_code(self): def get_userinfo_by_code(self):
return f'{self.host}/open-apis/authen/v1/access_token' return f'{self.host}/open-apis/authen/v1/access_token'
@property @property
def send_message(self): def send_message(self):
return f'{self.host}/open-apis/im/v1/messages' return f'{self.host}/open-apis/im/v1/messages'
def get_user_detail(self, user_id):
return f'{self.host}/open-apis/contact/v3/users/{user_id}'
class ErrorCode: class ErrorCode:
INVALID_APP_ACCESS_TOKEN = 99991664 INVALID_APP_ACCESS_TOKEN = 99991664
@ -103,10 +106,10 @@ class FeiShu(RequestMixin):
'code': code 'code': code
} }
data = self._requests.post(URL().get_user_info_by_code, json=body, check_errcode_is_0=False) data = self._requests.post(URL().get_userinfo_by_code, json=body, check_errcode_is_0=False)
self._requests.check_errcode_is_0(data) self._requests.check_errcode_is_0(data)
return data['data']['user_id'] return data['data']['user_id'], data['data']
def send_text(self, user_ids, msg): def send_text(self, user_ids, msg):
params = { params = {
@ -130,3 +133,15 @@ class FeiShu(RequestMixin):
logger.exception(e) logger.exception(e)
invalid_users.append(user_id) invalid_users.append(user_id)
return invalid_users return invalid_users
@staticmethod
def get_user_detail(user_id, **kwargs):
# get_user_id_by_code 已经返回个人信息,这里直接解析
data = kwargs['other_info']
username = user_id
name = data.get('name', username)
email = data.get('email') or data.get('enterprise_email')
email = construct_user_email(username, email)
return {
'username': username, 'name': name, 'email': email
}

View File

@ -3,8 +3,9 @@ from typing import Iterable, AnyStr
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework.exceptions import APIException from rest_framework.exceptions import APIException
from users.utils import construct_user_email
from common.utils.common import get_logger from common.utils.common import get_logger
from common.sdk.im.utils import digest, DictWrapper, update_values, set_default from common.sdk.im.utils import digest, update_values
from common.sdk.im.mixin import RequestMixin, BaseRequest from common.sdk.im.mixin import RequestMixin, BaseRequest
logger = get_logger(__name__) logger = get_logger(__name__)
@ -151,10 +152,7 @@ class WeCom(RequestMixin):
def get_user_id_by_code(self, code): def get_user_id_by_code(self, code):
# # https://open.work.weixin.qq.com/api/doc/90000/90135/91437 # # https://open.work.weixin.qq.com/api/doc/90000/90135/91437
params = {'code': code}
params = {
'code': code,
}
data = self._requests.get(URL.GET_USER_ID_BY_CODE, params=params, check_errcode_is_0=False) data = self._requests.get(URL.GET_USER_ID_BY_CODE, params=params, check_errcode_is_0=False)
errcode = data['errcode'] errcode = data['errcode']
@ -175,12 +173,15 @@ class WeCom(RequestMixin):
logger.error(f'WeCom response 200 but get field from json error: fields=UserId|OpenId') logger.error(f'WeCom response 200 but get field from json error: fields=UserId|OpenId')
raise WeComError raise WeComError
def get_user_detail(self, id): def get_user_detail(self, user_id, **kwargs):
# https://open.work.weixin.qq.com/api/doc/90000/90135/90196 # https://open.work.weixin.qq.com/api/doc/90000/90135/90196
params = {'userid': user_id}
params = { data = self._requests.get(URL.GET_USER_DETAIL, params)
'userid': id, username = data.get('userid')
name = data.get('name', username)
email = data.get('email') or data.get('biz_mail')
email = construct_user_email(username, email)
return {
'username': username, 'name': name, 'email': email
} }
data = self._requests.get(URL.GET_USER_DETAIL, params)
return data

View File

@ -365,18 +365,21 @@ class Config(dict):
'WECOM_CORPID': '', 'WECOM_CORPID': '',
'WECOM_AGENTID': '', 'WECOM_AGENTID': '',
'WECOM_SECRET': '', 'WECOM_SECRET': '',
'WECOM_CREATE_USER_IF_NOT_EXIST': False,
# 钉钉 # 钉钉
'AUTH_DINGTALK': False, 'AUTH_DINGTALK': False,
'DINGTALK_AGENTID': '', 'DINGTALK_AGENTID': '',
'DINGTALK_APPKEY': '', 'DINGTALK_APPKEY': '',
'DINGTALK_APPSECRET': '', 'DINGTALK_APPSECRET': '',
'DINGTALK_CREATE_USER_IF_NOT_EXIST': False,
# 飞书 # 飞书
'AUTH_FEISHU': False, 'AUTH_FEISHU': False,
'FEISHU_APP_ID': '', 'FEISHU_APP_ID': '',
'FEISHU_APP_SECRET': '', 'FEISHU_APP_SECRET': '',
'FEISHU_VERSION': 'feishu', 'FEISHU_VERSION': 'feishu',
'FEISHU_CREATE_USER_IF_NOT_EXIST': False,
'LOGIN_REDIRECT_TO_BACKEND': '', # 'OPENID / CAS / SAML2 'LOGIN_REDIRECT_TO_BACKEND': '', # 'OPENID / CAS / SAML2
'LOGIN_REDIRECT_MSG_ENABLED': True, 'LOGIN_REDIRECT_MSG_ENABLED': True,

View File

@ -13,3 +13,6 @@ class DingTalkSettingSerializer(serializers.Serializer):
DINGTALK_APPKEY = serializers.CharField(max_length=256, required=True, label='AppKey') DINGTALK_APPKEY = serializers.CharField(max_length=256, required=True, label='AppKey')
DINGTALK_APPSECRET = EncryptedField(max_length=256, required=False, label='AppSecret') DINGTALK_APPSECRET = EncryptedField(max_length=256, required=False, label='AppSecret')
AUTH_DINGTALK = serializers.BooleanField(default=False, label=_('Enable DingTalk Auth')) AUTH_DINGTALK = serializers.BooleanField(default=False, label=_('Enable DingTalk Auth'))
DINGTALK_CREATE_USER_IF_NOT_EXIST = serializers.BooleanField(
default=False, label=_('Create user if not')
)

View File

@ -19,3 +19,6 @@ class FeiShuSettingSerializer(serializers.Serializer):
FEISHU_VERSION = serializers.ChoiceField( FEISHU_VERSION = serializers.ChoiceField(
choices=VERSION_CHOICES, default='feishu', label=_('Version') choices=VERSION_CHOICES, default='feishu', label=_('Version')
) )
FEISHU_CREATE_USER_IF_NOT_EXIST = serializers.BooleanField(
default=False, label=_('Create user if not')
)

View File

@ -13,3 +13,6 @@ class WeComSettingSerializer(serializers.Serializer):
WECOM_AGENTID = serializers.CharField(max_length=256, required=True, label='agentid') WECOM_AGENTID = serializers.CharField(max_length=256, required=True, label='agentid')
WECOM_SECRET = EncryptedField(max_length=256, required=False, label='secret') WECOM_SECRET = EncryptedField(max_length=256, required=False, label='secret')
AUTH_WECOM = serializers.BooleanField(default=False, label=_('Enable WeCom Auth')) AUTH_WECOM = serializers.BooleanField(default=False, label=_('Enable WeCom Auth'))
WECOM_CREATE_USER_IF_NOT_EXIST = serializers.BooleanField(
default=False, label=_('Create user if not')
)

View File

@ -13,6 +13,6 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='user', model_name='user',
name='source', name='source',
field=models.CharField(choices=[('local', 'Local'), ('ldap', 'LDAP/AD'), ('openid', 'OpenID'), ('radius', 'Radius'), ('cas', 'CAS'), ('saml2', 'SAML2'), ('oauth2', 'OAuth2'), ('custom', 'Custom')], default='local', max_length=30, verbose_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'), ('custom', 'Custom')], default='local', max_length=30, verbose_name='Source'),
), ),
] ]

View File

@ -677,14 +677,15 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
cas = 'cas', 'CAS' cas = 'cas', 'CAS'
saml2 = 'saml2', 'SAML2' saml2 = 'saml2', 'SAML2'
oauth2 = 'oauth2', 'OAuth2' oauth2 = 'oauth2', 'OAuth2'
wecom = 'wecom', _('WeCom')
dingtalk = 'dingtalk', _('DingTalk')
feishu = 'feishu', _('FeiShu')
custom = 'custom', 'Custom' custom = 'custom', 'Custom'
SOURCE_BACKEND_MAPPING = { SOURCE_BACKEND_MAPPING = {
Source.local: [ Source.local: [
settings.AUTH_BACKEND_MODEL, settings.AUTH_BACKEND_MODEL,
settings.AUTH_BACKEND_PUBKEY, settings.AUTH_BACKEND_PUBKEY,
settings.AUTH_BACKEND_WECOM,
settings.AUTH_BACKEND_DINGTALK,
], ],
Source.ldap: [ Source.ldap: [
settings.AUTH_BACKEND_LDAP settings.AUTH_BACKEND_LDAP
@ -705,6 +706,15 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
Source.oauth2: [ Source.oauth2: [
settings.AUTH_BACKEND_OAUTH2 settings.AUTH_BACKEND_OAUTH2
], ],
Source.wecom: [
settings.AUTH_BACKEND_WECOM
],
Source.feishu: [
settings.AUTH_BACKEND_FEISHU
],
Source.dingtalk: [
settings.AUTH_BACKEND_DINGTALK
],
Source.custom: [ Source.custom: [
settings.AUTH_BACKEND_CUSTOM settings.AUTH_BACKEND_CUSTOM
] ]