mirror of https://github.com/jumpserver/jumpserver
feat: 支持CMPPv2.0协议短信网关 (#8591)
* feat: 支持CMPPv2.0协议短信网关 * 修改翻译 Co-authored-by: Jiangjie.Bai <32935519+BaiJiangJie@users.noreply.github.com>pull/8727/head
parent
6a30e0739d
commit
708a87c903
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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')
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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 "利用できないアカウント"
|
||||
|
||||
|
|
|
@ -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 "账号无效"
|
||||
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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})
|
|
@ -40,6 +40,7 @@ class SettingsApi(generics.RetrieveUpdateAPIView):
|
|||
'sms': serializers.SMSSettingSerializer,
|
||||
'alibaba': serializers.AlibabaSMSSettingSerializer,
|
||||
'tencent': serializers.TencentSMSSettingSerializer,
|
||||
'cmpp2': serializers.CMPP2SMSSettingSerializer,
|
||||
}
|
||||
|
||||
rbac_category_permissions = {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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})
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'),
|
||||
|
|
Loading…
Reference in New Issue