OpenApiUtilClient.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. <?php
  2. namespace AlibabaCloud\OpenApiUtil;
  3. use AlibabaCloud\Tea\Model;
  4. use AlibabaCloud\Tea\Request;
  5. use AlibabaCloud\Tea\Utils\Utils;
  6. use OneSm\Sm3;
  7. use Psr\Http\Message\StreamInterface;
  8. /**
  9. * This is for OpenApi Util.
  10. */
  11. class OpenApiUtilClient
  12. {
  13. /**
  14. * Convert all params of body other than type of readable into content.
  15. *
  16. * @param Model $body source Model
  17. * @param Model $content target Model
  18. */
  19. public static function convert($body, $content)
  20. {
  21. $class = new \ReflectionClass($body);
  22. foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
  23. $name = $property->getName();
  24. if (!$property->isStatic()) {
  25. $value = $property->getValue($body);
  26. if ($value instanceof StreamInterface) {
  27. continue;
  28. }
  29. $content->{$name} = $value;
  30. }
  31. }
  32. }
  33. /**
  34. * Get the string to be signed according to request.
  35. *
  36. * @param Request $request which contains signed messages
  37. *
  38. * @return string the signed string
  39. */
  40. public static function getStringToSign($request)
  41. {
  42. $pathname = $request->pathname ?: '';
  43. $query = $request->query ?: [];
  44. $accept = isset($request->headers['accept']) ? $request->headers['accept'] : '';
  45. $contentMD5 = isset($request->headers['content-md5']) ? $request->headers['content-md5'] : '';
  46. $contentType = isset($request->headers['content-type']) ? $request->headers['content-type'] : '';
  47. $date = isset($request->headers['date']) ? $request->headers['date'] : '';
  48. $result = $request->method . "\n" .
  49. $accept . "\n" .
  50. $contentMD5 . "\n" .
  51. $contentType . "\n" .
  52. $date . "\n";
  53. $canonicalizedHeaders = self::getCanonicalizedHeaders($request->headers);
  54. $canonicalizedResource = self::getCanonicalizedResource($pathname, $query);
  55. return $result . $canonicalizedHeaders . $canonicalizedResource;
  56. }
  57. /**
  58. * Get signature according to stringToSign, secret.
  59. *
  60. * @param string $stringToSign the signed string
  61. * @param string $secret accesskey secret
  62. *
  63. * @return string the signature
  64. */
  65. public static function getROASignature($stringToSign, $secret)
  66. {
  67. return base64_encode(hash_hmac('sha1', $stringToSign, $secret, true));
  68. }
  69. /**
  70. * Parse filter into a form string.
  71. *
  72. * @param array $filter object
  73. *
  74. * @return string the string
  75. */
  76. public static function toForm($filter)
  77. {
  78. $query = $filter;
  79. if (null === $query) {
  80. return '';
  81. }
  82. if ($query instanceof Model) {
  83. $query = $query->toMap();
  84. }
  85. $tmp = [];
  86. foreach ($query as $k => $v) {
  87. if (0 !== strpos($k, '_')) {
  88. $tmp[$k] = $v;
  89. }
  90. }
  91. $res = self::flatten($tmp);
  92. ksort($res);
  93. return http_build_query($res);
  94. }
  95. /**
  96. * Get timestamp.
  97. *
  98. * @return string the timestamp string
  99. */
  100. public static function getTimestamp()
  101. {
  102. return gmdate('Y-m-d\\TH:i:s\\Z');
  103. }
  104. /**
  105. * Parse filter into a object which's type is map[string]string.
  106. *
  107. * @param array $filter query param
  108. *
  109. * @return array the object
  110. */
  111. public static function query($filter)
  112. {
  113. if (null === $filter) {
  114. return [];
  115. }
  116. $dict = $filter;
  117. if ($dict instanceof Model) {
  118. $dict = $dict->toMap();
  119. }
  120. $tmp = [];
  121. foreach ($dict as $k => $v) {
  122. if (0 !== strpos($k, '_')) {
  123. $tmp[$k] = $v;
  124. }
  125. }
  126. return self::flatten($tmp);
  127. }
  128. /**
  129. * Get signature according to signedParams, method and secret.
  130. *
  131. * @param array $signedParams params which need to be signed
  132. * @param string $method http method e.g. GET
  133. * @param string $secret AccessKeySecret
  134. *
  135. * @return string the signature
  136. */
  137. public static function getRPCSignature($signedParams, $method, $secret)
  138. {
  139. $secret .= '&';
  140. $strToSign = self::getRpcStrToSign($method, $signedParams);
  141. $signMethod = 'HMAC-SHA1';
  142. return self::encode($signMethod, $strToSign, $secret);
  143. }
  144. /**
  145. * Parse object into a string with specified style.
  146. *
  147. * @style specified style e.g. repeatList
  148. *
  149. * @param mixed $object the object
  150. * @param string $prefix the prefix string
  151. * @param string $style
  152. *
  153. * @return string the string
  154. */
  155. public static function arrayToStringWithSpecifiedStyle($object, $prefix, $style)
  156. {
  157. if (null === $object) {
  158. return '';
  159. }
  160. if ('repeatList' === $style) {
  161. return self::toForm([$prefix => $object]);
  162. }
  163. if ('simple' == $style || 'spaceDelimited' == $style || 'pipeDelimited' == $style) {
  164. $strs = self::flatten($object);
  165. switch ($style) {
  166. case 'spaceDelimited':
  167. return implode(' ', $strs);
  168. case 'pipeDelimited':
  169. return implode('|', $strs);
  170. default:
  171. return implode(',', $strs);
  172. }
  173. } elseif ('json' === $style) {
  174. self::parse($object, $parsed);
  175. return json_encode($parsed);
  176. }
  177. return '';
  178. }
  179. /**
  180. * Transform input as array.
  181. *
  182. * @param mixed $input
  183. *
  184. * @return array
  185. */
  186. public static function parseToArray($input)
  187. {
  188. self::parse($input, $result);
  189. return $result;
  190. }
  191. /**
  192. * Transform input as map.
  193. *
  194. * @param mixed $input
  195. *
  196. * @return array
  197. */
  198. public static function parseToMap($input)
  199. {
  200. self::parse($input, $result);
  201. return $result;
  202. }
  203. public static function getEndpoint($endpoint, $useAccelerate, $endpointType = 'public')
  204. {
  205. if ('internal' == $endpointType) {
  206. $tmp = explode('.', $endpoint);
  207. $tmp[0] .= '-internal';
  208. $endpoint = implode('.', $tmp);
  209. }
  210. if ($useAccelerate && 'accelerate' == $endpointType) {
  211. return 'oss-accelerate.aliyuncs.com';
  212. }
  213. return $endpoint;
  214. }
  215. /**
  216. * Encode raw with base16.
  217. *
  218. * @param int[] $raw encoding data
  219. *
  220. * @return string encoded string
  221. */
  222. public static function hexEncode($raw)
  223. {
  224. if (is_array($raw)) {
  225. $raw = Utils::toString($raw);
  226. }
  227. return bin2hex($raw);
  228. }
  229. /**
  230. * Hash the raw data with signatureAlgorithm.
  231. *
  232. * @param int[] $raw hashing data
  233. * @param string $signatureAlgorithm the autograph method
  234. *
  235. * @return array hashed bytes
  236. */
  237. public static function hash($raw, $signatureAlgorithm)
  238. {
  239. $str = Utils::toString($raw);
  240. switch ($signatureAlgorithm) {
  241. case 'ACS3-HMAC-SHA256':
  242. case 'ACS3-RSA-SHA256':
  243. $res = hash('sha256', $str, true);
  244. return Utils::toBytes($res);
  245. case 'ACS3-HMAC-SM3':
  246. $res = self::sm3($str);
  247. return Utils::toBytes(hex2bin($res));
  248. }
  249. return [];
  250. }
  251. /**
  252. * Get the authorization.
  253. *
  254. * @param Request $request request params
  255. * @param string $signatureAlgorithm the autograph method
  256. * @param string $payload the hashed request
  257. * @param string $accesskey the accessKey string
  258. * @param string $accessKeySecret the accessKeySecret string
  259. *
  260. * @return string authorization string
  261. * @throws \ErrorException
  262. *
  263. */
  264. public static function getAuthorization($request, $signatureAlgorithm, $payload, $accesskey, $accessKeySecret)
  265. {
  266. $canonicalURI = $request->pathname ? $request->pathname : '/';
  267. $query = $request->query ?: [];
  268. $method = strtoupper($request->method);
  269. $canonicalQueryString = self::getCanonicalQueryString($query);
  270. $signHeaders = [];
  271. foreach ($request->headers as $k => $v) {
  272. $k = strtolower($k);
  273. if (0 === strpos($k, 'x-acs-') || 'host' === $k || 'content-type' === $k) {
  274. $signHeaders[$k] = $v;
  275. }
  276. }
  277. ksort($signHeaders);
  278. $headers = [];
  279. foreach ($request->headers as $k => $v) {
  280. $k = strtolower($k);
  281. if (0 === strpos($k, 'x-acs-') || 'host' === $k || 'content-type' === $k) {
  282. $headers[$k] = trim($v);
  283. }
  284. }
  285. $canonicalHeaderString = '';
  286. ksort($headers);
  287. foreach ($headers as $k => $v) {
  288. $canonicalHeaderString .= $k . ':' . trim(self::filter($v)) . "\n";
  289. }
  290. if (empty($canonicalHeaderString)) {
  291. $canonicalHeaderString = "\n";
  292. }
  293. $canonicalRequest = $method . "\n" . $canonicalURI . "\n" . $canonicalQueryString . "\n" .
  294. $canonicalHeaderString . "\n" . implode(';', array_keys($signHeaders)) . "\n" . $payload;
  295. $strtosign = $signatureAlgorithm . "\n" . self::hexEncode(self::hash(Utils::toBytes($canonicalRequest), $signatureAlgorithm));
  296. $signature = self::sign($accessKeySecret, $strtosign, $signatureAlgorithm);
  297. $signature = self::hexEncode($signature);
  298. return $signatureAlgorithm .
  299. ' Credential=' . $accesskey .
  300. ',SignedHeaders=' . implode(';', array_keys($signHeaders)) .
  301. ',Signature=' . $signature;
  302. }
  303. public static function sign($secret, $str, $algorithm)
  304. {
  305. $result = '';
  306. switch ($algorithm) {
  307. case 'ACS3-HMAC-SHA256':
  308. $result = hash_hmac('sha256', $str, $secret, true);
  309. break;
  310. case 'ACS3-HMAC-SM3':
  311. $result = self::hmac_sm3($str, $secret, true);
  312. break;
  313. case 'ACS3-RSA-SHA256':
  314. $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" . $secret . "\n-----END RSA PRIVATE KEY-----";
  315. @openssl_sign($str, $result, $privateKey, OPENSSL_ALGO_SHA256);
  316. }
  317. return Utils::toBytes($result);
  318. }
  319. /**
  320. * Get encoded path.
  321. *
  322. * @param string $path the raw path
  323. *
  324. * @return string encoded path
  325. */
  326. public static function getEncodePath($path)
  327. {
  328. $tmp = explode('/', $path);
  329. foreach ($tmp as &$t) {
  330. $t = rawurlencode($t);
  331. }
  332. return implode('/', $tmp);
  333. }
  334. /**
  335. * Get encoded param.
  336. *
  337. * @param string $param the raw param
  338. *
  339. * @return string encoded param
  340. */
  341. public static function getEncodeParam($param)
  342. {
  343. return rawurlencode($param);
  344. }
  345. private static function getRpcStrToSign($method, $query)
  346. {
  347. ksort($query);
  348. $params = [];
  349. foreach ($query as $k => $v) {
  350. if (null !== $v) {
  351. $k = rawurlencode($k);
  352. $v = rawurlencode($v);
  353. $params[] = $k . '=' . (string)$v;
  354. }
  355. }
  356. $str = implode('&', $params);
  357. return $method . '&' . rawurlencode('/') . '&' . rawurlencode($str);
  358. }
  359. private static function encode($signMethod, $strToSign, $secret)
  360. {
  361. switch ($signMethod) {
  362. case 'HMAC-SHA256':
  363. return base64_encode(hash_hmac('sha256', $strToSign, $secret, true));
  364. default:
  365. return base64_encode(hash_hmac('sha1', $strToSign, $secret, true));
  366. }
  367. }
  368. /**
  369. * @param array $items
  370. * @param string $delimiter
  371. * @param string $prepend
  372. *
  373. * @return array
  374. */
  375. private static function flatten($items = [], $delimiter = '.', $prepend = '')
  376. {
  377. $flatten = [];
  378. foreach ($items as $key => $value) {
  379. $pos = \is_int($key) ? $key + 1 : $key;
  380. if ($value instanceof Model) {
  381. $value = $value->toMap();
  382. } elseif (\is_object($value)) {
  383. $value = get_object_vars($value);
  384. }
  385. if (\is_array($value) && !empty($value)) {
  386. $flatten = array_merge(
  387. $flatten,
  388. self::flatten($value, $delimiter, $prepend . $pos . $delimiter)
  389. );
  390. } else {
  391. if (\is_bool($value)) {
  392. $value = true === $value ? 'true' : 'false';
  393. }
  394. $flatten[$prepend . $pos] = $value;
  395. }
  396. }
  397. return $flatten;
  398. }
  399. private static function getCanonicalizedHeaders($headers, $prefix = 'x-acs-')
  400. {
  401. ksort($headers);
  402. $str = '';
  403. foreach ($headers as $k => $v) {
  404. if (0 === strpos(strtolower($k), $prefix)) {
  405. $str .= $k . ':' . trim(self::filter($v)) . "\n";
  406. }
  407. }
  408. return $str;
  409. }
  410. private static function getCanonicalizedResource($pathname, $query)
  411. {
  412. if (0 === \count($query)) {
  413. return $pathname;
  414. }
  415. ksort($query);
  416. $tmp = [];
  417. foreach ($query as $k => $v) {
  418. if (!empty($v)) {
  419. $tmp[] = $k . '=' . $v;
  420. } else {
  421. $tmp[] = $k;
  422. }
  423. }
  424. return $pathname . '?' . implode('&', $tmp);
  425. }
  426. private static function parse($input, &$output)
  427. {
  428. if (null === $input || '' === $input) {
  429. $output = [];
  430. }
  431. $recursive = function ($input) use (&$recursive) {
  432. if ($input instanceof Model) {
  433. $input = $input->toMap();
  434. } elseif (\is_object($input)) {
  435. $input = get_object_vars($input);
  436. }
  437. if (!\is_array($input)) {
  438. return $input;
  439. }
  440. $data = [];
  441. foreach ($input as $k => $v) {
  442. $data[$k] = $recursive($v);
  443. }
  444. return $data;
  445. };
  446. $output = $recursive($input);
  447. if (!\is_array($output)) {
  448. $output = [$output];
  449. }
  450. }
  451. private static function filter($str)
  452. {
  453. return str_replace(["\t", "\n", "\r", "\f"], '', $str);
  454. }
  455. private static function hmac_sm3($data, $key, $raw_output = false)
  456. {
  457. $pack = 'H' . \strlen(self::sm3('test'));
  458. $blocksize = 64;
  459. if (\strlen($key) > $blocksize) {
  460. $key = pack($pack, self::sm3($key));
  461. }
  462. $key = str_pad($key, $blocksize, \chr(0x00));
  463. $ipad = $key ^ str_repeat(\chr(0x36), $blocksize);
  464. $opad = $key ^ str_repeat(\chr(0x5C), $blocksize);
  465. $hmac = self::sm3($opad . pack($pack, self::sm3($ipad . $data)));
  466. return $raw_output ? pack($pack, $hmac) : $hmac;
  467. }
  468. private static function sm3($message)
  469. {
  470. return (new Sm3())->sign($message);
  471. }
  472. private static function getCanonicalQueryString($query)
  473. {
  474. ksort($query);
  475. $params = [];
  476. foreach ($query as $k => $v) {
  477. if (null === $v) {
  478. continue;
  479. }
  480. $str = rawurlencode($k);
  481. if ('' !== $v && null !== $v) {
  482. $str .= '=' . rawurlencode($v);
  483. } else {
  484. $str .= '=';
  485. }
  486. $params[] = $str;
  487. }
  488. return implode('&', $params);
  489. }
  490. }