OpenApiUtilClient.php 16 KB

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