From 1a42d118f64b6af90ffa6787e58079e1dcd34e05 Mon Sep 17 00:00:00 2001 From: ItzNotABug Date: Sat, 1 Jun 2024 19:18:35 +0530 Subject: [PATCH 1/8] add: test to validate user-id isn't overridden. --- .../Functions/FunctionsCustomClientTest.php | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 119c1a22239..0acf297ea79 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -716,4 +716,92 @@ public function testSynchronousExecution(): array return []; } + + public function testExecutionWithUserId(): array + { + /** + * Test for SUCCESS + */ + $projectId = $this->getProject()['$id']; + $apikey = $this->getProject()['apiKey']; + + $function = $this->client->call(Client::METHOD_POST, '/functions', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-key' => $apikey, + ], [ + 'functionId' => ID::unique(), + 'name' => 'Test', + 'execute' => [Role::any()->toString()], + 'runtime' => 'node-18.0', + 'entrypoint' => 'index.js' + ]); + + $functionId = $function['body']['$id'] ?? ''; + + $this->assertEquals(201, $function['headers']['status-code']); + + $folder = 'node'; + $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; + $this->packageCode($folder); + + $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', [ + 'content-type' => 'multipart/form-data', + 'x-appwrite-project' => $projectId, + 'x-appwrite-key' => $apikey, + ], [ + 'entrypoint' => 'index.js', + 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), //different tarball names intentional + 'activate' => true + ]); + + $deploymentId = $deployment['body']['$id'] ?? ''; + + $this->assertEquals(202, $deployment['headers']['status-code']); + + // Poll until deployment is built + while (true) { + $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + if ( + $deployment['headers']['status-code'] >= 400 + || \in_array($deployment['body']['status'], ['ready', 'failed']) + ) { + break; + } + + \sleep(1); + } + + $this->assertEquals('ready', $deployment['body']['status']); + + $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'x-appwrite-user-id' => "665b0df20031bdf527fb", + ]); + + $output = json_decode($execution['body']['responseBody'], true); + $this->assertNotEquals('665b0df20031bdf527fb', $this->getUser()['$id']); + $this->assertEquals($this->getUser()['$id'], $output['APPWRITE_FUNCTION_USER_ID']); + // Client should never see logs and errors + $this->assertEmpty($execution['body']['logs']); + $this->assertEmpty($execution['body']['errors']); + + // Cleanup : Delete function + $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], []); + + $this->assertEquals(204, $response['headers']['status-code']); + + return []; + } } From 52f5a5c40c6a637a66cb34325cbd67034bd73b39 Mon Sep 17 00:00:00 2001 From: ItzNotABug Date: Mon, 3 Jun 2024 14:10:20 +0530 Subject: [PATCH 2/8] update test. --- .../Functions/FunctionsCustomClientTest.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 0acf297ea79..0adf11d0578 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -783,15 +783,19 @@ public function testExecutionWithUserId(): array 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'x-appwrite-user-id' => "665b0df20031bdf527fb", + 'x-appwrite-event' => "OVERRIDDEN", + 'x-appwrite-trigger' => "OVERRIDDEN", + 'x-appwrite-user-id' => "OVERRIDDEN", + 'x-appwrite-user-jwt' => "OVERRIDDEN", ]); $output = json_decode($execution['body']['responseBody'], true); - $this->assertNotEquals('665b0df20031bdf527fb', $this->getUser()['$id']); + $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_JWT']); + $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_EVENT']); + $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_TRIGGER']); + $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_USER_ID']); + $this->assertEquals($this->getUser()['$id'], $output['APPWRITE_FUNCTION_USER_ID']); - // Client should never see logs and errors - $this->assertEmpty($execution['body']['logs']); - $this->assertEmpty($execution['body']['errors']); // Cleanup : Delete function $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ From b54a599960dbd21e52d8ef923bb4857f666eab9a Mon Sep 17 00:00:00 2001 From: ItzNotABug Date: Mon, 3 Jun 2024 14:13:08 +0530 Subject: [PATCH 3/8] update test name for clarity. --- tests/e2e/Services/Functions/FunctionsCustomClientTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 0adf11d0578..e10aef840c5 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -717,7 +717,7 @@ public function testSynchronousExecution(): array return []; } - public function testExecutionWithUserId(): array + public function testNonOverrideOfHeaders(): array { /** * Test for SUCCESS From 37da4a0cd39e20aa9a1be37c8ef7a401a5843783 Mon Sep 17 00:00:00 2001 From: ItzNotABug Date: Mon, 3 Jun 2024 14:19:59 +0530 Subject: [PATCH 4/8] review comments. --- tests/e2e/Services/Functions/FunctionsCustomClientTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index e10aef840c5..0b8ea71aada 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -795,8 +795,6 @@ public function testNonOverrideOfHeaders(): array $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_TRIGGER']); $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_USER_ID']); - $this->assertEquals($this->getUser()['$id'], $output['APPWRITE_FUNCTION_USER_ID']); - // Cleanup : Delete function $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ 'content-type' => 'application/json', From cccda2a46cc7ee348864ae6c7be56bff1966c271 Mon Sep 17 00:00:00 2001 From: Bishwajeet Parhi Date: Wed, 5 Jun 2024 23:34:01 +0530 Subject: [PATCH 5/8] fix: Don't set target attribute if no existing Target found --- app/controllers/api/account.php | 4 +++- app/controllers/api/users.php | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index e1ddeb6b68b..20ce2541c54 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -290,7 +290,9 @@ $existingTarget = $dbForProject->findOne('targets', [ Query::equal('identifier', [$email]), ]); - $user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]); + if($existingTarget) { + $user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]); + } } $dbForProject->purgeCachedDocument('users', $user->getId()); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index abfcb34a5a6..9d08fcf7840 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -138,7 +138,9 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e $existingTarget = $dbForProject->findOne('targets', [ Query::equal('identifier', [$email]), ]); - $user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]); + if($existingTarget) { + $user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]); + } } } @@ -160,7 +162,9 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e $existingTarget = $dbForProject->findOne('targets', [ Query::equal('identifier', [$phone]), ]); - $user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]); + if($existingTarget) { + $user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]); + } } } From 485b8eeb8b2461f46a877cbc017baeaf487a9c33 Mon Sep 17 00:00:00 2001 From: Bishwajeet Parhi Date: Wed, 5 Jun 2024 23:53:31 +0530 Subject: [PATCH 6/8] chore: misc test assertions improvements --- .../Account/AccountCustomClientTest.php | 47 +++++++++---------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 23771712e8e..b1f7c85cd9e 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -150,7 +150,7 @@ public function testGetAccount($data): array $this->assertEquals(200, $response['headers']['status-code']); $this->assertNotEmpty($response['body']); $this->assertNotEmpty($response['body']['$id']); - $this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['registration'])); + $this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration'])); $this->assertEquals($response['body']['email'], $email); $this->assertEquals($response['body']['name'], $name); $this->assertArrayHasKey('accessedAt', $response['body']); @@ -294,7 +294,7 @@ public function testGetAccountLogs($data): array $this->assertIsNumeric($response['body']['total']); $this->assertEquals("user.create", $response['body']['logs'][2]['event']); $this->assertEquals(filter_var($response['body']['logs'][2]['ip'], FILTER_VALIDATE_IP), $response['body']['logs'][2]['ip']); - $this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['logs'][2]['time'])); + $this->assertTrue((new DatetimeValidator())->isValid($response['body']['logs'][2]['time'])); $this->assertEquals('Windows', $response['body']['logs'][1]['osName']); $this->assertEquals('WIN', $response['body']['logs'][1]['osCode']); @@ -327,7 +327,6 @@ public function testGetAccountLogs($data): array $this->assertEquals('desktop', $response['body']['logs'][2]['deviceName']); $this->assertEquals('', $response['body']['logs'][2]['deviceBrand']); $this->assertEquals('', $response['body']['logs'][2]['deviceModel']); - $this->assertEquals($response['body']['logs'][2]['ip'], filter_var($response['body']['logs'][2]['ip'], FILTER_VALIDATE_IP)); $this->assertEquals('--', $response['body']['logs'][2]['countryCode']); $this->assertEquals('Unknown', $response['body']['logs'][2]['countryName']); @@ -343,7 +342,7 @@ public function testGetAccountLogs($data): array ] ]); - $this->assertEquals($responseLimit['headers']['status-code'], 200); + $this->assertEquals(200, $responseLimit['headers']['status-code']); $this->assertIsArray($responseLimit['body']['logs']); $this->assertNotEmpty($responseLimit['body']['logs']); $this->assertCount(1, $responseLimit['body']['logs']); @@ -382,7 +381,7 @@ public function testGetAccountLogs($data): array ] ]); - $this->assertEquals($responseLimitOffset['headers']['status-code'], 200); + $this->assertEquals(200, $responseLimitOffset['headers']['status-code']); $this->assertIsArray($responseLimitOffset['body']['logs']); $this->assertNotEmpty($responseLimitOffset['body']['logs']); $this->assertCount(1, $responseLimitOffset['body']['logs']); @@ -430,7 +429,7 @@ public function testUpdateAccountName($data): array $this->assertIsArray($response['body']); $this->assertNotEmpty($response['body']); $this->assertNotEmpty($response['body']['$id']); - $this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['registration'])); + $this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration'])); $this->assertEquals($response['body']['email'], $email); $this->assertEquals($response['body']['name'], $newName); @@ -497,7 +496,7 @@ public function testUpdateAccountPassword($data): array $this->assertIsArray($response['body']); $this->assertNotEmpty($response['body']); $this->assertNotEmpty($response['body']['$id']); - $this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['registration'])); + $this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration'])); $this->assertEquals($response['body']['email'], $email); $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ @@ -587,7 +586,7 @@ public function testUpdateAccountEmail($data): array $this->assertIsArray($response['body']); $this->assertNotEmpty($response['body']); $this->assertNotEmpty($response['body']['$id']); - $this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['registration'])); + $this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration'])); $this->assertEquals($response['body']['email'], $newEmail); /** @@ -629,7 +628,7 @@ public function testUpdateAccountEmail($data): array $this->assertEquals(201, $response['headers']['status-code']); $this->assertNotEmpty($response['body']); $this->assertNotEmpty($response['body']['$id']); - $this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['registration'])); + $this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration'])); $this->assertEquals($response['body']['email'], $data['email']); $this->assertEquals($response['body']['name'], $data['name']); @@ -771,7 +770,7 @@ public function testCreateAccountVerification($data): array $this->assertEquals(201, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); $this->assertEmpty($response['body']['secret']); - $this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['expire'])); + $this->assertTrue((new DatetimeValidator())->isValid($response['body']['expire'])); $lastEmail = $this->getLastEmail(); @@ -1073,7 +1072,7 @@ public function testCreateAccountRecovery($data): array $this->assertEquals(201, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); $this->assertEmpty($response['body']['secret']); - $this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['expire'])); + $this->assertTrue((new DatetimeValidator())->isValid($response['body']['expire'])); $lastEmail = $this->getLastEmail(); @@ -1668,7 +1667,7 @@ public function testConvertAnonymousAccount() $this->assertIsArray($response['body']); $this->assertNotEmpty($response['body']); $this->assertNotEmpty($response['body']['$id']); - $this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['registration'])); + $this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration'])); $this->assertEquals($response['body']['email'], $email); $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ @@ -1756,13 +1755,11 @@ public function testConvertAnonymousAccountOAuth2() $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals($response['body']['$id'], $userId); - $this->assertEquals($response['body']['name'], 'User Name'); - $this->assertEquals($response['body']['email'], 'useroauth@localhost.test'); + $this->assertEquals('User Name', $response['body']['name']); + $this->assertEquals('useroauth@localhost.test', $response['body']['email']); // Since we only support one oauth user, let's also check updateSession here - $this->assertEquals(200, $response['headers']['status-code']); - $response = $this->client->call(Client::METHOD_GET, '/account/sessions/current', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', @@ -1808,7 +1805,7 @@ public function testGetSessionByID() $this->assertEquals(200, $response['headers']['status-code']); $this->assertEmpty($response['body']['secret']); - $this->assertEquals($response['body']['provider'], 'anonymous'); + $this->assertEquals('anonymous', $response['body']['provider']); $sessionID = $response['body']['$id']; @@ -1821,7 +1818,7 @@ public function testGetSessionByID() $this->assertEquals(200, $response['headers']['status-code']); $this->assertEmpty($response['body']['secret']); - $this->assertEquals($response['body']['provider'], 'anonymous'); + $this->assertEquals('anonymous', $response['body']['provider']); $response = $this->client->call(Client::METHOD_GET, '/account/sessions/97823askjdkasd80921371980', array_merge([ 'origin' => 'http://localhost', @@ -1934,7 +1931,7 @@ public function testCreatePhone(): array $this->assertEquals(201, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); $this->assertEmpty($response['body']['secret']); - $this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['expire'])); + $this->assertTrue((new DatetimeValidator())->isValid($response['body']['expire'])); $userId = $response['body']['userId']; @@ -2085,7 +2082,7 @@ public function testConvertPhoneToPassword(array $data): array $this->assertIsArray($response['body']); $this->assertNotEmpty($response['body']); $this->assertNotEmpty($response['body']['$id']); - $this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['registration'])); + $this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration'])); $this->assertEquals($response['body']['email'], $email); $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ @@ -2127,7 +2124,7 @@ public function testUpdatePhone(array $data): array $this->assertIsArray($response['body']); $this->assertNotEmpty($response['body']); $this->assertNotEmpty($response['body']['$id']); - $this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['registration'])); + $this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration'])); $this->assertEquals($response['body']['phone'], $newPhone); /** @@ -2240,7 +2237,7 @@ public function testPhoneVerification(array $data): array $this->assertEquals(201, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); $this->assertEmpty($response['body']['secret']); - $this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['expire'])); + $this->assertTrue((new DatetimeValidator())->isValid($response['body']['expire'])); $smsRequest = $this->getLastRequest(); @@ -2327,7 +2324,7 @@ public function testCreateMagicUrl(): array $this->assertNotEmpty($response['body']['$id']); $this->assertEmpty($response['body']['secret']); $this->assertEmpty($response['body']['phrase']); - $this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['expire'])); + $this->assertTrue((new DatetimeValidator())->isValid($response['body']['expire'])); $userId = $response['body']['userId']; @@ -2452,7 +2449,7 @@ public function testCreateSessionWithMagicUrl($data): array $this->assertEquals(200, $response['headers']['status-code']); $this->assertNotEmpty($response['body']); $this->assertNotEmpty($response['body']['$id']); - $this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['registration'])); + $this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration'])); $this->assertEquals($response['body']['email'], $email); $this->assertTrue($response['body']['emailVerification']); @@ -2512,7 +2509,7 @@ public function testUpdateAccountPasswordWithMagicUrl($data): array $this->assertIsArray($response['body']); $this->assertNotEmpty($response['body']); $this->assertNotEmpty($response['body']['$id']); - $this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['registration'])); + $this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration'])); $this->assertEquals($response['body']['email'], $email); $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ From 190560b8ef4604f81eae7a3f2aa393839066e875 Mon Sep 17 00:00:00 2001 From: Bishwajeet Parhi Date: Tue, 11 Jun 2024 21:17:25 +0530 Subject: [PATCH 7/8] chore: append attributes instead of replace --- app/controllers/api/account.php | 2 +- app/controllers/api/users.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 20ce2541c54..6f0345cfdc7 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -291,7 +291,7 @@ Query::equal('identifier', [$email]), ]); if($existingTarget) { - $user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]); + $user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND); } } diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 9d08fcf7840..193f3f095ec 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -139,7 +139,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e Query::equal('identifier', [$email]), ]); if($existingTarget) { - $user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]); + $user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND); } } } @@ -163,7 +163,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e Query::equal('identifier', [$phone]), ]); if($existingTarget) { - $user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]); + $user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND); } } } From 84d9b4befe0c10218bb4d6e3205eae6f6d6dbb23 Mon Sep 17 00:00:00 2001 From: choir27 Date: Thu, 13 Jun 2024 09:59:20 -0400 Subject: [PATCH 8/8] docs: fix hyperlink in storage code snippet --- app/controllers/api/storage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 9215d993452..1dd3bb597cc 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -351,7 +351,7 @@ ->label('sdk.response.model', Response::MODEL_FILE) ->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).') ->param('fileId', '', new CustomId(), 'File ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') - ->param('file', [], new File(), 'Binary file. Appwrite SDKs provide helpers to handle file input. [Learn about file input](https://appwrite.io/docs/storage#file-input).', skipValidation: true) + ->param('file', [], new File(), 'Binary file. Appwrite SDKs provide helpers to handle file input. [Learn about file input](https://appwrite.io/docs/products/storage/upload-download#input-file).', skipValidation: true) ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permission strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) ->inject('request') ->inject('response')