From 076e6de174106bdd4fae58c97141a10c90f613a3 Mon Sep 17 00:00:00 2001 From: deepshekhardas Date: Mon, 16 Mar 2026 17:56:56 +0530 Subject: [PATCH 1/2] feat: add Alibaba Cloud SMS adapter (#6307) --- .../Messaging/Adapter/SMS/AlibabaCloud.php | 114 ++++++++++++++++++ .../Adapter/SMS/AlibabaCloudTest.php | 32 +++++ 2 files changed, 146 insertions(+) create mode 100644 src/Utopia/Messaging/Adapter/SMS/AlibabaCloud.php create mode 100644 tests/Messaging/Adapter/SMS/AlibabaCloudTest.php diff --git a/src/Utopia/Messaging/Adapter/SMS/AlibabaCloud.php b/src/Utopia/Messaging/Adapter/SMS/AlibabaCloud.php new file mode 100644 index 00000000..f3a59378 --- /dev/null +++ b/src/Utopia/Messaging/Adapter/SMS/AlibabaCloud.php @@ -0,0 +1,114 @@ +getType()); + + foreach ($message->getTo() as $to) { + $params = [ + 'AccessKeyId' => $this->accessKeyId, + 'Action' => 'SendSms', + 'Format' => 'JSON', + 'PhoneNumbers' => $to, + 'RegionId' => 'cn-hangzhou', + 'SignName' => $this->signName, + 'SignatureMethod' => 'HMAC-SHA1', + 'SignatureNonce' => \uniqid(), + 'SignatureVersion' => '1.0', + 'TemplateCode' => $this->templateCode, + 'TemplateParam' => \json_encode(['code' => $message->getContent()]), + 'Timestamp' => \gmdate('Y-m-d\TH:i:s\Z'), + 'Version' => '2017-05-25', + ]; + + $params['Signature'] = $this->generateSignature($params); + + $result = $this->request( + method: 'GET', + url: 'https://dysmsapi.aliyuncs.com', + headers: [], + body: $params + ); + + if ($result['statusCode'] >= 200 && $result['statusCode'] < 300 && ($result['response']['Code'] ?? '') === 'OK') { + $response->incrementDeliveredTo(); + $response->addResult($to); + } else { + $response->addResult($to, $result['response']['Message'] ?? 'Unknown error'); + } + } + + return $response->toArray(); + } + + /** + * Generate Alibaba Cloud API Signature. + */ + private function generateSignature(array $params): string + { + \ksort($params); + + $canonicalizedQueryString = ''; + foreach ($params as $key => $value) { + $canonicalizedQueryString .= '&' . $this->percentEncode($key) . '=' . $this->percentEncode($value); + } + + $stringToSign = 'GET&' . $this->percentEncode('/') . '&' . $this->percentEncode(\substr($canonicalizedQueryString, 1)); + + $signature = \base64_encode(\hash_hmac('sha1', $stringToSign, $this->accessKeySecret . '&', true)); + + return $signature; + } + + private function percentEncode(string $str): string + { + $res = \urlencode($str); + $res = \str_replace(['+', '*'], ['%20', '%2A'], $res); + $res = \preg_replace('/%7E/i', '~', $res); + + return $res; + } +} diff --git a/tests/Messaging/Adapter/SMS/AlibabaCloudTest.php b/tests/Messaging/Adapter/SMS/AlibabaCloudTest.php new file mode 100644 index 00000000..762d2700 --- /dev/null +++ b/tests/Messaging/Adapter/SMS/AlibabaCloudTest.php @@ -0,0 +1,32 @@ + '123456']), + ); + + $result = $sender->send($message); + + $this->assertResponse($result); + } +} From bf2c6133025bc98e3fa7489110348d5d15caaa7a Mon Sep 17 00:00:00 2001 From: deepshekhardas Date: Sun, 10 May 2026 16:45:48 +0530 Subject: [PATCH 2/2] fix: send Alibaba GET params in query string not body Alibaba SMS signature validation requires query parameters, not body. Moved signature params from request body to URL query string. Identified by cubic (cubic.dev) --- src/Utopia/Messaging/Adapter/SMS/AlibabaCloud.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Utopia/Messaging/Adapter/SMS/AlibabaCloud.php b/src/Utopia/Messaging/Adapter/SMS/AlibabaCloud.php index f3a59378..ff4a23d6 100644 --- a/src/Utopia/Messaging/Adapter/SMS/AlibabaCloud.php +++ b/src/Utopia/Messaging/Adapter/SMS/AlibabaCloud.php @@ -64,13 +64,19 @@ protected function process(SMSMessage $message): array 'Version' => '2017-05-25', ]; - $params['Signature'] = $this->generateSignature($params); +$params['Signature'] = $this->generateSignature($params); + + $queryString = ''; + foreach ($params as $key => $value) { + $queryString .= '&' . $this->percentEncode($key) . '=' . $this->percentEncode($value); + } + $queryString = \substr($queryString, 1); $result = $this->request( method: 'GET', - url: 'https://dysmsapi.aliyuncs.com', + url: 'https://dysmsapi.aliyuncs.com?' . $queryString, headers: [], - body: $params + body: null ); if ($result['statusCode'] >= 200 && $result['statusCode'] < 300 && ($result['response']['Code'] ?? '') === 'OK') {