From 6bcbf113bc7ce35d0fdd47acf780c753f2fed245 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Sun, 28 Jun 2020 14:18:16 +0200 Subject: [PATCH 1/9] 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/9] 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/9] 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/9] 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/9] 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/9] 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/9] 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()) { From 79ee6c04e83cffbcf06e8e58d208ee2e0de5d886 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 30 Aug 2020 07:49:24 +0300 Subject: [PATCH 8/9] Updated error message --- app/controllers/api/database.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 548905edae..b455106bda 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -66,7 +66,7 @@ App::post('/v1/database/collections') 'rules' => $parsedRules, ]); } catch (AuthorizationException $exception) { - throw new Exception('Unauthorized action', 401); + throw new Exception('Unauthorized permissions', 401); } catch (StructureException $exception) { throw new Exception('Bad structure. '.$exception->getMessage(), 400); } catch (\Exception $exception) { @@ -266,7 +266,7 @@ App::put('/v1/database/collections/:collectionId') 'rules' => $parsedRules, ])); } catch (AuthorizationException $exception) { - throw new Exception('Unauthorized action', 401); + throw new Exception('Unauthorized permissions', 401); } catch (StructureException $exception) { throw new Exception('Bad structure. '.$exception->getMessage(), 400); } catch (\Exception $exception) { @@ -404,7 +404,7 @@ App::post('/v1/database/collections/:collectionId/documents') $authorization = new Authorization($parentDocument, 'write'); if (!$authorization->isValid($new->getPermissions())) { - throw new Exception('Unauthorized action', 401); + throw new Exception('Unauthorized permissions', 401); } $parentDocument @@ -429,7 +429,7 @@ App::post('/v1/database/collections/:collectionId/documents') try { $data = $projectDB->createDocument($data); } catch (AuthorizationException $exception) { - throw new Exception('Unauthorized action', 401); + throw new Exception('Unauthorized permissions', 401); } catch (StructureException $exception) { throw new Exception('Bad structure. '.$exception->getMessage(), 400); } catch (\Exception $exception) { @@ -624,7 +624,7 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') try { $data = $projectDB->updateDocument($data); } catch (AuthorizationException $exception) { - throw new Exception('Unauthorized action', 401); + throw new Exception('Unauthorized permissions', 401); } catch (StructureException $exception) { throw new Exception('Bad structure. '.$exception->getMessage(), 400); } catch (\Exception $exception) { @@ -680,7 +680,7 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') try { $projectDB->deleteDocument($documentId); } catch (AuthorizationException $exception) { - throw new Exception('Unauthorized action', 401); + throw new Exception('Unauthorized permissions', 401); } catch (StructureException $exception) { throw new Exception('Bad structure. '.$exception->getMessage(), 400); } catch (\Exception $exception) { From 44fe8310153b6822da0a75edc0d0d249d6e2e074 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 30 Aug 2020 07:53:13 +0300 Subject: [PATCH 9/9] Upgraded swoole version to 4.5.3 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2eeb7f84f3..1a2798c450 100755 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ FROM php:7.4-cli-alpine as step1 ENV TZ=Asia/Tel_Aviv \ PHP_REDIS_VERSION=5.3.0 \ - PHP_SWOOLE_VERSION=4.5.2 \ + PHP_SWOOLE_VERSION=4.5.3 \ PHP_XDEBUG_VERSION=sdebug_2_9-beta RUN \