diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 635d4add79..baa0b610d6 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -30,6 +30,8 @@ use Utopia\Validator\Integer; use Utopia\Database\Query; use Utopia\Database\Validator\Permissions; use Utopia\Storage\Validator\FileExt; +use Utopia\Database\Exception\Duplicate as DuplicateException; +use Utopia\Database\Exception\Structure as StructureException; App::post('/v1/storage/buckets') ->desc('Create storage bucket') @@ -522,6 +524,14 @@ App::post('/v1/storage/buckets/:bucketId/files') throw new Exception('Bucket not found', 404); } + // Check bucket permissions when enforced + if ($bucket->getAttribute('permission') === 'bucket') { + $validator = new Authorization('write'); + if (!$validator->isValid($bucket->getWrite())) { + throw new Exception('Unauthorized permissions', 401); + } + } + $file = $request->getFiles('file'); /* @@ -626,7 +636,23 @@ App::post('/v1/storage/buckets/:bucketId/files') $data['openSSLIV'] = \bin2hex($iv); } - $file = $dbForInternal->createDocument('bucket_' . $bucket->getId(), new Document($data)); + try { + if($bucket->getAttribute('permission') === 'bucket') { + $file = Authorization::skip(function() use ($dbForInternal, $bucket, $data) { + return $dbForInternal->createDocument('bucket_' . $bucket->getId(), new Document($data)); + }); + } else { + $file = $dbForInternal->createDocument('bucket_' . $bucket->getId(), new Document($data)); + } + + } + catch (StructureException $exception) { + throw new Exception($exception->getMessage(), 400); + } + catch (DuplicateException $exception) { + throw new Exception('Document already exists', 409); + } + $audits ->setParam('event', 'storage.files.create') @@ -677,6 +703,14 @@ App::get('/v1/storage/buckets/:bucketId/files') throw new Exception('Bucket not found', 404); } + // 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); + } + } + $queries = [new Query('bucketId', Query::TYPE_EQUAL, [$bucketId])]; if($search) { @@ -684,7 +718,13 @@ App::get('/v1/storage/buckets/:bucketId/files') } if (!empty($cursor)) { - $cursorFile = $dbForInternal->getDocument('bucket_' . $bucketId, $cursor); + if($bucket->getAttribute('permission') ==='bucket') { + $cursorFile = Authorization::skip(function() use ($dbForInternal, $bucket, $cursor) { + return $dbForInternal->getDocument('bucket_' . $bucket->getId(), $cursor); + }); + } else { + $cursorFile = $dbForInternal->getDocument('bucket_' . $bucket->getId(), $cursor); + } if ($cursorFile->isEmpty()) { throw new Exception("File '{$cursor}' for the 'cursor' value not found.", 400); @@ -697,13 +737,21 @@ App::get('/v1/storage/buckets/:bucketId/files') $queries[] = new Query('search', Query::TYPE_SEARCH, [$search]); } + if($bucket->getAttribute('permission') === 'bucket') { + $files = Authorization::skip(function() use ($dbForInternal, $bucketId, $queries, $limit, $offset, $cursor, $cursorDirection, $orderType) { + return $dbForInternal->find('bucket_' . $bucketId, $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection); + }); + } else { + $files = $dbForInternal->find('bucket_' . $bucketId, $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection); + } + $usage ->setParam('storage.files.read', 1) ->setParam('bucketId', $bucketId) ; $response->dynamic(new Document([ - 'files' => $dbForInternal->find('bucket_' . $bucketId, $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection), + 'files' => $files, 'sum' => $dbForInternal->count('bucket_' . $bucketId, $queries, APP_LIMIT_COUNT), ]), Response::MODEL_FILE_LIST); }); @@ -736,7 +784,21 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId') throw new Exception('Bucket not found', 404); } - $file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId); + // 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); + } + } + + if($bucket->getAttribute('permission') === 'bucket') { + $file = Authorization::skip(function() use ($dbForInternal, $bucketId, $fileId) { + return $dbForInternal->getDocument('bucket_' . $bucketId, $fileId); + }); + } else { + $file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId); + } if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) { throw new Exception('File not found', 404); @@ -800,6 +862,14 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') throw new Exception('Bucket not found', 404); } + // 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); + } + } + if ((\strpos($request->getAccept(), 'image/webp') === false) && ('webp' === $output)) { // Fallback webp to jpeg when no browser support $output = 'jpg'; } @@ -811,7 +881,14 @@ 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.$storage.$output); - $file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId); + if($bucket->getAttribute('permission')==='bucket') { + // skip authorization + $file = Authorization::skip(function () use ($dbForInternal, $bucketId, $fileId) { + return $dbForInternal->getDocument('bucket_' . $bucketId, $fileId); + }); + } else { + $file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId); + } if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) { throw new Exception('File not found', 404); @@ -943,7 +1020,21 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') throw new Exception('Bucket not found', 404); } - $file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId); + // 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); + } + } + + if($bucket->getAttribute('permission') === 'bucket') { + $file = Authorization::skip(function() use ($dbForInternal, $fileId, $bucketId) { + return $dbForInternal->getDocument('bucket_' . $bucketId, $fileId); + }); + } else { + $file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId); + } if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) { throw new Exception('File not found', 404); @@ -1016,7 +1107,22 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') throw new Exception('Bucket not found', 404); } - $file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId); + // 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); + } + } + + if($bucket->getAttribute('permission') === 'bucket') { + $file = Authorization::skip(function() use ($dbForInternal, $fileId, $bucketId) { + return $dbForInternal->getDocument('bucket_' . $bucketId, $fileId); + }); + } else { + $file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId); + } + $mimes = Config::getParam('storage-mimes'); if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) { @@ -1103,16 +1209,39 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') throw new Exception('Bucket not found', 404); } - $file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId); + // Check bucket permissions when enforced + if ($bucket->getAttribute('permission') === 'bucket') { + $validator = new Authorization('write'); + if (!$validator->isValid($bucket->getWrite())) { + throw new Exception('Unauthorized permissions', 401); + } + } + + if($bucket->getAttribute('permission') === 'bucket') { + $file = Authorization::skip(function() use ($dbForInternal, $fileId, $bucketId) { + return $dbForInternal->getDocument('bucket_' . $bucketId, $fileId); + }); + } else { + $file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId); + } if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) { throw new Exception('File not found', 404); } - $file = $dbForInternal->updateDocument('bucket_' . $bucketId, $fileId, $file + if($bucket->getAttribute('permission') === 'bucket') { + $file = Authorization::skip(function() use ($dbForInternal, $fileId, $bucketId, $file, $read, $write) { + return $dbForInternal->updateDocument('bucket_' . $bucketId, $fileId, $file + ->setAttribute('$read', $read) + ->setAttribute('$write', $write) + ); + }); + } else { + $file = $dbForInternal->updateDocument('bucket_' . $bucketId, $fileId, $file ->setAttribute('$read', $read) ->setAttribute('$write', $write) - ); + ); + } $audits ->setParam('event', 'storage.files.update') @@ -1159,7 +1288,21 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') throw new Exception('Bucket not found', 404); } - $file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId); + // Check bucket permissions when enforced + if ($bucket->getAttribute('permission') === 'bucket') { + $validator = new Authorization('write'); + if (!$validator->isValid($bucket->getWrite())) { + throw new Exception('Unauthorized permissions', 401); + } + } + + if($bucket->getAttribute('permission') === 'bucket') { + $file = Authorization::skip(function() use ($dbForInternal, $fileId, $bucketId) { + return $dbForInternal->getDocument('bucket_' . $bucketId, $fileId); + }); + } else { + $file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId); + } if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) { throw new Exception('File not found', 404); @@ -1168,7 +1311,14 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') $device = Storage::getDevice('files'); if ($device->delete($file->getAttribute('path', ''))) { - if (!$dbForInternal->deleteDocument('bucket_' . $bucketId, $fileId)) { + if($bucket->getAttribute('permission') === 'bucket') { + $deleted = Authorization::skip(function() use ($dbForInternal, $fileId, $bucketId) { + return $dbForInternal->deleteDocument('bucket_' . $bucketId, $fileId); + }); + } else { + $deleted = $dbForInternal->deleteDocument('bucket_' . $bucketId, $fileId); + } + if (!$deleted) { throw new Exception('Failed to remove file from DB', 500); } }