mirror of https://github.com/jumpserver/jumpserver
				
				
				
			feat: 支持MFA可配置华为云平台短信对接
							parent
							
								
									e6d30fa77d
								
							
						
					
					
						commit
						409d254a2e
					
				| 
						 | 
				
			
			@ -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')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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': '',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,6 +40,7 @@ class SettingsApi(generics.RetrieveUpdateAPIView):
 | 
			
		|||
        'sms': serializers.SMSSettingSerializer,
 | 
			
		||||
        'alibaba': serializers.AlibabaSMSSettingSerializer,
 | 
			
		||||
        'tencent': serializers.TencentSMSSettingSerializer,
 | 
			
		||||
        'huawei': serializers.HuaweiSMSSettingSerializer,
 | 
			
		||||
        'cmpp2': serializers.CMPP2SMSSettingSerializer,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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'],
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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'))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue