From f416e39098c400c61650ae898607f968b8eeaf03 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Wed, 12 Feb 2025 23:58:25 +0530 Subject: [PATCH 01/15] Move create rule to module --- app/controllers/api/proxy.php | 194 ----------------- src/Appwrite/Platform/Appwrite.php | 2 + .../Functions/Http/Functions/Create.php | 68 ------ .../Modules/Proxy/Http/Rules/Create.php | 195 ++++++++++++++++++ .../Platform/Modules/Proxy/Module.php | 14 ++ .../Platform/Modules/Proxy/Services/Http.php | 16 ++ .../Modules/Sites/Http/Sites/Create.php | 81 +------- .../Functions/FunctionsCustomServerTest.php | 54 +++++ tests/e2e/Services/Sites/SitesBase.php | 25 ++- .../Services/Sites/SitesCustomServerTest.php | 33 ++- 10 files changed, 332 insertions(+), 350 deletions(-) create mode 100644 src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php create mode 100644 src/Appwrite/Platform/Modules/Proxy/Module.php create mode 100644 src/Appwrite/Platform/Modules/Proxy/Services/Http.php diff --git a/app/controllers/api/proxy.php b/app/controllers/api/proxy.php index e691077adf..e7aa3acb6c 100644 --- a/app/controllers/api/proxy.php +++ b/app/controllers/api/proxy.php @@ -15,176 +15,13 @@ use Utopia\App; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Query as QueryException; -use Utopia\Database\Helpers\ID; use Utopia\Database\Query; use Utopia\Database\Validator\Query\Cursor; use Utopia\Database\Validator\UID; use Utopia\Domains\Domain; use Utopia\Logger\Log; use Utopia\System\System; -use Utopia\Validator\Domain as ValidatorDomain; use Utopia\Validator\Text; -use Utopia\Validator\WhiteList; - -App::post('/v1/proxy/rules') - ->groups(['api', 'proxy']) - ->desc('Create rule') - ->label('scope', 'rules.write') - ->label('event', 'rules.[ruleId].create') - ->label('audits.event', 'rule.create') - ->label('audits.resource', 'rule/{response.$id}') - ->label('sdk', new Method( - namespace: 'proxy', - name: 'createRule', - description: '/docs/references/proxy/create-rule.md', - auth: [AuthType::ADMIN], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_CREATED, - model: Response::MODEL_PROXY_RULE, - ) - ] - )) - ->param('domain', null, new ValidatorDomain(), 'Domain name.') - ->param('resourceType', null, new WhiteList(['api', 'function', 'site']), 'Action definition for the rule. Possible values are "api", "function" and "site"') - ->param('resourceId', '', new UID(), 'ID of resource for the action type. If resourceType is "api", leave empty. If resourceType is "function", provide ID of the function.', true) - ->inject('response') - ->inject('project') - ->inject('queueForCertificates') - ->inject('queueForEvents') - ->inject('dbForPlatform') - ->inject('dbForProject') - ->action(function (string $domain, string $resourceType, string $resourceId, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject) { - $mainDomain = System::getEnv('_APP_DOMAIN', ''); - if ($domain === $mainDomain) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'You cannot assign your main domain to specific resource. Please use subdomain or a different domain.'); - } - - $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); - $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); - - if ( - ($functionsDomain !== '' && str_ends_with($domain, $functionsDomain)) || - ($sitesDomain !== '' && str_ends_with($domain, $sitesDomain)) - ) { - // TODO: Refactor later - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'You cannot assign your functions or sites domain or their subdomains to a specific resource. Please use a different domain.'); - } - - if ($domain === 'localhost' || $domain === APP_HOSTNAME_INTERNAL) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please pick another one.'); - } - - // TODO: @christyjacob remove once we migrate the rules in 1.7.x - if (System::getEnv('_APP_RULES_FORMAT') === 'md5') { - $document = $dbForPlatform->getDocument('rules', md5($domain)); - } else { - $document = $dbForPlatform->findOne('rules', [ - Query::equal('domain', [$domain]), - ]); - } - - - if (!$document->isEmpty()) { - if ($document->getAttribute('projectId') === $project->getId()) { - $resourceType = $document->getAttribute('resourceType'); - $resourceId = $document->getAttribute('resourceId'); - $message = "Domain already assigned to '{$resourceType}' service"; - if (!empty($resourceId)) { - $message .= " with ID '{$resourceId}'"; - } - - $message .= '.'; - } else { - $message = 'Domain already assigned to different project.'; - } - - throw new Exception(Exception::RULE_ALREADY_EXISTS, $message); - } - - $resourceInternalId = ''; - - switch ($resourceType) { - case 'function': - if (empty($resourceId)) { - throw new Exception(Exception::FUNCTION_NOT_FOUND); - } - - $function = $dbForProject->getDocument('functions', $resourceId); - - if ($function->isEmpty()) { - throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND); - } - - $resourceInternalId = $function->getInternalId(); - break; - case 'site': - if (empty($resourceId)) { - throw new Exception(Exception::SITE_NOT_FOUND); - } - - $site = $dbForProject->getDocument('sites', $resourceId); - - if ($site->isEmpty()) { - throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND); - } - - $resourceInternalId = $site->getInternalId(); - break; - } - - try { - $domain = new Domain($domain); - } catch (\Throwable) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.'); - } - - // TODO: @christyjacob remove once we migrate the rules in 1.7.x - $ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique(); - - $rule = new Document([ - '$id' => $ruleId, - 'projectId' => $project->getId(), - 'projectInternalId' => $project->getInternalId(), - 'domain' => $domain->get(), - 'resourceType' => $resourceType, - 'resourceId' => $resourceId, - 'resourceInternalId' => $resourceInternalId, - 'certificateId' => '', - ]); - - $status = 'created'; - - if (\str_ends_with($domain->get(), $functionsDomain) || \str_ends_with($domain->get(), $sitesDomain)) { - $status = 'verified'; - } - - if ($status === 'created') { - $target = new Domain(System::getEnv('_APP_DOMAIN_TARGET', '')); - $validator = new CNAME($target->get()); // Verify Domain with DNS records - - if ($validator->isValid($domain->get())) { - $status = 'verifying'; - - $queueForCertificates - ->setDomain(new Document([ - 'domain' => $rule->getAttribute('domain') - ])) - ->trigger(); - } - } - - $rule->setAttribute('status', $status); - $rule = $dbForPlatform->createDocument('rules', $rule); - - $queueForEvents->setParam('ruleId', $rule->getId()); - - $rule->setAttribute('logs', ''); - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($rule, Response::MODEL_PROXY_RULE); - }); App::get('/v1/proxy/rules') ->groups(['api', 'proxy']) @@ -411,34 +248,3 @@ App::patch('/v1/proxy/rules/:ruleId/verification') $response->dynamic($rule, Response::MODEL_PROXY_RULE); }); - -App::get('/v1/proxy/subdomains') - ->desc('Check if subdomain is available') - ->groups(['api', 'proxy']) - ->label('scope', 'rules.read') - ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) - ->label('sdk.namespace', 'proxy') - ->label('sdk.method', 'checkSubdomain') - ->label('sdk.description', '/docs/references/proxy/check-subdomain.md') - ->label('sdk.response.code', Response::STATUS_CODE_OK) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_NONE) - ->param('resourceType', null, new WhiteList(['function', 'site']), 'Action definition for the rule. Possible values are "function" and "site"') - ->param('subdomain', '', new Text(256), 'Subdomain name.') - ->inject('response') - ->inject('dbForPlatform') - ->action(function (string $resourceType, string $subdomain, Response $response, Database $dbForPlatform) { - //TODO: Add tests for this endpoint - $resourceDomain = $resourceType === 'site' ? System::getEnv('_APP_DOMAIN_SITES', '') : System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); - $domain = $subdomain . '.' . $resourceDomain; - - $document = $dbForPlatform->findOne('rules', [ - Query::equal('domain', [$domain]), - ]); - - if ($document && !$document->isEmpty()) { - throw new Exception(Exception::RULE_ALREADY_EXISTS, 'Subdomain already assigned to different project.'); - } - - $response->noContent(); - }); diff --git a/src/Appwrite/Platform/Appwrite.php b/src/Appwrite/Platform/Appwrite.php index b77ccce979..dd6200dbcf 100644 --- a/src/Appwrite/Platform/Appwrite.php +++ b/src/Appwrite/Platform/Appwrite.php @@ -4,6 +4,7 @@ namespace Appwrite\Platform; use Appwrite\Platform\Modules\Core; use Appwrite\Platform\Modules\Functions; +use Appwrite\Platform\Modules\Proxy; use Appwrite\Platform\Modules\Sites; use Utopia\Platform\Platform; @@ -14,5 +15,6 @@ class Appwrite extends Platform parent::__construct(new Core()); $this->addModule(new Functions\Module()); $this->addModule(new Sites\Module()); + $this->addModule(new Proxy\Module()); } } diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php index 9d89b7edf0..8787d0db0b 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php @@ -7,7 +7,6 @@ use Appwrite\Event\Event; use Appwrite\Event\Validator\FunctionEvent; use Appwrite\Extend\Exception; use Appwrite\Functions\Validator\RuntimeSpecification; -use Appwrite\Messaging\Adapter\Realtime; use Appwrite\Platform\Modules\Compute\Base; use Appwrite\SDK\AuthType; use Appwrite\SDK\Method; @@ -15,7 +14,6 @@ use Appwrite\SDK\Response as SDKResponse; use Appwrite\Task\Validator\Cron; use Appwrite\Utopia\Database\Validator\CustomId; use Appwrite\Utopia\Response; -use Appwrite\Utopia\Response\Model\Rule; use Utopia\Abuse\Abuse; use Utopia\App; use Utopia\Config\Config; @@ -284,72 +282,6 @@ class Create extends Base ->setTemplate($template); } - $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); - if (!empty($functionsDomain)) { - $routeSubdomain = ID::unique(); - $domain = "{$routeSubdomain}.{$functionsDomain}"; - $ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain) : ID::unique(); - - $rule = Authorization::skip( - fn () => $dbForPlatform->createDocument('rules', new Document([ - '$id' => $ruleId, - 'projectId' => $project->getId(), - 'projectInternalId' => $project->getInternalId(), - 'domain' => $domain, - 'resourceType' => 'function', - 'resourceId' => $function->getId(), - 'resourceInternalId' => $function->getInternalId(), - 'status' => 'verified', - 'certificateId' => '', - ])) - ); - - /** Trigger Webhook */ - $ruleModel = new Rule(); - $ruleCreate = - $queueForEvents - ->setClass(Event::WEBHOOK_CLASS_NAME) - ->setQueue(Event::WEBHOOK_QUEUE_NAME); - - $ruleCreate - ->setProject($project) - ->setEvent('rules.[ruleId].create') - ->setParam('ruleId', $rule->getId()) - ->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules()))) - ->trigger(); - - /** Trigger Functions */ - $ruleCreate - ->setClass(Event::FUNCTIONS_CLASS_NAME) - ->setQueue(Event::FUNCTIONS_QUEUE_NAME) - ->trigger(); - - /** Trigger realtime event */ - $allEvents = Event::generateEvents('rules.[ruleId].create', [ - 'ruleId' => $rule->getId(), - ]); - $target = Realtime::fromPayload( - // Pass first, most verbose event pattern - event: $allEvents[0], - payload: $rule, - project: $project - ); - Realtime::send( - projectId: 'console', - payload: $rule->getArrayCopy(), - events: $allEvents, - channels: $target['channels'], - roles: $target['roles'] - ); - Realtime::send( - projectId: $project->getId(), - payload: $rule->getArrayCopy(), - events: $allEvents, - channels: $target['channels'], - roles: $target['roles'] - ); - } - $queueForEvents->setParam('functionId', $function->getId()); $response diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php new file mode 100644 index 0000000000..8ecc67bb1e --- /dev/null +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php @@ -0,0 +1,195 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_POST) + ->setHttpPath('/v1/proxy/rules') + ->groups(['api', 'proxy']) + ->desc('Create rule') + ->label('scope', 'rules.write') + ->label('event', 'rules.[ruleId].create') + ->label('audits.event', 'rule.create') + ->label('audits.resource', 'rule/{response.$id}') + ->label('sdk', new Method( + namespace: 'proxy', + name: 'createRule', + description: '/docs/references/proxy/create-rule.md', + auth: [AuthType::ADMIN], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_CREATED, + model: Response::MODEL_PROXY_RULE, + ) + ] + )) + ->label('abuse-limit', 10) + ->label('abuse-key', 'userId:{userId}, url:{url}') + ->label('abuse-time', 60) + ->param('domain', null, new ValidatorDomain(), 'Domain name.') + ->param('resourceType', null, new WhiteList(['api', 'function', 'site']), 'Action definition for the rule. Possible values are "api", "function" and "site"') + ->param('resourceId', '', new UID(), 'ID of resource for the action type. If resourceType is "api", leave empty. If resourceType is "function", provide ID of the function.', true) + ->inject('response') + ->inject('project') + ->inject('queueForCertificates') + ->inject('queueForEvents') + ->inject('dbForPlatform') + ->inject('dbForProject') + ->callback([$this, 'action']); + } + + public function action(string $domain, string $resourceType, string $resourceId, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject) + { + $mainDomain = System::getEnv('_APP_DOMAIN', ''); + if ($domain === $mainDomain) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'You cannot assign your main domain to specific resource. Please use subdomain or a different domain.'); + } + + $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); + $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); + + if ($domain === 'localhost' || $domain === APP_HOSTNAME_INTERNAL) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please pick another one.'); + } + + // TODO: @christyjacob remove once we migrate the rules in 1.7.x + if (System::getEnv('_APP_RULES_FORMAT') === 'md5') { + $document = $dbForPlatform->getDocument('rules', md5($domain)); + } else { + $document = $dbForPlatform->findOne('rules', [ + Query::equal('domain', [$domain]), + ]); + } + + if (!$document->isEmpty()) { + if ($document->getAttribute('projectId') === $project->getId()) { + $resourceType = $document->getAttribute('resourceType'); + $resourceId = $document->getAttribute('resourceId'); + $message = "Domain already assigned to another resource."; + if (!empty($resourceId)) { + $message .= " with ID '{$resourceId}'"; + } + + $message .= '.'; + } else { + $message = 'Domain already assigned to different project.'; + } + + throw new Exception(Exception::RULE_ALREADY_EXISTS, $message); + } + + $resourceInternalId = ''; + + switch ($resourceType) { + case 'function': + if (empty($resourceId)) { + throw new Exception(Exception::FUNCTION_NOT_FOUND); + } + + $function = $dbForProject->getDocument('functions', $resourceId); + + if ($function->isEmpty()) { + throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND); + } + + $resourceInternalId = $function->getInternalId(); + break; + case 'site': + if (empty($resourceId)) { + throw new Exception(Exception::SITE_NOT_FOUND); + } + + $site = $dbForProject->getDocument('sites', $resourceId); + + if ($site->isEmpty()) { + throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND); + } + + $resourceInternalId = $site->getInternalId(); + break; + } + + try { + $domain = new Domain($domain); + } catch (\Throwable) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.'); + } + + // TODO: @christyjacob remove once we migrate the rules in 1.7.x + $ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique(); + + $rule = new Document([ + '$id' => $ruleId, + 'projectId' => $project->getId(), + 'projectInternalId' => $project->getInternalId(), + 'domain' => $domain->get(), + 'resourceType' => $resourceType, + 'resourceId' => $resourceId, + 'resourceInternalId' => $resourceInternalId, + 'certificateId' => '', + ]); + + $status = 'created'; + + if (\str_ends_with($domain->get(), $functionsDomain) || \str_ends_with($domain->get(), $sitesDomain)) { + $status = 'verified'; + } + + if ($status === 'created') { + $target = new Domain(System::getEnv('_APP_DOMAIN_TARGET', '')); + $validator = new CNAME($target->get()); // Verify Domain with DNS records + + if ($validator->isValid($domain->get())) { + $status = 'verifying'; + + $queueForCertificates + ->setDomain(new Document([ + 'domain' => $rule->getAttribute('domain') + ])) + ->trigger(); + } + } + + $rule->setAttribute('status', $status); + $rule = $dbForPlatform->createDocument('rules', $rule); + + $queueForEvents->setParam('ruleId', $rule->getId()); + + $rule->setAttribute('logs', ''); + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($rule, Response::MODEL_PROXY_RULE); + } +} diff --git a/src/Appwrite/Platform/Modules/Proxy/Module.php b/src/Appwrite/Platform/Modules/Proxy/Module.php new file mode 100644 index 0000000000..cd8f6f86dc --- /dev/null +++ b/src/Appwrite/Platform/Modules/Proxy/Module.php @@ -0,0 +1,14 @@ +addService('http', new Http()); + } +} diff --git a/src/Appwrite/Platform/Modules/Proxy/Services/Http.php b/src/Appwrite/Platform/Modules/Proxy/Services/Http.php new file mode 100644 index 0000000000..bc564f3714 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Proxy/Services/Http.php @@ -0,0 +1,16 @@ +type = Service::TYPE_HTTP; + // Rules + $this->addAction(CreateRule::getName(), new CreateRule()); + } +} diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php index 079f2fec49..2fd7f4d1c1 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php @@ -5,7 +5,6 @@ namespace Appwrite\Platform\Modules\Sites\Http\Sites; use Appwrite\Event\Build; use Appwrite\Event\Event; use Appwrite\Extend\Exception; -use Appwrite\Messaging\Adapter\Realtime; use Appwrite\Platform\Modules\Compute\Base; use Appwrite\SDK\AuthType; use Appwrite\SDK\Method; @@ -13,7 +12,6 @@ use Appwrite\SDK\Response as SDKResponse; use Appwrite\Sites\Validator\FrameworkSpecification; use Appwrite\Utopia\Database\Validator\CustomId; use Appwrite\Utopia\Response; -use Appwrite\Utopia\Response\Model\Rule; use Utopia\App; use Utopia\Config\Config; use Utopia\Database\Database; @@ -74,7 +72,6 @@ class Create extends Base ->param('installCommand', '', new Text(8192, 0), 'Install Command.', true) ->param('buildCommand', '', new Text(8192, 0), 'Build Command.', true) ->param('outputDirectory', '', new Text(8192, 0), 'Output Directory for site.', true) - ->param('subdomain', '', new CustomId(), 'Unique custom sub-domain. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', true) ->param('buildRuntime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Runtime to use during build step.') ->param('adapter', '', new Text(8192, 0), 'Framework adapter. Allows: static, ssr', true) ->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for VCS (Version Control System) deployment.', true) @@ -105,7 +102,7 @@ class Create extends Base ->callback([$this, 'action']); } - public function action(string $siteId, string $name, string $framework, bool $enabled, int $timeout, string $installCommand, string $buildCommand, string $outputDirectory, string $subdomain, string $buildRuntime, string $adapter, string $installationId, ?string $fallbackFile, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateVersion, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForPlatform, GitHub $github) + public function action(string $siteId, string $name, string $framework, bool $enabled, int $timeout, string $installCommand, string $buildCommand, string $outputDirectory, string $buildRuntime, string $adapter, string $installationId, ?string $fallbackFile, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateVersion, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForPlatform, GitHub $github) { if (!empty($adapter)) { $configFramework = Config::getParam('frameworks')[$framework] ?? []; @@ -116,21 +113,6 @@ class Create extends Base } } - $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); - $routeSubdomain = ''; - $domain = ''; - - if (!empty($sitesDomain)) { - $routeSubdomain = $subdomain ?: ID::unique(); - $domain = "{$routeSubdomain}.{$sitesDomain}"; - - $subdomain = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', \md5($domain))); - - if ($subdomain && !$subdomain->isEmpty()) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Subdomain already exists. Please choose a different subdomain.'); - } - } - $siteId = ($siteId == 'unique()') ? ID::unique() : $siteId; $allowList = \array_filter(\explode(',', System::getEnv('_APP_SITES_FRAMEWORKS', ''))); @@ -271,67 +253,6 @@ class Create extends Base ->setTemplate($template); } - if (!empty($sitesDomain)) { - $rule = Authorization::skip( - fn () => $dbForPlatform->createDocument('rules', new Document([ - '$id' => \md5($domain), - 'projectId' => $project->getId(), - 'projectInternalId' => $project->getInternalId(), - 'domain' => $domain, - 'resourceType' => 'site', - 'resourceId' => $site->getId(), - 'resourceInternalId' => $site->getInternalId(), - 'status' => 'verified', - 'certificateId' => '', - ])) - ); - - /** Trigger Webhook */ - $ruleModel = new Rule(); - $ruleCreate = - $queueForEvents - ->setClass(Event::WEBHOOK_CLASS_NAME) - ->setQueue(Event::WEBHOOK_QUEUE_NAME); - - $ruleCreate - ->setProject($project) - ->setEvent('rules.[ruleId].create') - ->setParam('ruleId', $rule->getId()) - ->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules()))) - ->trigger(); - - /** Trigger Sites */ - $ruleCreate - ->setClass(Event::SITES_CLASS_NAME) - ->setQueue(Event::SITES_QUEUE_NAME) - ->trigger(); - - /** Trigger realtime event */ - $allEvents = Event::generateEvents('rules.[ruleId].create', [ - 'ruleId' => $rule->getId(), - ]); - $target = Realtime::fromPayload( - // Pass first, most verbose event pattern - event: $allEvents[0], - payload: $rule, - project: $project - ); - Realtime::send( - projectId: 'console', - payload: $rule->getArrayCopy(), - events: $allEvents, - channels: $target['channels'], - roles: $target['roles'] - ); - Realtime::send( - projectId: $project->getId(), - payload: $rule->getArrayCopy(), - events: $allEvents, - channels: $target['channels'], - roles: $target['roles'] - ); - } - $queueForEvents->setParam('siteId', $site->getId()); $response diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 98e03af1b8..79f3e33c10 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -1639,6 +1639,24 @@ class FunctionsCustomServerTest extends Scope 'execute' => ['any'] ]); + $rule = $this->client->call( + Client::METHOD_POST, + '/proxy/rules', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'domain' => 'test-' . ID::unique() . System::getEnv('_APP_DOMAIN_FUNCTIONS'), + 'resourceType' => 'function', + 'resourceId' => $functionId, + ], + ); + + $this->assertEquals(201, $rule['headers']['status-code']); + $this->assertNotEmpty($rule['body']['$id']); + $this->assertNotEmpty($rule['body']['domain']); + $rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1715,6 +1733,24 @@ class FunctionsCustomServerTest extends Scope 'execute' => ['any'] ]); + $rule = $this->client->call( + Client::METHOD_POST, + '/proxy/rules', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'domain' => 'test-' . ID::unique() . System::getEnv('_APP_DOMAIN_FUNCTIONS'), + 'resourceType' => 'function', + 'resourceId' => $functionId, + ], + ); + + $this->assertEquals(201, $rule['headers']['status-code']); + $this->assertNotEmpty($rule['body']['$id']); + $this->assertNotEmpty($rule['body']['domain']); + $rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1765,6 +1801,24 @@ class FunctionsCustomServerTest extends Scope 'execute' => ['any'] ]); + $rule = $this->client->call( + Client::METHOD_POST, + '/proxy/rules', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'domain' => 'test-' . ID::unique() . System::getEnv('_APP_DOMAIN_FUNCTIONS'), + 'resourceType' => 'function', + 'resourceId' => $functionId, + ], + ); + + $this->assertEquals(201, $rule['headers']['status-code']); + $this->assertNotEmpty($rule['body']['$id']); + $this->assertNotEmpty($rule['body']['domain']); + $rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], diff --git a/tests/e2e/Services/Sites/SitesBase.php b/tests/e2e/Services/Sites/SitesBase.php index d76e34fd24..49a272dcde 100644 --- a/tests/e2e/Services/Sites/SitesBase.php +++ b/tests/e2e/Services/Sites/SitesBase.php @@ -6,7 +6,9 @@ use Appwrite\Tests\Async; use CURLFile; use Tests\E2E\Client; use Utopia\CLI\Console; +use Utopia\Database\Helpers\ID; use Utopia\Database\Query; +use Utopia\System\System; trait SitesBase { @@ -47,7 +49,7 @@ trait SitesBase 'x-appwrite-key' => $this->getProject()['apiKey'], ])); $this->assertEquals('ready', $deployment['body']['status'], 'Deployment status is not ready, deployment: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); - }, 50000, 500); + }, 100000, 500); return $deploymentId; } @@ -257,6 +259,27 @@ trait SitesBase return $site; } + protected function createSiteDomain(string $siteId, string $subdomain = ''): string + { + $subdomain = $subdomain ? $subdomain : ID::unique(); + $rule = $this->client->call(Client::METHOD_POST, '/proxy/rules', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'domain' => $subdomain . System::getEnv('_APP_DOMAIN_SITES', ''), + 'resourceType' => 'site', + 'resourceId' => $siteId, + ]); + + $this->assertEquals(201, $rule['headers']['status-code']); + $this->assertNotEmpty($rule['body']['$id']); + $this->assertNotEmpty($rule['body']['domain']); + + $domain = $rule['body']['domain']; + + return $domain; + } + protected function getSiteDomain(string $siteId): string { $rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([ diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index 5d3e9c81ef..7a4bb3a20d 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -11,6 +11,7 @@ use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; use Utopia\Database\Validator\Datetime as DatetimeValidator; +use Utopia\System\System; class SitesCustomServerTest extends Scope { @@ -218,6 +219,8 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($siteId); + $domain = $this->createSiteDomain($siteId); + $secretVariable = $this->createVariable($siteId, [ 'key' => 'name', 'value' => 'Appwrite', @@ -1154,6 +1157,8 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($site['body']['deploymentId']); }, 50000, 500); + $domain = $this->createSiteDomain($siteId); + $domain = $this->getSiteDomain($siteId); $proxyClient = new Client(); $proxyClient->setEndpoint('http://' . $domain); @@ -1181,8 +1186,6 @@ class SitesCustomServerTest extends Scope public function testSiteDomainReclaiming(): void { - $subdomain = 'startup' . \uniqid(); - $siteId = $this->setupSite([ 'siteId' => ID::unique(), 'name' => 'Startup site', @@ -1193,11 +1196,13 @@ class SitesCustomServerTest extends Scope 'buildCommand' => '', 'installCommand' => '', 'fallbackFile' => '', - 'subdomain' => $subdomain ]); $this->assertNotEmpty($siteId); + $subdomain = 'startup' . \uniqid(); + $domain = $this->createSiteDomain($siteId, $subdomain); + $deploymentId = $this->setupDeployment($siteId, [ 'code' => $this->packageSite('static'), 'activate' => 'true' @@ -1227,11 +1232,19 @@ class SitesCustomServerTest extends Scope 'buildCommand' => '', 'installCommand' => '', 'fallbackFile' => '', - 'subdomain' => $subdomain ]); - $this->assertEquals(400, $site['headers']['status-code']); - $this->assertStringContainsString("Subdomain already exists.", $site['body']['message']); + $rule = $this->client->call(Client::METHOD_POST, '/proxy/rules', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'domain' => $subdomain . System::getEnv('_APP_DOMAIN_SITES', ''), + 'resourceType' => 'site', + 'resourceId' => $siteId, + ]); + + $this->assertEquals(409, $rule['headers']['status-code']); + $this->assertStringContainsString("Domain already assigned to another resource.", $rule['body']['message']); $this->cleanupSite($siteId); @@ -1267,10 +1280,16 @@ class SitesCustomServerTest extends Scope 'buildCommand' => '', 'installCommand' => '', 'fallbackFile' => '', - 'subdomain' => $subdomain ]); $this->assertEquals(201, $site['headers']['status-code']); + $this->assertNotEmpty($site['body']['$id']); + + $siteId = $site['body']['$id']; + + $domain = $this->createSiteDomain($siteId, $subdomain); + + $this->assertNotEmpty($domain); $this->cleanupSite($site['body']['$id']); } From f9ce932ac1ff64e6fa7e324d5361c7faa927f401 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Thu, 13 Feb 2025 00:11:43 +0530 Subject: [PATCH 02/15] Create site rule --- tests/e2e/Services/Sites/SitesCustomServerTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index 4f3fc2d327..c002dddbb5 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -74,12 +74,13 @@ class SitesCustomServerTest extends Scope 'framework' => 'other', 'buildRuntime' => 'ssr-22', 'outputDirectory' => './', - 'subdomain' => 'test-site', 'fallbackFile' => null, ]); $this->assertNotEmpty($siteId); + $this->createSiteDomain($siteId, 'test-site'); + $rule = $this->getSiteDomain($siteId); $response = $this->client->call(Client::METHOD_GET, '/console/resources', [ From 4b909ed7dc5ff69d43963ad741b0a7e54496784e Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Thu, 13 Feb 2025 00:23:13 +0530 Subject: [PATCH 03/15] Create rule in function logging test --- .../Functions/FunctionsCustomServerTest.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 79f3e33c10..94b1fe76b5 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -1954,6 +1954,24 @@ class FunctionsCustomServerTest extends Scope $functionId = $function['body']['$id'] ?? ''; + $rule = $this->client->call( + Client::METHOD_POST, + '/proxy/rules', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'domain' => 'test-' . ID::unique() . System::getEnv('_APP_DOMAIN_FUNCTIONS'), + 'resourceType' => 'function', + 'resourceId' => $functionId, + ], + ); + + $this->assertEquals(201, $rule['headers']['status-code']); + $this->assertNotEmpty($rule['body']['$id']); + $this->assertNotEmpty($rule['body']['domain']); + $this->setupDeployment($functionId, [ 'code' => $this->packageFunction('node'), 'activate' => true From bd58ba32c8d8a8f5348695a5cd2a8532de93fb84 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:21:52 +0530 Subject: [PATCH 04/15] Remove older test --- .../Projects/ProjectsCustomServerTest.php | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/tests/e2e/Services/Projects/ProjectsCustomServerTest.php b/tests/e2e/Services/Projects/ProjectsCustomServerTest.php index f81290e707..fac7c2fe95 100644 --- a/tests/e2e/Services/Projects/ProjectsCustomServerTest.php +++ b/tests/e2e/Services/Projects/ProjectsCustomServerTest.php @@ -6,7 +6,6 @@ use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideServer; -use Utopia\System\System; class ProjectsCustomServerTest extends Scope { @@ -34,25 +33,5 @@ class ProjectsCustomServerTest extends Scope $response = $this->client->call(Client::METHOD_DELETE, '/proxy/rules/' . $response['body']['$id'], $headers); $this->assertEquals(204, $response['headers']['status-code']); - - // prevent functions domain - $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); - - $response = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [ - 'resourceType' => 'api', - 'domain' => $functionsDomain, - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // prevent sites domain - $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); - - $response = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [ - 'resourceType' => 'api', - 'domain' => $sitesDomain, - ]); - - $this->assertEquals(400, $response['headers']['status-code']); } } From 3531d223e28266e33d6fdcde18b5da8947f04a5a Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Thu, 13 Feb 2025 20:27:25 +0530 Subject: [PATCH 05/15] Fix general tests --- tests/e2e/General/UsageTest.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/e2e/General/UsageTest.php b/tests/e2e/General/UsageTest.php index 74ae1c00bc..27fdd0b8fa 100644 --- a/tests/e2e/General/UsageTest.php +++ b/tests/e2e/General/UsageTest.php @@ -12,10 +12,12 @@ use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideServer; use Tests\E2E\Services\Functions\FunctionsBase; +use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Datetime as DatetimeValidator; +use Utopia\System\System; class UsageTest extends Scope { @@ -1090,6 +1092,24 @@ class UsageTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); + $rule = $this->client->call( + Client::METHOD_POST, + '/proxy/rules', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'domain' => 'test-' . ID::unique() . System::getEnv('_APP_DOMAIN_FUNCTIONS'), + 'resourceType' => 'function', + 'resourceId' => $functionId, + ], + ); + + $this->assertEquals(201, $rule['headers']['status-code']); + $this->assertNotEmpty($rule['body']['$id']); + $this->assertNotEmpty($rule['body']['domain']); + $rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], From b1854fee105515c6689e575014dae5d677f7ee25 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Sat, 15 Feb 2025 23:23:02 +0530 Subject: [PATCH 06/15] Add helper methods in functionBase --- docs/references/proxy/create-rule.md | 1 - .../Modules/Proxy/Http/Rules/Create.php | 4 +- .../e2e/Services/Functions/FunctionsBase.php | 46 ++++++ .../Functions/FunctionsCustomServerTest.php | 138 +----------------- 4 files changed, 57 insertions(+), 132 deletions(-) delete mode 100644 docs/references/proxy/create-rule.md diff --git a/docs/references/proxy/create-rule.md b/docs/references/proxy/create-rule.md deleted file mode 100644 index be567b1cc0..0000000000 --- a/docs/references/proxy/create-rule.md +++ /dev/null @@ -1 +0,0 @@ -Create a new proxy rule. \ No newline at end of file diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php index 8ecc67bb1e..ee70f7fdd3 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php @@ -45,7 +45,9 @@ class Create extends Action ->label('sdk', new Method( namespace: 'proxy', name: 'createRule', - description: '/docs/references/proxy/create-rule.md', + description: <<client->call(Client::METHOD_POST, '/proxy/rules', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'domain' => $subdomain . System::getEnv('_APP_DOMAIN_FUNCTIONS', ''), + 'resourceType' => 'function', + 'resourceId' => $functionId, + ]); + + $this->assertEquals(201, $rule['headers']['status-code']); + $this->assertNotEmpty($rule['body']['$id']); + $this->assertNotEmpty($rule['body']['domain']); + + $domain = $rule['body']['domain']; + + return $domain; + } + + protected function getFunctionDomain(string $functionId): string + { + $rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::equal('resourceId', [$functionId])->toString(), + Query::equal('resourceType', ['function'])->toString(), + ], + ]); + + $this->assertEquals(200, $rules['headers']['status-code']); + $this->assertGreaterThanOrEqual(1, $rules['body']['total']); + $this->assertGreaterThanOrEqual(1, \count($rules['body']['rules'])); + $this->assertNotEmpty($rules['body']['rules'][0]['domain']); + + $domain = $rules['body']['rules'][0]['domain']; + + return $domain; + } } diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 94b1fe76b5..01157667ae 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -1639,40 +1639,9 @@ class FunctionsCustomServerTest extends Scope 'execute' => ['any'] ]); - $rule = $this->client->call( - Client::METHOD_POST, - '/proxy/rules', - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), - [ - 'domain' => 'test-' . ID::unique() . System::getEnv('_APP_DOMAIN_FUNCTIONS'), - 'resourceType' => 'function', - 'resourceId' => $functionId, - ], - ); + $this->createFunctionDomain($functionId); - $this->assertEquals(201, $rule['headers']['status-code']); - $this->assertNotEmpty($rule['body']['$id']); - $this->assertNotEmpty($rule['body']['domain']); - - $rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ - Query::equal('resourceId', [$functionId])->toString(), - Query::equal('resourceType', ['function'])->toString(), - ], - ]); - - $this->assertEquals(200, $rules['headers']['status-code']); - $this->assertEquals(1, $rules['body']['total']); - $this->assertCount(1, $rules['body']['rules']); - $this->assertNotEmpty($rules['body']['rules'][0]['domain']); - - $domain = $rules['body']['rules'][0]['domain']; + $domain = $this->getFunctionDomain($functionId); $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', @@ -1733,40 +1702,9 @@ class FunctionsCustomServerTest extends Scope 'execute' => ['any'] ]); - $rule = $this->client->call( - Client::METHOD_POST, - '/proxy/rules', - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), - [ - 'domain' => 'test-' . ID::unique() . System::getEnv('_APP_DOMAIN_FUNCTIONS'), - 'resourceType' => 'function', - 'resourceId' => $functionId, - ], - ); + $this->createFunctionDomain($functionId); - $this->assertEquals(201, $rule['headers']['status-code']); - $this->assertNotEmpty($rule['body']['$id']); - $this->assertNotEmpty($rule['body']['domain']); - - $rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ - Query::equal('resourceId', [$functionId])->toString(), - Query::equal('resourceType', ['function'])->toString(), - ], - ]); - - $this->assertEquals(200, $rules['headers']['status-code']); - $this->assertEquals(1, $rules['body']['total']); - $this->assertCount(1, $rules['body']['rules']); - $this->assertNotEmpty($rules['body']['rules'][0]['domain']); - - $domain = $rules['body']['rules'][0]['domain']; + $domain = $this->getFunctionDomain($functionId); $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', @@ -1801,40 +1739,9 @@ class FunctionsCustomServerTest extends Scope 'execute' => ['any'] ]); - $rule = $this->client->call( - Client::METHOD_POST, - '/proxy/rules', - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), - [ - 'domain' => 'test-' . ID::unique() . System::getEnv('_APP_DOMAIN_FUNCTIONS'), - 'resourceType' => 'function', - 'resourceId' => $functionId, - ], - ); + $this->createFunctionDomain($functionId); - $this->assertEquals(201, $rule['headers']['status-code']); - $this->assertNotEmpty($rule['body']['$id']); - $this->assertNotEmpty($rule['body']['domain']); - - $rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ - Query::equal('resourceId', [$functionId])->toString(), - Query::equal('resourceType', ['function'])->toString(), - ], - ]); - - $this->assertEquals(200, $rules['headers']['status-code']); - $this->assertEquals(1, $rules['body']['total']); - $this->assertCount(1, $rules['body']['rules']); - $this->assertNotEmpty($rules['body']['rules'][0]['domain']); - - $domain = $rules['body']['rules'][0]['domain']; + $domain = $this->getFunctionDomain($functionId); $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', @@ -1954,23 +1861,7 @@ class FunctionsCustomServerTest extends Scope $functionId = $function['body']['$id'] ?? ''; - $rule = $this->client->call( - Client::METHOD_POST, - '/proxy/rules', - array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), - [ - 'domain' => 'test-' . ID::unique() . System::getEnv('_APP_DOMAIN_FUNCTIONS'), - 'resourceType' => 'function', - 'resourceId' => $functionId, - ], - ); - - $this->assertEquals(201, $rule['headers']['status-code']); - $this->assertNotEmpty($rule['body']['$id']); - $this->assertNotEmpty($rule['body']['domain']); + $this->createFunctionDomain($functionId); $this->setupDeployment($functionId, [ 'code' => $this->packageFunction('node'), @@ -2006,20 +1897,7 @@ class FunctionsCustomServerTest extends Scope }, 10000, 500); // Domain Executions test - $rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ - Query::equal('resourceId', [$functionId])->toString(), - Query::equal('resourceType', ['function'])->toString(), - ], - ]); - - $this->assertEquals(200, $rules['headers']['status-code']); - $this->assertNotEmpty($rules['body']['rules'][0]['domain']); - - $domain = $rules['body']['rules'][0]['domain']; + $domain = $this->getFunctionDomain($functionId); $proxyClient = new Client(); $proxyClient->setEndpoint('http://' . $domain); From 30cb7e8ff2b50df5a66a7d651d3ae854ba193677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Mon, 17 Feb 2025 09:47:38 +0100 Subject: [PATCH 07/15] Todos --- src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php index ee70f7fdd3..900bdf5cf7 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php @@ -81,6 +81,9 @@ class Create extends Action $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); + // Not xactly sitesDomain + // Not exactly functionsDomain + if ($domain === 'localhost' || $domain === APP_HOSTNAME_INTERNAL) { throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please pick another one.'); } From 9459c87b50d7d66f3eace7f86c246261c3e827ee Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Mon, 17 Feb 2025 17:15:23 +0530 Subject: [PATCH 08/15] Re-add correct project tests --- .../Modules/Proxy/Http/Rules/Create.php | 24 +++++++++---- .../Modules/Sites/Http/Sites/Create.php | 1 - .../Projects/ProjectsCustomServerTest.php | 36 +++++++++++++++++++ 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php index 900bdf5cf7..7bf9dcf133 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php @@ -81,8 +81,9 @@ class Create extends Action $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); - // Not xactly sitesDomain - // Not exactly functionsDomain + if ($domain === $functionsDomain || $domain === $sitesDomain) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'You cannot assign your functions or sites domain to a specific resource. Please use a different domain.'); + } if ($domain === 'localhost' || $domain === APP_HOSTNAME_INTERNAL) { throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please pick another one.'); @@ -119,7 +120,11 @@ class Create extends Action switch ($resourceType) { case 'function': if (empty($resourceId)) { - throw new Exception(Exception::FUNCTION_NOT_FOUND); + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, '$resourceId cannot be empty for resourceType "function".'); + } + + if (!\str_ends_with($domain, $functionsDomain)) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain must end with ' . $functionsDomain . ' for resourceType "function".'); } $function = $dbForProject->getDocument('functions', $resourceId); @@ -132,7 +137,11 @@ class Create extends Action break; case 'site': if (empty($resourceId)) { - throw new Exception(Exception::SITE_NOT_FOUND); + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, '$resourceId cannot be empty for resourceType "site".'); + } + + if (!\str_ends_with($domain, $sitesDomain)) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain must end with ' . $sitesDomain . ' for resourceType "site".'); } $site = $dbForProject->getDocument('sites', $resourceId); @@ -143,6 +152,11 @@ class Create extends Action $resourceInternalId = $site->getInternalId(); break; + case 'api': + if (\str_ends_with($domain, $functionsDomain) || \str_ends_with($domain, $sitesDomain)) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain must not end with ' . $functionsDomain . ' or ' . $sitesDomain . ' for resourceType "api".'); + } + break; } try { @@ -191,8 +205,6 @@ class Create extends Action $queueForEvents->setParam('ruleId', $rule->getId()); - $rule->setAttribute('logs', ''); - $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($rule, Response::MODEL_PROXY_RULE); diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php index 35ca34021f..6072489310 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php @@ -18,7 +18,6 @@ use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; -use Utopia\Database\Validator\Authorization; use Utopia\Platform\Action; use Utopia\Platform\Scope\HTTP; use Utopia\System\System; diff --git a/tests/e2e/Services/Projects/ProjectsCustomServerTest.php b/tests/e2e/Services/Projects/ProjectsCustomServerTest.php index fac7c2fe95..25a5a93daa 100644 --- a/tests/e2e/Services/Projects/ProjectsCustomServerTest.php +++ b/tests/e2e/Services/Projects/ProjectsCustomServerTest.php @@ -6,6 +6,7 @@ use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideServer; +use Utopia\System\System; class ProjectsCustomServerTest extends Scope { @@ -33,5 +34,40 @@ class ProjectsCustomServerTest extends Scope $response = $this->client->call(Client::METHOD_DELETE, '/proxy/rules/' . $response['body']['$id'], $headers); $this->assertEquals(204, $response['headers']['status-code']); + + $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); + + $response = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [ + 'resourceType' => 'api', + 'domain' => $functionsDomain, + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + + $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); + + $response = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [ + 'resourceType' => 'api', + 'domain' => $sitesDomain, + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // prevent functions domain + $response = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [ + 'resourceType' => 'function', + 'domain' => $functionsDomain, + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + // prevent sites domain + $response = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [ + 'resourceType' => 'site', + 'domain' => $sitesDomain, + ]); + + $this->assertEquals(400, $response['headers']['status-code']); } } From 71103b037f6a69d6c0219f58efb898c8c1e9055a Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Mon, 17 Feb 2025 17:17:27 +0530 Subject: [PATCH 09/15] Make switch case simpler --- .../Modules/Proxy/Http/Rules/Create.php | 30 +++++-------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php index 7bf9dcf133..4c115d8d4e 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php @@ -119,38 +119,24 @@ class Create extends Action switch ($resourceType) { case 'function': - if (empty($resourceId)) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, '$resourceId cannot be empty for resourceType "function".'); - } - - if (!\str_ends_with($domain, $functionsDomain)) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain must end with ' . $functionsDomain . ' for resourceType "function".'); - } - - $function = $dbForProject->getDocument('functions', $resourceId); - - if ($function->isEmpty()) { - throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND); - } - - $resourceInternalId = $function->getInternalId(); - break; case 'site': if (empty($resourceId)) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, '$resourceId cannot be empty for resourceType "site".'); + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, '$resourceId cannot be empty for resourceType "' . $resourceType . '".'); } - if (!\str_ends_with($domain, $sitesDomain)) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain must end with ' . $sitesDomain . ' for resourceType "site".'); + $expectedDomain = ($resourceType === 'function') ? $functionsDomain : $sitesDomain; + if (!\str_ends_with($domain, $expectedDomain)) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain must end with ' . $expectedDomain . ' for resourceType "' . $resourceType . '".'); } - $site = $dbForProject->getDocument('sites', $resourceId); + $collection = ($resourceType === 'function') ? 'functions' : 'sites'; + $document = $dbForProject->getDocument($collection, $resourceId); - if ($site->isEmpty()) { + if ($document->isEmpty()) { throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND); } - $resourceInternalId = $site->getInternalId(); + $resourceInternalId = $document->getInternalId(); break; case 'api': if (\str_ends_with($domain, $functionsDomain) || \str_ends_with($domain, $sitesDomain)) { From 28ef7da2318e3a27e5c1f410001eed645a80292c Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Mon, 17 Feb 2025 18:03:13 +0530 Subject: [PATCH 10/15] Fix new site tests --- .../Services/Functions/FunctionsCustomServerTest.php | 10 +++------- tests/e2e/Services/Sites/SitesCustomServerTest.php | 10 ++++------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 9ba474e4be..d3d7c0dc18 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -1717,9 +1717,7 @@ class FunctionsCustomServerTest extends Scope 'execute' => ['any'] ]); - $this->createFunctionDomain($functionId); - - $domain = $this->getFunctionDomain($functionId); + $domain = $this->createFunctionDomain($functionId); $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', @@ -1754,9 +1752,7 @@ class FunctionsCustomServerTest extends Scope 'execute' => ['any'] ]); - $this->createFunctionDomain($functionId); - - $domain = $this->getFunctionDomain($functionId); + $domain = $this->createFunctionDomain($functionId); $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', @@ -1876,7 +1872,7 @@ class FunctionsCustomServerTest extends Scope $functionId = $function['body']['$id'] ?? ''; - $this->createFunctionDomain($functionId); + $domain = $this->createFunctionDomain($functionId); $this->setupDeployment($functionId, [ 'code' => $this->packageFunction('node'), diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index c43d1937eb..723b49d5a2 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -79,9 +79,7 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($siteId); - $this->createSiteDomain($siteId, 'test-site'); - - $rule = $this->getSiteDomain($siteId); + $rule = $this->createSiteDomain($siteId, 'test-site'); $response = $this->client->call(Client::METHOD_GET, '/console/resources', [ 'origin' => 'http://localhost', @@ -1248,8 +1246,6 @@ class SitesCustomServerTest extends Scope }, 50000, 500); $domain = $this->createSiteDomain($siteId); - - $domain = $this->getSiteDomain($siteId); $proxyClient = new Client(); $proxyClient->setEndpoint('http://' . $domain); @@ -1400,6 +1396,8 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($siteId); + $domain = $this->createSiteDomain($siteId); + $deploymentId = $this->setupDeployment($siteId, [ 'code' => $this->packageSite('static'), 'activate' => 'true' @@ -1463,7 +1461,7 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($siteId); - $domain = $this->getSiteDomain($siteId); + $domain = $this->createSiteDomain($siteId, $subdomain); $this->assertNotEmpty($domain); From 2ea18c4761d7f7a558d524507c9ca3f9a09d2c8b Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Mon, 17 Feb 2025 18:12:29 +0530 Subject: [PATCH 11/15] Fix domain generation --- tests/e2e/Services/Functions/FunctionsBase.php | 2 +- tests/e2e/Services/Sites/SitesBase.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index fab6bd4027..bbdcbf3d67 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -266,7 +266,7 @@ trait FunctionsBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'domain' => $subdomain . System::getEnv('_APP_DOMAIN_FUNCTIONS', ''), + 'domain' => $subdomain . '.' . System::getEnv('_APP_DOMAIN_FUNCTIONS', ''), 'resourceType' => 'function', 'resourceId' => $functionId, ]); diff --git a/tests/e2e/Services/Sites/SitesBase.php b/tests/e2e/Services/Sites/SitesBase.php index 371e36955e..72448898f5 100644 --- a/tests/e2e/Services/Sites/SitesBase.php +++ b/tests/e2e/Services/Sites/SitesBase.php @@ -276,7 +276,7 @@ trait SitesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'domain' => $subdomain . System::getEnv('_APP_DOMAIN_SITES', ''), + 'domain' => $subdomain . '.' . System::getEnv('_APP_DOMAIN_SITES', ''), 'resourceType' => 'site', 'resourceId' => $siteId, ]); From 2e4a8dec78c2e08d1e8e3dad673593d360361113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Mon, 17 Feb 2025 15:05:19 +0100 Subject: [PATCH 12/15] Fix CORS --- app/controllers/general.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index c9efc90426..a1a5dac3d1 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -737,9 +737,19 @@ App::init() $validator = new Hostname($clients); if ($validator->isValid($origin)) { $refDomainOrigin = $origin; - } else { + } elseif (!empty($origin)) { // Auto-allow domains with linked rule - $rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($origin))); + if (System::getEnv('_APP_RULES_FORMAT') === 'md5') { + $rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($origin))); + } else { + $rule = Authorization::skip( + fn () => $dbForPlatform->find('rules', [ + Query::equal('domain', [$origin]), + Query::limit(1) + ]) + )[0] ?? new Document(); + } + if (!$rule->isEmpty() && $rule->getAttribute('projectInternalId') === $project->getInternalId()) { $refDomainOrigin = $origin; } From 262b152cd90cc22b80171f0ce236d85436688fb6 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Tue, 18 Feb 2025 12:12:12 +0530 Subject: [PATCH 13/15] Add more rule tests --- .../Modules/Proxy/Http/Rules/Create.php | 73 +++++++------------ tests/e2e/General/UsageTest.php | 17 +---- .../e2e/Services/Functions/FunctionsBase.php | 2 +- .../Functions/FunctionsCustomServerTest.php | 10 +-- .../Projects/ProjectsCustomServerTest.php | 36 +++++++++ tests/e2e/Services/Sites/SitesBase.php | 2 +- .../Services/Sites/SitesCustomServerTest.php | 15 ++-- 7 files changed, 78 insertions(+), 77 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php index 4c115d8d4e..b989310a3d 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Create.php @@ -13,7 +13,6 @@ use Appwrite\Utopia\Response; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; -use Utopia\Database\Query; use Utopia\Database\Validator\UID; use Utopia\Domains\Domain; use Utopia\Platform\Action; @@ -74,54 +73,28 @@ class Create extends Action public function action(string $domain, string $resourceType, string $resourceId, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject) { $mainDomain = System::getEnv('_APP_DOMAIN', ''); - if ($domain === $mainDomain) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'You cannot assign your main domain to specific resource. Please use subdomain or a different domain.'); - } - $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); - if ($domain === $functionsDomain || $domain === $sitesDomain) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'You cannot assign your functions or sites domain to a specific resource. Please use a different domain.'); - } + $deniedDomains = [ + $mainDomain, + $sitesDomain, + $functionsDomain, + 'localhost', + APP_HOSTNAME_INTERNAL, + ]; - if ($domain === 'localhost' || $domain === APP_HOSTNAME_INTERNAL) { + if (in_array($domain, $deniedDomains, true)) { throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please pick another one.'); } - // TODO: @christyjacob remove once we migrate the rules in 1.7.x - if (System::getEnv('_APP_RULES_FORMAT') === 'md5') { - $document = $dbForPlatform->getDocument('rules', md5($domain)); - } else { - $document = $dbForPlatform->findOne('rules', [ - Query::equal('domain', [$domain]), - ]); - } - - if (!$document->isEmpty()) { - if ($document->getAttribute('projectId') === $project->getId()) { - $resourceType = $document->getAttribute('resourceType'); - $resourceId = $document->getAttribute('resourceId'); - $message = "Domain already assigned to another resource."; - if (!empty($resourceId)) { - $message .= " with ID '{$resourceId}'"; - } - - $message .= '.'; - } else { - $message = 'Domain already assigned to different project.'; - } - - throw new Exception(Exception::RULE_ALREADY_EXISTS, $message); - } - $resourceInternalId = ''; switch ($resourceType) { case 'function': case 'site': if (empty($resourceId)) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, '$resourceId cannot be empty for resourceType "' . $resourceType . '".'); + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'resourceId cannot be empty for resourceType "' . $resourceType . '".'); } $expectedDomain = ($resourceType === 'function') ? $functionsDomain : $sitesDomain; @@ -154,16 +127,24 @@ class Create extends Action // TODO: @christyjacob remove once we migrate the rules in 1.7.x $ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique(); - $rule = new Document([ - '$id' => $ruleId, - 'projectId' => $project->getId(), - 'projectInternalId' => $project->getInternalId(), - 'domain' => $domain->get(), - 'resourceType' => $resourceType, - 'resourceId' => $resourceId, - 'resourceInternalId' => $resourceInternalId, - 'certificateId' => '', - ]); + try { + $rule = new Document([ + '$id' => $ruleId, + 'projectId' => $project->getId(), + 'projectInternalId' => $project->getInternalId(), + 'domain' => $domain->get(), + 'resourceType' => $resourceType, + 'resourceId' => $resourceId, + 'resourceInternalId' => $resourceInternalId, + 'certificateId' => '', + ]); + } catch (\Throwable $e) { + if ($e->getCode() === Exception::DOCUMENT_ALREADY_EXISTS) { + throw new Exception(Exception::RULE_ALREADY_EXISTS); + } + + throw new Exception(Exception::GENERAL_SERVER_ERROR, 'An unexpected error occurred: ' . $e->getMessage()); + } $status = 'created'; diff --git a/tests/e2e/General/UsageTest.php b/tests/e2e/General/UsageTest.php index 27fdd0b8fa..b053e4b9cd 100644 --- a/tests/e2e/General/UsageTest.php +++ b/tests/e2e/General/UsageTest.php @@ -1110,22 +1110,7 @@ class UsageTest extends Scope $this->assertNotEmpty($rule['body']['$id']); $this->assertNotEmpty($rule['body']['domain']); - $rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ - Query::equal('resourceId', [$functionId])->toString(), - Query::equal('resourceType', ['function'])->toString(), - ], - ]); - - $this->assertEquals(200, $rules['headers']['status-code']); - $this->assertEquals(1, $rules['body']['total']); - $this->assertCount(1, $rules['body']['rules']); - $this->assertNotEmpty($rules['body']['rules'][0]['domain']); - - $domain = $rules['body']['rules'][0]['domain']; + $domain = $rule['body']['domain']; $response = $this->client->call( Client::METHOD_GET, diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index bbdcbf3d67..ce5df8b746 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -259,7 +259,7 @@ trait FunctionsBase return $function; } - protected function createFunctionDomain(string $functionId, string $subdomain = ''): string + protected function setupFunctionDomain(string $functionId, string $subdomain = ''): string { $subdomain = $subdomain ? $subdomain : ID::unique(); $rule = $this->client->call(Client::METHOD_POST, '/proxy/rules', array_merge([ diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index d3d7c0dc18..f941c0658a 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -1654,9 +1654,7 @@ class FunctionsCustomServerTest extends Scope 'execute' => ['any'] ]); - $this->createFunctionDomain($functionId); - - $domain = $this->getFunctionDomain($functionId); + $domain = $this->setupFunctionDomain($functionId); $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', @@ -1717,7 +1715,7 @@ class FunctionsCustomServerTest extends Scope 'execute' => ['any'] ]); - $domain = $this->createFunctionDomain($functionId); + $domain = $this->setupFunctionDomain($functionId); $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', @@ -1752,7 +1750,7 @@ class FunctionsCustomServerTest extends Scope 'execute' => ['any'] ]); - $domain = $this->createFunctionDomain($functionId); + $domain = $this->setupFunctionDomain($functionId); $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', @@ -1872,7 +1870,7 @@ class FunctionsCustomServerTest extends Scope $functionId = $function['body']['$id'] ?? ''; - $domain = $this->createFunctionDomain($functionId); + $domain = $this->setupFunctionDomain($functionId); $this->setupDeployment($functionId, [ 'code' => $this->packageFunction('node'), diff --git a/tests/e2e/Services/Projects/ProjectsCustomServerTest.php b/tests/e2e/Services/Projects/ProjectsCustomServerTest.php index 25a5a93daa..60ae7e0bbb 100644 --- a/tests/e2e/Services/Projects/ProjectsCustomServerTest.php +++ b/tests/e2e/Services/Projects/ProjectsCustomServerTest.php @@ -31,6 +31,21 @@ class ProjectsCustomServerTest extends Scope $this->assertEquals(201, $response['headers']['status-code']); + $response = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [ + 'resourceType' => 'api', + 'domain' => 'abc.test.io', + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // duplicate rule + $response2 = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [ + 'resourceType' => 'api', + 'domain' => 'abc.test.io', + ]); + + $this->assertEquals(409, $response2['headers']['status-code']); + $response = $this->client->call(Client::METHOD_DELETE, '/proxy/rules/' . $response['body']['$id'], $headers); $this->assertEquals(204, $response['headers']['status-code']); @@ -69,5 +84,26 @@ class ProjectsCustomServerTest extends Scope ]); $this->assertEquals(400, $response['headers']['status-code']); + + $mainDomain = System::getEnv('_APP_DOMAIN', ''); + $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); + $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); + + $deniedDomains = [ + $mainDomain, + $sitesDomain, + $functionsDomain, + 'localhost', + APP_HOSTNAME_INTERNAL, + ]; + + foreach ($deniedDomains as $deniedDomain) { + $response = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [ + 'resourceType' => 'api', + 'domain' => $deniedDomain, + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + } } } diff --git a/tests/e2e/Services/Sites/SitesBase.php b/tests/e2e/Services/Sites/SitesBase.php index 72448898f5..4fcd34572d 100644 --- a/tests/e2e/Services/Sites/SitesBase.php +++ b/tests/e2e/Services/Sites/SitesBase.php @@ -269,7 +269,7 @@ trait SitesBase return $site; } - protected function createSiteDomain(string $siteId, string $subdomain = ''): string + protected function setupSiteDomain(string $siteId, string $subdomain = ''): string { $subdomain = $subdomain ? $subdomain : ID::unique(); $rule = $this->client->call(Client::METHOD_POST, '/proxy/rules', array_merge([ diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index 723b49d5a2..0ff6652ed5 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -79,7 +79,7 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($siteId); - $rule = $this->createSiteDomain($siteId, 'test-site'); + $rule = $this->setupSiteDomain($siteId); $response = $this->client->call(Client::METHOD_GET, '/console/resources', [ 'origin' => 'http://localhost', @@ -289,7 +289,7 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($siteId); - $domain = $this->createSiteDomain($siteId); + $domain = $this->setupSiteDomain($siteId); $secretVariable = $this->createVariable($siteId, [ 'key' => 'name', @@ -1245,7 +1245,7 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($site['body']['deploymentId']); }, 50000, 500); - $domain = $this->createSiteDomain($siteId); + $domain = $this->setupSiteDomain($siteId); $proxyClient = new Client(); $proxyClient->setEndpoint('http://' . $domain); @@ -1287,7 +1287,7 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($siteId); $subdomain = 'startup' . \uniqid(); - $domain = $this->createSiteDomain($siteId, $subdomain); + $domain = $this->setupSiteDomain($siteId, $subdomain); $deploymentId = $this->setupDeployment($siteId, [ 'code' => $this->packageSite('static'), @@ -1373,7 +1373,7 @@ class SitesCustomServerTest extends Scope $siteId = $site['body']['$id']; - $domain = $this->createSiteDomain($siteId, $subdomain); + $domain = $this->setupSiteDomain($siteId, $subdomain); $this->assertNotEmpty($domain); @@ -1396,7 +1396,7 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($siteId); - $domain = $this->createSiteDomain($siteId); + $domain = $this->setupSiteDomain($siteId); $deploymentId = $this->setupDeployment($siteId, [ 'code' => $this->packageSite('static'), @@ -1461,7 +1461,8 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($siteId); - $domain = $this->createSiteDomain($siteId, $subdomain); + $this->setupSiteDomain($siteId, $subdomain); + $domain = $this->getSiteDomain($siteId); $this->assertNotEmpty($domain); From c24cebf6ce32566c00a307489bb64bc20dc7884c Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Tue, 18 Feb 2025 12:50:09 +0530 Subject: [PATCH 14/15] Fix site tests --- tests/e2e/Services/Sites/SitesCustomServerTest.php | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index 0ff6652ed5..3fde0ba175 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -11,7 +11,6 @@ use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; use Utopia\Database\Validator\Datetime as DatetimeValidator; -use Utopia\System\System; class SitesCustomServerTest extends Scope { @@ -1320,17 +1319,10 @@ class SitesCustomServerTest extends Scope 'fallbackFile' => '', ]); - $rule = $this->client->call(Client::METHOD_POST, '/proxy/rules', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'domain' => $subdomain . System::getEnv('_APP_DOMAIN_SITES', ''), - 'resourceType' => 'site', - 'resourceId' => $siteId, - ]); + $domain = $this->setupSiteDomain($site['body']['$id'], $subdomain); - $this->assertEquals(409, $rule['headers']['status-code']); - $this->assertStringContainsString("Domain already assigned to another resource.", $rule['body']['message']); + $this->assertEquals(409, $domain['headers']['status-code']); + $this->assertStringContainsString("Domain already assigned to another resource.", $domain['body']['message']); $this->cleanupSite($siteId); From db8010f7c6c6cbbd020ce8ec74b9fe31d47a533b Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Tue, 18 Feb 2025 13:03:57 +0530 Subject: [PATCH 15/15] Fix site tests --- .../Services/Sites/SitesCustomServerTest.php | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index 3fde0ba175..eefa0ddcbb 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -11,6 +11,7 @@ use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; use Utopia\Database\Validator\Datetime as DatetimeValidator; +use Utopia\System\System; class SitesCustomServerTest extends Scope { @@ -1307,7 +1308,7 @@ class SitesCustomServerTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertStringNotContainsString("This domain is not connected to any Appwrite resource yet", $response['body']); - $site = $this->createSite([ + $site2 = $this->createSite([ 'siteId' => ID::unique(), 'name' => 'Startup 2 site', 'framework' => 'other', @@ -1319,10 +1320,19 @@ class SitesCustomServerTest extends Scope 'fallbackFile' => '', ]); - $domain = $this->setupSiteDomain($site['body']['$id'], $subdomain); + $siteId2 = $site2['body']['$id']; - $this->assertEquals(409, $domain['headers']['status-code']); - $this->assertStringContainsString("Domain already assigned to another resource.", $domain['body']['message']); + $rule = $this->client->call(Client::METHOD_POST, '/proxy/rules', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'domain' => $subdomain . '.' . System::getEnv('_APP_DOMAIN_SITES', ''), + 'resourceType' => 'site', + 'resourceId' => $siteId2, + ]); + + $this->assertEquals(409, $rule['headers']['status-code']); + $this->assertStringContainsString("Document with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.", $rule['body']['message']); $this->cleanupSite($siteId);