diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 605770d489..8fb3ac3518 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -46,7 +46,7 @@ jobs: - name: Start Appwrite run: | docker compose up -d - sleep 60 + sleep 30 - name: Doctor run: | diff --git a/app/config/errors.php b/app/config/errors.php index ed2b897d90..3638c616ce 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -363,6 +363,11 @@ return [ 'description' => 'The requested range is not satisfiable. Please check the value of the Range header.', 'code' => 416, ], + Exception::STORAGE_INVALID_APPWRITE_ID => [ + 'name' => Exception::STORAGE_INVALID_APPWRITE_ID, + 'description' => 'The value for x-appwrite-id header is invalid. Please check the value of the x-appwrite-id header is valid id and not unique().', + 'code' => 400, + ], /** VCS */ Exception::INSTALLATION_NOT_FOUND => [ @@ -385,6 +390,11 @@ return [ 'description' => 'External contribution is already authorized.', 'code' => 409, ], + Exception::GENERAL_PROVIDER_FAILURE => [ + 'name' => Exception::GENERAL_PROVIDER_FAILURE, + 'description' => 'VCS (Version Control System) provider failed to proccess the request.', + 'code' => 400, + ], /** Functions */ Exception::FUNCTION_NOT_FOUND => [ diff --git a/app/config/locale/translations/pt-pt.json b/app/config/locale/translations/pt-pt.json index e257b47281..cf9ef377a8 100644 --- a/app/config/locale/translations/pt-pt.json +++ b/app/config/locale/translations/pt-pt.json @@ -17,7 +17,7 @@ "emails.magicSession.signature": "Equipa {{project}}", "emails.recovery.subject": "Redefinição de senha", "emails.recovery.hello": "Olá {{name}}", - "emails.recovery.body": "tilize este link para redefinir a palavra-passe do seu projecto {{project}}", + "emails.recovery.body": "Utilize este link para redefinir a palavra-passe do seu projecto {{project}}", "emails.recovery.footer": "Se não pediu para redefinir a sua palavra-passe, pode ignorar esta mensagem.", "emails.recovery.thanks": "Obrigado", "emails.recovery.signature": "Equipa {{project}}", diff --git a/app/console b/app/console index a1ec99e863..451e591a1d 160000 --- a/app/console +++ b/app/console @@ -1 +1 @@ -Subproject commit a1ec99e86331e5eeb56614c69f50e83f334deafb +Subproject commit 451e591a1d6c0843fdaef131f1064fd6cdbb2a3b diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 353f207f37..50f3d7ea7a 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1086,11 +1086,12 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string ->param('required', null, new Boolean(), 'Is attribute required?') ->param('default', null, new Text(0, 0), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true) ->param('array', false, new Boolean(), 'Is attribute an array?', true) + ->param('encrypt', false, new Boolean(), 'Toggle encryption for the attribute. Encryption enhances security by not storing any plain text values in the database. However, encrypted attributes cannot be queried.', true) ->inject('response') ->inject('dbForProject') ->inject('database') ->inject('events') - ->action(function (string $databaseId, string $collectionId, string $key, ?int $size, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { + ->action(function (string $databaseId, string $collectionId, string $key, ?int $size, ?bool $required, ?string $default, bool $array, bool $encrypt, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { // Ensure attribute default is within required size $validator = new Text($size, 0); @@ -1098,6 +1099,12 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, $validator->getDescription()); } + $filters = []; + + if ($encrypt) { + $filters[] = 'encrypt'; + } + $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, 'type' => Database::VAR_STRING, @@ -1105,6 +1112,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string 'required' => $required, 'default' => $default, 'array' => $array, + 'filters' => $filters, ]), $response, $dbForProject, $database, $events); $response @@ -1509,6 +1517,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti ->inject('events') ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { + $filters[] = 'datetime'; + $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, 'type' => Database::VAR_DATETIME, @@ -1516,7 +1526,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti 'required' => $required, 'default' => $default, 'array' => $array, - 'filters' => ['datetime'] + 'filters' => $filters, ]), $response, $dbForProject, $database, $events); $response @@ -1739,6 +1749,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/strin ->inject('dbForProject') ->inject('events') ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, Response $response, Database $dbForProject, Event $events) { + $attribute = updateAttribute( databaseId: $databaseId, collectionId: $collectionId, diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index b3375340f2..16bd9cc974 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -142,7 +142,7 @@ App::post('/v1/functions') ->param('enabled', true, new Boolean(), 'Is function enabled?', true) ->param('logging', true, new Boolean(), 'Do executions get logged?', true) ->param('entrypoint', '', new Text(1028), 'Entrypoint File.') - ->param('commands', '', new Text(1028, 0), 'Build Commands.', true) + ->param('commands', '', new Text(8192, 0), 'Build Commands.', true) ->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for vcs deployment.', true) ->param('providerRepositoryId', '', new Text(128, 0), 'Repository ID of the repo linked to the function', true) ->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function', true) @@ -165,7 +165,12 @@ App::post('/v1/functions') // build from template $template = new Document([]); - if (!empty($templateRepository) && !empty($templateOwner) && !empty($templateRootDirectory) && !empty($templateBranch)) { + if ( + !empty($templateRepository) + && !empty($templateOwner) + && !empty($templateRootDirectory) + && !empty($templateBranch) + ) { $template->setAttribute('repositoryName', $templateRepository) ->setAttribute('ownerName', $templateOwner) ->setAttribute('rootDirectory', $templateRootDirectory) @@ -609,7 +614,7 @@ App::put('/v1/functions/:functionId') ->param('enabled', true, new Boolean(), 'Is function enabled?', true) ->param('logging', true, new Boolean(), 'Do executions get logged?', true) ->param('entrypoint', '', new Text(1028), 'Entrypoint File.') - ->param('commands', '', new Text(1028, 0), 'Build Commands.', true) + ->param('commands', '', new Text(8192, 0), 'Build Commands.', true) ->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for vcs deployment.', true) ->param('providerRepositoryId', '', new Text(128, 0), 'Repository ID of the repo linked to the function', true) ->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function', true) @@ -1273,7 +1278,8 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId') ->inject('dbForConsole') ->inject('project') ->inject('gitHub') - ->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Database $dbForConsole, Document $project, GitHub $github) use ($redeployVcs) { + ->inject('events') + ->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Database $dbForConsole, Document $project, GitHub $github, Event $events) use ($redeployVcs) { $function = $dbForProject->getDocument('functions', $functionId); $deployment = $dbForProject->getDocument('deployments', $deploymentId); @@ -1298,6 +1304,10 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId') $redeployVcs($request, $function, $project, $installation, $dbForProject, new Document([]), $github); + $events + ->setParam('functionId', $function->getId()) + ->setParam('deploymentId', $deployment->getId()); + $response->noContent(); }); @@ -1318,7 +1328,7 @@ App::post('/v1/functions/:functionId/executions') ->param('async', false, new Boolean(), 'Execute code in the background. Default value is false.', true) ->param('path', '/', new Text(2048), 'HTTP path of execution. Path can include query params. Default value is /', true) ->param('method', 'POST', new Whitelist(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], true), 'HTTP method of execution. Default value is GET.', true) - ->param('headers', [], new Assoc(), 'HTP headers of execution. Defaults to empty.', true) + ->param('headers', [], new Assoc(), 'HTTP headers of execution. Defaults to empty.', true) ->inject('response') ->inject('project') ->inject('dbForProject') @@ -1476,11 +1486,9 @@ App::post('/v1/functions/:functionId/executions') $vars = []; // Shared vars - $varsShared = $project->getAttribute('variables', []); - $vars = \array_merge($vars, \array_reduce($varsShared, function (array $carry, Document $var) { - $carry[$var->getAttribute('key')] = $var->getAttribute('value') ?? ''; - return $carry; - }, [])); + foreach ($project->getAttribute('variables', []) as $var) { + $vars[$var->getAttribute('key')] = $var->getAttribute('value', ''); + } // Function vars $vars = \array_merge($vars, array_reduce($function->getAttribute('vars', []), function (array $carry, Document $var) { @@ -1825,7 +1833,12 @@ App::get('/v1/functions/:functionId/variables/:variableId') } $variable = $dbForProject->getDocument('variables', $variableId); - if ($variable === false || $variable->isEmpty() || $variable->getAttribute('resourceInternalId') !== $function->getInternalId() || $variable->getAttribute('resourceType') !== 'function') { + if ( + $variable === false || + $variable->isEmpty() || + $variable->getAttribute('resourceInternalId') !== $function->getInternalId() || + $variable->getAttribute('resourceType') !== 'function' + ) { throw new Exception(Exception::VARIABLE_NOT_FOUND); } diff --git a/app/controllers/api/proxy.php b/app/controllers/api/proxy.php index 0a5153df07..c55dc0e4dc 100644 --- a/app/controllers/api/proxy.php +++ b/app/controllers/api/proxy.php @@ -56,7 +56,7 @@ App::post('/v1/proxy/rules') $message .= '.'; } else { - $message = "Domain already assigned to different project."; + $message = 'Domain already assigned to different project.'; } throw new Exception(Exception::RULE_ALREADY_EXISTS, $message); diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index dafb8a1409..c34515b5e1 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -443,6 +443,11 @@ App::post('/v1/storage/buckets/:bucketId/files') throw new Exception(Exception::STORAGE_INVALID_CONTENT_RANGE); } + $idValidator = new UID(); + if (!$idValidator->isValid($request->getHeader('x-appwrite-id'))) { + throw new Exception(Exception::STORAGE_INVALID_APPWRITE_ID); + } + // TODO remove the condition that checks `$end === $fileSize` in next breaking version if ($end === $fileSize - 1 || $end === $fileSize) { //if it's a last chunks the chunk size might differ, so we set the $chunks and $chunk to -1 notify it's last chunk diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index e6797cc43f..0f8ee9f5f4 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -36,6 +36,7 @@ use Utopia\Detector\Adapter\Swift; use Utopia\Detector\Detector; use Utopia\Validator\Boolean; +use function PHPUnit\Framework\throwException; use function Swoole\Coroutine\batch; $createGitDeployments = function (GitHub $github, string $providerInstallationId, array $repositories, string $providerBranch, string $providerBranchUrl, string $providerRepositoryName, string $providerRepositoryUrl, string $providerRepositoryOwner, string $providerCommitHash, string $providerCommitAuthor, string $providerCommitAuthorUrl, string $providerCommitMessage, string $providerCommitUrl, string $providerPullRequestId, bool $external, Database $dbForConsole, callable $getProjectDB, Request $request) { @@ -702,7 +703,11 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories') $dbForConsole->updateDocument('identities', $identity->getId(), $identity); } - $repository = $oauth2->createRepository($accessToken, $name, $private); + try { + $repository = $oauth2->createRepository($accessToken, $name, $private); + } catch (Exception $exception) { + throw new Exception(Exception::GENERAL_PROVIDER_FAILURE, "GitHub failed to process the request: " . $exception->getMessage()); + } } else { $providerInstallationId = $installation->getAttribute('providerInstallationId'); $privateKey = App::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY'); @@ -710,7 +715,11 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories') $github->initializeVariables($providerInstallationId, $privateKey, $githubAppId); $owner = $github->getOwnerName($providerInstallationId); - $repository = $github->createRepository($owner, $name, $private); + try { + $repository = $github->createRepository($owner, $name, $private); + } catch (Exception $exception) { + throw new Exception(Exception::GENERAL_PROVIDER_FAILURE, "GitHub failed to process the request: " . $exception->getMessage()); + } } if (isset($repository['message'])) { diff --git a/app/controllers/general.php b/app/controllers/general.php index 9dbb40c9a8..530f76584b 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -204,7 +204,7 @@ App::init() Request::setRoute($route); if ($route === null) { - return $response->setStatusCode(404)->send("Not Found"); + return $response->setStatusCode(404)->send('Not Found'); } $requestFormat = $request->getHeader('x-appwrite-response-format', App::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', '')); diff --git a/app/workers/builds.php b/app/workers/builds.php index 038650acd7..f9fc66f2f2 100644 --- a/app/workers/builds.php +++ b/app/workers/builds.php @@ -330,14 +330,15 @@ class BuildsV1 extends Worker $vars = []; - // global vars - $vars = \array_merge($vars, \array_reduce($dbForProject->find('variables', [ + // Global vars + $varsFromProject = $dbForProject->find('variables', [ Query::equal('resourceType', ['project']), Query::limit(APP_LIMIT_SUBQUERY) - ]), function (array $carry, Document $var) { - $carry[$var->getAttribute('key')] = $var->getAttribute('value') ?? ''; - return $carry; - }, [])); + ]); + + foreach ($varsFromProject as $var) { + $vars[$var->getAttribute('key')] = $var->getAttribute('value') ?? ''; + } // Function vars $vars = \array_merge($vars, array_reduce($function->getAttribute('vars', []), function (array $carry, Document $var) { @@ -454,12 +455,15 @@ class BuildsV1 extends Worker /** Update function schedule */ $dbForConsole = $this->getConsoleDB(); // Inform scheduler if function is still active - $schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId')); - $schedule - ->setAttribute('resourceUpdatedAt', DateTime::now()) - ->setAttribute('schedule', $function->getAttribute('schedule')) - ->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment'))); - Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); + $scheduleId = $function->getAttribute('scheduleId', ''); + if (!empty($scheduleId)) { + $schedule = $dbForConsole->getDocument('schedules', $scheduleId); + $schedule + ->setAttribute('resourceUpdatedAt', DateTime::now()) + ->setAttribute('schedule', $function->getAttribute('schedule')) + ->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment'))); + Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); + } } catch (\Throwable $th) { $endTime = DateTime::now(); $durationEnd = \microtime(true); diff --git a/composer.json b/composer.json index 69291bf077..c5d11d7f4a 100644 --- a/composer.json +++ b/composer.json @@ -85,7 +85,7 @@ } ], "require-dev": { - "appwrite/sdk-generator": "dev-master as 0.33.99", + "appwrite/sdk-generator": "0.34.*", "ext-fileinfo": "*", "phpunit/phpunit": "9.5.20", "squizlabs/php_codesniffer": "^3.7", diff --git a/composer.lock b/composer.lock index df4926d764..99ff7293a0 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": "b87e28f6f096af1fd3b1ddee62fe2f13", + "content-hash": "e4934eff80bec5e9fe402528df07d72d", "packages": [ { "name": "adhocore/jwt", @@ -727,16 +727,16 @@ }, { "name": "matomo/device-detector", - "version": "6.1.4", + "version": "6.1.5", "source": { "type": "git", "url": "https://github.com/matomo-org/device-detector.git", - "reference": "74f6c4f6732b3ad6cdf25560746841d522969112" + "reference": "40ca2990dba2c1719e5c62168e822e0b86c167d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/matomo-org/device-detector/zipball/74f6c4f6732b3ad6cdf25560746841d522969112", - "reference": "74f6c4f6732b3ad6cdf25560746841d522969112", + "url": "https://api.github.com/repos/matomo-org/device-detector/zipball/40ca2990dba2c1719e5c62168e822e0b86c167d4", + "reference": "40ca2990dba2c1719e5c62168e822e0b86c167d4", "shasum": "" }, "require": { @@ -792,7 +792,7 @@ "source": "https://github.com/matomo-org/matomo", "wiki": "https://dev.matomo.org/" }, - "time": "2023-08-02T08:48:53+00:00" + "time": "2023-08-17T16:17:41+00:00" }, { "name": "mongodb/mongodb", @@ -1963,16 +1963,16 @@ }, { "name": "utopia-php/migration", - "version": "0.3.0", + "version": "0.3.1", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "8a1d4de19002e4c8eabf368edff6b653b6722880" + "reference": "af4233f4ff6a37982dad294033199ce29cafc00c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/8a1d4de19002e4c8eabf368edff6b653b6722880", - "reference": "8a1d4de19002e4c8eabf368edff6b653b6722880", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/af4233f4ff6a37982dad294033199ce29cafc00c", + "reference": "af4233f4ff6a37982dad294033199ce29cafc00c", "shasum": "" }, "require": { @@ -2015,9 +2015,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/0.3.0" + "source": "https://github.com/utopia-php/migration/tree/0.3.1" }, - "time": "2023-08-16T11:57:13+00:00" + "time": "2023-08-17T14:18:09+00:00" }, { "name": "utopia-php/mongo", @@ -2564,6 +2564,12 @@ "url": "https://github.com/utopia-php/vcs.git", "reference": "fc9c38a3f84a4391470cc7184199dec6f953b144" }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/vcs/zipball/fc9c38a3f84a4391470cc7184199dec6f953b144", + "reference": "fc9c38a3f84a4391470cc7184199dec6f953b144", + "shasum": "" + }, "require": { "adhocore/jwt": "^1.1", "php": ">=8.0", @@ -2582,25 +2588,7 @@ "Utopia\\Detector\\": "src/Detector" } }, - "autoload-dev": { - "psr-4": { - "Utopia\\Tests\\": "tests/VCS" - } - }, - "scripts": { - "lint": [ - "./vendor/bin/pint --test --config pint.json" - ], - "format": [ - "./vendor/bin/pint --config pint.json" - ], - "check": [ - "./vendor/bin/phpstan analyse --level 8 -c phpstan.neon src tests" - ], - "test": [ - "./vendor/bin/phpunit --configuration phpunit.xml --debug" - ] - }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -2611,6 +2599,10 @@ "utopia", "vcs" ], + "support": { + "issues": "https://github.com/utopia-php/vcs/issues", + "source": "https://github.com/utopia-php/vcs/tree/0.1.0" + }, "time": "2023-08-09T20:48:51+00:00" }, { @@ -2798,7 +2790,7 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "dev-master", + "version": "0.34.0", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", @@ -2823,7 +2815,6 @@ "phpunit/phpunit": "^9.5.21", "squizlabs/php_codesniffer": "^3.6" }, - "default-branch": true, "type": "library", "autoload": { "psr-4": { @@ -5409,18 +5400,9 @@ "time": "2023-07-26T07:16:09+00:00" } ], - "aliases": [ - { - "package": "appwrite/sdk-generator", - "version": "9999999-dev", - "alias": "0.33.99", - "alias_normalized": "0.33.99.0" - } - ], + "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "appwrite/sdk-generator": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index 4f467f0b1d..aca8fd6a44 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -115,12 +115,14 @@ class Exception extends \Exception public const STORAGE_BUCKET_NOT_FOUND = 'storage_bucket_not_found'; public const STORAGE_INVALID_CONTENT_RANGE = 'storage_invalid_content_range'; public const STORAGE_INVALID_RANGE = 'storage_invalid_range'; + public const STORAGE_INVALID_APPWRITE_ID = 'storage_invalid_appwrite_id'; /** VCS */ public const INSTALLATION_NOT_FOUND = 'installation_not_found'; public const PROVIDER_REPOSITORY_NOT_FOUND = 'provider_repository_not_found'; public const REPOSITORY_NOT_FOUND = 'repository_not_found'; public const PROVIDER_CONTRIBUTION_CONFLICT = 'provider_contribution_conflict'; + public const GENERAL_PROVIDER_FAILURE = 'general_provider_failure'; /** Functions */ public const FUNCTION_NOT_FOUND = 'function_not_found'; diff --git a/src/Appwrite/Migration/Version/V19.php b/src/Appwrite/Migration/Version/V19.php index 75cd472ebb..39522aff0b 100644 --- a/src/Appwrite/Migration/Version/V19.php +++ b/src/Appwrite/Migration/Version/V19.php @@ -29,6 +29,7 @@ class V19 extends Migration $this->projectDB->setNamespace("_{$this->project->getInternalId()}"); $this->alterPermissionIndex('_metadata'); + $this->alterUidType('_metadata'); Console::info('Migrating Databases'); $this->migrateDatabases(); @@ -57,11 +58,13 @@ class V19 extends Migration $databaseTable = "database_{$database->getInternalId()}"; $this->alterPermissionIndex($databaseTable); + $this->alterUidType($databaseTable); foreach ($this->documentsIterator($databaseTable) as $collection) { $collectionTable = "{$databaseTable}_collection_{$collection->getInternalId()}"; Console::log("Migrating Collections of {$collectionTable} {$collection->getId()} ({$collection->getAttribute('name')})"); $this->alterPermissionIndex($collectionTable); + $this->alterUidType($collectionTable); } } } @@ -98,6 +101,7 @@ class V19 extends Migration } if (!in_array($id, ['files', 'collections'])) { $this->alterPermissionIndex($id); + $this->alterUidType($id); } usleep(50000); @@ -131,7 +135,7 @@ class V19 extends Migration protected function alterPermissionIndex($collectionName): void { try { - $table = "`{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}_{$collectionName}_perms"; + $table = "`{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}_{$collectionName}_perms`"; $this->pdo->prepare(" ALTER TABLE {$table} DROP INDEX `_permission`, @@ -142,6 +146,19 @@ class V19 extends Migration } } + protected function alterUidType($collectionName): void + { + try { + $table = "`{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}_{$collectionName}`"; + $this->pdo->prepare(" + ALTER TABLE {$table} + CHANGE COLUMN `_uid` `_uid` VARCHAR(255) NOT NULL ; + ")->execute(); + } catch (\Throwable $th) { + Console::warning($th->getMessage()); + } + } + /** * Migrating all Bucket tables. * @@ -155,6 +172,7 @@ class V19 extends Migration $id = "bucket_{$bucket->getInternalId()}"; Console::log("Migrating Bucket {$id} {$bucket->getId()} ({$bucket->getAttribute('name')})"); $this->alterPermissionIndex($id); + $this->alterUidType($id); } } } diff --git a/src/Appwrite/Utopia/Response/Filters/V12.php b/src/Appwrite/Utopia/Response/Filters/V12.php index aaf885996b..d9e4caacfd 100644 --- a/src/Appwrite/Utopia/Response/Filters/V12.php +++ b/src/Appwrite/Utopia/Response/Filters/V12.php @@ -81,7 +81,6 @@ class V12 extends Filter case Response::MODEL_WEBHOOK_LIST: case Response::MODEL_KEY_LIST: case Response::MODEL_PLATFORM_LIST: - // case Response::MODEL_DOMAIN_LIST: case Response::MODEL_COUNTRY_LIST: case Response::MODEL_CONTINENT_LIST: case Response::MODEL_LANGUAGE_LIST: diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php index cb817be31f..4ee1625f8a 100644 --- a/src/Executor/Executor.php +++ b/src/Executor/Executor.php @@ -100,8 +100,6 @@ class Executor } /** - * - * * Listen to realtime logs stream of a runtime * * @param string $deploymentId diff --git a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php index 5e734fbcb2..648a4de800 100644 --- a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php +++ b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php @@ -593,6 +593,110 @@ class DatabasesCustomServerTest extends Scope $this->assertFalse($collection['body']['enabled']); } + /** + * @depends testListCollections + */ + public function testCreateEncryptedAttribute(array $data): void + { + + $databaseId = $data['databaseId']; + + /** + * Test for SUCCESS + */ + + // Create collection + $actors = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'Encrypted Actors Data', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'documentSecurity' => true, + ]); + + $this->assertEquals(201, $actors['headers']['status-code']); + $this->assertEquals($actors['body']['name'], 'Encrypted Actors Data'); + + /** + * Test for creating encrypted attributes + */ + + $attributesPath = '/databases/' . $databaseId . '/collections/' . $actors['body']['$id'] . '/attributes'; + + $firstName = $this->client->call(Client::METHOD_POST, $attributesPath . '/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'firstName', + 'size' => 256, + 'required' => true, + ]); + + $lastName = $this->client->call(Client::METHOD_POST, $attributesPath . '/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'lastName', + 'size' => 256, + 'required' => true, + 'encrypt' => true, + ]); + + + /** + * Check status of every attribute + */ + $this->assertEquals(202, $firstName['headers']['status-code']); + $this->assertEquals('firstName', $firstName['body']['key']); + $this->assertEquals('string', $firstName['body']['type']); + + $this->assertEquals(202, $lastName['headers']['status-code']); + $this->assertEquals('lastName', $lastName['body']['key']); + $this->assertEquals('string', $lastName['body']['type']); + + // Wait for database worker to finish creating attributes + sleep(2); + + // Creating document to ensure cache is purged on schema change + $document = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $actors['body']['$id'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'documentId' => ID::unique(), + 'data' => [ + 'firstName' => 'Jonah', + 'lastName' => 'Jameson', + ], + 'permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + // Check document to ensure cache is purged on schema change + $document = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $actors['body']['$id'] . '/documents/' . $document['body']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(200, $document['headers']['status-code']); + $this->assertEquals('Jonah', $document['body']['firstName']); + $this->assertEquals('Jameson', $document['body']['lastName']); + } + public function testDeleteAttribute(): array { $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ diff --git a/tests/e2e/Services/Storage/StorageBase.php b/tests/e2e/Services/Storage/StorageBase.php index b4a457bab5..b19fe49ed0 100644 --- a/tests/e2e/Services/Storage/StorageBase.php +++ b/tests/e2e/Services/Storage/StorageBase.php @@ -241,6 +241,29 @@ trait StorageBase $this->assertEquals(400, $failedBucket['headers']['status-code']); + /** + * Test for FAILURE set x-appwrite-id to unique() + */ + $source = realpath(__DIR__ . '/../../../resources/logo.png'); + $totalSize = \filesize($source); + $res = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', array_merge([ + 'content-type' => 'multipart/form-data', + 'x-appwrite-project' => $this->getProject()['$id'], + 'content-range' => 'bytes 0-' . $size . '/' . $size, + 'x-appwrite-id' => 'unique()', + ], $this->getHeaders()), [ + 'fileId' => ID::unique(), + 'file' => new CURLFile($source, 'image/png', 'logo.png'), + 'permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $this->assertEquals(400, $res['headers']['status-code']); + $this->assertEquals('The value for x-appwrite-id header is invalid. Please check the value of the x-appwrite-id header is valid id and not unique().', $res['body']['message']); + return ['bucketId' => $bucketId, 'fileId' => $file['body']['$id'], 'largeFileId' => $largeFile['body']['$id'], 'largeBucketId' => $bucket2['body']['$id']]; } diff --git a/tests/e2e/Services/Teams/TeamsBaseServer.php b/tests/e2e/Services/Teams/TeamsBaseServer.php index 7bb6241c4a..648bb99f49 100644 --- a/tests/e2e/Services/Teams/TeamsBaseServer.php +++ b/tests/e2e/Services/Teams/TeamsBaseServer.php @@ -4,6 +4,7 @@ namespace Tests\E2E\Services\Teams; use Tests\E2E\Client; use Utopia\Database\Validator\Datetime as DatetimeValidator; +use Utopia\Database\Helpers\ID; trait TeamsBaseServer { diff --git a/tests/unit/Utopia/Response/Filters/V15Test.php b/tests/unit/Utopia/Response/Filters/V15Test.php index 50fb654c48..e174a6c3c7 100644 --- a/tests/unit/Utopia/Response/Filters/V15Test.php +++ b/tests/unit/Utopia/Response/Filters/V15Test.php @@ -562,40 +562,6 @@ class V15Test extends TestCase $this->assertEquals($expected, $result); } - // /** - // * @dataProvider createdAtUpdatedAtProvider - // */ - // public function testDomain(array $content, array $expected): void - // { - // $model = Response::MODEL_DOMAIN; - - // $result = $this->filter->parse($content, $model); - - // $this->assertEquals($expected, $result); - // } - - // /** - // * @dataProvider createdAtUpdatedAtProvider - // */ - // public function testDomainList(array $content, array $expected): void - // { - // $model = Response::MODEL_DOMAIN_LIST; - - // $content = [ - // 'domains' => [$content], - // 'total' => 1, - // ]; - - // $expected = [ - // 'domains' => [$expected], - // 'total' => 1, - // ]; - - // $result = $this->filter->parse($content, $model); - - // $this->assertEquals($expected, $result); - // } - public function executionProvider(): array { return [