perf: 支持自定义短信认证

pull/10552/head
jiangweidong 2023-05-24 17:32:57 +08:00 committed by Jiangjie.Bai
parent 5e7d474bb7
commit 20cc4ea320
9 changed files with 99 additions and 6 deletions

View File

@ -0,0 +1,48 @@
import requests
from collections import OrderedDict
from django.conf import settings
from common.utils import get_logger
from common.exceptions import JMSException
from .base import BaseSMSClient
logger = get_logger(__file__)
class CustomSMS(BaseSMSClient):
@classmethod
def new_from_settings(cls):
return cls()
@staticmethod
def need_pre_check():
return False
def send_sms(self, phone_numbers: list, template_param: OrderedDict, **kwargs):
phone_numbers_str = ','.join(phone_numbers)
params = {}
for k, v in settings.CUSTOM_SMS_API_PARAMS.items():
params[k] = v.format(
code=template_param.get('code'), phone_numbers=phone_numbers_str
)
logger.info(f'Custom sms send: phone_numbers={phone_numbers}param={params}')
if settings.CUSTOM_SMS_REQUEST_METHOD == 'post':
action = requests.post
kwargs = {'json': params}
else:
action = requests.get
kwargs = {'params': params}
try:
response = action(url=settings.CUSTOM_SMS_URL, **kwargs)
if response.reason != 'OK':
raise JMSException(detail=response.text, code=response.status_code)
except Exception as exc:
logger.error('Custom sms error: {}'.format(exc))
client = CustomSMS

View File

