diff --git a/apps/common/sdk/sms/endpoint.py b/apps/common/sdk/sms/endpoint.py index 3bcaa8559..044cf28a2 100644 --- a/apps/common/sdk/sms/endpoint.py +++ b/apps/common/sdk/sms/endpoint.py @@ -15,6 +15,7 @@ logger = get_logger(__name__) class BACKENDS(TextChoices): ALIBABA = 'alibaba', _('Alibaba cloud') TENCENT = 'tencent', _('Tencent cloud') + HUAWEI = 'huawei', _('Huawei Cloud') CMPP2 = 'cmpp2', _('CMPP v2.0') diff --git a/apps/common/sdk/sms/huawei.py b/apps/common/sdk/sms/huawei.py new file mode 100644 index 000000000..154b554fd --- /dev/null +++ b/apps/common/sdk/sms/huawei.py @@ -0,0 +1,94 @@ +import base64 +import hashlib +import time +import uuid + +import requests + +from collections import OrderedDict + +from django.conf import settings +from common.exceptions import JMSException +from common.utils import get_logger + +from .base import BaseSMSClient + +logger = get_logger(__file__) + + +class HuaweiClient: + def __init__(self, app_key, app_secret, url, sign_channel_num): + self.url = url[:-1] if url.endswith('/') else url + self.app_key = app_key + self.app_secret = app_secret + self.sign_channel_num = sign_channel_num + + def build_wsse_header(self): + now = time.strftime('%Y-%m-%dT%H:%M:%SZ') + nonce = str(uuid.uuid4()).replace('-', '') + digest = hashlib.sha256((nonce + now + self.app_secret).encode()).hexdigest() + digestBase64 = base64.b64encode(digest.encode()).decode() + formatter = 'UsernameToken Username="{}",PasswordDigest="{}",Nonce="{}",Created="{}"' + return formatter.format(self.app_key, digestBase64, nonce, now) + + def send_sms(self, receiver, signature, template_id, template_param): + sms_url = '%s/%s' % (self.url, 'sms/batchSendSms/v1') + headers = { + 'Authorization': 'WSSE realm="SDP",profile="UsernameToken",type="Appkey"', + 'X-WSSE': self.build_wsse_header() + } + body = { + 'from': self.sign_channel_num, 'to': receiver, 'templateId': template_id, + 'templateParas': template_param, 'signature': signature + } + try: + response = requests.post(sms_url, headers=headers, data=body) + msg = response.json() + except Exception as error: + raise JMSException(code='response_bad', detail=error) + return msg + + +class HuaweiSMS(BaseSMSClient): + SIGN_AND_TMPL_SETTING_FIELD_PREFIX = 'HUAWEI' + + @classmethod + def new_from_settings(cls): + return cls( + app_key=settings.HUAWEI_APP_KEY, + app_secret=settings.HUAWEI_APP_SECRET, + url=settings.HUAWEI_SMS_ENDPOINT, + sign_channel_num=settings.HUAWEI_SIGN_CHANNEL_NUM + ) + + def __init__(self, app_key: str, app_secret: str, url: str, sign_channel_num: str): + self.client = HuaweiClient(app_key, app_secret, url, sign_channel_num) + + def send_sms( + self, phone_numbers: list, sign_name: str, template_code: str, + template_param: OrderedDict, **kwargs + ): + phone_numbers_str = ','.join(phone_numbers) + template_param = '["%s"]' % template_param.get('code') + req_params = { + 'receiver': phone_numbers_str, 'signature': sign_name, + 'template_id': template_code, 'template_param': template_param + } + try: + logger.info(f'Huawei sms send: ' + f'phone_numbers={phone_numbers} ' + f'sign_name={sign_name} ' + f'template_code={template_code} ' + f'template_param={template_param}') + + resp_msg = self.client.send_sms(**req_params) + + except Exception as error: + raise JMSException(code='response_bad', detail=error) + + if resp_msg.get('code' != '000000'): + raise JMSException(code='response_bad', detail=resp_msg) + return resp_msg + + +client = HuaweiSMS diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 442089979..f31f4250a 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -381,6 +381,13 @@ class Config(dict): 'TENCENT_VERIFY_SIGN_NAME': '', 'TENCENT_VERIFY_TEMPLATE_CODE': '', + 'HUAWEI_APP_KEY': '', + 'HUAWEI_APP_SECRET': '', + 'HUAWEI_SMS_ENDPOINT': '', + 'HUAWEI_SIGN_CHANNEL_NUM': '', + 'HUAWEI_VERIFY_SIGN_NAME': '', + 'HUAWEI_VERIFY_TEMPLATE_CODE': '', + 'CMPP2_HOST': '', 'CMPP2_PORT': 7890, 'CMPP2_SP_ID': '', diff --git a/apps/settings/api/settings.py b/apps/settings/api/settings.py index 0f487c280..3e22b0bf8 100644 --- a/apps/settings/api/settings.py +++ b/apps/settings/api/settings.py @@ -40,6 +40,7 @@ class SettingsApi(generics.RetrieveUpdateAPIView): 'sms': serializers.SMSSettingSerializer, 'alibaba': serializers.AlibabaSMSSettingSerializer, 'tencent': serializers.TencentSMSSettingSerializer, + 'huawei': serializers.HuaweiSMSSettingSerializer, 'cmpp2': serializers.CMPP2SMSSettingSerializer, } diff --git a/apps/settings/api/sms.py b/apps/settings/api/sms.py index 668b5ec56..301f4e203 100644 --- a/apps/settings/api/sms.py +++ b/apps/settings/api/sms.py @@ -38,6 +38,7 @@ class SMSTestingAPI(GenericAPIView): backends_serializer = { 'alibaba': serializers.AlibabaSMSSettingSerializer, 'tencent': serializers.TencentSMSSettingSerializer, + 'huawei': serializers.HuaweiSMSSettingSerializer, 'cmpp2': serializers.CMPP2SMSSettingSerializer } rbac_perms = { @@ -82,6 +83,22 @@ class SMSTestingAPI(GenericAPIView): } return init_params, send_sms_params + def get_huawei_params(self, data): + init_params = { + 'app_key': data['HUAWEI_APP_KEY'], + 'app_secret': self.get_or_from_setting( + 'HUAWEI_APP_SECRET', data.get('HUAWEI_APP_SECRET') + ), + 'url': data['HUAWEI_SMS_ENDPOINT'], + 'sign_channel_num': data['HUAWEI_SIGN_CHANNEL_NUM'], + } + send_sms_params = { + 'sign_name': data['HUAWEI_VERIFY_SIGN_NAME'], + 'template_code': data['HUAWEI_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'], diff --git a/apps/settings/serializers/auth/sms.py b/apps/settings/serializers/auth/sms.py index 1278dea06..dda38b586 100644 --- a/apps/settings/serializers/auth/sms.py +++ b/apps/settings/serializers/auth/sms.py @@ -7,7 +7,7 @@ from common.sdk.sms import BACKENDS __all__ = [ 'SMSSettingSerializer', 'AlibabaSMSSettingSerializer', 'TencentSMSSettingSerializer', - 'CMPP2SMSSettingSerializer' + 'HuaweiSMSSettingSerializer', 'CMPP2SMSSettingSerializer' ] @@ -52,6 +52,15 @@ class TencentSMSSettingSerializer(BaseSMSSettingSerializer): TENCENT_VERIFY_TEMPLATE_CODE = serializers.CharField(max_length=256, required=True, label=_('Template code')) +class HuaweiSMSSettingSerializer(BaseSMSSettingSerializer): + HUAWEI_APP_KEY = serializers.CharField(max_length=256, required=True, label='App key') + HUAWEI_APP_SECRET = EncryptedField(max_length=256, required=False, label='App secret') + HUAWEI_SMS_ENDPOINT = serializers.CharField(max_length=1024, required=True, label=_('App Access Address')) + HUAWEI_SIGN_CHANNEL_NUM = serializers.CharField(max_length=1024, required=True, label=_('Signature channel number')) + HUAWEI_VERIFY_SIGN_NAME = serializers.CharField(max_length=256, required=True, label=_('Signature')) + HUAWEI_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'))