client.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. # -*- coding: utf-8 -*-
  2. import binascii
  3. import datetime
  4. import hashlib
  5. import hmac
  6. import base64
  7. import copy
  8. from cryptography.hazmat.backends import default_backend
  9. from cryptography.hazmat.primitives import hashes
  10. from cryptography.hazmat.primitives.asymmetric import padding
  11. from cryptography.hazmat.primitives.serialization import load_pem_private_key
  12. try:
  13. from urllib import quote_plus, quote
  14. except ImportError:
  15. from urllib.parse import quote_plus, quote
  16. from .sm3 import hash_sm3, Sm3
  17. from alibabacloud_tea_util.client import Client as Util
  18. from Tea.stream import STREAM_CLASS
  19. from Tea.model import TeaModel
  20. from Tea.converter import TeaConverter
  21. def prepare_headers(headers):
  22. canon_keys = []
  23. tmp_headers = {}
  24. for k, v in headers.items():
  25. if v is not None:
  26. if k.lower() not in canon_keys:
  27. canon_keys.append(k.lower())
  28. tmp_headers[k.lower()] = [TeaConverter.to_string(v).strip()]
  29. else:
  30. tmp_headers[k.lower()].append(TeaConverter.to_string(v).strip())
  31. canon_keys.sort()
  32. return {key: ','.join(sorted(tmp_headers[key])) for key in canon_keys}
  33. def rsa_sign(plaintext, secret):
  34. if not secret.startswith('-----BEGIN RSA PRIVATE KEY-----'):
  35. secret = '-----BEGIN RSA PRIVATE KEY-----\n%s' % secret
  36. if not secret.endswith('-----END RSA PRIVATE KEY-----'):
  37. secret = '%s\n-----END RSA PRIVATE KEY-----' % secret
  38. key = load_pem_private_key(secret, password=None, backend=default_backend())
  39. return key.sign(plaintext, padding.PKCS1v15(), hashes.SHA256())
  40. def signature_method(secret, source, sign_type):
  41. source = source.encode('utf-8')
  42. secret = secret.encode('utf-8')
  43. if sign_type == 'ACS3-HMAC-SHA256':
  44. return hmac.new(secret, source, hashlib.sha256).digest()
  45. elif sign_type == 'ACS3-HMAC-SM3':
  46. return hmac.new(secret, source, Sm3).digest()
  47. elif sign_type == 'ACS3-RSA-SHA256':
  48. return rsa_sign(source, secret)
  49. def get_canonical_query_string(query):
  50. if query is None or len(query) <= 0:
  51. return ''
  52. canon_keys = []
  53. for k, v in query.items():
  54. if v is not None:
  55. canon_keys.append(k)
  56. canon_keys.sort()
  57. query_string = ''
  58. for key in canon_keys:
  59. value = quote(TeaConverter.to_str(query[key]), safe='~')
  60. if value is None:
  61. s = '%s&' % key
  62. else:
  63. s = '%s=%s&' % (key, value)
  64. query_string += s
  65. return query_string[:-1]
  66. def get_canonicalized_headers(headers):
  67. canon_keys = []
  68. tmp_headers = {}
  69. for k, v in headers.items():
  70. if v is not None:
  71. if k.lower() not in canon_keys:
  72. canon_keys.append(k.lower())
  73. tmp_headers[k.lower()] = [TeaConverter.to_string(v).strip()]
  74. else:
  75. tmp_headers[k.lower()].append(TeaConverter.to_string(v).strip())
  76. canon_keys.sort()
  77. canonical_headers = ''
  78. for key in canon_keys:
  79. header_entry = ','.join(sorted(tmp_headers[key]))
  80. s = '%s:%s\n' % (key, header_entry)
  81. canonical_headers += s
  82. return canonical_headers, ';'.join(canon_keys)
  83. class Client(object):
  84. """
  85. This is for OpenApi Util
  86. """
  87. @staticmethod
  88. def convert(body, content):
  89. """
  90. Convert all params of body other than type of readable into content
  91. @param body: source Model
  92. @param content: target Model
  93. @return: void
  94. """
  95. pros = {}
  96. body_map = body.to_map()
  97. for k, v in body_map.items():
  98. if not isinstance(v, STREAM_CLASS):
  99. pros[k] = copy.deepcopy(v)
  100. content.from_map(pros)
  101. @staticmethod
  102. def _get_canonicalized_headers(headers):
  103. canon_keys = []
  104. for k in headers:
  105. if k.startswith('x-acs-'):
  106. canon_keys.append(k)
  107. canon_keys = sorted(canon_keys)
  108. canon_header = ''
  109. for k in canon_keys:
  110. canon_header += '%s:%s\n' % (k, headers[k])
  111. return canon_header
  112. @staticmethod
  113. def _get_canonicalized_resource(pathname, query):
  114. if len(query) <= 0:
  115. return pathname
  116. resource = '%s?' % pathname
  117. query_list = sorted(list(query))
  118. for key in query_list:
  119. if query[key] is not None:
  120. if query[key] == '':
  121. s = '%s&' % key
  122. else:
  123. s = '%s=%s&' % (key, TeaConverter.to_string(query[key]))
  124. resource += s
  125. return resource[:-1]
  126. @staticmethod
  127. def get_string_to_sign(request):
  128. """
  129. Get the string to be signed according to request
  130. @param request: which contains signed messages
  131. @return: the signed string
  132. """
  133. method, pathname, headers, query = request.method, request.pathname, request.headers, request.query
  134. accept = '' if headers.get('accept') is None else headers.get('accept')
  135. content_md5 = '' if headers.get('content-md5') is None else headers.get('content-md5')
  136. content_type = '' if headers.get('content-type') is None else headers.get('content-type')
  137. date = '' if headers.get('date') is None else headers.get('date')
  138. header = '%s\n%s\n%s\n%s\n%s\n' % (method, accept, content_md5, content_type, date)
  139. canon_headers = Client._get_canonicalized_headers(headers)
  140. canon_resource = Client._get_canonicalized_resource(pathname, query)
  141. sign_str = header + canon_headers + canon_resource
  142. return sign_str
  143. @staticmethod
  144. def get_roasignature(string_to_sign, secret):
  145. """
  146. Get signature according to stringToSign, secret
  147. @type string_to_sign: str
  148. @param string_to_sign: the signed string
  149. @type secret: str
  150. @param secret: accesskey secret
  151. @return: the signature
  152. """
  153. hash_val = hmac.new(secret.encode('utf-8'), string_to_sign.encode('utf-8'), hashlib.sha1).digest()
  154. signature = base64.b64encode(hash_val).decode('utf-8')
  155. return signature
  156. @staticmethod
  157. def _object_handler(key, value, out):
  158. if value is None:
  159. return
  160. if isinstance(value, dict):
  161. for k, v in value.items():
  162. Client._object_handler('%s.%s' % (key, k), v, out)
  163. elif isinstance(value, TeaModel):
  164. for k, v in value.to_map().items():
  165. Client._object_handler('%s.%s' % (key, k), v, out)
  166. elif isinstance(value, (list, tuple)):
  167. for index, val in enumerate(value):
  168. Client._object_handler('%s.%s' % (key, index + 1), val, out)
  169. else:
  170. if key.startswith('.'):
  171. key = key[1:]
  172. if not isinstance(value, STREAM_CLASS):
  173. out[key] = TeaConverter.to_string(value)
  174. @staticmethod
  175. def to_form(filter):
  176. """
  177. Parse filter into a form string
  178. @type filter: dict
  179. @param filter: object
  180. @return: the string
  181. """
  182. result = {}
  183. if filter:
  184. Client._object_handler('', filter, result)
  185. return Util.to_form_string(
  186. Util.anyify_map_value(result)
  187. )
  188. @staticmethod
  189. def get_timestamp():
  190. """
  191. Get timestamp
  192. @return: the timestamp string
  193. """
  194. return datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
  195. @staticmethod
  196. def query(filter):
  197. """
  198. Parse filter into a object which's type is map[string]string
  199. @type filter: dict
  200. @param filter: query param
  201. @return: the object
  202. """
  203. out_dict = {}
  204. if filter:
  205. Client._object_handler('', filter, out_dict)
  206. return out_dict
  207. @staticmethod
  208. def get_rpcsignature(signed_params, method, secret):
  209. """
  210. Get signature according to signedParams, method and secret
  211. @type signed_params: dict
  212. @param signed_params: params which need to be signed
  213. @type method: str
  214. @param method: http method e.g. GET
  215. @type secret: str
  216. @param secret: AccessKeySecret
  217. @return: the signature
  218. """
  219. queries = signed_params.copy()
  220. keys = list(queries.keys())
  221. keys.sort()
  222. canonicalized_query_string = ""
  223. for k in keys:
  224. if queries[k] is not None:
  225. canonicalized_query_string += "&"
  226. canonicalized_query_string += quote(TeaConverter.to_str(k), safe='')
  227. canonicalized_query_string += "="
  228. canonicalized_query_string += quote(TeaConverter.to_str(queries[k]), safe='')
  229. string_to_sign = ""
  230. string_to_sign += method
  231. string_to_sign += '&'
  232. string_to_sign += quote_plus("/")
  233. string_to_sign += '&'
  234. string_to_sign += quote_plus(
  235. canonicalized_query_string[1:])
  236. digest_maker = hmac.new(TeaConverter.to_bytes(secret + '&'),
  237. TeaConverter.to_bytes(string_to_sign),
  238. digestmod=hashlib.sha1)
  239. hash_bytes = digest_maker.digest()
  240. signed_str = TeaConverter.to_string(base64.b64encode(hash_bytes))
  241. return signed_str
  242. @staticmethod
  243. def array_to_string_with_specified_style(array, prefix, style):
  244. """
  245. Parse array into a string with specified style
  246. @type array: any
  247. @param array: the array
  248. @type prefix: str
  249. @param prefix: the prefix string
  250. @param style: specified style e.g. repeatList
  251. @return: the string
  252. """
  253. if array is None:
  254. return ''
  255. if style == 'repeatList':
  256. return Client._flat_repeat_list({prefix: array})
  257. elif style == 'simple':
  258. return ','.join(map(str, array))
  259. elif style == 'spaceDelimited':
  260. return ' '.join(map(str, array))
  261. elif style == 'pipeDelimited':
  262. return '|'.join(map(str, array))
  263. elif style == 'json':
  264. return Util.to_jsonstring(array)
  265. else:
  266. return ''
  267. @staticmethod
  268. def _flat_repeat_list(dic):
  269. query = {}
  270. if dic:
  271. Client._object_handler('', dic, query)
  272. l = []
  273. q = sorted(query)
  274. for i in q:
  275. k = quote_plus(TeaConverter.to_str(i))
  276. v = quote_plus(TeaConverter.to_str(query[i]))
  277. l.append(k + '=' + v)
  278. return '&&'.join(l)
  279. @staticmethod
  280. def parse_to_map(inp):
  281. """
  282. Transform input as map.
  283. """
  284. try:
  285. result = Client._parse_to_dict(inp)
  286. return copy.deepcopy(result)
  287. except TypeError:
  288. return
  289. @staticmethod
  290. def _parse_to_dict(val):
  291. if isinstance(val, dict):
  292. result = {}
  293. for k, v in val.items():
  294. if isinstance(v, (list, dict, TeaModel)):
  295. result[k] = Client._parse_to_dict(v)
  296. else:
  297. result[k] = v
  298. return result
  299. elif isinstance(val, list):
  300. result = []
  301. for i in val:
  302. if isinstance(i, (list, dict, TeaModel)):
  303. result.append(Client._parse_to_dict(i))
  304. else:
  305. result.append(i)
  306. return result
  307. elif isinstance(val, TeaModel):
  308. return val.to_map()
  309. @staticmethod
  310. def get_endpoint(endpoint, server_use, endpoint_type):
  311. """
  312. If endpointType is internal, use internal endpoint
  313. If serverUse is true and endpointType is accelerate, use accelerate endpoint
  314. Default return endpoint
  315. @param server_use whether use accelerate endpoint
  316. @param endpoint_type value must be internal or accelerate
  317. @return the final endpoint
  318. """
  319. if endpoint_type == "internal":
  320. str_split = endpoint.split('.')
  321. str_split[0] += "-internal"
  322. endpoint = ".".join(str_split)
  323. if server_use and endpoint_type == "accelerate":
  324. return "oss-accelerate.aliyuncs.com"
  325. return endpoint
  326. @staticmethod
  327. def hash(raw, sign_type):
  328. if sign_type == 'ACS3-HMAC-SHA256' or sign_type == 'ACS3-RSA-SHA256':
  329. return hashlib.sha256(raw).digest()
  330. elif sign_type == 'ACS3-HMAC-SM3':
  331. return hash_sm3(raw)
  332. @staticmethod
  333. def hex_encode(raw):
  334. if raw:
  335. return binascii.b2a_hex(raw).decode('utf-8')
  336. @staticmethod
  337. def get_authorization(request, sign_type, payload, ak, secret):
  338. canonical_uri = request.pathname if request.pathname else '/'
  339. canonicalized_query = get_canonical_query_string(request.query)
  340. canonicalized_headers, signed_headers = get_canonicalized_headers(request.headers)
  341. request.headers = prepare_headers(request.headers)
  342. canonical_request = '%s\n%s\n%s\n%s\n%s\n%s' % (
  343. request.method,
  344. canonical_uri,
  345. canonicalized_query,
  346. canonicalized_headers,
  347. signed_headers, payload
  348. )
  349. str_to_sign = '%s\n%s' % (
  350. sign_type,
  351. Client.hex_encode(Client.hash(canonical_request.encode("utf-8"), sign_type))
  352. )
  353. signature = Client.hex_encode(signature_method(secret, str_to_sign, sign_type))
  354. auth = '%s Credential=%s,SignedHeaders=%s,Signature=%s' % (
  355. sign_type, ak, signed_headers, signature
  356. )
  357. return auth
  358. @staticmethod
  359. def get_encode_path(path):
  360. return quote(TeaConverter.to_str(path), safe='/~')
  361. @staticmethod
  362. def get_encode_param(param):
  363. return quote(TeaConverter.to_str(param), safe='~')