mirror of
https://github.com/appwrite/appwrite
synced 2026-05-23 00:49:02 +00:00
Process permissions before txn logging
This commit is contained in:
parent
2235848262
commit
3d1ae4feda
7 changed files with 141 additions and 141 deletions
|
|
@ -13,10 +13,12 @@ use Appwrite\SDK\Method;
|
|||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Conflict as ConflictException;
|
||||
use Utopia\Database\Exception\Limit as LimitException;
|
||||
use Utopia\Database\Exception\NotFound as NotFoundException;
|
||||
use Utopia\Database\Exception\Type as TypeException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
|
|
@ -85,12 +87,12 @@ class Decrement extends Action
|
|||
|
||||
public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $min, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, array $plan): void
|
||||
{
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
$collection = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception($this->getParentNotFoundException());
|
||||
}
|
||||
|
|
@ -142,7 +144,7 @@ class Decrement extends Action
|
|||
'$id' => $documentId,
|
||||
'$collectionId' => $collectionId,
|
||||
'$databaseId' => $databaseId,
|
||||
$attribute => $value, // Mock response - actual value would be computed during commit
|
||||
$attribute => $value,
|
||||
]);
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_OK)
|
||||
|
|
|
|||
|
|
@ -13,10 +13,12 @@ use Appwrite\SDK\Method;
|
|||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Conflict as ConflictException;
|
||||
use Utopia\Database\Exception\Limit as LimitException;
|
||||
use Utopia\Database\Exception\NotFound as NotFoundException;
|
||||
use Utopia\Database\Exception\Type as TypeException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
|
|
@ -142,7 +144,7 @@ class Increment extends Action
|
|||
'$id' => $documentId,
|
||||
'$collectionId' => $collectionId,
|
||||
'$databaseId' => $databaseId,
|
||||
$attribute => $value, // Mock response - actual value would be computed during commit
|
||||
$attribute => $value,
|
||||
]);
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_OK)
|
||||
|
|
|
|||
|
|
@ -133,7 +133,6 @@ class Delete extends Action
|
|||
'databaseInternalId' => $database->getSequence(),
|
||||
'collectionInternalId' => $collection->getSequence(),
|
||||
'transactionInternalId' => $transaction->getSequence(),
|
||||
'documentId' => null, // Bulk operation doesn't have specific document ID
|
||||
'action' => 'bulkDelete',
|
||||
'data' => [
|
||||
'queries' => $queries,
|
||||
|
|
@ -146,7 +145,6 @@ class Delete extends Action
|
|||
'transactions',
|
||||
$transactionId,
|
||||
'operations',
|
||||
1
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -122,6 +122,13 @@ class Update extends Action
|
|||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if ($data['$permissions']) {
|
||||
$validator = new Permissions();
|
||||
if (!$validator->isValid($data['$permissions'])) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, $validator->getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
// Handle transaction staging
|
||||
if ($transactionId !== null) {
|
||||
$transaction = $dbForProject->getDocument('transactions', $transactionId);
|
||||
|
|
@ -145,7 +152,6 @@ class Update extends Action
|
|||
'databaseInternalId' => $database->getSequence(),
|
||||
'collectionInternalId' => $collection->getSequence(),
|
||||
'transactionInternalId' => $transaction->getSequence(),
|
||||
'documentId' => null, // Bulk operation doesn't have specific document ID
|
||||
'action' => 'bulkUpdate',
|
||||
'data' => [
|
||||
'data' => $data,
|
||||
|
|
@ -171,13 +177,6 @@ class Update extends Action
|
|||
return;
|
||||
}
|
||||
|
||||
if ($data['$permissions']) {
|
||||
$validator = new Permissions();
|
||||
if (!$validator->isValid($data['$permissions'])) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, $validator->getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
$documents = [];
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -101,6 +101,10 @@ class Upsert extends Action
|
|||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk upsert is not supported for ' . $this->getSdkNamespace() . ' with relationship attributes');
|
||||
}
|
||||
|
||||
foreach ($documents as $key => $document) {
|
||||
$documents[$key] = new Document($document);
|
||||
}
|
||||
|
||||
// Handle transaction staging
|
||||
if ($transactionId !== null) {
|
||||
$transaction = $dbForProject->getDocument('transactions', $transactionId);
|
||||
|
|
@ -111,7 +115,7 @@ class Upsert extends Action
|
|||
// Enforce max operations per transaction
|
||||
$maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION;
|
||||
$existing = $transaction->getAttribute('operations', 0);
|
||||
if (($existing + \count($documents)) > $maxBatch) {
|
||||
if (($existing + 1) > $maxBatch) {
|
||||
throw new Exception(
|
||||
Exception::TRANSACTION_LIMIT_EXCEEDED,
|
||||
'Transaction already has ' . $existing . ' operations, adding ' . \count($documents) . ' would exceed the maximum of ' . $maxBatch
|
||||
|
|
@ -119,21 +123,17 @@ class Upsert extends Action
|
|||
}
|
||||
|
||||
// Stage the operations in transaction logs
|
||||
$staged = [];
|
||||
foreach ($documents as $document) {
|
||||
$staged[] = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'databaseInternalId' => $database->getSequence(),
|
||||
'collectionInternalId' => $collection->getSequence(),
|
||||
'transactionInternalId' => $transaction->getSequence(),
|
||||
'documentId' => $document['$id'] ?? ID::unique(),
|
||||
'action' => 'upsert',
|
||||
'data' => $document,
|
||||
]);
|
||||
}
|
||||
$staged = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'databaseInternalId' => $database->getSequence(),
|
||||
'collectionInternalId' => $collection->getSequence(),
|
||||
'transactionInternalId' => $transaction->getSequence(),
|
||||
'action' => 'bulkUpsert',
|
||||
'data' => $documents,
|
||||
]);
|
||||
|
||||
$dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged) {
|
||||
$dbForProject->createDocuments('transactionLogs', $staged);
|
||||
$dbForProject->createDocument('transactionLogs', $staged);
|
||||
$dbForProject->increaseDocumentAttribute(
|
||||
'transactions',
|
||||
$transactionId,
|
||||
|
|
@ -147,11 +147,8 @@ class Upsert extends Action
|
|||
$this->getSdkGroup() => [],
|
||||
'total' => \count($documents),
|
||||
]), $this->getResponseModel());
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($documents as $key => $document) {
|
||||
$documents[$key] = new Document($document);
|
||||
return;
|
||||
}
|
||||
|
||||
$upserted = [];
|
||||
|
|
|
|||
|
|
@ -130,61 +130,6 @@ class Update extends Action
|
|||
throw new Exception($this->getNotFoundException());
|
||||
}
|
||||
|
||||
// Handle transaction staging
|
||||
if ($transactionId !== null) {
|
||||
$transaction = $dbForProject->getDocument('transactions', $transactionId);
|
||||
if ($transaction->isEmpty()) {
|
||||
throw new Exception(Exception::TRANSACTION_NOT_FOUND);
|
||||
}
|
||||
if ($transaction->getAttribute('status', '') !== 'pending') {
|
||||
throw new Exception(Exception::TRANSACTION_NOT_READY);
|
||||
}
|
||||
|
||||
// Enforce max operations per transaction
|
||||
$maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION;
|
||||
$existing = $transaction->getAttribute('operations', 0);
|
||||
if (($existing + 1) > $maxBatch) {
|
||||
throw new Exception(
|
||||
Exception::TRANSACTION_LIMIT_EXCEEDED,
|
||||
'Transaction already has ' . $existing . ' operations, adding 1 would exceed the maximum of ' . $maxBatch
|
||||
);
|
||||
}
|
||||
|
||||
// Stage the operation in transaction logs
|
||||
$staged = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'databaseInternalId' => $database->getSequence(),
|
||||
'collectionInternalId' => $collection->getSequence(),
|
||||
'transactionInternalId' => $transaction->getSequence(),
|
||||
'documentId' => $documentId,
|
||||
'action' => 'update',
|
||||
'data' => $data,
|
||||
]);
|
||||
|
||||
$dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged) {
|
||||
$dbForProject->createDocument('transactionLogs', $staged);
|
||||
$dbForProject->increaseDocumentAttribute(
|
||||
'transactions',
|
||||
$transactionId,
|
||||
'operations',
|
||||
1
|
||||
);
|
||||
});
|
||||
|
||||
// Return successful response without actually updating document
|
||||
$mockDocument = new Document([
|
||||
'$id' => $documentId,
|
||||
'$collectionId' => $collectionId,
|
||||
'$databaseId' => $databaseId,
|
||||
...$document->getArrayCopy(),
|
||||
...$data
|
||||
]);
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_OK)
|
||||
->dynamic($mockDocument, $this->getResponseModel());
|
||||
return;
|
||||
}
|
||||
|
||||
// Map aggregate permissions into the multiple permissions they represent.
|
||||
$permissions = Permission::aggregate($permissions, [
|
||||
Database::PERMISSION_READ,
|
||||
|
|
@ -298,6 +243,63 @@ class Update extends Action
|
|||
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, max($operations, 1))
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), $operations);
|
||||
|
||||
|
||||
// Handle transaction staging
|
||||
if ($transactionId !== null) {
|
||||
$transaction = $dbForProject->getDocument('transactions', $transactionId);
|
||||
if ($transaction->isEmpty()) {
|
||||
throw new Exception(Exception::TRANSACTION_NOT_FOUND);
|
||||
}
|
||||
if ($transaction->getAttribute('status', '') !== 'pending') {
|
||||
throw new Exception(Exception::TRANSACTION_NOT_READY);
|
||||
}
|
||||
|
||||
// Enforce max operations per transaction
|
||||
$maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION;
|
||||
$existing = $transaction->getAttribute('operations', 0);
|
||||
if (($existing + 1) > $maxBatch) {
|
||||
throw new Exception(
|
||||
Exception::TRANSACTION_LIMIT_EXCEEDED,
|
||||
'Transaction already has ' . $existing . ' operations, adding 1 would exceed the maximum of ' . $maxBatch
|
||||
);
|
||||
}
|
||||
|
||||
// Stage the operation in transaction logs
|
||||
$staged = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'databaseInternalId' => $database->getSequence(),
|
||||
'collectionInternalId' => $collection->getSequence(),
|
||||
'transactionInternalId' => $transaction->getSequence(),
|
||||
'documentId' => $documentId,
|
||||
'action' => 'update',
|
||||
'data' => $data,
|
||||
]);
|
||||
|
||||
$dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged) {
|
||||
$dbForProject->createDocument('transactionLogs', $staged);
|
||||
$dbForProject->increaseDocumentAttribute(
|
||||
'transactions',
|
||||
$transactionId,
|
||||
'operations',
|
||||
1
|
||||
);
|
||||
});
|
||||
|
||||
// Return successful response without actually updating document
|
||||
$mockDocument = new Document([
|
||||
'$id' => $documentId,
|
||||
'$collectionId' => $collectionId,
|
||||
'$databaseId' => $databaseId,
|
||||
...$document->getArrayCopy(),
|
||||
...$data
|
||||
]);
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_OK)
|
||||
->dynamic($mockDocument, $this->getResponseModel());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
$document = $dbForProject->withRequestTimestamp(
|
||||
$requestTimestamp,
|
||||
|
|
|
|||
|
|
@ -112,60 +112,6 @@ class Upsert extends Action
|
|||
throw new Exception($this->getParentNotFoundException());
|
||||
}
|
||||
|
||||
// Handle transaction staging
|
||||
if ($transactionId !== null) {
|
||||
$transaction = $dbForProject->getDocument('transactions', $transactionId);
|
||||
if ($transaction->isEmpty()) {
|
||||
throw new Exception(Exception::TRANSACTION_NOT_FOUND);
|
||||
}
|
||||
if ($transaction->getAttribute('status', '') !== 'pending') {
|
||||
throw new Exception(Exception::TRANSACTION_NOT_READY);
|
||||
}
|
||||
|
||||
// Enforce max operations per transaction
|
||||
$maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION;
|
||||
$existing = $transaction->getAttribute('operations', 0);
|
||||
if (($existing + 1) > $maxBatch) {
|
||||
throw new Exception(
|
||||
Exception::TRANSACTION_LIMIT_EXCEEDED,
|
||||
'Transaction already has ' . $existing . ' operations, adding 1 would exceed the maximum of ' . $maxBatch
|
||||
);
|
||||
}
|
||||
|
||||
// Stage the operation in transaction logs
|
||||
$staged = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'databaseInternalId' => $database->getSequence(),
|
||||
'collectionInternalId' => $collection->getSequence(),
|
||||
'transactionInternalId' => $transaction->getSequence(),
|
||||
'documentId' => $documentId,
|
||||
'action' => 'upsert',
|
||||
'data' => $data,
|
||||
]);
|
||||
|
||||
$dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged) {
|
||||
$dbForProject->createDocument('transactionLogs', $staged);
|
||||
$dbForProject->increaseDocumentAttribute(
|
||||
'transactions',
|
||||
$transactionId,
|
||||
'operations',
|
||||
1
|
||||
);
|
||||
});
|
||||
|
||||
// Return successful response without actually upserting document
|
||||
$mockDocument = new Document([
|
||||
'$id' => $documentId,
|
||||
'$collectionId' => $collectionId,
|
||||
'$databaseId' => $databaseId,
|
||||
...$data
|
||||
]);
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_CREATED)
|
||||
->dynamic($mockDocument, $this->getResponseModel());
|
||||
return;
|
||||
}
|
||||
|
||||
$allowedPermissions = [
|
||||
Database::PERMISSION_READ,
|
||||
Database::PERMISSION_UPDATE,
|
||||
|
|
@ -300,6 +246,60 @@ class Upsert extends Action
|
|||
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $operations))
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $operations));
|
||||
|
||||
// Handle transaction staging
|
||||
if ($transactionId !== null) {
|
||||
$transaction = $dbForProject->getDocument('transactions', $transactionId);
|
||||
if ($transaction->isEmpty()) {
|
||||
throw new Exception(Exception::TRANSACTION_NOT_FOUND);
|
||||
}
|
||||
if ($transaction->getAttribute('status', '') !== 'pending') {
|
||||
throw new Exception(Exception::TRANSACTION_NOT_READY);
|
||||
}
|
||||
|
||||
// Enforce max operations per transaction
|
||||
$maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION;
|
||||
$existing = $transaction->getAttribute('operations', 0);
|
||||
if (($existing + 1) > $maxBatch) {
|
||||
throw new Exception(
|
||||
Exception::TRANSACTION_LIMIT_EXCEEDED,
|
||||
'Transaction already has ' . $existing . ' operations, adding 1 would exceed the maximum of ' . $maxBatch
|
||||
);
|
||||
}
|
||||
|
||||
// Stage the operation in transaction logs
|
||||
$staged = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'databaseInternalId' => $database->getSequence(),
|
||||
'collectionInternalId' => $collection->getSequence(),
|
||||
'transactionInternalId' => $transaction->getSequence(),
|
||||
'documentId' => $documentId,
|
||||
'action' => 'upsert',
|
||||
'data' => $data,
|
||||
]);
|
||||
|
||||
$dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged) {
|
||||
$dbForProject->createDocument('transactionLogs', $staged);
|
||||
$dbForProject->increaseDocumentAttribute(
|
||||
'transactions',
|
||||
$transactionId,
|
||||
'operations',
|
||||
1
|
||||
);
|
||||
});
|
||||
|
||||
// Return successful response without actually upserting document
|
||||
$mockDocument = new Document([
|
||||
'$id' => $documentId,
|
||||
'$collectionId' => $collectionId,
|
||||
'$databaseId' => $databaseId,
|
||||
...$data
|
||||
]);
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_CREATED)
|
||||
->dynamic($mockDocument, $this->getResponseModel());
|
||||
return;
|
||||
}
|
||||
|
||||
$upserted = [];
|
||||
try {
|
||||
$dbForProject->withPreserveDates(function () use (&$upserted, $dbForProject, $database, $collection, $newDocument) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue