diff --git a/app/config/collections.php b/app/config/collections.php index a36a14b225..6d3be7b2a8 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -212,7 +212,7 @@ $collections = [ '$collection' => Database::SYSTEM_COLLECTION_RULES, 'label' => 'Status', 'key' => 'status', - 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, + 'type' => Database::SYSTEM_VAR_TYPE_BOOLEAN, 'default' => '', 'required' => true, 'array' => false, diff --git a/app/config/collections2.php b/app/config/collections2.php index d1e5539dd1..fbac358daf 100644 --- a/app/config/collections2.php +++ b/app/config/collections2.php @@ -240,7 +240,7 @@ $collections = [ ], [ '$id' => 'status', - 'type' => Database::VAR_INTEGER, + 'type' => Database::VAR_BOOLEAN, 'format' => '', 'size' => 0, 'signed' => true, diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 72d46ca177..2c41c159fd 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -91,7 +91,7 @@ App::post('/v1/account') '$write' => ['user:'.$userId], 'email' => $email, 'emailVerification' => false, - 'status' => Auth::USER_STATUS_UNACTIVATED, + 'status' => true, 'password' => Auth::passwordHash($password), 'passwordUpdate' => \time(), 'registration' => \time(), @@ -168,7 +168,7 @@ App::post('/v1/account/sessions') throw new Exception('Invalid credentials', 401); // Wrong password or username } - if (Auth::USER_STATUS_BLOCKED == $profile->getAttribute('status')) { // Account is blocked + if (false === $profile->getAttribute('status')) { // Account is blocked throw new Exception('Invalid credentials. User is blocked', 401); // User is in status blocked } @@ -472,7 +472,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') '$write' => ['user:'.$userId], 'email' => $email, 'emailVerification' => true, - 'status' => Auth::USER_STATUS_ACTIVATED, // Email should already be authenticated by OAuth2 provider + 'status' => true, // Email should already be authenticated by OAuth2 provider 'password' => Auth::passwordHash(Auth::passwordGenerator()), 'passwordUpdate' => 0, 'registration' => \time(), @@ -491,7 +491,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') } } - if (Auth::USER_STATUS_BLOCKED == $user->getAttribute('status')) { // Account is blocked + if (false === $user->getAttribute('status')) { // Account is blocked throw new Exception('Invalid credentials. User is blocked', 401); // User is in status blocked } @@ -524,7 +524,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') } $user - ->setAttribute('status', Auth::USER_STATUS_ACTIVATED) + ->setAttribute('status', true) ->setAttribute('sessions', $session, Document::SET_TYPE_APPEND) ; @@ -630,7 +630,7 @@ App::post('/v1/account/sessions/anonymous') '$write' => ['user:'.$userId], 'email' => null, 'emailVerification' => false, - 'status' => Auth::USER_STATUS_UNACTIVATED, + 'status' => true, 'password' => null, 'passwordUpdate' => \time(), 'registration' => \time(), @@ -1143,7 +1143,7 @@ App::delete('/v1/account') /** @var Appwrite\Event\Event $events */ $protocol = $request->getProtocol(); - $user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('status', Auth::USER_STATUS_BLOCKED)); + $user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('status', false)); //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 @@ -1374,8 +1374,8 @@ App::post('/v1/account/recovery') throw new Exception('User not found', 404); // TODO maybe hide this } - if (Auth::USER_STATUS_BLOCKED == $profile->getAttribute('status')) { // Account is blocked - throw new Exception('Invalid credentials. User is blocked', 401); // User is in status blocked + if (false === $profile->getAttribute('status')) { // Account is blocked + throw new Exception('Invalid credentials. User is blocked', 401); } $secret = Auth::tokenGenerator(); diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 39ced4b747..f4e6631dc9 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -297,7 +297,7 @@ App::post('/v1/teams/:teamId/memberships') '$write' => ['user:'.$userId], 'email' => $email, 'emailVerification' => false, - 'status' => Auth::USER_STATUS_UNACTIVATED, + 'status' => true, 'password' => Auth::passwordHash(Auth::passwordGenerator()), /** * Set the password update time to 0 for users created using diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 9777e5b250..1c9c5b317c 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -49,7 +49,7 @@ App::post('/v1/users') '$write' => ['user:'.$userId], 'email' => $email, 'emailVerification' => false, - 'status' => Auth::USER_STATUS_UNACTIVATED, + 'status' => true, 'password' => Auth::passwordHash($password), 'passwordUpdate' => \time(), 'registration' => \time(), @@ -321,7 +321,7 @@ App::patch('/v1/users/:userId/status') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_USER) ->param('userId', '', new UID(), 'User unique ID.') - ->param('status', '', new WhiteList([Auth::USER_STATUS_ACTIVATED, Auth::USER_STATUS_BLOCKED, Auth::USER_STATUS_UNACTIVATED], true, Validator::TYPE_INTEGER), 'User Status code. To activate the user pass '.Auth::USER_STATUS_ACTIVATED.', to block the user pass '.Auth::USER_STATUS_BLOCKED.' and for disabling the user pass '.Auth::USER_STATUS_UNACTIVATED) + ->param('status', null, new Boolean(true), 'User Status. To activate the user pass `true` and to block the user pass `false`') ->inject('response') ->inject('dbForInternal') ->action(function ($userId, $status, $response, $dbForInternal) { @@ -334,7 +334,7 @@ App::patch('/v1/users/:userId/status') throw new Exception('User not found', 404); } - $user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('status', (int)$status)); + $user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('status', (bool) $status)); $response->dynamic2($user, Response::MODEL_USER); }); diff --git a/app/controllers/general.php b/app/controllers/general.php index e8392cde4f..3046b39e4b 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -234,7 +234,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB if ($key && $user->isEmpty()) { $user = new Document([ '$id' => '', - 'status' => Auth::USER_STATUS_ACTIVATED, + 'status' => true, 'email' => 'app.'.$project->getId().'@service.'.$request->getHostname(), 'password' => '', 'name' => $project->getAttribute('name', 'Untitled'), @@ -278,8 +278,8 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB throw new Exception($user->getAttribute('email', 'User').' (role: '.\strtolower($roles[$role]['label']).') missing scope ('.$scope.')', 401); } - if (Auth::USER_STATUS_BLOCKED == $user->getAttribute('status')) { // Account has not been activated - throw new Exception('Invalid credentials. User is blocked', 401); // User is in status blocked + if (false === $user->getAttribute('status')) { // Account is blocked + throw new Exception('Invalid credentials. User is blocked', 401); } if ($user->getAttribute('reset')) { diff --git a/app/views/console/users/index.phtml b/app/views/console/users/index.phtml index 65b611be21..df4d245fb4 100644 --- a/app/views/console/users/index.phtml +++ b/app/views/console/users/index.phtml @@ -85,15 +85,15 @@ $auth = $this->getParam('auth', []); - + Verified - + Unverified - + Blocked diff --git a/app/views/console/users/user.phtml b/app/views/console/users/user.phtml index d949823501..8326a25783 100644 --- a/app/views/console/users/user.phtml +++ b/app/views/console/users/user.phtml @@ -34,7 +34,7 @@
  • Overview

    -
  • -
    +
    - +
    -
    +
    - +
    diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index b25cfcdadf..3ae28997b1 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -6,13 +6,6 @@ use Appwrite\Database\Document; class Auth { - /** - * User Status. - */ - const USER_STATUS_UNACTIVATED = 0; - const USER_STATUS_ACTIVATED = 1; - const USER_STATUS_BLOCKED = 2; - /** * User Roles. */ diff --git a/src/Appwrite/Migration/Version/V09.php b/src/Appwrite/Migration/Version/V09.php new file mode 100644 index 0000000000..d7610e1891 --- /dev/null +++ b/src/Appwrite/Migration/Version/V09.php @@ -0,0 +1,52 @@ +project; + Console::log('Migrating project: ' . $project->getAttribute('name') . ' (' . $project->getId() . ')'); + + $this->forEachDocument([$this, 'fixDocument']); + } + + protected function fixDocument(Document $document) + { + switch ($document->getAttribute('$collection')) { + case Database::SYSTEM_COLLECTION_USERS: + /** + * Remove deprecated user status 0 and replace with boolean. + */ + if ($document->getAttribute('status') === 0 || $document->getAttribute('status') === 1) { + $document->setAttribute('status', true); + } + if ($document->getAttribute('status') === 2) { + $document->setAttribute('status', false); + } + } + + foreach ($document as &$attr) { + if ($attr instanceof Document) { + $attr = $this->fixDocument($attr); + } + + if (\is_array($attr)) { + foreach ($attr as &$child) { + if ($child instanceof Document) { + $child = $this->fixDocument($child); + } + } + } + } + + return $document; + } +} diff --git a/src/Appwrite/Utopia/Response/Filters/V06.php b/src/Appwrite/Utopia/Response/Filters/V06.php index d3d5f03293..51775d60c1 100644 --- a/src/Appwrite/Utopia/Response/Filters/V06.php +++ b/src/Appwrite/Utopia/Response/Filters/V06.php @@ -327,7 +327,7 @@ class V06 extends Filter { $content['oauth2'.ucfirst($key)] = ''; $content['oauth2'.ucfirst($key).'AccessToken'] = ''; } - $content['status'] = empty($content['status']) ? 0 : $content['status']; + $content['status'] = $content['status'] ? 0 : 2; $content['roles'] = Authorization::getRoles() ?? []; return $content; } diff --git a/src/Appwrite/Utopia/Response/Model/User.php b/src/Appwrite/Utopia/Response/Model/User.php index 00353724cd..f7a1095227 100644 --- a/src/Appwrite/Utopia/Response/Model/User.php +++ b/src/Appwrite/Utopia/Response/Model/User.php @@ -29,10 +29,10 @@ class User extends Model 'example' => 1592981250, ]) ->addRule('status', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'User status. 0 for Unactivated, 1 for active and 2 is blocked.', - 'default' => 0, - 'example' => 0, + 'type' => self::TYPE_BOOLEAN, + 'description' => 'User status. Pass `true` for enabled and `false` for disabled.', + 'default' => true, + 'example' => true, ]) ->addRule('passwordUpdate', [ 'type' => self::TYPE_INTEGER, diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 7c04b59f63..9c7d190bc9 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -102,7 +102,7 @@ class AccountCustomClientTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ - 'status' => 2, + 'status' => false, ]); $this->assertEquals($response['headers']['status-code'], 200); diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index be64204749..866179b8d5 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -23,7 +23,7 @@ trait UsersBase $this->assertEquals($user['headers']['status-code'], 201); $this->assertEquals($user['body']['name'], 'Project User'); $this->assertEquals($user['body']['email'], 'users.service@example.com'); - $this->assertEquals($user['body']['status'], 0); + $this->assertEquals($user['body']['status'], true); $this->assertGreaterThan(0, $user['body']['registration']); return ['userId' => $user['body']['$id']]; @@ -45,7 +45,7 @@ trait UsersBase $this->assertEquals($user['headers']['status-code'], 200); $this->assertEquals($user['body']['name'], 'Project User'); $this->assertEquals($user['body']['email'], 'users.service@example.com'); - $this->assertEquals($user['body']['status'], 0); + $this->assertEquals($user['body']['status'], true); $this->assertGreaterThan(0, $user['body']['registration']); $sessions = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/sessions', array_merge([ @@ -105,11 +105,11 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'status' => 2, + 'status' => false, ]); $this->assertEquals($user['headers']['status-code'], 200); - $this->assertEquals($user['body']['status'], 2); + $this->assertEquals($user['body']['status'], false); $user = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'], array_merge([ 'content-type' => 'application/json', @@ -117,7 +117,7 @@ trait UsersBase ], $this->getHeaders())); $this->assertEquals($user['headers']['status-code'], 200); - $this->assertEquals($user['body']['status'], 2); + $this->assertEquals($user['body']['status'], false); return $data; } diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php index 3328c71f1c..57e9ed2457 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php @@ -50,7 +50,7 @@ class WebhooksCustomClientTest extends Scope $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']['status'], true); $this->assertEquals($webhook['data']['email'], $email); $this->assertEquals($webhook['data']['emailVerification'], false); $this->assertEquals($webhook['data']['prefs'], []); @@ -119,7 +119,7 @@ class WebhooksCustomClientTest extends Scope $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']['status'], false); $this->assertEquals($webhook['data']['email'], $email); $this->assertEquals($webhook['data']['emailVerification'], false); $this->assertEquals($webhook['data']['prefs'], []); @@ -389,7 +389,7 @@ class WebhooksCustomClientTest extends Scope $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']['status'], true); $this->assertEquals($webhook['data']['email'], $email); $this->assertEquals($webhook['data']['emailVerification'], false); $this->assertEquals($webhook['data']['prefs'], []); @@ -433,7 +433,7 @@ class WebhooksCustomClientTest extends Scope $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']['status'], true); $this->assertEquals($webhook['data']['email'], $email); $this->assertEquals($webhook['data']['emailVerification'], false); $this->assertEquals($webhook['data']['prefs'], []); @@ -479,7 +479,7 @@ class WebhooksCustomClientTest extends Scope $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']['status'], true); $this->assertEquals($webhook['data']['email'], $newEmail); $this->assertEquals($webhook['data']['emailVerification'], false); $this->assertEquals($webhook['data']['prefs'], []); @@ -526,7 +526,7 @@ class WebhooksCustomClientTest extends Scope $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']['status'], true); $this->assertEquals($webhook['data']['email'], $email); $this->assertEquals($webhook['data']['emailVerification'], false); $this->assertEquals($webhook['data']['prefs'], [ diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php index 4e4c768e61..2f254b0e6d 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php @@ -194,7 +194,7 @@ class WebhooksCustomServerTest extends Scope $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']['status'], true); $this->assertEquals($webhook['data']['email'], $email); $this->assertEquals($webhook['data']['emailVerification'], false); $this->assertEquals($webhook['data']['prefs'], []); @@ -250,7 +250,7 @@ class WebhooksCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'status' => 2, + 'status' => false, ]); $this->assertEquals($user['headers']['status-code'], 200); @@ -269,7 +269,7 @@ class WebhooksCustomServerTest extends Scope $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']['status'], false); $this->assertEquals($webhook['data']['email'], $data['email']); $this->assertEquals($webhook['data']['emailVerification'], false); $this->assertEquals($webhook['data']['prefs']['a'], 'b'); @@ -305,7 +305,7 @@ class WebhooksCustomServerTest extends Scope $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']['status'], false); $this->assertEquals($webhook['data']['email'], $data['email']); $this->assertEquals($webhook['data']['emailVerification'], false); $this->assertEquals($webhook['data']['prefs']['a'], 'b'); diff --git a/tests/e2e/Services/Workers/WebhooksTest.php b/tests/e2e/Services/Workers/WebhooksTest.php index ced80832ae..12154c9b0f 100644 --- a/tests/e2e/Services/Workers/WebhooksTest.php +++ b/tests/e2e/Services/Workers/WebhooksTest.php @@ -141,7 +141,7 @@ class WebhooksTest extends Scope $this->assertNotEmpty($webhook['data']); $this->assertNotEmpty($webhook['data']['$id']); - $this->assertIsNumeric($webhook['data']['status']); + $this->assertIsBool($webhook['data']['status']); $this->assertIsNumeric($webhook['data']['registration']); $this->assertEquals($webhook['data']['email'], $email); $this->assertEquals($webhook['data']['name'], $name); diff --git a/tests/unit/Migration/MigrationV09Test.php b/tests/unit/Migration/MigrationV09Test.php new file mode 100644 index 0000000000..5059eca178 --- /dev/null +++ b/tests/unit/Migration/MigrationV09Test.php @@ -0,0 +1,50 @@ +pdo = new \PDO('sqlite::memory:'); + $this->migration = new V09($this->pdo); + $reflector = new ReflectionClass('Appwrite\Migration\Version\V09'); + $this->method = $reflector->getMethod('fixDocument'); + $this->method->setAccessible(true); + } + + public function testMigration() + { + $document = $this->fixDocument(new Document([ + '$id' => uniqid(), + '$collection' => Database::SYSTEM_COLLECTION_USERS, + 'status' => 0 + ])); + + $this->assertIsBool($document->getAttribute('status')); + $this->assertEquals(true, $document->getAttribute('env', false)); + + $document = $this->fixDocument(new Document([ + '$id' => uniqid(), + '$collection' => Database::SYSTEM_COLLECTION_USERS, + 'status' => 1 + ])); + + $this->assertIsBool($document->getAttribute('status')); + $this->assertEquals(true, $document->getAttribute('env', false)); + + $document = $this->fixDocument(new Document([ + '$id' => uniqid(), + '$collection' => Database::SYSTEM_COLLECTION_USERS, + 'status' => 2 + ])); + + $this->assertIsBool($document->getAttribute('status')); + $this->assertEquals(false, $document->getAttribute('env', false)); + } +}