diff --git a/.env b/.env index c10c12613b..d18e63c56e 100644 --- a/.env +++ b/.env @@ -15,7 +15,7 @@ _APP_SYSTEM_TEAM_EMAIL=team@appwrite.io _APP_EMAIL_SECURITY=security@appwrite.io _APP_EMAIL_CERTIFICATES=certificates@appwrite.io _APP_SYSTEM_RESPONSE_FORMAT= -_APP_OPTIONS_ABUSE=disabled +_APP_OPTIONS_ABUSE=enabled _APP_OPTIONS_ROUTER_PROTECTION=disabled _APP_OPTIONS_FORCE_HTTPS=disabled _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS=disabled diff --git a/tests/e2e/Services/Projects/ProjectsBase.php b/tests/e2e/Services/Projects/ProjectsBase.php index 53d9626252..0d1d6a5a44 100644 --- a/tests/e2e/Services/Projects/ProjectsBase.php +++ b/tests/e2e/Services/Projects/ProjectsBase.php @@ -2,6 +2,48 @@ namespace Tests\E2E\Services\Projects; +use Tests\E2E\Client; +use Utopia\Database\Helpers\ID; + trait ProjectsBase { + protected function setupProject(mixed $params): string + { + $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'teamId' => ID::unique(), + 'name' => 'Project Test', + ]); + + $this->assertEquals(201, $team['headers']['status-code'], 'Setup team failed with status code: ' . $team['headers']['status-code'] . ' and response: ' . json_encode($team['body'], JSON_PRETTY_PRINT)); + + $project = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + ...$params, + 'teamId' => $team['body']['$id'], + ]); + + $this->assertEquals(201, $project['headers']['status-code'], 'Setup project failed with status code: ' . $project['headers']['status-code'] . ' and response: ' . json_encode($project['body'], JSON_PRETTY_PRINT)); + + return $project['body']['$id']; + } + + protected function setupDevKey(mixed $params): array + { + $devKey = $this->client->call(Client::METHOD_POST, '/projects/' . $params['projectId'] . '/dev-keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), $params); + + $this->assertEquals(201, $devKey['headers']['status-code'], 'Setup devKey failed with status code: ' . $devKey['headers']['status-code'] . ' and response: ' . json_encode($devKey['body'], JSON_PRETTY_PRINT)); + + return [ + '$id' => $devKey['body']['$id'], + 'secret' => $devKey['body']['secret'], + ]; + } } diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 34ecba242f..491e0371b0 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -22,7 +22,6 @@ class ProjectsConsoleClientTest extends Scope use ProjectConsole; use SideClient; use Async; - use ProjectsDevKeys; /** * @group devKeys @@ -4241,4 +4240,596 @@ class ProjectsConsoleClientTest extends Scope return $data; } + + /** + * Devkeys Tests starts here ------------------------------------------------ + */ + + /** + * @group devKeys + */ + public function testCreateProjectDevKey(): void + { + /** + * Test for SUCCESS + */ + $id = $this->setupProject([ + 'projectId' => ID::unique(), + 'name' => 'testCreateProjectDevKey', + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/dev-keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), 36000) + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals('Key Test', $response['body']['name']); + $this->assertNotEmpty($response['body']['secret']); + $this->assertArrayHasKey('accessedAt', $response['body']); + $this->assertEmpty($response['body']['accessedAt']); + + /** Create a second dev key */ + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/dev-keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Dev Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), 36000) + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals('Dev Key Test', $response['body']['name']); + $this->assertNotEmpty($response['body']['secret']); + $this->assertArrayHasKey('accessedAt', $response['body']); + $this->assertEmpty($response['body']['accessedAt']); + + /** + * Test for FAILURE + */ + + /** TEST expiry date is required */ + $res = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/dev-keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Key Test' + ]); + + $this->assertEquals(400, $res['headers']['status-code']); + } + + + /** + * @group devKeys + */ + public function testListProjectDevKey(): void + { + /** + * Test for SUCCESS + */ + $projectId = $this->setupProject([ + 'projectId' => ID::unique(), + 'name' => 'testListProjectDevKey', + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + /** Create devKey 1 */ + $this->setupDevKey([ + 'projectId' => $projectId, + 'name' => 'Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), 36000) + ]); + + /** Create devKey 2 */ + $this->setupDevKey([ + 'projectId' => $projectId, + 'name' => 'Dev Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), 36000) + ]); + + /** List all dev keys */ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/dev-keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(2, $response['body']['total']); + + /** List dev keys with limit */ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/dev-keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::limit(1)->toString() + ] + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(1, $response['body']['total']); + + /** List dev keys with search */ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/dev-keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'search' => 'Dev' + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(1, $response['body']['total']); + $this->assertEquals('Dev Key Test', $response['body']['devKeys'][0]['name']); + + /** List dev keys with querying `expire` */ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/dev-keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [Query::lessThan('expire', (new \DateTime())->format('Y-m-d H:i:s'))->toString()] + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(0, $response['body']['total']); // No dev keys expired + + /** + * Test for FAILURE + */ + + /** Test for search with invalid query */ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/dev-keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::search('name', 'Invalid')->toString() + ] + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertEquals('Invalid `queries` param: Invalid query: Attribute not found in schema: name', $response['body']['message']); + } + + + /** + * @group devKeys + */ + public function testGetProjectDevKey(): void + { + /** + * Test for SUCCESS + */ + $projectId = $this->setupProject([ + 'projectId' => ID::unique(), + 'name' => 'testGetProjectDevKey', + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $devKey = $this->setupDevKey([ + 'projectId' => $projectId, + 'name' => 'Dev Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), 36000) + ]); + + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/dev-keys/' . $devKey['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals($devKey['$id'], $response['body']['$id']); + $this->assertEquals('Dev Key Test', $response['body']['name']); + $this->assertNotEmpty($response['body']['secret']); + $this->assertArrayHasKey('accessedAt', $response['body']); + $this->assertEmpty($response['body']['accessedAt']); + + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/dev-keys/error', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + + $this->assertEquals(404, $response['headers']['status-code']); + } + + /** + * @group devKeys + */ + public function testGetDevKeyWithSdks(): void + { + /** + * Test for SUCCESS + */ + $projectId = $this->setupProject([ + 'projectId' => ID::unique(), + 'name' => 'testGetDevKeyWithSdks', + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $devKey = $this->setupDevKey([ + 'projectId' => $projectId, + 'name' => 'Dev Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), 36000) + ]); + + /** Use dev key with python sdk */ + $res = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-dev-key' => $devKey['secret'], + 'x-sdk-name' => 'python' + ], [ + 'email' => 'user@appwrite.io', + 'password' => 'password' + ]); + $this->assertEquals(401, $res['headers']['status-code']); + + /** Use dev key with php sdk */ + $res = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-dev-key' => $devKey['secret'], + 'x-sdk-name' => 'php' + ], [ + 'email' => 'user@appwrite.io', + 'password' => 'password' + ]); + $this->assertEquals(401, $res['headers']['status-code']); + + /** Get the dev key */ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/dev-keys/' . $devKey['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertArrayHasKey('sdks', $response['body']); + $this->assertCount(2, $response['body']['sdks']); + $this->assertContains('python', $response['body']['sdks']); + $this->assertContains('php', $response['body']['sdks']); + } + + /** + * @group devKeys + */ + public function testNoHostValidationWithDevKey(): void + { + /** + * Test for SUCCESS + */ + $projectId = $this->setupProject([ + 'projectId' => ID::unique(), + 'name' => 'testNoHostValidationWithDevKey', + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $devKey = $this->setupDevKey([ + 'projectId' => $projectId, + 'name' => 'Dev Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), 36000) + ]); + + /** Test oauth2 and get invalid `success` URL */ + $response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/google', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], [ + 'success' => 'https://example.com', + 'failure' => 'https://example.com' + ]); + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertEquals('Invalid `success` param: URL host must be one of: localhost, appwrite.io, *.appwrite.io', $response['body']['message']); + + /** Test oauth2 with devKey and now get oauth2 is disabled */ + $response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/google', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-dev-key' => $devKey['secret'] + ], [ + 'success' => 'https://example.com', + 'failure' => 'https://example.com' + ]); + $this->assertEquals(412, $response['headers']['status-code']); + $this->assertEquals('This provider is disabled. Please enable the provider from your Appwrite console to continue.', $response['body']['message']); + + /** Test hostname in Magic URL */ + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/magic-url', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], [ + 'userId' => ID::unique(), + 'email' => 'user@appwrite.io', + 'url' => 'https://example.com', + ]); + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertEquals('Invalid `url` param: URL host must be one of: localhost, appwrite.io, *.appwrite.io', $response['body']['message']); + + /** Test hostname in Magic URL with devKey */ + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/magic-url', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-dev-key' => $devKey['secret'] + ], [ + 'userId' => ID::unique(), + 'email' => 'user@appwrite.io', + 'url' => 'https://example.com', + ]); + $this->assertEquals(201, $response['headers']['status-code']); + } + + /** + * @group devKeys + */ + public function testCorsWithDevKey(): void + { + /** + * Test for SUCCESS + */ + $projectId = $this->setupProject([ + 'projectId' => ID::unique(), + 'name' => 'testCorsWithDevKey', + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $devKey = $this->setupDevKey([ + 'projectId' => $projectId, + 'name' => 'Dev Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), 36000) + ]); + + $origin = 'http://example.com'; + + /** + * Test CORS without Dev Key (should fail due to origin) + */ + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'origin' => $origin, + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], [ + 'email' => 'user@appwrite.io', + 'password' => 'password' + ]); + + $this->assertEquals(403, $response['headers']['status-code']); + $this->assertNotEquals($origin, $response['headers']['access-control-allow-origin'] ?? null); + $this->assertEquals('http://localhost', $response['headers']['access-control-allow-origin'] ?? null); + + + /** + * Test CORS with Dev Key (should bypass origin check) + */ + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'origin' => $origin, + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-dev-key' => $devKey['secret'] + ], [ + 'email' => 'user@appwrite.io', + 'password' => 'password' + ]); + + $this->assertEquals(401, $response['headers']['status-code']); + $this->assertEquals('*', $response['headers']['access-control-allow-origin'] ?? null); + } + + /** + * @group devKeys + */ + public function testNoRateLimitWithDevKey(): void + { + /** + * Test for SUCCESS + */ + $projectId = $this->setupProject([ + 'projectId' => ID::unique(), + 'name' => 'testNoRateLimitWithDevKey', + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $devKey = $this->setupDevKey([ + 'projectId' => $projectId, + 'name' => 'Dev Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), 36000) + ]); + + /** + * Test for SUCCESS + */ + for ($i = 0; $i < 10; $i++) { + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], [ + 'email' => 'user@appwrite.io', + 'password' => 'password' + ]); + $this->assertEquals(401, $response['headers']['status-code']); + } + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], [ + 'email' => 'user@appwrite.io', + 'password' => 'password' + ]); + $this->assertEquals(429, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-dev-key' => $devKey['secret'] + ], [ + 'email' => 'user@appwrite.io', + 'password' => 'password' + ]); + $this->assertEquals(401, $response['headers']['status-code']); + + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $projectId . '/dev-keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), -3600), + ]); + + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-dev-key' => $response['body']['secret'] + ], [ + 'email' => 'user@appwrite.io', + 'password' => 'password' + ]); + $this->assertEquals(429, $response['headers']['status-code']); + + /** + * Test for FAILURE after expire + */ + $devKey = $this->setupDevKey([ + 'projectId' => $projectId, + 'name' => 'Dev Key Test Expire 5 seconds', + 'expire' => DateTime::addSeconds(new \DateTime(), 5) + ]); + + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-dev-key' => $devKey['secret'] + ], [ + 'email' => 'user@appwrite.io', + 'password' => 'password' + ]); + $this->assertEquals(401, $response['headers']['status-code']); + + sleep(5); + + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-dev-key' => $devKey['secret'] + ], [ + 'email' => 'user@appwrite.io', + 'password' => 'password' + ]); + $this->assertEquals(429, $response['headers']['status-code']); + } + + /** + * @group devKeys + */ + public function testUpdateProjectDevKey(): void + { + $projectId = $this->setupProject([ + 'projectId' => ID::unique(), + 'name' => 'testUpdateProjectDevKey', + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $devKey = $this->setupDevKey([ + 'projectId' => $projectId, + 'name' => 'Dev Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), 36000) + ]); + + $response = $this->client->call(Client::METHOD_PUT, '/projects/' . $projectId . '/dev-keys/' . $devKey['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Key Test Update', + 'expire' => DateTime::addSeconds(new \DateTime(), 360), + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals($devKey['$id'], $response['body']['$id']); + $this->assertEquals('Key Test Update', $response['body']['name']); + $this->assertArrayHasKey('accessedAt', $response['body']); + $this->assertEmpty($response['body']['accessedAt']); + + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/dev-keys/' . $devKey['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals($devKey['$id'], $response['body']['$id']); + $this->assertEquals('Key Test Update', $response['body']['name']); + $this->assertArrayHasKey('accessedAt', $response['body']); + $this->assertEmpty($response['body']['accessedAt']); + } + + /** + * @group devKeys + */ + public function testDeleteProjectDevKey(): void + { + $projectId = $this->setupProject([ + 'projectId' => ID::unique(), + 'name' => 'testDeleteProjectDevKey', + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + $devKey = $this->setupDevKey([ + 'projectId' => $projectId, + 'name' => 'Dev Key Test', + 'expire' => DateTime::addSeconds(new \DateTime(), 36000) + ]); + + $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $projectId . '/dev-keys/' . $devKey['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + + $this->assertEquals(204, $response['headers']['status-code']); + $this->assertEmpty($response['body']); + + /** + * Get rate limit trying to use the deleted key + */ + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-dev-key' => $devKey['secret'] + ], [ + 'email' => 'user@appwrite.io', + 'password' => 'password' + ]); + $this->assertEquals(429, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId . '/dev-keys/' . $devKey['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + + $this->assertEquals(404, $response['headers']['status-code']); + + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $projectId . '/dev-keys/error', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + + $this->assertEquals(404, $response['headers']['status-code']); + } + + /** + * Devkeys Tests ends here ------------------------------------------------ + */ } diff --git a/tests/e2e/Services/Projects/ProjectsDevKeys.php b/tests/e2e/Services/Projects/ProjectsDevKeys.php deleted file mode 100644 index 57c832750e..0000000000 --- a/tests/e2e/Services/Projects/ProjectsDevKeys.php +++ /dev/null @@ -1,529 +0,0 @@ -client->call(Client::METHOD_POST, '/projects/' . $id . '/dev-keys', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'name' => 'Key Test', - 'expire' => DateTime::addSeconds(new \DateTime(), 36000) - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - $this->assertNotEmpty($response['body']['$id']); - $this->assertEquals('Key Test', $response['body']['name']); - $this->assertNotEmpty($response['body']['secret']); - $this->assertArrayHasKey('accessedAt', $response['body']); - $this->assertEmpty($response['body']['accessedAt']); - - /** Create a second dev key */ - $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/dev-keys', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'name' => 'Dev Key Test', - 'expire' => DateTime::addSeconds(new \DateTime(), 36000) - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - $this->assertNotEmpty($response['body']['$id']); - $this->assertEquals('Dev Key Test', $response['body']['name']); - $this->assertNotEmpty($response['body']['secret']); - $this->assertArrayHasKey('accessedAt', $response['body']); - $this->assertEmpty($response['body']['accessedAt']); - - /** - * Test for FAILURE - */ - - /** TEST expiry date is required */ - $res = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/dev-keys', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'name' => 'Key Test' - ]); - - $this->assertEquals(400, $res['headers']['status-code']); - - $data = array_merge($data, [ - 'keyId' => $response['body']['$id'], - 'secret' => $response['body']['secret'] - ]); - - return $data; - } - - - /** - * @depends testCreateProjectDevKey - * @group devKeys - */ - public function testListProjectDevKey($data): array - { - /** - * Test for SUCCESS - */ - - /** List all dev keys */ - $id = $data['projectId'] ?? ''; - - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/dev-keys', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(2, $response['body']['total']); - - /** List dev keys with limit */ - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/dev-keys', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ - Query::limit(1)->toString() - ] - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(1, $response['body']['total']); - - /** List dev keys with search */ - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/dev-keys', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'search' => 'Dev' - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(1, $response['body']['total']); - $this->assertEquals('Dev Key Test', $response['body']['devKeys'][0]['name']); - - /** List dev keys with querying `expire` */ - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/dev-keys', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [Query::lessThan('expire', (new \DateTime())->format('Y-m-d H:i:s'))->toString()] - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(0, $response['body']['total']); // No dev keys expired - - /** - * Test for FAILURE - */ - - /** Test for search with invalid query */ - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/dev-keys', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ - Query::search('name', 'Invalid')->toString() - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - $this->assertEquals('Invalid `queries` param: Invalid query: Attribute not found in schema: name', $response['body']['message']); - - return $data; - } - - - /** - * @depends testCreateProjectDevKey - * @group devKeys - */ - public function testGetProjectDevKey($data): array - { - /** - * Test for SUCCESS - */ - $id = $data['projectId'] ?? ''; - $keyId = $data['keyId'] ?? ''; - - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/dev-keys/' . $keyId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertNotEmpty($response['body']['$id']); - $this->assertEquals($keyId, $response['body']['$id']); - $this->assertEquals('Dev Key Test', $response['body']['name']); - $this->assertNotEmpty($response['body']['secret']); - $this->assertArrayHasKey('accessedAt', $response['body']); - $this->assertEmpty($response['body']['accessedAt']); - - /** - * Test for FAILURE - */ - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/dev-keys/error', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); - - $this->assertEquals(404, $response['headers']['status-code']); - - return $data; - } - - /** - * @depends testCreateProjectDevKey - * @group devKeys - */ - public function testGetDevKeyWithSdks($data): array - { - $id = $data['projectId'] ?? ''; - $keyId = $data['keyId'] ?? ''; - $devKey = $data['secret'] ?? ''; - - /** Use dev key with python sdk */ - $res = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $id, - 'x-appwrite-dev-key' => $devKey, - 'x-sdk-name' => 'python' - ], [ - 'email' => 'user@appwrite.io', - 'password' => 'password' - ]); - $this->assertEquals(401, $res['headers']['status-code']); - - /** Use dev key with php sdk */ - $res = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $id, - 'x-appwrite-dev-key' => $devKey, - 'x-sdk-name' => 'php' - ], [ - 'email' => 'user@appwrite.io', - 'password' => 'password' - ]); - $this->assertEquals(401, $res['headers']['status-code']); - - /** Get the dev key */ - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/dev-keys/' . $keyId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertArrayHasKey('sdks', $response['body']); - $this->assertCount(2, $response['body']['sdks']); - $this->assertContains('python', $response['body']['sdks']); - $this->assertContains('php', $response['body']['sdks']); - - return $data; - } - - /** - * @depends testCreateProjectDevKey - * @group devKeys - */ - public function testNoHostValidationWithDevKey($data): void - { - $id = $data['projectId'] ?? ''; - $devKey = $data['secret'] ?? ''; - - /** Test oauth2 and get invalid `success` URL */ - $response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/google', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $id - ], [ - 'success' => 'https://example.com', - 'failure' => 'https://example.com' - ]); - $this->assertEquals(400, $response['headers']['status-code']); - $this->assertEquals('Invalid `success` param: URL host must be one of: localhost, appwrite.io, *.appwrite.io', $response['body']['message']); - - /** Test oauth2 with devKey and now get oauth2 is disabled */ - $response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/google', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $id, - 'x-appwrite-dev-key' => $devKey - ], [ - 'success' => 'https://example.com', - 'failure' => 'https://example.com' - ]); - $this->assertEquals(412, $response['headers']['status-code']); - $this->assertEquals('This provider is disabled. Please enable the provider from your Appwrite console to continue.', $response['body']['message']); - - /** Test hostname in Magic URL */ - $response = $this->client->call(Client::METHOD_POST, '/account/sessions/magic-url', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $id, - ], [ - 'userId' => ID::unique(), - 'email' => 'user@appwrite.io', - 'url' => 'https://example.com', - ]); - $this->assertEquals(400, $response['headers']['status-code']); - $this->assertEquals('Invalid `url` param: URL host must be one of: localhost, appwrite.io, *.appwrite.io', $response['body']['message']); - - /** Test hostname in Magic URL with devKey */ - $response = $this->client->call(Client::METHOD_POST, '/account/sessions/magic-url', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $id, - 'x-appwrite-dev-key' => $devKey - ], [ - 'userId' => ID::unique(), - 'email' => 'user@appwrite.io', - 'url' => 'https://example.com', - ]); - $this->assertEquals(201, $response['headers']['status-code']); - } - - /** - * @depends testCreateProjectDevKey - * @group devKeys - */ - public function testCorsWithDevKey($data): void - { - $projectId = $data['projectId'] ?? ''; - $devKey = $data['secret'] ?? ''; - $origin = 'http://example.com'; - - /** - * Test CORS without Dev Key (should fail due to origin) - */ - $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ - 'origin' => $origin, - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - ], [ - 'email' => 'user@appwrite.io', - 'password' => 'password' - ]); - - $this->assertEquals(403, $response['headers']['status-code']); - $this->assertNotEquals($origin, $response['headers']['access-control-allow-origin'] ?? null); - $this->assertEquals('http://localhost', $response['headers']['access-control-allow-origin'] ?? null); - - - /** - * Test CORS with Dev Key (should bypass origin check) - */ - $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ - 'origin' => $origin, - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-dev-key' => $devKey - ], [ - 'email' => 'user@appwrite.io', - 'password' => 'password' - ]); - - $this->assertEquals(401, $response['headers']['status-code']); - $this->assertEquals('*', $response['headers']['access-control-allow-origin'] ?? null); - } - - /** - * @depends testCreateProjectDevKey - * @group devKeys - */ - public function testNoRateLimitWithDevKey($data): void - { - $id = $data['projectId'] ?? ''; - $devKey = $data['secret'] ?? ''; - - /** - * Test for SUCCESS - */ - for ($i = 0; $i < 10; $i++) { - $res = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $id, - ], [ - 'email' => 'user@appwrite.io', - 'password' => 'password' - ]); - $this->assertEquals(401, $res['headers']['status-code']); - } - $res = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $id, - ], [ - 'email' => 'user@appwrite.io', - 'password' => 'password' - ]); - $this->assertEquals(429, $res['headers']['status-code']); - - $res = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $id, - 'x-appwrite-dev-key' => $devKey - ], [ - 'email' => 'user@appwrite.io', - 'password' => 'password' - ]); - $this->assertEquals(401, $res['headers']['status-code']); - - /** - * Test for FAILURE - */ - $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/dev-keys', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'name' => 'Key Test', - 'expire' => DateTime::addSeconds(new \DateTime(), -3600), - ]); - - $res = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $id, - 'x-appwrite-dev-key' => $response['body']['secret'] - ], [ - 'email' => 'user@appwrite.io', - 'password' => 'password' - ]); - $this->assertEquals(429, $res['headers']['status-code']); - - - /** - * Test for FAILURE after expire - */ - $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/dev-keys', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'name' => 'Key Test', - 'expire' => DateTime::addSeconds(new \DateTime(), 5), - ]); - - $res = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $id, - 'x-appwrite-dev-key' => $response['body']['secret'] - ], [ - 'email' => 'user@appwrite.io', - 'password' => 'password' - ]); - $this->assertEquals(401, $res['headers']['status-code']); - - sleep(5); - - $res = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $id, - 'x-appwrite-dev-key' => $response['body']['secret'] - ], [ - 'email' => 'user@appwrite.io', - 'password' => 'password' - ]); - $this->assertEquals(429, $res['headers']['status-code']); - } - - /** - * @depends testCreateProjectDevKey - * @group devKeys - */ - public function testUpdateProjectDevKey($data): array - { - $id = $data['projectId'] ?? ''; - $keyId = $data['keyId'] ?? ''; - - $response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/dev-keys/' . $keyId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'name' => 'Key Test Update', - 'expire' => DateTime::addSeconds(new \DateTime(), 360), - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertNotEmpty($response['body']['$id']); - $this->assertEquals($keyId, $response['body']['$id']); - $this->assertEquals('Key Test Update', $response['body']['name']); - $this->assertArrayHasKey('accessedAt', $response['body']); - $this->assertNotEmpty($response['body']['accessedAt']); - - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/dev-keys/' . $keyId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertNotEmpty($response['body']['$id']); - $this->assertEquals($keyId, $response['body']['$id']); - $this->assertEquals('Key Test Update', $response['body']['name']); - $this->assertArrayHasKey('accessedAt', $response['body']); - $this->assertNotEmpty($response['body']['accessedAt']); - - return $data; - } - - /** - * @depends testCreateProjectDevKey - * @group devKeys - */ - public function testDeleteProjectDevKey($data): array - { - $id = $data['projectId'] ?? ''; - $keyId = $data['keyId'] ?? ''; - - $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/dev-keys/' . $keyId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); - - $this->assertEquals(204, $response['headers']['status-code']); - $this->assertEmpty($response['body']); - - /** - * Get rate limit trying to use the deleted key - */ - $res = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $id, - 'x-appwrite-dev-key' => $data['secret'] - ], [ - 'email' => 'user@appwrite.io', - 'password' => 'password' - ]); - $this->assertEquals(429, $res['headers']['status-code']); - - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/dev-keys/' . $keyId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); - - $this->assertEquals(404, $response['headers']['status-code']); - - - /** - * Test for FAILURE - */ - $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/dev-keys/error', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); - - $this->assertEquals(404, $response['headers']['status-code']); - - return $data; - } -}