From 52021eef254ac5a04947a9ea81c91c796f63d71c Mon Sep 17 00:00:00 2001 From: wess Date: Wed, 19 Jul 2023 09:36:11 -0400 Subject: [PATCH 1/8] Added start to APNS --- .gitignore | 1 + src/Utopia/Messaging/Adapters/Push/APNS.php | 33 +++++++++++++ src/Utopia/Messaging/Apple/Notification.php | 51 +++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 src/Utopia/Messaging/Adapters/Push/APNS.php create mode 100644 src/Utopia/Messaging/Apple/Notification.php diff --git a/.gitignore b/.gitignore index 24265e89..7539ebce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea vendor .phpunit.result.cache +Makefile \ No newline at end of file diff --git a/src/Utopia/Messaging/Adapters/Push/APNS.php b/src/Utopia/Messaging/Adapters/Push/APNS.php new file mode 100644 index 00000000..0cce5fd8 --- /dev/null +++ b/src/Utopia/Messaging/Adapters/Push/APNS.php @@ -0,0 +1,33 @@ +client = new ApplePushNotification($certificatePath, $passphrase, $ssl); + } + + public function getName(): string + { + return 'applePush'; + } + + public function process(Push $message): string + { + try { + $this->client->send($message->getTo(), $message->getBody()); + + return true; + } catch (\Exception $e) { + throw new \Exception($e->getMessage(), 500); + } + } +} + +?> diff --git a/src/Utopia/Messaging/Apple/Notification.php b/src/Utopia/Messaging/Apple/Notification.php new file mode 100644 index 00000000..7821f7f8 --- /dev/null +++ b/src/Utopia/Messaging/Apple/Notification.php @@ -0,0 +1,51 @@ +certificatePath = $certificatePath; + $this->passphrase = $passphrase; + $this->ssl = $ssl; + } + + public function send($deviceToken, $message) + { + $ctx = stream_context_create(); + stream_context_set_option($ctx, 'ssl', 'local_cert', $this->certificatePath); + stream_context_set_option($ctx, 'ssl', 'passphrase', $this->passphrase); + + $fp = stream_socket_client( + $this->ssl, $err, + $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx + ); + + if (!$fp) + exit("Failed to connect: $err $errstr" . PHP_EOL); + + $body['aps'] = array( + 'alert' => $message, + 'sound' => 'default' + ); + + $payload = json_encode($body); + + $msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload; + $result = fwrite($fp, $msg, strlen($msg)); + + if (!$result) + echo 'Message not delivered' . PHP_EOL; + else + echo 'Message successfully delivered' . PHP_EOL; + + fclose($fp); + } +} + +?> From 8ee18447c9d757ee572ddc0c21f63f47cf66e215 Mon Sep 17 00:00:00 2001 From: wess Date: Sat, 29 Jul 2023 09:41:20 -0400 Subject: [PATCH 2/8] Data structures and apns --- .../Adapters/Push/Apple/Rest/Config.php | 112 ++++++++++++++++++ .../Adapters/Push/Apple/Rest/Exception.php | 7 ++ .../Adapters/Push/Apple/Rest/Message.php | 41 +++++++ .../Adapters/Push/Apple/Rest/Request.php | 51 ++++++++ .../Adapters/Push/Apple/Rest/Response.php | 38 ++++++ .../Adapters/Push/{APNS.php => Apple/SSL.php} | 2 + .../Adapters/Push/Entities/Entity.php | 7 ++ .../Adapters/Push/Entities/Message.php | 43 +++++++ .../Adapters/Push/Entities/Provider.php | 35 ++++++ .../Adapters/Push/Entities/Subscriber.php | 31 +++++ .../Adapters/Push/Entities/Target.php | 35 ++++++ .../Adapters/Push/Entities/Topic.php | 29 +++++ 12 files changed, 431 insertions(+) create mode 100644 src/Utopia/Messaging/Adapters/Push/Apple/Rest/Config.php create mode 100644 src/Utopia/Messaging/Adapters/Push/Apple/Rest/Exception.php create mode 100644 src/Utopia/Messaging/Adapters/Push/Apple/Rest/Message.php create mode 100644 src/Utopia/Messaging/Adapters/Push/Apple/Rest/Request.php create mode 100644 src/Utopia/Messaging/Adapters/Push/Apple/Rest/Response.php rename src/Utopia/Messaging/Adapters/Push/{APNS.php => Apple/SSL.php} (94%) create mode 100644 src/Utopia/Messaging/Adapters/Push/Entities/Entity.php create mode 100644 src/Utopia/Messaging/Adapters/Push/Entities/Message.php create mode 100644 src/Utopia/Messaging/Adapters/Push/Entities/Provider.php create mode 100644 src/Utopia/Messaging/Adapters/Push/Entities/Subscriber.php create mode 100644 src/Utopia/Messaging/Adapters/Push/Entities/Target.php create mode 100644 src/Utopia/Messaging/Adapters/Push/Entities/Topic.php diff --git a/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Config.php b/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Config.php new file mode 100644 index 00000000..91f16fe0 --- /dev/null +++ b/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Config.php @@ -0,0 +1,112 @@ + 'https://api.push.apple.com/3/device/', + 'sandbox' => 'https://api.development.push.apple.com/3/device/' + ]; + + /** + * @var string + */ + private $url; + + /** + * @var string + */ + private $keypath; + + /** + * @var string + */ + private $topic; + + /** + * @var string + */ + private $secretKey; + + /** + * @var string + */ + private $token; + + + /** + * Construct + * + * Construct a new APNS push configuration. + * + * @param string $env + * @param string $keypath + * @param string $secretKey + * @param string $topic + */ + public function __construct($env = Config::SANDBOX, $keypath, $secretKey, $topic) + { + $this->url = $this->endpoint[$env]; + $this->keypath = $keypath; + $this->secretKey = $secretKey; + $this->topic = $topic; + } + + /** + * Get Secret key + * + * @return string + */ + public function getSecretKey() + { + return $this->secretKey; + } + + /** + * Get Key path + * + * @return string + */ + public function getKeypath() + { + return $this->keypath; + } + + /** + * Get URL + * + * @return string + */ + public function getURL() + { + return $this->url; + } + + /** + * Get Topic + * + * @return string + */ + public function getTopic() + { + return $this->topic; + } + + /** + * Returns headers used for sending notification. + * + * @return associative array + * + * @todo: add support for various push types. + */ + public function getHeaders() { + return [ + 'apns-topic: ' . $this->getTopic(), + 'apns-push-type: ' . 'alert', + ]; + } +} \ No newline at end of file diff --git a/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Exception.php b/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Exception.php new file mode 100644 index 00000000..9eb94cbc --- /dev/null +++ b/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Exception.php @@ -0,0 +1,7 @@ +title = $title; + $this->body = $body; + $this->sound = $sound; + } + + public function toJson() + { + return json_encode([ + 'aps' => [ + 'alert' => [ + 'title' => $this->title, + 'body' => $this->body + ], + 'sound' => $this->sound + ] + ]); + } +} \ No newline at end of file diff --git a/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Request.php b/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Request.php new file mode 100644 index 00000000..431f8f72 --- /dev/null +++ b/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Request.php @@ -0,0 +1,51 @@ +config = $config; + } + + public function send(string $deviceToken, Message $message) + { + $url = $this->config->getURL() . $deviceToken; + + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_HTTPHEADER, $this->config->getHeaders()); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($message->toJson())); + curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); + + curl_setopt($ch, CURLOPT_SSLCERT, $this->config->getKeyPath()); + curl_setopt($ch, CURLOPT_SSLCERTPASSWD, $this->config->getSecretKey()); + + curl_setopt($ch, CURLOPT_FAILONERROR, true); + curl_exec($ch); + + $response = new Response(curl_getinfo($ch, CURLINFO_HTTP_CODE)); + + curl_close($ch); + + return [ + 'code' => $response->code, + 'response' => $response->description + ]; + } +} \ No newline at end of file diff --git a/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Response.php b/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Response.php new file mode 100644 index 00000000..df1ba709 --- /dev/null +++ b/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Response.php @@ -0,0 +1,38 @@ + 'Invalid configuration.', + '200' => 'Success.', + '400' => 'Bad request.', + '403' => 'There was an error with the certificate or with the provider’s authentication token.', + '404' => 'The request contained an invalid :path value.', + '405' => 'The request used an invalid :method value. Only POST requests are supported.', + '410' => 'The device token is no longer active for the topic.', + '413' => 'The notification payload was too large.', + '429' => 'The server received too many requests for the same device token.', + '500' => 'Internal server error.', + '503' => 'The server is shutting down and unavailable.', + ]; + + /** + * @var string + */ + public $code; + + /** + * @var string + */ + public $description; + + public function __construct(string $statusCode) + { + $code = isset(self::$descriptions[$statusCode]) ? $statusCode : 0; + + $this->code = $code; + $this->description = self::$descriptions[$code]; + } +} \ No newline at end of file diff --git a/src/Utopia/Messaging/Adapters/Push/APNS.php b/src/Utopia/Messaging/Adapters/Push/Apple/SSL.php similarity index 94% rename from src/Utopia/Messaging/Adapters/Push/APNS.php rename to src/Utopia/Messaging/Adapters/Push/Apple/SSL.php index 0cce5fd8..21831f62 100644 --- a/src/Utopia/Messaging/Adapters/Push/APNS.php +++ b/src/Utopia/Messaging/Adapters/Push/Apple/SSL.php @@ -1,5 +1,7 @@ id = $id; + $this->providerId = $providerId; + $this->providerInternalId = $providerInternalId; + $this->data = $data; + $this->to = $to; + $this->deliveryTime = $deliveryTime; + $this->deliveryError = $deliveryError; + $this->deliveredTo = $deliveredTo; + $this->delivered = $delivered; + $this->search = $search; + } + +} \ No newline at end of file diff --git a/src/Utopia/Messaging/Adapters/Push/Entities/Provider.php b/src/Utopia/Messaging/Adapters/Push/Entities/Provider.php new file mode 100644 index 00000000..d85b604e --- /dev/null +++ b/src/Utopia/Messaging/Adapters/Push/Entities/Provider.php @@ -0,0 +1,35 @@ +id = $id; + $this->userId = $userId; + $this->name = $name; + $this->provider = $provider; + $this->type = $type; + $this->credentials = $credentials; + } + +} \ No newline at end of file diff --git a/src/Utopia/Messaging/Adapters/Push/Entities/Subscriber.php b/src/Utopia/Messaging/Adapters/Push/Entities/Subscriber.php new file mode 100644 index 00000000..d7594427 --- /dev/null +++ b/src/Utopia/Messaging/Adapters/Push/Entities/Subscriber.php @@ -0,0 +1,31 @@ +id = $id; + $this->userId = $userId; + $this->userInternalId = $userInternalId; + $this->targetId = $targetId; + $this->targetInternalId = $targetInternalId; + } + +} \ No newline at end of file diff --git a/src/Utopia/Messaging/Adapters/Push/Entities/Target.php b/src/Utopia/Messaging/Adapters/Push/Entities/Target.php new file mode 100644 index 00000000..2058440b --- /dev/null +++ b/src/Utopia/Messaging/Adapters/Push/Entities/Target.php @@ -0,0 +1,35 @@ +id = $id; + $this->userId = $userId; + $this->userInternalId = $userInternalId; + $this->providerId = $providerId; + $this->providerInternalId = $providerInternalId; + $this->providerType = $providerType; + $this->identifier = $identifier; + } + +} \ No newline at end of file diff --git a/src/Utopia/Messaging/Adapters/Push/Entities/Topic.php b/src/Utopia/Messaging/Adapters/Push/Entities/Topic.php new file mode 100644 index 00000000..b9759b8e --- /dev/null +++ b/src/Utopia/Messaging/Adapters/Push/Entities/Topic.php @@ -0,0 +1,29 @@ +id = $id; + $this->providerId = $providerId; + $this->providerInternalId = $providerInternalId; + $this->name = $name; + $this->description = $description; + } + +} \ No newline at end of file From 2e37e489e3f922d815baa37419bf61b18d616d61 Mon Sep 17 00:00:00 2001 From: wess Date: Mon, 31 Jul 2023 11:54:40 -0400 Subject: [PATCH 3/8] working on email adapter for service and impl --- src/Utopia/Messaging/Message.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Utopia/Messaging/Message.php b/src/Utopia/Messaging/Message.php index 3b5e21ae..c9533ad1 100644 --- a/src/Utopia/Messaging/Message.php +++ b/src/Utopia/Messaging/Message.php @@ -7,4 +7,5 @@ */ interface Message { + function getTo():string; } From 5ccda373cd0376f407c7114f810d6815dba15215 Mon Sep 17 00:00:00 2001 From: wess Date: Mon, 31 Jul 2023 12:10:29 -0400 Subject: [PATCH 4/8] Little reorg --- src/Utopia/Messaging/{Adapters/Push => }/Entities/Entity.php | 0 src/Utopia/Messaging/{Adapters/Push => }/Entities/Message.php | 0 src/Utopia/Messaging/{Adapters/Push => }/Entities/Provider.php | 0 .../Messaging/{Adapters/Push => }/Entities/Subscriber.php | 0 src/Utopia/Messaging/{Adapters/Push => }/Entities/Target.php | 0 src/Utopia/Messaging/{Adapters/Push => }/Entities/Topic.php | 0 src/Utopia/Messaging/Message.php | 3 ++- 7 files changed, 2 insertions(+), 1 deletion(-) rename src/Utopia/Messaging/{Adapters/Push => }/Entities/Entity.php (100%) rename src/Utopia/Messaging/{Adapters/Push => }/Entities/Message.php (100%) rename src/Utopia/Messaging/{Adapters/Push => }/Entities/Provider.php (100%) rename src/Utopia/Messaging/{Adapters/Push => }/Entities/Subscriber.php (100%) rename src/Utopia/Messaging/{Adapters/Push => }/Entities/Target.php (100%) rename src/Utopia/Messaging/{Adapters/Push => }/Entities/Topic.php (100%) diff --git a/src/Utopia/Messaging/Adapters/Push/Entities/Entity.php b/src/Utopia/Messaging/Entities/Entity.php similarity index 100% rename from src/Utopia/Messaging/Adapters/Push/Entities/Entity.php rename to src/Utopia/Messaging/Entities/Entity.php diff --git a/src/Utopia/Messaging/Adapters/Push/Entities/Message.php b/src/Utopia/Messaging/Entities/Message.php similarity index 100% rename from src/Utopia/Messaging/Adapters/Push/Entities/Message.php rename to src/Utopia/Messaging/Entities/Message.php diff --git a/src/Utopia/Messaging/Adapters/Push/Entities/Provider.php b/src/Utopia/Messaging/Entities/Provider.php similarity index 100% rename from src/Utopia/Messaging/Adapters/Push/Entities/Provider.php rename to src/Utopia/Messaging/Entities/Provider.php diff --git a/src/Utopia/Messaging/Adapters/Push/Entities/Subscriber.php b/src/Utopia/Messaging/Entities/Subscriber.php similarity index 100% rename from src/Utopia/Messaging/Adapters/Push/Entities/Subscriber.php rename to src/Utopia/Messaging/Entities/Subscriber.php diff --git a/src/Utopia/Messaging/Adapters/Push/Entities/Target.php b/src/Utopia/Messaging/Entities/Target.php similarity index 100% rename from src/Utopia/Messaging/Adapters/Push/Entities/Target.php rename to src/Utopia/Messaging/Entities/Target.php diff --git a/src/Utopia/Messaging/Adapters/Push/Entities/Topic.php b/src/Utopia/Messaging/Entities/Topic.php similarity index 100% rename from src/Utopia/Messaging/Adapters/Push/Entities/Topic.php rename to src/Utopia/Messaging/Entities/Topic.php diff --git a/src/Utopia/Messaging/Message.php b/src/Utopia/Messaging/Message.php index c9533ad1..0b70a2ce 100644 --- a/src/Utopia/Messaging/Message.php +++ b/src/Utopia/Messaging/Message.php @@ -7,5 +7,6 @@ */ interface Message { - function getTo():string; + function getTo():array; + function getFrom():?string; } From e9c792a82e722447573f571d415e3254868bfc28 Mon Sep 17 00:00:00 2001 From: wess Date: Wed, 2 Aug 2023 14:30:25 -0400 Subject: [PATCH 5/8] Sendgrid test and some key clean up --- .gitignore | 4 +- composer.json | 4 +- composer.lock | 49 ++++++++++--------- docker-compose.yml | 2 + phpunit.xml | 2 +- .../Messaging/Adapters/Email/Mailgun.php | 6 ++- .../Messaging/Adapters/Email/Sendgrid.php | 4 +- src/Utopia/Messaging/Entities/Target.php | 1 - tests/e2e/Email/MailgunTest.php | 40 +++++++++++++++ tests/e2e/Email/SendgridTest.php | 34 +++++++++++++ 10 files changed, 113 insertions(+), 33 deletions(-) create mode 100644 tests/e2e/Email/MailgunTest.php create mode 100644 tests/e2e/Email/SendgridTest.php diff --git a/.gitignore b/.gitignore index 7539ebce..5176c240 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .idea vendor .phpunit.result.cache -Makefile \ No newline at end of file +Makefile +.envrc +.env \ No newline at end of file diff --git a/composer.json b/composer.json index 0e9de463..7dfe178e 100644 --- a/composer.json +++ b/composer.json @@ -26,8 +26,8 @@ "ext-curl": "*" }, "require-dev": { - "phpunit/phpunit": "9.5.*", - "phpmailer/phpmailer": "6.6.*", + "phpunit/phpunit": "^9.6", + "phpmailer/phpmailer": "^6.8", "laravel/pint": "^1.2" }, "config": { diff --git a/composer.lock b/composer.lock index c2118454..9f6b978b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1ca9da311c804e40032e90c09ad04d76", + "content-hash": "5ecbd865cbd7f14e7819fb79643573be", "packages": [], "packages-dev": [ { @@ -371,16 +371,16 @@ }, { "name": "phpmailer/phpmailer", - "version": "v6.6.4", + "version": "v6.8.0", "source": { "type": "git", "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "a94fdebaea6bd17f51be0c2373ab80d3d681269b" + "reference": "df16b615e371d81fb79e506277faea67a1be18f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/a94fdebaea6bd17f51be0c2373ab80d3d681269b", - "reference": "a94fdebaea6bd17f51be0c2373ab80d3d681269b", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/df16b615e371d81fb79e506277faea67a1be18f1", + "reference": "df16b615e371d81fb79e506277faea67a1be18f1", "shasum": "" }, "require": { @@ -390,22 +390,24 @@ "php": ">=5.5.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "doctrine/annotations": "^1.2", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.2", + "doctrine/annotations": "^1.2.6 || ^1.13.3", "php-parallel-lint/php-console-highlighter": "^1.0.0", "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcompatibility/php-compatibility": "^9.3.5", "roave/security-advisories": "dev-latest", - "squizlabs/php_codesniffer": "^3.6.2", - "yoast/phpunit-polyfills": "^1.0.0" + "squizlabs/php_codesniffer": "^3.7.1", + "yoast/phpunit-polyfills": "^1.0.4" }, "suggest": { "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", "league/oauth2-google": "Needed for Google XOAUTH2 authentication", "psr/log": "For optional PSR-3 debug logging", - "stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication", - "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)" + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" }, "type": "library", "autoload": { @@ -437,7 +439,7 @@ "description": "PHPMailer is a full-featured email creation and transfer class for PHP", "support": { "issues": "https://github.com/PHPMailer/PHPMailer/issues", - "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.6.4" + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.8.0" }, "funding": [ { @@ -445,7 +447,7 @@ "type": "github" } ], - "time": "2022-08-22T09:22:00+00:00" + "time": "2023-03-06T14:43:22+00:00" }, { "name": "phpunit/php-code-coverage", @@ -767,20 +769,20 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.25", + "version": "9.6.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d" + "reference": "a6d351645c3fe5a30f5e86be6577d946af65a328" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", - "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a6d351645c3fe5a30f5e86be6577d946af65a328", + "reference": "a6d351645c3fe5a30f5e86be6577d946af65a328", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1", + "doctrine/instantiator": "^1.3.1 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", @@ -809,8 +811,8 @@ "sebastian/version": "^3.0.2" }, "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "bin": [ "phpunit" @@ -818,7 +820,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.5-dev" + "dev-master": "9.6-dev" } }, "autoload": { @@ -849,7 +851,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.25" + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.10" }, "funding": [ { @@ -865,7 +868,7 @@ "type": "tidelift" } ], - "time": "2022-09-25T03:44:45+00:00" + "time": "2023-07-10T04:04:23+00:00" }, { "name": "sebastian/cli-parser", diff --git a/docker-compose.yml b/docker-compose.yml index 96054433..e0a8130f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,8 @@ services: - ./src:/usr/local/src/src - ./tests:/usr/local/src/tests - ./phpunit.xml:/usr/local/src/phpunit.xml + env_file: + - .env maildev: image: appwrite/mailcatcher:1.0.0 diff --git a/phpunit.xml b/phpunit.xml index 8db6586a..53cd4abd 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,7 +6,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false" + stopOnFailure="true" > diff --git a/src/Utopia/Messaging/Adapters/Email/Mailgun.php b/src/Utopia/Messaging/Adapters/Email/Mailgun.php index ba64c906..148cb174 100644 --- a/src/Utopia/Messaging/Adapters/Email/Mailgun.php +++ b/src/Utopia/Messaging/Adapters/Email/Mailgun.php @@ -34,19 +34,21 @@ public function getMaxMessagesPerRequest(): int */ protected function process(Email $message): string { - return $this->request( + $response = $this->request( method: 'POST', url: "https://api.mailgun.net/v3/{$this->domain}/messages", headers: [ 'Authorization: Basic '.base64_encode('api:'.$this->apiKey), ], body: \http_build_query([ - 'from' => $message->getFrom(), 'to' => \implode(',', $message->getTo()), + 'from' => $message->getFrom(), 'subject' => $message->getSubject(), 'text' => $message->isHtml() ? null : $message->getContent(), 'html' => $message->isHtml() ? $message->getContent() : null, ]), ); + + return $response; } } diff --git a/src/Utopia/Messaging/Adapters/Email/Sendgrid.php b/src/Utopia/Messaging/Adapters/Email/Sendgrid.php index 17745c48..a805e6c6 100644 --- a/src/Utopia/Messaging/Adapters/Email/Sendgrid.php +++ b/src/Utopia/Messaging/Adapters/Email/Sendgrid.php @@ -7,9 +7,7 @@ class Sendgrid extends EmailAdapter { - public function __construct( - private string $apiKey, - ) { + public function __construct(private string $apiKey) { } public function getName(): string diff --git a/src/Utopia/Messaging/Entities/Target.php b/src/Utopia/Messaging/Entities/Target.php index 2058440b..b0d37a35 100644 --- a/src/Utopia/Messaging/Entities/Target.php +++ b/src/Utopia/Messaging/Entities/Target.php @@ -2,7 +2,6 @@ namespace Utopia\Messaging\Adapters\Push\Entities; -use DateTime; use Utopia\Messaging\Adapters\Push\Entities\Entity; class Target extends Entity { diff --git a/tests/e2e/Email/MailgunTest.php b/tests/e2e/Email/MailgunTest.php new file mode 100644 index 00000000..0f55ee15 --- /dev/null +++ b/tests/e2e/Email/MailgunTest.php @@ -0,0 +1,40 @@ +send($message); + + $this->assertEquals(true, true); + } +} diff --git a/tests/e2e/Email/SendgridTest.php b/tests/e2e/Email/SendgridTest.php new file mode 100644 index 00000000..0786a1eb --- /dev/null +++ b/tests/e2e/Email/SendgridTest.php @@ -0,0 +1,34 @@ +send($message); + + $this->assertEquals(true, true); + } +} From cb8f99efa6244dd77809dc3607226999f401404f Mon Sep 17 00:00:00 2001 From: wess Date: Thu, 3 Aug 2023 15:48:09 -0400 Subject: [PATCH 6/8] Removes unneeded classes for apns Creates APNS Adapter Creates tests for Apple Push --- .gitignore | 3 +- src/Utopia/Messaging/Adapters/Push/APNS.php | 95 +++++++++++++++ .../Adapters/Push/Apple/Rest/Config.php | 112 ------------------ .../Adapters/Push/Apple/Rest/Exception.php | 7 -- .../Adapters/Push/Apple/Rest/Message.php | 41 ------- .../Adapters/Push/Apple/Rest/Request.php | 51 -------- .../Adapters/Push/Apple/Rest/Response.php | 38 ------ .../Messaging/Adapters/Push/Apple/SSL.php | 35 ------ src/Utopia/Messaging/Apple/Notification.php | 51 -------- src/Utopia/Messaging/Messages/Push.php | 5 + tests/e2e/Push/APNSTest.php | 37 ++++++ 11 files changed, 139 insertions(+), 336 deletions(-) create mode 100644 src/Utopia/Messaging/Adapters/Push/APNS.php delete mode 100644 src/Utopia/Messaging/Adapters/Push/Apple/Rest/Config.php delete mode 100644 src/Utopia/Messaging/Adapters/Push/Apple/Rest/Exception.php delete mode 100644 src/Utopia/Messaging/Adapters/Push/Apple/Rest/Message.php delete mode 100644 src/Utopia/Messaging/Adapters/Push/Apple/Rest/Request.php delete mode 100644 src/Utopia/Messaging/Adapters/Push/Apple/Rest/Response.php delete mode 100644 src/Utopia/Messaging/Adapters/Push/Apple/SSL.php delete mode 100644 src/Utopia/Messaging/Apple/Notification.php create mode 100644 tests/e2e/Push/APNSTest.php diff --git a/.gitignore b/.gitignore index 5176c240..03bf4c75 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ vendor .phpunit.result.cache Makefile .envrc -.env \ No newline at end of file +.env +*.p8 \ No newline at end of file diff --git a/src/Utopia/Messaging/Adapters/Push/APNS.php b/src/Utopia/Messaging/Adapters/Push/APNS.php new file mode 100644 index 00000000..b5bd8a03 --- /dev/null +++ b/src/Utopia/Messaging/Adapters/Push/APNS.php @@ -0,0 +1,95 @@ +authKey = $authKey; + $this->authKeyId = $authKeyId; + $this->teamId = $teamId; + $this->bundleId = $bundleId; + $this->endpoint = $endpoint; + } + + public function getName(): string + { + return 'APNS'; + } + + + public function getMaxMessagesPerRequest(): int + { + return 1000; + } + + public function process(Push $message): string + { + $headers = [ + 'authorization: bearer ' . $this->generateJwt(), + 'apns-topic: ' . $this->bundleId, + ]; + + $payload = json_encode([ + 'aps' => [ + 'alert' => [ + 'title' => $message->getTitle(), + 'body' => $message->getBody(), + ], + 'badge' => $message->getBadge(), + 'sound' => $message->getSound(), + 'data' => $message->getData(), + ], + ]); + + // Assuming the 'to' array contains device tokens for the push notification recipients. + foreach ($message->getTo() as $to) { + $url = $this->endpoint . '/3/device/' . $to; + $response = $this->request('POST', $url, $headers, $payload); + + // You might want to handle each response here, for instance, logging failures + } + + // This example simply returns the last response, adjust as needed + return $response; + } + + private function generateJwt(): string + { + $header = json_encode(['alg' => 'ES256', 'kid' => $this->authKeyId]); + $claims = json_encode([ + 'iss' => $this->teamId, + 'iat' => time(), + ]); + + $base64UrlHeader = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($header)); + $base64UrlClaims = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($claims)); + + $privateKeyResource = openssl_pkey_get_private(file_get_contents($this->authKey)); + if (!$privateKeyResource) { + throw new \Exception('Invalid private key'); + } + + $signature = ''; + $success = openssl_sign("$base64UrlHeader.$base64UrlClaims", $signature, $privateKeyResource, OPENSSL_ALGO_SHA256); + + if (!$success) { + throw new \Exception('Failed to sign JWT'); + } + + $base64UrlSignature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature)); + + return "$base64UrlHeader.$base64UrlClaims.$base64UrlSignature"; + } + +} diff --git a/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Config.php b/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Config.php deleted file mode 100644 index 91f16fe0..00000000 --- a/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Config.php +++ /dev/null @@ -1,112 +0,0 @@ - 'https://api.push.apple.com/3/device/', - 'sandbox' => 'https://api.development.push.apple.com/3/device/' - ]; - - /** - * @var string - */ - private $url; - - /** - * @var string - */ - private $keypath; - - /** - * @var string - */ - private $topic; - - /** - * @var string - */ - private $secretKey; - - /** - * @var string - */ - private $token; - - - /** - * Construct - * - * Construct a new APNS push configuration. - * - * @param string $env - * @param string $keypath - * @param string $secretKey - * @param string $topic - */ - public function __construct($env = Config::SANDBOX, $keypath, $secretKey, $topic) - { - $this->url = $this->endpoint[$env]; - $this->keypath = $keypath; - $this->secretKey = $secretKey; - $this->topic = $topic; - } - - /** - * Get Secret key - * - * @return string - */ - public function getSecretKey() - { - return $this->secretKey; - } - - /** - * Get Key path - * - * @return string - */ - public function getKeypath() - { - return $this->keypath; - } - - /** - * Get URL - * - * @return string - */ - public function getURL() - { - return $this->url; - } - - /** - * Get Topic - * - * @return string - */ - public function getTopic() - { - return $this->topic; - } - - /** - * Returns headers used for sending notification. - * - * @return associative array - * - * @todo: add support for various push types. - */ - public function getHeaders() { - return [ - 'apns-topic: ' . $this->getTopic(), - 'apns-push-type: ' . 'alert', - ]; - } -} \ No newline at end of file diff --git a/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Exception.php b/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Exception.php deleted file mode 100644 index 9eb94cbc..00000000 --- a/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Exception.php +++ /dev/null @@ -1,7 +0,0 @@ -title = $title; - $this->body = $body; - $this->sound = $sound; - } - - public function toJson() - { - return json_encode([ - 'aps' => [ - 'alert' => [ - 'title' => $this->title, - 'body' => $this->body - ], - 'sound' => $this->sound - ] - ]); - } -} \ No newline at end of file diff --git a/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Request.php b/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Request.php deleted file mode 100644 index 431f8f72..00000000 --- a/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Request.php +++ /dev/null @@ -1,51 +0,0 @@ -config = $config; - } - - public function send(string $deviceToken, Message $message) - { - $url = $this->config->getURL() . $deviceToken; - - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_HTTPHEADER, $this->config->getHeaders()); - curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($message->toJson())); - curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); - - curl_setopt($ch, CURLOPT_SSLCERT, $this->config->getKeyPath()); - curl_setopt($ch, CURLOPT_SSLCERTPASSWD, $this->config->getSecretKey()); - - curl_setopt($ch, CURLOPT_FAILONERROR, true); - curl_exec($ch); - - $response = new Response(curl_getinfo($ch, CURLINFO_HTTP_CODE)); - - curl_close($ch); - - return [ - 'code' => $response->code, - 'response' => $response->description - ]; - } -} \ No newline at end of file diff --git a/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Response.php b/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Response.php deleted file mode 100644 index df1ba709..00000000 --- a/src/Utopia/Messaging/Adapters/Push/Apple/Rest/Response.php +++ /dev/null @@ -1,38 +0,0 @@ - 'Invalid configuration.', - '200' => 'Success.', - '400' => 'Bad request.', - '403' => 'There was an error with the certificate or with the provider’s authentication token.', - '404' => 'The request contained an invalid :path value.', - '405' => 'The request used an invalid :method value. Only POST requests are supported.', - '410' => 'The device token is no longer active for the topic.', - '413' => 'The notification payload was too large.', - '429' => 'The server received too many requests for the same device token.', - '500' => 'Internal server error.', - '503' => 'The server is shutting down and unavailable.', - ]; - - /** - * @var string - */ - public $code; - - /** - * @var string - */ - public $description; - - public function __construct(string $statusCode) - { - $code = isset(self::$descriptions[$statusCode]) ? $statusCode : 0; - - $this->code = $code; - $this->description = self::$descriptions[$code]; - } -} \ No newline at end of file diff --git a/src/Utopia/Messaging/Adapters/Push/Apple/SSL.php b/src/Utopia/Messaging/Adapters/Push/Apple/SSL.php deleted file mode 100644 index 21831f62..00000000 --- a/src/Utopia/Messaging/Adapters/Push/Apple/SSL.php +++ /dev/null @@ -1,35 +0,0 @@ -client = new ApplePushNotification($certificatePath, $passphrase, $ssl); - } - - public function getName(): string - { - return 'applePush'; - } - - public function process(Push $message): string - { - try { - $this->client->send($message->getTo(), $message->getBody()); - - return true; - } catch (\Exception $e) { - throw new \Exception($e->getMessage(), 500); - } - } -} - -?> diff --git a/src/Utopia/Messaging/Apple/Notification.php b/src/Utopia/Messaging/Apple/Notification.php deleted file mode 100644 index 7821f7f8..00000000 --- a/src/Utopia/Messaging/Apple/Notification.php +++ /dev/null @@ -1,51 +0,0 @@ -certificatePath = $certificatePath; - $this->passphrase = $passphrase; - $this->ssl = $ssl; - } - - public function send($deviceToken, $message) - { - $ctx = stream_context_create(); - stream_context_set_option($ctx, 'ssl', 'local_cert', $this->certificatePath); - stream_context_set_option($ctx, 'ssl', 'passphrase', $this->passphrase); - - $fp = stream_socket_client( - $this->ssl, $err, - $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx - ); - - if (!$fp) - exit("Failed to connect: $err $errstr" . PHP_EOL); - - $body['aps'] = array( - 'alert' => $message, - 'sound' => 'default' - ); - - $payload = json_encode($body); - - $msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload; - $result = fwrite($fp, $msg, strlen($msg)); - - if (!$result) - echo 'Message not delivered' . PHP_EOL; - else - echo 'Message successfully delivered' . PHP_EOL; - - fclose($fp); - } -} - -?> diff --git a/src/Utopia/Messaging/Messages/Push.php b/src/Utopia/Messaging/Messages/Push.php index d4a4333b..12e1d7c3 100644 --- a/src/Utopia/Messaging/Messages/Push.php +++ b/src/Utopia/Messaging/Messages/Push.php @@ -40,6 +40,11 @@ public function getTo(): array return $this->to; } + public function getFrom(): ?string + { + return null; + } + /** * @return string */ diff --git a/tests/e2e/Push/APNSTest.php b/tests/e2e/Push/APNSTest.php new file mode 100644 index 00000000..39fe3dd6 --- /dev/null +++ b/tests/e2e/Push/APNSTest.php @@ -0,0 +1,37 @@ +send($message); + + $this->assertEquals('', $response); + } +} From f0c5cbb762ae28e072a7574affa44798e1b34415 Mon Sep 17 00:00:00 2001 From: wess Date: Fri, 4 Aug 2023 13:45:25 -0400 Subject: [PATCH 7/8] Removes TwilioNotify as Twilio is ending it this year Adds FCM and FCM tests Twilio update and tests --- .../Messaging/Adapters/SMS/TwilioNotify.php | 53 ------------------- tests/e2e/Push/FCMTest.php | 33 ++++++++++++ tests/e2e/SMS/TwilioTest.php | 35 ++++++++++++ 3 files changed, 68 insertions(+), 53 deletions(-) delete mode 100644 src/Utopia/Messaging/Adapters/SMS/TwilioNotify.php create mode 100644 tests/e2e/Push/FCMTest.php create mode 100644 tests/e2e/SMS/TwilioTest.php diff --git a/src/Utopia/Messaging/Adapters/SMS/TwilioNotify.php b/src/Utopia/Messaging/Adapters/SMS/TwilioNotify.php deleted file mode 100644 index a977ed79..00000000 --- a/src/Utopia/Messaging/Adapters/SMS/TwilioNotify.php +++ /dev/null @@ -1,53 +0,0 @@ -request( - method: 'POST', - url: "https://notify.twilio.com/v1/Services/{$this->serviceSid}/Notifications", - headers: [ - 'Authorization: Basic '.base64_encode("{$this->accountSid}:{$this->authToken}"), - ], - body: \http_build_query([ - 'Body' => $message->getContent(), - 'ToBinding' => \json_encode(\array_map( - fn ($to) => ['binding_type' => 'sms', 'address' => $to], - $message->getTo() - )), - ]), - ); - } -} diff --git a/tests/e2e/Push/FCMTest.php b/tests/e2e/Push/FCMTest.php new file mode 100644 index 00000000..cd10658c --- /dev/null +++ b/tests/e2e/Push/FCMTest.php @@ -0,0 +1,33 @@ +send($message); + + $this->assertNotEmpty($response); + } +} diff --git a/tests/e2e/SMS/TwilioTest.php b/tests/e2e/SMS/TwilioTest.php new file mode 100644 index 00000000..21e1f87e --- /dev/null +++ b/tests/e2e/SMS/TwilioTest.php @@ -0,0 +1,35 @@ +send($message); + + $smsRequest = $this->getLastRequest(); + + $this->assertEquals('http://request-catcher:5000/mock-sms', $smsRequest['url']); + $this->assertEquals('Appwrite Mock Message Sender', $smsRequest['headers']['User-Agent']); + $this->assertEquals('username', $smsRequest['headers']['X-Username']); + $this->assertEquals('password', $smsRequest['headers']['X-Key']); + $this->assertEquals('POST', $smsRequest['method']); + $this->assertEquals('+987654321', $smsRequest['data']['from']); + $this->assertEquals('+123456789', $smsRequest['data']['to']); + } +} From 6472702c74a4e03c9b63ec572c6abcf80011571f Mon Sep 17 00:00:00 2001 From: wess Date: Fri, 4 Aug 2023 14:58:28 -0400 Subject: [PATCH 8/8] Twillio test Stubbed in, but on hold, other tests because of different requirements/issues --- tests/e2e/SMS/Msg91Test.php | 27 +++++++++++++++++++++++++++ tests/e2e/SMS/TelesignTest.php | 32 ++++++++++++++++++++++++++++++++ tests/e2e/SMS/TelnyxTest.php | 29 +++++++++++++++++++++++++++++ tests/e2e/SMS/TwilioTest.php | 14 +++----------- tests/e2e/SMS/vonage.php | 29 +++++++++++++++++++++++++++++ 5 files changed, 120 insertions(+), 11 deletions(-) create mode 100644 tests/e2e/SMS/Msg91Test.php create mode 100644 tests/e2e/SMS/TelesignTest.php create mode 100644 tests/e2e/SMS/TelnyxTest.php create mode 100644 tests/e2e/SMS/vonage.php diff --git a/tests/e2e/SMS/Msg91Test.php b/tests/e2e/SMS/Msg91Test.php new file mode 100644 index 00000000..3183d764 --- /dev/null +++ b/tests/e2e/SMS/Msg91Test.php @@ -0,0 +1,27 @@ +send($message), true); + + $this->assertEquals('success', $result["type"]); + } +} diff --git a/tests/e2e/SMS/TelesignTest.php b/tests/e2e/SMS/TelesignTest.php new file mode 100644 index 00000000..7cf276f9 --- /dev/null +++ b/tests/e2e/SMS/TelesignTest.php @@ -0,0 +1,32 @@ +send($message), true); + + // $this->assertEquals('success', $result["type"]); + + $this->markTestSkipped('Telesign requires sales calls and such to setup an account'); + } +} diff --git a/tests/e2e/SMS/TelnyxTest.php b/tests/e2e/SMS/TelnyxTest.php new file mode 100644 index 00000000..dc711498 --- /dev/null +++ b/tests/e2e/SMS/TelnyxTest.php @@ -0,0 +1,29 @@ +send($message), true); + + // $this->assertEquals('success', $result["type"]); + + $this->markTestSkipped('Telnyx had no testing numbers available at this time.'); + } +} diff --git a/tests/e2e/SMS/TwilioTest.php b/tests/e2e/SMS/TwilioTest.php index 21e1f87e..3c50ae4e 100644 --- a/tests/e2e/SMS/TwilioTest.php +++ b/tests/e2e/SMS/TwilioTest.php @@ -12,7 +12,7 @@ class TwilioTest extends Base */ public function testSendSMS() { - $sender = new Twilio('AC902ede6fda93fb923cc2c3128a2b79bd', 'cfc931ba5b7c1a3878ef5696938c8afc'); + $sender = new Twilio(getenv('TWILIO_ACCOUNT_SID'), getenv('TWILIO_AUTH_TOKEN')); $message = new SMS( to: ['+18034041123'], @@ -20,16 +20,8 @@ public function testSendSMS() from: '+15005550006' ); - $sender->send($message); + $result = $sender->send($message); - $smsRequest = $this->getLastRequest(); - - $this->assertEquals('http://request-catcher:5000/mock-sms', $smsRequest['url']); - $this->assertEquals('Appwrite Mock Message Sender', $smsRequest['headers']['User-Agent']); - $this->assertEquals('username', $smsRequest['headers']['X-Username']); - $this->assertEquals('password', $smsRequest['headers']['X-Key']); - $this->assertEquals('POST', $smsRequest['method']); - $this->assertEquals('+987654321', $smsRequest['data']['from']); - $this->assertEquals('+123456789', $smsRequest['data']['to']); + $this->assertNotEmpty($result); } } diff --git a/tests/e2e/SMS/vonage.php b/tests/e2e/SMS/vonage.php new file mode 100644 index 00000000..dc175df9 --- /dev/null +++ b/tests/e2e/SMS/vonage.php @@ -0,0 +1,29 @@ +send($message), true); + + // $this->assertEquals('success', $result["type"]); + + $this->markTestSkipped('Requires a business account, and to contact them first before getting access.'); + } +}