diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 7c51d5377e..a10d7a007f 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/app/controllers/mock.php b/app/controllers/mock.php index 8a4d5650d7..593063ae84 100644 --- a/app/controllers/mock.php +++ b/app/controllers/mock.php @@ -444,6 +444,19 @@ App::post('/v1/mock/tests/general/nullable') ->action(function (string $required, string $nullable, ?string $optional) { }); +App::post('/v1/mock/tests/general/enum') + ->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', 'enum') + ->label('sdk.description', 'Mock an enum parameter.') + ->label('sdk.mock', true) + ->param('mockType', '', new WhiteList(['first', 'second', 'third']), 'Sample enum param') + ->action(function (string $mockType) { + }); + App::get('/v1/mock/tests/general/400-error') ->desc('400 Error') ->groups(['mock']) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 7a672cc104..c2102057fa 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()) ); @@ -489,7 +489,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, diff --git a/app/workers/builds.php b/app/workers/builds.php index 89aa22edb7..fb71dadcf5 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); 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/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); 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.*')); 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.*')); + } +}