@ -17,6 +17,7 @@ class BACKENDS(TextChoices):
TENCENT = 'tencent', _('Tencent cloud')
HUAWEI = 'huawei', _('Huawei Cloud')
CMPP2 = 'cmpp2', _('CMPP v2.0')
Custom = 'custom', _('Custom type')
class SMS:
@ -42,8 +43,9 @@ class SMS:
)
def send_verify_code(self, phone_number, code):
sign_name = getattr(settings, f'{self.client.SIGN_AND_TMPL_SETTING_FIELD_PREFIX}_VERIFY_SIGN_NAME')
template_code = getattr(settings, f'{self.client.SIGN_AND_TMPL_SETTING_FIELD_PREFIX}_VERIFY_TEMPLATE_CODE')
prefix = getattr(self.client, 'SIGN_AND_TMPL_SETTING_FIELD_PREFIX', '')
sign_name = getattr(settings, f'{prefix}_VERIFY_SIGN_NAME', None)
template_code = getattr(settings, f'{prefix}_VERIFY_TEMPLATE_CODE', None)
if self.client.need_pre_check() and not (sign_name and template_code):
raise JMSException(

View File

@ -414,6 +414,10 @@ class Config(dict):
'CMPP2_VERIFY_SIGN_NAME': '',
'CMPP2_VERIFY_TEMPLATE_CODE': '{code}',
'CUSTOM_SMS_URL': '',
'CUSTOM_SMS_API_PARAMS': {'phone_numbers': '{phone_numbers}', 'code': '{code}'},
'CUSTOM_SMS_REQUEST_METHOD': 'get',
# Email
'EMAIL_CUSTOM_USER_CREATED_SUBJECT': _('Create account successfully'),
'EMAIL_CUSTOM_USER_CREATED_HONORIFIC': _('Hello'),

View File

@ -43,6 +43,7 @@ class SettingsApi(generics.RetrieveUpdateAPIView):
'tencent': serializers.TencentSMSSettingSerializer,
'huawei': serializers.HuaweiSMSSettingSerializer,
'cmpp2': serializers.CMPP2SMSSettingSerializer,
'custom': serializers.CustomSMSSettingSerializer,
}
rbac_category_permissions = {

View File

@ -39,7 +39,8 @@ class SMSTestingAPI(GenericAPIView):
'alibaba': serializers.AlibabaSMSSettingSerializer,
'tencent': serializers.TencentSMSSettingSerializer,
'huawei': serializers.HuaweiSMSSettingSerializer,
'cmpp2': serializers.CMPP2SMSSettingSerializer
'cmpp2': serializers.CMPP2SMSSettingSerializer,
'custom': serializers.CustomSMSSettingSerializer,
}
rbac_perms = {
'POST': 'settings.change_sms'
@ -115,6 +116,12 @@ class SMSTestingAPI(GenericAPIView):
}
return init_params, send_sms_params
@staticmethod
def get_custom_params(data):
init_params = {}
send_sms_params = {'template_param': OrderedDict(code='666666')}
return init_params, send_sms_params
def get_params_by_backend(self, backend, data):
"""
返回两部分参数

View File

@ -1,4 +1,5 @@
from django.utils.translation import ugettext_lazy as _
from django.db import models
from rest_framework import serializers
from common.serializers.fields import EncryptedField
@ -7,7 +8,7 @@ from common.sdk.sms import BACKENDS
__all__ = [
'SMSSettingSerializer', 'AlibabaSMSSettingSerializer', 'TencentSMSSettingSerializer',
'HuaweiSMSSettingSerializer', 'CMPP2SMSSettingSerializer'
'HuaweiSMSSettingSerializer', 'CMPP2SMSSettingSerializer', 'CustomSMSSettingSerializer',
]
@ -87,3 +88,27 @@ class CMPP2SMSSettingSerializer(BaseSMSSettingSerializer):
# 保证验证码内容在一条短信中(长度小于70字), 签名两边的括号和空格占3个字再减去2个即可(验证码占用4个但占位符6个
raise serializers.ValidationError(_('Signature + Template must not exceed 65 words'))
return attrs
class CustomSMSSettingSerializer(BaseSMSSettingSerializer):
class RequestType(models.TextChoices):
get = 'get', 'Get'
post = 'post', 'Post'
CUSTOM_SMS_URL = serializers.URLField(required=True, label=_("URL"))
CUSTOM_SMS_API_PARAMS = serializers.DictField(
label=_('Parameters'), default={'phone_number': '{phone_number}', 'code': '{code}'}
)
CUSTOM_SMS_REQUEST_METHOD = serializers.ChoiceField(
default=RequestType.get, choices=RequestType.choices, label=_("Request method")
)
@staticmethod
def validate(attrs):
need_params = {'{phone_numbers}', '{code}'}
params = attrs.get('CUSTOM_SMS_API_PARAMS', {})
if len(set(params.values()) & need_params) != len(need_params):
raise serializers.ValidationError(
_('The value in the parameter must contain %s') % ','.join(need_params)
)
return attrs

View File

@ -12,7 +12,8 @@ from .auth import (
CASSettingSerializer, RadiusSettingSerializer, FeiShuSettingSerializer,
WeComSettingSerializer, DingTalkSettingSerializer, AlibabaSMSSettingSerializer,
TencentSMSSettingSerializer, CMPP2SMSSettingSerializer, AuthSettingSerializer,
SAML2SettingSerializer, OAuth2SettingSerializer, SSOSettingSerializer
SAML2SettingSerializer, OAuth2SettingSerializer, SSOSettingSerializer,
CustomSMSSettingSerializer,
)
from .terminal import TerminalSettingSerializer
from .security import SecuritySettingSerializer
@ -47,6 +48,7 @@ class SettingsSerializer(
AlibabaSMSSettingSerializer,
TencentSMSSettingSerializer,
CMPP2SMSSettingSerializer,
CustomSMSSettingSerializer,
):
CACHE_KEY = 'SETTING_FIELDS_MAPPING'

View File

@ -100,7 +100,10 @@ class UserTokenResetPasswordForm(forms.Form):
class UserForgotPasswordForm(forms.Form):
email = forms.CharField(label=_("Email"), required=False)
sms = forms.CharField(label=_('SMS'), required=False, max_length=11)
sms = forms.CharField(
label=_('SMS'), required=False,
help_text=_('The phone number must contain an area code, for example, +86')
)
code = forms.CharField(label=_('Verify code'), max_length=6, required=False)
form_type = forms.ChoiceField(
choices=[('sms', _('SMS')), ('email', _('Email'))],

View File

@ -59,6 +59,7 @@
<div id="validate-sms" class="validate-field margin-bottom">
<input type="tel" id="sms" name="sms" class="form-control input-style"
placeholder="{% trans 'Mobile number' %}" value="{{ sms }}">
<small style="color: #999; margin-left: 5px">{{ form.sms.help_text }}</small>
</div>
<div class="margin-bottom challenge-required">
<input type="text" id="code" name="code" class="form-control input-style"