From a48fd13ced5c20a857d30f102a1f7c1cebd7cfc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 23 Apr 2026 15:19:49 +0200 Subject: [PATCH] Add getPolicy + tests + move wrongly placed project tests --- .../Project/Http/Project/Policies/Get.php | 151 ++++++++++++++++++ .../Modules/Project/Services/Http.php | 2 + tests/e2e/Services/Project/PoliciesBase.php | 115 +++++++++++++ tests/e2e/Services/Project/ProjectBase.php | 7 + .../Project/ProjectConsoleClientTest.php | 33 ++++ .../Project/ProjectCustomServerTest.php | 14 ++ tests/e2e/Services/Projects/ProjectsBase.php | 77 --------- 7 files changed, 322 insertions(+), 77 deletions(-) create mode 100644 src/Appwrite/Platform/Modules/Project/Http/Project/Policies/Get.php create mode 100644 tests/e2e/Services/Project/ProjectBase.php create mode 100644 tests/e2e/Services/Project/ProjectConsoleClientTest.php create mode 100644 tests/e2e/Services/Project/ProjectCustomServerTest.php diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/Get.php b/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/Get.php new file mode 100644 index 0000000000..3d633cd2e4 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/Get.php @@ -0,0 +1,151 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_GET) + ->setHttpPath('/v1/project/policies/:policyId') + ->desc('Get project policy') + ->groups(['api', 'project']) + ->label('scope', 'policies.read') + ->label('sdk', new Method( + namespace: 'project', + group: 'policies', + name: 'getPolicy', + description: <<param('policyId', '', new WhiteList([ + 'password-dictionary', + 'password-history', + 'password-personal-data', + 'session-alert', + 'session-duration', + 'session-invalidation', + 'session-limit', + 'user-limit', + 'membership-privacy', + ], true), 'Policy ID. Can be one of: password-dictionary, password-history, password-personal-data, session-alert, session-duration, session-invalidation, session-limit, user-limit, membership-privacy.') + ->inject('response') + ->inject('project') + ->callback($this->action(...)); + } + + public function action( + string $policyId, + Response $response, + Document $project, + ): void { + $auths = $project->getAttribute('auths', []); + + [$policy, $model] = match ($policyId) { + 'password-dictionary' => [ + new Document([ + '$id' => 'password-dictionary', + 'enabled' => $auths['passwordDictionary'] ?? false, + ]), + Response::MODEL_POLICY_PASSWORD_DICTIONARY, + ], + 'password-history' => [ + new Document([ + '$id' => 'password-history', + 'total' => $auths['passwordHistory'] ?? 0, + ]), + Response::MODEL_POLICY_PASSWORD_HISTORY, + ], + 'password-personal-data' => [ + new Document([ + '$id' => 'password-personal-data', + 'enabled' => $auths['personalDataCheck'] ?? false, + ]), + Response::MODEL_POLICY_PASSWORD_PERSONAL_DATA, + ], + 'session-alert' => [ + new Document([ + '$id' => 'session-alert', + 'enabled' => $auths['sessionAlerts'] ?? false, + ]), + Response::MODEL_POLICY_SESSION_ALERT, + ], + 'session-duration' => [ + new Document([ + '$id' => 'session-duration', + 'duration' => $auths['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG, + ]), + Response::MODEL_POLICY_SESSION_DURATION, + ], + 'session-invalidation' => [ + new Document([ + '$id' => 'session-invalidation', + 'enabled' => $auths['invalidateSessions'] ?? true, + ]), + Response::MODEL_POLICY_SESSION_INVALIDATION, + ], + 'session-limit' => [ + new Document([ + '$id' => 'session-limit', + 'total' => $auths['maxSessions'] ?? 0, + ]), + Response::MODEL_POLICY_SESSION_LIMIT, + ], + 'user-limit' => [ + new Document([ + '$id' => 'user-limit', + 'total' => $auths['limit'] ?? 0, + ]), + Response::MODEL_POLICY_USER_LIMIT, + ], + 'membership-privacy' => [ + new Document([ + '$id' => 'membership-privacy', + 'userId' => $auths['membershipsUserId'] ?? false, + 'userEmail' => $auths['membershipsUserEmail'] ?? false, + 'userPhone' => $auths['membershipsUserPhone'] ?? false, + 'userName' => $auths['membershipsUserName'] ?? false, + 'userMFA' => $auths['membershipsMfa'] ?? false, + ]), + Response::MODEL_POLICY_MEMBERSHIP_PRIVACY, + ], + }; + + $response->dynamic($policy, $model); + } +} diff --git a/src/Appwrite/Platform/Modules/Project/Services/Http.php b/src/Appwrite/Platform/Modules/Project/Services/Http.php index 703359ea4e..64dad109f8 100644 --- a/src/Appwrite/Platform/Modules/Project/Services/Http.php +++ b/src/Appwrite/Platform/Modules/Project/Services/Http.php @@ -29,6 +29,7 @@ use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Web\Update as Updat use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Windows\Create as CreateWindowsPlatform; use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Windows\Update as UpdateWindowsPlatform; use Appwrite\Platform\Modules\Project\Http\Project\Platforms\XList as ListPlatforms; +use Appwrite\Platform\Modules\Project\Http\Project\Policies\Get as GetPolicy; use Appwrite\Platform\Modules\Project\Http\Project\Policies\MembershipPrivacy\Update as UpdateMembershipPrivacyPolicy; use Appwrite\Platform\Modules\Project\Http\Project\Policies\PasswordDictionary\Update as UpdatePasswordDictionaryPolicy; use Appwrite\Platform\Modules\Project\Http\Project\Policies\PasswordHistory\Update as UpdatePasswordHistoryPolicy; @@ -115,6 +116,7 @@ class Http extends Service // Policies $this->addAction(ListPolicies::getName(), new ListPolicies()); + $this->addAction(GetPolicy::getName(), new GetPolicy()); $this->addAction(UpdateMembershipPrivacyPolicy::getName(), new UpdateMembershipPrivacyPolicy()); $this->addAction(UpdatePasswordDictionaryPolicy::getName(), new UpdatePasswordDictionaryPolicy()); $this->addAction(UpdatePasswordHistoryPolicy::getName(), new UpdatePasswordHistoryPolicy()); diff --git a/tests/e2e/Services/Project/PoliciesBase.php b/tests/e2e/Services/Project/PoliciesBase.php index 7d532c98c1..04906c6c2b 100644 --- a/tests/e2e/Services/Project/PoliciesBase.php +++ b/tests/e2e/Services/Project/PoliciesBase.php @@ -7,6 +7,116 @@ use Utopia\Database\Query; trait PoliciesBase { + // ========================================================================= + // Get Policy + // ========================================================================= + + public function testGetPolicy(): void + { + $expectedFields = [ + 'password-dictionary' => ['enabled'], + 'password-history' => ['total'], + 'password-personal-data' => ['enabled'], + 'session-alert' => ['enabled'], + 'session-duration' => ['duration'], + 'session-invalidation' => ['enabled'], + 'session-limit' => ['total'], + 'user-limit' => ['total'], + 'membership-privacy' => ['userId', 'userEmail', 'userPhone', 'userName', 'userMFA'], + ]; + + foreach ($expectedFields as $policyId => $fields) { + $response = $this->getPolicy($policyId); + + $this->assertSame(200, $response['headers']['status-code']); + $this->assertSame($policyId, $response['body']['$id']); + + foreach ($fields as $field) { + $this->assertArrayHasKey($field, $response['body']); + } + } + } + + public function testGetPolicyMatchesListPolicies(): void + { + $list = $this->listPolicies(); + + $this->assertSame(200, $list['headers']['status-code']); + + $byId = []; + foreach ($list['body']['policies'] as $policy) { + $byId[$policy['$id']] = $policy; + } + + foreach (\array_keys($byId) as $policyId) { + $response = $this->getPolicy($policyId); + + $this->assertSame(200, $response['headers']['status-code']); + $this->assertSame($byId[$policyId], $response['body']); + } + } + + public function testGetPolicyReflectsUpdates(): void + { + $this->updatePasswordDictionaryPolicy(true); + $this->updatePasswordHistoryPolicy(5); + $this->updateSessionDurationPolicy(3600); + $this->updateMembershipPrivacyPolicy([ + 'userId' => true, + 'userEmail' => true, + 'userPhone' => false, + 'userName' => true, + 'userMFA' => true, + ]); + + $passwordDictionary = $this->getPolicy('password-dictionary'); + $passwordHistory = $this->getPolicy('password-history'); + $sessionDuration = $this->getPolicy('session-duration'); + $membershipPrivacy = $this->getPolicy('membership-privacy'); + + $this->assertSame(200, $passwordDictionary['headers']['status-code']); + $this->assertSame(true, $passwordDictionary['body']['enabled']); + + $this->assertSame(200, $passwordHistory['headers']['status-code']); + $this->assertSame(5, $passwordHistory['body']['total']); + + $this->assertSame(200, $sessionDuration['headers']['status-code']); + $this->assertSame(3600, $sessionDuration['body']['duration']); + + $this->assertSame(200, $membershipPrivacy['headers']['status-code']); + $this->assertSame(true, $membershipPrivacy['body']['userId']); + $this->assertSame(true, $membershipPrivacy['body']['userEmail']); + $this->assertSame(false, $membershipPrivacy['body']['userPhone']); + $this->assertSame(true, $membershipPrivacy['body']['userName']); + $this->assertSame(true, $membershipPrivacy['body']['userMFA']); + + // Cleanup + $this->updatePasswordDictionaryPolicy(false); + $this->updatePasswordHistoryPolicy(null); + $this->updateSessionDurationPolicy(31536000); + $this->updateMembershipPrivacyPolicy([ + 'userId' => false, + 'userEmail' => false, + 'userPhone' => false, + 'userName' => false, + 'userMFA' => false, + ]); + } + + public function testGetPolicyWithoutAuthentication(): void + { + $response = $this->getPolicy('password-dictionary', authenticated: false); + + $this->assertSame(401, $response['headers']['status-code']); + } + + public function testGetPolicyInvalidPolicyId(): void + { + $response = $this->getPolicy('invalid-policy'); + + $this->assertSame(400, $response['headers']['status-code']); + } + // ========================================================================= // List Policies // ========================================================================= @@ -1002,6 +1112,11 @@ trait PoliciesBase return $this->client->call(Client::METHOD_GET, '/project/policies', $this->buildHeaders($authenticated), $params); } + protected function getPolicy(string $policyId, bool $authenticated = true): mixed + { + return $this->client->call(Client::METHOD_GET, '/project/policies/' . $policyId, $this->buildHeaders($authenticated)); + } + protected function updatePasswordDictionaryPolicy(bool $enabled, bool $authenticated = true): mixed { return $this->client->call(Client::METHOD_PATCH, '/project/policies/password-dictionary', $this->buildHeaders($authenticated), [ diff --git a/tests/e2e/Services/Project/ProjectBase.php b/tests/e2e/Services/Project/ProjectBase.php new file mode 100644 index 0000000000..3caec392a5 --- /dev/null +++ b/tests/e2e/Services/Project/ProjectBase.php @@ -0,0 +1,7 @@ + 'http://localhost', - 'content-type' => 'application/json', - 'cookie' => 'a_session_console=' . $this->getRoot()['session'], - 'x-appwrite-project' => 'console', - ]; - - $team = $this->client->call(Client::METHOD_POST, '/teams', $rootHeaders, [ - 'teamId' => ID::unique(), - 'name' => 'Delete Project Team', - ]); - - $this->assertSame(201, $team['headers']['status-code']); - - $project = $this->client->call(Client::METHOD_POST, '/projects', $rootHeaders, [ - 'projectId' => ID::unique(), - 'name' => 'Delete Project Test', - 'teamId' => $team['body']['$id'], - 'region' => System::getEnv('_APP_REGION', 'default'), - ]); - - $this->assertSame(201, $project['headers']['status-code']); - - $key = $this->client->call(Client::METHOD_POST, '/projects/' . $project['body']['$id'] . '/keys', $rootHeaders, [ - 'keyId' => ID::unique(), - 'name' => 'Delete Project Key', - 'scopes' => [ - 'project.read', - 'project.write', - ], - ]); - - $this->assertSame(201, $key['headers']['status-code']); - - return [ - 'projectId' => $project['body']['$id'], - 'apiKey' => $key['body']['secret'], - ]; - } - - #[Group('projectsCRUD')] - public function testDeleteProject(): void - { - $project = $this->createProjectForDeleteTest(); - - $headers = match ($this->getSide()) { - 'server' => [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project['projectId'], - 'x-appwrite-key' => $project['apiKey'], - 'x-appwrite-mode' => 'admin', - ], - default => [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'cookie' => 'a_session_console=' . $this->getRoot()['session'], - 'x-appwrite-project' => $project['projectId'], - 'x-appwrite-mode' => 'admin', - ], - }; - - $response = $this->client->call(Client::METHOD_DELETE, '/project', $headers); - - $this->assertSame(204, $response['headers']['status-code']); - - $get = $this->client->call(Client::METHOD_GET, '/projects/' . $project['projectId'], [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'cookie' => 'a_session_console=' . $this->getRoot()['session'], - 'x-appwrite-project' => 'console', - ]); - - $this->assertSame(404, $get['headers']['status-code']); - } - /** * Setup and cache a basic project with team */