From cef7a5197f543ca60d93ecb552fab30791504d81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 23 Apr 2026 13:24:39 +0200 Subject: [PATCH] List policies API --- app/init/models.php | 20 +++ .../Project/Http/Project/Policies/XList.php | 132 +++++++++++++++ src/Appwrite/Utopia/Response.php | 10 ++ .../Utopia/Response/Model/PolicyBase.php | 19 +++ .../Utopia/Response/Model/PolicyList.php | 46 +++++ .../Model/PolicyMembershipPrivacy.php | 59 +++++++ .../Model/PolicyPasswordDictionary.php | 34 ++++ .../Response/Model/PolicyPasswordHistory.php | 34 ++++ .../Model/PolicyPasswordPersonalData.php | 34 ++++ .../Response/Model/PolicySessionAlert.php | 34 ++++ .../Response/Model/PolicySessionDuration.php | 34 ++++ .../Model/PolicySessionInvalidation.php | 34 ++++ .../Response/Model/PolicySessionLimit.php | 34 ++++ .../Utopia/Response/Model/PolicyUserLimit.php | 34 ++++ tests/e2e/Services/Project/PoliciesBase.php | 160 ++++++++++++++++++ 15 files changed, 718 insertions(+) create mode 100644 src/Appwrite/Platform/Modules/Project/Http/Project/Policies/XList.php create mode 100644 src/Appwrite/Utopia/Response/Model/PolicyBase.php create mode 100644 src/Appwrite/Utopia/Response/Model/PolicyList.php create mode 100644 src/Appwrite/Utopia/Response/Model/PolicyMembershipPrivacy.php create mode 100644 src/Appwrite/Utopia/Response/Model/PolicyPasswordDictionary.php create mode 100644 src/Appwrite/Utopia/Response/Model/PolicyPasswordHistory.php create mode 100644 src/Appwrite/Utopia/Response/Model/PolicyPasswordPersonalData.php create mode 100644 src/Appwrite/Utopia/Response/Model/PolicySessionAlert.php create mode 100644 src/Appwrite/Utopia/Response/Model/PolicySessionDuration.php create mode 100644 src/Appwrite/Utopia/Response/Model/PolicySessionInvalidation.php create mode 100644 src/Appwrite/Utopia/Response/Model/PolicySessionLimit.php create mode 100644 src/Appwrite/Utopia/Response/Model/PolicyUserLimit.php diff --git a/app/init/models.php b/app/init/models.php index 8f569d3252..b713d61cd2 100644 --- a/app/init/models.php +++ b/app/init/models.php @@ -112,6 +112,16 @@ use Appwrite\Utopia\Response\Model\PlatformLinux; use Appwrite\Utopia\Response\Model\PlatformList; use Appwrite\Utopia\Response\Model\PlatformWeb; use Appwrite\Utopia\Response\Model\PlatformWindows; +use Appwrite\Utopia\Response\Model\PolicyList; +use Appwrite\Utopia\Response\Model\PolicyMembershipPrivacy; +use Appwrite\Utopia\Response\Model\PolicyPasswordDictionary; +use Appwrite\Utopia\Response\Model\PolicyPasswordHistory; +use Appwrite\Utopia\Response\Model\PolicyPasswordPersonalData; +use Appwrite\Utopia\Response\Model\PolicySessionAlert; +use Appwrite\Utopia\Response\Model\PolicySessionDuration; +use Appwrite\Utopia\Response\Model\PolicySessionInvalidation; +use Appwrite\Utopia\Response\Model\PolicySessionLimit; +use Appwrite\Utopia\Response\Model\PolicyUserLimit; use Appwrite\Utopia\Response\Model\Preferences; use Appwrite\Utopia\Response\Model\Project; use Appwrite\Utopia\Response\Model\Provider; @@ -211,6 +221,7 @@ Response::setModel(new BaseList('Phones List', Response::MODEL_PHONE_LIST, 'phon Response::setModel(new BaseList('Metric List', Response::MODEL_METRIC_LIST, 'metrics', Response::MODEL_METRIC, true, false)); Response::setModel(new BaseList('Variables List', Response::MODEL_VARIABLE_LIST, 'variables', Response::MODEL_VARIABLE)); Response::setModel(new BaseList('Mock Numbers List', Response::MODEL_MOCK_NUMBER_LIST, 'mockNumbers', Response::MODEL_MOCK_NUMBER)); +Response::setModel(new PolicyList()); Response::setModel(new BaseList('Email Templates List', Response::MODEL_EMAIL_TEMPLATE_LIST, 'templates', Response::MODEL_EMAIL_TEMPLATE)); Response::setModel(new BaseList('Status List', Response::MODEL_HEALTH_STATUS_LIST, 'statuses', Response::MODEL_HEALTH_STATUS)); Response::setModel(new BaseList('Rule List', Response::MODEL_PROXY_RULE_LIST, 'rules', Response::MODEL_PROXY_RULE)); @@ -339,6 +350,15 @@ Response::setModel(new Webhook()); Response::setModel(new Key()); Response::setModel(new DevKey()); Response::setModel(new MockNumber()); +Response::setModel(new PolicyPasswordDictionary()); +Response::setModel(new PolicyPasswordHistory()); +Response::setModel(new PolicyPasswordPersonalData()); +Response::setModel(new PolicySessionAlert()); +Response::setModel(new PolicySessionDuration()); +Response::setModel(new PolicySessionInvalidation()); +Response::setModel(new PolicySessionLimit()); +Response::setModel(new PolicyUserLimit()); +Response::setModel(new PolicyMembershipPrivacy()); Response::setModel(new AuthProvider()); Response::setModel(new PlatformWeb()); Response::setModel(new PlatformApple()); diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/XList.php b/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/XList.php new file mode 100644 index 0000000000..893b28fef2 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/XList.php @@ -0,0 +1,132 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_GET) + ->setHttpPath('/v1/project/policies') + ->desc('List project policies') + ->groups(['api', 'project']) + ->label('scope', 'policies.read') + ->label('sdk', new Method( + namespace: 'project', + group: 'policies', + name: 'listPolicies', + description: <<param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) + ->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true) + ->inject('response') + ->inject('project') + ->callback($this->action(...)); + } + + /** + * @param array $queries + */ + public function action( + array $queries, + bool $includeTotal, + Response $response, + Document $project, + ) { + try { + $queries = Query::parseQueries($queries); + } catch (QueryException $e) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); + } + + $auths = $project->getAttribute('auths', []); + + $policies = [ + new Document([ + '$id' => 'password-dictionary', + 'enabled' => $auths['passwordDictionary'] ?? false, + ]), + new Document([ + '$id' => 'password-history', + 'total' => $auths['passwordHistory'] ?? 0, + ]), + new Document([ + '$id' => 'password-personal-data', + 'enabled' => $auths['personalDataCheck'] ?? false, + ]), + new Document([ + '$id' => 'session-alert', + 'enabled' => $auths['sessionAlerts'] ?? false, + ]), + new Document([ + '$id' => 'session-duration', + 'duration' => $auths['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG, + ]), + new Document([ + '$id' => 'session-invalidation', + 'enabled' => $auths['invalidateSessions'] ?? true, + ]), + new Document([ + '$id' => 'session-limit', + 'total' => $auths['maxSessions'] ?? 0, + ]), + new Document([ + '$id' => 'user-limit', + 'total' => $auths['limit'] ?? 0, + ]), + 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, + ]), + ]; + + $total = $includeTotal ? \count($policies) : 0; + + $grouped = Query::groupByType($queries); + $offset = $grouped['offset'] ?? 0; + $limit = $grouped['limit'] ?? null; + + $policies = \array_slice($policies, $offset, $limit); + + $response->dynamic(new Document([ + 'policies' => $policies, + 'total' => $total, + ]), Response::MODEL_POLICY_LIST); + } +} diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index d72b52e4cb..c4e616ea12 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -255,6 +255,16 @@ class Response extends SwooleResponse public const MODEL_DEV_KEY_LIST = 'devKeyList'; public const MODEL_MOCK_NUMBER = 'mockNumber'; public const MODEL_MOCK_NUMBER_LIST = 'mockNumberList'; + public const MODEL_POLICY_LIST = 'policyList'; + public const MODEL_POLICY_PASSWORD_DICTIONARY = 'policyPasswordDictionary'; + public const MODEL_POLICY_PASSWORD_HISTORY = 'policyPasswordHistory'; + public const MODEL_POLICY_PASSWORD_PERSONAL_DATA = 'policyPasswordPersonalData'; + public const MODEL_POLICY_SESSION_ALERT = 'policySessionAlert'; + public const MODEL_POLICY_SESSION_DURATION = 'policySessionDuration'; + public const MODEL_POLICY_SESSION_INVALIDATION = 'policySessionInvalidation'; + public const MODEL_POLICY_SESSION_LIMIT = 'policySessionLimit'; + public const MODEL_POLICY_USER_LIMIT = 'policyUserLimit'; + public const MODEL_POLICY_MEMBERSHIP_PRIVACY = 'policyMembershipPrivacy'; public const MODEL_AUTH_PROVIDER = 'authProvider'; public const MODEL_AUTH_PROVIDER_LIST = 'authProviderList'; public const MODEL_PLATFORM_APPLE = 'platformApple'; diff --git a/src/Appwrite/Utopia/Response/Model/PolicyBase.php b/src/Appwrite/Utopia/Response/Model/PolicyBase.php new file mode 100644 index 0000000000..04a44d9ffd --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/PolicyBase.php @@ -0,0 +1,19 @@ +addRule('$id', [ + 'type' => self::TYPE_STRING, + 'description' => 'Policy ID.', + 'default' => '', + 'example' => 'password-dictionary', + ]); + } +} diff --git a/src/Appwrite/Utopia/Response/Model/PolicyList.php b/src/Appwrite/Utopia/Response/Model/PolicyList.php new file mode 100644 index 0000000000..09548fedcf --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/PolicyList.php @@ -0,0 +1,46 @@ +addRule('total', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Total number of policies in the given project.', + 'default' => 0, + 'example' => 9, + ]) + ->addRule('policies', [ + 'type' => [ + 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, + ], + 'description' => 'List of policies.', + 'default' => [], + 'array' => true, + ]); + } + + public function getName(): string + { + return 'Policies List'; + } + + public function getType(): string + { + return Response::MODEL_POLICY_LIST; + } +} diff --git a/src/Appwrite/Utopia/Response/Model/PolicyMembershipPrivacy.php b/src/Appwrite/Utopia/Response/Model/PolicyMembershipPrivacy.php new file mode 100644 index 0000000000..fe2851d35b --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/PolicyMembershipPrivacy.php @@ -0,0 +1,59 @@ + 'membership-privacy', + ]; + + public function __construct() + { + parent::__construct(); + + $this + ->addRule('userId', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Whether user ID is visible in memberships.', + 'default' => false, + 'example' => true, + ]) + ->addRule('userEmail', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Whether user email is visible in memberships.', + 'default' => false, + 'example' => true, + ]) + ->addRule('userPhone', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Whether user phone is visible in memberships.', + 'default' => false, + 'example' => true, + ]) + ->addRule('userName', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Whether user name is visible in memberships.', + 'default' => false, + 'example' => true, + ]) + ->addRule('userMFA', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Whether user MFA status is visible in memberships.', + 'default' => false, + 'example' => true, + ]); + } + + public function getName(): string + { + return 'Policy Membership Privacy'; + } + + public function getType(): string + { + return Response::MODEL_POLICY_MEMBERSHIP_PRIVACY; + } +} diff --git a/src/Appwrite/Utopia/Response/Model/PolicyPasswordDictionary.php b/src/Appwrite/Utopia/Response/Model/PolicyPasswordDictionary.php new file mode 100644 index 0000000000..78cd284332 --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/PolicyPasswordDictionary.php @@ -0,0 +1,34 @@ + 'password-dictionary', + ]; + + public function __construct() + { + parent::__construct(); + + $this->addRule('enabled', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Whether password dictionary policy is enabled.', + 'default' => false, + 'example' => true, + ]); + } + + public function getName(): string + { + return 'Policy Password Dictionary'; + } + + public function getType(): string + { + return Response::MODEL_POLICY_PASSWORD_DICTIONARY; + } +} diff --git a/src/Appwrite/Utopia/Response/Model/PolicyPasswordHistory.php b/src/Appwrite/Utopia/Response/Model/PolicyPasswordHistory.php new file mode 100644 index 0000000000..a9b5951db6 --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/PolicyPasswordHistory.php @@ -0,0 +1,34 @@ + 'password-history', + ]; + + public function __construct() + { + parent::__construct(); + + $this->addRule('total', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Password history length. A value of 0 means the policy is disabled.', + 'default' => 0, + 'example' => 5, + ]); + } + + public function getName(): string + { + return 'Policy Password History'; + } + + public function getType(): string + { + return Response::MODEL_POLICY_PASSWORD_HISTORY; + } +} diff --git a/src/Appwrite/Utopia/Response/Model/PolicyPasswordPersonalData.php b/src/Appwrite/Utopia/Response/Model/PolicyPasswordPersonalData.php new file mode 100644 index 0000000000..feffd95f1b --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/PolicyPasswordPersonalData.php @@ -0,0 +1,34 @@ + 'password-personal-data', + ]; + + public function __construct() + { + parent::__construct(); + + $this->addRule('enabled', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Whether password personal data policy is enabled.', + 'default' => false, + 'example' => true, + ]); + } + + public function getName(): string + { + return 'Policy Password Personal Data'; + } + + public function getType(): string + { + return Response::MODEL_POLICY_PASSWORD_PERSONAL_DATA; + } +} diff --git a/src/Appwrite/Utopia/Response/Model/PolicySessionAlert.php b/src/Appwrite/Utopia/Response/Model/PolicySessionAlert.php new file mode 100644 index 0000000000..4f1a66c65c --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/PolicySessionAlert.php @@ -0,0 +1,34 @@ + 'session-alert', + ]; + + public function __construct() + { + parent::__construct(); + + $this->addRule('enabled', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Whether session alert policy is enabled.', + 'default' => false, + 'example' => true, + ]); + } + + public function getName(): string + { + return 'Policy Session Alert'; + } + + public function getType(): string + { + return Response::MODEL_POLICY_SESSION_ALERT; + } +} diff --git a/src/Appwrite/Utopia/Response/Model/PolicySessionDuration.php b/src/Appwrite/Utopia/Response/Model/PolicySessionDuration.php new file mode 100644 index 0000000000..1242802c42 --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/PolicySessionDuration.php @@ -0,0 +1,34 @@ + 'session-duration', + ]; + + public function __construct() + { + parent::__construct(); + + $this->addRule('duration', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Session duration in seconds.', + 'default' => TOKEN_EXPIRATION_LOGIN_LONG, + 'example' => 3600, + ]); + } + + public function getName(): string + { + return 'Policy Session Duration'; + } + + public function getType(): string + { + return Response::MODEL_POLICY_SESSION_DURATION; + } +} diff --git a/src/Appwrite/Utopia/Response/Model/PolicySessionInvalidation.php b/src/Appwrite/Utopia/Response/Model/PolicySessionInvalidation.php new file mode 100644 index 0000000000..12cbe10851 --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/PolicySessionInvalidation.php @@ -0,0 +1,34 @@ + 'session-invalidation', + ]; + + public function __construct() + { + parent::__construct(); + + $this->addRule('enabled', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Whether session invalidation policy is enabled.', + 'default' => true, + 'example' => true, + ]); + } + + public function getName(): string + { + return 'Policy Session Invalidation'; + } + + public function getType(): string + { + return Response::MODEL_POLICY_SESSION_INVALIDATION; + } +} diff --git a/src/Appwrite/Utopia/Response/Model/PolicySessionLimit.php b/src/Appwrite/Utopia/Response/Model/PolicySessionLimit.php new file mode 100644 index 0000000000..2f187ef1f9 --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/PolicySessionLimit.php @@ -0,0 +1,34 @@ + 'session-limit', + ]; + + public function __construct() + { + parent::__construct(); + + $this->addRule('total', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Maximum number of sessions allowed per user. A value of 0 means the policy is disabled.', + 'default' => 0, + 'example' => 10, + ]); + } + + public function getName(): string + { + return 'Policy Session Limit'; + } + + public function getType(): string + { + return Response::MODEL_POLICY_SESSION_LIMIT; + } +} diff --git a/src/Appwrite/Utopia/Response/Model/PolicyUserLimit.php b/src/Appwrite/Utopia/Response/Model/PolicyUserLimit.php new file mode 100644 index 0000000000..0ae80445ea --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/PolicyUserLimit.php @@ -0,0 +1,34 @@ + 'user-limit', + ]; + + public function __construct() + { + parent::__construct(); + + $this->addRule('total', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Maximum number of users allowed in the project. A value of 0 means the policy is disabled.', + 'default' => 0, + 'example' => 100, + ]); + } + + public function getName(): string + { + return 'Policy User Limit'; + } + + public function getType(): string + { + return Response::MODEL_POLICY_USER_LIMIT; + } +} diff --git a/tests/e2e/Services/Project/PoliciesBase.php b/tests/e2e/Services/Project/PoliciesBase.php index 84f5938d3e..7d532c98c1 100644 --- a/tests/e2e/Services/Project/PoliciesBase.php +++ b/tests/e2e/Services/Project/PoliciesBase.php @@ -3,9 +3,154 @@ namespace Tests\E2E\Services\Project; use Tests\E2E\Client; +use Utopia\Database\Query; trait PoliciesBase { + // ========================================================================= + // List Policies + // ========================================================================= + + public function testListPolicies(): void + { + $response = $this->listPolicies(); + + $this->assertSame(200, $response['headers']['status-code']); + $this->assertArrayHasKey('policies', $response['body']); + $this->assertArrayHasKey('total', $response['body']); + $this->assertIsArray($response['body']['policies']); + $this->assertIsInt($response['body']['total']); + $this->assertSame(9, $response['body']['total']); + $this->assertCount(9, $response['body']['policies']); + + $policyIds = \array_column($response['body']['policies'], '$id'); + + $this->assertContains('password-dictionary', $policyIds); + $this->assertContains('password-history', $policyIds); + $this->assertContains('password-personal-data', $policyIds); + $this->assertContains('session-alert', $policyIds); + $this->assertContains('session-duration', $policyIds); + $this->assertContains('session-invalidation', $policyIds); + $this->assertContains('session-limit', $policyIds); + $this->assertContains('user-limit', $policyIds); + $this->assertContains('membership-privacy', $policyIds); + } + + public function testListPoliciesResponseModel(): void + { + $response = $this->listPolicies(); + + $this->assertSame(200, $response['headers']['status-code']); + + foreach ($response['body']['policies'] as $policy) { + $this->assertArrayHasKey('$id', $policy); + } + + $byId = []; + foreach ($response['body']['policies'] as $policy) { + $byId[$policy['$id']] = $policy; + } + + $this->assertArrayHasKey('enabled', $byId['password-dictionary']); + $this->assertArrayHasKey('total', $byId['password-history']); + $this->assertArrayHasKey('enabled', $byId['password-personal-data']); + $this->assertArrayHasKey('enabled', $byId['session-alert']); + $this->assertArrayHasKey('duration', $byId['session-duration']); + $this->assertArrayHasKey('enabled', $byId['session-invalidation']); + $this->assertArrayHasKey('total', $byId['session-limit']); + $this->assertArrayHasKey('total', $byId['user-limit']); + $this->assertArrayHasKey('userId', $byId['membership-privacy']); + $this->assertArrayHasKey('userEmail', $byId['membership-privacy']); + $this->assertArrayHasKey('userPhone', $byId['membership-privacy']); + $this->assertArrayHasKey('userName', $byId['membership-privacy']); + $this->assertArrayHasKey('userMFA', $byId['membership-privacy']); + } + + public function testListPoliciesReflectsUpdates(): void + { + $this->updatePasswordDictionaryPolicy(true); + $this->updatePasswordHistoryPolicy(5); + $this->updateSessionDurationPolicy(3600); + $this->updateMembershipPrivacyPolicy([ + 'userId' => true, + 'userEmail' => true, + 'userPhone' => false, + 'userName' => true, + 'userMFA' => true, + ]); + + $response = $this->listPolicies(); + + $this->assertSame(200, $response['headers']['status-code']); + + $byId = []; + foreach ($response['body']['policies'] as $policy) { + $byId[$policy['$id']] = $policy; + } + + $this->assertSame(true, $byId['password-dictionary']['enabled']); + $this->assertSame(5, $byId['password-history']['total']); + $this->assertSame(3600, $byId['session-duration']['duration']); + $this->assertSame(true, $byId['membership-privacy']['userId']); + $this->assertSame(true, $byId['membership-privacy']['userEmail']); + $this->assertSame(false, $byId['membership-privacy']['userPhone']); + $this->assertSame(true, $byId['membership-privacy']['userName']); + $this->assertSame(true, $byId['membership-privacy']['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 testListPoliciesTotalFalse(): void + { + $response = $this->listPolicies(total: false); + + $this->assertSame(200, $response['headers']['status-code']); + $this->assertSame(0, $response['body']['total']); + $this->assertCount(9, $response['body']['policies']); + } + + public function testListPoliciesWithLimit(): void + { + $response = $this->listPolicies([ + Query::limit(1)->toString(), + ]); + + $this->assertSame(200, $response['headers']['status-code']); + $this->assertCount(1, $response['body']['policies']); + $this->assertSame(9, $response['body']['total']); + } + + public function testListPoliciesWithOffset(): void + { + $listAll = $this->listPolicies(); + $this->assertSame(200, $listAll['headers']['status-code']); + + $listOffset = $this->listPolicies([ + Query::offset(1)->toString(), + ]); + + $this->assertSame(200, $listOffset['headers']['status-code']); + $this->assertCount(\count($listAll['body']['policies']) - 1, $listOffset['body']['policies']); + $this->assertSame($listAll['body']['total'], $listOffset['body']['total']); + } + + public function testListPoliciesWithoutAuthentication(): void + { + $response = $this->listPolicies(authenticated: false); + + $this->assertSame(401, $response['headers']['status-code']); + } + // ========================================================================= // Password Dictionary Policy // ========================================================================= @@ -842,6 +987,21 @@ trait PoliciesBase ]); } + protected function listPolicies(?array $queries = null, ?bool $total = null, bool $authenticated = true): mixed + { + $params = []; + + if ($queries !== null) { + $params['queries'] = $queries; + } + + if ($total !== null) { + $params['total'] = $total; + } + + return $this->client->call(Client::METHOD_GET, '/project/policies', $this->buildHeaders($authenticated), $params); + } + protected function updatePasswordDictionaryPolicy(bool $enabled, bool $authenticated = true): mixed { return $this->client->call(Client::METHOD_PATCH, '/project/policies/password-dictionary', $this->buildHeaders($authenticated), [