Use helpers to process permissions

This commit is contained in:
Jake Barnby
2022-08-05 18:01:25 +12:00
parent e8af744c97
commit 5e4a422495
2 changed files with 70 additions and 155 deletions
+50 -96
View File
@@ -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);
+20 -59
View File
@@ -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);