diff --git a/.gitignore b/.gitignore index 24265e89..03bf4c75 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ .idea vendor .phpunit.result.cache +Makefile +.envrc +.env +*.p8 \ 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/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/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/src/Utopia/Messaging/Entities/Entity.php b/src/Utopia/Messaging/Entities/Entity.php new file mode 100644 index 00000000..126407e2 --- /dev/null +++ b/src/Utopia/Messaging/Entities/Entity.php @@ -0,0 +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/Entities/Provider.php b/src/Utopia/Messaging/Entities/Provider.php new file mode 100644 index 00000000..d85b604e --- /dev/null +++ b/src/Utopia/Messaging/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/Entities/Subscriber.php b/src/Utopia/Messaging/Entities/Subscriber.php new file mode 100644 index 00000000..d7594427 --- /dev/null +++ b/src/Utopia/Messaging/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/Entities/Target.php b/src/Utopia/Messaging/Entities/Target.php new file mode 100644 index 00000000..b0d37a35 --- /dev/null +++ b/src/Utopia/Messaging/Entities/Target.php @@ -0,0 +1,34 @@ +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/Entities/Topic.php b/src/Utopia/Messaging/Entities/Topic.php new file mode 100644 index 00000000..b9759b8e --- /dev/null +++ b/src/Utopia/Messaging/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 diff --git a/src/Utopia/Messaging/Message.php b/src/Utopia/Messaging/Message.php index 3b5e21ae..0b70a2ce 100644 --- a/src/Utopia/Messaging/Message.php +++ b/src/Utopia/Messaging/Message.php @@ -7,4 +7,6 @@ */ interface Message { + function getTo():array; + function getFrom():?string; } 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/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); + } +} 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); + } +} 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/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 new file mode 100644 index 00000000..3c50ae4e --- /dev/null +++ b/tests/e2e/SMS/TwilioTest.php @@ -0,0 +1,27 @@ +send($message); + + $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.'); + } +}