mirror of
https://github.com/aaPanel/aaPanel.git
synced 2026-08-17 21:25:47 +02:00
1. Added Mail Marketing Automation trigger tasks 2. Added Mail Marketing Groups Import, Export, Merge 3. Added Mail Marketing Subscribers to support paste import 4. Added Mail Marketing Template Import Export Duplicate 5. Added Mail server add timed automatic reply 6. Added HTTPS Protection function for Website (disables automatic HTTP to HTTPS redirection when enabled) 7. Optimize Website Interface Button Integration 8. Optimize the response speed of WP Toolkit interface
339 lines
12 KiB
Python
339 lines
12 KiB
Python
import json
|
|
|
|
from sslModel.base import sslBase
|
|
|
|
import public
|
|
|
|
import copy
|
|
import sys
|
|
import hashlib
|
|
import hmac
|
|
import binascii
|
|
from datetime import datetime
|
|
import requests
|
|
|
|
|
|
class main(sslBase):
|
|
dns_provider_name = "huaweicloud"
|
|
_type = 0
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.region = "cn-south-1"
|
|
self.BasicDateFormat = "%Y%m%dT%H%M%SZ"
|
|
self.Algorithm = "SDK-HMAC-SHA256"
|
|
self.HeaderXDate = "X-Sdk-Date"
|
|
self.HeaderHost = "host"
|
|
self.HeaderAuthorization = "Authorization"
|
|
self.HeaderContentSha256 = "x-sdk-content-sha256"
|
|
self.url = "https://dns.cn-north-1.myhuaweicloud.com"
|
|
|
|
def __init_data(self, data):
|
|
self.ak = data["AccessKey"]
|
|
self.sk = data["SecretKey"]
|
|
self.project_id = data["project_id"]
|
|
|
|
def sign_to_response(self, dns_id, method="", url="", headers=None, body=""):
|
|
url = self.url + url
|
|
self.__init_data(self.get_dns_data(None)[dns_id])
|
|
if sys.version_info.major < 3:
|
|
body = body
|
|
from urllib import quote, unquote
|
|
|
|
def hmacsha256(keyByte, message):
|
|
return hmac.new(keyByte, message, digestmod=hashlib.sha256).digest()
|
|
|
|
# Create a "String to Sign".
|
|
def StringToSign(canonicalRequest, t):
|
|
bytes = HexEncodeSHA256Hash(canonicalRequest)
|
|
return "%s\n%s\n%s" % (self.Algorithm, datetime.strftime(t, self.BasicDateFormat), bytes)
|
|
|
|
else:
|
|
body = body.encode("utf-8")
|
|
from urllib.parse import quote, unquote
|
|
|
|
def hmacsha256(keyByte, message):
|
|
return hmac.new(keyByte.encode('utf-8'), message.encode('utf-8'), digestmod=hashlib.sha256).digest()
|
|
|
|
# Create a "String to Sign".
|
|
def StringToSign(canonicalRequest, t):
|
|
bytes = HexEncodeSHA256Hash(canonicalRequest.encode('utf-8'))
|
|
return "%s\n%s\n%s" % (self.Algorithm, datetime.strftime(t, self.BasicDateFormat), bytes)
|
|
|
|
def HexEncodeSHA256Hash(data):
|
|
sha256 = hashlib.sha256()
|
|
sha256.update(data)
|
|
return sha256.hexdigest()
|
|
|
|
def findHeader(headers, header):
|
|
for k in headers:
|
|
if k.lower() == header.lower():
|
|
return headers[k]
|
|
return None
|
|
|
|
def CanonicalQueryString(query):
|
|
keys = []
|
|
for key in query:
|
|
keys.append(key)
|
|
keys.sort()
|
|
a = []
|
|
for key in keys:
|
|
k = quote(key, safe='~')
|
|
value = query[key]
|
|
if type(value) is list:
|
|
value.sort()
|
|
for v in value:
|
|
kv = k + "=" + quote(str(v), safe='~')
|
|
a.append(kv)
|
|
else:
|
|
kv = k + "=" + quote(str(value), safe='~')
|
|
a.append(kv)
|
|
return '&'.join(a)
|
|
|
|
def SignStringToSign(stringToSign, signingKey):
|
|
hm = hmacsha256(signingKey, stringToSign)
|
|
return binascii.hexlify(hm).decode()
|
|
|
|
def AuthHeaderValue(signature, AppKey, signedHeaders):
|
|
return "%s Access=%s, SignedHeaders=%s, Signature=%s" % (
|
|
self.Algorithm, AppKey, ";".join(signedHeaders), signature)
|
|
|
|
spl = url.split("://", 1)
|
|
scheme = 'http'
|
|
if len(spl) > 1:
|
|
scheme = spl[0]
|
|
url = spl[1]
|
|
query = {}
|
|
spl = url.split('?', 1)
|
|
url = spl[0]
|
|
if len(spl) > 1:
|
|
for kv in spl[1].split("&"):
|
|
spl = kv.split("=", 1)
|
|
key = spl[0]
|
|
value = ""
|
|
if len(spl) > 1:
|
|
value = spl[1]
|
|
if key != '':
|
|
key = unquote(key)
|
|
value = unquote(value)
|
|
if key in query:
|
|
query[key].append(value)
|
|
else:
|
|
query[key] = [value]
|
|
spl = url.split('/', 1)
|
|
host = spl[0]
|
|
if len(spl) > 1:
|
|
url = '/' + spl[1]
|
|
else:
|
|
url = '/'
|
|
if headers is None:
|
|
headers = {"content-type": "application/json"}
|
|
else:
|
|
headers = copy.deepcopy(headers)
|
|
|
|
headerTime = findHeader(headers, self.HeaderXDate)
|
|
|
|
if headerTime is None:
|
|
t = datetime.utcnow()
|
|
headers[self.HeaderXDate] = datetime.strftime(t, self.BasicDateFormat)
|
|
else:
|
|
t = datetime.strptime(headerTime, self.BasicDateFormat)
|
|
|
|
haveHost = False
|
|
for key in headers:
|
|
if key.lower() == 'host':
|
|
haveHost = True
|
|
break
|
|
if not haveHost:
|
|
headers["host"] = host
|
|
signedHeaders = []
|
|
for key in headers:
|
|
signedHeaders.append(key.lower())
|
|
signedHeaders.sort()
|
|
a = []
|
|
__headers = {}
|
|
for key in headers:
|
|
keyEncoded = key.lower()
|
|
value = headers[key]
|
|
valueEncoded = value.strip()
|
|
__headers[keyEncoded] = valueEncoded
|
|
if sys.version_info.major == 3:
|
|
headers[key] = valueEncoded.encode("utf-8").decode('iso-8859-1')
|
|
for key in signedHeaders:
|
|
a.append(key + ":" + __headers[key])
|
|
canonicalHeaders = '\n'.join(a) + "\n"
|
|
|
|
hexencode = findHeader(headers, self.HeaderContentSha256)
|
|
if hexencode is None:
|
|
hexencode = HexEncodeSHA256Hash(body)
|
|
pattens = unquote(url).split('/')
|
|
CanonicalURI = []
|
|
for v in pattens:
|
|
CanonicalURI.append(quote(v, safe="~"))
|
|
CanonicalURL = "/".join(CanonicalURI)
|
|
if CanonicalURL[-1] != '/':
|
|
CanonicalURL = CanonicalURL + "/" # always end with /
|
|
|
|
canonicalRequest = "%s\n%s\n%s\n%s\n%s\n%s" % (method.upper(), CanonicalURL, CanonicalQueryString(query),
|
|
canonicalHeaders, ";".join(signedHeaders), hexencode)
|
|
|
|
stringToSign = StringToSign(canonicalRequest, t)
|
|
signature = SignStringToSign(stringToSign, self.sk)
|
|
authValue = AuthHeaderValue(signature, self.ak, signedHeaders)
|
|
headers[self.HeaderAuthorization] = authValue
|
|
headers["content-length"] = str(len(body))
|
|
queryString = CanonicalQueryString(query)
|
|
if queryString != "":
|
|
url = url + "?" + queryString
|
|
|
|
res = requests.request(method, scheme + "://" + host + url, headers=headers, data=body)
|
|
return res
|
|
|
|
def create_dns_record(self, get):
|
|
domain_name = get.domain_name
|
|
domain_dns_value = get.domain_dns_value
|
|
record_type = 'TXT'
|
|
if 'record_type' in get:
|
|
record_type = get.record_type
|
|
if record_type == 'TXT':
|
|
domain_dns_value = "\"{}\"".format(domain_dns_value)
|
|
|
|
root_domain, sub_domain, _ = self.extract_zone(domain_name)
|
|
if sub_domain == "@":
|
|
domain_name = root_domain
|
|
|
|
try:
|
|
zone_dic = self.get_zoneid_dict(get.dns_id)
|
|
zone_id = zone_dic.get(root_domain)
|
|
body = json.dumps({
|
|
"name": domain_name,
|
|
"type": record_type,
|
|
"records": [domain_dns_value],
|
|
})
|
|
|
|
res = self.sign_to_response(get.dns_id, "POST", "/v2/zones/{}/recordsets".format(zone_id), body=body)
|
|
if res.status_code != 202:
|
|
return public.returnMsg(False, self.get_error(res.text))
|
|
|
|
return public.returnMsg(True, '添加成功')
|
|
except Exception as e:
|
|
return public.returnMsg(False, self.get_error(str(e)))
|
|
|
|
def delete_dns_record(self, get):
|
|
domain_name = get.domain_name
|
|
RecordId = get.RecordId
|
|
root_domain, _, sub_domain = self.extract_zone(domain_name)
|
|
|
|
zone_dic = self.get_zoneid_dict(get.dns_id)
|
|
zone_id = zone_dic[root_domain]
|
|
try:
|
|
res = self.sign_to_response(get.dns_id, "DELETE", "/v2/zones/{}/recordsets/{}".format(zone_id, RecordId))
|
|
if res.status_code != 202:
|
|
return public.returnMsg(False, self.get_error(res.text))
|
|
return public.returnMsg(True, '删除成功')
|
|
except Exception as e:
|
|
return public.returnMsg(False, self.get_error(str(e)))
|
|
|
|
def get_zoneid_dict(self, dns_id):
|
|
"""
|
|
获取所有域名对应id
|
|
"""
|
|
res = self.sign_to_response(dns_id, "GET", "/v2/zones")
|
|
if res.status_code != 200:
|
|
return {}
|
|
response = res.json()
|
|
data = {i["name"][:-1]: i["id"] for i in response["zones"]}
|
|
|
|
return data
|
|
|
|
def get_dns_record(self, get):
|
|
domain_name = get.domain_name
|
|
root_domain, _, sub_domain = self.extract_zone(domain_name)
|
|
data = {}
|
|
try:
|
|
zone_dic = self.get_zoneid_dict(get.dns_id)
|
|
limit = 100
|
|
offset = 0
|
|
zone_id = zone_dic[root_domain]
|
|
res = self.sign_to_response(get.dns_id, "GET",
|
|
"/v2/zones/{}/recordsets?limit={}&offset={}".format(zone_id, limit, offset))
|
|
if res.status_code != 200:
|
|
return {}
|
|
response = res.json()
|
|
|
|
data["list"] = [
|
|
{
|
|
"RecordId": i["id"],
|
|
"name": i["name"][:-1],
|
|
"value": '\r\n'.join(i["records"]) if i["type"] != "TXT" else '\r\n'.join([j.replace('"', '') for j in i["records"]]),
|
|
"line": "默认",
|
|
"ttl": i["ttl"],
|
|
"type": i["type"],
|
|
"status": "启用"if i["status"] == "ACTIVE" else "暂停" if i["status"] == "DISABLE" else i["status"],
|
|
"mx": "",
|
|
"updated_on": i["update_at"],
|
|
"remark": i.get("description") or "",
|
|
}
|
|
for i in response["recordsets"]
|
|
]
|
|
data["info"] = {
|
|
'record_total': response['metadata']['total_count']
|
|
}
|
|
except Exception as e:
|
|
pass
|
|
self.set_record_data({root_domain: data})
|
|
return data
|
|
|
|
def update_dns_record(self, get):
|
|
domain_name = get.domain_name
|
|
RecordId = get.RecordId
|
|
record_type = get.record_type
|
|
domain_dns_value = get.domain_dns_value
|
|
root_domain, _, sub_domain = self.extract_zone(domain_name)
|
|
|
|
if sub_domain == "@":
|
|
domain_name = root_domain
|
|
if record_type == 'TXT':
|
|
domain_dns_value = "\"{}\"".format(domain_dns_value)
|
|
|
|
zone_dic = self.get_zoneid_dict(get.dns_id)
|
|
zone_id = zone_dic[root_domain]
|
|
try:
|
|
body = json.dumps({
|
|
"name": domain_name,
|
|
"type": record_type,
|
|
"records": [domain_dns_value],
|
|
})
|
|
|
|
res = self.sign_to_response(get.dns_id, "PUT", "/v2/zones/{}/recordsets/{}".format(zone_id, RecordId), body=body)
|
|
if res.status_code != 202:
|
|
return public.returnMsg(False, self.get_error(res.text))
|
|
|
|
return public.returnMsg(True, '修改成功')
|
|
except Exception as e:
|
|
return public.returnMsg(False, self.get_error(str(e)))
|
|
|
|
def get_error(self, error):
|
|
if "DNS.0317" in error:
|
|
return "此记录为系统默认域名记录值,不能删除。"
|
|
elif "DNS.0318" in error:
|
|
return "此记录为系统默认域名记录值,不能更新。"
|
|
elif "DNS.0321" in error:
|
|
return "子域名级别超过限制。"
|
|
elif "DNS.0324" in error:
|
|
return "系统默认解析记录不能操作。"
|
|
elif "DNS.0308" in error:
|
|
return "记录集类型非法。"
|
|
elif "DNS.0307" in error:
|
|
return "记录集的值非法。"
|
|
elif "DNS.0302" in error:
|
|
return "这个华为云账户下面不存在这个域名,请检查dns接口配置后重试"
|
|
elif "APIGW.0301" in error:
|
|
return "dns账号信息有误"
|
|
elif "DNS.0312" in error:
|
|
return "存在同名的主机记录"
|
|
elif "DNS.0313" in error:
|
|
return "不存在这条记录"
|
|
else:
|
|
return error
|