From 20cc4ea320b810932915ae7caea61fc04083686a Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Wed, 24 May 2023 17:32:57 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E6=94=AF=E6=8C=81=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E7=9F=AD=E4=BF=A1=E8=AE=A4=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/sdk/sms/custom.py | 48 +++++++++++++++++++ apps/common/sdk/sms/endpoint.py | 6 ++- apps/jumpserver/conf.py | 4 ++ apps/settings/api/settings.py | 1 + apps/settings/api/sms.py | 9 +++- apps/settings/serializers/auth/sms.py | 27 ++++++++++- apps/settings/serializers/settings.py | 4 +- apps/users/forms/profile.py | 5 +- .../templates/users/forgot_password.html | 1 + 9 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 apps/common/sdk/sms/custom.py diff --git a/apps/common/sdk/sms/custom.py b/apps/common/sdk/sms/custom.py new file mode 100644 index 000000000..6eb0748ed --- /dev/null +++ b/apps/common/sdk/sms/custom.py @@ -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 diff --git a/apps/common/sdk/sms/endpoint.py b/apps/common/sdk/sms/endpoint.py index 044cf28a2..ce016888a 100644 --- a/apps/common/sdk/sms/endpoint.py +++ b/apps/common/sdk/sms/endpoint.py @@ -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( diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 18872058a..ee4ee690a 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -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'), diff --git a/apps/settings/api/settings.py b/apps/settings/api/settings.py index 5a516491c..6cc2743e2 100644 --- a/apps/settings/api/settings.py +++ b/apps/settings/api/settings.py @@ -43,6 +43,7 @@ class SettingsApi(generics.RetrieveUpdateAPIView): 'tencent': serializers.TencentSMSSettingSerializer, 'huawei': serializers.HuaweiSMSSettingSerializer, 'cmpp2': serializers.CMPP2SMSSettingSerializer, + 'custom': serializers.CustomSMSSettingSerializer, } rbac_category_permissions = { diff --git a/apps/settings/api/sms.py b/apps/settings/api/sms.py index 301f4e203..01fca4436 100644 --- a/apps/settings/api/sms.py +++ b/apps/settings/api/sms.py @@ -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): """ 返回两部分参数 diff --git a/apps/settings/serializers/auth/sms.py b/apps/settings/serializers/auth/sms.py index cb5085386..41d7d5ac6 100644 --- a/apps/settings/serializers/auth/sms.py +++ b/apps/settings/serializers/auth/sms.py @@ -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 diff --git a/apps/settings/serializers/settings.py b/apps/settings/serializers/settings.py index 21ca6d461..2fb15b0af 100644 --- a/apps/settings/serializers/settings.py +++ b/apps/settings/serializers/settings.py @@ -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' diff --git a/apps/users/forms/profile.py b/apps/users/forms/profile.py index 56c496ce4..48dcb6220 100644 --- a/apps/users/forms/profile.py +++ b/apps/users/forms/profile.py @@ -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'))], diff --git a/apps/users/templates/users/forgot_password.html b/apps/users/templates/users/forgot_password.html index 755e48dcf..64137ea8a 100644 --- a/apps/users/templates/users/forgot_password.html +++ b/apps/users/templates/users/forgot_password.html @@ -59,6 +59,7 @@
+ {{ form.sms.help_text }}