From 6bcbf113bc7ce35d0fdd47acf780c753f2fed245 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Sun, 28 Jun 2020 14:18:16 +0200 Subject: [PATCH 1/7] add basic user delete endpoint - deletes user - deletes sessions of user --- app/controllers/api/users.php | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index d1744bf48a..a73c656881 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -188,6 +188,38 @@ $utopia->get('/v1/users/:userId') } ); +$utopia->delete('/v1/users/:userId') + ->desc('Delete User') + ->groups(['api', 'users']) + ->label('scope', 'users.write') + ->label('sdk.platform', [APP_PLATFORM_SERVER]) + ->label('sdk.namespace', 'users') + ->label('sdk.method', 'deleteUser') + ->label('sdk.description', '/docs/references/users/delete-user.md') + ->label('abuse-limit', 100) + ->param('userId', '', function () {return new UID();}, 'User unique ID.') + ->action( + function ($userId) use ($response, $request, $projectDB) { + $user = $projectDB->getDocument($userId); + + if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { + throw new Exception('User not found', 404); + } + if (!$projectDB->deleteDocument($userId)) { + throw new Exception('Failed to remove file from DB', 500); + } + $tokens = $user->getAttribute('tokens', []); + + foreach ($tokens as $token) { + if (!$projectDB->deleteDocument($token->getId())) { + throw new Exception('Failed to remove token from DB', 500); + } + } + + $response->noContent(); + } + ); + $utopia->get('/v1/users/:userId/prefs') ->desc('Get User Preferences') ->groups(['api', 'users']) From 4c6a300a220af5ecf09ef3680fefb06800800192 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 2 Jul 2020 23:48:37 +0200 Subject: [PATCH 2/7] delete leftovers & reserve id - delete team memberships - create a reserved id --- app/config/collections.php | 7 +++++++ app/controllers/api/users.php | 28 ++++++++++++++++++++++++++++ src/Appwrite/Database/Database.php | 1 + 3 files changed, 36 insertions(+) diff --git a/app/config/collections.php b/app/config/collections.php index 3141419c1b..f40488950e 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1187,6 +1187,13 @@ $collections = [ ], ], ], + Database::SYSTEM_COLLECTION_RESERVED => [ + '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, + '$id' => Database::SYSTEM_COLLECTION_RESERVED, + '$permissions' => ['read' => ['*']], + 'name' => 'Reserved', + 'structure' => true, + ], ]; /* diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index a73c656881..01c4d01622 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -208,6 +208,19 @@ $utopia->delete('/v1/users/:userId') if (!$projectDB->deleteDocument($userId)) { throw new Exception('Failed to remove file from DB', 500); } + + $reservedId = $projectDB->createDocument([ + '$collection' => Database::SYSTEM_COLLECTION_RESERVED, + '$id' => $userId, + '$permissions' => [ + 'read' => ['*'], + ], + ]); + + if (false === $reservedId) { + throw new Exception('Failed saving reserved id to DB', 500); + } + $tokens = $user->getAttribute('tokens', []); foreach ($tokens as $token) { @@ -216,6 +229,21 @@ $utopia->delete('/v1/users/:userId') } } + $memberships = $projectDB->getCollection([ + 'limit' => 2000, + 'offset' => 0, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS, + 'userId='.$userId, + ], + ]); + + foreach ($memberships as $membership) { + if (!$projectDB->deleteDocument($membership->getId())) { + throw new Exception('Failed to remove team membership from DB', 500); + } + } + $response->noContent(); } ); diff --git a/src/Appwrite/Database/Database.php b/src/Appwrite/Database/Database.php index e75eb01b51..bd61eb3ffd 100644 --- a/src/Appwrite/Database/Database.php +++ b/src/Appwrite/Database/Database.php @@ -23,6 +23,7 @@ class Database const SYSTEM_COLLECTION_USAGES = 'usages'; //TODO add structure const SYSTEM_COLLECTION_DOMAINS = 'domains'; const SYSTEM_COLLECTION_CERTIFICATES = 'certificates'; + const SYSTEM_COLLECTION_RESERVED = 'reserved'; // Auth, Account and Users (private to user) const SYSTEM_COLLECTION_USERS = 'users'; From 7484bdc34751231d8fa5336ab62b7da24bbb5d91 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Fri, 3 Jul 2020 00:42:21 +0200 Subject: [PATCH 3/7] outsource user leftovers to delete worker --- app/controllers/api/users.php | 27 +++------------------------ app/workers/deletes.php | 31 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 01c4d01622..2592076d3f 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -199,14 +199,14 @@ $utopia->delete('/v1/users/:userId') ->label('abuse-limit', 100) ->param('userId', '', function () {return new UID();}, 'User unique ID.') ->action( - function ($userId) use ($response, $request, $projectDB) { + function ($userId) use ($response, $deletes, $projectDB) { $user = $projectDB->getDocument($userId); if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { throw new Exception('User not found', 404); } if (!$projectDB->deleteDocument($userId)) { - throw new Exception('Failed to remove file from DB', 500); + throw new Exception('Failed to remove user from DB', 500); } $reservedId = $projectDB->createDocument([ @@ -221,28 +221,7 @@ $utopia->delete('/v1/users/:userId') throw new Exception('Failed saving reserved id to DB', 500); } - $tokens = $user->getAttribute('tokens', []); - - foreach ($tokens as $token) { - if (!$projectDB->deleteDocument($token->getId())) { - throw new Exception('Failed to remove token from DB', 500); - } - } - - $memberships = $projectDB->getCollection([ - 'limit' => 2000, - 'offset' => 0, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS, - 'userId='.$userId, - ], - ]); - - foreach ($memberships as $membership) { - if (!$projectDB->deleteDocument($membership->getId())) { - throw new Exception('Failed to remove team membership from DB', 500); - } - } + $deletes->setParam('document', $user); $response->noContent(); } diff --git a/app/workers/deletes.php b/app/workers/deletes.php index aa0cbe6c2d..8d12bd64d0 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -29,6 +29,9 @@ class DeletesV1 case Database::SYSTEM_COLLECTION_PROJECTS: $this->deleteProject($document); break; + case Database::SYSTEM_COLLECTION_USERS: + $this->deleteUser($document); + break; default: break; @@ -52,4 +55,32 @@ class DeletesV1 $uploads->delete($uploads->getRoot(), true); $cache->delete($cache->getRoot(), true); } + + protected function deleteUser(Document $user) + { + global $projectDB; + + $tokens = $user->getAttribute('tokens', []); + + foreach ($tokens as $token) { + if (!$projectDB->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(), + ], + ]); + + foreach ($memberships as $membership) { + if (!$projectDB->deleteDocument($membership->getId())) { + throw new Exception('Failed to remove team membership from DB', 500); + } + } + } } From edde41e50f55b983ddd34374d626ec205fd116a8 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Fri, 28 Aug 2020 20:53:19 +0200 Subject: [PATCH 4/7] delete unique key from db --- app/controllers/api/users.php | 4 ++++ src/Appwrite/Database/Adapter.php | 9 +++++++++ src/Appwrite/Database/Adapter/MySQL.php | 22 +++++++++++++++++++++- src/Appwrite/Database/Adapter/Redis.php | 16 ++++++++++++++++ src/Appwrite/Database/Database.php | 12 ++++++++++++ 5 files changed, 62 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 2592076d3f..971396ffc3 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -209,6 +209,10 @@ $utopia->delete('/v1/users/:userId') throw new Exception('Failed to remove user from DB', 500); } + if (!$projectDB->deleteUniqueKey(md5('users:email='.$user->getAttribute('email', null)))) { + throw new Exception('Failed to remove unique key from DB', 500); + } + $reservedId = $projectDB->createDocument([ '$collection' => Database::SYSTEM_COLLECTION_RESERVED, '$id' => $userId, diff --git a/src/Appwrite/Database/Adapter.php b/src/Appwrite/Database/Adapter.php index 6fa79cbdcd..3526256b22 100644 --- a/src/Appwrite/Database/Adapter.php +++ b/src/Appwrite/Database/Adapter.php @@ -88,6 +88,15 @@ abstract class Adapter */ abstract public function deleteDocument($id); + /** + * Delete Unique Key. + * + * @param int $key + * + * @return array + */ + abstract public function deleteUniqueKey($key); + /** * Create Namespace. * diff --git a/src/Appwrite/Database/Adapter/MySQL.php b/src/Appwrite/Database/Adapter/MySQL.php index f766666306..52dd521d9e 100644 --- a/src/Appwrite/Database/Adapter/MySQL.php +++ b/src/Appwrite/Database/Adapter/MySQL.php @@ -191,7 +191,7 @@ class MySQL extends Adapter $st = $this->getPDO()->prepare('INSERT INTO `'.$this->getNamespace().'.database.unique` SET `key` = :key; '); - + $st->bindValue(':key', \md5($data['$collection'].':'.$key.'='.$value), PDO::PARAM_STR); if (!$st->execute()) { @@ -366,6 +366,26 @@ class MySQL extends Adapter return []; } + /** + * Delete Unique Key. + * + * @param int $id + * + * @return array + * + * @throws Exception + */ + public function deleteUniqueKey($key) + { + $st1 = $this->getPDO()->prepare('DELETE FROM `'.$this->getNamespace().'.database.unique` WHERE `key` = :key'); + + $st1->bindValue(':key', $key, PDO::PARAM_STR); + + $st1->execute(); + + return []; + } + /** * Create Relation. * diff --git a/src/Appwrite/Database/Adapter/Redis.php b/src/Appwrite/Database/Adapter/Redis.php index 15a7a887d8..cb35378a9c 100644 --- a/src/Appwrite/Database/Adapter/Redis.php +++ b/src/Appwrite/Database/Adapter/Redis.php @@ -153,6 +153,22 @@ class Redis extends Adapter return $data; } + /** + * Delete Unique Key. + * + * @param $key + * + * @return array + * + * @throws Exception + */ + public function deleteUniqueKey($key) + { + $data = $this->adapter->deleteUniqueKey($key); + + return $data; + } + /** * Create Namespace. * diff --git a/src/Appwrite/Database/Database.php b/src/Appwrite/Database/Database.php index bd61eb3ffd..d6650d3021 100644 --- a/src/Appwrite/Database/Database.php +++ b/src/Appwrite/Database/Database.php @@ -298,6 +298,18 @@ class Database return new Document($this->adapter->deleteDocument($id)); } + /** + * @param int $key + * + * @return Document|false + * + * @throws AuthorizationException + */ + public function deleteUniqueKey($key) + { + return new Document($this->adapter->deleteUniqueKey($key)); + } + /** * @return array */ From d9d32dbf46042270f01029adc725ad02de33e630 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Fri, 28 Aug 2020 23:42:24 +0200 Subject: [PATCH 5/7] fix phpdoc entry --- src/Appwrite/Database/Adapter/MySQL.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Database/Adapter/MySQL.php b/src/Appwrite/Database/Adapter/MySQL.php index 52dd521d9e..6b2114f145 100644 --- a/src/Appwrite/Database/Adapter/MySQL.php +++ b/src/Appwrite/Database/Adapter/MySQL.php @@ -369,7 +369,7 @@ class MySQL extends Adapter /** * Delete Unique Key. * - * @param int $id + * @param int $key * * @return array * From 532b547f9bdd9e7d5a7f77013ba5bcadec16486e Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Fri, 28 Aug 2020 23:56:22 +0200 Subject: [PATCH 6/7] add docs & e2e test --- docs/references/users/delete-user.md | 1 + tests/e2e/Services/Users/UsersBase.php | 28 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 docs/references/users/delete-user.md diff --git a/docs/references/users/delete-user.md b/docs/references/users/delete-user.md new file mode 100644 index 0000000000..7eb4963485 --- /dev/null +++ b/docs/references/users/delete-user.md @@ -0,0 +1 @@ +Delete a user by its unique ID. \ No newline at end of file diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index f66841ce05..c4c99de29e 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -168,6 +168,34 @@ trait UsersBase return $data; } + /** + * @depends testGetUser + */ + 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); + + /** + * Test for FAILURE + */ + $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'], 404); + + return $data; + } + // TODO add test for session delete // TODO add test for all sessions delete } \ No newline at end of file From 2d107e3ed999bfbc4245c3bbdfb5e21024dcc5ed Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Fri, 28 Aug 2020 23:58:31 +0200 Subject: [PATCH 7/7] revert whitespace removal --- src/Appwrite/Database/Adapter/MySQL.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Database/Adapter/MySQL.php b/src/Appwrite/Database/Adapter/MySQL.php index 6b2114f145..3d966d165b 100644 --- a/src/Appwrite/Database/Adapter/MySQL.php +++ b/src/Appwrite/Database/Adapter/MySQL.php @@ -191,7 +191,7 @@ class MySQL extends Adapter $st = $this->getPDO()->prepare('INSERT INTO `'.$this->getNamespace().'.database.unique` SET `key` = :key; '); - + $st->bindValue(':key', \md5($data['$collection'].':'.$key.'='.$value), PDO::PARAM_STR); if (!$st->execute()) {