From a46879f1cba0973367d78f0cf549168888e5595d Mon Sep 17 00:00:00 2001 From: shimon Date: Tue, 5 Sep 2023 18:29:11 +0300 Subject: [PATCH 1/9] cache key reformat --- app/controllers/shared/api.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 9dd800b69b..b65e35e605 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -203,7 +203,7 @@ App::init() $useCache = $route->getLabel('cache', false); if ($useCache) { - $key = md5($request->getURI() . implode('*', $request->getParams())) . '*' . APP_CACHE_BUSTER; + $key = md5($request->getURI() . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER); $cache = new Cache( new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId()) ); @@ -491,7 +491,7 @@ App::shutdown() $resourceType = $parseLabel($pattern, $responsePayload, $requestParams, $user); } - $key = md5($request->getURI() . implode('*', $request->getParams())) . '*' . APP_CACHE_BUSTER; + $key = md5($request->getURI() . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER); $data = json_encode([ 'resourceType' => $resourceType, 'resource' => $resource, From f246d4d6e5c52c3ecc2a627f8dfc5e42806af194 Mon Sep 17 00:00:00 2001 From: shimon Date: Tue, 5 Sep 2023 21:21:24 +0300 Subject: [PATCH 2/9] removed $route->setIsActive(false); from cache output --- app/controllers/shared/api.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index b65e35e605..13db405091 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -255,7 +255,6 @@ App::init() ->send(base64_decode($data['payload'])) ; - $route->setIsActive(false); } else { $response->addHeader('X-Appwrite-Cache', 'miss'); } From cf92936fcc45ce77126708501c295de82d1b7962 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 7 Sep 2023 17:21:06 -0400 Subject: [PATCH 3/9] Add mock endpoint with whitelist param --- app/controllers/mock.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/controllers/mock.php b/app/controllers/mock.php index a763df82c0..874d384ae1 100644 --- a/app/controllers/mock.php +++ b/app/controllers/mock.php @@ -442,6 +442,19 @@ App::post('/v1/mock/tests/general/nullable') ->action(function (string $required, string $nullable, ?string $optional) { }); +App::post('/v1/mock/tests/general/enum') + ->desc('Nullable Test') + ->groups(['mock']) + ->label('scope', 'public') + ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) + ->label('sdk.namespace', 'general') + ->label('sdk.method', 'nullable') + ->label('sdk.description', 'Mock a nullable parameter.') + ->label('sdk.mock', true) + ->param('mockType', '', new WhiteList(['first', 'second', 'third']), 'Sample string param') + ->action(function (string $required, string $nullable, ?string $optional) { + }); + App::get('/v1/mock/tests/general/400-error') ->desc('400 Error') ->groups(['mock']) From d4f6b99ee208f084ad7a3e8ba87275d4a71b28aa Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 7 Sep 2023 19:25:42 -0400 Subject: [PATCH 4/9] Fix mock enum endpoint --- app/controllers/mock.php | 10 +++++----- src/Appwrite/Platform/Tasks/Specs.php | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/mock.php b/app/controllers/mock.php index 8ed3d736eb..593063ae84 100644 --- a/app/controllers/mock.php +++ b/app/controllers/mock.php @@ -445,16 +445,16 @@ App::post('/v1/mock/tests/general/nullable') }); App::post('/v1/mock/tests/general/enum') - ->desc('Nullable Test') + ->desc('Enum Test') ->groups(['mock']) ->label('scope', 'public') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'general') - ->label('sdk.method', 'nullable') - ->label('sdk.description', 'Mock a nullable parameter.') + ->label('sdk.method', 'enum') + ->label('sdk.description', 'Mock an enum parameter.') ->label('sdk.mock', true) - ->param('mockType', '', new WhiteList(['first', 'second', 'third']), 'Sample string param') - ->action(function (string $required, string $nullable, ?string $optional) { + ->param('mockType', '', new WhiteList(['first', 'second', 'third']), 'Sample enum param') + ->action(function (string $mockType) { }); App::get('/v1/mock/tests/general/400-error') diff --git a/src/Appwrite/Platform/Tasks/Specs.php b/src/Appwrite/Platform/Tasks/Specs.php index 2a22c59103..28d31f6d7b 100644 --- a/src/Appwrite/Platform/Tasks/Specs.php +++ b/src/Appwrite/Platform/Tasks/Specs.php @@ -253,7 +253,7 @@ class Specs extends Action ->setParam('docs.url', $endpoint . '/docs'); if ($mocks) { - $path = __DIR__ . '/../config/specs/' . $format . '-mocks-' . $platform . '.json'; + $path = __DIR__ . '/../../../../app/config/specs/' . $format . '-mocks-' . $platform . '.json'; if (!file_put_contents($path, json_encode($specs->parse()))) { throw new Exception('Failed to save mocks spec file: ' . $path); From 5cb03da0017d0dfcf2e413997d7e2568cf4a0cc9 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 11 Sep 2023 23:24:11 -0400 Subject: [PATCH 5/9] Extracting function event validator to its own class. --- app/controllers/api/functions.php | 6 +- src/Appwrite/Event/Validator/Event.php | 6 -- .../Event/Validator/FunctionEvent.php | 25 +++++++ src/Appwrite/GraphQL/Types/Mapper.php | 1 + .../Validator/FunctionEventValidatorTest.php | 73 +++++++++++++++++++ 5 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 src/Appwrite/Event/Validator/FunctionEvent.php create mode 100644 tests/unit/Event/Validator/FunctionEventValidatorTest.php diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 1cabc77eb7..ba323dc5a4 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -7,7 +7,7 @@ use Appwrite\Event\Delete; use Appwrite\Event\Event; use Appwrite\Event\Func; use Appwrite\Event\Usage; -use Appwrite\Event\Validator\Event as ValidatorEvent; +use Appwrite\Event\Validator\FunctionEvent; use Appwrite\Utopia\Response\Model\Rule; use Appwrite\Extend\Exception; use Appwrite\Utopia\Database\Validator\CustomId; @@ -136,7 +136,7 @@ App::post('/v1/functions') ->param('name', '', new Text(128), 'Function name. Max length: 128 chars.') ->param('runtime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Execution runtime.') ->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of role strings with execution permissions. By default no user is granted with any execute permissions. [learn more about roles](https://appwrite.io/docs/permissions#permission-roles). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.', true) - ->param('events', [], new ArrayList(new ValidatorEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true) + ->param('events', [], new ArrayList(new FunctionEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true) ->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true) ->param('timeout', 15, new Range(1, (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Function maximum execution time in seconds.', true) ->param('enabled', true, new Boolean(), 'Is function enabled? When set to \'disabled\', users cannot access the function but Server SDKs with and API key can still access the function. No data is lost when this is toggled.', true) @@ -662,7 +662,7 @@ App::put('/v1/functions/:functionId') ->param('name', '', new Text(128), 'Function name. Max length: 128 chars.') ->param('runtime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Execution runtime.', true) ->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of role strings with execution permissions. By default no user is granted with any execute permissions. [learn more about roles](https://appwrite.io/docs/permissions#permission-roles). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.', true) - ->param('events', [], new ArrayList(new ValidatorEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true) + ->param('events', [], new ArrayList(new FunctionEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true) ->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true) ->param('timeout', 15, new Range(1, (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Maximum execution time in seconds.', true) ->param('enabled', true, new Boolean(), 'Is function enabled? When set to \'disabled\', users cannot access the function but Server SDKs with and API key can still access the function. No data is lost when this is toggled.', true) diff --git a/src/Appwrite/Event/Validator/Event.php b/src/Appwrite/Event/Validator/Event.php index 3f22900486..2061d53ed8 100644 --- a/src/Appwrite/Event/Validator/Event.php +++ b/src/Appwrite/Event/Validator/Event.php @@ -45,12 +45,6 @@ class Event extends Validator * Identify all sections of the pattern. */ $type = $parts[0] ?? false; - - if ($type == 'functions') { - $this->message = 'Triggering a function on a function event is not allowed.'; - return false; - } - $resource = $parts[1] ?? false; $hasSubResource = $count > 3 && ($events[$type]['$resource'] ?? false) && ($events[$type][$parts[2]]['$resource'] ?? false); $hasSubSubResource = $count > 5 && $hasSubResource && ($events[$type][$parts[2]][$parts[4]]['$resource'] ?? false); diff --git a/src/Appwrite/Event/Validator/FunctionEvent.php b/src/Appwrite/Event/Validator/FunctionEvent.php new file mode 100644 index 0000000000..6443b127ad --- /dev/null +++ b/src/Appwrite/Event/Validator/FunctionEvent.php @@ -0,0 +1,25 @@ +message = 'Triggering a function on a function event is not allowed.'; + return false; + } + + return parent::isValid($value); + } +} diff --git a/src/Appwrite/GraphQL/Types/Mapper.php b/src/Appwrite/GraphQL/Types/Mapper.php index 0f9ad661f6..280d094a0c 100644 --- a/src/Appwrite/GraphQL/Types/Mapper.php +++ b/src/Appwrite/GraphQL/Types/Mapper.php @@ -235,6 +235,7 @@ class Mapper case 'Utopia\Validator\Domain': case 'Appwrite\Network\Validator\Email': case 'Appwrite\Event\Validator\Event': + case 'Appwrite\Event\Validator\FunctionEvent': case 'Utopia\Validator\HexColor': case 'Utopia\Validator\Host': case 'Utopia\Validator\IP': diff --git a/tests/unit/Event/Validator/FunctionEventValidatorTest.php b/tests/unit/Event/Validator/FunctionEventValidatorTest.php new file mode 100644 index 0000000000..ea59f6771a --- /dev/null +++ b/tests/unit/Event/Validator/FunctionEventValidatorTest.php @@ -0,0 +1,73 @@ +object = new FunctionEvent(); + } + + public function tearDown(): void + { + } + + public function testValues(): void + { + /** + * Test for SUCCESS + */ + $this->assertTrue($this->object->isValid('users.*.create')); + $this->assertTrue($this->object->isValid('users.torsten.update')); + $this->assertTrue($this->object->isValid('users.torsten')); + $this->assertTrue($this->object->isValid('users.*.update.email')); + $this->assertTrue($this->object->isValid('users.*.update')); + $this->assertTrue($this->object->isValid('users.*')); + $this->assertTrue($this->object->isValid('databases.books.collections.chapters.documents.prolog.create')); + $this->assertTrue($this->object->isValid('databases.books.collections.chapters.documents.prolog')); + $this->assertTrue($this->object->isValid('databases.books.collections.chapters.documents.*.create')); + $this->assertTrue($this->object->isValid('databases.books.collections.chapters.documents.*')); + $this->assertTrue($this->object->isValid('databases.books.collections.*.documents.prolog.create')); + $this->assertTrue($this->object->isValid('databases.books.collections.*.documents.prolog')); + $this->assertTrue($this->object->isValid('databases.books.collections.*.documents.*.create')); + $this->assertTrue($this->object->isValid('databases.books.collections.*.documents.*')); + $this->assertTrue($this->object->isValid('databases.*.collections.chapters.documents.prolog.create')); + $this->assertTrue($this->object->isValid('databases.*.collections.chapters.documents.prolog')); + $this->assertTrue($this->object->isValid('databases.*.collections.chapters.documents.*.create')); + $this->assertTrue($this->object->isValid('databases.*.collections.chapters.documents.*')); + $this->assertTrue($this->object->isValid('databases.*.collections.*.documents.prolog.create')); + $this->assertTrue($this->object->isValid('databases.*.collections.*.documents.prolog')); + $this->assertTrue($this->object->isValid('databases.*.collections.*.documents.*.create')); + $this->assertTrue($this->object->isValid('databases.*.collections.*.documents.*')); + $this->assertTrue($this->object->isValid('databases.*.collections.*')); + $this->assertTrue($this->object->isValid('databases.*')); + $this->assertTrue($this->object->isValid('databases.books')); + $this->assertTrue($this->object->isValid('databases.books.collections.chapters')); + $this->assertTrue($this->object->isValid('databases.books.collections.*')); + $this->assertTrue($this->object->isValid('buckets.*')); + $this->assertTrue($this->object->isValid('teams.*')); + $this->assertTrue($this->object->isValid('users.*')); + $this->assertTrue($this->object->isValid('teams.*.memberships.*.update.status')); + + /** + * Test for FAILURE + */ + $this->assertFalse($this->object->isValid(false)); + $this->assertFalse($this->object->isValid(null)); + $this->assertFalse($this->object->isValid('')); + $this->assertFalse($this->object->isValid('unknown.*')); + $this->assertFalse($this->object->isValid('collections')); + $this->assertFalse($this->object->isValid('collections.*.unknown')); + $this->assertFalse($this->object->isValid('collections.*.documents.*.unknown')); + $this->assertFalse($this->object->isValid('users.torsten.unknown')); + $this->assertFalse($this->object->isValid('users.torsten.delete.email')); + $this->assertFalse($this->object->isValid('teams.*.memberships.*.update.unknown')); + $this->assertFalse($this->object->isValid('functions.*')); + } +} From b37c2658ecb899996c93bf66e0a1223eb728c854 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 11 Sep 2023 23:56:30 -0400 Subject: [PATCH 6/9] Adding the `functions.*` test for the base event --- tests/unit/Event/Validator/EventValidatorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/Event/Validator/EventValidatorTest.php b/tests/unit/Event/Validator/EventValidatorTest.php index cf62ecca65..e9f652adeb 100644 --- a/tests/unit/Event/Validator/EventValidatorTest.php +++ b/tests/unit/Event/Validator/EventValidatorTest.php @@ -50,7 +50,7 @@ class EventValidatorTest extends TestCase $this->assertTrue($this->object->isValid('databases.books')); $this->assertTrue($this->object->isValid('databases.books.collections.chapters')); $this->assertTrue($this->object->isValid('databases.books.collections.*')); - // $this->assertTrue($this->object->isValid('functions.*')); TODO @christyjacob4 : enable test once we allow functions.* events + $this->assertTrue($this->object->isValid('functions.*')); $this->assertTrue($this->object->isValid('buckets.*')); $this->assertTrue($this->object->isValid('teams.*')); $this->assertTrue($this->object->isValid('users.*')); From c30fa277d3eae6148b4a1c6e2f5ef39a4375a1b5 Mon Sep 17 00:00:00 2001 From: shimon Date: Wed, 13 Sep 2023 10:24:36 +0300 Subject: [PATCH 7/9] linter fix --- app/controllers/shared/api.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 13db405091..c2102057fa 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -254,7 +254,6 @@ App::init() ->setContentType($data['contentType']) ->send(base64_decode($data['payload'])) ; - } else { $response->addHeader('X-Appwrite-Cache', 'miss'); } From dc1d9fa6357883b6565c462effa11bb30c343d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Mon, 18 Sep 2023 12:39:19 +0200 Subject: [PATCH 8/9] Fix wrong device type --- app/workers/builds.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/workers/builds.php b/app/workers/builds.php index 89aa22edb7..52d8d1a4fa 100644 --- a/app/workers/builds.php +++ b/app/workers/builds.php @@ -112,6 +112,8 @@ class BuildsV1 extends Worker $isNewBuild = empty($buildId); + $deviceFunctions = $this->getFunctionsDevice($project->getId()); + if ($isNewBuild) { $buildId = ID::unique(); $build = $dbForProject->createDocument('builds', new Document([ @@ -124,7 +126,7 @@ class BuildsV1 extends Worker 'path' => '', 'runtime' => $function->getAttribute('runtime'), 'source' => $deployment->getAttribute('path', ''), - 'sourceType' => strtolower(App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL)), + 'sourceType' => strtolower($deviceFunctions->getType()), 'logs' => '', 'endTime' => null, 'duration' => 0, @@ -254,8 +256,6 @@ class BuildsV1 extends Worker Console::execute('tar --exclude code.tar.gz -czf ' . $tmpPathFile . ' -C /tmp/builds/' . \escapeshellcmd($buildId) . '/code' . (empty($rootDirectory) ? '' : '/' . $rootDirectory) . ' .', '', $stdout, $stderr); - $deviceFunctions = $this->getFunctionsDevice($project->getId()); - $localDevice = new Local(); $buffer = $localDevice->read($tmpPathFile); $mimeType = $localDevice->getFileMimeType($tmpPathFile); From d3f83c24664ad29b8e802264812183cf79638385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Mon, 18 Sep 2023 12:41:25 +0200 Subject: [PATCH 9/9] Linter fix --- app/workers/builds.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/workers/builds.php b/app/workers/builds.php index 52d8d1a4fa..fb71dadcf5 100644 --- a/app/workers/builds.php +++ b/app/workers/builds.php @@ -113,7 +113,7 @@ class BuildsV1 extends Worker $isNewBuild = empty($buildId); $deviceFunctions = $this->getFunctionsDevice($project->getId()); - + if ($isNewBuild) { $buildId = ID::unique(); $build = $dbForProject->createDocument('builds', new Document([