From 27fc8058b9c0439fb951205212cacd84e1ef02b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Sat, 11 Apr 2026 14:19:05 +0200 Subject: [PATCH] Fix failing tests --- app/controllers/shared/api.php | 19 ++++--- .../Database/Validator/Queries/Webhooks.php | 44 ++++++++++++++- tests/e2e/Scopes/ProjectCustom.php | 4 +- tests/e2e/Services/Project/KeysBase.php | 6 +- tests/e2e/Services/Project/ServicesBase.php | 3 - tests/e2e/Services/Projects/ProjectsBase.php | 8 +-- .../Projects/ProjectsConsoleClientTest.php | 56 +++++++++---------- 7 files changed, 90 insertions(+), 50 deletions(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 11ac345fca..9a786c2d18 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -424,7 +424,7 @@ Http::init() } if (! empty($method)) { - $namespace = $method->getNamespace(); + $namespace = \strtolower($method->getNamespace()); if ( array_key_exists($namespace, $project->getAttribute('services', [])) @@ -435,6 +435,15 @@ Http::init() } } + // Step 8b: Check REST protocol status + if ( + array_key_exists('rest', $project->getAttribute('apis', [])) + && ! $project->getAttribute('apis', [])['rest'] + && ! ($user->isPrivileged($authorization->getRoles()) || $user->isApp($authorization->getRoles())) + ) { + throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED); + } + // Step 9: Validate scope permissions $allowed = (array) $route->getLabel('scope', 'none'); if (empty(\array_intersect($allowed, $scopes))) { @@ -510,14 +519,6 @@ Http::init() default => '', }; - if ( - array_key_exists('rest', $project->getAttribute('apis', [])) - && ! $project->getAttribute('apis', [])['rest'] - && ! ($user->isPrivileged($authorization->getRoles()) || $user->isApp($authorization->getRoles())) - ) { - throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED); - } - /* * Abuse Check */ diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Webhooks.php b/src/Appwrite/Utopia/Database/Validator/Queries/Webhooks.php index 42d5166909..07e27f06cb 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Webhooks.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Webhooks.php @@ -15,12 +15,54 @@ class Webhooks extends Base 'attempts', ]; + /** + * Map API attribute names to DB column names. + */ + private const ATTRIBUTE_ALIASES = [ + 'tls' => 'security', + 'authUsername' => 'httpUser', + ]; + + /** + * DB column names used for schema validation. + */ + private const DB_ATTRIBUTES = [ + 'name', + 'url', + 'httpUser', + 'security', + 'events', + 'enabled', + 'logs', + 'attempts', + ]; + /** * Expression constructor * */ public function __construct() { - parent::__construct('webhooks', self::ALLOWED_ATTRIBUTES); + parent::__construct('webhooks', self::DB_ATTRIBUTES); + } + + /** + * Convert API attribute names to DB column names in query strings before validation. + */ + public function isValid($value): bool + { + if (\is_array($value)) { + foreach ($value as &$queryString) { + if (!\is_string($queryString)) { + continue; + } + foreach (self::ATTRIBUTE_ALIASES as $alias => $dbName) { + $queryString = \str_replace('"' . $alias . '"', '"' . $dbName . '"', $queryString); + } + } + unset($queryString); + } + + return parent::isValid($value); } } diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index 10641019f0..a62a1e8ba3 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -216,7 +216,7 @@ trait ProjectCustom 'users.*' ], 'url' => 'http://request-catcher-webhook:5000/', - 'security' => false, + 'tls' => false, ]); $this->assertEquals(201, $webhook['headers']['status-code']); @@ -243,7 +243,7 @@ trait ProjectCustom 'apiKey' => $key['body']['secret'], 'devKey' => $devKey['body']['secret'], 'webhookId' => $webhook['body']['$id'], - 'signatureKey' => $webhook['body']['signatureKey'], + 'signatureKey' => $webhook['body']['secret'], ]; } diff --git a/tests/e2e/Services/Project/KeysBase.php b/tests/e2e/Services/Project/KeysBase.php index 48c584a374..505c7f6539 100644 --- a/tests/e2e/Services/Project/KeysBase.php +++ b/tests/e2e/Services/Project/KeysBase.php @@ -78,12 +78,12 @@ trait KeysBase $this->deleteKey($key['body']['$id']); } - public function testCreateKeyWithNullScopes(): void + public function testCreateKeyWithEmptyScopes(): void { $key = $this->createKey( ID::unique(), - 'Null Scopes Key', - null, + 'Empty Scopes Key', + [], ); $this->assertSame(201, $key['headers']['status-code']); diff --git a/tests/e2e/Services/Project/ServicesBase.php b/tests/e2e/Services/Project/ServicesBase.php index 4c0070263e..1bc7ce5042 100644 --- a/tests/e2e/Services/Project/ServicesBase.php +++ b/tests/e2e/Services/Project/ServicesBase.php @@ -24,7 +24,6 @@ trait ServicesBase 'sites', 'functions', 'proxy', - 'graphql', 'migrations', 'messaging', ]; @@ -150,7 +149,6 @@ trait ServicesBase 'sites' => ['method' => Client::METHOD_GET, 'path' => '/sites'], 'functions' => ['method' => Client::METHOD_GET, 'path' => '/functions'], 'proxy' => ['method' => Client::METHOD_GET, 'path' => '/proxy/rules'], - 'graphql' => ['method' => Client::METHOD_POST, 'path' => '/graphql'], 'migrations' => ['method' => Client::METHOD_GET, 'path' => '/migrations'], 'messaging' => ['method' => Client::METHOD_GET, 'path' => '/messaging/providers'], ]; @@ -188,7 +186,6 @@ trait ServicesBase 'sites' => ['method' => Client::METHOD_GET, 'path' => '/sites'], 'functions' => ['method' => Client::METHOD_GET, 'path' => '/functions'], 'proxy' => ['method' => Client::METHOD_GET, 'path' => '/proxy/rules'], - 'graphql' => ['method' => Client::METHOD_POST, 'path' => '/graphql'], 'migrations' => ['method' => Client::METHOD_GET, 'path' => '/migrations'], 'messaging' => ['method' => Client::METHOD_GET, 'path' => '/messaging/providers'], ]; diff --git a/tests/e2e/Services/Projects/ProjectsBase.php b/tests/e2e/Services/Projects/ProjectsBase.php index 6122e95c75..122104e3d9 100644 --- a/tests/e2e/Services/Projects/ProjectsBase.php +++ b/tests/e2e/Services/Projects/ProjectsBase.php @@ -90,16 +90,16 @@ trait ProjectsBase 'name' => 'Webhook Test', 'events' => ['users.*.create', 'users.*.update.email'], 'url' => 'https://appwrite.io', - 'security' => true, - 'httpUser' => 'username', - 'httpPass' => 'password', + 'tls' => true, + 'authUsername' => 'username', + 'authPassword' => 'password', ]); $this->assertEquals(201, $response['headers']['status-code']); self::$cachedProjectWithWebhook = array_merge($projectData, [ 'webhookId' => $response['body']['$id'], - 'signatureKey' => $response['body']['signatureKey'] + 'signatureKey' => $response['body']['secret'] ]); return self::$cachedProjectWithWebhook; diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 096d90d8d2..7b9848e38f 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -3053,9 +3053,9 @@ class ProjectsConsoleClientTest extends Scope 'name' => 'Webhook Test', 'events' => ['users.*.create', 'users.*.update.email'], 'url' => 'https://appwrite.io', - 'security' => true, - 'httpUser' => 'username', - 'httpPass' => 'password', + 'tls' => true, + 'authUsername' => 'username', + 'authPassword' => 'password', ]); $this->assertEquals(201, $response['headers']['status-code']); @@ -3064,9 +3064,9 @@ class ProjectsConsoleClientTest extends Scope $this->assertContains('users.*.update.email', $response['body']['events']); $this->assertCount(2, $response['body']['events']); $this->assertEquals('https://appwrite.io', $response['body']['url']); - $this->assertIsBool($response['body']['security']); - $this->assertEquals(true, $response['body']['security']); - $this->assertEquals('username', $response['body']['httpUser']); + $this->assertIsBool($response['body']['tls']); + $this->assertEquals(true, $response['body']['tls']); + $this->assertEquals('username', $response['body']['authUsername']); /** * Test for FAILURE @@ -3080,9 +3080,9 @@ class ProjectsConsoleClientTest extends Scope 'name' => 'Webhook Test', 'events' => ['account.unknown', 'users.*.update.email'], 'url' => 'https://appwrite.io', - 'security' => true, - 'httpUser' => 'username', - 'httpPass' => 'password', + 'tls' => true, + 'authUsername' => 'username', + 'authPassword' => 'password', ]); $this->assertEquals(400, $response['headers']['status-code']); @@ -3140,8 +3140,8 @@ class ProjectsConsoleClientTest extends Scope $this->assertContains('users.*.update.email', $response['body']['events']); $this->assertCount(2, $response['body']['events']); $this->assertEquals('https://appwrite.io', $response['body']['url']); - $this->assertEquals('username', $response['body']['httpUser']); - $this->assertEquals('password', $response['body']['httpPass']); + $this->assertEquals('username', $response['body']['authUsername']); + $this->assertEquals('password', $response['body']['authPassword']); /** * Test for FAILURE @@ -3169,7 +3169,7 @@ class ProjectsConsoleClientTest extends Scope 'name' => 'Webhook Test Update', 'events' => ['users.*.delete', 'users.*.sessions.*.delete', 'buckets.*.files.*.create'], 'url' => 'https://appwrite.io/new', - 'security' => false, + 'tls' => false, ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -3181,10 +3181,10 @@ class ProjectsConsoleClientTest extends Scope $this->assertContains('buckets.*.files.*.create', $response['body']['events']); $this->assertCount(3, $response['body']['events']); $this->assertEquals('https://appwrite.io/new', $response['body']['url']); - $this->assertIsBool($response['body']['security']); - $this->assertEquals(false, $response['body']['security']); - $this->assertEquals('', $response['body']['httpUser']); - $this->assertEquals('', $response['body']['httpPass']); + $this->assertIsBool($response['body']['tls']); + $this->assertEquals(false, $response['body']['tls']); + $this->assertEquals('', $response['body']['authUsername']); + $this->assertEquals('', $response['body']['authPassword']); $response = $this->client->call(Client::METHOD_GET, '/webhooks/' . $webhookId, array_merge([ 'content-type' => 'application/json', @@ -3201,10 +3201,10 @@ class ProjectsConsoleClientTest extends Scope $this->assertContains('buckets.*.files.*.create', $response['body']['events']); $this->assertCount(3, $response['body']['events']); $this->assertEquals('https://appwrite.io/new', $response['body']['url']); - $this->assertIsBool($response['body']['security']); - $this->assertEquals(false, $response['body']['security']); - $this->assertEquals('', $response['body']['httpUser']); - $this->assertEquals('', $response['body']['httpPass']); + $this->assertIsBool($response['body']['tls']); + $this->assertEquals(false, $response['body']['tls']); + $this->assertEquals('', $response['body']['authUsername']); + $this->assertEquals('', $response['body']['authPassword']); /** * Test for FAILURE @@ -3217,7 +3217,7 @@ class ProjectsConsoleClientTest extends Scope 'name' => 'Webhook Test Update', 'events' => ['users.*.delete', 'users.*.sessions.*.delete', 'buckets.*.files.*.unknown'], 'url' => 'https://appwrite.io/new', - 'security' => false, + 'tls' => false, ]); $this->assertEquals(400, $response['headers']['status-code']); @@ -3230,7 +3230,7 @@ class ProjectsConsoleClientTest extends Scope 'name' => 'Webhook Test Update', 'events' => ['users.*.delete', 'users.*.sessions.*.delete', 'buckets.*.files.*.create'], 'url' => 'appwrite.io/new', - 'security' => false, + 'tls' => false, ]); $this->assertEquals(400, $response['headers']['status-code']); @@ -3255,15 +3255,15 @@ class ProjectsConsoleClientTest extends Scope $webhookId = $data['webhookId']; $signatureKey = $data['signatureKey']; - $response = $this->client->call(Client::METHOD_PATCH, '/webhooks/' . $webhookId . '/signature', array_merge([ + $response = $this->client->call(Client::METHOD_PATCH, '/webhooks/' . $webhookId . '/secret', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $id, 'x-appwrite-mode' => 'admin' ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertNotEmpty($response['body']['signatureKey']); - $this->assertNotEquals($signatureKey, $response['body']['signatureKey']); + $this->assertNotEmpty($response['body']['secret']); + $this->assertNotEquals($signatureKey, $response['body']['secret']); } public function testDeleteProjectWebhook(): void @@ -3282,9 +3282,9 @@ class ProjectsConsoleClientTest extends Scope 'name' => 'Webhook To Delete', 'events' => ['users.*.create'], 'url' => 'https://appwrite.io', - 'security' => true, - 'httpUser' => 'username', - 'httpPass' => 'password', + 'tls' => true, + 'authUsername' => 'username', + 'authPassword' => 'password', ]); $this->assertEquals(201, $response['headers']['status-code']);