feat: 支持CMPPv2.0协议短信网关 (#8591)

* feat: 支持CMPPv2.0协议短信网关

* 修改翻译

Co-authored-by: Jiangjie.Bai <32935519+BaiJiangJie@users.noreply.github.com>
pull/8727/head
jiangweidong 2022-08-09 16:09:20 +08:00 committed by GitHub
parent 6a30e0739d
commit 708a87c903
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 686 additions and 238 deletions

View File

@ -17,4 +17,8 @@ class BaseSMSClient:
def send_sms(self, phone_numbers: list, sign_name: str, template_code: str, template_param: dict, **kwargs):
raise NotImplementedError
@staticmethod
def need_pre_check():
return True

View File

@ -0,0 +1,322 @@
import hashlib
import socket
import struct
import time
from django.conf import settings
from common.utils import get_logger
from common.exceptions import JMSException
from .base import BaseSMSClient
logger = get_logger(__file__)
CMPP_CONNECT = 0x00000001 # 请求连接
CMPP_CONNECT_RESP = 0x80000001 # 请求连接应答
CMPP_TERMINATE = 0x00000002 # 终止连接
CMPP_TERMINATE_RESP = 0x80000002 # 终止连接应答
CMPP_SUBMIT = 0x00000004 # 提交短信
CMPP_SUBMIT_RESP = 0x80000004 # 提交短信应答
CMPP_DELIVER = 0x00000005 # 短信下发
CMPP_DELIVER_RESP = 0x80000005 # 下发短信应答
class CMPPBaseRequestInstance(object):
def __init__(self):
self.command_id = ''
self.body = b''
self.length = 0
def get_header(self, sequence_id):
length = struct.pack('!L', 12 + self.length)
command_id = struct.pack('!L', self.command_id)
sequence_id = struct.pack('!L', sequence_id)
return length + command_id + sequence_id
def get_message(self, sequence_id):
return self.get_header(sequence_id) + self.body
class CMPPConnectRequestInstance(CMPPBaseRequestInstance):
def __init__(self, sp_id, sp_secret):
if len(sp_id) != 6:
raise ValueError("sp_id and sp_secret are both 6 bits")
super().__init__()
source_addr = sp_id.encode('utf-8')
sp_secret = sp_secret.encode('utf-8')
version = struct.pack('!B', 0x02)
timestamp = struct.pack('!L', int(self.get_now()))
authenticator_source = source_addr + 9 * b'\x00' + sp_secret + self.get_now().encode('utf-8')
auth_source_md5 = hashlib.md5(authenticator_source).digest()
self.body = source_addr + auth_source_md5 + version + timestamp
self.length = len(self.body)
self.command_id = CMPP_CONNECT
@staticmethod
def get_now():
return time.strftime('%m%d%H%M%S', time.localtime(time.time()))
class CMPPSubmitRequestInstance(CMPPBaseRequestInstance):
def __init__(self, msg_src, dest_terminal_id, msg_content, src_id,
service_id='', dest_usr_tl=1):
if len(msg_content) >= 70:
raise JMSException('The message length should be within 70 characters')
if len(dest_terminal_id) > 100:
raise JMSException('The number of users receiving information should be less than 100')
super().__init__()
msg_id = 8 * b'\x00'
pk_total = struct.pack('!B', 1)
pk_number = struct.pack('!B', 1)
registered_delivery = struct.pack('!B', 0)
msg_level = struct.pack('!B', 0)
service_id = ((10 - len(service_id)) * '\x00' + service_id).encode('utf-8')
fee_user_type = struct.pack('!B', 2)
fee_terminal_id = ('0' * 21).encode('utf-8')
tp_pid = struct.pack('!B', 0)
tp_udhi = struct.pack('!B', 0)
msg_fmt = struct.pack('!B', 8)
fee_type = '01'.encode('utf-8')
fee_code = '000000'.encode('utf-8')
valid_time = ('\x00' * 17).encode('utf-8')
at_time = ('\x00' * 17).encode('utf-8')
src_id = ((21 - len(src_id)) * '\x00' + src_id).encode('utf-8')
reserve = b'\x00' * 8
_msg_length = struct.pack('!B', len(msg_content) * 2)
_msg_src = msg_src.encode('utf-8')
_dest_usr_tl = struct.pack('!B', dest_usr_tl)
_msg_content = msg_content.encode('utf-16-be')
_dest_terminal_id = b''.join([
(i + (21 - len(i)) * '\x00').encode('utf-8') for i in dest_terminal_id
])
self.length = 126 + 21 * dest_usr_tl + len(_msg_content)
self.command_id = CMPP_SUBMIT
self.body = msg_id + pk_total + pk_number + registered_delivery \
+ msg_level + service_id + fee_user_type + fee_terminal_id \
+ tp_pid + tp_udhi + msg_fmt + _msg_src + fee_type + fee_code \
+ valid_time + at_time + src_id + _dest_usr_tl + _dest_terminal_id \
+ _msg_length + _msg_content + reserve
class CMPPTerminateRequestInstance(CMPPBaseRequestInstance):
def __init__(self):
super().__init__()
self.body = b''
self.command_id = CMPP_TERMINATE
class CMPPDeliverRespRequestInstance(CMPPBaseRequestInstance):
def __init__(self, msg_id, result=0):
super().__init__()
msg_id = struct.pack('!Q', msg_id)
result = struct.pack('!B', result)
self.length = len(self.body)
self.body = msg_id + result
class CMPPResponseInstance(object):
def __init__(self):
self.command_id = None
self.length = None
self.response_handler_map = {
CMPP_CONNECT_RESP: self.connect_response_parse,
CMPP_SUBMIT_RESP: self.submit_response_parse,
CMPP_DELIVER: self.deliver_request_parse,
}
@staticmethod
def connect_response_parse(body):
status, = struct.unpack('!B', body[0:1])
authenticator_ISMG = body[1:17]
version, = struct.unpack('!B', body[17:18])
return {
'Status': status,
'AuthenticatorISMG': authenticator_ISMG,
'Version': version
}
@staticmethod
def submit_response_parse(body):
msg_id = body[:8]
result = struct.unpack('!B', body[8:9])
return {
'Msg_Id': msg_id, 'Result': result[0]
}
@staticmethod
def deliver_request_parse(body):
msg_id, = struct.unpack('!Q', body[0:8])
dest_id = body[8:29]
service_id = body[29:39]
tp_pid = struct.unpack('!B', body[39:40])
tp_udhi = struct.unpack('!B', body[40:41])
msg_fmt = struct.unpack('!B', body[41:42])
src_terminal_id = body[42:63]
registered_delivery = struct.unpack('!B', body[63:64])
msg_length = struct.unpack('!B', body[64:65])
msg_content = body[65:msg_length[0]+65]
return {
'Msg_Id': msg_id, 'Dest_Id': dest_id, 'Service_Id': service_id,
'TP_pid': tp_pid, 'TP_udhi': tp_udhi, 'Msg_Fmt': msg_fmt,
'Src_terminal_Id': src_terminal_id, 'Registered_Delivery': registered_delivery,
'Msg_Length': msg_length, 'Msg_content': msg_content
}
def parse_header(self, data):
self.command_id, = struct.unpack('!L', data[4:8])
sequence_id, = struct.unpack('!L', data[8:12])
return {
'length': self.length,
'command_id': hex(self.command_id),
'sequence_id': sequence_id
}
def parse_body(self, body):
response_body_func = self.response_handler_map.get(self.command_id)
if response_body_func is None:
raise JMSException('Unable to parse the returned result: %s' % body)
return response_body_func(body)
def parse(self, data):
self.length, = struct.unpack('!L', data[0:4])
header = self.parse_header(data)
body = self.parse_body(data[12:self.length])
return header, body
class CMPPClient(object):
def __init__(self, host, port, sp_id, sp_secret, src_id, service_id):
self.ip = host
self.port = port
self.sp_id = sp_id
self.sp_secret = sp_secret
self.src_id = src_id
self.service_id = service_id
self._sequence_id = 0
self._is_connect = False
self._times = 3
self.__socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._connect()
@property
def sequence_id(self):
s = self._sequence_id
self._sequence_id += 1
return s
def _connect(self):
self.__socket.settimeout(5)
for i in range(self._times):
try:
self.__socket.connect((self.ip, self.port))
except socket.timeout:
time.sleep(1)
else:
self._is_connect = True
break
def send(self, instance):
if isinstance(instance, CMPPBaseRequestInstance):
message = instance.get_message(sequence_id=self.sequence_id)
else:
message = instance
self.__socket.send(message)
def recv(self):
raw_length = self.__socket.recv(4)
length, = struct.unpack('!L', raw_length)
header, body = CMPPResponseInstance().parse(
raw_length + self.__socket.recv(length - 4)
)
return header, body
def close(self):
if self._is_connect:
terminate_request = CMPPTerminateRequestInstance()
self.send(terminate_request)
self.__socket.close()
def _cmpp_connect(self):
connect_request = CMPPConnectRequestInstance(self.sp_id, self.sp_secret)
self.send(connect_request)
header, body = self.recv()
if body['Status'] != 0:
raise JMSException('CMPPv2.0 authentication failed: %s' % body)
def _cmpp_send_sms(self, dest, sign_name, template_code, template_param):
"""
优先发送template_param中message的信息
若该内容不存在则根据template_code构建验证码发送
"""
message = template_param.get('message')
if message is None:
code = template_param.get('code')
message = template_code.replace('{code}', code)
msg = '%s%s' % (sign_name, message)
submit_request = CMPPSubmitRequestInstance(
msg_src=self.sp_id, src_id=self.src_id, msg_content=msg,
dest_usr_tl=len(dest), dest_terminal_id=dest,
service_id=self.service_id
)
self.send(submit_request)
header, body = self.recv()
command_id = header.get('command_id')
if command_id == CMPP_DELIVER:
deliver_request = CMPPDeliverRespRequestInstance(
msg_id=body['Msg_Id'], result=body['Result']
)
self.send(deliver_request)
def send_sms(self, dest, sign_name, template_code, template_param):
try:
self._cmpp_connect()
self._cmpp_send_sms(dest, sign_name, template_code, template_param)
except Exception as e:
logger.error('CMPPv2.0 Error: %s', e)
self.close()
raise JMSException(e)
class CMPP2SMS(BaseSMSClient):
SIGN_AND_TMPL_SETTING_FIELD_PREFIX = 'CMPP2'
@classmethod
def new_from_settings(cls):
return cls(
host=settings.CMPP2_HOST, port=settings.CMPP2_PORT,
sp_id=settings.CMPP2_SP_ID, sp_secret=settings.CMPP2_SP_SECRET,
service_id=settings.CMPP2_SERVICE_ID, src_id=getattr(settings, 'CMPP2_SRC_ID', ''),
)
def __init__(self, host: str, port: int, sp_id: str, sp_secret: str, service_id: str, src_id=''):
try:
self.client = CMPPClient(
host=host, port=port, sp_id=sp_id, sp_secret=sp_secret, src_id=src_id, service_id=service_id
)
except socket.timeout:
self.client = None
logger.warning('CMPPv2.0 connect remote time out.')
@staticmethod
def need_pre_check():
return False
def send_sms(self, phone_numbers: list, sign_name: str, template_code: str, template_param: dict, **kwargs):
try:
logger.info(f'CMPPv2.0 sms send: '
f'phone_numbers={phone_numbers} '
f'sign_name={sign_name} '
f'template_code={template_code} '
f'template_param={template_param}')
self.client.send_sms(phone_numbers, sign_name, template_code, template_param)
except Exception as e:
raise JMSException(e)
client = CMPP2SMS

View File

@ -15,6 +15,7 @@ logger = get_logger(__name__)
class BACKENDS(TextChoices):
ALIBABA = 'alibaba', _('Alibaba cloud')
TENCENT = 'tencent', _('Tencent cloud')
CMPP2 = 'cmpp2', _('CMPP v2.0')
class SMS:
@ -43,7 +44,7 @@ class SMS:
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')
if not (sign_name and template_code):
if self.client.need_pre_check() and not (sign_name and template_code):
raise JMSException(
code='verify_code_sign_tmpl_invalid',
detail=_('SMS verification code signature or template invalid')

View File

@ -376,6 +376,15 @@ class Config(dict):
'TENCENT_VERIFY_SIGN_NAME': '',
'TENCENT_VERIFY_TEMPLATE_CODE': '',
'CMPP2_HOST': '',
'CMPP2_PORT': 7890,
'CMPP2_SP_ID': '',
'CMPP2_SP_SECRET': '',
'CMPP2_SRC_ID': '',
'CMPP2_SERVICE_ID': '',
'CMPP2_VERIFY_SIGN_NAME': '',
'CMPP2_VERIFY_TEMPLATE_CODE': '{code}',
# Email
'EMAIL_CUSTOM_USER_CREATED_SUBJECT': _('Create account successfully'),
'EMAIL_CUSTOM_USER_CREATED_HONORIFIC': _('Hello'),

View File

@ -88,8 +88,8 @@ msgstr "ログイン確認"
#: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:20
#: assets/models/cmd_filter.py:30 assets/models/label.py:15 audits/models.py:37
#: audits/models.py:62 audits/models.py:87 audits/serializers.py:100
#: authentication/models.py:54 authentication/models.py:78 orgs/models.py:220
#: perms/models/base.py:84 rbac/builtin.py:120 rbac/models/rolebinding.py:41
#: authentication/models.py:54 authentication/models.py:78 orgs/models.py:214
#: perms/models/base.py:84 rbac/builtin.py:117 rbac/models/rolebinding.py:41
#: terminal/backends/command/models.py:20
#: terminal/backends/command/serializers.py:13 terminal/models/session.py:44
#: terminal/models/sharing.py:33 terminal/notifications.py:91
@ -393,8 +393,8 @@ msgstr "クラスター"
#: applications/serializers/attrs/application_category/db.py:11
#: ops/models/adhoc.py:157 settings/serializers/auth/radius.py:14
#: terminal/models/endpoint.py:11
#: xpack/plugins/cloud/serializers/account_attrs.py:72
#: settings/serializers/auth/sms.py:52 terminal/models/endpoint.py:11
#: xpack/plugins/cloud/serializers/account_attrs.py:70
msgid "Host"
msgstr "ホスト"
@ -407,8 +407,8 @@ msgstr "ホスト"
#: applications/serializers/attrs/application_type/redis.py:10
#: applications/serializers/attrs/application_type/sqlserver.py:10
#: assets/models/asset.py:214 assets/models/domain.py:62
#: settings/serializers/auth/radius.py:15
#: xpack/plugins/cloud/serializers/account_attrs.py:73
#: settings/serializers/auth/radius.py:15 settings/serializers/auth/sms.py:53
#: xpack/plugins/cloud/serializers/account_attrs.py:71
msgid "Port"
msgstr "ポート"
@ -591,7 +591,7 @@ msgstr "ホスト名生"
#: assets/models/asset.py:215 assets/serializers/account.py:16
#: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41
#: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:43
#: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:42
msgid "Protocols"
msgstr "プロトコル"
@ -990,7 +990,7 @@ msgid "Parent key"
msgstr "親キー"
#: assets/models/node.py:559 assets/serializers/system_user.py:267
#: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:70
#: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:69
msgid "Node"
msgstr "ノード"
@ -2318,7 +2318,7 @@ msgstr "コードエラー"
#: authentication/templates/authentication/_msg_reset_password.html:3
#: authentication/templates/authentication/_msg_rest_password_success.html:2
#: authentication/templates/authentication/_msg_rest_public_key_success.html:2
#: jumpserver/conf.py:323 ops/tasks.py:145 ops/tasks.py:148
#: jumpserver/conf.py:316 ops/tasks.py:145 ops/tasks.py:148
#: perms/templates/perms/_msg_item_permissions_expire.html:3
#: perms/templates/perms/_msg_permed_items_expire.html:3
#: tickets/templates/tickets/approve_check_password.html:33
@ -2723,11 +2723,15 @@ msgstr "アリ雲"
msgid "Tencent cloud"
msgstr "テンセント雲"
#: common/sdk/sms/endpoint.py:28
#: common/sdk/sms/endpoint.py:18
msgid "CMPP v2.0"
msgstr "CMPP v2.0"
#: common/sdk/sms/endpoint.py:29
msgid "SMS provider not support: {}"
msgstr "SMSプロバイダーはサポートしていません: {}"
#: common/sdk/sms/endpoint.py:49
#: common/sdk/sms/endpoint.py:50
msgid "SMS verification code signature or template invalid"
msgstr "SMS検証コードの署名またはテンプレートが無効"
@ -2763,11 +2767,11 @@ msgstr "特殊文字を含むべきではない"
msgid "The mobile phone number format is incorrect"
msgstr "携帯電話番号の形式が正しくありません"
#: jumpserver/conf.py:322
#: jumpserver/conf.py:315
msgid "Create account successfully"
msgstr "アカウントを正常に作成"
#: jumpserver/conf.py:324
#: jumpserver/conf.py:317
msgid "Your account has been created successfully"
msgstr "アカウントが正常に作成されました"
@ -3026,8 +3030,8 @@ msgstr "組織のリソース ({}) は削除できません"
msgid "App organizations"
msgstr "アプリ組織"
#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:85
#: orgs/models.py:217 rbac/const.py:7 rbac/models/rolebinding.py:48
#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:80
#: orgs/models.py:211 rbac/const.py:7 rbac/models/rolebinding.py:48
#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62
#: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:71
msgid "Organization"
@ -3271,27 +3275,27 @@ msgstr "{} 少なくとも1つのシステムロール"
msgid "RBAC"
msgstr "RBAC"
#: rbac/builtin.py:111
#: rbac/builtin.py:108
msgid "SystemAdmin"
msgstr "システム管理者"
#: rbac/builtin.py:114
#: rbac/builtin.py:111
msgid "SystemAuditor"
msgstr "システム監査人"
#: rbac/builtin.py:117
#: rbac/builtin.py:114
msgid "SystemComponent"
msgstr "システムコンポーネント"
#: rbac/builtin.py:123
#: rbac/builtin.py:120
msgid "OrgAdmin"
msgstr "組織管理者"
#: rbac/builtin.py:126
#: rbac/builtin.py:123
msgid "OrgAuditor"
msgstr "監査員を組織する"
#: rbac/builtin.py:129
#: rbac/builtin.py:126
msgid "OrgUser"
msgstr "組織ユーザー"
@ -3463,13 +3467,8 @@ msgstr "権限ツリーの表示"
msgid "Execute batch command"
msgstr "バッチ実行コマンド"
#: settings/api/alibaba_sms.py:31 settings/api/tencent_sms.py:35
msgid "test_phone is required"
msgstr "携帯番号をテストこのフィールドは必須です"
#: settings/api/alibaba_sms.py:52 settings/api/dingtalk.py:31
#: settings/api/feishu.py:36 settings/api/tencent_sms.py:57
#: settings/api/wecom.py:37
#: settings/api/dingtalk.py:31 settings/api/feishu.py:36
#: settings/api/sms.py:131 settings/api/wecom.py:37
msgid "Test success"
msgstr "テストの成功"
@ -3497,6 +3496,14 @@ msgstr "Ldapユーザーを取得するにはNone"
msgid "Imported {} users successfully (Organization: {})"
msgstr "{} 人のユーザーを正常にインポートしました (組織: {})"
#: settings/api/sms.py:113
msgid "Invalid SMS platform"
msgstr "無効なショートメッセージプラットフォーム"
#: settings/api/sms.py:119
msgid "test_phone is required"
msgstr "携帯番号をテストこのフィールドは必須です"
#: settings/apps.py:7
msgid "Settings"
msgstr "設定"
@ -3821,29 +3828,69 @@ msgstr "SP プライベートキー"
msgid "SP cert"
msgstr "SP 証明書"
#: settings/serializers/auth/sms.py:11
#: settings/serializers/auth/sms.py:14
msgid "Enable SMS"
msgstr "SMSの有効化"
#: settings/serializers/auth/sms.py:13
msgid "SMS provider"
msgstr "SMSプロバイダ"
#: settings/serializers/auth/sms.py:16
msgid "SMS provider / Protocol"
msgstr "SMSプロバイダ / プロトコル"
#: settings/serializers/auth/sms.py:18 settings/serializers/auth/sms.py:36
#: settings/serializers/auth/sms.py:44 settings/serializers/email.py:65
#: settings/serializers/auth/sms.py:21 settings/serializers/auth/sms.py:39
#: settings/serializers/auth/sms.py:47 settings/serializers/auth/sms.py:58
#: settings/serializers/email.py:65
msgid "Signature"
msgstr "署名"
#: settings/serializers/auth/sms.py:19 settings/serializers/auth/sms.py:37
#: settings/serializers/auth/sms.py:45
#: settings/serializers/auth/sms.py:22 settings/serializers/auth/sms.py:40
#: settings/serializers/auth/sms.py:48
msgid "Template code"
msgstr "テンプレートコード"
#: settings/serializers/auth/sms.py:23
#: settings/serializers/auth/sms.py:26
msgid "Test phone"
msgstr "テスト電話"
#: settings/serializers/auth/sso.py:11
#: settings/serializers/auth/sms.py:54
msgid "Gateway account(SP id)"
msgstr "ゲートウェイアカウント(SP id)"
#: settings/serializers/auth/sms.py:55
msgid "Gateway password(SP secret)"
msgstr "ゲートウェイパスワード(SP secret)"
#: settings/serializers/auth/sms.py:56
msgid "Original number(Src id)"
msgstr "元の番号(Src id)"
#: settings/serializers/auth/sms.py:57
msgid "Business type(Service id)"
msgstr "ビジネス・タイプ(Service id)"
#: settings/serializers/auth/sms.py:60
msgid "Template"
msgstr "テンプレート"
#: settings/serializers/auth/sms.py:61
msgid ""
"Template need contain {code} and Signature + template length does not exceed "
"67 words. For example, your verification code is {code}, which is valid for "
"5 minutes. Please do not disclose it to others."
msgstr ""
"テンプレートには{code}を含める必要があり、署名+テンプレートの長さは67ワード未"
"満です。たとえば、認証コードは{code}で、有効期間は5分です。他の人には言わない"
"でください。"
#: settings/serializers/auth/sms.py:70
#, python-brace-format
msgid "The template needs to contain {code}"
msgstr "テンプレートには{code}を含める必要があります"
#: settings/serializers/auth/sms.py:73
msgid "Signature + Template must not exceed 65 words"
msgstr "署名+テンプレートの長さは65文字以内"
#: settings/serializers/auth/sso.py:12
msgid "Enable SSO auth"
msgstr "SSO Token認証の有効化"
@ -6460,11 +6507,11 @@ msgstr "クラウドアカウント"
msgid "Test cloud account"
msgstr "クラウドアカウントのテスト"
#: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers/task.py:67
#: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers/task.py:66
msgid "Account"
msgstr "アカウント"
#: xpack/plugins/cloud/models.py:88 xpack/plugins/cloud/serializers/task.py:38
#: xpack/plugins/cloud/models.py:88 xpack/plugins/cloud/serializers/task.py:37
msgid "Regions"
msgstr "リージョン"
@ -6472,19 +6519,19 @@ msgstr "リージョン"
msgid "Hostname strategy"
msgstr "ホスト名戦略"
#: xpack/plugins/cloud/models.py:100 xpack/plugins/cloud/serializers/task.py:68
#: xpack/plugins/cloud/models.py:100 xpack/plugins/cloud/serializers/task.py:67
msgid "Unix admin user"
msgstr "Unix adminユーザー"
#: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:69
#: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:68
msgid "Windows admin user"
msgstr "Windows管理者"
#: xpack/plugins/cloud/models.py:110 xpack/plugins/cloud/serializers/task.py:46
#: xpack/plugins/cloud/models.py:110 xpack/plugins/cloud/serializers/task.py:45
msgid "IP network segment group"
msgstr "IPネットワークセグメントグループ"
#: xpack/plugins/cloud/models.py:113 xpack/plugins/cloud/serializers/task.py:72
#: xpack/plugins/cloud/models.py:113 xpack/plugins/cloud/serializers/task.py:71
msgid "Always update"
msgstr "常に更新"
@ -6790,11 +6837,9 @@ msgstr "テストポート"
#: xpack/plugins/cloud/serializers/task.py:29
msgid ""
"Only instances matching the IP range will be synced. <br>If the instance "
"contains multiple IP addresses, the first IP address that matches will be "
"used as the IP for the created asset. <br>The default value of * means sync "
"all instances and randomly match IP addresses. <br>Format for comma-"
"delimited string, Such as: 192.168.1.0/24, 10.1.1.1-10.1.1.20"
"The IP address that is first matched to will be used as the IP of the "
"created asset. <br>The default * indicates a random match. <br>Format for "
"comma-delimited string, Such as: 192.168.1.0/24, 10.1.1.1-10.1.1.20"
msgstr ""
"IP範囲に一致するインスタンスのみが同期されます。<br>インスタンスに複数のIPア"
"ドレスが含まれている場合、一致する最初のIPアドレスが作成されたアセットのIPと"
@ -6802,24 +6847,24 @@ msgstr ""
"ドレスをランダムに一致させることを意味します。 <br>形式はコンマ区切りの文字列"
"です。例192.168.1.0/24,10.1.1.1-10.1.1.20"
#: xpack/plugins/cloud/serializers/task.py:36
#: xpack/plugins/cloud/serializers/task.py:35
msgid "History count"
msgstr "実行回数"
#: xpack/plugins/cloud/serializers/task.py:37
#: xpack/plugins/cloud/serializers/task.py:36
msgid "Instance count"
msgstr "インスタンス数"
#: xpack/plugins/cloud/serializers/task.py:66
#: xpack/plugins/cloud/serializers/task.py:65
msgid "Linux admin user"
msgstr "Linux管理者"
#: xpack/plugins/cloud/serializers/task.py:71
#: xpack/plugins/cloud/serializers/task.py:70
#: xpack/plugins/gathered_user/serializers.py:20
msgid "Periodic display"
msgstr "定期的な表示"
#: xpack/plugins/cloud/utils.py:69
#: xpack/plugins/cloud/utils.py:68
msgid "Account unavailable"
msgstr "利用できないアカウント"

View File

@ -87,8 +87,8 @@ msgstr "登录复核"
#: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:20
#: assets/models/cmd_filter.py:30 assets/models/label.py:15 audits/models.py:37
#: audits/models.py:62 audits/models.py:87 audits/serializers.py:100
#: authentication/models.py:54 authentication/models.py:78 orgs/models.py:220
#: perms/models/base.py:84 rbac/builtin.py:120 rbac/models/rolebinding.py:41
#: authentication/models.py:54 authentication/models.py:78 orgs/models.py:214
#: perms/models/base.py:84 rbac/builtin.py:117 rbac/models/rolebinding.py:41
#: terminal/backends/command/models.py:20
#: terminal/backends/command/serializers.py:13 terminal/models/session.py:44
#: terminal/models/sharing.py:33 terminal/notifications.py:91
@ -388,8 +388,8 @@ msgstr "集群"
#: applications/serializers/attrs/application_category/db.py:11
#: ops/models/adhoc.py:157 settings/serializers/auth/radius.py:14
#: terminal/models/endpoint.py:11
#: xpack/plugins/cloud/serializers/account_attrs.py:72
#: settings/serializers/auth/sms.py:52 terminal/models/endpoint.py:11
#: xpack/plugins/cloud/serializers/account_attrs.py:70
msgid "Host"
msgstr "主机"
@ -402,8 +402,8 @@ msgstr "主机"
#: applications/serializers/attrs/application_type/redis.py:10
#: applications/serializers/attrs/application_type/sqlserver.py:10
#: assets/models/asset.py:214 assets/models/domain.py:62
#: settings/serializers/auth/radius.py:15
#: xpack/plugins/cloud/serializers/account_attrs.py:73
#: settings/serializers/auth/radius.py:15 settings/serializers/auth/sms.py:53
#: xpack/plugins/cloud/serializers/account_attrs.py:71
msgid "Port"
msgstr "端口"
@ -586,7 +586,7 @@ msgstr "主机名原始"
#: assets/models/asset.py:215 assets/serializers/account.py:16
#: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41
#: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:43
#: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:42
msgid "Protocols"
msgstr "协议组"
@ -985,7 +985,7 @@ msgid "Parent key"
msgstr "ssh私钥"
#: assets/models/node.py:559 assets/serializers/system_user.py:267
#: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:70
#: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:69
msgid "Node"
msgstr "节点"
@ -2293,7 +2293,7 @@ msgstr "代码错误"
#: authentication/templates/authentication/_msg_reset_password.html:3
#: authentication/templates/authentication/_msg_rest_password_success.html:2
#: authentication/templates/authentication/_msg_rest_public_key_success.html:2
#: jumpserver/conf.py:323 ops/tasks.py:145 ops/tasks.py:148
#: jumpserver/conf.py:316 ops/tasks.py:145 ops/tasks.py:148
#: perms/templates/perms/_msg_item_permissions_expire.html:3
#: perms/templates/perms/_msg_permed_items_expire.html:3
#: tickets/templates/tickets/approve_check_password.html:33
@ -2689,11 +2689,15 @@ msgstr "阿里云"
msgid "Tencent cloud"
msgstr "腾讯云"
#: common/sdk/sms/endpoint.py:28
#: common/sdk/sms/endpoint.py:18
msgid "CMPP v2.0"
msgstr "CMPP v2.0"
#: common/sdk/sms/endpoint.py:29
msgid "SMS provider not support: {}"
msgstr "短信服务商不支持:{}"
#: common/sdk/sms/endpoint.py:49
#: common/sdk/sms/endpoint.py:50
msgid "SMS verification code signature or template invalid"
msgstr "短信验证码签名或模版无效"
@ -2729,11 +2733,11 @@ msgstr "不能包含特殊字符"
msgid "The mobile phone number format is incorrect"
msgstr "手机号格式不正确"
#: jumpserver/conf.py:322
#: jumpserver/conf.py:315
msgid "Create account successfully"
msgstr "创建账号成功"
#: jumpserver/conf.py:324
#: jumpserver/conf.py:317
msgid "Your account has been created successfully"
msgstr "你的账号已创建成功"
@ -2986,8 +2990,8 @@ msgstr "组织存在资源 ({}) 不能被删除"
msgid "App organizations"
msgstr "组织管理"
#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:85
#: orgs/models.py:217 rbac/const.py:7 rbac/models/rolebinding.py:48
#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:80
#: orgs/models.py:211 rbac/const.py:7 rbac/models/rolebinding.py:48
#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62
#: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:71
msgid "Organization"
@ -3229,27 +3233,27 @@ msgstr "{} 至少有一个系统角色"
msgid "RBAC"
msgstr "RBAC"
#: rbac/builtin.py:111
#: rbac/builtin.py:108
msgid "SystemAdmin"
msgstr "系统管理员"
#: rbac/builtin.py:114
#: rbac/builtin.py:111
msgid "SystemAuditor"
msgstr "系统审计员"
#: rbac/builtin.py:117
#: rbac/builtin.py:114
msgid "SystemComponent"
msgstr "系统组件"
#: rbac/builtin.py:123
#: rbac/builtin.py:120
msgid "OrgAdmin"
msgstr "组织管理员"
#: rbac/builtin.py:126
#: rbac/builtin.py:123
msgid "OrgAuditor"
msgstr "组织审计员"
#: rbac/builtin.py:129
#: rbac/builtin.py:126
msgid "OrgUser"
msgstr "组织用户"
@ -3420,13 +3424,8 @@ msgstr "查看授权树"
msgid "Execute batch command"
msgstr "执行批量命令"
#: settings/api/alibaba_sms.py:31 settings/api/tencent_sms.py:35
msgid "test_phone is required"
msgstr "测试手机号 该字段是必填项。"
#: settings/api/alibaba_sms.py:52 settings/api/dingtalk.py:31
#: settings/api/feishu.py:36 settings/api/tencent_sms.py:57
#: settings/api/wecom.py:37
#: settings/api/dingtalk.py:31 settings/api/feishu.py:36
#: settings/api/sms.py:131 settings/api/wecom.py:37
msgid "Test success"
msgstr "测试成功"
@ -3454,6 +3453,14 @@ msgstr "获取 LDAP 用户为 None"
msgid "Imported {} users successfully (Organization: {})"
msgstr "成功导入 {} 个用户 ( 组织: {} )"
#: settings/api/sms.py:113
msgid "Invalid SMS platform"
msgstr "无效的短信平台"
#: settings/api/sms.py:119
msgid "test_phone is required"
msgstr "测试手机号 该字段是必填项。"
#: settings/apps.py:7
msgid "Settings"
msgstr "系统设置"
@ -3778,29 +3785,68 @@ msgstr "SP 密钥"
msgid "SP cert"
msgstr "SP 证书"
#: settings/serializers/auth/sms.py:11
#: settings/serializers/auth/sms.py:14
msgid "Enable SMS"
msgstr "启用 SMS"
#: settings/serializers/auth/sms.py:13
msgid "SMS provider"
msgstr "短信服务商"
#: settings/serializers/auth/sms.py:16
msgid "SMS provider / Protocol"
msgstr "短信服务商 / 协议"
#: settings/serializers/auth/sms.py:18 settings/serializers/auth/sms.py:36
#: settings/serializers/auth/sms.py:44 settings/serializers/email.py:65
#: settings/serializers/auth/sms.py:21 settings/serializers/auth/sms.py:39
#: settings/serializers/auth/sms.py:47 settings/serializers/auth/sms.py:58
#: settings/serializers/email.py:65
msgid "Signature"
msgstr "签名"
#: settings/serializers/auth/sms.py:19 settings/serializers/auth/sms.py:37
#: settings/serializers/auth/sms.py:45
#: settings/serializers/auth/sms.py:22 settings/serializers/auth/sms.py:40
#: settings/serializers/auth/sms.py:48
msgid "Template code"
msgstr "模板"
#: settings/serializers/auth/sms.py:23
#: settings/serializers/auth/sms.py:26
msgid "Test phone"
msgstr "测试手机号"
#: settings/serializers/auth/sso.py:11
#: settings/serializers/auth/sms.py:54
msgid "Gateway account(SP id)"
msgstr "网关账号(SP id)"
#: settings/serializers/auth/sms.py:55
msgid "Gateway password(SP secret)"
msgstr "网关密码(SP secret)"
#: settings/serializers/auth/sms.py:56
msgid "Original number(Src id)"
msgstr "原始号码(Src id)"
#: settings/serializers/auth/sms.py:57
msgid "Business type(Service id)"
msgstr "业务类型(Service id)"
#: settings/serializers/auth/sms.py:60
msgid "Template"
msgstr "模板"
#: settings/serializers/auth/sms.py:61
msgid ""
"Template need contain {code} and Signature + template length does not exceed "
"67 words. For example, your verification code is {code}, which is valid for "
"5 minutes. Please do not disclose it to others."
msgstr ""
"模板需要包含 {code},并且模板+签名长度不能超过67个字。例如, 您的验证码是 "
"{code}, 有效期为5分钟。请不要泄露给其他人。"
#: settings/serializers/auth/sms.py:70
#, python-brace-format
msgid "The template needs to contain {code}"
msgstr "模板需要包含 {code}"
#: settings/serializers/auth/sms.py:73
msgid "Signature + Template must not exceed 65 words"
msgstr "模板+签名不能超过65个字"
#: settings/serializers/auth/sso.py:12
msgid "Enable SSO auth"
msgstr "启用 SSO Token 认证"
@ -6365,11 +6411,11 @@ msgstr "云账号"
msgid "Test cloud account"
msgstr "测试云账号"
#: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers/task.py:67
#: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers/task.py:66
msgid "Account"
msgstr "账号"
#: xpack/plugins/cloud/models.py:88 xpack/plugins/cloud/serializers/task.py:38
#: xpack/plugins/cloud/models.py:88 xpack/plugins/cloud/serializers/task.py:37
msgid "Regions"
msgstr "地域"
@ -6377,19 +6423,19 @@ msgstr "地域"
msgid "Hostname strategy"
msgstr "主机名策略"
#: xpack/plugins/cloud/models.py:100 xpack/plugins/cloud/serializers/task.py:68
#: xpack/plugins/cloud/models.py:100 xpack/plugins/cloud/serializers/task.py:67
msgid "Unix admin user"
msgstr "Unix 管理员"
#: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:69
#: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:68
msgid "Windows admin user"
msgstr "Windows 管理员"
#: xpack/plugins/cloud/models.py:110 xpack/plugins/cloud/serializers/task.py:46
#: xpack/plugins/cloud/models.py:110 xpack/plugins/cloud/serializers/task.py:45
msgid "IP network segment group"
msgstr "IP网段组"
#: xpack/plugins/cloud/models.py:113 xpack/plugins/cloud/serializers/task.py:72
#: xpack/plugins/cloud/models.py:113 xpack/plugins/cloud/serializers/task.py:71
msgid "Always update"
msgstr "总是更新"
@ -6694,34 +6740,32 @@ msgstr "测试端口"
#: xpack/plugins/cloud/serializers/task.py:29
msgid ""
"Only instances matching the IP range will be synced. <br>If the instance "
"contains multiple IP addresses, the first IP address that matches will be "
"used as the IP for the created asset. <br>The default value of * means sync "
"all instances and randomly match IP addresses. <br>Format for comma-"
"delimited string, Such as: 192.168.1.0/24, 10.1.1.1-10.1.1.20"
"The IP address that is first matched to will be used as the IP of the "
"created asset. <br>The default * indicates a random match. <br>Format for "
"comma-delimited string, Such as: 192.168.1.0/24, 10.1.1.1-10.1.1.20"
msgstr ""
"只有匹配到 IP 段的实例会被同步。<br>如果实例包含多个 IP 地址,那么第一个匹配"
"到的 IP 地址将被用作创建的资产的 IP。<br>默认值 * 表示同步所有实例和随机匹配 "
"IP 地址。<br>格式为以逗号分隔的字符串,例如:192.168.1.0/24,10.1.1.1-10.1.1.20"
#: xpack/plugins/cloud/serializers/task.py:36
#: xpack/plugins/cloud/serializers/task.py:35
msgid "History count"
msgstr "执行次数"
#: xpack/plugins/cloud/serializers/task.py:37
#: xpack/plugins/cloud/serializers/task.py:36
msgid "Instance count"
msgstr "实例个数"
#: xpack/plugins/cloud/serializers/task.py:66
#: xpack/plugins/cloud/serializers/task.py:65
msgid "Linux admin user"
msgstr "Linux 管理员"
#: xpack/plugins/cloud/serializers/task.py:71
#: xpack/plugins/cloud/serializers/task.py:70
#: xpack/plugins/gathered_user/serializers.py:20
msgid "Periodic display"
msgstr "定时执行"
#: xpack/plugins/cloud/utils.py:69
#: xpack/plugins/cloud/utils.py:68
msgid "Account unavailable"
msgstr "账号无效"

View File

@ -5,6 +5,4 @@ from .dingtalk import *
from .feishu import *
from .public import *
from .email import *
from .alibaba_sms import *
from .tencent_sms import *
from .sms import *

View File

@ -1,58 +0,0 @@
from rest_framework.views import Response
from rest_framework.generics import GenericAPIView
from rest_framework.exceptions import APIException
from rest_framework import status
from django.utils.translation import gettext_lazy as _
from common.sdk.sms.alibaba import AlibabaSMS
from settings.models import Setting
from common.exceptions import JMSException
from .. import serializers
class AlibabaSMSTestingAPI(GenericAPIView):
serializer_class = serializers.AlibabaSMSSettingSerializer
rbac_perms = {
'POST': 'settings.change_sms'
}
def post(self, request):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
alibaba_access_key_id = serializer.validated_data['ALIBABA_ACCESS_KEY_ID']
alibaba_access_key_secret = serializer.validated_data.get('ALIBABA_ACCESS_KEY_SECRET')
alibaba_verify_sign_name = serializer.validated_data['ALIBABA_VERIFY_SIGN_NAME']
alibaba_verify_template_code = serializer.validated_data['ALIBABA_VERIFY_TEMPLATE_CODE']
test_phone = serializer.validated_data.get('SMS_TEST_PHONE')
if not test_phone:
raise JMSException(code='test_phone_required', detail=_('test_phone is required'))
if not alibaba_access_key_secret:
secret = Setting.objects.filter(name='ALIBABA_ACCESS_KEY_SECRET').first()
if secret:
alibaba_access_key_secret = secret.cleaned_value
alibaba_access_key_secret = alibaba_access_key_secret or ''
try:
client = AlibabaSMS(
access_key_id=alibaba_access_key_id,
access_key_secret=alibaba_access_key_secret
)
client.send_sms(
phone_numbers=[test_phone],
sign_name=alibaba_verify_sign_name,
template_code=alibaba_verify_template_code,
template_param={'code': 'test'}
)
return Response(status=status.HTTP_200_OK, data={'msg': _('Test success')})
except APIException as e:
try:
error = e.detail['errmsg']
except:
error = e.detail
return Response(status=status.HTTP_400_BAD_REQUEST, data={'error': error})

View File

@ -40,6 +40,7 @@ class SettingsApi(generics.RetrieveUpdateAPIView):
'sms': serializers.SMSSettingSerializer,
'alibaba': serializers.AlibabaSMSSettingSerializer,
'tencent': serializers.TencentSMSSettingSerializer,
'cmpp2': serializers.CMPP2SMSSettingSerializer,
}
rbac_category_permissions = {

View File

@ -1,8 +1,19 @@
from rest_framework.generics import ListAPIView
import importlib
from collections import OrderedDict
from rest_framework.generics import ListAPIView, GenericAPIView
from rest_framework.response import Response
from rest_framework.exceptions import APIException
from rest_framework import status
from django.utils.translation import gettext_lazy as _
from common.sdk.sms import BACKENDS
from common.exceptions import JMSException
from settings.serializers.sms import SMSBackendSerializer
from settings.models import Setting
from .. import serializers
class SMSBackendAPI(ListAPIView):
@ -21,3 +32,108 @@ class SMSBackendAPI(ListAPIView):
]
return Response(data)
class SMSTestingAPI(GenericAPIView):
backends_serializer = {
'alibaba': serializers.AlibabaSMSSettingSerializer,
'tencent': serializers.TencentSMSSettingSerializer,
'cmpp2': serializers.CMPP2SMSSettingSerializer
}
rbac_perms = {
'POST': 'settings.change_sms'
}
@staticmethod
def get_or_from_setting(key, value=''):
if not value:
secret = Setting.objects.filter(name=key).first()
if secret:
value = secret.cleaned_value
return value or ''
def get_alibaba_params(self, data):
init_params = {
'access_key_id': data['ALIBABA_ACCESS_KEY_ID'],
'access_key_secret': self.get_or_from_setting(
'ALIBABA_ACCESS_KEY_SECRET', data.get('ALIBABA_ACCESS_KEY_SECRET')
)
}
send_sms_params = {
'sign_name': data['ALIBABA_VERIFY_SIGN_NAME'],
'template_code': data['ALIBABA_VERIFY_TEMPLATE_CODE'],
'template_param': {'code': '666666'}
}
return init_params, send_sms_params
def get_tencent_params(self, data):
init_params = {
'secret_id': data['TENCENT_SECRET_ID'],
'secret_key': self.get_or_from_setting(
'TENCENT_SECRET_KEY', data.get('TENCENT_SECRET_KEY')
),
'sdkappid': data['TENCENT_SDKAPPID']
}
send_sms_params = {
'sign_name': data['TENCENT_VERIFY_SIGN_NAME'],
'template_code': data['TENCENT_VERIFY_TEMPLATE_CODE'],
'template_param': OrderedDict(code='666666')
}
return init_params, send_sms_params
def get_cmpp2_params(self, data):
init_params = {
'host': data['CMPP2_HOST'], 'port': data['CMPP2_PORT'],
'sp_id': data['CMPP2_SP_ID'], 'src_id': data['CMPP2_SRC_ID'],
'sp_secret': self.get_or_from_setting(
'CMPP2_SP_SECRET', data.get('CMPP2_SP_SECRET')
),
'service_id': data['CMPP2_SERVICE_ID'],
}
send_sms_params = {
'sign_name': data['CMPP2_VERIFY_SIGN_NAME'],
'template_code': data['CMPP2_VERIFY_TEMPLATE_CODE'],
'template_param': OrderedDict(code='666666')
}
return init_params, send_sms_params
def get_params_by_backend(self, backend, data):
"""
返回两部分参数
1实例化参数
2发送测试短信参数
"""
get_params_func = getattr(self, 'get_%s_params' % backend)
return get_params_func(data)
def post(self, request, backend):
serializer_class = self.backends_serializer.get(backend)
if serializer_class is None:
raise JMSException(_('Invalid SMS platform'))
serializer = serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
test_phone = serializer.validated_data.get('SMS_TEST_PHONE')
if not test_phone:
raise JMSException(code='test_phone_required', detail=_('test_phone is required'))
init_params, send_sms_params = self.get_params_by_backend(backend, serializer.validated_data)
m = importlib.import_module(f'common.sdk.sms.{backend}', __package__)
try:
client = m.client(**init_params)
client.send_sms(
phone_numbers=[test_phone],
**send_sms_params
)
status_code = status.HTTP_200_OK
data = {'msg': _('Test success')}
except APIException as e:
try:
error = e.detail['errmsg']
except:
error = e.detail
status_code = status.HTTP_400_BAD_REQUEST
data = {'error': error}
return Response(status=status_code, data=data)

View File

@ -1,63 +0,0 @@
from collections import OrderedDict
from rest_framework.views import Response
from rest_framework.generics import GenericAPIView
from rest_framework.exceptions import APIException
from rest_framework import status
from django.utils.translation import gettext_lazy as _
from common.sdk.sms.tencent import TencentSMS
from settings.models import Setting
from common.exceptions import JMSException
from .. import serializers
class TencentSMSTestingAPI(GenericAPIView):
serializer_class = serializers.TencentSMSSettingSerializer
rbac_perms = {
'POST': 'settings.change_sms'
}
def post(self, request):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
tencent_secret_id = serializer.validated_data['TENCENT_SECRET_ID']
tencent_secret_key = serializer.validated_data.get('TENCENT_SECRET_KEY')
tencent_verify_sign_name = serializer.validated_data['TENCENT_VERIFY_SIGN_NAME']
tencent_verify_template_code = serializer.validated_data['TENCENT_VERIFY_TEMPLATE_CODE']
tencent_sdkappid = serializer.validated_data.get('TENCENT_SDKAPPID')
test_phone = serializer.validated_data.get('SMS_TEST_PHONE')
if not test_phone:
raise JMSException(code='test_phone_required', detail=_('test_phone is required'))
if not tencent_secret_key:
secret = Setting.objects.filter(name='TENCENT_SECRET_KEY').first()
if secret:
tencent_secret_key = secret.cleaned_value
tencent_secret_key = tencent_secret_key or ''
try:
client = TencentSMS(
secret_id=tencent_secret_id,
secret_key=tencent_secret_key,
sdkappid=tencent_sdkappid
)
client.send_sms(
phone_numbers=[test_phone],
sign_name=tencent_verify_sign_name,
template_code=tencent_verify_template_code,
template_param=OrderedDict(code='666666')
)
return Response(status=status.HTTP_200_OK, data={'msg': _('Test success')})
except APIException as e:
try:
error = e.detail['errmsg']
except:
error = e.detail
return Response(status=status.HTTP_400_BAD_REQUEST, data={'error': error})

View File

@ -4,13 +4,16 @@ from rest_framework import serializers
from common.drf.fields import EncryptedField
from common.sdk.sms import BACKENDS
__all__ = ['SMSSettingSerializer', 'AlibabaSMSSettingSerializer', 'TencentSMSSettingSerializer']
__all__ = [
'SMSSettingSerializer', 'AlibabaSMSSettingSerializer', 'TencentSMSSettingSerializer',
'CMPP2SMSSettingSerializer'
]
class SMSSettingSerializer(serializers.Serializer):
SMS_ENABLED = serializers.BooleanField(default=False, label=_('Enable SMS'))
SMS_BACKEND = serializers.ChoiceField(
choices=BACKENDS.choices, default=BACKENDS.ALIBABA, label=_('SMS provider')
choices=BACKENDS.choices, default=BACKENDS.ALIBABA, label=_('SMS provider / Protocol')
)
@ -43,3 +46,29 @@ class TencentSMSSettingSerializer(BaseSMSSettingSerializer):
TENCENT_SDKAPPID = serializers.CharField(max_length=256, required=True, label='SDK app id')
TENCENT_VERIFY_SIGN_NAME = serializers.CharField(max_length=256, required=True, label=_('Signature'))
TENCENT_VERIFY_TEMPLATE_CODE = serializers.CharField(max_length=256, required=True, label=_('Template code'))
class CMPP2SMSSettingSerializer(BaseSMSSettingSerializer):
CMPP2_HOST = serializers.CharField(max_length=256, required=True, label=_('Host'))
CMPP2_PORT = serializers.IntegerField(default=7890, label=_('Port'))
CMPP2_SP_ID = serializers.CharField(max_length=128, required=True, label=_('Gateway account(SP id)'))
CMPP2_SP_SECRET = EncryptedField(max_length=256, required=False, label=_('Gateway password(SP secret)'))
CMPP2_SRC_ID = serializers.CharField(max_length=256, required=False, label=_('Original number(Src id)'))
CMPP2_SERVICE_ID = serializers.CharField(max_length=256, required=True, label=_('Business type(Service id)'))
CMPP2_VERIFY_SIGN_NAME = serializers.CharField(max_length=256, required=True, label=_('Signature'))
CMPP2_VERIFY_TEMPLATE_CODE = serializers.CharField(
max_length=69, required=True, label=_('Template'),
help_text=_('Template need contain {code} and Signature + template length does not exceed 67 words. '
'For example, your verification code is {code}, which is valid for 5 minutes. '
'Please do not disclose it to others.')
)
def validate(self, attrs):
sign_name = attrs.get('CMPP2_VERIFY_SIGN_NAME', '')
template_code = attrs.get('CMPP2_VERIFY_TEMPLATE_CODE', '')
if template_code.find('{code}') == -1:
raise serializers.ValidationError(_('The template needs to contain {code}'))
if len(sign_name + template_code) > 65:
# 保证验证码内容在一条短信中(长度小于70字), 签名两边的括号和空格占3个字再减去2个即可(验证码占用4个但占位符6个
raise serializers.ValidationError(_('Signature + Template must not exceed 65 words'))
return attrs

View File

@ -7,7 +7,7 @@ from .auth import (
LDAPSettingSerializer, OIDCSettingSerializer, KeycloakSettingSerializer,
CASSettingSerializer, RadiusSettingSerializer, FeiShuSettingSerializer,
WeComSettingSerializer, DingTalkSettingSerializer, AlibabaSMSSettingSerializer,
TencentSMSSettingSerializer,
TencentSMSSettingSerializer, CMPP2SMSSettingSerializer
)
from .terminal import TerminalSettingSerializer
from .security import SecuritySettingSerializer
@ -37,6 +37,7 @@ class SettingsSerializer(
CleaningSerializer,
AlibabaSMSSettingSerializer,
TencentSMSSettingSerializer,
CMPP2SMSSettingSerializer,
):
# encrypt_fields 现在使用 write_only 来判断了
pass

View File

@ -16,8 +16,7 @@ urlpatterns = [
path('wecom/testing/', api.WeComTestingAPI.as_view(), name='wecom-testing'),
path('dingtalk/testing/', api.DingTalkTestingAPI.as_view(), name='dingtalk-testing'),
path('feishu/testing/', api.FeiShuTestingAPI.as_view(), name='feishu-testing'),
path('alibaba/testing/', api.AlibabaSMSTestingAPI.as_view(), name='alibaba-sms-testing'),
path('tencent/testing/', api.TencentSMSTestingAPI.as_view(), name='tencent-sms-testing'),
path('sms/<str:backend>/testing/', api.SMSTestingAPI.as_view(), name='sms-testing'),
path('sms/backend/', api.SMSBackendAPI.as_view(), name='sms-backend'),
path('setting/', api.SettingsApi.as_view(), name='settings-setting'),