Add getPolicy + tests + move wrongly placed project tests

This commit is contained in:
Matej Bačo
2026-04-23 15:19:49 +02:00
parent 9c6ed9565e
commit a48fd13ced
7 changed files with 322 additions and 77 deletions
@@ -0,0 +1,151 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\Policies;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Document;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\WhiteList;
class Get extends Action
{
use HTTP;
public static function getName()
{
return 'getProjectPolicy';
}
public function __construct()
{
$this
->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: <<<EOT
Get a policy by its unique ID. This endpoint returns the current configuration for the requested project policy.
EOT,
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: [
Response::MODEL_POLICY_PASSWORD_DICTIONARY,
Response::MODEL_POLICY_PASSWORD_HISTORY,
Response::MODEL_POLICY_PASSWORD_PERSONAL_DATA,
Response::MODEL_POLICY_SESSION_ALERT,
Response::MODEL_POLICY_SESSION_DURATION,
Response::MODEL_POLICY_SESSION_INVALIDATION,
Response::MODEL_POLICY_SESSION_LIMIT,
Response::MODEL_POLICY_USER_LIMIT,
Response::MODEL_POLICY_MEMBERSHIP_PRIVACY,
],
)
]
))
->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);
}
}
@@ -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());
+115
View File
@@ -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), [
@@ -0,0 +1,7 @@
<?php
namespace Tests\E2E\Services\Project;
trait ProjectBase
{
}
@@ -0,0 +1,33 @@
<?php
namespace Tests\E2E\Services\Project;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideConsole;
class ProjectConsoleClientTest extends Scope
{
use ProjectBase;
use ProjectCustom;
use SideConsole;
public function testDeleteProject(): void
{
// TODO:
// 1. Create new team
// 2. Create new project
// 3. Delete project
// 4. Verify project is deleted
}
public function testDeleteProjectUsingKey(): void
{
// TODO:
// 1. Create new team
// 2. Create new project
// 3. Create new API key
// 4. Delete project using API key
// 5. Verify project is deleted
}
}
@@ -0,0 +1,14 @@
<?php
namespace Tests\E2E\Services\Project;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideServer;
class ProjectCustomServerTest extends Scope
{
use ProjectBase;
use ProjectCustom;
use SideServer;
}
@@ -18,83 +18,6 @@ trait ProjectsBase
private static array $cachedProjectWithAuthLimit = [];
private static array $cachedProjectWithServicesDisabled = [];
protected function createProjectForDeleteTest(): array
{
$rootHeaders = [
'origin' => '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
*/