From d0181787b916ea843da81e1cc3a8f8957080ebe2 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Tue, 15 Apr 2025 17:38:34 +0400 Subject: [PATCH 1/6] fix: usage test assertion --- tests/e2e/General/UsageTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/General/UsageTest.php b/tests/e2e/General/UsageTest.php index e614e2e185..9406e6af35 100644 --- a/tests/e2e/General/UsageTest.php +++ b/tests/e2e/General/UsageTest.php @@ -148,7 +148,7 @@ class UsageTest extends Scope ); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(31, count($response['body'])); + $this->assertGreaterThanOrEqual(31, count($response['body'])); $this->validateDates($response['body']['network']); $this->validateDates($response['body']['requests']); $this->validateDates($response['body']['users']); From 304f67c88f1421a454e1a74e88d58dabc6e59626 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Tue, 15 Apr 2025 18:18:40 +0400 Subject: [PATCH 2/6] fix: usage test assertion --- tests/e2e/General/UsageTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/General/UsageTest.php b/tests/e2e/General/UsageTest.php index 9406e6af35..d0e37e5639 100644 --- a/tests/e2e/General/UsageTest.php +++ b/tests/e2e/General/UsageTest.php @@ -327,7 +327,7 @@ class UsageTest extends Scope ] ); - $this->assertEquals(31, count($response['body'])); + $this->assertGreaterThanOrEqual(31, count($response['body'])); $this->assertEquals(1, count($response['body']['requests'])); $this->assertEquals($requestsTotal, $response['body']['requests'][array_key_last($response['body']['requests'])]['value']); $this->validateDates($response['body']['requests']); @@ -548,7 +548,7 @@ class UsageTest extends Scope ] ); - $this->assertEquals(31, count($response['body'])); + $this->assertGreaterThanOrEqual(31, count($response['body'])); $this->assertEquals(1, count($response['body']['requests'])); $this->assertEquals(1, count($response['body']['network'])); $this->assertEquals($requestsTotal, $response['body']['requests'][array_key_last($response['body']['requests'])]['value']); From ba58a372d17a0b88f80cbcdd99006bf88bb260b5 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 16 Apr 2025 10:50:53 +1200 Subject: [PATCH 3/6] Throw on key not found --- app/controllers/shared/api.php | 48 ++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index bf35f9073b..22dc54d2c9 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -238,34 +238,36 @@ App::init() subject: 'keys' ); - if ($dbKey) { - $accessedAt = $dbKey->getAttribute('accessedAt', ''); + if (!$dbKey) { + throw new Exception(Exception::USER_UNAUTHORIZED); + } - if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) { - $dbKey->setAttribute('accessedAt', DateTime::now()); + $accessedAt = $dbKey->getAttribute('accessedAt', ''); + + if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) { + $dbKey->setAttribute('accessedAt', DateTime::now()); + $dbForPlatform->updateDocument('keys', $dbKey->getId(), $dbKey); + $dbForPlatform->purgeCachedDocument('projects', $project->getId()); + } + + $sdkValidator = new WhiteList($servers, true); + $sdk = $request->getHeader('x-sdk-name', 'UNKNOWN'); + + if ($sdkValidator->isValid($sdk)) { + $sdks = $dbKey->getAttribute('sdks', []); + + if (!in_array($sdk, $sdks)) { + $sdks[] = $sdk; + $dbKey->setAttribute('sdks', $sdks); + + /** Update access time as well */ + $dbKey->setAttribute('accessedAt', Datetime::now()); $dbForPlatform->updateDocument('keys', $dbKey->getId(), $dbKey); $dbForPlatform->purgeCachedDocument('projects', $project->getId()); } - - $sdkValidator = new WhiteList($servers, true); - $sdk = $request->getHeader('x-sdk-name', 'UNKNOWN'); - - if ($sdkValidator->isValid($sdk)) { - $sdks = $dbKey->getAttribute('sdks', []); - - if (!in_array($sdk, $sdks)) { - $sdks[] = $sdk; - $dbKey->setAttribute('sdks', $sdks); - - /** Update access time as well */ - $dbKey->setAttribute('accessedAt', Datetime::now()); - $dbForPlatform->updateDocument('keys', $dbKey->getId(), $dbKey); - $dbForPlatform->purgeCachedDocument('projects', $project->getId()); - } - } - - $queueForAudits->setUser($user); } + + $queueForAudits->setUser($user); } } // Admin User Authentication elseif (($project->getId() === 'console' && !$team->isEmpty() && !$user->isEmpty()) || ($project->getId() !== 'console' && !$user->isEmpty() && $mode === APP_MODE_ADMIN)) { From bea723ff2ef3e02e3d48a24c4d8c7c41d1756e1f Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 16 Apr 2025 12:04:19 +1200 Subject: [PATCH 4/6] Add test --- composer.lock | 34 +++++++++---------- .../Projects/ProjectsConsoleClientTest.php | 34 +++++++++++++++++++ 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/composer.lock b/composer.lock index c6c9ae2900..c8c1e3c192 100644 --- a/composer.lock +++ b/composer.lock @@ -1365,16 +1365,16 @@ }, { "name": "open-telemetry/sdk", - "version": "1.2.3", + "version": "1.2.4", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sdk.git", - "reference": "0e7804c176c4b09d95b7985400aa38ce544cb7fc" + "reference": "47fcb66ae5328c5a799195247b1dce551d85873e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/0e7804c176c4b09d95b7985400aa38ce544cb7fc", - "reference": "0e7804c176c4b09d95b7985400aa38ce544cb7fc", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/47fcb66ae5328c5a799195247b1dce551d85873e", + "reference": "47fcb66ae5328c5a799195247b1dce551d85873e", "shasum": "" }, "require": { @@ -1451,7 +1451,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-04-08T09:55:41+00:00" + "time": "2025-04-15T07:02:07+00:00" }, { "name": "open-telemetry/sem-conv", @@ -3351,16 +3351,16 @@ }, { "name": "utopia-php/cli", - "version": "0.15.1", + "version": "0.15.2", "source": { "type": "git", "url": "https://github.com/utopia-php/cli.git", - "reference": "d69bbe51a6a94dc4e5bcdd542b5938038b985a65" + "reference": "da00ff6b8b29a826a1794002ae43442cdf3a0f5f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cli/zipball/d69bbe51a6a94dc4e5bcdd542b5938038b985a65", - "reference": "d69bbe51a6a94dc4e5bcdd542b5938038b985a65", + "url": "https://api.github.com/repos/utopia-php/cli/zipball/da00ff6b8b29a826a1794002ae43442cdf3a0f5f", + "reference": "da00ff6b8b29a826a1794002ae43442cdf3a0f5f", "shasum": "" }, "require": { @@ -3394,9 +3394,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cli/issues", - "source": "https://github.com/utopia-php/cli/tree/0.15.1" + "source": "https://github.com/utopia-php/cli/tree/0.15.2" }, - "time": "2024-10-04T13:55:36+00:00" + "time": "2025-04-15T10:08:48+00:00" }, { "name": "utopia-php/compression", @@ -4767,16 +4767,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "0.40.11", + "version": "0.40.12", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "0ec5f4a60c15e33e208bc3444ba6148b1d0f0027" + "reference": "182ec17848f81b78c336379bac94ff92b7a73365" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/0ec5f4a60c15e33e208bc3444ba6148b1d0f0027", - "reference": "0ec5f4a60c15e33e208bc3444ba6148b1d0f0027", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/182ec17848f81b78c336379bac94ff92b7a73365", + "reference": "182ec17848f81b78c336379bac94ff92b7a73365", "shasum": "" }, "require": { @@ -4812,9 +4812,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/0.40.11" + "source": "https://github.com/appwrite/sdk-generator/tree/0.40.12" }, - "time": "2025-03-26T10:53:16+00:00" + "time": "2025-04-02T23:36:11+00:00" }, { "name": "doctrine/annotations", diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 8e2ab03880..a53d53ace4 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -2989,6 +2989,40 @@ class ProjectsConsoleClientTest extends Scope $this->assertEmpty($response['body']); } + /** + * @depends testCreateProject + */ + public function testUseInvalidKey(array $data): void + { + $id = $data['projectId'] ?? ''; + + // Create bucket + $bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $id, + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders()), [ + 'bucketId' => ID::unique(), + 'name' => 'Test Bucket', + 'permission' => 'file', + 'enabled' => true, + ]); + + $this->assertEquals(201, $bucket['headers']['status-code']); + $this->assertNotEmpty($bucket['body']['$id']); + + $bucketId = $bucket['body']['$id']; + + // Use invalid key to read + $response = $this->client->call(Client::METHOD_GET, "/storage/buckets/{$bucketId}/files", [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $id, + 'x-appwrite-key' => 'invalid-key' + ]); + + $this->assertEquals(401, $response['headers']['status-code']); + } + // JWT Keys /** From 53660f310019859fd4c5e9d60cf35627e8d4fe7a Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 16 Apr 2025 12:30:22 +1200 Subject: [PATCH 5/6] Add more cases --- .../Projects/ProjectsConsoleClientTest.php | 142 ++++++++++++------ 1 file changed, 93 insertions(+), 49 deletions(-) diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index a53d53ace4..f89428d79a 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -2786,32 +2786,35 @@ class ProjectsConsoleClientTest extends Scope */ public function testValidateProjectKey($data): void { - $id = $data['projectId'] ?? ''; + $projectId = $data['projectId'] ?? ''; + $teamId = $data['teamId'] ?? ''; /** * Test for SUCCESS */ - $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/keys', array_merge([ + + // Expiring key + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $projectId . '/keys', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'name' => 'Key Test', - 'scopes' => ['health.read'], + 'scopes' => ['users.write'], 'expire' => DateTime::addSeconds(new \DateTime(), 3600), ]); - $response = $this->client->call(Client::METHOD_GET, '/health', [ + $response = $this->client->call(Client::METHOD_POST, '/users', [ 'content-type' => 'application/json', - 'x-appwrite-project' => $id, + 'x-appwrite-project' => $projectId, 'x-appwrite-key' => $response['body']['secret'] - ], []); + ], [ + 'userId' => ID::unique(), + ]); - $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(201, $response['headers']['status-code']); - /** - * Test for SUCCESS - */ - $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/keys', array_merge([ + // No expiry + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $projectId . '/keys', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -2822,7 +2825,7 @@ class ProjectsConsoleClientTest extends Scope $response = $this->client->call(Client::METHOD_GET, '/health', [ 'content-type' => 'application/json', - 'x-appwrite-project' => $id, + 'x-appwrite-project' => $projectId, 'x-appwrite-key' => $response['body']['secret'] ], []); @@ -2831,7 +2834,9 @@ class ProjectsConsoleClientTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/keys', array_merge([ + + // Expired key + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $projectId . '/keys', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -2842,9 +2847,82 @@ class ProjectsConsoleClientTest extends Scope $response = $this->client->call(Client::METHOD_GET, '/health', [ 'content-type' => 'application/json', - 'x-appwrite-project' => $id, + 'x-appwrite-project' => $projectId, 'x-appwrite-key' => $response['body']['secret'] - ], []); + ]); + + $this->assertEquals(401, $response['headers']['status-code']); + + // Invalid key + $bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders()), [ + 'bucketId' => ID::unique(), + 'name' => 'Test Bucket', + ]); + + $this->assertEquals(201, $bucket['headers']['status-code']); + $this->assertNotEmpty($bucket['body']['$id']); + + $bucketId = $bucket['body']['$id']; + + $response = $this->client->call(Client::METHOD_GET, "/storage/buckets/{$bucketId}/files", [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-key' => 'invalid-key' + ]); + + $this->assertEquals(401, $response['headers']['status-code']); + + // Invalid scopes + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $projectId . '/keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Key Test', + 'scopes' => ['teams.read'], + 'expire' => DateTime::addSeconds(new \DateTime(), 3600), + ]); + + $response = $this->client->call(Client::METHOD_GET, '/users', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-key' => $response['body']['secret'] + ]); + + $this->assertEquals(401, $response['headers']['status-code']); + + // Invalid key from different project + $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'projectId' => ID::unique(), + 'name' => 'Project Test 2', + 'teamId' => $teamId, + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + $project2Id = $response['body']['$id']; + + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $project2Id . '/keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Key Test', + 'scopes' => ['health.read'], + 'expire' => DateTime::addSeconds(new \DateTime(), 3600), + ]); + + $response = $this->client->call(Client::METHOD_GET, '/health', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-key' => $response['body']['secret'] + ]); $this->assertEquals(401, $response['headers']['status-code']); } @@ -2989,40 +3067,6 @@ class ProjectsConsoleClientTest extends Scope $this->assertEmpty($response['body']); } - /** - * @depends testCreateProject - */ - public function testUseInvalidKey(array $data): void - { - $id = $data['projectId'] ?? ''; - - // Create bucket - $bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $id, - 'x-appwrite-mode' => 'admin', - ], $this->getHeaders()), [ - 'bucketId' => ID::unique(), - 'name' => 'Test Bucket', - 'permission' => 'file', - 'enabled' => true, - ]); - - $this->assertEquals(201, $bucket['headers']['status-code']); - $this->assertNotEmpty($bucket['body']['$id']); - - $bucketId = $bucket['body']['$id']; - - // Use invalid key to read - $response = $this->client->call(Client::METHOD_GET, "/storage/buckets/{$bucketId}/files", [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $id, - 'x-appwrite-key' => 'invalid-key' - ]); - - $this->assertEquals(401, $response['headers']['status-code']); - } - // JWT Keys /** From b9e8c1d8bce09480d259bba6586e9e2a9cca40d4 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 16 Apr 2025 13:07:11 +1200 Subject: [PATCH 6/6] Skip server console test --- tests/e2e/Services/Databases/DatabasesBase.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index 0f57f94515..dca832ac01 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -90,6 +90,12 @@ trait DatabasesBase */ public function testConsoleProject(array $data) { + if ($this->getSide() === 'server') { + // Server side can't get past the invalid key check anyway + $this->expectNotToPerformAssertions(); + return; + } + $response = $this->client->call( Client::METHOD_GET, '/databases/console/collections/' . $data['moviesId'] . '/documents',