mirror of https://github.com/jumpserver/jumpserver
perf: 支持自定义短信认证
parent
5e7d474bb7
commit
20cc4ea320
|
@ -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
|
|
@ -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(
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -43,6 +43,7 @@ class SettingsApi(generics.RetrieveUpdateAPIView):
|
|||
'tencent': serializers.TencentSMSSettingSerializer,
|
||||
'huawei': serializers.HuaweiSMSSettingSerializer,
|
||||
'cmpp2': serializers.CMPP2SMSSettingSerializer,
|
||||
'custom': serializers.CustomSMSSettingSerializer,
|
||||
}
|
||||
|
||||
rbac_category_permissions = {
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
返回两部分参数
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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'))],
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue