diff --git a/app/config/scopes/consoleProject.php b/app/config/scopes/consoleProject.php index 9420486776..633334c40b 100644 --- a/app/config/scopes/consoleProject.php +++ b/app/config/scopes/consoleProject.php @@ -4,4 +4,6 @@ return [ 'platforms' => 'platforms.read', + 'devKeys' => 'devKeys.read', + 'devKeysWrite' => 'devKeys.write', ]; diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index 9a47530630..8b866ededf 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -353,6 +353,7 @@ class Migrations extends Action 'messages.write', 'targets.read', 'targets.write', + 'migrations.write', ] ]); diff --git a/src/Appwrite/Utopia/Response/Model/MigrationReport.php b/src/Appwrite/Utopia/Response/Model/MigrationReport.php index 388630af3f..61cf9a11f6 100644 --- a/src/Appwrite/Utopia/Response/Model/MigrationReport.php +++ b/src/Appwrite/Utopia/Response/Model/MigrationReport.php @@ -59,6 +59,12 @@ class MigrationReport extends Model 'default' => 0, 'example' => 5, ]) + ->addRule(Resource::TYPE_DEV_KEY, [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Number of dev keys to be migrated.', + 'default' => 0, + 'example' => 5, + ]) ->addRule(Resource::TYPE_SITE, [ 'type' => self::TYPE_INTEGER, 'description' => 'Number of sites to be migrated.', diff --git a/tests/e2e/Services/Migrations/MigrationsBase.php b/tests/e2e/Services/Migrations/MigrationsBase.php index 569600c01d..c4088d1b4f 100644 --- a/tests/e2e/Services/Migrations/MigrationsBase.php +++ b/tests/e2e/Services/Migrations/MigrationsBase.php @@ -1297,6 +1297,91 @@ trait MigrationsBase $this->client->call(Client::METHOD_DELETE, '/projects/' . $this->getProject()['$id'] . '/platforms/' . $platform['$id'], $consoleSessionHeaders); } + public function testAppwriteMigrationDevKey(): void + { + $consoleSessionHeaders = [ + 'content-type' => 'application/json', + 'x-appwrite-project' => 'console', + 'origin' => 'http://localhost', + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], + ]; + + // Create dev key on source project + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $this->getProject()['$id'] . '/dev-keys', $consoleSessionHeaders, [ + 'name' => 'Test Dev Key', + 'expire' => '2030-01-01T00:00:00.000+00:00', + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + + $devKey = $response['body']; + + $result = $this->performMigrationSync([ + 'resources' => [ + Resource::TYPE_DEV_KEY, + ], + 'endpoint' => $this->webEndpoint, + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals('completed', $result['status']); + $this->assertEquals([Resource::TYPE_DEV_KEY], $result['resources']); + $this->assertArrayHasKey(Resource::TYPE_DEV_KEY, $result['statusCounters']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DEV_KEY]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DEV_KEY]['pending']); + $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_DEV_KEY]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DEV_KEY]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DEV_KEY]['warning']); + + // Get a console key for the destination project to access console-scoped endpoints + $consoleKeyResponse = $this->client->call(Client::METHOD_POST, '/migrations/appwrite/console-key', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDestinationProject()['$id'], + 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], + ]); + + $this->assertEquals(200, $consoleKeyResponse['headers']['status-code']); + $destConsoleKey = $consoleKeyResponse['body']['key']; + + // Verify dev key on destination project using console key + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $this->getDestinationProject()['$id'] . '/dev-keys', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => 'console', + 'x-appwrite-key' => $destConsoleKey, + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertGreaterThan(0, $response['body']['total']); + + $foundKey = null; + + foreach ($response['body']['devKeys'] as $k) { + if ($k['name'] === 'Test Dev Key') { + $foundKey = $k; + + break; + } + } + + $this->assertNotNull($foundKey); + $this->assertEquals('Test Dev Key', $foundKey['name']); + $this->assertEquals('2030-01-01T00:00:00.000+00:00', $foundKey['expire']); + + // Cleanup on destination using console key + $this->client->call(Client::METHOD_DELETE, '/projects/' . $this->getDestinationProject()['$id'] . '/dev-keys/' . $foundKey['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => 'console', + 'x-appwrite-key' => $destConsoleKey, + ]); + + // Cleanup on source using console project + session auth + $this->client->call(Client::METHOD_DELETE, '/projects/' . $this->getProject()['$id'] . '/dev-keys/' . $devKey['$id'], $consoleSessionHeaders); + } + /** * Import documents from a CSV file. */