client.py 15 KB

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