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