Merge branch '1.8.x' into allow-head-requests

This commit is contained in:
Chirag Aggarwal
2025-08-27 16:45:30 +07:00
committed by GitHub
99 changed files with 2069 additions and 610 deletions
@@ -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,59 @@ 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)) {
// Ensure site domains are exactly 1 subdomain, and dont start with reserved prefix
$domainLevel = \count(\explode('.', $sitesDomain));
$restrictions[] = DomainValidator::createRestriction($sitesDomain, $domainLevel + 1, ['commit-', 'branch-']);
}
if (!empty($functionsDomain)) {
// Ensure function domains are exactly 1 subdomain
$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', [
@@ -29,7 +29,7 @@ abstract class Action extends AppwriteAction
// Use the same helper method to ensure consistency
$contextId = '$' . $this->getCollectionsEventsContext() . 'Id';
$this->removableAttributes = ['$databaseId', $contextId];
$this->removableAttributes = ['$databaseId', $contextId, '$sequence'];
return parent::setHttpPath($path);
}
@@ -127,6 +127,9 @@ class Update extends Action
}
}
// Remove sequence if set
unset($document['$sequence']);
$documents = [];
try {
@@ -351,6 +351,9 @@ class Create extends Action
}
}
// Remove sequence if set
unset($document['$sequence']);
// Assign a unique ID if needed, otherwise use the provided ID.
$document['$id'] = $sourceId === 'unique()' ? ID::unique() : $sourceId;
@@ -159,6 +159,9 @@ class Update extends Action
$permissions = $document->getPermissions() ?? [];
}
// Remove sequence if set
unset($document['$sequence']);
$data['$id'] = $documentId;
$data['$permissions'] = $permissions;
$newDocument = new Document($data);
@@ -32,7 +32,7 @@ class Create extends BooleanCreate
->desc('Create boolean column')
->groups(['api', 'database', 'schema'])
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'column.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
@@ -33,7 +33,7 @@ class Update extends BooleanUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/boolean/:key')
->desc('Update boolean column')
->groups(['api', 'database', 'schema'])
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -33,7 +33,7 @@ class Create extends DatetimeCreate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/datetime')
->desc('Create datetime column')
->groups(['api', 'database'])
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
@@ -35,7 +35,7 @@ class Update extends DatetimeUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/datetime/:key')
->desc('Update dateTime column')
->groups(['api', 'database', 'schema'])
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -32,7 +32,7 @@ class Delete extends AttributesDelete
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/:key')
->desc('Delete column')
->groups(['api', 'database', 'schema'])
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.delete')
@@ -32,7 +32,7 @@ class Create extends EmailCreate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/email')
->desc('Create email column')
->groups(['api', 'database', 'schema'])
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
@@ -34,7 +34,7 @@ class Update extends EmailUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/email/:key')
->desc('Update email column')
->groups(['api', 'database', 'schema'])
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -34,7 +34,7 @@ class Create extends EnumCreate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/enum')
->desc('Create enum column')
->groups(['api', 'database', 'schema'])
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
@@ -36,7 +36,7 @@ class Update extends EnumUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/enum/:key')
->desc('Update enum column')
->groups(['api', 'database', 'schema'])
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -32,7 +32,7 @@ class Create extends FloatCreate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/float')
->desc('Create float column')
->groups(['api', 'database', 'schema'])
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
@@ -34,7 +34,7 @@ class Update extends FloatUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/float/:key')
->desc('Update float column')
->groups(['api', 'database', 'schema'])
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -41,7 +41,7 @@ class Get extends AttributesGet
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/:key')
->desc('Get column')
->groups(['api', 'database'])
->label('scope', 'tables.read')
->label('scope', ['tables.read', 'collections.read'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
@@ -32,7 +32,7 @@ class Create extends IPCreate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/ip')
->desc('Create IP address column')
->groups(['api', 'database', 'schema'])
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
@@ -34,7 +34,7 @@ class Update extends IPUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/ip/:key')
->desc('Update IP address column')
->groups(['api', 'database', 'schema'])
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -32,7 +32,7 @@ class Create extends IntegerCreate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/integer')
->desc('Create integer column')
->groups(['api', 'database', 'schema'])
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
@@ -34,7 +34,7 @@ class Update extends IntegerUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/integer/:key')
->desc('Update integer column')
->groups(['api', 'database', 'schema'])
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -33,7 +33,7 @@ class Create extends RelationshipCreate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/relationship')
->desc('Create relationship column')
->groups(['api', 'database'])
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
@@ -33,7 +33,7 @@ class Update extends RelationshipUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/:key/relationship')
->desc('Update relationship column')
->groups(['api', 'database', 'schema'])
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -34,7 +34,7 @@ class Create extends StringCreate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/string')
->desc('Create string column')
->groups(['api', 'database', 'schema'])
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
@@ -36,7 +36,7 @@ class Update extends StringUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/string/:key')
->desc('Update string column')
->groups(['api', 'database', 'schema'])
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -32,7 +32,7 @@ class Create extends URLCreate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/url')
->desc('Create URL column')
->groups(['api', 'database', 'schema'])
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
@@ -34,7 +34,7 @@ class Update extends URLUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/url/:key')
->desc('Update URL column')
->groups(['api', 'database', 'schema'])
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -30,7 +30,7 @@ class XList extends AttributesXList
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns')
->desc('List columns')
->groups(['api', 'database'])
->label('scope', 'tables.read')
->label('scope', ['tables.read', 'collections.read'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
@@ -35,7 +35,7 @@ class Create extends CollectionCreate
->desc('Create table')
->groups(['api', 'database'])
->label('event', 'databases.[databaseId].tables.[tableId].create')
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'table.create')
->label('audits.resource', 'database/{request.databaseId}/table/{response.$id}')
@@ -30,7 +30,7 @@ class Delete extends CollectionDelete
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId')
->desc('Delete table')
->groups(['api', 'database', 'schema'])
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].delete')
->label('audits.event', 'table.delete')
@@ -30,7 +30,7 @@ class Get extends CollectionGet
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId')
->desc('Get table')
->groups(['api', 'database'])
->label('scope', 'tables.read')
->label('scope', ['tables.read', 'collections.read'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
@@ -37,7 +37,7 @@ class Create extends IndexCreate
->desc('Create index')
->groups(['api', 'database'])
->label('event', 'databases.[databaseId].tables.[tableId].indexes.[indexId].create')
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'index.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
@@ -35,7 +35,7 @@ class Delete extends IndexDelete
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/indexes/:key')
->desc('Delete index')
->groups(['api', 'database'])
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].indexes.[indexId].update')
->label('audits.event', 'index.delete')
@@ -31,7 +31,7 @@ class Get extends IndexGet
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/indexes/:key')
->desc('Get index')
->groups(['api', 'database'])
->label('scope', 'tables.read')
->label('scope', ['tables.read', 'collections.read'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
@@ -31,7 +31,7 @@ class XList extends IndexXList
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/indexes')
->desc('List indexes')
->groups(['api', 'database'])
->label('scope', 'tables.read')
->label('scope', ['tables.read', 'collections.read'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
@@ -27,7 +27,7 @@ class XList extends CollectionLogXList
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/logs')
->desc('List table logs')
->groups(['api', 'database'])
->label('scope', 'tables.read')
->label('scope', ['tables.read', 'collections.read'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
@@ -32,7 +32,7 @@ class Delete extends DocumentsDelete
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/rows')
->desc('Delete rows')
->groups(['api', 'database'])
->label('scope', 'rows.write')
->label('scope', ['rows.write', 'documents.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'rows.delete')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
@@ -33,7 +33,7 @@ class Update extends DocumentsUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/rows')
->desc('Update rows')
->groups(['api', 'database'])
->label('scope', 'rows.write')
->label('scope', ['rows.write', 'documents.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'rows.update')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
@@ -32,7 +32,7 @@ class Upsert extends DocumentsUpsert
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/rows')
->desc('Create or update rows')
->groups(['api', 'database'])
->label('scope', 'rows.write')
->label('scope', ['rows.write', 'documents.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'row.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
@@ -33,7 +33,7 @@ class Decrement extends DecrementDocumentAttribute
->desc('Decrement row column')
->groups(['api', 'database'])
->label('event', 'databases.[databaseId].tables.[tableId].rows.[rowId].update')
->label('scope', 'rows.write')
->label('scope', ['rows.write', 'documents.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'rows.update')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
@@ -33,7 +33,7 @@ class Increment extends IncrementDocumentAttribute
->desc('Increment row column')
->groups(['api', 'database'])
->label('event', 'databases.[databaseId].tables.[tableId].rows.[rowId].update')
->label('scope', 'rows.write')
->label('scope', ['rows.write', 'documents.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'rows.update')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
@@ -41,7 +41,7 @@ class Create extends DocumentCreate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/rows')
->desc('Create row')
->groups(['api', 'database'])
->label('scope', 'rows.write')
->label('scope', ['rows.write', 'documents.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'row.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
@@ -36,7 +36,7 @@ class Delete extends DocumentDelete
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/rows/:rowId')
->desc('Delete row')
->groups(['api', 'database'])
->label('scope', 'rows.write')
->label('scope', ['rows.write', 'documents.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].rows.[rowId].delete')
->label('audits.event', 'row.delete')
@@ -32,7 +32,7 @@ class Get extends DocumentGet
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/rows/:rowId')
->desc('Get row')
->groups(['api', 'database'])
->label('scope', 'rows.read')
->label('scope', ['rows.read', 'documents.read'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
@@ -27,7 +27,7 @@ class XList extends DocumentLogXList
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/rows/:rowId/logs')
->desc('List row logs')
->groups(['api', 'database'])
->label('scope', 'rows.read')
->label('scope', ['rows.read', 'documents.read'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
@@ -34,7 +34,7 @@ class Update extends DocumentUpdate
->desc('Update row')
->groups(['api', 'database'])
->label('event', 'databases.[databaseId].tables.[tableId].rows.[rowId].update')
->label('scope', 'rows.write')
->label('scope', ['rows.write', 'documents.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'row.update')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}/row/{response.$id}')
@@ -34,7 +34,7 @@ class Upsert extends DocumentUpsert
->desc('Create or update a row')
->groups(['api', 'database'])
->label('event', 'databases.[databaseId].tables.[tableId].rows.[rowId].upsert')
->label('scope', 'rows.write')
->label('scope', ['rows.write', 'documents.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'row.upsert')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}/row/{response.$id}')
@@ -32,7 +32,7 @@ class XList extends DocumentXList
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/rows')
->desc('List rows')
->groups(['api', 'database'])
->label('scope', 'rows.read')
->label('scope', ['rows.read', 'documents.read'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
@@ -33,7 +33,7 @@ class Update extends CollectionUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId')
->desc('Update table')
->groups(['api', 'database', 'schema'])
->label('scope', 'tables.write')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].update')
->label('audits.event', 'table.update')
@@ -31,7 +31,7 @@ class Get extends CollectionUsageGet
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/usage')
->desc('Get table usage stats')
->groups(['api', 'database', 'usage'])
->label('scope', 'tables.read')
->label('scope', ['tables.read', 'collections.read'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
@@ -32,7 +32,7 @@ class XList extends CollectionXList
->setHttpPath('/v1/tablesdb/:databaseId/tables')
->desc('List tables')
->groups(['api', 'database'])
->label('scope', 'tables.read')
->label('scope', ['tables.read', 'collections.read'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
@@ -26,7 +26,7 @@ class Get extends DatabaseUsageGet
->setHttpPath('/v1/tablesdb/:databaseId/usage')
->desc('Get TablesDB usage stats')
->groups(['api', 'database', 'usage'])
->label('scope', 'tables.read')
->label('scope', ['tables.read', 'collections.read'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('sdk', [
new Method(
@@ -25,7 +25,7 @@ class XList extends DatabaseUsageXList
->setHttpPath('/v1/tablesdb/usage')
->desc('Get TablesDB usage stats')
->groups(['api', 'database', 'usage'])
->label('scope', 'tables.read')
->label('scope', ['tables.read', 'collections.read'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('sdk', [
new Method(
@@ -80,7 +80,7 @@ class Create extends Base
->param('body', '', new Text(10485760, 0), 'HTTP body of execution. Default value is empty string.', true)
->param('async', false, new Boolean(true), 'Execute code in the background. Default value is false.', true)
->param('path', '/', new Text(2048), 'HTTP path of execution. Path can include query params. Default value is /', true)
->param('method', 'POST', new Whitelist(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD'], true), 'HTTP method of execution. Default value is GET.', true)
->param('method', 'POST', new Whitelist(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD'], true), 'HTTP method of execution. Default value is POST.', true)
->param('headers', [], new AnyOf([new Assoc(), new Text(65535)], AnyOf::TYPE_MIXED), 'HTTP headers of execution. Defaults to empty.', true)
->param('scheduledAt', null, new Text(100), 'Scheduled execution time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future with precision in minutes.', true)
->inject('response')
@@ -221,6 +221,8 @@ class Create extends Base
'scopes' => $function->getAttribute('scopes', [])
]);
$executionId = ID::unique();
$headers['x-appwrite-execution-id'] = $executionId ?? '';
$headers['x-appwrite-key'] = API_KEY_DYNAMIC . '_' . $apiKey;
$headers['x-appwrite-trigger'] = 'http';
$headers['x-appwrite-user-id'] = $user->getId() ?? '';
@@ -228,8 +230,9 @@ class Create extends Base
$headers['x-appwrite-country-code'] = '';
$headers['x-appwrite-continent-code'] = '';
$headers['x-appwrite-continent-eu'] = 'false';
$ip = $request->getIP();
$headers['x-appwrite-client-ip'] = $ip;
$ip = $headers['x-real-ip'] ?? '';
if (!empty($ip)) {
$record = $geodb->get($ip);
@@ -249,7 +252,7 @@ class Create extends Base
}
}
$executionId = ID::unique();
$status = $async ? 'waiting' : 'processing';
@@ -481,6 +484,8 @@ class Create extends Base
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
}
$executionResponse['headers']['x-appwrite-execution-id'] = $execution->getId();
$headers = [];
foreach (($executionResponse['headers'] ?? []) as $key => $value) {
$headers[] = ['name' => $key, 'value' => $value];
@@ -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) {
@@ -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) {
@@ -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) {
@@ -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) {
+7 -2
View File
@@ -264,6 +264,8 @@ class Functions extends Action
string $jwt = null,
string $event = null,
): void {
$executionId = ID::unique();
$headers['x-appwrite-execution-id'] = $executionId ?? '';
$headers['x-appwrite-trigger'] = $trigger;
$headers['x-appwrite-event'] = $event ?? '';
$headers['x-appwrite-user-id'] = $user->getId() ?? '';
@@ -276,7 +278,6 @@ class Functions extends Action
}
}
$executionId = ID::unique();
$execution = new Document([
'$id' => $executionId,
'$permissions' => $user->isEmpty() ? [] : [Permission::read(Role::user($user->getId()))],
@@ -397,6 +398,7 @@ class Functions extends Action
'scopes' => $function->getAttribute('scopes', [])
]);
$headers['x-appwrite-execution-id'] = $executionId ?? '';
$headers['x-appwrite-key'] = API_KEY_DYNAMIC . '_' . $apiKey;
$headers['x-appwrite-trigger'] = $trigger;
$headers['x-appwrite-event'] = $event ?? '';
@@ -409,6 +411,8 @@ class Functions extends Action
/** Create execution or update execution status */
$execution = $dbForProject->getDocument('executions', $executionId ?? '');
if ($execution->isEmpty()) {
$executionId = ID::unique();
$headers['x-appwrite-execution-id'] = $executionId;
$headersFiltered = [];
foreach ($headers as $key => $value) {
if (\in_array(\strtolower($key), FUNCTION_ALLOWLIST_HEADERS_REQUEST)) {
@@ -416,7 +420,6 @@ class Functions extends Action
}
}
$executionId = ID::unique();
$execution = new Document([
'$id' => $executionId,
'$permissions' => $user->isEmpty() ? [] : [Permission::read(Role::user($user->getId()))],
@@ -543,6 +546,8 @@ class Functions extends Action
$status = $executionResponse['statusCode'] >= 500 ? 'failed' : 'completed';
$executionResponse['headers']['x-appwrite-execution-id'] = $execution->getId();
$headersFiltered = [];
foreach ($executionResponse['headers'] as $key => $value) {
if (\in_array(\strtolower($key), FUNCTION_ALLOWLIST_HEADERS_RESPONSE)) {
@@ -349,11 +349,10 @@ class Swagger2 extends Format
}
}
if (!empty($scope)) { // && 'public' != $scope
if (!empty($scope)) {
$securities = ['Project' => []];
foreach ($sdk->getAuth() as $security) {
/** @var AuthType $security */
if (\array_key_exists($security->value, $this->keys)) {
$securities[$security->value] = [];
}
+56 -18
View File
@@ -96,36 +96,74 @@ class V20 extends Filter
/**
* Returns all relationship attribute keys in `key.*` format for use with `Query::select`.
*/
private function getRelatedCollectionKeys(): array
{
$dbForProject = $this->getDbForProject();
private function getRelatedCollectionKeys(
?string $databaseId = null,
?string $collectionId = null,
?string $prefix = null,
int $depth = 1,
): array {
$databaseId ??= $this->getParamValue('databaseId');
$collectionId ??= $this->getParamValue('collectionId');
if (
empty($databaseId) ||
empty($collectionId) ||
$depth > Database::RELATION_MAX_DEPTH
) {
return [];
}
$dbForProject = $this->getDbForProject();
if ($dbForProject === null) {
return [];
}
$databaseId = $this->getParamValue('databaseId');
$collectionId = $this->getParamValue('collectionId');
if (empty($databaseId) || empty($collectionId)) {
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
return [];
}
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
$collection = $dbForProject->getDocument(
$collection = Authorization::skip(fn () => $dbForProject->getDocument(
'database_' . $database->getSequence(),
$collectionId
);
));
if ($collection->isEmpty()) {
return [];
}
$attributes = $collection->getAttribute('attributes', []);
$relationshipKeys = [];
return \array_values(\array_map(
fn ($attr) => $attr['key'] . '.*',
\array_filter(
$attributes,
fn ($attr) => ($attr['type'] ?? null) === Database::VAR_RELATIONSHIP
)
));
foreach ($attributes as $attr) {
if (
($attr['type'] ?? null) !== Database::VAR_RELATIONSHIP ||
$attr['status'] !== 'available'
) {
continue;
}
$key = $attr['key'];
$fullKey = $prefix ? $prefix . '.' . $key : $key;
// Add the wildcard select for this relationship
$relationshipKeys[] = $fullKey . '.*';
// Get the related collection for nested relationships
$relatedCollectionId = $attr['relatedCollection'] ?? null;
if ($relatedCollectionId) {
// Recursively get nested relationship keys
$nestedKeys = $this->getRelatedCollectionKeys(
$databaseId,
$relatedCollectionId,
$fullKey,
$depth + 1,
);
$relationshipKeys = \array_merge($relationshipKeys, $nestedKeys);
}
}
return \array_values(\array_unique($relationshipKeys));
}
}
+1 -4
View File
@@ -12,18 +12,15 @@ class V20 extends Filter
{
$parsedResponse = $content;
$parsedResponse = match($model) {
return match($model) {
Response::MODEL_DOCUMENT => $this->parseDocument($content),
Response::MODEL_DOCUMENT_LIST => $this->handleList($content, 'documents', fn ($item) => $this->parseDocument($item)),
default => $parsedResponse,
};
return $parsedResponse;
}
protected function parseDocument(array $content): array
{
unset($content['$sequence']);
return $content;
}
}
@@ -79,6 +79,7 @@ class Row extends Any
{
$document->removeAttribute('$collection');
$document->removeAttribute('$tenant');
$document->setAttribute('$sequence', (int)$document->getAttribute('$sequence', 0));
foreach ($document->getAttributes() as $column) {
if (\is_array($column)) {