mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01305007a2 | ||
|
|
7f00e497f5 | ||
|
|
6d4e03e6fc | ||
|
|
6f50b4712f | ||
|
|
5ed0721c36 | ||
|
|
1093251990 | ||
|
|
e780c5fa4a | ||
|
|
bf0fb14631 | ||
|
|
16573dcab7 | ||
|
|
2809b5f31d | ||
|
|
0feae3ba27 | ||
|
|
a3cf19410c | ||
|
|
47edd6c0c3 | ||
|
|
79d7b124e5 | ||
|
|
f2a7f7f132 | ||
|
|
de12173fca | ||
|
|
1a084d7acc | ||
|
|
f722f5fd35 | ||
|
|
6ef76472e5 | ||
|
|
b6f9980e02 | ||
|
|
80b6347edf | ||
|
|
0c5df2da5a | ||
|
|
677c2952e8 | ||
|
|
8be36a3664 | ||
|
|
2be9b50815 | ||
|
|
3441903e7e | ||
|
|
ecf163b197 | ||
|
|
f48a620bd8 | ||
|
|
a934ba24e8 | ||
|
|
d939415c3a | ||
|
|
73da590d65 |
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Domain\Validator;
|
||||
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Domain;
|
||||
|
||||
class AppwriteDomain extends Domain
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Value must be a valid one-level subdomain';
|
||||
}
|
||||
|
||||
public function isValid($value): bool
|
||||
{
|
||||
if (empty($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reject domains with leading/trailing whitespace
|
||||
if (!is_string($value) || $value !== trim($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (\filter_var($value, FILTER_VALIDATE_DOMAIN) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (preg_match('/^https?:\/\//', $value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (str_starts_with($value, '.') || str_ends_with($value, '.') || str_contains($value, '..')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$domain = strtolower($value);
|
||||
|
||||
$parts = explode('.', $domain);
|
||||
$firstLabel = $parts[0];
|
||||
if (str_starts_with($firstLabel, 'commit-') || str_starts_with($firstLabel, 'branch-')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$managedDomains = [
|
||||
System::getEnv('_APP_DOMAIN_FUNCTIONS'),
|
||||
System::getEnv('_APP_DOMAIN_SITES')
|
||||
];
|
||||
|
||||
foreach ($managedDomains as $managedDomain) {
|
||||
if (empty($managedDomain)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Block exact match
|
||||
if ($domain === $managedDomain) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate subdomains - reject sub-subdomains
|
||||
if (str_ends_with($domain, '.' . $managedDomain)) {
|
||||
$subdomain = substr($domain, 0, -strlen('.' . $managedDomain));
|
||||
|
||||
// Reject sub-subdomains (contains dots) or invalid subdomain format
|
||||
if (
|
||||
$subdomain === '' ||
|
||||
strpos($subdomain, '.') !== false ||
|
||||
strlen($subdomain) > 63 ||
|
||||
!preg_match('/^[a-z0-9-]+$/i', $subdomain) ||
|
||||
str_starts_with($subdomain, '-') ||
|
||||
str_ends_with($subdomain, '-')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Console\Http\Resources;
|
||||
|
||||
use Appwrite\Domain\Validator\AppwriteDomain;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
@@ -13,7 +14,6 @@ use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Domain;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
@@ -67,10 +67,10 @@ class Get extends Action
|
||||
Database $dbForPlatform
|
||||
) {
|
||||
if ($type === 'rules') {
|
||||
$validator = new Domain($value);
|
||||
$appwriteDomainValidator = new AppwriteDomain();
|
||||
|
||||
if (!$validator->isValid($value)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $validator->getDescription());
|
||||
if (!$appwriteDomainValidator->isValid($value)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Value must be a valid domain name or a valid Appwrite subdomain.');
|
||||
}
|
||||
|
||||
$document = Authorization::skip(fn () => $dbForPlatform->findOne('rules', [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Proxy\Http\Rules\API;
|
||||
|
||||
use Appwrite\Domain\Validator\AppwriteDomain;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
@@ -19,7 +20,6 @@ use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\AnyOf;
|
||||
use Utopia\Validator\Domain as ValidatorDomain;
|
||||
use Utopia\Validator\IP;
|
||||
|
||||
class Create extends Action
|
||||
@@ -60,7 +60,7 @@ class Create extends Action
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'userId:{userId}, url:{url}')
|
||||
->label('abuse-time', 60)
|
||||
->param('domain', null, new ValidatorDomain(), 'Domain name.')
|
||||
->param('domain', null, new AppwriteDomain(), 'Domain name.')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('queueForCertificates')
|
||||
@@ -102,10 +102,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) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Function;
|
||||
|
||||
use Appwrite\Domain\Validator\AppwriteDomain;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
@@ -20,7 +21,6 @@ use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\AnyOf;
|
||||
use Utopia\Validator\Domain as ValidatorDomain;
|
||||
use Utopia\Validator\IP;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
@@ -62,7 +62,7 @@ class Create extends Action
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'userId:{userId}, url:{url}')
|
||||
->label('abuse-time', 60)
|
||||
->param('domain', null, new ValidatorDomain(), 'Domain name.')
|
||||
->param('domain', null, new AppwriteDomain(), 'Domain name.')
|
||||
->param('functionId', '', new UID(), 'ID of function to be executed.')
|
||||
->param('branch', '', new Text(255, 0), 'Name of VCS branch to deploy changes automatically', true)
|
||||
->inject('response')
|
||||
@@ -107,10 +107,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) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Redirect;
|
||||
|
||||
use Appwrite\Domain\Validator\AppwriteDomain;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
@@ -20,7 +21,6 @@ use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\AnyOf;
|
||||
use Utopia\Validator\Domain as ValidatorDomain;
|
||||
use Utopia\Validator\IP;
|
||||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
@@ -63,7 +63,7 @@ class Create extends Action
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'userId:{userId}, url:{url}')
|
||||
->label('abuse-time', 60)
|
||||
->param('domain', null, new ValidatorDomain(), 'Domain name.')
|
||||
->param('domain', null, new AppwriteDomain(), 'Domain name.')
|
||||
->param('url', null, new URL(), 'Target URL of redirection')
|
||||
->param('statusCode', null, new WhiteList([301, 302, 307, 308]), 'Status code of redirection')
|
||||
->param('resourceId', '', new UID(), 'ID of parent resource.')
|
||||
@@ -110,10 +110,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) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Site;
|
||||
|
||||
use Appwrite\Domain\Validator\AppwriteDomain;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
@@ -20,7 +21,6 @@ use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\AnyOf;
|
||||
use Utopia\Validator\Domain as ValidatorDomain;
|
||||
use Utopia\Validator\IP;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
@@ -62,7 +62,7 @@ class Create extends Action
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'userId:{userId}, url:{url}')
|
||||
->label('abuse-time', 60)
|
||||
->param('domain', null, new ValidatorDomain(), 'Domain name.')
|
||||
->param('domain', null, new AppwriteDomain(), 'Domain name.')
|
||||
->param('siteId', '', new UID(), 'ID of site to be executed.')
|
||||
->param('branch', '', new Text(255, 0), 'Name of VCS branch to deploy changes automatically', true)
|
||||
->inject('response')
|
||||
@@ -107,9 +107,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);
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Domain\Validators;
|
||||
|
||||
use Appwrite\Domain\Validator\AppwriteDomain;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class AppwriteDomainTest extends TestCase
|
||||
{
|
||||
protected ?AppwriteDomain $validator = null;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->validator = new AppwriteDomain();
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
$this->validator = null;
|
||||
}
|
||||
|
||||
public function testIsValid(): void
|
||||
{
|
||||
$sitesDomain = \Utopia\System\System::getEnv('_APP_DOMAIN_SITES');
|
||||
$functionsDomain = \Utopia\System\System::getEnv('_APP_DOMAIN_FUNCTIONS');
|
||||
|
||||
if (!empty($sitesDomain)) {
|
||||
$this->assertEquals(true, $this->validator->isValid('api.' . $sitesDomain));
|
||||
$this->assertEquals(true, $this->validator->isValid('test.' . $sitesDomain));
|
||||
$this->assertEquals(true, $this->validator->isValid('myapp.' . $sitesDomain));
|
||||
$this->assertEquals(true, $this->validator->isValid('staging.' . $sitesDomain));
|
||||
$this->assertEquals(true, $this->validator->isValid('prod.' . $sitesDomain));
|
||||
$this->assertEquals(true, $this->validator->isValid('app123.' . $sitesDomain));
|
||||
$this->assertEquals(true, $this->validator->isValid('test-app.' . $sitesDomain));
|
||||
$this->assertEquals(true, $this->validator->isValid('my-awesome-app.' . $sitesDomain));
|
||||
$this->assertEquals(true, $this->validator->isValid('a.' . $sitesDomain));
|
||||
$this->assertEquals(true, $this->validator->isValid('x1.' . $sitesDomain));
|
||||
|
||||
$this->assertEquals(false, $this->validator->isValid('api.dev.' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('foo.bar.' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('app.staging.test.' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('sub.domain.example.' . $sitesDomain));
|
||||
|
||||
$this->assertEquals(true, $this->validator->isValid('API.' . strtoupper($sitesDomain)));
|
||||
$this->assertEquals(true, $this->validator->isValid('Test.' . ucfirst($sitesDomain)));
|
||||
$this->assertEquals(true, $this->validator->isValid('MyApp.' . $sitesDomain));
|
||||
|
||||
$this->assertEquals(false, $this->validator->isValid('my app.' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('test .' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid(' api.' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('api.' . $sitesDomain . ' '));
|
||||
$this->assertEquals(false, $this->validator->isValid('app@test.' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('app#test.' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('app$test.' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('app%test.' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('app_test.' . $sitesDomain));
|
||||
|
||||
$this->assertEquals(false, $this->validator->isValid('.api.' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('api.' . $sitesDomain . '.'));
|
||||
$this->assertEquals(false, $this->validator->isValid('api..' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('.' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid($sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('.' . $sitesDomain . '.'));
|
||||
$this->assertEquals(false, $this->validator->isValid('..' . $sitesDomain));
|
||||
|
||||
$this->assertEquals(false, $this->validator->isValid('commit-api.' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('commit-test.' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('commit-123.' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('branch-api.' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('branch-test.' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('branch-123.' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('COMMIT-api.' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('BRANCH-test.' . $sitesDomain));
|
||||
|
||||
$this->assertEquals(true, $this->validator->isValid('commitment.' . $sitesDomain));
|
||||
$this->assertEquals(true, $this->validator->isValid('branching.' . $sitesDomain));
|
||||
$this->assertEquals(true, $this->validator->isValid('my-commit.' . $sitesDomain));
|
||||
$this->assertEquals(true, $this->validator->isValid('my-branch.' . $sitesDomain));
|
||||
$this->assertEquals(true, $this->validator->isValid('pre-commit.' . $sitesDomain));
|
||||
$this->assertEquals(true, $this->validator->isValid('post-branch.' . $sitesDomain));
|
||||
|
||||
$this->assertEquals(false, $this->validator->isValid('.api.' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('api..' . $sitesDomain));
|
||||
}
|
||||
|
||||
if (!empty($functionsDomain)) {
|
||||
$this->assertEquals(true, $this->validator->isValid('api.' . $functionsDomain));
|
||||
$this->assertEquals(true, $this->validator->isValid('test.' . $functionsDomain));
|
||||
$this->assertEquals(true, $this->validator->isValid('myapp.' . $functionsDomain));
|
||||
|
||||
$this->assertEquals(false, $this->validator->isValid('api.dev.' . $functionsDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('foo.bar.' . $functionsDomain));
|
||||
}
|
||||
|
||||
$this->assertEquals(true, $this->validator->isValid('example.com'));
|
||||
$this->assertEquals(true, $this->validator->isValid('api.example.com'));
|
||||
$this->assertEquals(true, $this->validator->isValid('test.google.com'));
|
||||
$this->assertEquals(true, $this->validator->isValid('app.github.io'));
|
||||
$this->assertEquals(true, $this->validator->isValid('myapp.herokuapp.com'));
|
||||
$this->assertEquals(true, $this->validator->isValid('sub.domain.example.com'));
|
||||
|
||||
// Invalid subdomain formats
|
||||
$this->assertEquals(false, $this->validator->isValid('-api.' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('api-.' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('-test-.' . $sitesDomain));
|
||||
$this->assertEquals(false, $this->validator->isValid('-.' . $sitesDomain));
|
||||
|
||||
// Too long subdomain (over 63 characters)
|
||||
$longSubdomain = str_repeat('a', 64) . '.' . $sitesDomain;
|
||||
$this->assertEquals(false, $this->validator->isValid($longSubdomain));
|
||||
|
||||
// Exactly 63 characters should be valid
|
||||
$maxLengthSubdomain = str_repeat('a', 63) . '.' . $sitesDomain;
|
||||
$this->assertEquals(true, $this->validator->isValid($maxLengthSubdomain));
|
||||
|
||||
// Single character subdomain should be valid
|
||||
$this->assertEquals(true, $this->validator->isValid('a.' . $sitesDomain));
|
||||
|
||||
// Numbers in subdomain
|
||||
$this->assertEquals(true, $this->validator->isValid('123.' . $sitesDomain));
|
||||
$this->assertEquals(true, $this->validator->isValid('api123.' . $sitesDomain));
|
||||
$this->assertEquals(true, $this->validator->isValid('123api.' . $sitesDomain));
|
||||
|
||||
// Mixed case with hyphens
|
||||
$this->assertEquals(true, $this->validator->isValid('My-Test-App.' . $sitesDomain));
|
||||
$this->assertEquals(true, $this->validator->isValid('app-v2.' . $sitesDomain));
|
||||
}
|
||||
|
||||
public function testGetType(): void
|
||||
{
|
||||
$this->assertEquals('string', $this->validator->getType());
|
||||
}
|
||||
|
||||
public function testIsArray(): void
|
||||
{
|
||||
$this->assertEquals(false, $this->validator->isArray());
|
||||
}
|
||||
|
||||
public function testGetDescription(): void
|
||||
{
|
||||
$description = $this->validator->getDescription();
|
||||
$this->assertIsString($description);
|
||||
$this->assertNotEmpty($description);
|
||||
$this->assertStringContainsString('one-level subdomain', $description);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user