diff --git a/CHANGES.md b/CHANGES.md index 1418fb4532..f682c7dd2f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -39,6 +39,7 @@ - New OAuth adapter for PayPal sandbox (@armino-dev - [#420](https://github.com/appwrite/appwrite/issues/410)) - Introducing new permssion types: role:guest, role:member, role:app - Disabled rate-limits on server side integrations +- Refactored migration script ### User Interface @@ -105,6 +106,7 @@ - Fixed OAuth redirect when using the self-hosted instance default success URL ([#454](https://github.com/appwrite/appwrite/issues/454)) - Fixed bug denying authentication with Github OAuth provider - Fixed a bug making read permission overwrite write permission in some cases +- Fixed consistent property names in databases by enforcing camel case ## Security @@ -113,6 +115,7 @@ - Now using your `_APP_SYSTEM_EMAIL_ADDRESS` as the email address for issuing and renewing SSL certificates - Block iframe access to Appwrite console using the `X-Frame-Options` header. - Fixed `roles` param input validator +- API Keys are now stored encrypted # Version 0.6.2 (PRE-RELEASE) diff --git a/app/config/collections.php b/app/config/collections.php index a9451d4a5b..9170b07573 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -228,7 +228,7 @@ $collections = [ [ '$collection' => Database::SYSTEM_COLLECTION_RULES, 'label' => 'Password Update Date', - 'key' => 'password-update', + 'key' => 'passwordUpdate', 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, 'default' => '', 'required' => true, @@ -827,6 +827,7 @@ $collections = [ 'type' => Database::SYSTEM_VAR_TYPE_TEXT, 'default' => '', 'required' => false, + 'filter' => ['encrypt'], ], ], ], diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 9e366abbaf..9adba0f6c3 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -100,7 +100,7 @@ App::post('/v1/account') 'emailVerification' => false, 'status' => Auth::USER_STATUS_UNACTIVATED, 'password' => Auth::passwordHash($password), - 'password-update' => \time(), + 'passwordUpdate' => \time(), 'registration' => \time(), 'reset' => false, 'name' => $name, @@ -512,7 +512,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'emailVerification' => true, 'status' => Auth::USER_STATUS_ACTIVATED, // Email should already be authenticated by OAuth2 provider 'password' => Auth::passwordHash(Auth::passwordGenerator()), - 'password-update' => \time(), + 'passwordUpdate' => \time(), 'registration' => \time(), 'reset' => false, 'name' => $name, @@ -1456,7 +1456,7 @@ App::put('/v1/account/recovery') $profile = $projectDB->updateDocument(\array_merge($profile->getArrayCopy(), [ 'password' => Auth::passwordHash($password), - 'password-update' => \time(), + 'passwordUpdate' => \time(), 'emailVerification' => true, ])); diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 084bdeeb1b..5ebeb63669 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -324,7 +324,7 @@ App::post('/v1/teams/:teamId/memberships') 'emailVerification' => false, 'status' => Auth::USER_STATUS_UNACTIVATED, 'password' => Auth::passwordHash(Auth::passwordGenerator()), - 'password-update' => \time(), + 'passwordUpdate' => \time(), 'registration' => \time(), 'reset' => false, 'name' => $name, diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 76e20aad98..a0cff97232 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -62,7 +62,7 @@ App::post('/v1/users') 'emailVerification' => false, 'status' => Auth::USER_STATUS_UNACTIVATED, 'password' => Auth::passwordHash($password), - 'password-update' => \time(), + 'passwordUpdate' => \time(), 'registration' => \time(), 'reset' => false, 'name' => $name, diff --git a/app/http.php b/app/http.php index 74fb950825..9e45bc3b52 100644 --- a/app/http.php +++ b/app/http.php @@ -18,6 +18,7 @@ use Utopia\CLI\Console; ini_set('memory_limit','512M'); ini_set('display_errors', 1); ini_set('display_startup_errors', 1); +ini_set('default_socket_timeout', -1); error_reporting(E_ALL); $http = new Server("0.0.0.0", App::getEnv('PORT', 80)); diff --git a/app/tasks/migrate.php b/app/tasks/migrate.php index bba8ad416c..f9e92b24b7 100644 --- a/app/tasks/migrate.php +++ b/app/tasks/migrate.php @@ -5,187 +5,26 @@ global $cli, $register, $projectDB, $console; use Utopia\Config\Config; use Utopia\CLI\Console; use Appwrite\Database\Database; -use Appwrite\Database\Document; use Appwrite\Database\Validator\Authorization; use Appwrite\Database\Adapter\MySQL as MySQLAdapter; use Appwrite\Database\Adapter\Redis as RedisAdapter; - -$callbacks = [ - '0.4.0' => function() { - Console::log('I got nothing to do.'); - }, - - '0.5.0' => function($project) use ($register, $projectDB) { - $db = $register->get('db'); - - Console::log('Migrating project: '.$project->getAttribute('name').' ('.$project->getId().')'); - - // Update all documents $uid -> $id - - $limit = 30; - $sum = 30; - $offset = 0; - - while ($sum >= 30) { - $all = $projectDB->getCollection([ - 'limit' => $limit, - 'offset' => $offset, - 'orderType' => 'DESC', - ]); - - $sum = \count($all); - - Console::log('Migrating: '.$offset.' / '.$projectDB->getSum()); - - foreach($all as $document) { - $document = fixDocument($document); - - if(empty($document->getId())) { - throw new Exception('Missing ID'); - } - - try { - $new = $projectDB->overwriteDocument($document->getArrayCopy()); - } catch (\Throwable $th) { - var_dump($document); - Console::error('Failed to update document: '.$th->getMessage()); - continue; - } - - if($new->getId() !== $document->getId()) { - throw new Exception('Duplication Error'); - } - } - - $offset = $offset + $limit; - } - - $schema = $_SERVER['_APP_DB_SCHEMA'] ?? ''; - - try { - $statement = $db->prepare(" - - CREATE TABLE IF NOT EXISTS `template.database.unique` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `key` varchar(128) DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `index1` (`key`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - - CREATE TABLE IF NOT EXISTS `{$schema}`.`app_{$project->getId()}.database.unique` LIKE `template.database.unique`; - ALTER TABLE `{$schema}`.`app_{$project->getId()}.audit.audit` DROP COLUMN IF EXISTS `userType`; - ALTER TABLE `{$schema}`.`app_{$project->getId()}.audit.audit` DROP INDEX IF EXISTS `index_1`; - ALTER TABLE `{$schema}`.`app_{$project->getId()}.audit.audit` ADD INDEX IF NOT EXISTS `index_1` (`userId` ASC); - "); - - $statement->closeCursor(); - - $statement->execute(); - } - catch (\Exception $e) { - Console::error('Failed to alter table for project: '.$project->getId().' with message: '.$e->getMessage().'/'); - } - }, -]; - -function fixDocument(Document $document) { - $providers = Config::getParam('providers'); - - if($document->getAttribute('$collection') === Database::SYSTEM_COLLECTION_PROJECTS){ - foreach($providers as $key => $provider) { - if(!empty($document->getAttribute('usersOauth'.\ucfirst($key).'Appid'))) { - $document - ->setAttribute('usersOauth2'.\ucfirst($key).'Appid', $document->getAttribute('usersOauth'.\ucfirst($key).'Appid', '')) - ->removeAttribute('usersOauth'.\ucfirst($key).'Appid') - ; - } - - if(!empty($document->getAttribute('usersOauth'.\ucfirst($key).'Secret'))) { - $document - ->setAttribute('usersOauth2'.\ucfirst($key).'Secret', $document->getAttribute('usersOauth'.\ucfirst($key).'Secret', '')) - ->removeAttribute('usersOauth'.\ucfirst($key).'Secret') - ; - } - } - } - - if($document->getAttribute('$collection') === Database::SYSTEM_COLLECTION_WEBHOOKS){ - $document->setAttribute('security', ($document->getAttribute('security')) ? true : false); - } - - if($document->getAttribute('$collection') === Database::SYSTEM_COLLECTION_TASKS){ - $document->setAttribute('security', ($document->getAttribute('security')) ? true : false); - } - - if($document->getAttribute('$collection') === Database::SYSTEM_COLLECTION_USERS) { - foreach($providers as $key => $provider) { - if(!empty($document->getAttribute('oauth'.\ucfirst($key)))) { - $document - ->setAttribute('oauth2'.\ucfirst($key), $document->getAttribute('oauth'.\ucfirst($key), '')) - ->removeAttribute('oauth'.\ucfirst($key)) - ; - } - - if(!empty($document->getAttribute('oauth'.\ucfirst($key).'AccessToken'))) { - $document - ->setAttribute('oauth2'.\ucfirst($key).'AccessToken', $document->getAttribute('oauth'.\ucfirst($key).'AccessToken', '')) - ->removeAttribute('oauth'.\ucfirst($key).'AccessToken') - ; - } - } - - if($document->getAttribute('confirm', null) !== null) { - $document - ->setAttribute('emailVerification', $document->getAttribute('confirm', $document->getAttribute('emailVerification', false))) - ->removeAttribute('confirm') - ; - } - } - - if($document->getAttribute('$collection') === Database::SYSTEM_COLLECTION_PLATFORMS) { - if($document->getAttribute('url', null) !== null) { - $document - ->setAttribute('hostname', \parse_url($document->getAttribute('url', $document->getAttribute('hostname', '')), PHP_URL_HOST)) - ->removeAttribute('url') - ; - } - } - - $document - ->setAttribute('$id', $document->getAttribute('$uid', $document->getAttribute('$id'))) - ->removeAttribute('$uid') - ; - - foreach($document as &$attr) { // Handle child documents - if($attr instanceof Document) { - $attr = fixDocument($attr); - } - - if(\is_array($attr)) { - foreach($attr as &$child) { - if($child instanceof Document) { - $child = fixDocument($child); - } - } - } - } - - return $document; -} +use Appwrite\Migration\Version; $cli ->task('migrate') - ->action(function () use ($register, $callbacks) { + ->action(function () use ($register) { Console::success('Starting Data Migration'); $consoleDB = new Database(); - $consoleDB->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register)); - $consoleDB->setNamespace('app_console'); // Main DB - $consoleDB->setMocks(Config::getParam('collections', [])); - + $consoleDB + ->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register)) + ->setNamespace('app_console') // Main DB + ->setMocks(Config::getParam('collections', [])); + $projectDB = new Database(); - $projectDB->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register)); - $projectDB->setMocks(Config::getParam('collections', [])); + $projectDB + ->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register)) + ->setMocks(Config::getParam('collections', [])); $console = $consoleDB->getDocument('console'); @@ -197,13 +36,14 @@ $cli $projects = [$console]; $count = 0; - while ($sum >= 30) { - foreach($projects as $project) { - - $projectDB->setNamespace('app_'.$project->getId()); + $migration = new Version\V06($register->get('db')); //TODO: remove hardcoded version and move to dynamic migration + while ($sum > 0) { + foreach ($projects as $project) { try { - $callbacks['0.5.0']($project, $projectDB); + $migration + ->setProject($project, $projectDB) + ->execute(); } catch (\Throwable $th) { throw $th; Console::error('Failed to update project ("'.$project->getId().'") version with error: '.$th->getMessage()); @@ -221,9 +61,11 @@ $cli $sum = \count($projects); $offset = $offset + $limit; $count = $count + $sum; - - Console::log('Fetched '.$count.'/'.$consoleDB->getSum().' projects...'); + + if ($sum > 0) { + Console::log('Fetched '.$count.'/'.$consoleDB->getSum().' projects...'); + } } Console::success('Data Migration Completed'); - }); \ No newline at end of file + }); diff --git a/src/Appwrite/Database/Database.php b/src/Appwrite/Database/Database.php index 8610aeac16..c4e1370e1b 100644 --- a/src/Appwrite/Database/Database.php +++ b/src/Appwrite/Database/Database.php @@ -57,6 +57,11 @@ class Database */ static protected $filters = []; + /** + * @var bool + */ + static protected $statusFilters = true; + /** * @var array */ @@ -448,6 +453,26 @@ class Database ]; } + /** + * Disable Attribute decoding + * + * @return void + */ + public static function disableFilters(): void + { + self::$statusFilters = false; + } + + /** + * Enable Attribute decoding + * + * @return void + */ + public static function enableFilters(): void + { + self::$statusFilters = true; + } + public function encode(Document $document):Document { $collection = $this->getDocument($document->getCollection(), true , false); @@ -550,6 +575,10 @@ class Database */ static protected function decodeAttribute(string $name, $value) { + if (!self::$statusFilters) { + return $value; + } + if (!isset(self::$filters[$name])) { return $value; throw new Exception('Filter not found'); diff --git a/src/Appwrite/Migration/Migration.php b/src/Appwrite/Migration/Migration.php new file mode 100644 index 0000000000..fabf968b06 --- /dev/null +++ b/src/Appwrite/Migration/Migration.php @@ -0,0 +1,119 @@ +db = $db; + } + + /** + * Set project for migration. + * + * @param Document $project + * @param Database $projectDB + * + * @return Migration + */ + public function setProject(Document $project, Database $projectDB): Migration + { + $this->project = $project; + $this->projectDB = $projectDB; + $this->projectDB->setNamespace('app_' . $project->getId()); + return $this; + } + + /** + * Iterates through every document. + * + * @param callable $callback + */ + public function forEachDocument(callable $callback): void + { + $sum = $this->limit; + $offset = 0; + + while ($sum >= $this->limit) { + $all = $this->projectDB->getCollection([ + 'limit' => $this->limit, + 'offset' => $offset, + 'orderType' => 'DESC', + ]); + + $sum = \count($all); + Runtime::setHookFlags(SWOOLE_HOOK_ALL); + + Console::log('Migrating: ' . $offset . ' / ' . $this->projectDB->getSum()); + \Co\run(function () use ($all, $callback) { + Runtime::enableCoroutine(SWOOLE_HOOK_ALL); + + foreach ($all as $document) { + go(function () use ($document, $callback) { + + $old = $document->getArrayCopy(); + $new = call_user_func($callback, $document); + + if (empty($new->getId())) { + throw new Exception('Missing ID'); + } + if (!array_diff($new->getArrayCopy(), $old)) { + return; + } + + try { + $new = $this->projectDB->overwriteDocument($document->getArrayCopy()); + } catch (\Throwable $th) { + Console::error('Failed to update document: ' . $th->getMessage()); + return; + + if ($document && $new->getId() !== $document->getId()) { + throw new Exception('Duplication Error'); + } + } + }); + } + }); + + $offset += $this->limit; + } + } + + /** + * Executes migration for set project. + */ + abstract public function execute(): void; +} diff --git a/src/Appwrite/Migration/Version/V04.php b/src/Appwrite/Migration/Version/V04.php new file mode 100644 index 0000000000..3b13d8920b --- /dev/null +++ b/src/Appwrite/Migration/Version/V04.php @@ -0,0 +1,14 @@ +db; + $project = $this->project; + Console::log('Migrating project: ' . $project->getAttribute('name') . ' (' . $project->getId() . ')'); + + // Update all documents $uid -> $id + + $this->forEachDocument([$this, 'fixDocument']); + + $schema = $_SERVER['_APP_DB_SCHEMA'] ?? ''; + + try { + $statement = $db->prepare(" + + CREATE TABLE IF NOT EXISTS `template.database.unique` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `key` varchar(128) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `index1` (`key`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + + CREATE TABLE IF NOT EXISTS `{$schema}`.`app_{$project->getId()}.database.unique` LIKE `template.database.unique`; + ALTER TABLE `{$schema}`.`app_{$project->getId()}.audit.audit` DROP COLUMN IF EXISTS `userType`; + ALTER TABLE `{$schema}`.`app_{$project->getId()}.audit.audit` DROP INDEX IF EXISTS `index_1`; + ALTER TABLE `{$schema}`.`app_{$project->getId()}.audit.audit` ADD INDEX IF NOT EXISTS `index_1` (`userId` ASC); + "); + + $statement->closeCursor(); + + $statement->execute(); + } catch (\Exception $e) { + Console::error('Failed to alter table for project: ' . $project->getId() . ' with message: ' . $e->getMessage() . '/'); + } + } + + protected function fixDocument(Document $document) + { + $providers = Config::getParam('providers'); + + switch ($document->getAttribute('$collection')) { + case Database::SYSTEM_COLLECTION_PROJECTS: + foreach ($providers as $key => $provider) { + if (!empty($document->getAttribute('usersOauth' . \ucfirst($key) . 'Appid'))) { + $document + ->setAttribute('usersOauth2' . \ucfirst($key) . 'Appid', $document->getAttribute('usersOauth' . \ucfirst($key) . 'Appid', '')) + ->removeAttribute('usersOauth' . \ucfirst($key) . 'Appid'); + } + + if (!empty($document->getAttribute('usersOauth' . \ucfirst($key) . 'Secret'))) { + $document + ->setAttribute('usersOauth2' . \ucfirst($key) . 'Secret', $document->getAttribute('usersOauth' . \ucfirst($key) . 'Secret', '')) + ->removeAttribute('usersOauth' . \ucfirst($key) . 'Secret'); + } + } + $document->setAttribute('security', $document->getAttribute('security') ? true : false); + break; + + case Database::SYSTEM_COLLECTION_TASKS: + $document->setAttribute('security', $document->getAttribute('security') ? true : false); + break; + + case Database::SYSTEM_COLLECTION_USERS: + foreach ($providers as $key => $provider) { + if (!empty($document->getAttribute('oauth' . \ucfirst($key)))) { + $document + ->setAttribute('oauth2' . \ucfirst($key), $document->getAttribute('oauth' . \ucfirst($key), '')) + ->removeAttribute('oauth' . \ucfirst($key)); + } + + if (!empty($document->getAttribute('oauth' . \ucfirst($key) . 'AccessToken'))) { + $document + ->setAttribute('oauth2' . \ucfirst($key) . 'AccessToken', $document->getAttribute('oauth' . \ucfirst($key) . 'AccessToken', '')) + ->removeAttribute('oauth' . \ucfirst($key) . 'AccessToken'); + } + } + + if ($document->getAttribute('confirm', null) !== null) { + $document + ->setAttribute('emailVerification', $document->getAttribute('confirm', $document->getAttribute('emailVerification', false))) + ->removeAttribute('confirm'); + } + break; + + case Database::SYSTEM_COLLECTION_PLATFORMS: + if ($document->getAttribute('url', null) !== null) { + $document + ->setAttribute('hostname', \parse_url($document->getAttribute('url', $document->getAttribute('hostname', '')), PHP_URL_HOST)) + ->removeAttribute('url'); + } + break; + } + + $document + ->setAttribute('$id', $document->getAttribute('$uid', $document->getAttribute('$id'))) + ->removeAttribute('$uid'); + + foreach ($document as &$attr) { // Handle child documents + 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/Migration/Version/V06.php b/src/Appwrite/Migration/Version/V06.php new file mode 100644 index 0000000000..686330620c --- /dev/null +++ b/src/Appwrite/Migration/Version/V06.php @@ -0,0 +1,60 @@ +project; + Console::log('Migrating project: ' . $project->getAttribute('name') . ' (' . $project->getId() . ')'); + + $this->projectDB->disableFilters(); + $this->forEachDocument([$this, 'fixDocument']); + $this->projectDB->enableFilters(); + } + + protected function fixDocument(Document $document) + { + switch ($document->getAttribute('$collection')) { + case Database::SYSTEM_COLLECTION_USERS: + if ($document->getAttribute('password-update', null)) { + $document + ->setAttribute('passwordUpdate', $document->getAttribute('password-update', $document->getAttribute('passwordUpdate', ''))) + ->removeAttribute('password-update'); + } + break; + case Database::SYSTEM_COLLECTION_KEYS: + if ($document->getAttribute('secret', null)) { + $json = \json_decode($document->getAttribute('secret')); + if ($json->{'data'} || $json->{'method'} || $json->{'iv'} || $json->{'tag'} || $json->{'version'}) + { + Console::log('Secret already encrypted. Skipped: ' . $document->getId()); + break; + } + + $key = App::getEnv('_APP_OPENSSL_KEY_V1'); + $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); + $tag = null; + + $document->setAttribute('secret', json_encode([ + 'data' => OpenSSL::encrypt($document->getAttribute('secret'), OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag), + 'method' => OpenSSL::CIPHER_AES_128_GCM, + 'iv' => bin2hex($iv), + 'tag' => bin2hex($tag), + 'version' => '1', + ])); + } + break; + } + return $document; + } +} diff --git a/tests/unit/Migration/MigrationTest.php b/tests/unit/Migration/MigrationTest.php new file mode 100644 index 0000000000..5a85ee23a2 --- /dev/null +++ b/tests/unit/Migration/MigrationTest.php @@ -0,0 +1,39 @@ +method->invokeArgs($this->migration, [ + $this->method->invokeArgs($this->migration, [$document]) + ]); + } + +} diff --git a/tests/unit/Migration/MigrationV05Test.php b/tests/unit/Migration/MigrationV05Test.php new file mode 100644 index 0000000000..09e28dd054 --- /dev/null +++ b/tests/unit/Migration/MigrationV05Test.php @@ -0,0 +1,86 @@ +pdo = new \PDO('sqlite::memory:'); + $this->migration = new V05($this->pdo); + $reflector = new ReflectionClass('Appwrite\Migration\Version\V05'); + $this->method = $reflector->getMethod('fixDocument'); + $this->method->setAccessible(true); + } + + public function testMigration() + { + $document = $this->fixDocument(new Document([ + '$uid' => 'unique', + '$collection' => Database::SYSTEM_COLLECTION_PROJECTS, + 'usersOauthGithubAppid' => 123, + 'usersOauthGithubSecret' => 456 + ])); + + $this->assertEquals($document->getAttribute('$uid', null), null); + $this->assertEquals($document->getAttribute('$id', null), 'unique'); + + $this->assertEquals($document->getAttribute('usersOauthGithubAppid', null), null); + $this->assertEquals($document->getAttribute('usersOauth2GithubAppid', null), 123); + + $this->assertEquals($document->getAttribute('usersOauthGithubSecret', null), null); + $this->assertEquals($document->getAttribute('usersOauth2GithubSecret', null), 456); + + $this->assertEquals($document->getAttribute('security', true), false); + + $document = $this->fixDocument(new Document([ + '$uid' => 'unique', + '$collection' => Database::SYSTEM_COLLECTION_TASKS + ])); + + $this->assertEquals($document->getAttribute('$uid', null), null); + $this->assertEquals($document->getAttribute('$id', null), 'unique'); + + $this->assertEquals($document->getAttribute('security', true), false); + + $document = $this->fixDocument(new Document([ + '$uid' => 'unique', + '$collection' => Database::SYSTEM_COLLECTION_USERS, + 'oauthGithub' => 'id', + 'oauthGithubAccessToken' => 'token', + 'confirm' => false + ])); + + $this->assertEquals($document->getAttribute('$uid', null), null); + $this->assertEquals($document->getAttribute('$id', null), 'unique'); + + $this->assertEquals($document->getAttribute('confirm', null), null); + $this->assertEquals($document->getAttribute('emailVerification', true), false); + + $this->assertEquals($document->getAttribute('oauthGithub', null), null); + $this->assertEquals($document->getAttribute('oauth2Github', null), 'id'); + + $this->assertEquals($document->getAttribute('oauthGithubAccessToken', null), null); + $this->assertEquals($document->getAttribute('oauth2GithubAccessToken', null), 'token'); + + $document = $this->fixDocument(new Document([ + '$uid' => 'unique', + '$collection' => Database::SYSTEM_COLLECTION_PLATFORMS, + 'url' => 'https://appwrite.io' + ])); + + $this->assertEquals($document->getAttribute('$uid', null), null); + $this->assertEquals($document->getAttribute('$id', null), 'unique'); + + $this->assertEquals($document->getAttribute('url', null), null); + $this->assertEquals($document->getAttribute('hostname', null), 'appwrite.io'); + } +} diff --git a/tests/unit/Migration/MigrationV06Test.php b/tests/unit/Migration/MigrationV06Test.php new file mode 100644 index 0000000000..19e892c435 --- /dev/null +++ b/tests/unit/Migration/MigrationV06Test.php @@ -0,0 +1,48 @@ +pdo = new \PDO('sqlite::memory:'); + + $this->migration = new V06($this->pdo); + $reflector = new ReflectionClass('Appwrite\Migration\Version\V06'); + $this->method = $reflector->getMethod('fixDocument'); + $this->method->setAccessible(true); + } + + public function testMigration() + { + $document = $this->fixDocument(new Document([ + '$id' => uniqid(), + '$collection' => Database::SYSTEM_COLLECTION_USERS, + 'password-update' => 123 + ])); + + $this->assertEquals($document->getAttribute('password-update', null), null); + $this->assertEquals($document->getAttribute('passwordUpdate', null), 123); + + $document = $this->fixDocument( + new Document([ + '$id' => uniqid(), + '$collection' => Database::SYSTEM_COLLECTION_KEYS, + 'secret' => 123 + ]) + ); + + $encrypted = json_decode($document->getAttribute('secret', null)); + $this->assertObjectHasAttribute('data', $encrypted); + $this->assertObjectHasAttribute('method', $encrypted); + $this->assertObjectHasAttribute('iv', $encrypted); + $this->assertObjectHasAttribute('tag', $encrypted); + $this->assertObjectHasAttribute('version', $encrypted); + } +}