diff --git a/composer.lock b/composer.lock index de48072614..a6986d0816 100644 --- a/composer.lock +++ b/composer.lock @@ -3856,16 +3856,16 @@ }, { "name": "utopia-php/framework", - "version": "0.33.21", + "version": "0.33.22", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "eb0e82e90b8fa493f99b8d131bdd25173422c493" + "reference": "c01a815cb976c9255e045fc3bcc3f5fcf477e0bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/eb0e82e90b8fa493f99b8d131bdd25173422c493", - "reference": "eb0e82e90b8fa493f99b8d131bdd25173422c493", + "url": "https://api.github.com/repos/utopia-php/http/zipball/c01a815cb976c9255e045fc3bcc3f5fcf477e0bc", + "reference": "c01a815cb976c9255e045fc3bcc3f5fcf477e0bc", "shasum": "" }, "require": { @@ -3897,9 +3897,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.21" + "source": "https://github.com/utopia-php/http/tree/0.33.22" }, - "time": "2025-08-19T10:52:15+00:00" + "time": "2025-08-26T10:29:50+00:00" }, { "name": "utopia-php/image", @@ -8446,5 +8446,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/src/Appwrite/Platform/Modules/Console/Http/Resources/Get.php b/src/Appwrite/Platform/Modules/Console/Http/Resources/Get.php index 114a24ef22..4f197fcd79 100644 --- a/src/Appwrite/Platform/Modules/Console/Http/Resources/Get.php +++ b/src/Appwrite/Platform/Modules/Console/Http/Resources/Get.php @@ -11,9 +11,11 @@ use Appwrite\Utopia\Response; use Utopia\Database\Database; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; +use Utopia\Domains\Domain as Domain; use Utopia\Platform\Action; use Utopia\Platform\Scope\HTTP; -use Utopia\Validator\Domain; +use Utopia\System\System; +use Utopia\Validator\Domain as DomainValidator; use Utopia\Validator\Text; use Utopia\Validator\WhiteList; @@ -67,10 +69,57 @@ class Get extends Action Database $dbForPlatform ) { if ($type === 'rules') { - $validator = new Domain($value); + $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); + $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); + + $restrictions = []; + if (!empty($sitesDomain)) { + $domainLevel = \count(\explode('.', $sitesDomain)); + $restrictions[] = DomainValidator::createRestriction($sitesDomain, $domainLevel + 1, ['commit-', 'branch-']); + } + if (!empty($functionsDomain)) { + $domainLevel = \count(\explode('.', $functionsDomain)); + $restrictions[] = DomainValidator::createRestriction($functionsDomain, $domainLevel + 1); + } + $validator = new DomainValidator($restrictions); if (!$validator->isValid($value)) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $validator->getDescription()); + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); + } + + $deniedDomains = [ + 'localhost', + APP_HOSTNAME_INTERNAL + ]; + + $mainDomain = System::getEnv('_APP_DOMAIN', ''); + $deniedDomains[] = $mainDomain; + + if (!empty($sitesDomain)) { + $deniedDomains[] = $sitesDomain; + } + + if (!empty($functionsDomain)) { + $deniedDomains[] = $functionsDomain; + } + + $denyListDomains = System::getEnv('_APP_CUSTOM_DOMAIN_DENY_LIST', ''); + $denyListDomains = \array_map('trim', explode(',', $denyListDomains)); + foreach ($denyListDomains as $denyListDomain) { + if (empty($denyListDomain)) { + continue; + } + $deniedDomains[] = $denyListDomain; + } + + if (\in_array($value, $deniedDomains)) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); + } + + try { + $domain = new Domain($value); + } catch (\Throwable) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.'); } $document = Authorization::skip(fn () => $dbForPlatform->findOne('rules', [ diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php index 4efe8176f6..4cc8f48e7c 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php @@ -71,6 +71,24 @@ class Create extends Action public function action(string $domain, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform) { + $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); + $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); + + $restrictions = []; + if (!empty($sitesDomain)) { + $domainLevel = \count(\explode('.', $sitesDomain)); + $restrictions[] = ValidatorDomain::createRestriction($sitesDomain, $domainLevel + 1, ['commit-', 'branch-']); + } + if (!empty($functionsDomain)) { + $domainLevel = \count(\explode('.', $functionsDomain)); + $restrictions[] = ValidatorDomain::createRestriction($functionsDomain, $domainLevel + 1); + } + $validator = new ValidatorDomain($restrictions); + + if (!$validator->isValid($domain)) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); + } + $deniedDomains = [ 'localhost', APP_HOSTNAME_INTERNAL @@ -79,12 +97,10 @@ class Create extends Action $mainDomain = System::getEnv('_APP_DOMAIN', ''); $deniedDomains[] = $mainDomain; - $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); if (!empty($sitesDomain)) { $deniedDomains[] = $sitesDomain; } - $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); if (!empty($functionsDomain)) { $deniedDomains[] = $functionsDomain; } @@ -102,10 +118,6 @@ class Create extends Action throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); } - if (\str_starts_with($domain, 'commit-') || \str_starts_with($domain, 'branch-')) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); - } - try { $domain = new Domain($domain); } catch (\Throwable) { diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php index 1c8fe7b04d..5839e03e25 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php @@ -76,6 +76,24 @@ class Create extends Action public function action(string $domain, string $functionId, string $branch, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject) { + $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); + $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); + + $restrictions = []; + if (!empty($sitesDomain)) { + $domainLevel = \count(\explode('.', $sitesDomain)); + $restrictions[] = ValidatorDomain::createRestriction($sitesDomain, $domainLevel + 1, ['commit-', 'branch-']); + } + if (!empty($functionsDomain)) { + $domainLevel = \count(\explode('.', $functionsDomain)); + $restrictions[] = ValidatorDomain::createRestriction($functionsDomain, $domainLevel + 1); + } + $validator = new ValidatorDomain($restrictions); + + if (!$validator->isValid($domain)) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); + } + $deniedDomains = [ 'localhost', APP_HOSTNAME_INTERNAL @@ -84,12 +102,10 @@ class Create extends Action $mainDomain = System::getEnv('_APP_DOMAIN', ''); $deniedDomains[] = $mainDomain; - $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); if (!empty($sitesDomain)) { $deniedDomains[] = $sitesDomain; } - $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); if (!empty($functionsDomain)) { $deniedDomains[] = $functionsDomain; } @@ -107,10 +123,6 @@ class Create extends Action throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); } - if (\str_starts_with($domain, 'commit-') || \str_starts_with($domain, 'branch-')) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); - } - try { $domain = new Domain($domain); } catch (\Throwable) { diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php index 580d92bc74..1dfef8bcc7 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php @@ -79,6 +79,24 @@ class Create extends Action public function action(string $domain, string $url, int $statusCode, string $resourceId, string $resourceType, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject) { + $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); + $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); + + $restrictions = []; + if (!empty($sitesDomain)) { + $domainLevel = \count(\explode('.', $sitesDomain)); + $restrictions[] = ValidatorDomain::createRestriction($sitesDomain, $domainLevel + 1, ['commit-', 'branch-']); + } + if (!empty($functionsDomain)) { + $domainLevel = \count(\explode('.', $functionsDomain)); + $restrictions[] = ValidatorDomain::createRestriction($functionsDomain, $domainLevel + 1); + } + $validator = new ValidatorDomain($restrictions); + + if (!$validator->isValid($domain)) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); + } + $deniedDomains = [ 'localhost', APP_HOSTNAME_INTERNAL @@ -87,12 +105,10 @@ class Create extends Action $mainDomain = System::getEnv('_APP_DOMAIN', ''); $deniedDomains[] = $mainDomain; - $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); if (!empty($sitesDomain)) { $deniedDomains[] = $sitesDomain; } - $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); if (!empty($functionsDomain)) { $deniedDomains[] = $functionsDomain; } @@ -110,10 +126,6 @@ class Create extends Action throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); } - if (\str_starts_with($domain, 'commit-') || \str_starts_with($domain, 'branch-')) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); - } - try { $domain = new Domain($domain); } catch (\Throwable) { diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Site/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Site/Create.php index 7a5a1f4952..43cf09eaca 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Site/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Site/Create.php @@ -76,6 +76,24 @@ class Create extends Action public function action(string $domain, string $siteId, string $branch, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject) { + $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); + $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); + + $restrictions = []; + if (!empty($sitesDomain)) { + $domainLevel = \count(\explode('.', $sitesDomain)); + $restrictions[] = ValidatorDomain::createRestriction($sitesDomain, $domainLevel + 1, ['commit-', 'branch-']); + } + if (!empty($functionsDomain)) { + $domainLevel = \count(\explode('.', $functionsDomain)); + $restrictions[] = ValidatorDomain::createRestriction($functionsDomain, $domainLevel + 1); + } + $validator = new ValidatorDomain($restrictions); + + if (!$validator->isValid($domain)) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); + } + $deniedDomains = [ 'localhost', APP_HOSTNAME_INTERNAL @@ -84,12 +102,10 @@ class Create extends Action $mainDomain = System::getEnv('_APP_DOMAIN', ''); $deniedDomains[] = $mainDomain; - $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); if (!empty($sitesDomain)) { $deniedDomains[] = $sitesDomain; } - $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); if (!empty($functionsDomain)) { $deniedDomains[] = $functionsDomain; } @@ -107,10 +123,6 @@ class Create extends Action throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); } - if (\str_starts_with($domain, 'commit-') || \str_starts_with($domain, 'branch-')) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); - } - try { $domain = new Domain($domain); } catch (\Throwable) { diff --git a/tests/e2e/General/UsageTest.php b/tests/e2e/General/UsageTest.php index 6389258e3a..92c5f75504 100644 --- a/tests/e2e/General/UsageTest.php +++ b/tests/e2e/General/UsageTest.php @@ -1040,7 +1040,7 @@ class UsageTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'domain' => 'test-' . ID::unique() . System::getEnv('_APP_DOMAIN_FUNCTIONS'), + 'domain' => 'test-' . ID::unique() . '.' . System::getEnv('_APP_DOMAIN_FUNCTIONS'), 'functionId' => $functionId, ], ); diff --git a/tests/e2e/Services/Proxy/ProxyCustomServerTest.php b/tests/e2e/Services/Proxy/ProxyCustomServerTest.php index da23b9ab7c..5a1cd1dea6 100644 --- a/tests/e2e/Services/Proxy/ProxyCustomServerTest.php +++ b/tests/e2e/Services/Proxy/ProxyCustomServerTest.php @@ -70,12 +70,29 @@ class ProxyCustomServerTest extends Scope $this->assertNotEmpty($siteId); $this->assertNotEmpty($deploymentId); + $rule = $this->createSiteRule('commit-' . $domain, $siteId); + $this->assertEquals(201, $rule['headers']['status-code']); + $this->cleanupRule($rule['body']['$id']); + + $rule = $this->createSiteRule('branch-' . $domain, $siteId); + $this->assertEquals(201, $rule['headers']['status-code']); + $this->cleanupRule($rule['body']['$id']); + + $rule = $this->createSiteRule('anything-' . $domain, $siteId); + $this->assertEquals(201, $rule['headers']['status-code']); + $this->cleanupRule($rule['body']['$id']); + + $domain = \uniqid() . '-vcs.' . System::getEnv('_APP_DOMAIN_SITES', ''); + $rule = $this->createSiteRule('commit-' . $domain, $siteId); $this->assertEquals(400, $rule['headers']['status-code']); $rule = $this->createSiteRule('branch-' . $domain, $siteId); $this->assertEquals(400, $rule['headers']['status-code']); + $rule = $this->createSiteRule('subdomain.anything-' . $domain, $siteId); + $this->assertEquals(400, $rule['headers']['status-code']); + $rule = $this->createSiteRule('anything-' . $domain, $siteId); $this->assertEquals(201, $rule['headers']['status-code']); $this->cleanupRule($rule['body']['$id']);