From ea0f51a4dadb218b6c2f0127daf1aca7568e182f Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 18 Nov 2020 21:38:31 +0200 Subject: [PATCH 01/36] Added new webhook events --- app/config/events.php | 6 ++++ app/controllers/api/account.php | 36 +++++++++++++++++--- src/Appwrite/Utopia/Response/Model/Token.php | 12 +++---- tests/e2e/Services/Account/AccountBase.php | 2 ++ 4 files changed, 46 insertions(+), 10 deletions(-) diff --git a/app/config/events.php b/app/config/events.php index 34c4a4f2de..d5c946f85d 100644 --- a/app/config/events.php +++ b/app/config/events.php @@ -19,6 +19,12 @@ return [ 'account.update.prefs' => [ 'description' => 'This event triggers when the account preferences are updated.', ], + 'account.recovery.create' => [ + 'description' => 'This event triggers when the account recovery token is created.', + ], + 'account.recovery.update' => [ + 'description' => 'This event triggers when the account recovery token is validated.', + ], 'account.delete' => [ 'description' => 'This event triggers when the account is deleted.', ], diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 20fc724d2a..c42ebaa3cf 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1153,6 +1153,7 @@ App::post('/v1/account/recovery') ->desc('Create Password Recovery') ->groups(['api', 'account']) ->label('scope', 'public') + ->label('event', 'account.recovery.create') ->label('sdk.platform', [APP_PLATFORM_CLIENT]) ->label('sdk.namespace', 'account') ->label('sdk.method', 'createRecovery') @@ -1164,7 +1165,7 @@ App::post('/v1/account/recovery') ->label('abuse-key', 'url:{url},email:{param-email}') ->param('email', '', new Email(), 'User email.') ->param('url', '', function ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients']) - ->action(function ($email, $url, $request, $response, $projectDB, $project, $locale, $mails, $audits) { + ->action(function ($email, $url, $request, $response, $projectDB, $project, $locale, $mails, $audits, $webhooks, $mode) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ @@ -1172,6 +1173,8 @@ App::post('/v1/account/recovery') /** @var Utopia\Locale\Locale $locale */ /** @var Appwrite\Event\Event $mails */ /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Event $webhooks */ + /** @var bool $mode */ $profile = $projectDB->getCollectionFirst([ // Get user by email address 'limit' => 1, @@ -1246,6 +1249,17 @@ App::post('/v1/account/recovery') ->trigger(); ; + $webhooks + ->setParam('payload', + $response->output($recovery->setAttribute('secret', $secret), + Response::MODEL_TOKEN + )) + ; + + $recovery // Hide secret for clients, sp + ->setAttribute('secret', + ((APP_MODE_ADMIN === $mode)) ? $secret : ''); + $audits ->setParam('userId', $profile->getId()) ->setParam('event', 'account.recovery.create') @@ -1256,12 +1270,13 @@ App::post('/v1/account/recovery') ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($recovery, Response::MODEL_TOKEN) ; - }, ['request', 'response', 'projectDB', 'project', 'locale', 'mails', 'audits']); + }, ['request', 'response', 'projectDB', 'project', 'locale', 'mails', 'audits', 'webhooks', 'mode']); App::put('/v1/account/recovery') ->desc('Complete Password Recovery') ->groups(['api', 'account']) ->label('scope', 'public') + ->label('event', 'account.recovery.update') ->label('sdk.platform', [APP_PLATFORM_CLIENT]) ->label('sdk.namespace', 'account') ->label('sdk.method', 'updateRecovery') @@ -1347,7 +1362,7 @@ App::post('/v1/account/verification') ->label('abuse-limit', 10) ->label('abuse-key', 'url:{url},email:{param-email}') ->param('url', '', function ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients']) // TODO add built-in confirm page - ->action(function ($url, $request, $response, $project, $user, $projectDB, $locale, $audits, $mails) { + ->action(function ($url, $request, $response, $project, $user, $projectDB, $locale, $audits, $webhooks, $mails, $mode) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ @@ -1355,7 +1370,9 @@ App::post('/v1/account/verification') /** @var Appwrite\Database\Database $projectDB */ /** @var Utopia\Locale\Locale $locale */ /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Event $webhooks */ /** @var Appwrite\Event\Event $mails */ + /** @var bool $mode */ $verificationSecret = Auth::tokenGenerator(); @@ -1419,6 +1436,17 @@ App::post('/v1/account/verification') ->trigger() ; + $webhooks + ->setParam('payload', + $response->output($verification->setAttribute('secret', $verificationSecret), + Response::MODEL_TOKEN + )) + ; + + $verification // Hide secret for clients, sp + ->setAttribute('secret', + ((APP_MODE_ADMIN === $mode)) ? $verificationSecret : ''); + $audits ->setParam('userId', $user->getId()) ->setParam('event', 'account.verification.create') @@ -1429,7 +1457,7 @@ App::post('/v1/account/verification') ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($verification, Response::MODEL_TOKEN) ; - }, ['request', 'response', 'project', 'user', 'projectDB', 'locale', 'audits', 'mails']); + }, ['request', 'response', 'project', 'user', 'projectDB', 'locale', 'audits', 'webhooks', 'mails', 'mode']); App::put('/v1/account/verification') ->desc('Complete Email Verification') diff --git a/src/Appwrite/Utopia/Response/Model/Token.php b/src/Appwrite/Utopia/Response/Model/Token.php index cf2347755f..43bb4ba713 100644 --- a/src/Appwrite/Utopia/Response/Model/Token.php +++ b/src/Appwrite/Utopia/Response/Model/Token.php @@ -15,12 +15,12 @@ class Token extends Model 'description' => 'Token ID.', 'example' => '5e5ea5c16897e', ]) - // ->addRule('type', [ TODO: use this when token types will be strings - // 'type' => self::TYPE_STRING, - // 'description' => 'Token type. Possible values: play, pause', - // 'default' => '', - // 'example' => '127.0.0.1', - // ]) + ->addRule('secret', [ + 'type' => self::TYPE_STRING, + 'description' => 'Token secret key. This will return an empty string unless the response is returned using an API key or as part of a webhook payload.', + 'default' => 0, + 'example' => '', + ]) ->addRule('expire', [ 'type' => self::TYPE_INTEGER, 'description' => 'Token expiration date in Unix timestamp.', diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index 0f6044d763..c061986d6a 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -648,6 +648,7 @@ trait AccountBase $this->assertEquals(201, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); + $this->assertEmpty($response['body']['secret']); $this->assertIsNumeric($response['body']['expire']); $lastEmail = $this->getLastEmail(); @@ -939,6 +940,7 @@ trait AccountBase $this->assertEquals(201, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); + $this->assertEmpty($response['body']['secret']); $this->assertIsNumeric($response['body']['expire']); $lastEmail = $this->getLastEmail(); From 2596c2c1f89551aa2624652cbd4c1f80366117f2 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 19 Nov 2020 00:08:01 +0200 Subject: [PATCH 02/36] Added user ID to webhook payload --- app/controllers/general.php | 5 +++-- app/workers/webhooks.php | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index c921816206..ba52b3e3db 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -224,6 +224,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo */ $webhooks ->setParam('projectId', $project->getId()) + ->setParam('userId', $user->getId()) ->setParam('event', $route->getLabel('event', '')) ->setParam('payload', []) ; @@ -283,8 +284,8 @@ App::shutdown(function ($utopia, $request, $response, $project, $webhooks, $audi $route = $utopia->match($request); if ($project->getId() - && $mode !== APP_MODE_ADMIN - && !empty($route->getLabel('sdk.namespace', null))) { // Don't calculate console usage and admin mode + && $mode !== APP_MODE_ADMIN //TODO: add check to make sure user is admin + && !empty($route->getLabel('sdk.namespace', null))) { // Don't calculate console usage on admin mode $usage ->setParam('networkRequestSize', $request->getSize() + $usage->getParam('storage')) diff --git a/app/workers/webhooks.php b/app/workers/webhooks.php index cfbc8a6712..b371419a49 100644 --- a/app/workers/webhooks.php +++ b/app/workers/webhooks.php @@ -34,8 +34,9 @@ class WebhooksV1 $errors = []; // Event - $projectId = $this->args['projectId']; - $event = $this->args['event']; + $projectId = $this->args['projectId'] ?? ''; + $userId = $this->args['userId'] ?? ''; + $event = $this->args['event'] ?? ''; $payload = \json_encode($this->args['payload']); // Webhook From e9c1e8e0b1bc112e22f5483c6dde05e8bf43733e Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 20 Nov 2020 14:35:16 +0200 Subject: [PATCH 03/36] Added new tests --- app/controllers/api/account.php | 20 ++-- tests/e2e/Scopes/ProjectCustom.php | 42 ++++++- tests/e2e/Services/Webhooks/WebhooksBase.php | 104 ++++++++++++++++++ .../Webhooks/WebhooksCustomClientTest.php | 14 +++ .../Webhooks/WebhooksCustomServerTest.php | 14 +++ 5 files changed, 185 insertions(+), 9 deletions(-) create mode 100644 tests/e2e/Services/Webhooks/WebhooksBase.php create mode 100644 tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php create mode 100644 tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index c42ebaa3cf..b6c531150f 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1165,7 +1165,7 @@ App::post('/v1/account/recovery') ->label('abuse-key', 'url:{url},email:{param-email}') ->param('email', '', new Email(), 'User email.') ->param('url', '', function ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients']) - ->action(function ($email, $url, $request, $response, $projectDB, $project, $locale, $mails, $audits, $webhooks, $mode) { + ->action(function ($email, $url, $request, $response, $projectDB, $project, $locale, $mails, $audits, $webhooks) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ @@ -1174,7 +1174,9 @@ App::post('/v1/account/recovery') /** @var Appwrite\Event\Event $mails */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $webhooks */ - /** @var bool $mode */ + + $isPreviliggedUser = Auth::isPreviliggedUser(Authorization::$roles); + $isAppUser = Auth::isAppUser(Authorization::$roles); $profile = $projectDB->getCollectionFirst([ // Get user by email address 'limit' => 1, @@ -1258,7 +1260,7 @@ App::post('/v1/account/recovery') $recovery // Hide secret for clients, sp ->setAttribute('secret', - ((APP_MODE_ADMIN === $mode)) ? $secret : ''); + ($isPreviliggedUser || $isAppUser) ? $secret : ''); $audits ->setParam('userId', $profile->getId()) @@ -1270,7 +1272,7 @@ App::post('/v1/account/recovery') ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($recovery, Response::MODEL_TOKEN) ; - }, ['request', 'response', 'projectDB', 'project', 'locale', 'mails', 'audits', 'webhooks', 'mode']); + }, ['request', 'response', 'projectDB', 'project', 'locale', 'mails', 'audits', 'webhooks']); App::put('/v1/account/recovery') ->desc('Complete Password Recovery') @@ -1362,7 +1364,7 @@ App::post('/v1/account/verification') ->label('abuse-limit', 10) ->label('abuse-key', 'url:{url},email:{param-email}') ->param('url', '', function ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients']) // TODO add built-in confirm page - ->action(function ($url, $request, $response, $project, $user, $projectDB, $locale, $audits, $webhooks, $mails, $mode) { + ->action(function ($url, $request, $response, $project, $user, $projectDB, $locale, $audits, $webhooks, $mails) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ @@ -1372,7 +1374,9 @@ App::post('/v1/account/verification') /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $webhooks */ /** @var Appwrite\Event\Event $mails */ - /** @var bool $mode */ + + $isPreviliggedUser = Auth::isPreviliggedUser(Authorization::$roles); + $isAppUser = Auth::isAppUser(Authorization::$roles); $verificationSecret = Auth::tokenGenerator(); @@ -1445,7 +1449,7 @@ App::post('/v1/account/verification') $verification // Hide secret for clients, sp ->setAttribute('secret', - ((APP_MODE_ADMIN === $mode)) ? $verificationSecret : ''); + ($isPreviliggedUser || $isAppUser) ? $verificationSecret : ''); $audits ->setParam('userId', $user->getId()) @@ -1457,7 +1461,7 @@ App::post('/v1/account/verification') ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($verification, Response::MODEL_TOKEN) ; - }, ['request', 'response', 'project', 'user', 'projectDB', 'locale', 'audits', 'webhooks', 'mails', 'mode']); + }, ['request', 'response', 'project', 'user', 'projectDB', 'locale', 'audits', 'webhooks', 'mails']); App::put('/v1/account/verification') ->desc('Complete Email Verification') diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index 831a0fbb7a..e900e4e74f 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -81,10 +81,50 @@ trait ProjectCustom ], ]); - $this->assertEquals(201, $project['headers']['status-code']); + $this->assertEquals(201, $key['headers']['status-code']); $this->assertNotEmpty($key['body']); $this->assertNotEmpty($key['body']['secret']); + $webhook = $this->client->call(Client::METHOD_POST, '/projects/'.$project['body']['$id'].'/webhooks', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Webhook Test', + 'events' => [ + 'account.create', + 'account.update.email', + 'account.update.name', + 'account.update.password', + 'account.update.prefs', + 'account.recovery.create', + 'account.recovery.update', + 'account.delete', + 'account.sessions.create', + 'account.sessions.delete', + 'database.collections.create', + 'database.collections.update', + 'database.collections.delete', + 'database.documents.create', + 'database.documents.patch', + 'database.documents.delete', + 'storage.files.create', + 'storage.files.update', + 'storage.files.delete', + 'users.create', + 'users.update.status', + 'users.delete', + 'users.sessions.delete', + ], + 'url' => 'http://request-catcher:5000/webhook', + 'security' => false, + 'httpUser' => '', + 'httpPass' => '', + ]); + + $this->assertEquals(201, $webhook['headers']['status-code']); + $this->assertNotEmpty($webhook['body']); + $this->assertNotEmpty($webhook['body']['secret']); + // return [ // 'email' => $this->demoEmail, // 'password' => $this->demoPassword, diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php new file mode 100644 index 0000000000..9221fcbcf1 --- /dev/null +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -0,0 +1,104 @@ +client->call(Client::METHOD_POST, '/storage/files', array_merge([ + 'content-type' => 'multipart/form-data', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'logo.png'), + 'read' => ['*'], + 'write' => ['*'], + 'folderId' => 'xyz', + ]); + + $this->assertEquals($file['headers']['status-code'], 201); + $this->assertNotEmpty($file['body']['$id']); + + $webhook = $this->getLastRequest(); + + var_dump($webhook); + + /** + * Test for FAILURE + */ + return ['fileId' => $file['body']['$id']]; + } + + // /** + // * @depends testCreateFile + // */ + // public function testGetFile(array $data):array + // { + // /** + // * Test for SUCCESS + // */ + // $file1 = $this->client->call(Client::METHOD_GET, '/storage/files/' . $data['fileId'], array_merge([ + // 'content-type' => 'application/json', + // 'x-appwrite-project' => $this->getProject()['$id'], + // ], $this->getHeaders())); + + // $this->assertEquals($file1['headers']['status-code'], 200); + // $this->assertNotEmpty($file1['body']['$id']); + // $this->assertIsInt($file1['body']['dateCreated']); + // $this->assertEquals('logo.png', $file1['body']['name']); + // $this->assertEquals('image/png', $file1['body']['mimeType']); + // $this->assertEquals(47218, $file1['body']['sizeOriginal']); + // //$this->assertEquals(54944, $file1['body']['sizeActual']); + // //$this->assertEquals('gzip', $file1['body']['algorithm']); + // //$this->assertEquals('1', $file1['body']['fileOpenSSLVersion']); + // //$this->assertEquals('aes-128-gcm', $file1['body']['fileOpenSSLCipher']); + // //$this->assertNotEmpty($file1['body']['fileOpenSSLTag']); + // //$this->assertNotEmpty($file1['body']['fileOpenSSLIV']); + // $this->assertIsArray($file1['body']['$permissions']['read']); + // $this->assertIsArray($file1['body']['$permissions']['write']); + // $this->assertCount(1, $file1['body']['$permissions']['read']); + // $this->assertCount(1, $file1['body']['$permissions']['write']); + + // $file2 = $this->client->call(Client::METHOD_GET, '/storage/files/' . $data['fileId'] . '/preview', array_merge([ + // 'content-type' => 'application/json', + // 'x-appwrite-project' => $this->getProject()['$id'], + // ], $this->getHeaders())); + + // $this->assertEquals(200, $file2['headers']['status-code']); + // $this->assertEquals('image/png', $file2['headers']['content-type']); + // $this->assertNotEmpty($file2['body']); + + // $file3 = $this->client->call(Client::METHOD_GET, '/storage/files/' . $data['fileId'] . '/download', array_merge([ + // 'content-type' => 'application/json', + // 'x-appwrite-project' => $this->getProject()['$id'], + // ], $this->getHeaders())); + + // $this->assertEquals(200, $file3['headers']['status-code']); + // $this->assertEquals('attachment; filename="logo.png"', $file3['headers']['content-disposition']); + // $this->assertEquals('image/png', $file3['headers']['content-type']); + // $this->assertNotEmpty($file3['body']); + + // $file4 = $this->client->call(Client::METHOD_GET, '/storage/files/' . $data['fileId'] . '/view', array_merge([ + // 'content-type' => 'application/json', + // 'x-appwrite-project' => $this->getProject()['$id'], + // ], $this->getHeaders())); + + // $this->assertEquals(200, $file4['headers']['status-code']); + // $this->assertEquals('image/png', $file4['headers']['content-type']); + // $this->assertNotEmpty($file4['body']); + + // /** + // * Test for FAILURE + // */ + + // return $data; + // } + +} \ No newline at end of file diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php new file mode 100644 index 0000000000..7bd73bf3bb --- /dev/null +++ b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php @@ -0,0 +1,14 @@ + Date: Fri, 20 Nov 2020 23:02:10 +0200 Subject: [PATCH 04/36] Added missing verification events --- app/config/events.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/config/events.php b/app/config/events.php index d5c946f85d..51a645e336 100644 --- a/app/config/events.php +++ b/app/config/events.php @@ -25,6 +25,12 @@ return [ 'account.recovery.update' => [ 'description' => 'This event triggers when the account recovery token is validated.', ], + 'account.verification.create' => [ + 'description' => 'This event triggers when the account verification token is created.', + ], + 'account.verification.update' => [ + 'description' => 'This event triggers when the account verification token is validated.', + ], 'account.delete' => [ 'description' => 'This event triggers when the account is deleted.', ], From c3d398d2e3fc883c5bcecc0b681b80dc69c3e937 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 20 Nov 2020 23:02:26 +0200 Subject: [PATCH 05/36] Added missing response attributes --- app/controllers/api/account.php | 38 +++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index b6c531150f..d813f5ca82 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -105,6 +105,10 @@ App::post('/v1/account') Authorization::enable(); + Authorization::unsetRole('role:'.Auth::USER_ROLE_GUEST); + Authorization::setRole('user:'.$user->getId()); + Authorization::setRole('role:'.Auth::USER_ROLE_MEMBER); + if (false === $user) { throw new Exception('Failed saving user to DB', 500); } @@ -624,9 +628,7 @@ App::get('/v1/account') /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ - $user - ->setAttribute('roles', Authorization::getRoles()) - ; + $user->setAttribute('roles', Authorization::getRoles()); $response->dynamic($user, Response::MODEL_USER); }, ['response', 'user']); @@ -1011,6 +1013,8 @@ App::delete('/v1/account') ->setParam('data', $user->getArrayCopy()) ; + $user->setAttribute('roles', Authorization::getRoles()); + $webhooks ->setParam('payload', $response->output($user, Response::MODEL_USER)) ; @@ -1069,23 +1073,27 @@ App::delete('/v1/account/sessions/:sessionId') ->setParam('resource', '/user/'.$user->getId()) ; - $webhooks - ->setParam('payload', $response->output($user, Response::MODEL_USER)) - ; - if (!Config::getParam('domainVerification')) { $response ->addHeader('X-Fallback-Cookies', \json_encode([])) ; } + + $token->setAttribute('current', false); if ($token->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too + $token->setAttribute('current', true); + $response ->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ; } + $webhooks + ->setParam('payload', $response->output($token, Response::MODEL_SESSION)) + ; + return $response->noContent(); } } @@ -1127,10 +1135,6 @@ App::delete('/v1/account/sessions') ->setParam('event', 'account.sessions.delete') ->setParam('resource', '/user/'.$user->getId()) ; - - $webhooks - ->setParam('payload', $response->output($user, Response::MODEL_USER)) - ; if (!Config::getParam('domainVerification')) { $response @@ -1138,13 +1142,23 @@ App::delete('/v1/account/sessions') ; } + $token->setAttribute('current', false); + if ($token->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too + $token->setAttribute('current', true); $response ->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ; } } + + $webhooks + ->setParam('payload', $response->output(new Document([ + 'sum' => count($tokens), + 'sessions' => $tokens + ]), Response::MODEL_SESSION_LIST)) + ; $response->noContent(); }, ['request', 'response', 'user', 'projectDB', 'audits', 'webhooks']); @@ -1354,6 +1368,7 @@ App::post('/v1/account/verification') ->desc('Create Email Verification') ->groups(['api', 'account']) ->label('scope', 'account') + ->label('event', 'account.verification.create') ->label('sdk.platform', [APP_PLATFORM_CLIENT]) ->label('sdk.namespace', 'account') ->label('sdk.method', 'createVerification') @@ -1467,6 +1482,7 @@ App::put('/v1/account/verification') ->desc('Complete Email Verification') ->groups(['api', 'account']) ->label('scope', 'public') + ->label('event', 'account.verification.update') ->label('sdk.platform', [APP_PLATFORM_CLIENT]) ->label('sdk.namespace', 'account') ->label('sdk.method', 'updateVerification') From 7a444d5a24ac1cb37b2a3543c64b618dcc852527 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 20 Nov 2020 23:02:42 +0200 Subject: [PATCH 06/36] Fixed default session value --- src/Appwrite/Utopia/Response/Model/Session.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Utopia/Response/Model/Session.php b/src/Appwrite/Utopia/Response/Model/Session.php index a0ca7b7143..38863016d4 100644 --- a/src/Appwrite/Utopia/Response/Model/Session.php +++ b/src/Appwrite/Utopia/Response/Model/Session.php @@ -114,7 +114,7 @@ class Session extends Model ->addRule('current', [ 'type' => self::TYPE_BOOLEAN, 'description' => 'Returns true if this the current user session.', - 'default' => '', + 'default' => false, 'example' => true, ]) ; From 53e2f2fcc1b3d8ccdbc355cb39af93c153e2a4d5 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 20 Nov 2020 23:03:14 +0200 Subject: [PATCH 07/36] Added test webhook --- tests/e2e/Scopes/ProjectCustom.php | 20 +++++++------------- tests/e2e/Scopes/Scope.php | 2 +- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index e900e4e74f..43ae3721e7 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -85,10 +85,12 @@ trait ProjectCustom $this->assertNotEmpty($key['body']); $this->assertNotEmpty($key['body']['secret']); - $webhook = $this->client->call(Client::METHOD_POST, '/projects/'.$project['body']['$id'].'/webhooks', array_merge([ + $webhook = $this->client->call(Client::METHOD_POST, '/projects/'.$project['body']['$id'].'/webhooks', [ + 'origin' => 'http://localhost', 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], + 'x-appwrite-project' => 'console', + ], [ 'name' => 'Webhook Test', 'events' => [ 'account.create', @@ -98,6 +100,8 @@ trait ProjectCustom 'account.update.prefs', 'account.recovery.create', 'account.recovery.update', + 'account.verification.create', + 'account.verification.update', 'account.delete', 'account.sessions.create', 'account.sessions.delete', @@ -123,16 +127,6 @@ trait ProjectCustom $this->assertEquals(201, $webhook['headers']['status-code']); $this->assertNotEmpty($webhook['body']); - $this->assertNotEmpty($webhook['body']['secret']); - - // return [ - // 'email' => $this->demoEmail, - // 'password' => $this->demoPassword, - // 'session' => $session, - // 'projectUid' => $project['body']['$id'], - // 'projectAPIKeySecret' => $key['body']['secret'], - // 'projectSession' => $this->client->parseCookie($user['headers']['set-cookie'])['a_session_' . $project['body']['$id']], - // ]; self::$project = [ '$id' => $project['body']['$id'], diff --git a/tests/e2e/Scopes/Scope.php b/tests/e2e/Scopes/Scope.php index 36832b5d25..fae34a87fb 100644 --- a/tests/e2e/Scopes/Scope.php +++ b/tests/e2e/Scopes/Scope.php @@ -46,7 +46,7 @@ abstract class Scope extends TestCase protected function getLastRequest():array { - sleep(10); + sleep(5); $resquest = json_decode(file_get_contents('http://request-catcher:5000/__last_request__'), true); $resquest['data'] = json_decode($resquest['data'], true); From fe244880b447535dd370a52fceec1b771dd5663b Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 20 Nov 2020 23:03:36 +0200 Subject: [PATCH 08/36] Added unsetRole method --- src/Appwrite/Database/Validator/Authorization.php | 10 ++++++++++ tests/unit/Database/Validator/AuthorizationTest.php | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/src/Appwrite/Database/Validator/Authorization.php b/src/Appwrite/Database/Validator/Authorization.php index 65fe548e75..a668c397f2 100644 --- a/src/Appwrite/Database/Validator/Authorization.php +++ b/src/Appwrite/Database/Validator/Authorization.php @@ -97,6 +97,16 @@ class Authorization extends Validator self::$roles[$role] = true; } + /** + * @param string $role + * + * @return void + */ + public static function unsetRole(string $role): void + { + unset(self::$roles[$role]); + } + /** * @return array */ diff --git a/tests/unit/Database/Validator/AuthorizationTest.php b/tests/unit/Database/Validator/AuthorizationTest.php index e2e9ceac51..d46fec3bc6 100644 --- a/tests/unit/Database/Validator/AuthorizationTest.php +++ b/tests/unit/Database/Validator/AuthorizationTest.php @@ -79,5 +79,12 @@ class AuthorizationTest extends TestCase $this->assertEquals($this->object->isValid($this->document->getPermissions()), false); + Authorization::setRole('textX'); + + $this->assertContains('textX', Authorization::getRoles()); + + Authorization::unsetRole('textX'); + + $this->assertNotContains('textX', Authorization::getRoles()); } } \ No newline at end of file From f3fae632cce6381040f310bf70ed1de1e5b1d14a Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 20 Nov 2020 23:03:48 +0200 Subject: [PATCH 09/36] Added webhook tests --- tests/e2e/Services/Webhooks/WebhooksBase.php | 535 ++++++++++++++++--- 1 file changed, 471 insertions(+), 64 deletions(-) diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index 9221fcbcf1..99a8ff01d0 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -7,9 +7,400 @@ use Tests\E2E\Client; trait WebhooksBase { + public function testCreateAccount():array + { + $email = uniqid().'user@localhost.test'; + $password = 'password'; + $name = 'User Name'; + + /** + * Test for SUCCESS + */ + $account = $this->client->call(Client::METHOD_POST, '/account', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'email' => $email, + 'password' => $password, + 'name' => $name, + ]); + + $id = $account['body']['$id']; + + $this->assertEquals($account['headers']['status-code'], 201); + $this->assertNotEmpty($account['body']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.create'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals($webhook['data']['name'], $name); + $this->assertIsInt($webhook['data']['registration']); + $this->assertEquals($webhook['data']['status'], 0); + $this->assertEquals($webhook['data']['email'], $email); + $this->assertEquals($webhook['data']['emailVerification'], false); + $this->assertEquals($webhook['data']['prefs'], []); + $this->assertEquals($webhook['data']['roles'], ['*', 'user:'.$id, 'role:1']); + + return [ + 'id' => $id, + 'email' => $email, + 'password' => $password, + 'name' => $name, + ]; + } + + /** + * @depends testCreateAccount + */ + public function testCreateAccountSession($data):array + { + $email = $data['email'] ?? ''; + $password = $data['password'] ?? ''; + + /** + * Test for SUCCESS + */ + $accountSession = $this->client->call(Client::METHOD_POST, '/account/sessions', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'email' => $email, + 'password' => $password, + ]); + + $this->assertEquals($accountSession['headers']['status-code'], 201); + + $sessionId = $accountSession['body']['$id']; + $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_'.$this->getProject()['$id']]; + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.sessions.create'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertIsInt($webhook['data']['expire']); + $this->assertEquals($webhook['data']['ip'], '127.0.0.1'); + $this->assertNotEmpty($webhook['data']['osCode']); + $this->assertIsString($webhook['data']['osCode']); + $this->assertNotEmpty($webhook['data']['osName']); + $this->assertIsString($webhook['data']['osName']); + $this->assertNotEmpty($webhook['data']['osVersion']); + $this->assertIsString($webhook['data']['osVersion']); + $this->assertEquals($webhook['data']['clientType'], 'browser'); + $this->assertEquals($webhook['data']['clientCode'], 'CH'); + $this->assertEquals($webhook['data']['clientName'], 'Chrome'); + $this->assertNotEmpty($webhook['data']['clientVersion']); + $this->assertIsString($webhook['data']['clientVersion']); + $this->assertNotEmpty($webhook['data']['clientEngine']); + $this->assertIsString($webhook['data']['clientEngine']); + $this->assertIsString($webhook['data']['clientEngineVersion']); + $this->assertIsString($webhook['data']['deviceName']); + $this->assertIsString($webhook['data']['deviceBrand']); + $this->assertIsString($webhook['data']['deviceModel']); + $this->assertIsString($webhook['data']['countryCode']); + $this->assertIsString($webhook['data']['countryName']); + $this->assertEquals($webhook['data']['current'], true); + + return array_merge($data, [ + 'sessionId' => $sessionId, + 'session' => $session, + ]); + } + + /** + * @depends testCreateAccount + */ + public function testDeleteAccountSession($data):array + { + $email = $data['email'] ?? ''; + $password = $data['password'] ?? ''; + + /** + * Test for SUCCESS + */ + $accountSession = $this->client->call(Client::METHOD_POST, '/account/sessions', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'email' => $email, + 'password' => $password, + ]); + + $sessionId = $accountSession['body']['$id']; + $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_'.$this->getProject()['$id']]; + + $this->assertEquals($accountSession['headers']['status-code'], 201); + + $accountSession = $this->client->call(Client::METHOD_DELETE, '/account/sessions/'.$sessionId, array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ])); + + $this->assertEquals($accountSession['headers']['status-code'], 204); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.sessions.delete'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertIsInt($webhook['data']['expire']); + $this->assertEquals($webhook['data']['ip'], '127.0.0.1'); + $this->assertNotEmpty($webhook['data']['osCode']); + $this->assertIsString($webhook['data']['osCode']); + $this->assertNotEmpty($webhook['data']['osName']); + $this->assertIsString($webhook['data']['osName']); + $this->assertNotEmpty($webhook['data']['osVersion']); + $this->assertIsString($webhook['data']['osVersion']); + $this->assertEquals($webhook['data']['clientType'], 'browser'); + $this->assertEquals($webhook['data']['clientCode'], 'CH'); + $this->assertEquals($webhook['data']['clientName'], 'Chrome'); + $this->assertNotEmpty($webhook['data']['clientVersion']); + $this->assertIsString($webhook['data']['clientVersion']); + $this->assertNotEmpty($webhook['data']['clientEngine']); + $this->assertIsString($webhook['data']['clientEngine']); + $this->assertIsString($webhook['data']['clientEngineVersion']); + $this->assertIsString($webhook['data']['deviceName']); + $this->assertIsString($webhook['data']['deviceBrand']); + $this->assertIsString($webhook['data']['deviceModel']); + $this->assertIsString($webhook['data']['countryCode']); + $this->assertIsString($webhook['data']['countryName']); + $this->assertEquals($webhook['data']['current'], true); + + return $data; + } + + /** + * @depends testCreateAccount + */ + public function testDeleteAccountSessions($data):array + { + $email = $data['email'] ?? ''; + $password = $data['password'] ?? ''; + + /** + * Test for SUCCESS + */ + $accountSession = $this->client->call(Client::METHOD_POST, '/account/sessions', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'email' => $email, + 'password' => $password, + ]); + + $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_'.$this->getProject()['$id']]; + + $this->assertEquals($accountSession['headers']['status-code'], 201); + + $accountSession = $this->client->call(Client::METHOD_DELETE, '/account/sessions', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ])); + + $this->assertEquals($accountSession['headers']['status-code'], 204); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.sessions.delete'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals($webhook['data']['sum'], 2); + $this->assertNotEmpty($webhook['data']['sessions'][1]['$id']); + $this->assertIsInt($webhook['data']['sessions'][1]['expire']); + $this->assertEquals($webhook['data']['sessions'][1]['ip'], '127.0.0.1'); + $this->assertNotEmpty($webhook['data']['sessions'][1]['osCode']); + $this->assertIsString($webhook['data']['sessions'][1]['osCode']); + $this->assertNotEmpty($webhook['data']['sessions'][1]['osName']); + $this->assertIsString($webhook['data']['sessions'][1]['osName']); + $this->assertNotEmpty($webhook['data']['sessions'][1]['osVersion']); + $this->assertIsString($webhook['data']['sessions'][1]['osVersion']); + $this->assertEquals($webhook['data']['sessions'][1]['clientType'], 'browser'); + $this->assertEquals($webhook['data']['sessions'][1]['clientCode'], 'CH'); + $this->assertEquals($webhook['data']['sessions'][1]['clientName'], 'Chrome'); + $this->assertNotEmpty($webhook['data']['sessions'][1]['clientVersion']); + $this->assertIsString($webhook['data']['sessions'][1]['clientVersion']); + $this->assertNotEmpty($webhook['data']['sessions'][1]['clientEngine']); + $this->assertIsString($webhook['data']['sessions'][1]['clientEngine']); + $this->assertIsString($webhook['data']['sessions'][1]['clientEngineVersion']); + $this->assertIsString($webhook['data']['sessions'][1]['deviceName']); + $this->assertIsString($webhook['data']['sessions'][1]['deviceBrand']); + $this->assertIsString($webhook['data']['sessions'][1]['deviceModel']); + $this->assertIsString($webhook['data']['sessions'][1]['countryCode']); + $this->assertIsString($webhook['data']['sessions'][1]['countryName']); + $this->assertEquals($webhook['data']['sessions'][1]['current'], true); + + $accountSession = $this->client->call(Client::METHOD_POST, '/account/sessions', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'email' => $email, + 'password' => $password, + ]); + + $this->assertEquals($accountSession['headers']['status-code'], 201); + + $sessionId = $accountSession['body']['$id']; + $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_'.$this->getProject()['$id']]; + + return array_merge($data, [ + 'sessionId' => $sessionId, + 'session' => $session, + ]); + } + + /** + * @depends testDeleteAccountSessions + */ + public function testUpdateAccountName($data): array + { + $id = $data['id'] ?? ''; + $email = $data['email'] ?? ''; + $session = $data['session'] ?? ''; + $newName = 'New Name'; + + $account = $this->client->call(Client::METHOD_PATCH, '/account/name', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ]), [ + 'name' => $newName + ]); + + $this->assertEquals($account['headers']['status-code'], 200); + $this->assertIsArray($account['body']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.update.name'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals($webhook['data']['name'], $newName); + $this->assertIsInt($webhook['data']['registration']); + $this->assertEquals($webhook['data']['status'], 0); + $this->assertEquals($webhook['data']['email'], $email); + $this->assertEquals($webhook['data']['emailVerification'], false); + $this->assertEquals($webhook['data']['prefs'], []); + $this->assertEquals($webhook['data']['roles'], ['*', 'user:'.$id, 'role:1']); + + return $data; + } + + /** + * @depends testUpdateAccountName + */ + public function testUpdateAccountPassword($data): array + { + $id = $data['id'] ?? ''; + $email = $data['email'] ?? ''; + $password = $data['password'] ?? ''; + $session = $data['session'] ?? ''; + + $account = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ]), [ + 'password' => 'new-password', + 'oldPassword' => $password, + ]); + + $this->assertEquals($account['headers']['status-code'], 200); + $this->assertIsArray($account['body']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.update.password'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals($webhook['data']['name'], 'New Name'); + $this->assertIsInt($webhook['data']['registration']); + $this->assertEquals($webhook['data']['status'], 0); + $this->assertEquals($webhook['data']['email'], $email); + $this->assertEquals($webhook['data']['emailVerification'], false); + $this->assertEquals($webhook['data']['prefs'], []); + $this->assertEquals($webhook['data']['roles'], ['*', 'user:'.$id, 'role:1']); + + $data['password'] = 'new-password'; + + return $data; + } + + /** + * @depends testUpdateAccountPassword + */ + public function testUpdateAccountEmail($data): array + { + $id = $data['id'] ?? ''; + $email = $data['email'] ?? ''; + $newEmail = uniqid().'new@localhost.test'; + $session = $data['session'] ?? ''; + + $account = $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ]), [ + 'email' => $newEmail, + 'password' => 'new-password', + ]); + + $this->assertEquals($account['headers']['status-code'], 200); + $this->assertIsArray($account['body']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.update.email'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals($webhook['data']['name'], 'New Name'); + $this->assertIsInt($webhook['data']['registration']); + $this->assertEquals($webhook['data']['status'], 0); + $this->assertEquals($webhook['data']['email'], $newEmail); + $this->assertEquals($webhook['data']['emailVerification'], false); + $this->assertEquals($webhook['data']['prefs'], []); + $this->assertEquals($webhook['data']['roles'], ['*', 'user:'.$id, 'role:1']); + + $data['email'] = $newEmail; + + return $data; + } + public function testCreateFile():array { - echo 'hello'; /** * Test for SUCCESS */ @@ -25,10 +416,21 @@ trait WebhooksBase $this->assertEquals($file['headers']['status-code'], 201); $this->assertNotEmpty($file['body']['$id']); - + $webhook = $this->getLastRequest(); - var_dump($webhook); + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'storage.files.create'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertIsArray($webhook['data']['$permissions']); + $this->assertEquals($webhook['data']['name'], 'logo.png'); + $this->assertIsInt($webhook['data']['dateCreated'], 'logo.png'); + $this->assertNotEmpty($webhook['data']['signature']); + $this->assertEquals($webhook['data']['mimeType'], 'image/png'); + $this->assertEquals($webhook['data']['sizeOriginal'], 47218); /** * Test for FAILURE @@ -36,69 +438,74 @@ trait WebhooksBase return ['fileId' => $file['body']['$id']]; } - // /** - // * @depends testCreateFile - // */ - // public function testGetFile(array $data):array - // { - // /** - // * Test for SUCCESS - // */ - // $file1 = $this->client->call(Client::METHOD_GET, '/storage/files/' . $data['fileId'], array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders())); + /** + * @depends testCreateFile + */ + public function testUpdateFile(array $data):array + { + /** + * Test for SUCCESS + */ + $file = $this->client->call(Client::METHOD_PUT, '/storage/files/' . $data['fileId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'read' => ['*'], + 'write' => ['*'], + ]); - // $this->assertEquals($file1['headers']['status-code'], 200); - // $this->assertNotEmpty($file1['body']['$id']); - // $this->assertIsInt($file1['body']['dateCreated']); - // $this->assertEquals('logo.png', $file1['body']['name']); - // $this->assertEquals('image/png', $file1['body']['mimeType']); - // $this->assertEquals(47218, $file1['body']['sizeOriginal']); - // //$this->assertEquals(54944, $file1['body']['sizeActual']); - // //$this->assertEquals('gzip', $file1['body']['algorithm']); - // //$this->assertEquals('1', $file1['body']['fileOpenSSLVersion']); - // //$this->assertEquals('aes-128-gcm', $file1['body']['fileOpenSSLCipher']); - // //$this->assertNotEmpty($file1['body']['fileOpenSSLTag']); - // //$this->assertNotEmpty($file1['body']['fileOpenSSLIV']); - // $this->assertIsArray($file1['body']['$permissions']['read']); - // $this->assertIsArray($file1['body']['$permissions']['write']); - // $this->assertCount(1, $file1['body']['$permissions']['read']); - // $this->assertCount(1, $file1['body']['$permissions']['write']); + $this->assertEquals($file['headers']['status-code'], 200); + $this->assertNotEmpty($file['body']['$id']); - // $file2 = $this->client->call(Client::METHOD_GET, '/storage/files/' . $data['fileId'] . '/preview', array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders())); + $webhook = $this->getLastRequest(); - // $this->assertEquals(200, $file2['headers']['status-code']); - // $this->assertEquals('image/png', $file2['headers']['content-type']); - // $this->assertNotEmpty($file2['body']); - - // $file3 = $this->client->call(Client::METHOD_GET, '/storage/files/' . $data['fileId'] . '/download', array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders())); - - // $this->assertEquals(200, $file3['headers']['status-code']); - // $this->assertEquals('attachment; filename="logo.png"', $file3['headers']['content-disposition']); - // $this->assertEquals('image/png', $file3['headers']['content-type']); - // $this->assertNotEmpty($file3['body']); - - // $file4 = $this->client->call(Client::METHOD_GET, '/storage/files/' . $data['fileId'] . '/view', array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders())); - - // $this->assertEquals(200, $file4['headers']['status-code']); - // $this->assertEquals('image/png', $file4['headers']['content-type']); - // $this->assertNotEmpty($file4['body']); - - // /** - // * Test for FAILURE - // */ - - // return $data; - // } + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'storage.files.update'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertIsArray($webhook['data']['$permissions']); + $this->assertEquals($webhook['data']['name'], 'logo.png'); + $this->assertIsInt($webhook['data']['dateCreated'], 'logo.png'); + $this->assertNotEmpty($webhook['data']['signature']); + $this->assertEquals($webhook['data']['mimeType'], 'image/png'); + $this->assertEquals($webhook['data']['sizeOriginal'], 47218); + + return $data; + } + /** + * @depends testUpdateFile + */ + public function testDeleteFile(array $data):array + { + /** + * Test for SUCCESS + */ + $file = $this->client->call(Client::METHOD_DELETE, '/storage/files/' . $data['fileId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(204, $file['headers']['status-code']); + $this->assertEmpty($file['body']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'storage.files.delete'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertIsArray($webhook['data']['$permissions']); + $this->assertEquals($webhook['data']['name'], 'logo.png'); + $this->assertIsInt($webhook['data']['dateCreated'], 'logo.png'); + $this->assertNotEmpty($webhook['data']['signature']); + $this->assertEquals($webhook['data']['mimeType'], 'image/png'); + $this->assertEquals($webhook['data']['sizeOriginal'], 47218); + + return $data; + } } \ No newline at end of file From ffdfc3622956a4045b4b86ac87c85a2518a476c0 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 21 Nov 2020 01:44:00 +0200 Subject: [PATCH 10/36] Skip mail when no SMTP hostname set --- app/workers/mails.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/workers/mails.php b/app/workers/mails.php index e80759cead..dcb5026bda 100644 --- a/app/workers/mails.php +++ b/app/workers/mails.php @@ -1,6 +1,7 @@ args['event']; $from = $this->args['from']; $recipient = $this->args['recipient']; From 1afd9f2c5be9735765bfcb9e7e4c25ef92c71a35 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 21 Nov 2020 08:26:22 +0200 Subject: [PATCH 11/36] Updated log description --- app/workers/mails.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/workers/mails.php b/app/workers/mails.php index dcb5026bda..a7f6f6c235 100644 --- a/app/workers/mails.php +++ b/app/workers/mails.php @@ -25,7 +25,7 @@ class MailsV1 global $register; if(empty(App::getEnv('_APP_SMTP_HOST'))) { - Console::info('Skipped mail. No SMTP server hostname has been set.'); + Console::info('Skipped mail processing. No SMTP server hostname has been set.'); return; } From 25aa12a22de1519d733c679455f15bcb99870b28 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 21 Nov 2020 13:16:10 +0200 Subject: [PATCH 12/36] Upgraded Swoole version --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1503f2a364..d41dbc50fd 100755 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ RUN composer update --ignore-platform-reqs --optimize-autoloader \ FROM php:7.4-cli-alpine as step1 ENV PHP_REDIS_VERSION=5.3.0 \ - PHP_SWOOLE_VERSION=v4.5.6 \ + PHP_SWOOLE_VERSION=v4.5.8 \ PHP_MAXMINDDB_VERSION=v1.8.0 \ PHP_XDEBUG_VERSION=sdebug_2_9-beta From 1a1204ad1ad21d3ae2f8d548268b7001d3ef3a8f Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 21 Nov 2020 13:19:21 +0200 Subject: [PATCH 13/36] Updated changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 9aadc06e46..b90dd2f1ea 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -40,6 +40,7 @@ - Upgraded Traefik image to version 2.3 - Upgraded Redis Docker image to version 6.0 (alpine) - Upgraded Influxdb Docker image to version 1.8 (alpine) +- Added option to disable mail sending by setting empty SMTP host ## Breaking Changes (Read before upgrading!) - **Deprecated** `first` and `last` query params for documents list route in the database API From fc6d7a2e313efaf38fdd82ad59e392a96e2dc064 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 21 Nov 2020 13:21:04 +0200 Subject: [PATCH 14/36] Updated docs --- docs/tutorials/environment-variables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/environment-variables.md b/docs/tutorials/environment-variables.md index 7de4c95e49..3ab2183ff2 100644 --- a/docs/tutorials/environment-variables.md +++ b/docs/tutorials/environment-variables.md @@ -112,7 +112,7 @@ If running in production, it might be easier to use a 3rd party SMTP server as i ### _APP_SMTP_HOST -SMTP server host name address. Default value is: 'smtp' +SMTP server host name address. Default value is: 'smtp'. Pass an empty string to disable all mail sending from the server. ### _APP_SMTP_PORT From beb8a00c741c8ad4c8ef066a5b1998339e45c0f3 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 22 Nov 2020 22:10:51 +0200 Subject: [PATCH 15/36] Added User ID to webhook headers --- app/workers/webhooks.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/workers/webhooks.php b/app/workers/webhooks.php index b371419a49..ad8adee94a 100644 --- a/app/workers/webhooks.php +++ b/app/workers/webhooks.php @@ -81,6 +81,7 @@ class WebhooksV1 'Content-Length: '.\strlen($payload), 'X-'.APP_NAME.'-Webhook-Event: '.$event, 'X-'.APP_NAME.'-Webhook-Name: '.$name, + 'X-'.APP_NAME.'-Webhook-UserID: '.$userId, 'X-'.APP_NAME.'-Webhook-Signature: '.$signature, ] ); From 374e504923e106bf39dabc31c9e962fbb5b29ee6 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 23 Nov 2020 17:04:02 +0200 Subject: [PATCH 16/36] Added more tests --- app/controllers/api/account.php | 18 +-- src/Appwrite/Utopia/Response/Model/User.php | 7 -- tests/e2e/Services/Account/AccountBase.php | 8 +- tests/e2e/Services/Users/UsersBase.php | 2 - tests/e2e/Services/Webhooks/WebhooksBase.php | 49 +++++++- .../Webhooks/WebhooksCustomServerTest.php | 114 ++++++++++++++++++ tests/e2e/Services/Workers/WebhooksTest.php | 1 - 7 files changed, 162 insertions(+), 37 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index d813f5ca82..e97576ee5d 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -119,10 +119,6 @@ App::post('/v1/account') ->setParam('resource', 'users/'.$user->getId()) ; - $user - ->setAttribute('roles', Authorization::getRoles()) - ; - $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($user, Response::MODEL_USER) @@ -628,8 +624,6 @@ App::get('/v1/account') /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ - $user->setAttribute('roles', Authorization::getRoles()); - $response->dynamic($user, Response::MODEL_USER); }, ['response', 'user']); @@ -820,8 +814,6 @@ App::patch('/v1/account/name') throw new Exception('Failed saving user to DB', 500); } - $user->setAttribute('roles', Authorization::getRoles()); - $audits ->setParam('userId', $user->getId()) ->setParam('event', 'account.update.name') @@ -863,8 +855,6 @@ App::patch('/v1/account/password') throw new Exception('Failed saving user to DB', 500); } - $user->setAttribute('roles', Authorization::getRoles()); - $audits ->setParam('userId', $user->getId()) ->setParam('event', 'account.update.password') @@ -920,8 +910,6 @@ App::patch('/v1/account/email') if (false === $user) { throw new Exception('Failed saving user to DB', 500); } - - $user->setAttribute('roles', Authorization::getRoles()); $audits ->setParam('userId', $user->getId()) @@ -964,9 +952,7 @@ App::patch('/v1/account/prefs') ->setParam('resource', 'users/'.$user->getId()) ; - $prefs = $user->getAttribute('prefs', new \stdClass); - - $response->dynamic(new Document($prefs), Response::MODEL_ANY); + $response->dynamic($user, Response::MODEL_USER); }, ['response', 'user', 'projectDB', 'audits']); App::delete('/v1/account') @@ -1013,8 +999,6 @@ App::delete('/v1/account') ->setParam('data', $user->getArrayCopy()) ; - $user->setAttribute('roles', Authorization::getRoles()); - $webhooks ->setParam('payload', $response->output($user, Response::MODEL_USER)) ; diff --git a/src/Appwrite/Utopia/Response/Model/User.php b/src/Appwrite/Utopia/Response/Model/User.php index c89956b3e2..79c34db5ef 100644 --- a/src/Appwrite/Utopia/Response/Model/User.php +++ b/src/Appwrite/Utopia/Response/Model/User.php @@ -47,13 +47,6 @@ class User extends Model 'default' => new \stdClass, 'example' => ['theme' => 'pink', 'timezone' => 'UTC'], ]) - ->addRule('roles', [ - 'type' => self::TYPE_STRING, - 'description' => 'User list of roles', - 'default' => [], - 'example' => '*', - 'array' => true, - ]) ; } diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index c061986d6a..ce93ead1ca 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -152,10 +152,6 @@ trait AccountBase $this->assertIsNumeric($response['body']['registration']); $this->assertEquals($response['body']['email'], $email); $this->assertEquals($response['body']['name'], $name); - $this->assertContains('*', $response['body']['roles']); - $this->assertContains('user:'.$response['body']['$id'], $response['body']['roles']); - $this->assertContains('role:1', $response['body']['roles']); - $this->assertCount(3, $response['body']['roles']); /** * Test for FAILURE @@ -573,8 +569,8 @@ trait AccountBase $this->assertIsArray($response['body']); $this->assertNotEmpty($response['body']); $this->assertNotEmpty($response['body']); - $this->assertEquals('prefValue1', $response['body']['prefKey1']); - $this->assertEquals('prefValue2', $response['body']['prefKey2']); + $this->assertEquals('prefValue1', $response['body']['prefs']['prefKey1']); + $this->assertEquals('prefValue2', $response['body']['prefs']['prefKey2']); /** * Test for FAILURE diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index 747fd53916..14805273e6 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -25,7 +25,6 @@ trait UsersBase $this->assertEquals($user['body']['email'], 'users.service@example.com'); $this->assertEquals($user['body']['status'], 0); $this->assertGreaterThan(0, $user['body']['registration']); - $this->assertIsArray($user['body']['roles']); return ['userId' => $user['body']['$id']]; } @@ -48,7 +47,6 @@ trait UsersBase $this->assertEquals($user['body']['email'], 'users.service@example.com'); $this->assertEquals($user['body']['status'], 0); $this->assertGreaterThan(0, $user['body']['registration']); - $this->assertIsArray($user['body']['roles']); $sessions = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/sessions', array_merge([ 'content-type' => 'application/json', diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index 99a8ff01d0..a4f0c9e5dc 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -45,7 +45,6 @@ trait WebhooksBase $this->assertEquals($webhook['data']['email'], $email); $this->assertEquals($webhook['data']['emailVerification'], false); $this->assertEquals($webhook['data']['prefs'], []); - $this->assertEquals($webhook['data']['roles'], ['*', 'user:'.$id, 'role:1']); return [ 'id' => $id, @@ -306,7 +305,6 @@ trait WebhooksBase $this->assertEquals($webhook['data']['email'], $email); $this->assertEquals($webhook['data']['emailVerification'], false); $this->assertEquals($webhook['data']['prefs'], []); - $this->assertEquals($webhook['data']['roles'], ['*', 'user:'.$id, 'role:1']); return $data; } @@ -348,7 +346,6 @@ trait WebhooksBase $this->assertEquals($webhook['data']['email'], $email); $this->assertEquals($webhook['data']['emailVerification'], false); $this->assertEquals($webhook['data']['prefs'], []); - $this->assertEquals($webhook['data']['roles'], ['*', 'user:'.$id, 'role:1']); $data['password'] = 'new-password'; @@ -392,13 +389,57 @@ trait WebhooksBase $this->assertEquals($webhook['data']['email'], $newEmail); $this->assertEquals($webhook['data']['emailVerification'], false); $this->assertEquals($webhook['data']['prefs'], []); - $this->assertEquals($webhook['data']['roles'], ['*', 'user:'.$id, 'role:1']); $data['email'] = $newEmail; return $data; } + /** + * @depends testUpdateAccountEmail + */ + public function testUpdateAccountPrefs($data): array + { + $id = $data['id'] ?? ''; + $email = $data['email'] ?? ''; + $session = $data['session'] ?? ''; + + $account = $this->client->call(Client::METHOD_PATCH, '/account/prefs', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ]), [ + 'prefs' => [ + 'prefKey1' => 'prefValue1', + 'prefKey2' => 'prefValue2', + ] + ]); + + $this->assertEquals($account['headers']['status-code'], 200); + $this->assertIsArray($account['body']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.update.prefs'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals($webhook['data']['name'], 'New Name'); + $this->assertIsInt($webhook['data']['registration']); + $this->assertEquals($webhook['data']['status'], 0); + $this->assertEquals($webhook['data']['email'], $email); + $this->assertEquals($webhook['data']['emailVerification'], false); + $this->assertEquals($webhook['data']['prefs'], [ + 'prefKey1' => 'prefValue1', + 'prefKey2' => 'prefValue2', + ]); + + return $data; + } + public function testCreateFile():array { /** diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php index 68b36e9d14..91446d175c 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php @@ -2,6 +2,7 @@ namespace Tests\E2E\Services\Webhooks; +use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideServer; @@ -11,4 +12,117 @@ class WebhooksCustomServerTest extends Scope use WebhooksBase; use ProjectCustom; use SideServer; + + public function testCreateUser():array + { + $email = uniqid().'user@localhost.test'; + $password = 'password'; + $name = 'User Name'; + + /** + * Test for SUCCESS + */ + $user = $this->client->call(Client::METHOD_POST, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'email' => $email, + 'password' => $password, + 'name' => $name, + ]); + + $this->assertEquals($user['headers']['status-code'], 201); + $this->assertNotEmpty($user['body']['$id']); + + $id = $user['body']['$id']; + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'users.create'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals($webhook['data']['name'], $name); + $this->assertIsInt($webhook['data']['registration']); + $this->assertEquals($webhook['data']['status'], 0); + $this->assertEquals($webhook['data']['email'], $email); + $this->assertEquals($webhook['data']['emailVerification'], false); + $this->assertEquals($webhook['data']['prefs'], []); + + /** + * Test for FAILURE + */ + return ['userId' => $user['body']['$id'], 'name' => $user['body']['name'], 'email' => $user['body']['email']]; + } + + /** + * @depends testCreateUser + */ + public function testUpdateUserStatus(array $data):array + { + /** + * Test for SUCCESS + */ + $user = $this->client->call(Client::METHOD_PATCH, '/users/' . $data['userId'] . '/status', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'status' => 2, + ]); + + $this->assertEquals($user['headers']['status-code'], 200); + $this->assertNotEmpty($user['body']['$id']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'users.update.status'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals($webhook['data']['name'], $data['name']); + $this->assertIsInt($webhook['data']['registration']); + $this->assertEquals($webhook['data']['status'], 2); + $this->assertEquals($webhook['data']['email'], $data['email']); + $this->assertEquals($webhook['data']['emailVerification'], false); + $this->assertEquals($webhook['data']['prefs'], []); + + return $data; + } + + /** + * @depends testUpdateUserStatus + */ + public function testDeleteUser(array $data):array + { + /** + * Test for SUCCESS + */ + $user = $this->client->call(Client::METHOD_DELETE, '/users/' . $data['userId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals($user['headers']['status-code'], 204); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'users.delete'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals($webhook['data']['name'], $data['name']); + $this->assertIsInt($webhook['data']['registration']); + $this->assertEquals($webhook['data']['status'], 2); + $this->assertEquals($webhook['data']['email'], $data['email']); + $this->assertEquals($webhook['data']['emailVerification'], false); + $this->assertEquals($webhook['data']['prefs'], []); + + return $data; + } } \ No newline at end of file diff --git a/tests/e2e/Services/Workers/WebhooksTest.php b/tests/e2e/Services/Workers/WebhooksTest.php index 67c51785cd..ced80832ae 100644 --- a/tests/e2e/Services/Workers/WebhooksTest.php +++ b/tests/e2e/Services/Workers/WebhooksTest.php @@ -147,6 +147,5 @@ class WebhooksTest extends Scope $this->assertEquals($webhook['data']['name'], $name); $this->assertIsBool($webhook['data']['emailVerification']); $this->assertIsArray($webhook['data']['prefs']); - $this->assertIsArray($webhook['data']['roles']); } } \ No newline at end of file From 4e46a8c160e9fbca618671d9c74c90f3d77d4d75 Mon Sep 17 00:00:00 2001 From: Lior Haim <51369094+Glide7@users.noreply.github.com> Date: Tue, 24 Nov 2020 09:33:08 +0200 Subject: [PATCH 17/36] Update CHANGES.md Updated changelog to v0.7 --- CHANGES.md | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index b90dd2f1ea..5191bf4fa3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,13 +4,29 @@ - New route in Locale API to fetch a list of languages (@TorstenDittmann) - Added Google Fonts to Appwrite for offline availability +- Added response to /locale/languages API with a list of languages (@TorstenDittmann ,[#351](https://github.com/appwrite/appwrite/issues/351)) +- Added response to /locale/languages API with a list of languages (@TorstenDittmann ,[#351](https://github.com/appwrite/appwrite/issues/351)) +- Added API response payload structure info and examples to the docs site ([#381](https://github.com/appwrite/appwrite/issues/381)) +- Added Google Fonts to Appwrite for offline availability +- Added a new route in the Avatars API to get user initials avatar ([#386](https://github.com/appwrite/appwrite/issues/386)) +- Added option to delete team from the console ([#380](https://github.com/appwrite/appwrite/issues/380)) +- Added option to view team members from the console ([#378](https://github.com/appwrite/appwrite/issues/378)) +- Add option to assign new team members to a team from the console and the API ([#379](https://github.com/appwrite/appwrite/issues/379)) +- Added support for Brotli compression (@PedroCisnerosSantana, @Rohitub222, [#310](https://github.com/appwrite/appwrite/issues/310)) - Added a new route in the Avatars API to get user initials avatar - Added option to delete team from the console +- Added Select All Checkbox for on Console API key Scopes Screen ([#477](https://github.com/appwrite/appwrite/issues/477)) +- Added pagination and search for team memberships route ([#387](https://github.com/appwrite/appwrite/issues/387)) +- UI performance & accessibility improvements ([#406](https://github.com/appwrite/appwrite/pull/406)) - Added option to view team members from the console - Added option to join a user to any team from the console - Added option to delete user from the API (@TorstenDittmann - #378) - Added option to delete user from the console (@PineappleIOnic - #538) -- Added support for Brotli compression (@PedroCisnerosSantana, @Rohitub222) +- Created lazy deletion of data worker ([#521](https://github.com/appwrite/appwrite/issues/521)) +- All emails are now sent asynchronously for improved performance (@TorstenDittmann ,[#402](https://github.com/appwrite/appwrite/pull/402)) +- Updated grid for OAuth2 providers list in the console ([#413](https://github.com/appwrite/appwrite/issues/413)) +- Upgraded Redis Resque queue library to version 1.3.6 ([#319](https://github.com/appwrite/appwrite/issues/319)) +- Moved all Appwrite container logs to STDOUT & STDERR ([#389](https://github.com/appwrite/appwrite/issues/389)) - New UI micro-interactions and CSS fixes (@AnatoleLucet) - UI performance & accessibility improvements (#406) - New Doctor CLI to debug the Appwrite server ([#415](https://github.com/appwrite/appwrite/issues/415)) @@ -41,6 +57,7 @@ - Upgraded Redis Docker image to version 6.0 (alpine) - Upgraded Influxdb Docker image to version 1.8 (alpine) - Added option to disable mail sending by setting empty SMTP host +- Upgraded installation script ([#490](https://github.com/appwrite/appwrite/issues/490)) ## Breaking Changes (Read before upgrading!) - **Deprecated** `first` and `last` query params for documents list route in the database API @@ -52,6 +69,17 @@ ## Bug Fixes +- Fixed an issue where Special characters in _APP_OPENSSL_KEY_V1_ env caused an error ([#732](https://github.com/appwrite/appwrite/issues/732)) +- Fixed an issue where Account webhook doesn't trigger through the console ([#493](https://github.com/appwrite/appwrite/issues/493)) +- Fixed case sensitive country flag code ([#526](https://github.com/appwrite/appwrite/issues/526)) +- Fixed redirect to Appwrite login page when deep link is provided ([#427](https://github.com/appwrite/appwrite/issues/427)) +- Fixed an issue where Creating documents fails for parent documents would result in an error ([#514](https://github.com/appwrite/appwrite/issues/514)) +- Fixed an issue with Email Sending Problem using external smtp ([#707](https://github.com/appwrite/appwrite/issues/707)) +- Fixed an issue where you could not remove a key from User Prefs ([#316](https://github.com/appwrite/appwrite/issues/316)) +- Fixed an issue where events are not fully visible in the console ([#492](https://github.com/appwrite/appwrite/issues/492)) +- Fixed an issue where UI would wrongly validate integers ([#394](https://github.com/appwrite/appwrite/issues/394)) +- Fixed an issue where graphs were cut in mobile view ([#376](https://github.com/appwrite/appwrite/issues/376)) +- Fixed URL issue where console/ would not display list of projects ([#372](https://github.com/appwrite/appwrite/issues/372)) - Fixed output of /v1/health/queue/certificates returning wrong data - Fixed bug where team members count was wrong in some cases - Fixed network calculation for uploaded files From 8d41590596f2732185db5e0af11fed799c3b876a Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 24 Nov 2020 22:54:37 +0200 Subject: [PATCH 18/36] Added more tests --- tests/e2e/Services/Account/AccountBase.php | 8 +- tests/e2e/Services/Webhooks/WebhooksBase.php | 155 +++++++++++++++++++ 2 files changed, 159 insertions(+), 4 deletions(-) diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index ce93ead1ca..7bd96682d4 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -658,24 +658,24 @@ trait AccountBase /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_POST, '/account/recovery', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, ]), [ - 'url' => 'localhost/recovery', + 'url' => 'localhost/verification', ]); $this->assertEquals(400, $response['headers']['status-code']); - $response = $this->client->call(Client::METHOD_POST, '/account/recovery', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, ]), [ - 'url' => 'http://remotehost/recovery', + 'url' => 'http://remotehost/verification', ]); $this->assertEquals(400, $response['headers']['status-code']); diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index a4f0c9e5dc..98109b34a0 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -440,6 +440,161 @@ trait WebhooksBase return $data; } + /** + * @depends testUpdateAccountPrefs + */ + public function testCreateAccountRecovery($data): array + { + $id = $data['id'] ?? ''; + $email = $data['email'] ?? ''; + $session = $data['session'] ?? ''; + + $recovery = $this->client->call(Client::METHOD_POST, '/account/recovery', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'email' => $email, + 'url' => 'http://localhost/recovery', + ]); + + $this->assertEquals(201, $recovery['headers']['status-code']); + $this->assertIsArray($recovery['body']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.recovery.create'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertNotEmpty($webhook['data']['secret']); + $this->assertIsNumeric($webhook['data']['expire']); + + $data['secret'] = $webhook['data']['secret']; + + return $data; + } + + /** + * @depends testCreateAccountRecovery + */ + public function testUpdateAccountRecovery($data): array + { + $id = $data['id'] ?? ''; + $email = $data['email'] ?? ''; + $session = $data['session'] ?? ''; + $secret = $data['secret'] ?? ''; + $password = 'newPassowrd2'; + + $recovery = $this->client->call(Client::METHOD_PUT, '/account/recovery', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'userId' => $id, + 'secret' => $secret, + 'password' => $password, + 'passwordAgain' => $password, + ]); + + $this->assertEquals(200, $recovery['headers']['status-code']); + $this->assertIsArray($recovery['body']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.recovery.update'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertNotEmpty($webhook['data']['secret']); + $this->assertIsNumeric($webhook['data']['expire']); + + $data['secret'] = $webhook['data']['secret']; + + return $data; + } + + /** + * @depends testUpdateAccountPrefs + */ + public function testCreateAccountVerification($data): array + { + $id = $data['id'] ?? ''; + $email = $data['email'] ?? ''; + $session = $data['session'] ?? ''; + + $verification = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + + ]), [ + 'url' => 'http://localhost/verification', + ]); + + $this->assertEquals(201, $verification['headers']['status-code']); + $this->assertIsArray($verification['body']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.verification.create'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertNotEmpty($webhook['data']['secret']); + $this->assertIsNumeric($webhook['data']['expire']); + + $data['secret'] = $webhook['data']['secret']; + + return $data; + } + + /** + * @depends testCreateAccountVerification + */ + public function testUpdateAccountVerification($data): array + { + $id = $data['id'] ?? ''; + $email = $data['email'] ?? ''; + $session = $data['session'] ?? ''; + $secret = $data['secret'] ?? ''; + + $verification = $this->client->call(Client::METHOD_PUT, '/account/verification', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ]), [ + 'userId' => $id, + 'secret' => $secret, + ]); + + $this->assertEquals(200, $verification['headers']['status-code']); + $this->assertIsArray($verification['body']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.verification.update'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertNotEmpty($webhook['data']['secret']); + $this->assertIsNumeric($webhook['data']['expire']); + + $data['secret'] = $webhook['data']['secret']; + + return $data; + } + public function testCreateFile():array { /** From 362f84e51b6ead3093de7c2232b19da64feca530 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 26 Nov 2020 08:12:24 +0200 Subject: [PATCH 19/36] Added missing user ID to both session and token models --- app/config/collections.php | 9 +++++++++ app/controllers/api/account.php | 7 ++++--- app/controllers/api/teams.php | 1 + app/controllers/general.php | 2 +- src/Appwrite/Utopia/Response/Model/Session.php | 5 +++++ src/Appwrite/Utopia/Response/Model/Token.php | 7 ++++++- 6 files changed, 26 insertions(+), 5 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index 408e713b7d..a9451d4a5b 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -300,6 +300,15 @@ $collections = [ 'name' => 'Token', 'structure' => true, 'rules' => [ + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'User ID', + 'key' => 'userId', + 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + 'default' => null, + 'required' => false, + 'array' => false, + ], [ '$collection' => Database::SYSTEM_COLLECTION_RULES, 'label' => 'Type', diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index e97576ee5d..4e9d10ba0a 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -190,12 +190,12 @@ App::post('/v1/account/sessions') $session = new Document([ '$collection' => Database::SYSTEM_COLLECTION_TOKENS, '$permissions' => ['read' => ['user:'.$profile->getId()], 'write' => ['user:'.$profile->getId()]], + 'userId' => $profile->getId(), 'type' => Auth::TOKEN_TYPE_LOGIN, 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'expire' => $expiry, 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), - 'osCode' => $osCode, 'osName' => $osName, 'osVersion' => $osVersion, @@ -505,7 +505,6 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') // Create session token, verify user account and update OAuth2 ID and Access Token - $dd = new DeviceDetector($request->getUserAgent('UNKNOWN')); $dd->parse(); @@ -528,12 +527,12 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $session = new Document([ '$collection' => Database::SYSTEM_COLLECTION_TOKENS, '$permissions' => ['read' => ['user:'.$user['$id']], 'write' => ['user:'.$user['$id']]], + 'userId' => $user->getId(), 'type' => Auth::TOKEN_TYPE_LOGIN, 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'expire' => $expiry, 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), - 'osCode' => $osCode, 'osName' => $osName, 'osVersion' => $osVersion, @@ -1192,6 +1191,7 @@ App::post('/v1/account/recovery') $recovery = new Document([ '$collection' => Database::SYSTEM_COLLECTION_TOKENS, '$permissions' => ['read' => ['user:'.$profile->getId()], 'write' => ['user:'.$profile->getId()]], + 'userId' => $profile->getId(), 'type' => Auth::TOKEN_TYPE_RECOVERY, 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'expire' => \time() + Auth::TOKEN_EXPIRATION_RECOVERY, @@ -1382,6 +1382,7 @@ App::post('/v1/account/verification') $verification = new Document([ '$collection' => Database::SYSTEM_COLLECTION_TOKENS, '$permissions' => ['read' => ['user:'.$user->getId()], 'write' => ['user:'.$user->getId()]], + 'userId' => $user->getId(), 'type' => Auth::TOKEN_TYPE_VERIFICATION, 'secret' => Auth::hash($verificationSecret), // One way hash encryption to protect DB leak 'expire' => \time() + Auth::TOKEN_EXPIRATION_CONFIRM, diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 32d77e5e1d..37fbad3352 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -581,6 +581,7 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status') $session = new Document([ '$collection' => Database::SYSTEM_COLLECTION_TOKENS, '$permissions' => ['read' => ['user:'.$user->getId()], 'write' => ['user:'.$user->getId()]], + 'userId' => $user->getId(), 'type' => Auth::TOKEN_TYPE_LOGIN, 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'expire' => $expiry, diff --git a/app/controllers/general.php b/app/controllers/general.php index 219a4efaef..9f61db6eec 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -170,7 +170,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo */ if (null !== $key && $user->isEmpty()) { $user = new Document([ - '$id' => 0, + '$id' => '', 'status' => Auth::USER_STATUS_ACTIVATED, 'email' => 'app.'.$project->getId().'@service.'.$request->getHostname(), 'password' => '', diff --git a/src/Appwrite/Utopia/Response/Model/Session.php b/src/Appwrite/Utopia/Response/Model/Session.php index 38863016d4..01cd23ee26 100644 --- a/src/Appwrite/Utopia/Response/Model/Session.php +++ b/src/Appwrite/Utopia/Response/Model/Session.php @@ -15,6 +15,11 @@ class Session extends Model 'description' => 'Session ID.', 'example' => '5e5ea5c16897e', ]) + ->addRule('userId', [ + 'type' => self::TYPE_STRING, + 'description' => 'User ID.', + 'example' => '5e5bb8c16897e', + ]) ->addRule('expire', [ 'type' => self::TYPE_INTEGER, 'description' => 'Session expiration date in Unix timestamp.', diff --git a/src/Appwrite/Utopia/Response/Model/Token.php b/src/Appwrite/Utopia/Response/Model/Token.php index 43bb4ba713..9d00fe6f91 100644 --- a/src/Appwrite/Utopia/Response/Model/Token.php +++ b/src/Appwrite/Utopia/Response/Model/Token.php @@ -13,7 +13,12 @@ class Token extends Model ->addRule('$id', [ 'type' => self::TYPE_STRING, 'description' => 'Token ID.', - 'example' => '5e5ea5c16897e', + 'example' => 'bb8ea5c16897e', + ]) + ->addRule('userId', [ + 'type' => self::TYPE_STRING, + 'description' => 'User ID.', + 'example' => '5e5ea5c168bb8', ]) ->addRule('secret', [ 'type' => self::TYPE_STRING, From 47a5a4a8ed2a4f8a85b6b75107b2abfcc12b7ba0 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 26 Nov 2020 08:12:51 +0200 Subject: [PATCH 20/36] Fixed userid header name standard --- app/workers/webhooks.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/workers/webhooks.php b/app/workers/webhooks.php index ad8adee94a..f51dfbbc8a 100644 --- a/app/workers/webhooks.php +++ b/app/workers/webhooks.php @@ -81,7 +81,7 @@ class WebhooksV1 'Content-Length: '.\strlen($payload), 'X-'.APP_NAME.'-Webhook-Event: '.$event, 'X-'.APP_NAME.'-Webhook-Name: '.$name, - 'X-'.APP_NAME.'-Webhook-UserID: '.$userId, + 'X-'.APP_NAME.'-Webhook-Userid: '.$userId, 'X-'.APP_NAME.'-Webhook-Signature: '.$signature, ] ); From 07f50e66506419902fe3b16985dc712572813b05 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 26 Nov 2020 08:31:59 +0200 Subject: [PATCH 21/36] Updated tests --- tests/e2e/Scopes/SideClient.php | 8 + tests/e2e/Scopes/SideNone.php | 8 + tests/e2e/Scopes/SideServer.php | 8 + tests/e2e/Services/Webhooks/WebhooksBase.php | 591 +---------------- .../Webhooks/WebhooksCustomClientTest.php | 607 ++++++++++++++++++ .../Webhooks/WebhooksCustomServerTest.php | 3 + 6 files changed, 637 insertions(+), 588 deletions(-) diff --git a/tests/e2e/Scopes/SideClient.php b/tests/e2e/Scopes/SideClient.php index 5322bcdcdd..acf79a3721 100644 --- a/tests/e2e/Scopes/SideClient.php +++ b/tests/e2e/Scopes/SideClient.php @@ -11,4 +11,12 @@ trait SideClient 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $this->getUser()['session'], ]; } + + /** + * @return string + */ + public function getSide() + { + return 'client'; + } } diff --git a/tests/e2e/Scopes/SideNone.php b/tests/e2e/Scopes/SideNone.php index 5fe002923b..41522d0353 100644 --- a/tests/e2e/Scopes/SideNone.php +++ b/tests/e2e/Scopes/SideNone.php @@ -8,4 +8,12 @@ trait SideNone { return []; } + + /** + * @return string + */ + public function getSide() + { + return 'none'; + } } diff --git a/tests/e2e/Scopes/SideServer.php b/tests/e2e/Scopes/SideServer.php index 5da9b3311d..a979b25b07 100644 --- a/tests/e2e/Scopes/SideServer.php +++ b/tests/e2e/Scopes/SideServer.php @@ -15,4 +15,12 @@ trait SideServer 'x-appwrite-key' => $this->getProject()['apiKey'] ]; } + + /** + * @return string + */ + public function getSide() + { + return 'server'; + } } diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index 98109b34a0..35eb7547df 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -7,594 +7,6 @@ use Tests\E2E\Client; trait WebhooksBase { - public function testCreateAccount():array - { - $email = uniqid().'user@localhost.test'; - $password = 'password'; - $name = 'User Name'; - - /** - * Test for SUCCESS - */ - $account = $this->client->call(Client::METHOD_POST, '/account', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'email' => $email, - 'password' => $password, - 'name' => $name, - ]); - - $id = $account['body']['$id']; - - $this->assertEquals($account['headers']['status-code'], 201); - $this->assertNotEmpty($account['body']); - - $webhook = $this->getLastRequest(); - - $this->assertEquals($webhook['method'], 'POST'); - $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); - $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.create'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertNotEmpty($webhook['data']['$id']); - $this->assertEquals($webhook['data']['name'], $name); - $this->assertIsInt($webhook['data']['registration']); - $this->assertEquals($webhook['data']['status'], 0); - $this->assertEquals($webhook['data']['email'], $email); - $this->assertEquals($webhook['data']['emailVerification'], false); - $this->assertEquals($webhook['data']['prefs'], []); - - return [ - 'id' => $id, - 'email' => $email, - 'password' => $password, - 'name' => $name, - ]; - } - - /** - * @depends testCreateAccount - */ - public function testCreateAccountSession($data):array - { - $email = $data['email'] ?? ''; - $password = $data['password'] ?? ''; - - /** - * Test for SUCCESS - */ - $accountSession = $this->client->call(Client::METHOD_POST, '/account/sessions', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'email' => $email, - 'password' => $password, - ]); - - $this->assertEquals($accountSession['headers']['status-code'], 201); - - $sessionId = $accountSession['body']['$id']; - $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_'.$this->getProject()['$id']]; - - $webhook = $this->getLastRequest(); - - $this->assertEquals($webhook['method'], 'POST'); - $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); - $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.sessions.create'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertNotEmpty($webhook['data']['$id']); - $this->assertIsInt($webhook['data']['expire']); - $this->assertEquals($webhook['data']['ip'], '127.0.0.1'); - $this->assertNotEmpty($webhook['data']['osCode']); - $this->assertIsString($webhook['data']['osCode']); - $this->assertNotEmpty($webhook['data']['osName']); - $this->assertIsString($webhook['data']['osName']); - $this->assertNotEmpty($webhook['data']['osVersion']); - $this->assertIsString($webhook['data']['osVersion']); - $this->assertEquals($webhook['data']['clientType'], 'browser'); - $this->assertEquals($webhook['data']['clientCode'], 'CH'); - $this->assertEquals($webhook['data']['clientName'], 'Chrome'); - $this->assertNotEmpty($webhook['data']['clientVersion']); - $this->assertIsString($webhook['data']['clientVersion']); - $this->assertNotEmpty($webhook['data']['clientEngine']); - $this->assertIsString($webhook['data']['clientEngine']); - $this->assertIsString($webhook['data']['clientEngineVersion']); - $this->assertIsString($webhook['data']['deviceName']); - $this->assertIsString($webhook['data']['deviceBrand']); - $this->assertIsString($webhook['data']['deviceModel']); - $this->assertIsString($webhook['data']['countryCode']); - $this->assertIsString($webhook['data']['countryName']); - $this->assertEquals($webhook['data']['current'], true); - - return array_merge($data, [ - 'sessionId' => $sessionId, - 'session' => $session, - ]); - } - - /** - * @depends testCreateAccount - */ - public function testDeleteAccountSession($data):array - { - $email = $data['email'] ?? ''; - $password = $data['password'] ?? ''; - - /** - * Test for SUCCESS - */ - $accountSession = $this->client->call(Client::METHOD_POST, '/account/sessions', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'email' => $email, - 'password' => $password, - ]); - - $sessionId = $accountSession['body']['$id']; - $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_'.$this->getProject()['$id']]; - - $this->assertEquals($accountSession['headers']['status-code'], 201); - - $accountSession = $this->client->call(Client::METHOD_DELETE, '/account/sessions/'.$sessionId, array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, - ])); - - $this->assertEquals($accountSession['headers']['status-code'], 204); - - $webhook = $this->getLastRequest(); - - $this->assertEquals($webhook['method'], 'POST'); - $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); - $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.sessions.delete'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertNotEmpty($webhook['data']['$id']); - $this->assertIsInt($webhook['data']['expire']); - $this->assertEquals($webhook['data']['ip'], '127.0.0.1'); - $this->assertNotEmpty($webhook['data']['osCode']); - $this->assertIsString($webhook['data']['osCode']); - $this->assertNotEmpty($webhook['data']['osName']); - $this->assertIsString($webhook['data']['osName']); - $this->assertNotEmpty($webhook['data']['osVersion']); - $this->assertIsString($webhook['data']['osVersion']); - $this->assertEquals($webhook['data']['clientType'], 'browser'); - $this->assertEquals($webhook['data']['clientCode'], 'CH'); - $this->assertEquals($webhook['data']['clientName'], 'Chrome'); - $this->assertNotEmpty($webhook['data']['clientVersion']); - $this->assertIsString($webhook['data']['clientVersion']); - $this->assertNotEmpty($webhook['data']['clientEngine']); - $this->assertIsString($webhook['data']['clientEngine']); - $this->assertIsString($webhook['data']['clientEngineVersion']); - $this->assertIsString($webhook['data']['deviceName']); - $this->assertIsString($webhook['data']['deviceBrand']); - $this->assertIsString($webhook['data']['deviceModel']); - $this->assertIsString($webhook['data']['countryCode']); - $this->assertIsString($webhook['data']['countryName']); - $this->assertEquals($webhook['data']['current'], true); - - return $data; - } - - /** - * @depends testCreateAccount - */ - public function testDeleteAccountSessions($data):array - { - $email = $data['email'] ?? ''; - $password = $data['password'] ?? ''; - - /** - * Test for SUCCESS - */ - $accountSession = $this->client->call(Client::METHOD_POST, '/account/sessions', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'email' => $email, - 'password' => $password, - ]); - - $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_'.$this->getProject()['$id']]; - - $this->assertEquals($accountSession['headers']['status-code'], 201); - - $accountSession = $this->client->call(Client::METHOD_DELETE, '/account/sessions', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, - ])); - - $this->assertEquals($accountSession['headers']['status-code'], 204); - - $webhook = $this->getLastRequest(); - - $this->assertEquals($webhook['method'], 'POST'); - $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); - $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.sessions.delete'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals($webhook['data']['sum'], 2); - $this->assertNotEmpty($webhook['data']['sessions'][1]['$id']); - $this->assertIsInt($webhook['data']['sessions'][1]['expire']); - $this->assertEquals($webhook['data']['sessions'][1]['ip'], '127.0.0.1'); - $this->assertNotEmpty($webhook['data']['sessions'][1]['osCode']); - $this->assertIsString($webhook['data']['sessions'][1]['osCode']); - $this->assertNotEmpty($webhook['data']['sessions'][1]['osName']); - $this->assertIsString($webhook['data']['sessions'][1]['osName']); - $this->assertNotEmpty($webhook['data']['sessions'][1]['osVersion']); - $this->assertIsString($webhook['data']['sessions'][1]['osVersion']); - $this->assertEquals($webhook['data']['sessions'][1]['clientType'], 'browser'); - $this->assertEquals($webhook['data']['sessions'][1]['clientCode'], 'CH'); - $this->assertEquals($webhook['data']['sessions'][1]['clientName'], 'Chrome'); - $this->assertNotEmpty($webhook['data']['sessions'][1]['clientVersion']); - $this->assertIsString($webhook['data']['sessions'][1]['clientVersion']); - $this->assertNotEmpty($webhook['data']['sessions'][1]['clientEngine']); - $this->assertIsString($webhook['data']['sessions'][1]['clientEngine']); - $this->assertIsString($webhook['data']['sessions'][1]['clientEngineVersion']); - $this->assertIsString($webhook['data']['sessions'][1]['deviceName']); - $this->assertIsString($webhook['data']['sessions'][1]['deviceBrand']); - $this->assertIsString($webhook['data']['sessions'][1]['deviceModel']); - $this->assertIsString($webhook['data']['sessions'][1]['countryCode']); - $this->assertIsString($webhook['data']['sessions'][1]['countryName']); - $this->assertEquals($webhook['data']['sessions'][1]['current'], true); - - $accountSession = $this->client->call(Client::METHOD_POST, '/account/sessions', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'email' => $email, - 'password' => $password, - ]); - - $this->assertEquals($accountSession['headers']['status-code'], 201); - - $sessionId = $accountSession['body']['$id']; - $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_'.$this->getProject()['$id']]; - - return array_merge($data, [ - 'sessionId' => $sessionId, - 'session' => $session, - ]); - } - - /** - * @depends testDeleteAccountSessions - */ - public function testUpdateAccountName($data): array - { - $id = $data['id'] ?? ''; - $email = $data['email'] ?? ''; - $session = $data['session'] ?? ''; - $newName = 'New Name'; - - $account = $this->client->call(Client::METHOD_PATCH, '/account/name', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, - ]), [ - 'name' => $newName - ]); - - $this->assertEquals($account['headers']['status-code'], 200); - $this->assertIsArray($account['body']); - - $webhook = $this->getLastRequest(); - - $this->assertEquals($webhook['method'], 'POST'); - $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); - $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.update.name'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertNotEmpty($webhook['data']['$id']); - $this->assertEquals($webhook['data']['name'], $newName); - $this->assertIsInt($webhook['data']['registration']); - $this->assertEquals($webhook['data']['status'], 0); - $this->assertEquals($webhook['data']['email'], $email); - $this->assertEquals($webhook['data']['emailVerification'], false); - $this->assertEquals($webhook['data']['prefs'], []); - - return $data; - } - - /** - * @depends testUpdateAccountName - */ - public function testUpdateAccountPassword($data): array - { - $id = $data['id'] ?? ''; - $email = $data['email'] ?? ''; - $password = $data['password'] ?? ''; - $session = $data['session'] ?? ''; - - $account = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, - ]), [ - 'password' => 'new-password', - 'oldPassword' => $password, - ]); - - $this->assertEquals($account['headers']['status-code'], 200); - $this->assertIsArray($account['body']); - - $webhook = $this->getLastRequest(); - - $this->assertEquals($webhook['method'], 'POST'); - $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); - $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.update.password'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertNotEmpty($webhook['data']['$id']); - $this->assertEquals($webhook['data']['name'], 'New Name'); - $this->assertIsInt($webhook['data']['registration']); - $this->assertEquals($webhook['data']['status'], 0); - $this->assertEquals($webhook['data']['email'], $email); - $this->assertEquals($webhook['data']['emailVerification'], false); - $this->assertEquals($webhook['data']['prefs'], []); - - $data['password'] = 'new-password'; - - return $data; - } - - /** - * @depends testUpdateAccountPassword - */ - public function testUpdateAccountEmail($data): array - { - $id = $data['id'] ?? ''; - $email = $data['email'] ?? ''; - $newEmail = uniqid().'new@localhost.test'; - $session = $data['session'] ?? ''; - - $account = $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, - ]), [ - 'email' => $newEmail, - 'password' => 'new-password', - ]); - - $this->assertEquals($account['headers']['status-code'], 200); - $this->assertIsArray($account['body']); - - $webhook = $this->getLastRequest(); - - $this->assertEquals($webhook['method'], 'POST'); - $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); - $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.update.email'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertNotEmpty($webhook['data']['$id']); - $this->assertEquals($webhook['data']['name'], 'New Name'); - $this->assertIsInt($webhook['data']['registration']); - $this->assertEquals($webhook['data']['status'], 0); - $this->assertEquals($webhook['data']['email'], $newEmail); - $this->assertEquals($webhook['data']['emailVerification'], false); - $this->assertEquals($webhook['data']['prefs'], []); - - $data['email'] = $newEmail; - - return $data; - } - - /** - * @depends testUpdateAccountEmail - */ - public function testUpdateAccountPrefs($data): array - { - $id = $data['id'] ?? ''; - $email = $data['email'] ?? ''; - $session = $data['session'] ?? ''; - - $account = $this->client->call(Client::METHOD_PATCH, '/account/prefs', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, - ]), [ - 'prefs' => [ - 'prefKey1' => 'prefValue1', - 'prefKey2' => 'prefValue2', - ] - ]); - - $this->assertEquals($account['headers']['status-code'], 200); - $this->assertIsArray($account['body']); - - $webhook = $this->getLastRequest(); - - $this->assertEquals($webhook['method'], 'POST'); - $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); - $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.update.prefs'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertNotEmpty($webhook['data']['$id']); - $this->assertEquals($webhook['data']['name'], 'New Name'); - $this->assertIsInt($webhook['data']['registration']); - $this->assertEquals($webhook['data']['status'], 0); - $this->assertEquals($webhook['data']['email'], $email); - $this->assertEquals($webhook['data']['emailVerification'], false); - $this->assertEquals($webhook['data']['prefs'], [ - 'prefKey1' => 'prefValue1', - 'prefKey2' => 'prefValue2', - ]); - - return $data; - } - - /** - * @depends testUpdateAccountPrefs - */ - public function testCreateAccountRecovery($data): array - { - $id = $data['id'] ?? ''; - $email = $data['email'] ?? ''; - $session = $data['session'] ?? ''; - - $recovery = $this->client->call(Client::METHOD_POST, '/account/recovery', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'email' => $email, - 'url' => 'http://localhost/recovery', - ]); - - $this->assertEquals(201, $recovery['headers']['status-code']); - $this->assertIsArray($recovery['body']); - - $webhook = $this->getLastRequest(); - - $this->assertEquals($webhook['method'], 'POST'); - $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); - $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.recovery.create'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertNotEmpty($webhook['data']['$id']); - $this->assertNotEmpty($webhook['data']['secret']); - $this->assertIsNumeric($webhook['data']['expire']); - - $data['secret'] = $webhook['data']['secret']; - - return $data; - } - - /** - * @depends testCreateAccountRecovery - */ - public function testUpdateAccountRecovery($data): array - { - $id = $data['id'] ?? ''; - $email = $data['email'] ?? ''; - $session = $data['session'] ?? ''; - $secret = $data['secret'] ?? ''; - $password = 'newPassowrd2'; - - $recovery = $this->client->call(Client::METHOD_PUT, '/account/recovery', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'userId' => $id, - 'secret' => $secret, - 'password' => $password, - 'passwordAgain' => $password, - ]); - - $this->assertEquals(200, $recovery['headers']['status-code']); - $this->assertIsArray($recovery['body']); - - $webhook = $this->getLastRequest(); - - $this->assertEquals($webhook['method'], 'POST'); - $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); - $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.recovery.update'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertNotEmpty($webhook['data']['$id']); - $this->assertNotEmpty($webhook['data']['secret']); - $this->assertIsNumeric($webhook['data']['expire']); - - $data['secret'] = $webhook['data']['secret']; - - return $data; - } - - /** - * @depends testUpdateAccountPrefs - */ - public function testCreateAccountVerification($data): array - { - $id = $data['id'] ?? ''; - $email = $data['email'] ?? ''; - $session = $data['session'] ?? ''; - - $verification = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, - - ]), [ - 'url' => 'http://localhost/verification', - ]); - - $this->assertEquals(201, $verification['headers']['status-code']); - $this->assertIsArray($verification['body']); - - $webhook = $this->getLastRequest(); - - $this->assertEquals($webhook['method'], 'POST'); - $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); - $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.verification.create'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertNotEmpty($webhook['data']['$id']); - $this->assertNotEmpty($webhook['data']['secret']); - $this->assertIsNumeric($webhook['data']['expire']); - - $data['secret'] = $webhook['data']['secret']; - - return $data; - } - - /** - * @depends testCreateAccountVerification - */ - public function testUpdateAccountVerification($data): array - { - $id = $data['id'] ?? ''; - $email = $data['email'] ?? ''; - $session = $data['session'] ?? ''; - $secret = $data['secret'] ?? ''; - - $verification = $this->client->call(Client::METHOD_PUT, '/account/verification', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, - ]), [ - 'userId' => $id, - 'secret' => $secret, - ]); - - $this->assertEquals(200, $verification['headers']['status-code']); - $this->assertIsArray($verification['body']); - - $webhook = $this->getLastRequest(); - - $this->assertEquals($webhook['method'], 'POST'); - $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); - $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.verification.update'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertNotEmpty($webhook['data']['$id']); - $this->assertNotEmpty($webhook['data']['secret']); - $this->assertIsNumeric($webhook['data']['expire']); - - $data['secret'] = $webhook['data']['secret']; - - return $data; - } - public function testCreateFile():array { /** @@ -620,6 +32,7 @@ trait WebhooksBase $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'storage.files.create'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertIsArray($webhook['data']['$permissions']); $this->assertEquals($webhook['data']['name'], 'logo.png'); @@ -660,6 +73,7 @@ trait WebhooksBase $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'storage.files.update'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertIsArray($webhook['data']['$permissions']); $this->assertEquals($webhook['data']['name'], 'logo.png'); @@ -694,6 +108,7 @@ trait WebhooksBase $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'storage.files.delete'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertIsArray($webhook['data']['$permissions']); $this->assertEquals($webhook['data']['name'], 'logo.png'); diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php index 7bd73bf3bb..8a36076dae 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php @@ -2,6 +2,7 @@ namespace Tests\E2E\Services\Webhooks; +use Tests\E2E\Client; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\SideClient; @@ -11,4 +12,610 @@ class WebhooksCustomClientTest extends Scope use WebhooksBase; use ProjectCustom; use SideClient; + + public function testCreateAccount():array + { + $email = uniqid().'user@localhost.test'; + $password = 'password'; + $name = 'User Name'; + + /** + * Test for SUCCESS + */ + $account = $this->client->call(Client::METHOD_POST, '/account', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'email' => $email, + 'password' => $password, + 'name' => $name, + ]); + + $id = $account['body']['$id']; + + $this->assertEquals($account['headers']['status-code'], 201); + $this->assertNotEmpty($account['body']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.create'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid']), true); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals($webhook['data']['name'], $name); + $this->assertIsInt($webhook['data']['registration']); + $this->assertEquals($webhook['data']['status'], 0); + $this->assertEquals($webhook['data']['email'], $email); + $this->assertEquals($webhook['data']['emailVerification'], false); + $this->assertEquals($webhook['data']['prefs'], []); + + return [ + 'id' => $id, + 'email' => $email, + 'password' => $password, + 'name' => $name, + ]; + } + + /** + * @depends testCreateAccount + */ + public function testCreateAccountSession($data):array + { + $email = $data['email'] ?? ''; + $password = $data['password'] ?? ''; + + /** + * Test for SUCCESS + */ + $accountSession = $this->client->call(Client::METHOD_POST, '/account/sessions', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'email' => $email, + 'password' => $password, + ]); + + $this->assertEquals($accountSession['headers']['status-code'], 201); + + $sessionId = $accountSession['body']['$id']; + $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_'.$this->getProject()['$id']]; + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.sessions.create'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid']), true); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertNotEmpty($webhook['data']['userId']); + $this->assertIsInt($webhook['data']['expire']); + $this->assertEquals($webhook['data']['ip'], '127.0.0.1'); + $this->assertNotEmpty($webhook['data']['osCode']); + $this->assertIsString($webhook['data']['osCode']); + $this->assertNotEmpty($webhook['data']['osName']); + $this->assertIsString($webhook['data']['osName']); + $this->assertNotEmpty($webhook['data']['osVersion']); + $this->assertIsString($webhook['data']['osVersion']); + $this->assertEquals($webhook['data']['clientType'], 'browser'); + $this->assertEquals($webhook['data']['clientCode'], 'CH'); + $this->assertEquals($webhook['data']['clientName'], 'Chrome'); + $this->assertNotEmpty($webhook['data']['clientVersion']); + $this->assertIsString($webhook['data']['clientVersion']); + $this->assertNotEmpty($webhook['data']['clientEngine']); + $this->assertIsString($webhook['data']['clientEngine']); + $this->assertIsString($webhook['data']['clientEngineVersion']); + $this->assertIsString($webhook['data']['deviceName']); + $this->assertIsString($webhook['data']['deviceBrand']); + $this->assertIsString($webhook['data']['deviceModel']); + $this->assertIsString($webhook['data']['countryCode']); + $this->assertIsString($webhook['data']['countryName']); + $this->assertEquals($webhook['data']['current'], true); + + return array_merge($data, [ + 'sessionId' => $sessionId, + 'session' => $session, + ]); + } + + /** + * @depends testCreateAccount + */ + public function testDeleteAccountSession($data):array + { + $email = $data['email'] ?? ''; + $password = $data['password'] ?? ''; + + /** + * Test for SUCCESS + */ + $accountSession = $this->client->call(Client::METHOD_POST, '/account/sessions', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'email' => $email, + 'password' => $password, + ]); + + $sessionId = $accountSession['body']['$id']; + $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_'.$this->getProject()['$id']]; + + $this->assertEquals($accountSession['headers']['status-code'], 201); + + $accountSession = $this->client->call(Client::METHOD_DELETE, '/account/sessions/'.$sessionId, array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ])); + + $this->assertEquals($accountSession['headers']['status-code'], 204); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.sessions.delete'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertNotEmpty($webhook['data']['userId']); + $this->assertIsInt($webhook['data']['expire']); + $this->assertEquals($webhook['data']['ip'], '127.0.0.1'); + $this->assertNotEmpty($webhook['data']['osCode']); + $this->assertIsString($webhook['data']['osCode']); + $this->assertNotEmpty($webhook['data']['osName']); + $this->assertIsString($webhook['data']['osName']); + $this->assertNotEmpty($webhook['data']['osVersion']); + $this->assertIsString($webhook['data']['osVersion']); + $this->assertEquals($webhook['data']['clientType'], 'browser'); + $this->assertEquals($webhook['data']['clientCode'], 'CH'); + $this->assertEquals($webhook['data']['clientName'], 'Chrome'); + $this->assertNotEmpty($webhook['data']['clientVersion']); + $this->assertIsString($webhook['data']['clientVersion']); + $this->assertNotEmpty($webhook['data']['clientEngine']); + $this->assertIsString($webhook['data']['clientEngine']); + $this->assertIsString($webhook['data']['clientEngineVersion']); + $this->assertIsString($webhook['data']['deviceName']); + $this->assertIsString($webhook['data']['deviceBrand']); + $this->assertIsString($webhook['data']['deviceModel']); + $this->assertIsString($webhook['data']['countryCode']); + $this->assertIsString($webhook['data']['countryName']); + $this->assertEquals($webhook['data']['current'], true); + + return $data; + } + + /** + * @depends testCreateAccount + */ + public function testDeleteAccountSessions($data):array + { + $email = $data['email'] ?? ''; + $password = $data['password'] ?? ''; + + /** + * Test for SUCCESS + */ + $accountSession = $this->client->call(Client::METHOD_POST, '/account/sessions', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'email' => $email, + 'password' => $password, + ]); + + $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_'.$this->getProject()['$id']]; + + $this->assertEquals($accountSession['headers']['status-code'], 201); + + $accountSession = $this->client->call(Client::METHOD_DELETE, '/account/sessions', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ])); + + $this->assertEquals($accountSession['headers']['status-code'], 204); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.sessions.delete'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['data']['sum'], 2); + $this->assertNotEmpty($webhook['data']['sessions'][1]['$id']); + $this->assertNotEmpty($webhook['data']['sessions'][1]['userId']); + $this->assertIsInt($webhook['data']['sessions'][1]['expire']); + $this->assertEquals($webhook['data']['sessions'][1]['ip'], '127.0.0.1'); + $this->assertNotEmpty($webhook['data']['sessions'][1]['osCode']); + $this->assertIsString($webhook['data']['sessions'][1]['osCode']); + $this->assertNotEmpty($webhook['data']['sessions'][1]['osName']); + $this->assertIsString($webhook['data']['sessions'][1]['osName']); + $this->assertNotEmpty($webhook['data']['sessions'][1]['osVersion']); + $this->assertIsString($webhook['data']['sessions'][1]['osVersion']); + $this->assertEquals($webhook['data']['sessions'][1]['clientType'], 'browser'); + $this->assertEquals($webhook['data']['sessions'][1]['clientCode'], 'CH'); + $this->assertEquals($webhook['data']['sessions'][1]['clientName'], 'Chrome'); + $this->assertNotEmpty($webhook['data']['sessions'][1]['clientVersion']); + $this->assertIsString($webhook['data']['sessions'][1]['clientVersion']); + $this->assertNotEmpty($webhook['data']['sessions'][1]['clientEngine']); + $this->assertIsString($webhook['data']['sessions'][1]['clientEngine']); + $this->assertIsString($webhook['data']['sessions'][1]['clientEngineVersion']); + $this->assertIsString($webhook['data']['sessions'][1]['deviceName']); + $this->assertIsString($webhook['data']['sessions'][1]['deviceBrand']); + $this->assertIsString($webhook['data']['sessions'][1]['deviceModel']); + $this->assertIsString($webhook['data']['sessions'][1]['countryCode']); + $this->assertIsString($webhook['data']['sessions'][1]['countryName']); + $this->assertEquals($webhook['data']['sessions'][1]['current'], true); + + $accountSession = $this->client->call(Client::METHOD_POST, '/account/sessions', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'email' => $email, + 'password' => $password, + ]); + + $this->assertEquals($accountSession['headers']['status-code'], 201); + + $sessionId = $accountSession['body']['$id']; + $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_'.$this->getProject()['$id']]; + + return array_merge($data, [ + 'sessionId' => $sessionId, + 'session' => $session, + ]); + } + + /** + * @depends testDeleteAccountSessions + */ + public function testUpdateAccountName($data): array + { + $id = $data['id'] ?? ''; + $email = $data['email'] ?? ''; + $session = $data['session'] ?? ''; + $newName = 'New Name'; + + $account = $this->client->call(Client::METHOD_PATCH, '/account/name', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ]), [ + 'name' => $newName + ]); + + $this->assertEquals($account['headers']['status-code'], 200); + $this->assertIsArray($account['body']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.update.name'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals($webhook['data']['name'], $newName); + $this->assertIsInt($webhook['data']['registration']); + $this->assertEquals($webhook['data']['status'], 0); + $this->assertEquals($webhook['data']['email'], $email); + $this->assertEquals($webhook['data']['emailVerification'], false); + $this->assertEquals($webhook['data']['prefs'], []); + + return $data; + } + + /** + * @depends testUpdateAccountName + */ + public function testUpdateAccountPassword($data): array + { + $id = $data['id'] ?? ''; + $email = $data['email'] ?? ''; + $password = $data['password'] ?? ''; + $session = $data['session'] ?? ''; + + $account = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ]), [ + 'password' => 'new-password', + 'oldPassword' => $password, + ]); + + $this->assertEquals($account['headers']['status-code'], 200); + $this->assertIsArray($account['body']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.update.password'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals($webhook['data']['name'], 'New Name'); + $this->assertIsInt($webhook['data']['registration']); + $this->assertEquals($webhook['data']['status'], 0); + $this->assertEquals($webhook['data']['email'], $email); + $this->assertEquals($webhook['data']['emailVerification'], false); + $this->assertEquals($webhook['data']['prefs'], []); + + $data['password'] = 'new-password'; + + return $data; + } + + /** + * @depends testUpdateAccountPassword + */ + public function testUpdateAccountEmail($data): array + { + $id = $data['id'] ?? ''; + $email = $data['email'] ?? ''; + $newEmail = uniqid().'new@localhost.test'; + $session = $data['session'] ?? ''; + + $account = $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ]), [ + 'email' => $newEmail, + 'password' => 'new-password', + ]); + + $this->assertEquals($account['headers']['status-code'], 200); + $this->assertIsArray($account['body']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.update.email'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals($webhook['data']['name'], 'New Name'); + $this->assertIsInt($webhook['data']['registration']); + $this->assertEquals($webhook['data']['status'], 0); + $this->assertEquals($webhook['data']['email'], $newEmail); + $this->assertEquals($webhook['data']['emailVerification'], false); + $this->assertEquals($webhook['data']['prefs'], []); + + $data['email'] = $newEmail; + + return $data; + } + + /** + * @depends testUpdateAccountEmail + */ + public function testUpdateAccountPrefs($data): array + { + $id = $data['id'] ?? ''; + $email = $data['email'] ?? ''; + $session = $data['session'] ?? ''; + + $account = $this->client->call(Client::METHOD_PATCH, '/account/prefs', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ]), [ + 'prefs' => [ + 'prefKey1' => 'prefValue1', + 'prefKey2' => 'prefValue2', + ] + ]); + + $this->assertEquals($account['headers']['status-code'], 200); + $this->assertIsArray($account['body']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.update.prefs'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals($webhook['data']['name'], 'New Name'); + $this->assertIsInt($webhook['data']['registration']); + $this->assertEquals($webhook['data']['status'], 0); + $this->assertEquals($webhook['data']['email'], $email); + $this->assertEquals($webhook['data']['emailVerification'], false); + $this->assertEquals($webhook['data']['prefs'], [ + 'prefKey1' => 'prefValue1', + 'prefKey2' => 'prefValue2', + ]); + + return $data; + } + + /** + * @depends testUpdateAccountPrefs + */ + public function testCreateAccountRecovery($data): array + { + $id = $data['id'] ?? ''; + $email = $data['email'] ?? ''; + $session = $data['session'] ?? ''; + + $recovery = $this->client->call(Client::METHOD_POST, '/account/recovery', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'email' => $email, + 'url' => 'http://localhost/recovery', + ]); + + $this->assertEquals(201, $recovery['headers']['status-code']); + $this->assertIsArray($recovery['body']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.recovery.create'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid']), true); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertNotEmpty($webhook['data']['userId']); + $this->assertNotEmpty($webhook['data']['secret']); + $this->assertIsNumeric($webhook['data']['expire']); + + $data['secret'] = $webhook['data']['secret']; + + return $data; + } + + /** + * @depends testCreateAccountRecovery + */ + public function testUpdateAccountRecovery($data): array + { + $id = $data['id'] ?? ''; + $email = $data['email'] ?? ''; + $session = $data['session'] ?? ''; + $secret = $data['secret'] ?? ''; + $password = 'newPassowrd2'; + + $recovery = $this->client->call(Client::METHOD_PUT, '/account/recovery', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'userId' => $id, + 'secret' => $secret, + 'password' => $password, + 'passwordAgain' => $password, + ]); + + $this->assertEquals(200, $recovery['headers']['status-code']); + $this->assertIsArray($recovery['body']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.recovery.update'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid']), true); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertNotEmpty($webhook['data']['userId']); + $this->assertNotEmpty($webhook['data']['secret']); + $this->assertIsNumeric($webhook['data']['expire']); + + $data['secret'] = $webhook['data']['secret']; + + return $data; + } + + /** + * @depends testUpdateAccountPrefs + */ + public function testCreateAccountVerification($data): array + { + $id = $data['id'] ?? ''; + $email = $data['email'] ?? ''; + $session = $data['session'] ?? ''; + + $verification = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ]), [ + 'url' => 'http://localhost/verification', + ]); + + $this->assertEquals(201, $verification['headers']['status-code']); + $this->assertIsArray($verification['body']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.verification.create'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertNotEmpty($webhook['data']['userId']); + $this->assertNotEmpty($webhook['data']['secret']); + $this->assertIsNumeric($webhook['data']['expire']); + + $data['secret'] = $webhook['data']['secret']; + + return $data; + } + + /** + * @depends testCreateAccountVerification + */ + public function testUpdateAccountVerification($data): array + { + $id = $data['id'] ?? ''; + $email = $data['email'] ?? ''; + $session = $data['session'] ?? ''; + $secret = $data['secret'] ?? ''; + + $verification = $this->client->call(Client::METHOD_PUT, '/account/verification', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ]), [ + 'userId' => $id, + 'secret' => $secret, + ]); + + $this->assertEquals(200, $verification['headers']['status-code']); + $this->assertIsArray($verification['body']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.verification.update'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertNotEmpty($webhook['data']['userId']); + $this->assertNotEmpty($webhook['data']['secret']); + $this->assertIsNumeric($webhook['data']['expire']); + + $data['secret'] = $webhook['data']['secret']; + + return $data; + } } \ No newline at end of file diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php index 91446d175c..27b6ccc771 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php @@ -43,6 +43,7 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'users.create'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['name'], $name); $this->assertIsInt($webhook['data']['registration']); @@ -82,6 +83,7 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'users.update.status'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['name'], $data['name']); $this->assertIsInt($webhook['data']['registration']); @@ -115,6 +117,7 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'users.delete'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['name'], $data['name']); $this->assertIsInt($webhook['data']['registration']); From aa18bbfb58ad78dcb482221aeb29ee39d11e7243 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 29 Nov 2020 08:47:47 +0200 Subject: [PATCH 22/36] Added account delete test for webhooks --- .../Webhooks/WebhooksCustomClientTest.php | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php index 8a36076dae..b2eadc9ab4 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php @@ -61,6 +61,68 @@ class WebhooksCustomClientTest extends Scope ]; } + public function testDeleteAccount():array + { + $email = uniqid().'user1@localhost.test'; + $password = 'password'; + $name = 'User Name 1'; + + /** + * Test for SUCCESS + */ + $account = $this->client->call(Client::METHOD_POST, '/account', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'email' => $email, + 'password' => $password, + 'name' => $name, + ]); + + $accountSession = $this->client->call(Client::METHOD_POST, '/account/sessions', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'email' => $email, + 'password' => $password, + ]); + + $this->assertEquals($accountSession['headers']['status-code'], 201); + + $sessionId = $accountSession['body']['$id']; + $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_'.$this->getProject()['$id']]; + + $account = $this->client->call(Client::METHOD_DELETE, '/account', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ])); + + $this->assertEquals($account['headers']['status-code'], 204); + $this->assertEmpty($account['body']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.delete'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals($webhook['data']['name'], $name); + $this->assertIsInt($webhook['data']['registration']); + $this->assertEquals($webhook['data']['status'], 2); + $this->assertEquals($webhook['data']['email'], $email); + $this->assertEquals($webhook['data']['emailVerification'], false); + $this->assertEquals($webhook['data']['prefs'], []); + + return []; + } + /** * @depends testCreateAccount */ From f5df201e4a5676cb7f3cecce1f7929fa628fe363 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 30 Nov 2020 23:41:58 +0200 Subject: [PATCH 23/36] Added more missing tests --- app/config/events.php | 4 +- src/Appwrite/Utopia/Response.php | 5 +- tests/e2e/Scopes/ProjectCustom.php | 2 +- tests/e2e/Services/Webhooks/WebhooksBase.php | 190 +++++++++++++++++- .../Webhooks/WebhooksCustomServerTest.php | 125 ++++++++++++ 5 files changed, 318 insertions(+), 8 deletions(-) diff --git a/app/config/events.php b/app/config/events.php index 51a645e336..0936cab1e5 100644 --- a/app/config/events.php +++ b/app/config/events.php @@ -52,8 +52,8 @@ return [ 'database.documents.create' => [ 'description' => 'This event triggers when a database document is created.', ], - 'database.documents.patch' => [ - 'description' => 'This event triggers when a database document is patched.', + 'database.documents.update' => [ + 'description' => 'This event triggers when a database document is updated.', ], 'database.documents.delete' => [ 'description' => 'This event triggers when a database document is deleted.', diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index 62e95b0d18..a854416c3f 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -261,7 +261,8 @@ class Response extends SwooleResponse $output = []; if ($model->isAny()) { - return $document->getArrayCopy(); + $this->payload = $document->getArrayCopy(); + return $this->payload; } foreach ($model->getRules() as $key => $rule) { @@ -294,7 +295,7 @@ class Response extends SwooleResponse $this->payload = $output; - return $output; + return $this->payload; } /** diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index 43ae3721e7..ca3f272227 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -109,7 +109,7 @@ trait ProjectCustom 'database.collections.update', 'database.collections.delete', 'database.documents.create', - 'database.documents.patch', + 'database.documents.update', 'database.documents.delete', 'storage.files.create', 'storage.files.update', diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index 35eb7547df..76a23d5d8a 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -7,7 +7,191 @@ use Tests\E2E\Client; trait WebhooksBase { - public function testCreateFile():array + public function testCreateCollection(): array + { + /** + * Test for SUCCESS + */ + $actors = $this->client->call(Client::METHOD_POST, '/database/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'name' => 'Actors', + 'read' => ['*'], + 'write' => ['*'], + 'rules' => [ + [ + 'label' => 'First Name', + 'key' => 'firstName', + 'type' => 'text', + 'default' => '', + 'required' => true, + 'array' => false + ], + [ + 'label' => 'Last Name', + 'key' => 'lastName', + 'type' => 'text', + 'default' => '', + 'required' => true, + 'array' => false + ], + ], + ]); + + $this->assertEquals($actors['headers']['status-code'], 201); + $this->assertNotEmpty($actors['body']['$id']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.collections.create'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), true); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals($webhook['data']['name'], 'Actors'); + $this->assertIsArray($webhook['data']['$permissions']); + $this->assertIsArray($webhook['data']['$permissions']['read']); + $this->assertIsArray($webhook['data']['$permissions']['write']); + $this->assertCount(1, $webhook['data']['$permissions']['read']); + $this->assertCount(1, $webhook['data']['$permissions']['write']); + $this->assertCount(2, $webhook['data']['rules']); + + return array_merge(['actorsId' => $actors['body']['$id']]); + } + + /** + * @depends testCreateCollection + */ + public function testCreateDocument(array $data): array + { + $document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'data' => [ + 'firstName' => 'Chris', + 'lastName' => 'Evans', + + ], + 'read' => ['*'], + 'write' => ['*'], + ]); + + $this->assertEquals($document['headers']['status-code'], 201); + $this->assertNotEmpty($document['body']['$id']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.documents.create'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals($webhook['data']['firstName'], 'Chris'); + $this->assertEquals($webhook['data']['lastName'], 'Evans'); + $this->assertIsArray($webhook['data']['$permissions']['read']); + $this->assertIsArray($webhook['data']['$permissions']['write']); + $this->assertCount(1, $webhook['data']['$permissions']['read']); + $this->assertCount(1, $webhook['data']['$permissions']['write']); + + $data['documentId'] = $document['body']['$id']; + + return $data; + } + + /** + * @depends testCreateDocument + */ + public function testUpdateDocument(array $data): array + { + $document = $this->client->call(Client::METHOD_PATCH, '/database/collections/' . $data['actorsId'] . '/documents/'.$data['documentId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'data' => [ + 'firstName' => 'Chris1', + 'lastName' => 'Evans2', + ], + 'read' => ['*'], + 'write' => ['*'], + ]); + + $this->assertEquals($document['headers']['status-code'], 200); + $this->assertNotEmpty($document['body']['$id']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.documents.update'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals($webhook['data']['firstName'], 'Chris1'); + $this->assertEquals($webhook['data']['lastName'], 'Evans2'); + $this->assertIsArray($webhook['data']['$permissions']['read']); + $this->assertIsArray($webhook['data']['$permissions']['write']); + $this->assertCount(1, $webhook['data']['$permissions']['read']); + $this->assertCount(1, $webhook['data']['$permissions']['write']); + + return $data; + } + + /** + * @depends testCreateCollection + */ + public function testDeleteDocument(array $data): array + { + $document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'data' => [ + 'firstName' => 'Bradly', + 'lastName' => 'Cooper', + + ], + 'read' => ['*'], + 'write' => ['*'], + ]); + + $this->assertEquals($document['headers']['status-code'], 201); + $this->assertNotEmpty($document['body']['$id']); + + $document = $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $data['actorsId'] . '/documents/' . $document['body']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals($document['headers']['status-code'], 204); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.documents.delete'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals($webhook['data']['firstName'], 'Bradly'); + $this->assertEquals($webhook['data']['lastName'], 'Cooper'); + $this->assertIsArray($webhook['data']['$permissions']['read']); + $this->assertIsArray($webhook['data']['$permissions']['write']); + $this->assertCount(1, $webhook['data']['$permissions']['read']); + $this->assertCount(1, $webhook['data']['$permissions']['write']); + + return $data; + } + + public function testCreateFile(): array { /** * Test for SUCCESS @@ -50,7 +234,7 @@ trait WebhooksBase /** * @depends testCreateFile */ - public function testUpdateFile(array $data):array + public function testUpdateFile(array $data): array { /** * Test for SUCCESS @@ -88,7 +272,7 @@ trait WebhooksBase /** * @depends testUpdateFile */ - public function testDeleteFile(array $data):array + public function testDeleteFile(array $data): array { /** * Test for SUCCESS diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php index 27b6ccc771..38da1f50d1 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php @@ -13,6 +13,131 @@ class WebhooksCustomServerTest extends Scope use ProjectCustom; use SideServer; + /** + * @depends testCreateCollection + */ + public function testUpdateCollection($data): array + { + var_dump($data); + exit(); + /** + * Test for SUCCESS + */ + $actors = $this->client->call(Client::METHOD_PUT, '/database/collections/'.$data['actorsId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'name' => 'Actors1', + 'read' => ['*'], + 'write' => ['*'], + 'rules' => [ + [ + 'label' => 'First Name', + 'key' => 'firstName', + 'type' => 'text', + 'default' => '', + 'required' => true, + 'array' => false + ], + [ + 'label' => 'Last Name', + 'key' => 'lastName', + 'type' => 'text', + 'default' => '', + 'required' => true, + 'array' => false + ], + ], + ]); + + $this->assertEquals($actors['headers']['status-code'], 200); + $this->assertNotEmpty($actors['body']['$id']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.collections.update'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), true); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals($webhook['data']['name'], 'Actors1'); + $this->assertIsArray($webhook['data']['$permissions']); + $this->assertIsArray($webhook['data']['$permissions']['read']); + $this->assertIsArray($webhook['data']['$permissions']['write']); + $this->assertCount(1, $webhook['data']['$permissions']['read']); + $this->assertCount(1, $webhook['data']['$permissions']['write']); + $this->assertCount(2, $webhook['data']['rules']); + + return array_merge(['actorsId' => $actors['body']['$id']]); + } + + public function testDeleteCollection(): array + { + /** + * Test for SUCCESS + */ + $actors = $this->client->call(Client::METHOD_POST, '/database/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'name' => 'Demo', + 'read' => ['*'], + 'write' => ['*'], + 'rules' => [ + [ + 'label' => 'First Name', + 'key' => 'firstName', + 'type' => 'text', + 'default' => '', + 'required' => true, + 'array' => false + ], + [ + 'label' => 'Last Name', + 'key' => 'lastName', + 'type' => 'text', + 'default' => '', + 'required' => true, + 'array' => false + ], + ], + ]); + + $this->assertEquals($actors['headers']['status-code'], 201); + $this->assertNotEmpty($actors['body']['$id']); + + $actors = $this->client->call(Client::METHOD_DELETE, '/database/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), []); + + $this->assertEquals($actors['headers']['status-code'], 204); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.collections.delete'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), true); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals($webhook['data']['name'], 'Demo'); + $this->assertIsArray($webhook['data']['$permissions']); + $this->assertIsArray($webhook['data']['$permissions']['read']); + $this->assertIsArray($webhook['data']['$permissions']['write']); + $this->assertCount(1, $webhook['data']['$permissions']['read']); + $this->assertCount(1, $webhook['data']['$permissions']['write']); + $this->assertCount(2, $webhook['data']['rules']); + + return array_merge(['actorsId' => $actors['body']['$id']]); + } + public function testCreateUser():array { $email = uniqid().'user@localhost.test'; From 4df6cb2ef5df0c946fabaf8d2ab2dd63d68819d9 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 1 Dec 2020 20:53:31 +0200 Subject: [PATCH 24/36] Added tests for Database webhooks --- src/Appwrite/Utopia/Response/Model/Session.php | 1 + tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Appwrite/Utopia/Response/Model/Session.php b/src/Appwrite/Utopia/Response/Model/Session.php index 01cd23ee26..a0edfbb4b6 100644 --- a/src/Appwrite/Utopia/Response/Model/Session.php +++ b/src/Appwrite/Utopia/Response/Model/Session.php @@ -18,6 +18,7 @@ class Session extends Model ->addRule('userId', [ 'type' => self::TYPE_STRING, 'description' => 'User ID.', + 'default' => '', 'example' => '5e5bb8c16897e', ]) ->addRule('expire', [ diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php index 38da1f50d1..0ff7f39e8e 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php @@ -18,8 +18,6 @@ class WebhooksCustomServerTest extends Scope */ public function testUpdateCollection($data): array { - var_dump($data); - exit(); /** * Test for SUCCESS */ @@ -110,7 +108,7 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($actors['headers']['status-code'], 201); $this->assertNotEmpty($actors['body']['$id']); - $actors = $this->client->call(Client::METHOD_DELETE, '/database/collections', array_merge([ + $actors = $this->client->call(Client::METHOD_DELETE, '/database/collections/'.$actors['body']['$id'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -135,7 +133,7 @@ class WebhooksCustomServerTest extends Scope $this->assertCount(1, $webhook['data']['$permissions']['write']); $this->assertCount(2, $webhook['data']['rules']); - return array_merge(['actorsId' => $actors['body']['$id']]); + return []; } public function testCreateUser():array From 28623c6baa090baa7c627d07660cf4cda79b7413 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 3 Dec 2020 00:15:20 +0200 Subject: [PATCH 25/36] =?UTF-8?q?Added=20team=20events=20and=20tests=20?= =?UTF-8?q?=F0=9F=9F=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/config/events.php | 18 ++ app/controllers/api/teams.php | 24 +- tests/e2e/Scopes/ProjectCustom.php | 6 + tests/e2e/Services/Webhooks/WebhooksBase.php | 222 ++++++++++++++++++ .../Webhooks/WebhooksCustomClientTest.php | 46 ++++ 5 files changed, 312 insertions(+), 4 deletions(-) diff --git a/app/config/events.php b/app/config/events.php index 0936cab1e5..db1bbc2434 100644 --- a/app/config/events.php +++ b/app/config/events.php @@ -79,4 +79,22 @@ return [ 'users.sessions.delete' => [ 'description' => 'This event triggers when a user session is deleted from users API.', ], + 'teams.create' => [ + 'description' => 'This event triggers when a team is created.', + ], + 'teams.update' => [ + 'description' => 'This event triggers when a team is updated.', + ], + 'teams.delete' => [ + 'description' => 'This event triggers when a team is deleted.', + ], + 'teams.memberships.create' => [ + 'description' => 'This event triggers when a team memberships is created.', + ], + 'teams.memberships.update.status' => [ + 'description' => 'This event triggers when a team memberships status is updated.', + ], + 'teams.memberships.delete' => [ + 'description' => 'This event triggers when a team memberships is deleted.', + ], ]; \ No newline at end of file diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 37fbad3352..33ba1b333b 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -23,6 +23,7 @@ use DeviceDetector\DeviceDetector; App::post('/v1/teams') ->desc('Create Team') ->groups(['api', 'teams']) + ->label('event', 'teams.create') ->label('scope', 'teams.write') ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'teams') @@ -157,6 +158,7 @@ App::get('/v1/teams/:teamId') App::put('/v1/teams/:teamId') ->desc('Update Team') ->groups(['api', 'teams']) + ->label('event', 'teams.update') ->label('scope', 'teams.write') ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'teams') @@ -191,6 +193,7 @@ App::put('/v1/teams/:teamId') App::delete('/v1/teams/:teamId') ->desc('Delete Team') ->groups(['api', 'teams']) + ->label('event', 'teams.delete') ->label('scope', 'teams.write') ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'teams') @@ -200,9 +203,10 @@ App::delete('/v1/teams/:teamId') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_NONE) ->param('teamId', '', new UID(), 'Team unique ID.') - ->action(function ($teamId, $response, $projectDB) { + ->action(function ($teamId, $response, $projectDB, $webhooks) { /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $webhooks */ $team = $projectDB->getDocument($teamId); @@ -229,12 +233,17 @@ App::delete('/v1/teams/:teamId') throw new Exception('Failed to remove team from DB', 500); } + $webhooks + ->setParam('payload', $response->output($team, Response::MODEL_TEAM)) + ; + $response->noContent(); - }, ['response', 'projectDB']); + }, ['response', 'projectDB', 'webhooks']); App::post('/v1/teams/:teamId/memberships') ->desc('Create Team Membership') ->groups(['api', 'teams']) + ->label('event', 'teams.memberships.create') ->label('scope', 'teams.write') ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'teams') @@ -483,6 +492,7 @@ App::get('/v1/teams/:teamId/memberships') App::patch('/v1/teams/:teamId/memberships/:inviteId/status') ->desc('Update Team Membership Status') ->groups(['api', 'teams']) + ->label('event', 'teams.memberships.update.status') ->label('scope', 'public') ->label('sdk.platform', [APP_PLATFORM_CLIENT]) ->label('sdk.namespace', 'teams') @@ -662,6 +672,7 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status') App::delete('/v1/teams/:teamId/memberships/:inviteId') ->desc('Delete Team Membership') ->groups(['api', 'teams']) + ->label('event', 'teams.memberships.delete') ->label('scope', 'teams.write') ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'teams') @@ -672,10 +683,11 @@ App::delete('/v1/teams/:teamId/memberships/:inviteId') ->label('sdk.response.model', Response::MODEL_NONE) ->param('teamId', '', new UID(), 'Team unique ID.') ->param('inviteId', '', new UID(), 'Invite unique ID.') - ->action(function ($teamId, $inviteId, $response, $projectDB, $audits) { + ->action(function ($teamId, $inviteId, $response, $projectDB, $audits, $webhooks) { /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Event $webhooks */ $membership = $projectDB->getDocument($inviteId); @@ -713,5 +725,9 @@ App::delete('/v1/teams/:teamId/memberships/:inviteId') ->setParam('resource', 'teams/'.$teamId) ; + $webhooks + ->setParam('payload', $response->output($membership, Response::MODEL_MEMBERSHIP)) + ; + $response->noContent(); - }, ['response', 'projectDB', 'audits']); + }, ['response', 'projectDB', 'audits', 'webhooks']); diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index ca3f272227..55157c5e4a 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -118,6 +118,12 @@ trait ProjectCustom 'users.update.status', 'users.delete', 'users.sessions.delete', + 'teams.create', + 'teams.update', + 'teams.delete', + 'teams.memberships.create', + 'teams.memberships.update.status', + 'teams.memberships.delete', ], 'url' => 'http://request-catcher:5000/webhook', 'security' => false, diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index 76a23d5d8a..a29dbf6ed1 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -303,4 +303,226 @@ trait WebhooksBase return $data; } + + public function testCreateTeam(): array + { + /** + * Test for SUCCESS + */ + $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Arsenal' + ]); + + $this->assertEquals(201, $team['headers']['status-code']); + $this->assertNotEmpty($team['body']['$id']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.create'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals('Arsenal', $webhook['data']['name']); + $this->assertGreaterThan(-1, $webhook['data']['sum']); + $this->assertIsInt($webhook['data']['sum']); + $this->assertIsInt($webhook['data']['dateCreated']); + + /** + * Test for FAILURE + */ + return ['teamId' => $team['body']['$id']]; + } + + /** + * @depends testCreateTeam + */ + public function testUpdateTeam($data): array + { + /** + * Test for SUCCESS + */ + $team = $this->client->call(Client::METHOD_PUT, '/teams/'.$data['teamId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Demo New' + ]); + + $this->assertEquals(200, $team['headers']['status-code']); + $this->assertNotEmpty($team['body']['$id']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.update'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals('Demo New', $webhook['data']['name']); + $this->assertGreaterThan(-1, $webhook['data']['sum']); + $this->assertIsInt($webhook['data']['sum']); + $this->assertIsInt($webhook['data']['dateCreated']); + + /** + * Test for FAILURE + */ + return ['teamId' => $team['body']['$id']]; + } + + public function testDeleteTeam(): array + { + /** + * Test for SUCCESS + */ + $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Chelsea' + ]); + + $this->assertEquals(201, $team['headers']['status-code']); + $this->assertNotEmpty($team['body']['$id']); + + $team = $this->client->call(Client::METHOD_DELETE, '/teams/'.$team['body']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.delete'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals('Chelsea', $webhook['data']['name']); + $this->assertGreaterThan(-1, $webhook['data']['sum']); + $this->assertIsInt($webhook['data']['sum']); + $this->assertIsInt($webhook['data']['dateCreated']); + + /** + * Test for FAILURE + */ + return []; + } + + /** + * @depends testCreateTeam + */ + public function testCreateTeamMembership($data): array + { + $teamUid = $data['teamId'] ?? ''; + $email = uniqid().'friend@localhost.test'; + + /** + * Test for SUCCESS + */ + $team = $this->client->call(Client::METHOD_POST, '/teams/'.$teamUid.'/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'email' => $email, + 'name' => 'Friend User', + 'roles' => ['admin', 'editor'], + 'url' => 'http://localhost:5000/join-us#title' + ]); + + $this->assertEquals(201, $team['headers']['status-code']); + $this->assertNotEmpty($team['body']['$id']); + + $lastEmail = $this->getLastEmail(); + + $secret = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256); + $inviteUid = substr($lastEmail['text'], strpos($lastEmail['text'], '?inviteId=', 0) + 10, 13); + $userUid = substr($lastEmail['text'], strpos($lastEmail['text'], '&userId=', 0) + 8, 13); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.memberships.create'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertNotEmpty($webhook['data']['userId']); + $this->assertNotEmpty($webhook['data']['teamId']); + $this->assertCount(2, $webhook['data']['roles']); + $this->assertIsInt($webhook['data']['joined']); + $this->assertEquals(('server' === $this->getSide()), $webhook['data']['confirm']); + + /** + * Test for FAILURE + */ + return [ + 'teamId' => $teamUid, + 'secret' => $secret, + 'inviteId' => $inviteUid, + 'userId' => $webhook['data']['userId'], + ]; + } + + /** + * @depends testCreateTeam + */ + public function testDeleteTeamMembership($data): array + { + $teamUid = $data['teamId'] ?? ''; + $email = uniqid().'friend@localhost.test'; + + /** + * Test for SUCCESS + */ + $team = $this->client->call(Client::METHOD_POST, '/teams/'.$teamUid.'/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'email' => $email, + 'name' => 'Friend User', + 'roles' => ['admin', 'editor'], + 'url' => 'http://localhost:5000/join-us#title' + ]); + + $this->assertEquals(201, $team['headers']['status-code']); + $this->assertNotEmpty($team['body']['$id']); + + $team = $this->client->call(Client::METHOD_DELETE, '/teams/'.$teamUid.'/memberships/'.$team['body']['$id'], array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(204, $team['headers']['status-code']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.memberships.delete'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertNotEmpty($webhook['data']['userId']); + $this->assertNotEmpty($webhook['data']['teamId']); + $this->assertCount(2, $webhook['data']['roles']); + $this->assertIsInt($webhook['data']['joined']); + $this->assertEquals(('server' === $this->getSide()), $webhook['data']['confirm']); + + /** + * Test for FAILURE + */ + return []; + } } \ No newline at end of file diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php index b2eadc9ab4..09136bcbb2 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php @@ -680,4 +680,50 @@ class WebhooksCustomClientTest extends Scope return $data; } + + /** + * @depends testCreateTeamMembership + */ + public function testUpdateTeamMembership($data): array + { + $teamUid = $data['teamId'] ?? ''; + $secret = $data['secret'] ?? ''; + $inviteUid = $data['inviteId'] ?? ''; + $userUid = $data['userId'] ?? ''; + + /** + * Test for SUCCESS + */ + $team = $this->client->call(Client::METHOD_PATCH, '/teams/'.$teamUid.'/memberships/'.$inviteUid.'/status', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'secret' => $secret, + 'userId' => $userUid, + ]); + + $this->assertEquals(200, $team['headers']['status-code']); + $this->assertNotEmpty($team['body']['$id']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.memberships.update.status'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), true); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertNotEmpty($webhook['data']['userId']); + $this->assertNotEmpty($webhook['data']['teamId']); + $this->assertCount(2, $webhook['data']['roles']); + $this->assertIsInt($webhook['data']['joined']); + $this->assertEquals(true, $webhook['data']['confirm']); + + /** + * Test for FAILURE + */ + return []; + } } \ No newline at end of file From c82630e693f7f2ac9f4d2ebdc5514f0e2441d1cb Mon Sep 17 00:00:00 2001 From: Lior Haim <51369094+Glide7@users.noreply.github.com> Date: Thu, 3 Dec 2020 09:59:57 +0200 Subject: [PATCH 26/36] Update CHANGES.md --- CHANGES.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 5191bf4fa3..4c21ee6f58 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,6 @@ - New route in Locale API to fetch a list of languages (@TorstenDittmann) - Added Google Fonts to Appwrite for offline availability - Added response to /locale/languages API with a list of languages (@TorstenDittmann ,[#351](https://github.com/appwrite/appwrite/issues/351)) -- Added response to /locale/languages API with a list of languages (@TorstenDittmann ,[#351](https://github.com/appwrite/appwrite/issues/351)) - Added API response payload structure info and examples to the docs site ([#381](https://github.com/appwrite/appwrite/issues/381)) - Added Google Fonts to Appwrite for offline availability - Added a new route in the Avatars API to get user initials avatar ([#386](https://github.com/appwrite/appwrite/issues/386)) From 5ca0e85fc3aadecc27085acb06054a641c6371ec Mon Sep 17 00:00:00 2001 From: Lior Haim <51369094+Glide7@users.noreply.github.com> Date: Thu, 3 Dec 2020 13:41:29 +0200 Subject: [PATCH 27/36] Update CHANGES.md Added webhooks --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 4c21ee6f58..d4ca4ea29f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,7 @@ ## Features - New route in Locale API to fetch a list of languages (@TorstenDittmann) +- Improved Webhooks and New System Events - [Documentation](https://appwrite.io/docs/webhooks) - Added Google Fonts to Appwrite for offline availability - Added response to /locale/languages API with a list of languages (@TorstenDittmann ,[#351](https://github.com/appwrite/appwrite/issues/351)) - Added API response payload structure info and examples to the docs site ([#381](https://github.com/appwrite/appwrite/issues/381)) From 517f7de8dd600e29774c910981c227fbc39f3ccd Mon Sep 17 00:00:00 2001 From: Lior Haim <51369094+Glide7@users.noreply.github.com> Date: Thu, 3 Dec 2020 13:46:18 +0200 Subject: [PATCH 28/36] Update CHANGES.md removed link --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index d4ca4ea29f..ed3ba228c8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,7 +3,7 @@ ## Features - New route in Locale API to fetch a list of languages (@TorstenDittmann) -- Improved Webhooks and New System Events - [Documentation](https://appwrite.io/docs/webhooks) +- Improved Webhooks and New System Events - [Learn more]() - Added Google Fonts to Appwrite for offline availability - Added response to /locale/languages API with a list of languages (@TorstenDittmann ,[#351](https://github.com/appwrite/appwrite/issues/351)) - Added API response payload structure info and examples to the docs site ([#381](https://github.com/appwrite/appwrite/issues/381)) From b2ccc6581387a481d9de422ca35276fc56512b36 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 3 Dec 2020 19:56:07 +0200 Subject: [PATCH 29/36] Added new webhooks headers --- app/workers/webhooks.php | 5 +- tests/e2e/Scopes/ProjectCustom.php | 1 + tests/e2e/Services/Webhooks/WebhooksBase.php | 48 ++++++++++++---- .../Webhooks/WebhooksCustomClientTest.php | 56 ++++++++++++++----- .../Webhooks/WebhooksCustomServerTest.php | 20 +++++-- 5 files changed, 98 insertions(+), 32 deletions(-) diff --git a/app/workers/webhooks.php b/app/workers/webhooks.php index f51dfbbc8a..76c94a79fe 100644 --- a/app/workers/webhooks.php +++ b/app/workers/webhooks.php @@ -56,6 +56,7 @@ class WebhooksV1 continue; } + $id = $webhook['$id'] ?? ''; $name = $webhook['name'] ?? ''; $signature = $webhook['signature'] ?? 'not-yet-implemented'; $url = $webhook['url'] ?? ''; @@ -79,9 +80,11 @@ class WebhooksV1 [ 'Content-Type: application/json', 'Content-Length: '.\strlen($payload), + 'X-'.APP_NAME.'-Webhook-Id: '.$id, 'X-'.APP_NAME.'-Webhook-Event: '.$event, 'X-'.APP_NAME.'-Webhook-Name: '.$name, - 'X-'.APP_NAME.'-Webhook-Userid: '.$userId, + 'X-'.APP_NAME.'-Webhook-User-Id: '.$userId, + 'X-'.APP_NAME.'-Webhook-Project-Id: '.$projectId, 'X-'.APP_NAME.'-Webhook-Signature: '.$signature, ] ); diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index 55157c5e4a..c17bd52878 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -138,6 +138,7 @@ trait ProjectCustom '$id' => $project['body']['$id'], 'name' => $project['body']['name'], 'apiKey' => $key['body']['secret'], + 'webhookId' => $webhook['body']['$id'], ]; return self::$project; diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index a29dbf6ed1..5dc6cfafe2 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -50,7 +50,9 @@ trait WebhooksBase $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.collections.create'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), true); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), true); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['name'], 'Actors'); $this->assertIsArray($webhook['data']['$permissions']); @@ -91,7 +93,9 @@ trait WebhooksBase $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.documents.create'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['firstName'], 'Chris'); $this->assertEquals($webhook['data']['lastName'], 'Evans'); @@ -132,7 +136,9 @@ trait WebhooksBase $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.documents.update'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['firstName'], 'Chris1'); $this->assertEquals($webhook['data']['lastName'], 'Evans2'); @@ -179,7 +185,9 @@ trait WebhooksBase $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.documents.delete'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['firstName'], 'Bradly'); $this->assertEquals($webhook['data']['lastName'], 'Cooper'); @@ -216,7 +224,9 @@ trait WebhooksBase $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'storage.files.create'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertIsArray($webhook['data']['$permissions']); $this->assertEquals($webhook['data']['name'], 'logo.png'); @@ -257,7 +267,9 @@ trait WebhooksBase $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'storage.files.update'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertIsArray($webhook['data']['$permissions']); $this->assertEquals($webhook['data']['name'], 'logo.png'); @@ -292,7 +304,9 @@ trait WebhooksBase $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'storage.files.delete'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertIsArray($webhook['data']['$permissions']); $this->assertEquals($webhook['data']['name'], 'logo.png'); @@ -326,7 +340,9 @@ trait WebhooksBase $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.create'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals('Arsenal', $webhook['data']['name']); $this->assertGreaterThan(-1, $webhook['data']['sum']); @@ -364,7 +380,9 @@ trait WebhooksBase $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.update'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals('Demo New', $webhook['data']['name']); $this->assertGreaterThan(-1, $webhook['data']['sum']); @@ -404,7 +422,9 @@ trait WebhooksBase $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.delete'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals('Chelsea', $webhook['data']['name']); $this->assertGreaterThan(-1, $webhook['data']['sum']); @@ -454,7 +474,9 @@ trait WebhooksBase $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.memberships.create'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertNotEmpty($webhook['data']['userId']); $this->assertNotEmpty($webhook['data']['teamId']); @@ -512,7 +534,9 @@ trait WebhooksBase $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.memberships.delete'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertNotEmpty($webhook['data']['userId']); $this->assertNotEmpty($webhook['data']['teamId']); diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php index 09136bcbb2..f4ed81ac0f 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php @@ -44,7 +44,9 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.create'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid']), true); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id']), true); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['name'], $name); $this->assertIsInt($webhook['data']['registration']); @@ -111,7 +113,9 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.delete'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['name'], $name); $this->assertIsInt($webhook['data']['registration']); @@ -155,7 +159,9 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.sessions.create'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid']), true); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id']), true); $this->assertNotEmpty($webhook['data']['$id']); $this->assertNotEmpty($webhook['data']['userId']); $this->assertIsInt($webhook['data']['expire']); @@ -228,7 +234,9 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.sessions.delete'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertNotEmpty($webhook['data']['userId']); $this->assertIsInt($webhook['data']['expire']); @@ -297,7 +305,9 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.sessions.delete'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertEquals($webhook['data']['sum'], 2); $this->assertNotEmpty($webhook['data']['sessions'][1]['$id']); $this->assertNotEmpty($webhook['data']['sessions'][1]['userId']); @@ -373,7 +383,9 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.update.name'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['name'], $newName); $this->assertIsInt($webhook['data']['registration']); @@ -415,7 +427,9 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.update.password'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['name'], 'New Name'); $this->assertIsInt($webhook['data']['registration']); @@ -459,7 +473,9 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.update.email'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['name'], 'New Name'); $this->assertIsInt($webhook['data']['registration']); @@ -504,7 +520,9 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.update.prefs'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['name'], 'New Name'); $this->assertIsInt($webhook['data']['registration']); @@ -547,7 +565,9 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.recovery.create'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid']), true); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id']), true); $this->assertNotEmpty($webhook['data']['$id']); $this->assertNotEmpty($webhook['data']['userId']); $this->assertNotEmpty($webhook['data']['secret']); @@ -590,7 +610,9 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.recovery.update'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid']), true); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id']), true); $this->assertNotEmpty($webhook['data']['$id']); $this->assertNotEmpty($webhook['data']['userId']); $this->assertNotEmpty($webhook['data']['secret']); @@ -629,7 +651,9 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.verification.create'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertNotEmpty($webhook['data']['userId']); $this->assertNotEmpty($webhook['data']['secret']); @@ -670,7 +694,9 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.verification.update'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertNotEmpty($webhook['data']['userId']); $this->assertNotEmpty($webhook['data']['secret']); @@ -713,7 +739,9 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.memberships.update.status'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), true); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), true); $this->assertNotEmpty($webhook['data']['$id']); $this->assertNotEmpty($webhook['data']['userId']); $this->assertNotEmpty($webhook['data']['teamId']); diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php index 0ff7f39e8e..360dee2cb3 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php @@ -59,7 +59,9 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.collections.update'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), true); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), true); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['name'], 'Actors1'); $this->assertIsArray($webhook['data']['$permissions']); @@ -123,7 +125,9 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.collections.delete'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), true); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), true); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['name'], 'Demo'); $this->assertIsArray($webhook['data']['$permissions']); @@ -166,7 +170,9 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'users.create'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['name'], $name); $this->assertIsInt($webhook['data']['registration']); @@ -206,7 +212,9 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'users.update.status'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['name'], $data['name']); $this->assertIsInt($webhook['data']['registration']); @@ -240,7 +248,9 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'users.delete'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['name'], $data['name']); $this->assertIsInt($webhook['data']['registration']); From d44c2ead0ec44df71e0393deafc74bcb60afa3cd Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Sat, 5 Dec 2020 02:17:02 +0530 Subject: [PATCH 30/36] feat: delete documents using delete worker --- app/controllers/api/database.php | 10 +- app/workers/deletes.php | 183 +++++++++++++++--- debug/nginx-stdout---supervisor-DAF7HT.log | 0 debug/php7-fpm-stdout---supervisor-TGTF4g.log | 0 debug/supervisord.pid | 1 + ...v1-schedule-stdout---supervisor-cNs2Cv.log | 1 + 6 files changed, 163 insertions(+), 32 deletions(-) create mode 100644 debug/nginx-stdout---supervisor-DAF7HT.log create mode 100644 debug/php7-fpm-stdout---supervisor-TGTF4g.log create mode 100644 debug/supervisord.pid create mode 100644 debug/v1-schedule-stdout---supervisor-cNs2Cv.log diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 55487eee52..eb6bd5ed4d 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -235,7 +235,7 @@ App::delete('/v1/database/collections/:collectionId') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_NONE) ->param('collectionId', '', new UID(), 'Collection unique ID.') - ->action(function ($collectionId, $response, $projectDB, $webhooks, $audits) { + ->action(function ($collectionId, $response, $projectDB, $webhooks, $audits, $deletes) { /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $webhooks */ @@ -250,7 +250,11 @@ App::delete('/v1/database/collections/:collectionId') if (!$projectDB->deleteDocument($collectionId)) { throw new Exception('Failed to remove collection from DB', 500); } - + + $deletes + ->setParam('document', $collection) + ; + $webhooks ->setParam('payload', $response->output($collection, Response::MODEL_COLLECTION)) ; @@ -262,7 +266,7 @@ App::delete('/v1/database/collections/:collectionId') ; $response->noContent(); - }, ['response', 'projectDB', 'webhooks', 'audits']); + }, ['response', 'projectDB', 'webhooks', 'audits', 'deletes']); App::post('/v1/database/collections/:collectionId/documents') ->desc('Create Document') diff --git a/app/workers/deletes.php b/app/workers/deletes.php index d5f7c9284d..d017ee1feb 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -10,31 +10,43 @@ use Appwrite\Database\Database; use Appwrite\Database\Adapter\MySQL as MySQLAdapter; use Appwrite\Database\Adapter\Redis as RedisAdapter; use Appwrite\Database\Document; +use Appwrite\Database\Validator\Authorization; use Appwrite\Storage\Device\Local; +use Utopia\CLI\Console; use Utopia\Config\Config; class DeletesV1 { public $args = []; + protected $consoleDB = null; + public function setUp(): void { } public function perform() { + $projectId = $this->args['projectId']; $document = $this->args['document']; + $document = new Document($document); - switch ($document->getCollection()) { + switch (strval($document->getCollection())) { case Database::SYSTEM_COLLECTION_PROJECTS: $this->deleteProject($document); break; - case Database::SYSTEM_COLLECTION_USERS: - $this->deleteUser($document); + case Database::SYSTEM_COLLECTION_FUNCTIONS: + $this->deleteFunction($document, $projectId); + break; + case Database::SYSTEM_COLLECTION_USERS: + $this->deleteUser($document, $projectId); + break; + case Database::SYSTEM_COLLECTION_COLLECTIONS: + $this->deleteDocuments($document,$projectId); break; - default: + Console::error('No lazy delete operation available for document of type: '.$document->getCollection()); break; } } @@ -43,50 +55,163 @@ class DeletesV1 { // ... Remove environment for this job } + + protected function deleteDocuments(Document $document, $projectId) + { + $collectionId = $document->getId(); + + // Delete Documents in the deleted collection + $this->deleteByGroup([ + '$collection='.$collectionId + ], $this->getProjectDB($projectId)); + } protected function deleteProject(Document $document) { - global $register; - - $consoleDB = new Database(); - $consoleDB->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register)); - $consoleDB->setNamespace('app_console'); // Main DB - $consoleDB->setMocks(Config::getParam('collections', [])); - // Delete all DBs - $consoleDB->deleteNamespace($document->getId()); + $this->getConsoleDB()->deleteNamespace($document->getId()); $uploads = new Local(APP_STORAGE_UPLOADS.'/app-'.$document->getId()); $cache = new Local(APP_STORAGE_CACHE.'/app-'.$document->getId()); + // Delete all storage directories $uploads->delete($uploads->getRoot(), true); $cache->delete($cache->getRoot(), true); } - protected function deleteUser(Document $user) + protected function deleteUser(Document $document, $projectId) { - global $projectDB; - - $tokens = $user->getAttribute('tokens', []); + $tokens = $document->getAttribute('tokens', []); foreach ($tokens as $token) { - if (!$projectDB->deleteDocument($token->getId())) { + if (!$this->getProjectDB($projectId)->deleteDocument($token->getId())) { throw new Exception('Failed to remove token from DB', 500); } } - $memberships = $projectDB->getCollection([ - 'limit' => 2000, // TODO add members limit - 'offset' => 0, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS, - 'userId='.$user->getId(), - ], - ]); + // Delete Memberships + $this->deleteByGroup([ + '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS, + 'userId='.$document->getId(), + ], $this->getProjectDB($projectId)); + } - foreach ($memberships as $membership) { - if (!$projectDB->deleteDocument($membership->getId())) { - throw new Exception('Failed to remove team membership from DB', 500); + protected function deleteFunction(Document $document, $projectId) + { + $projectDB = $this->getProjectDB($projectId); + $device = new Local(APP_STORAGE_FUNCTIONS.'/app-'.$projectId); + + // Delete Tags + $this->deleteByGroup([ + '$collection='.Database::SYSTEM_COLLECTION_TAGS, + 'functionId='.$document->getId(), + ], $projectDB, function(Document $document) use ($device) { + + if ($device->delete($document->getAttribute('path', ''))) { + Console::success('Delete code tag: '.$document->getAttribute('path', '')); + } + else { + Console::error('Dailed to delete code tag: '.$document->getAttribute('path', '')); + } + }); + + // Delete Executions + $this->deleteByGroup([ + '$collection='.Database::SYSTEM_COLLECTION_EXECUTIONS, + 'functionId='.$document->getId(), + ], $projectDB); + } + + protected function deleteById(Document $document, Database $database, callable $callback = null): bool + { + Authorization::disable(); + + if($database->deleteDocument($document->getId())) { + Console::success('Deleted document "'.$document->getId().'" successfully'); + + if(is_callable($callback)) { + $callback($document); + } + + return true; + } + else { + Console::error('Failed to delete document: '.$document->getId()); + return false; + } + + Authorization::reset(); + } + + protected function deleteByGroup(array $filters, Database $database, callable $callback = null) + { + $count = 0; + $chunk = 0; + $limit = 50; + $results = []; + $sum = $limit; + + $executionStart = \microtime(true); + + while($sum === $limit) { + $chunk++; + + Authorization::disable(); + + $results = $database->getCollection([ + 'limit' => $limit, + 'offset' => 0, + 'orderField' => '$id', + 'orderType' => 'ASC', + 'orderCast' => 'string', + 'filters' => $filters, + ]); + + Authorization::reset(); + + $sum = count($results); + + Console::info('Deleting chunk #'.$chunk.'. Found '.$sum.' documents'); + + foreach ($results as $document) { + $this->deleteById($document, $database, $callback); + $count++; } } + + $executionEnd = \microtime(true); + + Console::info("Deleted {$count} document by group in " . ($executionEnd - $executionStart) . " seconds"); } -} + + /** + * @return Database; + */ + protected function getConsoleDB(): Database + { + global $register; + + if($this->consoleDB === null) { + $this->consoleDB = new Database(); + $this->consoleDB->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register)); + $this->consoleDB->setNamespace('app_console'); // Main DB + $this->consoleDB->setMocks(Config::getParam('collections', [])); + } + + return $this->consoleDB; + } + + /** + * @return Database; + */ + protected function getProjectDB($projectId): Database + { + global $register; + + $projectDB = new Database(); + $projectDB->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register)); + $projectDB->setNamespace('app_'.$projectId); // Main DB + $projectDB->setMocks(Config::getParam('collections', [])); + + return $projectDB; + } +} \ No newline at end of file diff --git a/debug/nginx-stdout---supervisor-DAF7HT.log b/debug/nginx-stdout---supervisor-DAF7HT.log new file mode 100644 index 0000000000..e69de29bb2 diff --git a/debug/php7-fpm-stdout---supervisor-TGTF4g.log b/debug/php7-fpm-stdout---supervisor-TGTF4g.log new file mode 100644 index 0000000000..e69de29bb2 diff --git a/debug/supervisord.pid b/debug/supervisord.pid new file mode 100644 index 0000000000..920a139664 --- /dev/null +++ b/debug/supervisord.pid @@ -0,0 +1 @@ +43 diff --git a/debug/v1-schedule-stdout---supervisor-cNs2Cv.log b/debug/v1-schedule-stdout---supervisor-cNs2Cv.log new file mode 100644 index 0000000000..7570e1d217 --- /dev/null +++ b/debug/v1-schedule-stdout---supervisor-cNs2Cv.log @@ -0,0 +1 @@ +*** Starting scheduler worker From 4d6927b45af31806c6660580a759359049796f3c Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Sat, 5 Dec 2020 02:22:17 +0530 Subject: [PATCH 31/36] chore: update .gitignore --- .gitignore | 3 ++- debug/nginx-stdout---supervisor-DAF7HT.log | 0 debug/php7-fpm-stdout---supervisor-TGTF4g.log | 0 debug/supervisord.pid | 1 - debug/v1-schedule-stdout---supervisor-cNs2Cv.log | 1 - 5 files changed, 2 insertions(+), 3 deletions(-) delete mode 100644 debug/nginx-stdout---supervisor-DAF7HT.log delete mode 100644 debug/php7-fpm-stdout---supervisor-TGTF4g.log delete mode 100644 debug/supervisord.pid delete mode 100644 debug/v1-schedule-stdout---supervisor-cNs2Cv.log diff --git a/.gitignore b/.gitignore index d0b2a74730..224a970df4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ /tests/resources/storage/ /.idea/ .DS_Store -.php_cs.cache \ No newline at end of file +.php_cs.cache +debug/ \ No newline at end of file diff --git a/debug/nginx-stdout---supervisor-DAF7HT.log b/debug/nginx-stdout---supervisor-DAF7HT.log deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/debug/php7-fpm-stdout---supervisor-TGTF4g.log b/debug/php7-fpm-stdout---supervisor-TGTF4g.log deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/debug/supervisord.pid b/debug/supervisord.pid deleted file mode 100644 index 920a139664..0000000000 --- a/debug/supervisord.pid +++ /dev/null @@ -1 +0,0 @@ -43 diff --git a/debug/v1-schedule-stdout---supervisor-cNs2Cv.log b/debug/v1-schedule-stdout---supervisor-cNs2Cv.log deleted file mode 100644 index 7570e1d217..0000000000 --- a/debug/v1-schedule-stdout---supervisor-cNs2Cv.log +++ /dev/null @@ -1 +0,0 @@ -*** Starting scheduler worker From 9128348affbd111cb0bb9b62f578024b4c5a4d74 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Sat, 5 Dec 2020 11:01:59 +0530 Subject: [PATCH 32/36] chore: added e2e tests for collection deletion --- .../Database/DatabaseCustomServerTest.php | 113 +++++++++++++++++- 1 file changed, 112 insertions(+), 1 deletion(-) diff --git a/tests/e2e/Services/Database/DatabaseCustomServerTest.php b/tests/e2e/Services/Database/DatabaseCustomServerTest.php index dec0b108ef..f0c948db63 100644 --- a/tests/e2e/Services/Database/DatabaseCustomServerTest.php +++ b/tests/e2e/Services/Database/DatabaseCustomServerTest.php @@ -5,10 +5,121 @@ namespace Tests\E2E\Services\Database; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideServer; +use Tests\E2E\Client; class DatabaseCustomServerTest extends Scope { - use DatabaseBase; + // use DatabaseBase; use ProjectCustom; use SideServer; + + public function testDeleteCollection() + { + /** + * Test for SUCCESS + */ + + var_dump("Test Starting"); + + // Create collection + $actors = $this->client->call(Client::METHOD_POST, '/database/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'name' => 'Actors', + 'read' => ['*'], + 'write' => ['role:1', 'role:2'], + 'rules' => [ + [ + 'label' => 'First Name', + 'key' => 'firstName', + 'type' => 'text', + 'default' => '', + 'required' => true, + 'array' => false + ], + [ + 'label' => 'Last Name', + 'key' => 'lastName', + 'type' => 'text', + 'default' => '', + 'required' => true, + 'array' => false + ], + ], + ]); + + $this->assertEquals($actors['headers']['status-code'], 201); + $this->assertEquals($actors['body']['$collection'], 0); + $this->assertEquals($actors['body']['name'], 'Actors'); + $this->assertIsArray($actors['body']['$permissions']); + $this->assertIsArray($actors['body']['$permissions']['read']); + $this->assertIsArray($actors['body']['$permissions']['write']); + $this->assertCount(1, $actors['body']['$permissions']['read']); + $this->assertCount(2, $actors['body']['$permissions']['write']); + + // Add Documents to the collection + $document1 = $this->client->call(Client::METHOD_POST, '/database/collections/' . $actors['body']['$id'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'data' => [ + 'firstName' => 'Tom', + 'lastName' => 'Holland', + ], + 'read' => ['user:'.$this->getUser()['$id']], + 'write' => ['user:'.$this->getUser()['$id']], + ]); + + $document2 = $this->client->call(Client::METHOD_POST, '/database/collections/' . $actors['body']['$id'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'data' => [ + 'firstName' => 'Samuel', + 'lastName' => 'Jackson', + ], + 'read' => ['user:'.$this->getUser()['$id']], + 'write' => ['user:'.$this->getUser()['$id']], + ]); + + $this->assertEquals($document1['headers']['status-code'], 201); + $this->assertEquals($document1['body']['$collection'], $actors['body']['$id']); + $this->assertIsArray($document1['body']['$permissions']); + $this->assertIsArray($document1['body']['$permissions']['read']); + $this->assertIsArray($document1['body']['$permissions']['write']); + $this->assertCount(1, $document1['body']['$permissions']['read']); + $this->assertCount(1, $document1['body']['$permissions']['write']); + $this->assertEquals($document1['body']['firstName'], 'Tom'); + $this->assertEquals($document1['body']['lastName'], 'Holland'); + + $this->assertEquals($document2['headers']['status-code'], 201); + $this->assertEquals($document2['body']['$collection'], $actors['body']['$id']); + $this->assertIsArray($document2['body']['$permissions']); + $this->assertIsArray($document2['body']['$permissions']['read']); + $this->assertIsArray($document2['body']['$permissions']['write']); + $this->assertCount(1, $document2['body']['$permissions']['read']); + $this->assertCount(1, $document2['body']['$permissions']['write']); + $this->assertEquals($document2['body']['firstName'], 'Samuel'); + $this->assertEquals($document2['body']['lastName'], 'Jackson'); + + // Delete the actors collection + $response = $this->client->call(Client::METHOD_DELETE, '/database/collections/'.$actors['body']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], $this->getHeaders())); + + $this->assertEquals($response['headers']['status-code'], 204); + $this->assertEquals($response['body'],""); + + // Try to get the collection and check if it has been deleted + $response = $this->client->call(Client::METHOD_GET, '/database/collections/'.$actors['body']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'] + ], $this->getHeaders())); + + $this->assertEquals($response['headers']['status-code'], 404); + } } \ No newline at end of file From d0f7558ddf1c58ecaeffa9503ac84d0ccc11daee Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Sat, 5 Dec 2020 11:03:14 +0530 Subject: [PATCH 33/36] chore: review comments --- app/workers/deletes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/workers/deletes.php b/app/workers/deletes.php index d017ee1feb..14addd2d18 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -43,7 +43,7 @@ class DeletesV1 $this->deleteUser($document, $projectId); break; case Database::SYSTEM_COLLECTION_COLLECTIONS: - $this->deleteDocuments($document,$projectId); + $this->deleteDocuments($document, $projectId); break; default: Console::error('No lazy delete operation available for document of type: '.$document->getCollection()); From 54c3b4c84a049c6b7b9a7ba6cdf1a6b2adb03fbf Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Sat, 5 Dec 2020 11:04:58 +0530 Subject: [PATCH 34/36] chore: review comments --- tests/e2e/Services/Database/DatabaseCustomServerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Database/DatabaseCustomServerTest.php b/tests/e2e/Services/Database/DatabaseCustomServerTest.php index f0c948db63..30a1e908f7 100644 --- a/tests/e2e/Services/Database/DatabaseCustomServerTest.php +++ b/tests/e2e/Services/Database/DatabaseCustomServerTest.php @@ -9,7 +9,7 @@ use Tests\E2E\Client; class DatabaseCustomServerTest extends Scope { - // use DatabaseBase; + use DatabaseBase; use ProjectCustom; use SideServer; From df1822cd18b0f846d62ae51385c212b33e8303a9 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Sat, 5 Dec 2020 12:23:09 +0530 Subject: [PATCH 35/36] chore: review comments --- tests/e2e/Services/Database/DatabaseCustomServerTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/e2e/Services/Database/DatabaseCustomServerTest.php b/tests/e2e/Services/Database/DatabaseCustomServerTest.php index 30a1e908f7..2f0bdfc766 100644 --- a/tests/e2e/Services/Database/DatabaseCustomServerTest.php +++ b/tests/e2e/Services/Database/DatabaseCustomServerTest.php @@ -19,8 +19,6 @@ class DatabaseCustomServerTest extends Scope * Test for SUCCESS */ - var_dump("Test Starting"); - // Create collection $actors = $this->client->call(Client::METHOD_POST, '/database/collections', array_merge([ 'content-type' => 'application/json', From 59e8f99eb0fcb366a5c17b670cf66c25e6f41c87 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 5 Dec 2020 18:17:08 +0200 Subject: [PATCH 36/36] Updated files --- public/images/appwrite-footer-dark.svg | 3 ++- public/images/appwrite-footer-light.svg | 3 ++- public/images/appwrite-nav.svg | 3 ++- public/images/appwrite-white.svg | 3 ++- public/images/appwrite.svg | 3 ++- public/images/icon-nav.svg | 2 +- public/images/icon.svg | 2 +- 7 files changed, 12 insertions(+), 7 deletions(-) diff --git a/public/images/appwrite-footer-dark.svg b/public/images/appwrite-footer-dark.svg index dddc42887c..4e4fbc5e08 100644 --- a/public/images/appwrite-footer-dark.svg +++ b/public/images/appwrite-footer-dark.svg @@ -1 +1,2 @@ - \ No newline at end of file + + \ No newline at end of file diff --git a/public/images/appwrite-footer-light.svg b/public/images/appwrite-footer-light.svg index a6caddbacf..142c03ca0c 100644 --- a/public/images/appwrite-footer-light.svg +++ b/public/images/appwrite-footer-light.svg @@ -1 +1,2 @@ - \ No newline at end of file + + \ No newline at end of file diff --git a/public/images/appwrite-nav.svg b/public/images/appwrite-nav.svg index f416bde648..9a052d4533 100644 --- a/public/images/appwrite-nav.svg +++ b/public/images/appwrite-nav.svg @@ -1 +1,2 @@ - \ No newline at end of file + + \ No newline at end of file diff --git a/public/images/appwrite-white.svg b/public/images/appwrite-white.svg index 3d941c0d8a..b82cfef311 100644 --- a/public/images/appwrite-white.svg +++ b/public/images/appwrite-white.svg @@ -1 +1,2 @@ - \ No newline at end of file + + \ No newline at end of file diff --git a/public/images/appwrite.svg b/public/images/appwrite.svg index 53e6ea421e..7128b7b733 100644 --- a/public/images/appwrite.svg +++ b/public/images/appwrite.svg @@ -1 +1,2 @@ - \ No newline at end of file + + \ No newline at end of file diff --git a/public/images/icon-nav.svg b/public/images/icon-nav.svg index 96ee99bfad..6c91e72a4a 100644 --- a/public/images/icon-nav.svg +++ b/public/images/icon-nav.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/images/icon.svg b/public/images/icon.svg index d26e87d003..00ef5b67f8 100644 --- a/public/images/icon.svg +++ b/public/images/icon.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file