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')
|
TENCENT = 'tencent', _('Tencent cloud')
|
||||||
HUAWEI = 'huawei', _('Huawei Cloud')
|
HUAWEI = 'huawei', _('Huawei Cloud')
|
||||||
CMPP2 = 'cmpp2', _('CMPP v2.0')
|
CMPP2 = 'cmpp2', _('CMPP v2.0')
|
||||||
|
Custom = 'custom', _('Custom type')
|
||||||
|
|
||||||
|
|
||||||
class SMS:
|
class SMS:
|
||||||
|
@ -42,8 +43,9 @@ class SMS:
|
||||||
)
|
)
|
||||||
|
|
||||||
def send_verify_code(self, phone_number, code):
|
def send_verify_code(self, phone_number, code):
|
||||||
sign_name = getattr(settings, f'{self.client.SIGN_AND_TMPL_SETTING_FIELD_PREFIX}_VERIFY_SIGN_NAME')
|
prefix = getattr(self.client, 'SIGN_AND_TMPL_SETTING_FIELD_PREFIX', '')
|
||||||
template_code = getattr(settings, f'{self.client.SIGN_AND_TMPL_SETTING_FIELD_PREFIX}_VERIFY_TEMPLATE_CODE')
|
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):
|
if self.client.need_pre_check() and not (sign_name and template_code):
|
||||||
raise JMSException(
|
raise JMSException(
|
||||||
|
|
|
@ -414,6 +414,10 @@ class Config(dict):
|
||||||
'CMPP2_VERIFY_SIGN_NAME': '',
|
'CMPP2_VERIFY_SIGN_NAME': '',
|
||||||
'CMPP2_VERIFY_TEMPLATE_CODE': '{code}',
|
'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
|
||||||
'EMAIL_CUSTOM_USER_CREATED_SUBJECT': _('Create account successfully'),
|
'EMAIL_CUSTOM_USER_CREATED_SUBJECT': _('Create account successfully'),
|
||||||
'EMAIL_CUSTOM_USER_CREATED_HONORIFIC': _('Hello'),
|
'EMAIL_CUSTOM_USER_CREATED_HONORIFIC': _('Hello'),
|
||||||
|
|
|
@ -43,6 +43,7 @@ class SettingsApi(generics.RetrieveUpdateAPIView):
|
||||||
'tencent': serializers.TencentSMSSettingSerializer,
|
'tencent': serializers.TencentSMSSettingSerializer,
|
||||||
'huawei': serializers.HuaweiSMSSettingSerializer,
|
'huawei': serializers.HuaweiSMSSettingSerializer,
|
||||||
'cmpp2': serializers.CMPP2SMSSettingSerializer,
|
'cmpp2': serializers.CMPP2SMSSettingSerializer,
|
||||||
|
'custom': serializers.CustomSMSSettingSerializer,
|
||||||
}
|
}
|
||||||
|
|
||||||
rbac_category_permissions = {
|
rbac_category_permissions = {
|
||||||
|
|
|
@ -39,7 +39,8 @@ class SMSTestingAPI(GenericAPIView):
|
||||||
'alibaba': serializers.AlibabaSMSSettingSerializer,
|
'alibaba': serializers.AlibabaSMSSettingSerializer,
|
||||||
'tencent': serializers.TencentSMSSettingSerializer,
|
'tencent': serializers.TencentSMSSettingSerializer,
|
||||||
'huawei': serializers.HuaweiSMSSettingSerializer,
|
'huawei': serializers.HuaweiSMSSettingSerializer,
|
||||||
'cmpp2': serializers.CMPP2SMSSettingSerializer
|
'cmpp2': serializers.CMPP2SMSSettingSerializer,
|
||||||
|
'custom': serializers.CustomSMSSettingSerializer,
|
||||||
}
|
}
|
||||||
rbac_perms = {
|
rbac_perms = {
|
||||||
'POST': 'settings.change_sms'
|
'POST': 'settings.change_sms'
|
||||||
|
@ -115,6 +116,12 @@ class SMSTestingAPI(GenericAPIView):
|
||||||
}
|
}
|
||||||
return init_params, send_sms_params
|
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):
|
def get_params_by_backend(self, backend, data):
|
||||||
"""
|
"""
|
||||||
返回两部分参数
|
返回两部分参数
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.db import models
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from common.serializers.fields import EncryptedField
|
from common.serializers.fields import EncryptedField
|
||||||
|
@ -7,7 +8,7 @@ from common.sdk.sms import BACKENDS
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'SMSSettingSerializer', 'AlibabaSMSSettingSerializer', 'TencentSMSSettingSerializer',
|
'SMSSettingSerializer', 'AlibabaSMSSettingSerializer', 'TencentSMSSettingSerializer',
|
||||||
'HuaweiSMSSettingSerializer', 'CMPP2SMSSettingSerializer'
|
'HuaweiSMSSettingSerializer', 'CMPP2SMSSettingSerializer', 'CustomSMSSettingSerializer',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -87,3 +88,27 @@ class CMPP2SMSSettingSerializer(BaseSMSSettingSerializer):
|
||||||
# 保证验证码内容在一条短信中(长度小于70字), 签名两边的括号和空格占3个字,再减去2个即可(验证码占用4个但占位符6个
|
# 保证验证码内容在一条短信中(长度小于70字), 签名两边的括号和空格占3个字,再减去2个即可(验证码占用4个但占位符6个
|
||||||
raise serializers.ValidationError(_('Signature + Template must not exceed 65 words'))
|
raise serializers.ValidationError(_('Signature + Template must not exceed 65 words'))
|
||||||
return attrs
|
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,
|
CASSettingSerializer, RadiusSettingSerializer, FeiShuSettingSerializer,
|
||||||
WeComSettingSerializer, DingTalkSettingSerializer, AlibabaSMSSettingSerializer,
|
WeComSettingSerializer, DingTalkSettingSerializer, AlibabaSMSSettingSerializer,
|
||||||
TencentSMSSettingSerializer, CMPP2SMSSettingSerializer, AuthSettingSerializer,
|
TencentSMSSettingSerializer, CMPP2SMSSettingSerializer, AuthSettingSerializer,
|
||||||
SAML2SettingSerializer, OAuth2SettingSerializer, SSOSettingSerializer
|
SAML2SettingSerializer, OAuth2SettingSerializer, SSOSettingSerializer,
|
||||||
|
CustomSMSSettingSerializer,
|
||||||
)
|
)
|
||||||
from .terminal import TerminalSettingSerializer
|
from .terminal import TerminalSettingSerializer
|
||||||
from .security import SecuritySettingSerializer
|
from .security import SecuritySettingSerializer
|
||||||
|
@ -47,6 +48,7 @@ class SettingsSerializer(
|
||||||
AlibabaSMSSettingSerializer,
|
AlibabaSMSSettingSerializer,
|
||||||
TencentSMSSettingSerializer,
|
TencentSMSSettingSerializer,
|
||||||
CMPP2SMSSettingSerializer,
|
CMPP2SMSSettingSerializer,
|
||||||
|
CustomSMSSettingSerializer,
|
||||||
):
|
):
|
||||||
CACHE_KEY = 'SETTING_FIELDS_MAPPING'
|
CACHE_KEY = 'SETTING_FIELDS_MAPPING'
|
||||||
|
|
||||||
|
|
|
@ -100,7 +100,10 @@ class UserTokenResetPasswordForm(forms.Form):
|
||||||
|
|
||||||
class UserForgotPasswordForm(forms.Form):
|
class UserForgotPasswordForm(forms.Form):
|
||||||
email = forms.CharField(label=_("Email"), required=False)
|
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)
|
code = forms.CharField(label=_('Verify code'), max_length=6, required=False)
|
||||||
form_type = forms.ChoiceField(
|
form_type = forms.ChoiceField(
|
||||||
choices=[('sms', _('SMS')), ('email', _('Email'))],
|
choices=[('sms', _('SMS')), ('email', _('Email'))],
|
||||||
|
|
|
@ -59,6 +59,7 @@
|
||||||
<div id="validate-sms" class="validate-field margin-bottom">
|
<div id="validate-sms" class="validate-field margin-bottom">
|
||||||
<input type="tel" id="sms" name="sms" class="form-control input-style"
|
<input type="tel" id="sms" name="sms" class="form-control input-style"
|
||||||
placeholder="{% trans 'Mobile number' %}" value="{{ sms }}">
|
placeholder="{% trans 'Mobile number' %}" value="{{ sms }}">
|
||||||
|
<small style="color: #999; margin-left: 5px">{{ form.sms.help_text }}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="margin-bottom challenge-required">
|
<div class="margin-bottom challenge-required">
|
||||||
<input type="text" id="code" name="code" class="form-control input-style"
|
<input type="text" id="code" name="code" class="form-control input-style"
|
||||||
|
|
Loading…
Reference in New Issue