diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 7c6cd891ce..ca80481212 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -906,7 +906,9 @@ App::get('/v1/account/identities') $queries[] = Query::equal('userInternalId', [$user->getInternalId()]); // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = \array_filter($queries, function ($query) { + return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + }); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 8c857b2fc4..6aa010e8ee 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -26,6 +26,7 @@ use Utopia\Database\Exception\Duplicate as DuplicateException; use Utopia\Database\Exception\Limit as LimitException; use Utopia\Database\Exception\Restricted as RestrictedException; use Utopia\Database\Exception\Structure as StructureException; +use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; @@ -356,7 +357,7 @@ function updateAttribute( ); } - $dbForProject->updateDocument('attributes', $db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key, $attribute); + $attribute = $dbForProject->updateDocument('attributes', $db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key, $attribute); $dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $collection->getId()); $events @@ -471,7 +472,9 @@ App::get('/v1/databases') } // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = \array_filter($queries, function ($query) { + return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + }); $cursor = reset($cursor); if ($cursor) { $databaseId = $cursor->getValue(); @@ -725,7 +728,7 @@ App::post('/v1/databases/:databaseId/collections') $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); - if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { + if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); } @@ -788,7 +791,7 @@ App::get('/v1/databases/:databaseId/collections') $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); - if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { + if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); } @@ -799,7 +802,9 @@ App::get('/v1/databases/:databaseId/collections') } // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = \array_filter($queries, function ($query) { + return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + }); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ @@ -844,7 +849,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId') $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); - if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { + if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); } @@ -984,7 +989,7 @@ App::put('/v1/databases/:databaseId/collections/:collectionId') $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); - if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { + if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); } @@ -1050,7 +1055,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId') $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); - if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { + if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); } @@ -1684,11 +1689,18 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') $queries = Query::parseQueries($queries); - \array_push($queries, Query::equal('collectionId', [$collectionId]), Query::equal('databaseId', [$databaseId])); + \array_push( + $queries, + Query::equal('collectionId', [$collectionId]), + Query::equal('databaseId', [$databaseId]) + ); // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); - $cursor = reset($cursor); + $cursor = \array_filter($queries, function ($query) { + return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + }); + + $cursor = \reset($cursor); if ($cursor) { $attributeId = $cursor->getValue(); @@ -1698,17 +1710,22 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') Query::equal('key', [$attributeId]), Query::limit(1), ])); + if (empty($cursorDocument) || $cursorDocument[0]->isEmpty()) { throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Attribute '{$attributeId}' for the 'cursor' value not found."); } + $cursor->setValue($cursorDocument[0]); } - $filterQueries = Query::groupByType($queries)['filters']; + $filters = Query::groupByType($queries)['filters']; + + $attributes = $dbForProject->find('attributes', $queries); + $total = $dbForProject->count('attributes', $filters, APP_LIMIT_COUNT); $response->dynamic(new Document([ - 'total' => $dbForProject->count('attributes', $filterQueries, APP_LIMIT_COUNT), - 'attributes' => $dbForProject->find('attributes', $queries), + 'attributes' => $attributes, + 'total' => $total, ]), Response::MODEL_ATTRIBUTE_LIST); }); @@ -2541,7 +2558,9 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes') \array_push($queries, Query::equal('collectionId', [$collectionId]), Query::equal('databaseId', [$databaseId])); // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = \array_filter($queries, function ($query) { + return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + }); $cursor = reset($cursor); if ($cursor) { @@ -2720,16 +2739,17 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); - if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + + if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } $collection = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); - if ($collection->isEmpty() || !$collection->getAttribute('enabled')) { - if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) { - throw new Exception(Exception::COLLECTION_NOT_FOUND); - } + if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { + throw new Exception(Exception::COLLECTION_NOT_FOUND); } $allowedPermissions = [ @@ -2752,8 +2772,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') } // Users can only manage their own roles, API keys and Admin users can manage any - $roles = Authorization::getRoles(); - if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) { + if (!$isAPIKey && !$isPrivilegedUser) { foreach (Database::PERMISSIONS as $type) { foreach ($permissions as $permission) { $permission = Permission::parse($permission); @@ -2766,7 +2785,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') $permission->getDimension() ))->toString(); if (!Authorization::isRole($role)) { - throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')'); + throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', Authorization::getRoles()) . ')'); } } } @@ -2936,40 +2955,30 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') ->inject('dbForProject') ->inject('mode') ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, string $mode) { - $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); - if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + + if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } $collection = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); - if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) { - if (!$collection->getAttribute('documentSecurity', false)) { - $validator = new Authorization(Database::PERMISSION_READ); - if (!$validator->isValid($collection->getRead())) { - $collection = new Document(); - } - } - } - - if ($collection->isEmpty() || !$collection->getAttribute('enabled')) { + if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::COLLECTION_NOT_FOUND); } - // Validate queries - $queriesValidator = new Documents($collection->getAttribute('attributes'), $collection->getAttribute('indexes')); - $validQueries = $queriesValidator->isValid($queries); - if (!$validQueries) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $queriesValidator->getDescription()); - } - $queries = Query::parseQueries($queries); // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); - $cursor = reset($cursor); + $cursor = \array_filter($queries, function ($query) { + return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + }); + + $cursor = \reset($cursor); + if ($cursor) { $documentId = $cursor->getValue(); @@ -2982,13 +2991,19 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') $cursor->setValue($cursorDocument); } - $filterQueries = Query::groupByType($queries)['filters']; + $filters = Query::groupByType($queries)['filters']; - $documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries); - $total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $filterQueries, APP_LIMIT_COUNT); + try { + $documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries); + $total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $filters, APP_LIMIT_COUNT); + } catch (AuthorizationException) { + throw new Exception(Exception::USER_UNAUTHORIZED); + } catch (QueryException $e) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $e->getMessage()); + } // Add $collectionId and $databaseId for all documents - $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database): bool { + $processDocument = (function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database): bool { if ($document->isEmpty()) { return false; } @@ -3033,12 +3048,11 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') } return true; - }; + }); - // The linter is forcing this indentation - foreach ($documents as $document) { - $processDocument($collection, $document); - } + foreach ($documents as $document) { + $processDocument($collection, $document); + } $response->dynamic(new Document([ 'total' => $total, @@ -3070,31 +3084,30 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ->inject('dbForProject') ->inject('mode') ->action(function (string $databaseId, string $collectionId, string $documentId, array $queries, Response $response, Database $dbForProject, string $mode) { - $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); - if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + + if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } $collection = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); - if ($collection->isEmpty() || !$collection->getAttribute('enabled')) { - if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) { - throw new Exception(Exception::COLLECTION_NOT_FOUND); - } - } - - // Validate queries - $queriesValidator = new DocumentQueriesValidator($collection->getAttribute('attributes')); - $validQueries = $queriesValidator->isValid($queries); - if (!$validQueries) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $queriesValidator->getDescription()); + if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { + throw new Exception(Exception::COLLECTION_NOT_FOUND); } $queries = Query::parseQueries($queries); - $document = $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId, $queries); + try { + $document = $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId, $queries); + } catch (AuthorizationException) { + throw new Exception(Exception::USER_UNAUTHORIZED); + } catch (QueryException $e) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $e->getMessage()); + } if ($document->isEmpty()) { throw new Exception(Exception::DOCUMENT_NOT_FOUND); @@ -3285,16 +3298,17 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); - if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + + if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } $collection = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); - if ($collection->isEmpty() || !$collection->getAttribute('enabled')) { - if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) { - throw new Exception(Exception::COLLECTION_NOT_FOUND); - } + if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { + throw new Exception(Exception::COLLECTION_NOT_FOUND); } // Read permission should not be required for update @@ -3314,7 +3328,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum // Users can only manage their own roles, API keys and Admin users can manage any $roles = Authorization::getRoles(); - if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles) && !\is_null($permissions)) { + if (!$isAPIKey && !$isPrivilegedUser && !\is_null($permissions)) { foreach (Database::PERMISSIONS as $type) { foreach ($permissions as $permission) { $permission = Permission::parse($permission); @@ -3337,10 +3351,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum $permissions = $document->getPermissions() ?? []; } - $data = \array_merge($document->getArrayCopy(), $data); // Merge existing data with new data - $data['$collection'] = $document->getAttribute('$collection'); // Make sure user doesn't switch collectionID - $data['$createdAt'] = $document->getCreatedAt(); // Make sure user doesn't switch createdAt - $data['$id'] = $document->getId(); // Make sure user doesn't switch document unique ID + $data['$id'] = $documentId; $data['$permissions'] = $permissions; $newDocument = new Document($data); @@ -3505,19 +3516,19 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu ->inject('deletes') ->inject('mode') ->action(function (string $databaseId, string $collectionId, string $documentId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $events, Delete $deletes, string $mode) { - $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); - if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + + if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } $collection = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); - if ($collection->isEmpty() || !$collection->getAttribute('enabled')) { - if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) { - throw new Exception(Exception::COLLECTION_NOT_FOUND); - } + if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { + throw new Exception(Exception::COLLECTION_NOT_FOUND); } // Read permission should not be required for delete @@ -3527,68 +3538,18 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu throw new Exception(Exception::DOCUMENT_NOT_FOUND); } - $checkPermissions = function (Document $collection, Document $document) use (&$checkPermissions, $dbForProject, $database) { - $documentSecurity = $collection->getAttribute('documentSecurity', false); - $validator = new Authorization(Database::PERMISSION_DELETE); - - $valid = $validator->isValid($collection->getDelete()); - if (!$documentSecurity && !$valid) { - throw new Exception(Exception::USER_UNAUTHORIZED); - } - - $valid = $valid || $validator->isValid($document->getDelete()); - if ($documentSecurity && !$valid) { - throw new Exception(Exception::USER_UNAUTHORIZED); - } - - $relationships = \array_filter( - $collection->getAttribute('attributes', []), - fn($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP - ); - - foreach ($relationships as $relationship) { - $related = $document->getAttribute($relationship->getAttribute('key')); - - if (empty($related)) { - continue; - } - if (!\is_array($related)) { - $related = [$related]; - } - - $relatedCollectionId = $relationship->getAttribute('relatedCollection'); - $relatedCollection = Authorization::skip( - fn() => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId) - ); - - foreach ($related as $relation) { - if ( - $relation instanceof Document - && $relationship->getAttribute('onDelete') === Database::RELATION_MUTATE_CASCADE - ) { - $checkPermissions($relatedCollection, $relation); - } - } - } - }; - - $checkPermissions($collection, $document); - - Authorization::skip(fn() => $dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $database, $collection, $documentId) { + $dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $database, $collection, $documentId) { try { $dbForProject->deleteDocument( 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId ); + } catch (AuthorizationException) { + throw new Exception(Exception::USER_UNAUTHORIZED); } catch (RestrictedException) { throw new Exception(Exception::DOCUMENT_DELETE_RESTRICTED); } - })); - - $dbForProject->deleteCachedDocument( - 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), - $documentId - ); + }); // Add $collectionId and $databaseId for all documents $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) { diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index daa5d3e0a1..a316ba8a79 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -228,7 +228,7 @@ App::post('/v1/functions') 'active' => false, ])) ); - + $function->setAttribute('scheduleId', $schedule->getId()); $function->setAttribute('scheduleInternalId', $schedule->getInternalId()); @@ -356,7 +356,9 @@ App::get('/v1/functions') } // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = \array_filter($queries, function ($query) { + return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + }); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ @@ -854,7 +856,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download') ->setContentType('application/gzip') ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache ->addHeader('X-Peak', \memory_get_peak_usage()) - ->addHeader('Content-Disposition', 'attachment; filename="'.$deploymentId.'.tar.gz"') + ->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"') ; $size = $deviceFunctions->getFileSize($path); @@ -1257,7 +1259,9 @@ App::get('/v1/functions/:functionId/deployments') $queries[] = Query::equal('resourceType', ['functions']); // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = \array_filter($queries, function ($query) { + return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + }); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ @@ -1497,10 +1501,11 @@ App::post('/v1/functions/:functionId/executions') $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); - if ($function->isEmpty() || !$function->getAttribute('enabled')) { - if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) { - throw new Exception(Exception::FUNCTION_NOT_FOUND); - } + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + + if ($function->isEmpty() || (!$function->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { + throw new Exception(Exception::FUNCTION_NOT_FOUND); } $runtimes = Config::getParam('runtimes', []); @@ -1760,13 +1765,13 @@ App::get('/v1/functions/:functionId/executions') ->inject('dbForProject') ->inject('mode') ->action(function (string $functionId, array $queries, string $search, Response $response, Database $dbForProject, string $mode) { - $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); - if ($function->isEmpty() || !$function->getAttribute('enabled')) { - if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) { - throw new Exception(Exception::FUNCTION_NOT_FOUND); - } + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + + if ($function->isEmpty() || (!$function->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { + throw new Exception(Exception::FUNCTION_NOT_FOUND); } $queries = Query::parseQueries($queries); @@ -1779,7 +1784,9 @@ App::get('/v1/functions/:functionId/executions') $queries[] = Query::equal('functionId', [$function->getId()]); // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = \array_filter($queries, function ($query) { + return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + }); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ @@ -1832,13 +1839,13 @@ App::get('/v1/functions/:functionId/executions/:executionId') ->inject('dbForProject') ->inject('mode') ->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, string $mode) { - $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); - if ($function->isEmpty() || !$function->getAttribute('enabled')) { - if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) { - throw new Exception(Exception::FUNCTION_NOT_FOUND); - } + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + + if ($function->isEmpty() || (!$function->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { + throw new Exception(Exception::FUNCTION_NOT_FOUND); } $execution = $dbForProject->getDocument('executions', $executionId); diff --git a/app/controllers/api/migrations.php b/app/controllers/api/migrations.php index 77d352fee9..d981c36ec0 100644 --- a/app/controllers/api/migrations.php +++ b/app/controllers/api/migrations.php @@ -381,7 +381,9 @@ App::get('/v1/migrations') } // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = \array_filter($queries, function ($query) { + return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + }); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 7901c587fb..ea38ba35ae 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -241,7 +241,9 @@ App::get('/v1/projects') } // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = \array_filter($queries, function ($query) { + return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + }); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ diff --git a/app/controllers/api/proxy.php b/app/controllers/api/proxy.php index 1bd01a76e6..25584fab52 100644 --- a/app/controllers/api/proxy.php +++ b/app/controllers/api/proxy.php @@ -45,7 +45,7 @@ App::post('/v1/proxy/rules') if ($domain === $mainDomain || $domain === 'localhost' || $domain === APP_HOSTNAME_INTERNAL) { throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed for security reasons.'); } - + $document = $dbForConsole->findOne('rules', [ Query::equal('domain', [$domain]), ]); diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 7d39e1c137..3adfbcd722 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -168,7 +168,9 @@ App::get('/v1/storage/buckets') } // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = \array_filter($queries, function ($query) { + return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + }); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ @@ -361,10 +363,12 @@ App::post('/v1/storage/buckets/:bucketId/files') ->inject('deviceFiles') ->inject('deviceLocal') ->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal) { - $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + + if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } @@ -691,10 +695,12 @@ App::get('/v1/storage/buckets/:bucketId/files') ->inject('dbForProject') ->inject('mode') ->action(function (string $bucketId, array $queries, string $search, Response $response, Database $dbForProject, string $mode) { - $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + + if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } @@ -712,7 +718,9 @@ App::get('/v1/storage/buckets/:bucketId/files') } // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = \array_filter($queries, function ($query) { + return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + }); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ @@ -767,10 +775,12 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId') ->inject('dbForProject') ->inject('mode') ->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, string $mode) { - $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + + if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } @@ -839,7 +849,10 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + + if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } @@ -985,7 +998,10 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + + if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } @@ -1122,10 +1138,12 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') ->inject('mode') ->inject('deviceFiles') ->action(function (string $bucketId, string $fileId, Response $response, Request $request, Database $dbForProject, string $mode, Device $deviceFiles) { - $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + + if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } @@ -1281,10 +1299,12 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') ->inject('mode') ->inject('events') ->action(function (string $bucketId, string $fileId, ?string $name, ?array $permissions, Response $response, Database $dbForProject, Document $user, string $mode, Event $events) { - $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + + if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } @@ -1389,7 +1409,10 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') ->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $events, string $mode, Device $deviceFiles, Delete $deletes) { $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + + if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index e03e481d7c..99783d5660 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -153,7 +153,9 @@ App::get('/v1/teams') } // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = \array_filter($queries, function ($query) { + return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + }); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ @@ -649,7 +651,9 @@ App::get('/v1/teams/:teamId/memberships') $queries[] = Query::equal('teamId', [$teamId]); // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = \array_filter($queries, function ($query) { + return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + }); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 1ea7e099fb..b1898e44a7 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -405,7 +405,9 @@ App::get('/v1/users') } // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = \array_filter($queries, function ($query) { + return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + }); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ @@ -670,7 +672,9 @@ App::get('/v1/users/identities') } // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = \array_filter($queries, function ($query) { + return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + }); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index aafd862c3f..9dd800b69b 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -220,7 +220,10 @@ App::init() $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + + if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } diff --git a/app/init.php b/app/init.php index d5d0ea9748..11d8c6e2cb 100644 --- a/app/init.php +++ b/app/init.php @@ -293,7 +293,7 @@ Database::addFilter( return $value; }, function (mixed $value, Document $attribute) { - $formatOptions = json_decode($attribute->getAttribute('formatOptions', '[]'), true); + $formatOptions = \json_decode($attribute->getAttribute('formatOptions', '[]'), true); if (isset($formatOptions['elements'])) { $attribute->setAttribute('elements', $formatOptions['elements']); } @@ -363,7 +363,7 @@ Database::addFilter( ->find('indexes', [ Query::equal('collectionInternalId', [$document->getInternalId()]), Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]), - Query::limit(64), + Query::limit($database->getLimitForIndexes()), ]); } ); diff --git a/composer.json b/composer.json index b8c7e6b477..fc895056f9 100644 --- a/composer.json +++ b/composer.json @@ -58,7 +58,7 @@ "utopia-php/logger": "0.3.*", "utopia-php/messaging": "0.1.*", "utopia-php/orchestration": "0.9.*", - "utopia-php/platform": "0.4.1", + "utopia-php/platform": "0.4.*", "utopia-php/pools": "0.4.*", "utopia-php/preloader": "0.2.*", "utopia-php/queue": "0.5.*", diff --git a/composer.lock b/composer.lock index e86397caec..e3a016ba3e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "83427d18095b42c274d2bd6eb302e4aa", + "content-hash": "12d4da6b26d9f15f632cd681a837e026", "packages": [ { "name": "adhocore/jwt", @@ -2220,19 +2220,20 @@ }, { "name": "utopia-php/database", - "version": "0.42.2", + "version": "0.42.3", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "bc5ceb30c85fb685b0b5704d2f74886d813ebd41" + "reference": "ab0e2f8ad46884f69b354cd8ee84a1a75fee26d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/bc5ceb30c85fb685b0b5704d2f74886d813ebd41", - "reference": "bc5ceb30c85fb685b0b5704d2f74886d813ebd41", + "url": "https://api.github.com/repos/utopia-php/database/zipball/ab0e2f8ad46884f69b354cd8ee84a1a75fee26d1", + "reference": "ab0e2f8ad46884f69b354cd8ee84a1a75fee26d1", "shasum": "" }, "require": { + "ext-mbstring": "*", "ext-pdo": "*", "php": ">=8.0", "utopia-php/cache": "0.8.*", @@ -2270,9 +2271,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.42.2" + "source": "https://github.com/utopia-php/database/tree/0.42.3" }, - "time": "2023-08-17T19:04:37+00:00" + "time": "2023-08-22T02:15:28+00:00" }, { "name": "utopia-php/domains", @@ -3227,16 +3228,16 @@ }, { "name": "utopia-php/vcs", - "version": "dev-feat-clone-commit-hash", + "version": "0.2.0", "source": { "type": "git", "url": "https://github.com/utopia-php/vcs.git", - "reference": "5ed5d1685b3b714d6f20f7922ab4f5f5188313ee" + "reference": "ec388e056fec8675b5fe1fc7a4f77e4dc3f328cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/vcs/zipball/5ed5d1685b3b714d6f20f7922ab4f5f5188313ee", - "reference": "5ed5d1685b3b714d6f20f7922ab4f5f5188313ee", + "url": "https://api.github.com/repos/utopia-php/vcs/zipball/ec388e056fec8675b5fe1fc7a4f77e4dc3f328cf", + "reference": "ec388e056fec8675b5fe1fc7a4f77e4dc3f328cf", "shasum": "" }, "require": { @@ -3270,9 +3271,9 @@ ], "support": { "issues": "https://github.com/utopia-php/vcs/issues", - "source": "https://github.com/utopia-php/vcs/tree/feat-clone-commit-hash" + "source": "https://github.com/utopia-php/vcs/tree/0.2.0" }, - "time": "2023-08-21T09:02:54+00:00" + "time": "2023-08-21T10:59:48+00:00" }, { "name": "utopia-php/websocket", @@ -6069,18 +6070,9 @@ "time": "2023-07-26T07:16:09+00:00" } ], - "aliases": [ - { - "package": "utopia-php/vcs", - "version": "dev-feat-clone-commit-hash", - "alias": "0.1.99", - "alias_normalized": "0.1.99.0" - } - ], + "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "utopia-php/vcs": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -6104,5 +6096,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.3.0" } diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index c2c1c70bd6..f982d25cca 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -344,6 +344,63 @@ trait DatabasesBase $this->assertEquals(400, $response['headers']['status-code']); } + public function testUpdateAttributeEnum(): void + { + $database = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'Test Database 2' + ]); + + $players = $this->client->call(Client::METHOD_POST, '/databases/' . $database['body']['$id'] . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'Players', + 'documentSecurity' => true, + 'permissions' => [ + Permission::create(Role::user($this->getUser()['$id'])), + ], + ]); + + // Create enum attribute + $attribute = $this->client->call(Client::METHOD_POST, '/databases/' . $database['body']['$id'] . '/collections/' . $players['body']['$id'] . '/attributes/enum', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), [ + 'key' => 'position', + 'elements' => ['goalkeeper', 'defender', 'midfielder', 'forward'], + 'required' => true, + 'array' => false, + ]); + + $this->assertEquals(202, $attribute['headers']['status-code']); + $this->assertEquals($attribute['body']['key'], 'position'); + $this->assertEquals($attribute['body']['elements'], ['goalkeeper', 'defender', 'midfielder', 'forward']); + + \sleep(2); + + // Update enum attribute + $attribute = $this->client->call(Client::METHOD_PATCH, '/databases/' . $database['body']['$id'] . '/collections/' . $players['body']['$id'] . '/attributes/enum/' . $attribute['body']['key'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), [ + 'elements' => ['goalkeeper', 'defender', 'midfielder', 'forward', 'coach'], + 'required' => true, + 'default' => null + ]); + + $this->assertEquals(200, $attribute['headers']['status-code']); + $this->assertEquals($attribute['body']['elements'], ['goalkeeper', 'defender', 'midfielder', 'forward', 'coach']); + } + /** * @depends testCreateAttributes */ @@ -2862,8 +2919,6 @@ trait DatabasesBase $collectionId = $collection['body']['$id']; - sleep(2); - $attribute = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -2877,8 +2932,7 @@ trait DatabasesBase $this->assertEquals(202, $attribute['headers']['status-code'], 202); $this->assertEquals('attribute', $attribute['body']['key']); - // wait for db to add attribute - sleep(2); + \sleep(2); $index = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/indexes', array_merge([ 'content-type' => 'application/json', @@ -2893,8 +2947,7 @@ trait DatabasesBase $this->assertEquals(202, $index['headers']['status-code']); $this->assertEquals('key_attribute', $index['body']['key']); - // wait for db to add attribute - sleep(2); + \sleep(2); $document1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', array_merge([ 'content-type' => 'application/json', @@ -2993,7 +3046,7 @@ trait DatabasesBase 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session2, ]); - // Current user has no collection permissions and document permissions are disabled + // other2 has no collection permissions and document permissions are disabled $this->assertEquals(404, $document3GetWithDocumentRead['headers']['status-code']); $documentsUser2 = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', [ @@ -3003,8 +3056,8 @@ trait DatabasesBase 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session2, ]); - // Current user has no collection permissions and document permissions are disabled - $this->assertEquals(404, $documentsUser2['headers']['status-code']); + // other2 has no collection permissions and document permissions are disabled + $this->assertEquals(401, $documentsUser2['headers']['status-code']); // Enable document permissions $collection = $this->client->call(CLient::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $collectionId, [ diff --git a/tests/e2e/Services/Databases/DatabasesConsoleClientTest.php b/tests/e2e/Services/Databases/DatabasesConsoleClientTest.php index 2458e4080e..04ac00c263 100644 --- a/tests/e2e/Services/Databases/DatabasesConsoleClientTest.php +++ b/tests/e2e/Services/Databases/DatabasesConsoleClientTest.php @@ -29,6 +29,7 @@ class DatabasesConsoleClientTest extends Scope $this->assertTrue($database['body']['enabled']); $databaseId = $database['body']['$id']; + /** * Test for SUCCESS */ @@ -51,7 +52,7 @@ class DatabasesConsoleClientTest extends Scope $this->assertEquals($movies['body']['name'], 'Movies'); /** - * Test When database is disabled but can still create collections + * Test when database is disabled but can still create collections */ $database = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId, array_merge([ 'content-type' => 'application/json', @@ -78,6 +79,17 @@ class DatabasesConsoleClientTest extends Scope 'documentSecurity' => true, ]); + /** + * Test when collection is disabled but can still modify collections + */ + $database = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $movies['body']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Movies', + 'enabled' => false, + ]); + $this->assertEquals(201, $tvShows['headers']['status-code']); $this->assertEquals($tvShows['body']['name'], 'TvShows'); @@ -87,11 +99,12 @@ class DatabasesConsoleClientTest extends Scope /** * @depends testCreateCollection * @param array $data + * @throws \Exception */ public function testListCollection(array $data) { /** - * Test When database is disabled but can still call list collections + * Test when database is disabled but can still call list collections */ $databaseId = $data['databaseId']; @@ -108,49 +121,77 @@ class DatabasesConsoleClientTest extends Scope /** * @depends testCreateCollection * @param array $data + * @throws \Exception */ - // public function testGetDatabaseUsage(array $data) - // { - // $databaseId = $data['databaseId']; - // /** - // * Test for FAILURE - // */ + public function testGetCollection(array $data) + { + $databaseId = $data['databaseId']; + $moviesCollectionId = $data['moviesId']; - // $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/usage', array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'] - // ], $this->getHeaders()), [ - // 'range' => '32h' - // ]); + /** + * Test when database and collection are disabled but can still call get collection + */ + $collection = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $moviesCollectionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); - // $this->assertEquals(400, $response['headers']['status-code']); + $this->assertEquals(200, $collection['headers']['status-code']); + $this->assertEquals('Movies', $collection['body']['name']); + $this->assertEquals($moviesCollectionId, $collection['body']['$id']); + $this->assertFalse($collection['body']['enabled']); + } - // /** - // * Test for SUCCESS - // */ + /** + * @depends testCreateCollection + * @param array $data + * @throws \Exception + * @throws \Exception + */ + public function testUpdateCollection(array $data) + { + $databaseId = $data['databaseId']; + $moviesCollectionId = $data['moviesId']; - // $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/usage', array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'] - // ], $this->getHeaders()), [ - // 'range' => '24h' - // ]); + /** + * Test When database and collection are disabled but can still call update collection + */ + $collection = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $moviesCollectionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Movies Updated', + 'enabled' => false + ]); - // $this->assertEquals(200, $response['headers']['status-code']); - // $this->assertEquals(count($response['body']), 11); - // $this->assertEquals($response['body']['range'], '24h'); - // $this->assertIsArray($response['body']['documentsCount']); - // $this->assertIsArray($response['body']['collectionsCount']); - // $this->assertIsArray($response['body']['documentsCreate']); - // $this->assertIsArray($response['body']['documentsRead']); - // $this->assertIsArray($response['body']['documentsUpdate']); - // $this->assertIsArray($response['body']['documentsDelete']); - // $this->assertIsArray($response['body']['collectionsCreate']); - // $this->assertIsArray($response['body']['collectionsRead']); - // $this->assertIsArray($response['body']['collectionsUpdate']); - // $this->assertIsArray($response['body']['collectionsDelete']); - // } + $this->assertEquals(200, $collection['headers']['status-code']); + $this->assertEquals('Movies Updated', $collection['body']['name']); + $this->assertEquals($moviesCollectionId, $collection['body']['$id']); + $this->assertFalse($collection['body']['enabled']); + } + /** + * @depends testCreateCollection + * @param array $data + * @throws \Exception + * @throws \Exception + */ + public function testDeleteCollection(array $data) + { + $databaseId = $data['databaseId']; + $tvShowsId = $data['tvShowsId']; + + /** + * Test when database and collection are disabled but can still call delete collection + */ + $response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $tvShowsId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(204, $response['headers']['status-code']); + $this->assertEquals($response['body'], ""); + } /** * @depends testCreateCollection diff --git a/tests/e2e/Services/Databases/DatabasesPermissionsTeamTest.php b/tests/e2e/Services/Databases/DatabasesPermissionsTeamTest.php index dcbf3e4bff..8377b9c803 100644 --- a/tests/e2e/Services/Databases/DatabasesPermissionsTeamTest.php +++ b/tests/e2e/Services/Databases/DatabasesPermissionsTeamTest.php @@ -176,7 +176,7 @@ class DatabasesPermissionsTeamTest extends Scope if ($success) { $this->assertCount(1, $documents['body']['documents']); } else { - $this->assertEquals(404, $documents['headers']['status-code']); + $this->assertEquals(401, $documents['headers']['status-code']); } }