diff --git a/app/config/collections2.php b/app/config/collections2.php index 9ba9635457..866d1cb1b6 100644 --- a/app/config/collections2.php +++ b/app/config/collections2.php @@ -695,6 +695,17 @@ $collections = [ 'array' => true, 'filters' => ['json'], ], + [ + '$id' => 'deleted', + 'type' => Database::VAR_BOOLEAN, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], ], 'indexes' => [ [ diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index bc34f81dda..3ad04efe81 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -78,7 +78,9 @@ App::post('/v1/account') $limit = $project->getAttribute('auths', [])['limit'] ?? 0; if ($limit !== 0) { - $sum = $dbForInternal->count('users', [], APP_LIMIT_USERS); + $sum = $dbForInternal->count('users', [ + new Query('deleted', Query::TYPE_EQUAL, [false]), + ], APP_LIMIT_USERS); if ($sum >= $limit) { throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501); @@ -105,6 +107,7 @@ App::post('/v1/account') 'sessions' => [], 'tokens' => [], 'memberships' => [], + 'deleted' => false ])); } catch (Duplicate $th) { throw new Exception('Account already exists', 409); @@ -165,7 +168,7 @@ App::post('/v1/account/sessions') $email = \strtolower($email); $protocol = $request->getProtocol(); - $profile = $dbForInternal->findOne('users', [new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address + $profile = $dbForInternal->findOne('users', [new Query('deleted', Query::TYPE_EQUAL, [false]), new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address if (!$profile || !Auth::passwordVerify($password, $profile->getAttribute('password'))) { $audits @@ -462,13 +465,13 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $name = $oauth2->getUserName($accessToken); $email = $oauth2->getUserEmail($accessToken); - $user = $dbForInternal->findOne('users', [new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address + $user = $dbForInternal->findOne('users', [new Query('deleted', Query::TYPE_EQUAL, [false]), new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address if ($user === false || $user->isEmpty()) { // Last option -> create the user, generate random password $limit = $project->getAttribute('auths', [])['limit'] ?? 0; if ($limit !== 0) { - $sum = $dbForInternal->count('users', [], APP_LIMIT_COUNT); + $sum = $dbForInternal->count('users', [ new Query('deleted', Query::TYPE_EQUAL, [false]),], APP_LIMIT_COUNT); if ($sum >= $limit) { throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501); @@ -495,6 +498,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'sessions' => [], 'tokens' => [], 'memberships' => [], + 'deleted' => false ])); } catch (Duplicate $th) { throw new Exception('Account already exists', 409); @@ -639,7 +643,9 @@ App::post('/v1/account/sessions/anonymous') $limit = $project->getAttribute('auths', [])['limit'] ?? 0; if ($limit !== 0) { - $sum = $dbForInternal->count('users', [], APP_LIMIT_COUNT); + $sum = $dbForInternal->count('users', [ + new Query('deleted', Query::TYPE_EQUAL, [false]), + ], APP_LIMIT_COUNT); if ($sum >= $limit) { throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501); @@ -665,6 +671,7 @@ App::post('/v1/account/sessions/anonymous') 'sessions' => [], 'tokens' => [], 'memberships' => [], + 'deleted' => false ])); Authorization::reset(); @@ -1221,6 +1228,8 @@ App::delete('/v1/account') $protocol = $request->getProtocol(); $user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('status', false)); + // TODO Seems to be related to users.php/App::delete('/v1/users/:userId'). Can we share code between these two? Do todos below apply to users.php? + // TODO delete all tokens or only current session? // TODO delete all user data according to GDPR. Make sure everything is backed up and backups are deleted later /* @@ -1463,7 +1472,7 @@ App::post('/v1/account/recovery') $isAppUser = Auth::isAppUser(Authorization::$roles); $email = \strtolower($email); - $profile = $dbForInternal->findOne('users', [new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address + $profile = $dbForInternal->findOne('users', [new Query('deleted', Query::TYPE_EQUAL, [false]), new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address if (!$profile) { throw new Exception('User not found', 404); @@ -1566,7 +1575,7 @@ App::put('/v1/account/recovery') $profile = $dbForInternal->getDocument('users', $userId); - if ($profile->isEmpty()) { + if ($profile->isEmpty() || $profile->getAttribute('deleted')) { throw new Exception('User not found', 404); } diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index c6c8207e21..20266d1fda 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -66,6 +66,7 @@ App::post('/v1/users') 'sessions' => [], 'tokens' => [], 'memberships' => [], + 'deleted' => false ])); } catch (Duplicate $th) { throw new Exception('Account already exists', 409); @@ -106,13 +107,17 @@ App::get('/v1/users') if (!empty($after)) { $afterUser = $dbForInternal->getDocument('users', $after); - if ($afterUser->isEmpty()) { + if ($afterUser->isEmpty() || $afterUser->getAttribute('deleted')) { throw new Exception('User for after not found', 400); } } - $results = $dbForInternal->find('users', [], $limit, $offset, [], [$orderType], $afterUser ?? null); - $sum = $dbForInternal->count('users', [], APP_LIMIT_COUNT); + $results = $dbForInternal->find('users', [ + new Query('deleted', Query::TYPE_EQUAL, [false]), + ], $limit, $offset, [], [$orderType], $afterUser ?? null); + $sum = $dbForInternal->count('users', [ + new Query('deleted', Query::TYPE_EQUAL, [false]), + ], APP_LIMIT_COUNT); $usage ->setParam('users.read', 1) @@ -146,7 +151,7 @@ App::get('/v1/users/:userId') $user = $dbForInternal->getDocument('users', $userId); - if ($user->isEmpty()) { + if ($user->isEmpty() || $user->getAttribute('deleted')) { throw new Exception('User not found', 404); } @@ -178,7 +183,7 @@ App::get('/v1/users/:userId/prefs') $user = $dbForInternal->getDocument('users', $userId); - if ($user->isEmpty()) { + if ($user->isEmpty() || $user->getAttribute('deleted')) { throw new Exception('User not found', 404); } @@ -214,7 +219,7 @@ App::get('/v1/users/:userId/sessions') $user = $dbForInternal->getDocument('users', $userId); - if ($user->isEmpty()) { + if ($user->isEmpty() || $user->getAttribute('deleted')) { throw new Exception('User not found', 404); } @@ -266,7 +271,7 @@ App::get('/v1/users/:userId/logs') $user = $dbForInternal->getDocument('users', $userId); - if ($user->isEmpty()) { + if ($user->isEmpty() || $user->getAttribute('deleted')) { throw new Exception('User not found', 404); } @@ -374,7 +379,7 @@ App::patch('/v1/users/:userId/status') $user = $dbForInternal->getDocument('users', $userId); - if ($user->isEmpty()) { + if ($user->isEmpty() || $user->getAttribute('deleted')) { throw new Exception('User not found', 404); } @@ -410,7 +415,7 @@ App::patch('/v1/users/:userId/verification') $user = $dbForInternal->getDocument('users', $userId); - if ($user->isEmpty()) { + if ($user->isEmpty() || $user->getAttribute('deleted')) { throw new Exception('User not found', 404); } @@ -446,7 +451,7 @@ App::patch('/v1/users/:userId/name') $user = $dbForInternal->getDocument('users', $userId); - if ($user->isEmpty()) { + if ($user->isEmpty() || $user->getAttribute('deleted')) { throw new Exception('User not found', 404); } @@ -485,7 +490,7 @@ App::patch('/v1/users/:userId/password') $user = $dbForInternal->getDocument('users', $userId); - if ($user->isEmpty()) { + if ($user->isEmpty() || $user->getAttribute('deleted')) { throw new Exception('User not found', 404); } @@ -525,7 +530,7 @@ App::patch('/v1/users/:userId/email') $user = $dbForInternal->getDocument('users', $userId); - if ($user->isEmpty()) { + if ($user->isEmpty() || $user->getAttribute('deleted')) { throw new Exception('User not found', 404); } @@ -569,7 +574,7 @@ App::patch('/v1/users/:userId/prefs') $user = $dbForInternal->getDocument('users', $userId); - if ($user->isEmpty()) { + if ($user->isEmpty() || $user->getAttribute('deleted')) { throw new Exception('User not found', 404); } @@ -606,7 +611,7 @@ App::delete('/v1/users/:userId/sessions/:sessionId') $user = $dbForInternal->getDocument('users', $userId); - if ($user->isEmpty()) { + if ($user->isEmpty() || $user->getAttribute('deleted')) { throw new Exception('User not found', 404); } @@ -661,7 +666,7 @@ App::delete('/v1/users/:userId/sessions') $user = $dbForInternal->getDocument('users', $userId); - if ($user->isEmpty()) { + if ($user->isEmpty() || $user->getAttribute('deleted')) { throw new Exception('User not found', 404); } @@ -710,13 +715,17 @@ App::delete('/v1/users/:userId') $user = $dbForInternal->getDocument('users', $userId); - if ($user->isEmpty()) { + if ($user->isEmpty() || $user->getAttribute('deleted')) { throw new Exception('User not found', 404); } - if (!$dbForInternal->deleteDocument('users', $userId)) { - throw new Exception('Failed to remove user from DB', 500); - } + $emptyUser = clone $user; + $emptyUser->setAttribute("name", null); + $emptyUser->setAttribute("email", null); + $emptyUser->setAttribute("password", null); + $emptyUser->setAttribute("deleted", true); + + $dbForInternal->updateDocument('users', $userId, $emptyUser); $deletes ->setParam('type', DELETE_TYPE_DOCUMENT) diff --git a/composer.lock b/composer.lock index 8dac7bcc6d..c319daa5a3 100644 --- a/composer.lock +++ b/composer.lock @@ -248,16 +248,16 @@ }, { "name": "chillerlan/php-settings-container", - "version": "2.1.1", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/chillerlan/php-settings-container.git", - "reference": "98ccc1b31b31a53bcb563465c4961879b2b93096" + "reference": "ec834493a88682dd69652a1eeaf462789ed0c5f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/98ccc1b31b31a53bcb563465c4961879b2b93096", - "reference": "98ccc1b31b31a53bcb563465c4961879b2b93096", + "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/ec834493a88682dd69652a1eeaf462789ed0c5f5", + "reference": "ec834493a88682dd69652a1eeaf462789ed0c5f5", "shasum": "" }, "require": { @@ -307,7 +307,7 @@ "type": "ko_fi" } ], - "time": "2021-01-06T15:57:03+00:00" + "time": "2021-09-06T15:17:01+00:00" }, { "name": "colinmollenhour/credis", @@ -355,16 +355,16 @@ }, { "name": "composer/package-versions-deprecated", - "version": "1.11.99.3", + "version": "1.11.99.4", "source": { "type": "git", "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "fff576ac850c045158a250e7e27666e146e78d18" + "reference": "b174585d1fe49ceed21928a945138948cb394600" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/fff576ac850c045158a250e7e27666e146e78d18", - "reference": "fff576ac850c045158a250e7e27666e146e78d18", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b174585d1fe49ceed21928a945138948cb394600", + "reference": "b174585d1fe49ceed21928a945138948cb394600", "shasum": "" }, "require": { @@ -408,7 +408,7 @@ "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", "support": { "issues": "https://github.com/composer/package-versions-deprecated/issues", - "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.3" + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.4" }, "funding": [ { @@ -424,7 +424,7 @@ "type": "tidelift" } ], - "time": "2021-08-17T13:49:14+00:00" + "time": "2021-09-13T08:41:34+00:00" }, { "name": "dragonmantank/cron-expression", @@ -3383,16 +3383,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.12.0", + "version": "v4.13.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "6608f01670c3cc5079e18c1dab1104e002579143" + "reference": "50953a2691a922aa1769461637869a0a2faa3f53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6608f01670c3cc5079e18c1dab1104e002579143", - "reference": "6608f01670c3cc5079e18c1dab1104e002579143", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/50953a2691a922aa1769461637869a0a2faa3f53", + "reference": "50953a2691a922aa1769461637869a0a2faa3f53", "shasum": "" }, "require": { @@ -3433,9 +3433,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.12.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.0" }, - "time": "2021-07-21T10:44:31+00:00" + "time": "2021-09-20T12:20:58+00:00" }, { "name": "openlss/lib-array2xml", @@ -3712,16 +3712,16 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.4.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + "reference": "30f38bffc6f24293dadd1823936372dfa9e86e2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/30f38bffc6f24293dadd1823936372dfa9e86e2f", + "reference": "30f38bffc6f24293dadd1823936372dfa9e86e2f", "shasum": "" }, "require": { @@ -3729,7 +3729,8 @@ "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "ext-tokenizer": "*" + "ext-tokenizer": "*", + "psalm/phar": "^4.8" }, "type": "library", "extra": { @@ -3755,39 +3756,39 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.0" }, - "time": "2020-09-17T18:55:26+00:00" + "time": "2021-09-17T15:28:14+00:00" }, { "name": "phpspec/prophecy", - "version": "1.13.0", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea" + "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea", - "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", + "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", "shasum": "" }, "require": { "doctrine/instantiator": "^1.2", - "php": "^7.2 || ~8.0, <8.1", + "php": "^7.2 || ~8.0, <8.2", "phpdocumentor/reflection-docblock": "^5.2", "sebastian/comparator": "^3.0 || ^4.0", "sebastian/recursion-context": "^3.0 || ^4.0" }, "require-dev": { - "phpspec/phpspec": "^6.0", + "phpspec/phpspec": "^6.0 || ^7.0", "phpunit/phpunit": "^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { @@ -3822,29 +3823,29 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/1.13.0" + "source": "https://github.com/phpspec/prophecy/tree/1.14.0" }, - "time": "2021-03-17T13:42:18+00:00" + "time": "2021-09-10T09:02:12+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.6", + "version": "9.2.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f6293e1b30a2354e8428e004689671b83871edde" + "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde", - "reference": "f6293e1b30a2354e8428e004689671b83871edde", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d4c798ed8d51506800b441f7a13ecb0f76f12218", + "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.10.2", + "nikic/php-parser": "^4.12.0", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -3893,7 +3894,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.6" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.7" }, "funding": [ { @@ -3901,7 +3902,7 @@ "type": "github" } ], - "time": "2021-03-28T07:26:59+00:00" + "time": "2021-09-17T05:39:03+00:00" }, { "name": "phpunit/php-file-iterator", @@ -5313,16 +5314,16 @@ }, { "name": "symfony/console", - "version": "v5.3.6", + "version": "v5.3.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2" + "reference": "8b1008344647462ae6ec57559da166c2bfa5e16a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/51b71afd6d2dc8f5063199357b9880cea8d8bfe2", - "reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2", + "url": "https://api.github.com/repos/symfony/console/zipball/8b1008344647462ae6ec57559da166c2bfa5e16a", + "reference": "8b1008344647462ae6ec57559da166c2bfa5e16a", "shasum": "" }, "require": { @@ -5392,7 +5393,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.3.6" + "source": "https://github.com/symfony/console/tree/v5.3.7" }, "funding": [ { @@ -5408,7 +5409,7 @@ "type": "tidelift" } ], - "time": "2021-07-27T19:10:22+00:00" + "time": "2021-08-25T20:02:16+00:00" }, { "name": "symfony/deprecation-contracts", @@ -5882,16 +5883,16 @@ }, { "name": "symfony/string", - "version": "v5.3.3", + "version": "v5.3.7", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1" + "reference": "8d224396e28d30f81969f083a58763b8b9ceb0a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1", - "reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1", + "url": "https://api.github.com/repos/symfony/string/zipball/8d224396e28d30f81969f083a58763b8b9ceb0a5", + "reference": "8d224396e28d30f81969f083a58763b8b9ceb0a5", "shasum": "" }, "require": { @@ -5945,7 +5946,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.3.3" + "source": "https://github.com/symfony/string/tree/v5.3.7" }, "funding": [ { @@ -5961,7 +5962,7 @@ "type": "tidelift" } ], - "time": "2021-06-27T11:44:38+00:00" + "time": "2021-08-26T08:00:08+00:00" }, { "name": "theseer/tokenizer", @@ -6015,16 +6016,16 @@ }, { "name": "twig/twig", - "version": "v2.14.6", + "version": "v2.14.7", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "27e5cf2b05e3744accf39d4c68a3235d9966d260" + "reference": "8e202327ee1ed863629de9b18a5ec70ac614d88f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/27e5cf2b05e3744accf39d4c68a3235d9966d260", - "reference": "27e5cf2b05e3744accf39d4c68a3235d9966d260", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/8e202327ee1ed863629de9b18a5ec70ac614d88f", + "reference": "8e202327ee1ed863629de9b18a5ec70ac614d88f", "shasum": "" }, "require": { @@ -6034,7 +6035,7 @@ }, "require-dev": { "psr/container": "^1.0", - "symfony/phpunit-bridge": "^4.4.9|^5.0.9" + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" }, "type": "library", "extra": { @@ -6078,7 +6079,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v2.14.6" + "source": "https://github.com/twigphp/Twig/tree/v2.14.7" }, "funding": [ { @@ -6090,7 +6091,7 @@ "type": "tidelift" } ], - "time": "2021-05-16T12:12:47+00:00" + "time": "2021-09-17T08:39:54+00:00" }, { "name": "vimeo/psalm",