diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index d61340cfff..4fa40bcbc1 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -14,6 +14,7 @@ use Appwrite\SDK\Deprecated; use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; use Appwrite\Template\Template; +use Appwrite\Utopia\Database\Validator\CustomId; use Appwrite\Utopia\Response; use PHPMailer\PHPMailer\PHPMailer; use Utopia\Config\Config; @@ -1094,12 +1095,15 @@ Http::post('/v1/projects/:projectId/keys') ] )) ->param('projectId', '', new UID(), 'Project unique ID.') + // TODO: When migrating to Platform API, mark keyId required for consistency + ->param('keyId', 'unique()', new CustomId(), 'Key ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', true) ->param('name', null, new Text(128), 'Key name. Max length: 128 chars.') ->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.') ->param('expire', null, new Nullable(new DatetimeValidator()), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true) ->inject('response') ->inject('dbForPlatform') - ->action(function (string $projectId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForPlatform) { + ->action(function (string $projectId, string $keyId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForPlatform) { + $keyId = $keyId == 'unique()' ? ID::unique() : $keyId; $project = $dbForPlatform->getDocument('projects', $projectId); @@ -1108,7 +1112,7 @@ Http::post('/v1/projects/:projectId/keys') } $key = new Document([ - '$id' => ID::unique(), + '$id' => $keyId, '$permissions' => [ Permission::read(Role::any()), Permission::update(Role::any()), diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index 52c53016d6..1859d551a4 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -60,6 +60,7 @@ trait ProjectCustom 'cookie' => 'a_session_console=' . $this->getRoot()['session'], 'x-appwrite-project' => 'console', ], [ + 'keyId' => ID::unique(), 'name' => 'Demo Project Key', 'scopes' => [ 'users.read', @@ -194,6 +195,7 @@ trait ProjectCustom 'cookie' => 'a_session_console=' . $this->getRoot()['session'], 'x-appwrite-project' => 'console', ], [ + 'keyId' => ID::unique(), 'name' => 'Demo Project Key', 'scopes' => $scopes, ]); diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 5280509967..90c009da80 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -2774,6 +2774,7 @@ class ProjectsConsoleClientTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'cookie' => 'a_session_console=' . $this->getRoot()['session'], ]), [ + 'keyId' => ID::unique(), 'name' => 'Key Test', 'scopes' => ['functions.read', 'teams.write'], ]); @@ -3123,6 +3124,7 @@ class ProjectsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ + 'keyId' => ID::unique(), 'name' => 'Key Test', 'scopes' => ['teams.read', 'teams.write'], ]); @@ -3138,6 +3140,52 @@ class ProjectsConsoleClientTest extends Scope $this->assertArrayHasKey('accessedAt', $response['body']); $this->assertEmpty($response['body']['accessedAt']); + /** + * Test for SUCCESS without key ID + */ + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Key Custom', + 'scopes' => ['teams.read', 'teams.write'], + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']['$id']); + + /** + * Test for SUCCESS with custom ID + */ + $customKeyId = 'key-with-custom-id'; + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'keyId' => $customKeyId, + 'name' => 'Key Custom', + 'scopes' => ['teams.read', 'teams.write'], + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertSame($customKeyId, $response['body']['$id']); + + /** + * Test for SUCCESS with magic string ID + */ + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'keyId' => 'unique()', + 'name' => 'Key Custom', + 'scopes' => ['teams.read', 'teams.write'], + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertNotSame('unique()', $response['body']['$id']); + $data = array_merge($data, [ 'keyId' => $response['body']['$id'], 'secret' => $response['body']['secret'] @@ -3150,6 +3198,7 @@ class ProjectsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ + 'keyId' => ID::unique(), 'name' => 'Key Test', 'scopes' => ['unknown'], ]); @@ -3174,7 +3223,7 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(1, $response['body']['total']); + $this->assertEquals(4, $response['body']['total']); /** * Test for FAILURE @@ -3200,7 +3249,7 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); $this->assertEquals($keyId, $response['body']['$id']); - $this->assertEquals('Key Test', $response['body']['name']); + $this->assertEquals('Key Custom', $response['body']['name']); $this->assertContains('teams.read', $response['body']['scopes']); $this->assertContains('teams.write', $response['body']['scopes']); $this->assertCount(2, $response['body']['scopes']); @@ -3240,6 +3289,7 @@ class ProjectsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ + 'keyId' => ID::unique(), 'name' => 'Key Test', 'scopes' => ['users.write'], 'expire' => DateTime::addSeconds(new \DateTime(), 3600), @@ -3260,6 +3310,7 @@ class ProjectsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ + 'keyId' => ID::unique(), 'name' => 'Key Test', 'scopes' => ['health.read'], 'expire' => null, @@ -3282,6 +3333,7 @@ class ProjectsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ + 'keyId' => ID::unique(), 'name' => 'Key Test', 'scopes' => ['health.read'], 'expire' => DateTime::addSeconds(new \DateTime(), -3600), @@ -3323,6 +3375,7 @@ class ProjectsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ + 'keyId' => ID::unique(), 'name' => 'Key Test', 'scopes' => ['teams.read'], 'expire' => DateTime::addSeconds(new \DateTime(), 3600), @@ -3355,6 +3408,7 @@ class ProjectsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ + 'keyId' => ID::unique(), 'name' => 'Key Test', 'scopes' => ['health.read'], 'expire' => DateTime::addSeconds(new \DateTime(), 3600), @@ -4364,6 +4418,7 @@ class ProjectsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ + 'keyId' => ID::unique(), 'name' => 'Key Test', 'scopes' => ['users.read', 'users.write'], ]); @@ -4384,6 +4439,7 @@ class ProjectsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ + 'keyId' => ID::unique(), 'name' => 'Key Test', 'scopes' => ['users.read', 'users.write'], ]);