mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Add getPolicy + tests + move wrongly placed project tests
This commit is contained in:
@@ -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());
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user