From e462d73f65e7f1bb14924b9d5bcd83d6d8b3ca21 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 11 Feb 2024 09:31:09 +0000 Subject: [PATCH] prevent user with active team, from deletion --- app/config/errors.php | 5 ++ app/controllers/api/account.php | 19 ++++- src/Appwrite/Extend/Exception.php | 5 +- .../Account/AccountConsoleClientTest.php | 77 ++++++++++++++++++- 4 files changed, 100 insertions(+), 6 deletions(-) diff --git a/app/config/errors.php b/app/config/errors.php index b95f27915..7dedc373e 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -251,6 +251,11 @@ return [ 'description' => 'User phone is already verified', 'code' => 409 ], + Exception::USER_DELETION_PROHIBITED => [ + 'name' => Exception::USER_DELETION_PROHIBITED, + 'description' => 'User deletion is not allowed for users with active memberships. Please delete all confirmed memberships before deleting the account.', + 'code' => 400 + ], /** Teams */ Exception::TEAM_NOT_FOUND => [ diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index f1575a08d..f34082399 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -3033,12 +3033,27 @@ App::delete('/v1/account') ->label('sdk.description', '/docs/references/account/delete.md') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) - ->inject('user') + ->inject('project') ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') ->inject('queueForDeletes') - ->action(function (Document $user, Response $response, Database $dbForProject, Event $queueForEvents, Delete $queueForDeletes) { + ->action(function (Document $user, Document $project, Response $response, Database $dbForProject, Event $queueForEvents, Delete $queueForDeletes) { + if ($user->isEmpty()) { + throw new Exception(Exception::USER_NOT_FOUND); + } + + if ($project->getId() === 'console') { + // get all memberships + $memberships = $user->getAttribute('memberships', []); + foreach ($memberships as $membership) { + // prevent deletion if at least one active membership + if ($membership->getAttribute('confirm', false)) { + throw new Exception(Exception::USER_DELETION_PROHIBITED); + } + } + } + if ($user->isEmpty()) { throw new Exception(Exception::USER_NOT_FOUND); } diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index 43095334a..3cedbf11c 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -84,8 +84,9 @@ class Exception extends \Exception public const USER_OAUTH2_BAD_REQUEST = 'user_oauth2_bad_request'; public const USER_OAUTH2_UNAUTHORIZED = 'user_oauth2_unauthorized'; public const USER_OAUTH2_PROVIDER_ERROR = 'user_oauth2_provider_error'; - public const USER_EMAIL_ALREADY_VERIFIED = 'user_email_alread_verified'; - public const USER_PHONE_ALREADY_VERIFIED = 'user_phone_already_verified'; + public const USER_EMAIL_ALREADY_VERIFIED = 'user_email_alread_verified'; + public const USER_PHONE_ALREADY_VERIFIED = 'user_phone_already_verified'; + public const USER_DELETION_PROHIBITED = 'user_deletion_prohibited'; /** Teams */ public const TEAM_NOT_FOUND = 'team_not_found'; diff --git a/tests/e2e/Services/Account/AccountConsoleClientTest.php b/tests/e2e/Services/Account/AccountConsoleClientTest.php index 3d4445dcc..e3de52254 100644 --- a/tests/e2e/Services/Account/AccountConsoleClientTest.php +++ b/tests/e2e/Services/Account/AccountConsoleClientTest.php @@ -2,17 +2,90 @@ namespace Tests\E2E\Services\Account; -use Appwrite\Extend\Exception; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\ProjectConsole; use Tests\E2E\Scopes\SideClient; use Utopia\Database\Helpers\ID; use Tests\E2E\Client; -use Utopia\Database\Validator\Datetime as DatetimeValidator; class AccountConsoleClientTest extends Scope { use AccountBase; use ProjectConsole; use SideClient; + + public function testDeleteAccount(): void + { + $email = uniqid() . 'user@localhost.test'; + $password = 'password'; + $name = 'User Name'; + + $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'userId' => ID::unique(), + 'email' => $email, + 'password' => $password, + 'name' => $name, + ]); + + $this->assertEquals($response['headers']['status-code'], 201); + + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'email' => $email, + 'password' => $password, + ]); + + $this->assertEquals($response['headers']['status-code'], 201); + + $session = $response['cookies']['a_session_' . $this->getProject()['$id']]; + + // create team + $team = $this->client->call(Client::METHOD_POST, '/teams', [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, + ], [ + 'teamId' => 'unique()', + 'name' => 'myteam' + ]); + $this->assertEquals($team['headers']['status-code'], 201); + + $teamId = $team['body']['$id']; + + $response = $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($response['headers']['status-code'], 400); + + // DELETE TEAM + $response = $this->client->call(Client::METHOD_DELETE, '/teams/' . $teamId, array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, + ])); + $this->assertEquals($response['headers']['status-code'], 204); + sleep(2); + + $response = $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($response['headers']['status-code'], 204); + } }