mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Use helpers to process permissions
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Permissions\PermissionsProcessor;
|
||||
use Utopia\App;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Extend\Exception;
|
||||
@@ -514,6 +515,8 @@ App::post('/v1/databases/:databaseId/collections')
|
||||
|
||||
$collectionId = $collectionId == 'unique()' ? $dbForProject->getId() : $collectionId;
|
||||
|
||||
$permissions = PermissionsProcessor::handleAggregates($permissions);
|
||||
|
||||
try {
|
||||
$dbForProject->createDocument('database_' . $database->getInternalId(), new Document([
|
||||
'$id' => $collectionId,
|
||||
@@ -771,6 +774,7 @@ App::put('/v1/databases/:databaseId/collections/:collectionId')
|
||||
}
|
||||
|
||||
$permissions ??= $collection->getPermissions() ?? [];
|
||||
$permissions = PermissionsProcessor::handleAggregates($permissions);
|
||||
$enabled ??= $collection->getAttribute('enabled', true);
|
||||
|
||||
try {
|
||||
@@ -1787,8 +1791,8 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setParam('indexId', $index->getId())
|
||||
->setContext('collection', $collection)
|
||||
->setContext('database', $db)
|
||||
->setContext('collection', $collection)
|
||||
->setContext('database', $db)
|
||||
->setPayload($response->output($index, Response::MODEL_INDEX))
|
||||
;
|
||||
|
||||
@@ -1855,35 +1859,20 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
}
|
||||
}
|
||||
|
||||
// Check collection permissions when enforced
|
||||
if (!$collection->getAttribute('documentSecurity', false)) {
|
||||
$validator = new Authorization('create');
|
||||
if (!$validator->isValid($collection->getCreate())) {
|
||||
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
$permissions = PermissionsProcessor::addDefaultsIfNeeded($permissions, $user->getId());
|
||||
$permissions = PermissionsProcessor::handleAggregates($permissions);
|
||||
|
||||
$validator = new Authorization('create');
|
||||
$valid = $validator->isValid($collection->getCreate());
|
||||
if ($collection->getAttribute('documentSecurity', false)) {
|
||||
$valid |= $validator->isValid($permissions);
|
||||
}
|
||||
if (!$valid) {
|
||||
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$data['$collection'] = $collection->getId(); // Adding this param to make API easier for developers
|
||||
$data['$id'] = $documentId == 'unique()' ? $dbForProject->getId() : $documentId;
|
||||
|
||||
if (\is_null($permissions)) {
|
||||
$permissions = [];
|
||||
if (!$user->isEmpty()) {
|
||||
$permissions = [
|
||||
'read(user:' . $user->getId() . ') ',
|
||||
'write(user:' . $user->getId() . ') ',
|
||||
];
|
||||
}
|
||||
} elseif (empty(\preg_grep('#^read\(.+\)$#', $permissions))) {
|
||||
if (!$user->isEmpty()) {
|
||||
$permissions[] = 'read(user:' . $user->getId() . ')';
|
||||
}
|
||||
} elseif (empty(\preg_grep('#^write\(.+\)$#', $permissions))) {
|
||||
if (!$user->isEmpty()) {
|
||||
$permissions[] = 'write(user:' . $user->getId() . ')';
|
||||
}
|
||||
}
|
||||
|
||||
$data['$permissions'] = $permissions;
|
||||
|
||||
// Users can only add their roles to documents, API keys and Admin users can add any
|
||||
@@ -1892,24 +1881,20 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) {
|
||||
foreach ($permissions as $permission) {
|
||||
$matches = [];
|
||||
if (\preg_match('/^(create|write)\((.+)(?:,(.+))*\)$/', $permission, $matches)) {
|
||||
\array_shift($matches);
|
||||
foreach ($matches as $match) {
|
||||
if (!Authorization::isRole($match)) {
|
||||
throw new Exception('Permissions must be one of: (' . \implode(', ', $roles) . ')', 400, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
if (!\preg_match('#^create\((.+)(?:,(.+))*\)$#', $permission, $matches)) {
|
||||
continue;
|
||||
}
|
||||
\array_shift($matches);
|
||||
foreach ($matches as $match) {
|
||||
if (!Authorization::isRole($match)) {
|
||||
throw new Exception('Permissions must be one of: (' . \implode(', ', $roles) . ')', 400, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if ($collection->getAttribute('permission') === 'collection') {
|
||||
/** @var Document $document */
|
||||
$document = Authorization::skip(fn() => $dbForProject->createDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), new Document($data)));
|
||||
} else {
|
||||
$document = $dbForProject->createDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), new Document($data));
|
||||
}
|
||||
$document = $dbForProject->createDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), new Document($data));
|
||||
$document->setAttribute('$collection', $collectionId);
|
||||
} catch (StructureException $exception) {
|
||||
throw new Exception($exception->getMessage(), 400, Exception::DOCUMENT_INVALID_STRUCTURE);
|
||||
@@ -1985,12 +1970,11 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
}
|
||||
}
|
||||
|
||||
// Check collection permissions when enforced
|
||||
if ($collection->getAttribute('permission') === 'collection') {
|
||||
$validator = new Authorization('read');
|
||||
if (!$validator->isValid($collection->getRead())) {
|
||||
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
// Check collection permissions
|
||||
$validator = new Authorization('create');
|
||||
$valid = $validator->isValid($collection->getCreate());
|
||||
if (!$valid) {
|
||||
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$queries = \array_map(function ($query) {
|
||||
@@ -2019,23 +2003,15 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
|
||||
$cursorDocument = null;
|
||||
if (!empty($cursor)) {
|
||||
$cursorDocument = $collection->getAttribute('permission') === 'collection'
|
||||
? Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $cursor))
|
||||
: $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $cursor);
|
||||
|
||||
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $cursor));
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception("Document '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
if ($collection->getAttribute('permission') === 'collection') {
|
||||
/** @var Document[] $documents */
|
||||
$documents = Authorization::skip(fn() => $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, $limit, $offset, $orderAttributes, $orderTypes, $cursorDocument ?? null, $cursorDirection));
|
||||
$total = Authorization::skip(fn() => $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, APP_LIMIT_COUNT));
|
||||
} else {
|
||||
$documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, $limit, $offset, $orderAttributes, $orderTypes, $cursorDocument ?? null, $cursorDirection);
|
||||
$total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, APP_LIMIT_COUNT);
|
||||
}
|
||||
/** @var Document[] $documents */
|
||||
$documents = Authorization::skip(fn() => $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, $limit, $offset, $orderAttributes, $orderTypes, $cursorDocument ?? null, $cursorDirection));
|
||||
$total = Authorization::skip(fn() => $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, APP_LIMIT_COUNT));
|
||||
|
||||
/**
|
||||
* Reset $collection attribute to remove prefix.
|
||||
@@ -2091,20 +2067,14 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
|
||||
}
|
||||
}
|
||||
|
||||
// Check collection permissions when enforced
|
||||
if ($collection->getAttribute('permission') === 'collection') {
|
||||
$validator = new Authorization('read');
|
||||
if (!$validator->isValid($collection->getRead())) {
|
||||
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
// Check collection permissions
|
||||
$validator = new Authorization('create');
|
||||
$valid = $validator->isValid($collection->getCreate());
|
||||
if (!$valid) {
|
||||
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if ($collection->getAttribute('permission') === 'collection') {
|
||||
/** @var Document $document */
|
||||
$document = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId));
|
||||
} else {
|
||||
$document = $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId);
|
||||
}
|
||||
$document = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId));
|
||||
|
||||
/**
|
||||
* Reset $collection attribute to remove prefix.
|
||||
@@ -2260,18 +2230,13 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
||||
}
|
||||
}
|
||||
|
||||
// Check collection permissions when enforced
|
||||
if ($collection->getAttribute('permission') === 'collection') {
|
||||
$validator = new Authorization('write');
|
||||
if (!$validator->isValid($collection->getWrite())) {
|
||||
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$document = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId));
|
||||
} else {
|
||||
$document = $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId);
|
||||
// Check collection permissions
|
||||
$validator = new Authorization('write');
|
||||
if (!$validator->isValid($collection->getWrite())) {
|
||||
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$document = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId));
|
||||
|
||||
if ($document->isEmpty()) {
|
||||
throw new Exception('Document not found', 404, Exception::DOCUMENT_NOT_FOUND);
|
||||
@@ -2397,30 +2362,19 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
|
||||
}
|
||||
}
|
||||
|
||||
// Check collection permissions when enforced
|
||||
if ($collection->getAttribute('permission') === 'collection') {
|
||||
$validator = new Authorization('write');
|
||||
if (!$validator->isValid($collection->getWrite())) {
|
||||
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
// Check collection permissions
|
||||
$validator = new Authorization('write');
|
||||
if (!$validator->isValid($collection->getWrite())) {
|
||||
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if ($collection->getAttribute('permission') === 'collection') {
|
||||
/** @var Document $document */
|
||||
$document = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId));
|
||||
} else {
|
||||
$document = $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId);
|
||||
}
|
||||
$document = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId));
|
||||
|
||||
if ($document->isEmpty()) {
|
||||
throw new Exception('No document found', 404, Exception::DOCUMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($collection->getAttribute('permission') === 'collection') {
|
||||
Authorization::skip(fn() => $dbForProject->deleteDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId));
|
||||
} else {
|
||||
$dbForProject->deleteDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId);
|
||||
}
|
||||
Authorization::skip(fn() => $dbForProject->deleteDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId));
|
||||
|
||||
$dbForProject->deleteCachedDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use Appwrite\ClamAV\Network;
|
||||
use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Permissions\PermissionsProcessor;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\Stats\Stats;
|
||||
@@ -357,41 +358,21 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
) {
|
||||
throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Check bucket permissions when enforced
|
||||
$permissionBucket = $bucket->getAttribute('permission') === 'bucket';
|
||||
if ($permissionBucket) {
|
||||
$validator = new Authorization('write');
|
||||
if (!$validator->isValid($bucket->getWrite())) {
|
||||
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$permissions = PermissionsProcessor::addDefaultsIfNeeded($permissions, $user->getId());
|
||||
|
||||
$validator = new Authorization('write');
|
||||
if (!$validator->isValid($bucket->getWrite())) {
|
||||
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if (\is_null($permissions)) {
|
||||
$permissions = [];
|
||||
if (!$user->isEmpty()) {
|
||||
$permissions = [
|
||||
'read(user:' . $user->getId() . ') ',
|
||||
'write(user:' . $user->getId() . ') ',
|
||||
];
|
||||
}
|
||||
} elseif (empty(\preg_grep('#^read\(.+\)$#', $permissions))) {
|
||||
if (!$user->isEmpty()) {
|
||||
$permissions[] = 'read(user:' . $user->getId() . ')';
|
||||
}
|
||||
} elseif (empty(\preg_grep('#^write\(.+\)$#', $permissions))) {
|
||||
if (!$user->isEmpty()) {
|
||||
$permissions[] = 'write(user:' . $user->getId() . ')';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Users can only add their roles to files, API keys and Admin users can add any
|
||||
$roles = Authorization::getRoles();
|
||||
|
||||
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) {
|
||||
foreach ($permissions as $permission) {
|
||||
$matches = [];
|
||||
if (\preg_match('/^(create|write)\((.+)(?:,(.+))*\)$/', $permission, $matches)) {
|
||||
if (\preg_match('/^create\((.+)(?:,(.+))*\)$/', $permission, $matches)) {
|
||||
\array_shift($matches);
|
||||
foreach ($matches as $match) {
|
||||
if (!Authorization::isRole($match)) {
|
||||
@@ -699,13 +680,10 @@ App::get('/v1/storage/buckets/:bucketId/files')
|
||||
) {
|
||||
throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Check bucket permissions when enforced
|
||||
if ($bucket->getAttribute('permission') === 'bucket') {
|
||||
$validator = new Authorization('read');
|
||||
if (!$validator->isValid($bucket->getRead())) {
|
||||
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$validator = new Authorization('read');
|
||||
if (!$validator->isValid($bucket->getRead())) {
|
||||
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$queries = [new Query('bucketId', Query::TYPE_EQUAL, [$bucketId])];
|
||||
@@ -731,12 +709,8 @@ App::get('/v1/storage/buckets/:bucketId/files')
|
||||
if (!empty($search)) {
|
||||
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
|
||||
}
|
||||
|
||||
if ($bucket->getAttribute('permission') === 'bucket') {
|
||||
$files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection));
|
||||
} else {
|
||||
$files = $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection);
|
||||
}
|
||||
|
||||
$files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection));
|
||||
|
||||
$usage
|
||||
->setParam('storage.files.read', 1)
|
||||
@@ -785,12 +759,8 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
if ($bucket->getAttribute('permission') === 'bucket') {
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
} else {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
}
|
||||
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
|
||||
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
|
||||
throw new Exception('File not found', 404, Exception::STORAGE_FILE_NOT_FOUND);
|
||||
@@ -870,13 +840,8 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
||||
|
||||
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT'; // 45 days cache
|
||||
$key = \md5($fileId . $width . $height . $gravity . $quality . $borderWidth . $borderColor . $borderRadius . $opacity . $rotation . $background . $output);
|
||||
|
||||
if ($bucket->getAttribute('permission') === 'bucket') {
|
||||
// skip authorization
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
} else {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
}
|
||||
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
|
||||
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
|
||||
throw new Exception('File not found', 404, Exception::STORAGE_FILE_NOT_FOUND);
|
||||
@@ -1028,11 +993,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
|
||||
}
|
||||
}
|
||||
|
||||
if ($bucket->getAttribute('permission') === 'bucket') {
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
} else {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
}
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
|
||||
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
|
||||
throw new Exception('File not found', 404, Exception::STORAGE_FILE_NOT_FOUND);
|
||||
|
||||
Reference in New Issue
Block a user