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/CHANGES.md b/CHANGES.md index 9aadc06e46..ed3ba228c8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,14 +3,30 @@ ## Features - New route in Locale API to fetch a list of languages (@TorstenDittmann) +- 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)) +- 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)) @@ -40,6 +56,8 @@ - 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 +- 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 @@ -51,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 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 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/config/events.php b/app/config/events.php index 34c4a4f2de..db1bbc2434 100644 --- a/app/config/events.php +++ b/app/config/events.php @@ -19,6 +19,18 @@ 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.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.', ], @@ -40,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.', @@ -67,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/account.php b/app/controllers/api/account.php index 20fc724d2a..4e9d10ba0a 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); } @@ -115,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) @@ -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, @@ -624,10 +623,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']); @@ -818,8 +813,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') @@ -861,8 +854,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') @@ -918,8 +909,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()) @@ -962,9 +951,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') @@ -1069,23 +1056,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 +1118,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 +1125,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']); @@ -1153,6 +1150,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 +1162,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) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ @@ -1172,6 +1170,10 @@ 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 */ + + $isPreviliggedUser = Auth::isPreviliggedUser(Authorization::$roles); + $isAppUser = Auth::isAppUser(Authorization::$roles); $profile = $projectDB->getCollectionFirst([ // Get user by email address 'limit' => 1, @@ -1189,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, @@ -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', + ($isPreviliggedUser || $isAppUser) ? $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']); 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') @@ -1337,6 +1352,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') @@ -1347,7 +1363,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) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ @@ -1355,13 +1371,18 @@ 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 */ + $isPreviliggedUser = Auth::isPreviliggedUser(Authorization::$roles); + $isAppUser = Auth::isAppUser(Authorization::$roles); + $verificationSecret = Auth::tokenGenerator(); $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, @@ -1419,6 +1440,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', + ($isPreviliggedUser || $isAppUser) ? $verificationSecret : ''); + $audits ->setParam('userId', $user->getId()) ->setParam('event', 'account.verification.create') @@ -1429,12 +1461,13 @@ 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']); 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') 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/controllers/api/teams.php b/app/controllers/api/teams.php index 32d77e5e1d..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') @@ -581,6 +591,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, @@ -661,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') @@ -671,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); @@ -712,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/app/controllers/general.php b/app/controllers/general.php index f2e3dfe837..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' => '', @@ -222,6 +222,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', []) ; @@ -281,8 +282,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/deletes.php b/app/workers/deletes.php index d5f7c9284d..14addd2d18 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/app/workers/mails.php b/app/workers/mails.php index e80759cead..a7f6f6c235 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']; diff --git a/app/workers/webhooks.php b/app/workers/webhooks.php index cfbc8a6712..76c94a79fe 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 @@ -55,6 +56,7 @@ class WebhooksV1 continue; } + $id = $webhook['$id'] ?? ''; $name = $webhook['name'] ?? ''; $signature = $webhook['signature'] ?? 'not-yet-implemented'; $url = $webhook['url'] ?? ''; @@ -78,8 +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-User-Id: '.$userId, + 'X-'.APP_NAME.'-Webhook-Project-Id: '.$projectId, 'X-'.APP_NAME.'-Webhook-Signature: '.$signature, ] ); 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 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 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/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/src/Appwrite/Utopia/Response/Model/Session.php b/src/Appwrite/Utopia/Response/Model/Session.php index a0ca7b7143..a0edfbb4b6 100644 --- a/src/Appwrite/Utopia/Response/Model/Session.php +++ b/src/Appwrite/Utopia/Response/Model/Session.php @@ -15,6 +15,12 @@ class Session extends Model 'description' => 'Session ID.', 'example' => '5e5ea5c16897e', ]) + ->addRule('userId', [ + 'type' => self::TYPE_STRING, + 'description' => 'User ID.', + 'default' => '', + 'example' => '5e5bb8c16897e', + ]) ->addRule('expire', [ 'type' => self::TYPE_INTEGER, 'description' => 'Session expiration date in Unix timestamp.', @@ -114,7 +120,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, ]) ; diff --git a/src/Appwrite/Utopia/Response/Model/Token.php b/src/Appwrite/Utopia/Response/Model/Token.php index cf2347755f..9d00fe6f91 100644 --- a/src/Appwrite/Utopia/Response/Model/Token.php +++ b/src/Appwrite/Utopia/Response/Model/Token.php @@ -13,14 +13,19 @@ 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, + '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('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('expire', [ 'type' => self::TYPE_INTEGER, 'description' => 'Token expiration date in Unix timestamp.', 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/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index 831a0fbb7a..c17bd52878 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -81,23 +81,64 @@ 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']); - // 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']], - // ]; + $webhook = $this->client->call(Client::METHOD_POST, '/projects/'.$project['body']['$id'].'/webhooks', [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], + 'x-appwrite-project' => 'console', + ], [ + '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.verification.create', + 'account.verification.update', + 'account.delete', + 'account.sessions.create', + 'account.sessions.delete', + 'database.collections.create', + 'database.collections.update', + 'database.collections.delete', + 'database.documents.create', + 'database.documents.update', + 'database.documents.delete', + 'storage.files.create', + 'storage.files.update', + 'storage.files.delete', + 'users.create', + '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, + 'httpUser' => '', + 'httpPass' => '', + ]); + + $this->assertEquals(201, $webhook['headers']['status-code']); + $this->assertNotEmpty($webhook['body']); self::$project = [ '$id' => $project['body']['$id'], 'name' => $project['body']['name'], 'apiKey' => $key['body']['secret'], + 'webhookId' => $webhook['body']['$id'], ]; return self::$project; 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); 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/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index 0f6044d763..7bd96682d4 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 @@ -648,6 +644,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(); @@ -661,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']); @@ -939,6 +936,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(); diff --git a/tests/e2e/Services/Database/DatabaseCustomServerTest.php b/tests/e2e/Services/Database/DatabaseCustomServerTest.php index dec0b108ef..2f0bdfc766 100644 --- a/tests/e2e/Services/Database/DatabaseCustomServerTest.php +++ b/tests/e2e/Services/Database/DatabaseCustomServerTest.php @@ -5,10 +5,119 @@ 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 ProjectCustom; use SideServer; + + public function testDeleteCollection() + { + /** + * Test for SUCCESS + */ + + // 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 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 new file mode 100644 index 0000000000..5dc6cfafe2 --- /dev/null +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -0,0 +1,552 @@ +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($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']); + $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($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'); + $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($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'); + $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($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'); + $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 + */ + $file = $this->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(); + + $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->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'); + $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 + */ + return ['fileId' => $file['body']['$id']]; + } + + /** + * @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($file['headers']['status-code'], 200); + $this->assertNotEmpty($file['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'], 'storage.files.update'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $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'); + $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->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'); + $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; + } + + 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($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']); + $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($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']); + $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($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']); + $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($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']); + $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($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']); + $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 new file mode 100644 index 0000000000..f4ed81ac0f --- /dev/null +++ b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php @@ -0,0 +1,757 @@ +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($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']); + $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, + ]; + } + + 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($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']); + $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 + */ + 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($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']); + $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($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']); + $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['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']); + $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($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']); + $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($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']); + $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($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']); + $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($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']); + $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($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']); + $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($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']); + $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($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']); + $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($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']); + $this->assertIsNumeric($webhook['data']['expire']); + + $data['secret'] = $webhook['data']['secret']; + + 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($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']); + $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 diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php new file mode 100644 index 0000000000..360dee2cb3 --- /dev/null +++ b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php @@ -0,0 +1,264 @@ +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($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']); + $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/'.$actors['body']['$id'], 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($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']); + $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 []; + } + + 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->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']); + $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->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']); + $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->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']); + $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 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