From 582698bd03235a7ee1b458865bd0b32fa368d369 Mon Sep 17 00:00:00 2001 From: Suven-p Date: Thu, 18 May 2023 00:41:07 +0545 Subject: [PATCH 01/36] Validate value of x-appwrite-id header --- app/config/errors.php | 5 +++++ app/controllers/api/storage.php | 5 +++++ src/Appwrite/Extend/Exception.php | 1 + 3 files changed, 11 insertions(+) diff --git a/app/config/errors.php b/app/config/errors.php index 1361207a1d..078a620168 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -318,6 +318,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, + ], /** Functions */ Exception::FUNCTION_NOT_FOUND => [ diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index d59d950d93..b8a249f654 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -449,6 +449,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); + } + if ($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 $chunks = $chunk = -1; diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index ed7194125c..05caa75c63 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -106,6 +106,7 @@ 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'; /** Functions */ public const FUNCTION_NOT_FOUND = 'function_not_found'; From 5108ab7b061ffb1846b1cbdbd3077110e026482c Mon Sep 17 00:00:00 2001 From: Suven-p Date: Sun, 21 May 2023 15:43:26 +0000 Subject: [PATCH 02/36] Add test for x-appwrite-id = unique() --- tests/e2e/Services/Storage/StorageBase.php | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/e2e/Services/Storage/StorageBase.php b/tests/e2e/Services/Storage/StorageBase.php index 3169077f99..88a09fe66f 100644 --- a/tests/e2e/Services/Storage/StorageBase.php +++ b/tests/e2e/Services/Storage/StorageBase.php @@ -240,6 +240,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']]; } From 42b2587b6da7eaf8bf17b7519d7efc4aeba18d7b Mon Sep 17 00:00:00 2001 From: Suven-p Date: Sat, 27 May 2023 22:54:00 +0545 Subject: [PATCH 03/36] Fix linting error --- tests/e2e/Services/Storage/StorageBase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Storage/StorageBase.php b/tests/e2e/Services/Storage/StorageBase.php index 88a09fe66f..62b49e62f3 100644 --- a/tests/e2e/Services/Storage/StorageBase.php +++ b/tests/e2e/Services/Storage/StorageBase.php @@ -248,7 +248,7 @@ trait StorageBase $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, + 'content-range' => 'bytes 0-' . $size . '/' . $size, 'x-appwrite-id' => 'unique()', ], $this->getHeaders()), [ 'fileId' => ID::unique(), From 69504bfaac93609758902615c1b18a5e33c5eb7d Mon Sep 17 00:00:00 2001 From: Bishwajeet Parhi Date: Tue, 30 May 2023 12:30:10 +0530 Subject: [PATCH 04/36] add encrypt param --- app/controllers/api/databases.php | 138 +++++++++++++++++++++++++++--- 1 file changed, 128 insertions(+), 10 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 9eedbb986c..044b419b10 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1103,13 +1103,14 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string ->param('key', '', new Key(), 'Attribute Key.') ->param('size', null, new Range(1, APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH, Range::TYPE_INTEGER), 'Attribute size for text attributes, in number of characters.') ->param('required', null, new Boolean(), 'Is attribute required?') + ->param('encrypt', false, new Boolean(), 'Encrypt attribue? Encrypting an attribute means that the attribute can not be queried.', true) ->param('default', null, new Text(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) ->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, ?bool $encrypt, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { // Ensure attribute default is within required size $validator = new Text($size); @@ -1117,6 +1118,17 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, $validator->getDescription()); } + $filters = []; + + switch ($encrypt) { + case true: + $filters[] = 'encrypt'; + break; + case false: + $filters[] = 'decrypt'; + break; + } + $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, 'type' => Database::VAR_STRING, @@ -1124,6 +1136,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string 'required' => $required, 'default' => $default, 'array' => $array, + 'filters' => $filters, ]), $response, $dbForProject, $database, $events); $response @@ -1152,13 +1165,25 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email' ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') + ->param('encrypt', false, new Boolean(), 'Encrypt attribue? Encrypting an attribute means that the attribute can not be queried.', true) ->param('default', null, new Email(), '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) ->inject('response') ->inject('dbForProject') ->inject('database') ->inject('events') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, bool $encrypt, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { + + $filters = []; + + switch ($encrypt) { + case true: + $filters[] = 'encrypt'; + break; + case false: + $filters[] = 'decrypt'; + break; + } $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, @@ -1168,6 +1193,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email' 'default' => $default, 'array' => $array, 'format' => APP_DATABASE_ATTRIBUTE_EMAIL, + 'filters' => $filters ]), $response, $dbForProject, $database, $events); $response @@ -1197,13 +1223,14 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum') ->param('key', '', new Key(), 'Attribute Key.') ->param('elements', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE, min: 0), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of elements in enumerated type. Uses length of longest element to determine size. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' elements are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.') ->param('required', null, new Boolean(), 'Is attribute required?') + ->param('encrypt', false, new Boolean(), 'Encrypt attribue? Encrypting an attribute means that the attribute can not be queried.', true) ->param('default', null, new Text(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) ->inject('response') ->inject('dbForProject') ->inject('database') ->inject('events') - ->action(function (string $databaseId, string $collectionId, string $key, array $elements, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { + ->action(function (string $databaseId, string $collectionId, string $key, array $elements, ?bool $required, bool $encrypt, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { // use length of longest string as attribute size $size = 0; @@ -1219,6 +1246,17 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum') throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Default value not found in elements'); } + $filters = []; + + switch ($encrypt) { + case true: + $filters[] = 'encrypt'; + break; + case false: + $filters[] = 'decrypt'; + break; + } + $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, 'type' => Database::VAR_STRING, @@ -1228,6 +1266,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum') 'array' => $array, 'format' => APP_DATABASE_ATTRIBUTE_ENUM, 'formatOptions' => ['elements' => $elements], + 'filters' => $filters ]), $response, $dbForProject, $database, $events); $response @@ -1256,13 +1295,25 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') + ->param('encrypt', false, new Boolean(), 'Encrypt attribue? Encrypting an attribute means that the attribute can not be queried.', true) ->param('default', null, new IP(), '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) ->inject('response') ->inject('dbForProject') ->inject('database') ->inject('events') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, bool $encrypt, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { + + $filters = []; + + switch ($encrypt) { + case true: + $filters[] = 'encrypt'; + break; + case false: + $filters[] = 'decrypt'; + break; + } $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, @@ -1272,6 +1323,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip') 'default' => $default, 'array' => $array, 'format' => APP_DATABASE_ATTRIBUTE_IP, + 'filters' => $filters, ]), $response, $dbForProject, $database, $events); $response @@ -1300,13 +1352,25 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') + ->param('encrypt', false, new Boolean(), 'Encrypt attribue? Encrypting an attribute means that the attribute can not be queried.', true) ->param('default', null, new URL(), '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) ->inject('response') ->inject('dbForProject') ->inject('database') ->inject('events') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, bool $encrypt, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { + + $filters = []; + + switch ($encrypt) { + case true: + $filters[] = 'encrypt'; + break; + case false: + $filters[] = 'decrypt'; + break; + } $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, @@ -1316,6 +1380,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url') 'default' => $default, 'array' => $array, 'format' => APP_DATABASE_ATTRIBUTE_URL, + 'filters' => $filters, ]), $response, $dbForProject, $database, $events); $response @@ -1344,6 +1409,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') + ->param('encrypt', false, new Boolean(), 'Encrypt attribue? Encrypting an attribute means that the attribute can not be queried.', true) ->param('min', null, new Integer(), 'Minimum value to enforce on new documents', true) ->param('max', null, new Integer(), 'Maximum value to enforce on new documents', true) ->param('default', null, new Integer(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true) @@ -1352,7 +1418,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege ->inject('dbForProject') ->inject('database') ->inject('events') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, bool $encrypt, ?int $min, ?int $max, ?int $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { // Ensure attribute default is within range $min = (is_null($min)) ? PHP_INT_MIN : \intval($min); @@ -1370,6 +1436,17 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege $size = $max > 2147483647 ? 8 : 4; // Automatically create BigInt depending on max value + $filters = []; + + switch ($encrypt) { + case true: + $filters[] = 'encrypt'; + break; + case false: + $filters[] = 'decrypt'; + break; + } + $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, 'type' => Database::VAR_INTEGER, @@ -1382,6 +1459,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege 'min' => $min, 'max' => $max, ], + 'filters' => $filters, ]), $response, $dbForProject, $database, $events); $formatOptions = $attribute->getAttribute('formatOptions', []); @@ -1417,6 +1495,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float' ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') + ->param('encrypt', false, new Boolean(), 'Encrypt attribue? Encrypting an attribute means that the attribute can not be queried.', true) ->param('min', null, new FloatValidator(), 'Minimum value to enforce on new documents', true) ->param('max', null, new FloatValidator(), 'Maximum value to enforce on new documents', true) ->param('default', null, new FloatValidator(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true) @@ -1425,7 +1504,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float' ->inject('dbForProject') ->inject('database') ->inject('events') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, bool $encrypt, ?float $min, ?float $max, ?float $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { // Ensure attribute default is within range $min = (is_null($min)) ? -PHP_FLOAT_MAX : \floatval($min); @@ -1446,6 +1525,17 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float' throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, $validator->getDescription()); } + $filters = []; + + switch ($encrypt) { + case true: + $filters[] = 'encrypt'; + break; + case false: + $filters[] = 'decrypt'; + break; + } + $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, 'type' => Database::VAR_FLOAT, @@ -1458,6 +1548,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float' 'min' => $min, 'max' => $max, ], + 'filters' => $filters, ]), $response, $dbForProject, $database, $events); $formatOptions = $attribute->getAttribute('formatOptions', []); @@ -1493,13 +1584,25 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolea ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') + ->param('encrypt', false, new Boolean(), 'Encrypt attribue? Encrypting an attribute means that the attribute can not be queried.', true) ->param('default', null, new Boolean(), '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) ->inject('response') ->inject('dbForProject') ->inject('database') ->inject('events') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, bool $encrypt, ?bool $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { + + $filters = []; + + switch ($encrypt) { + case true: + $filters[] = 'encrypt'; + break; + case false: + $filters[] = 'decrypt'; + break; + } $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, @@ -1508,6 +1611,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolea 'required' => $required, 'default' => $default, 'array' => $array, + 'filters' => $filters ]), $response, $dbForProject, $database, $events); $response @@ -1536,13 +1640,27 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') + ->param('encrypt', false, new Boolean(), 'Encrypt attribue? Encrypting an attribute means that the attribute can not be queried.', true) ->param('default', null, new DatetimeValidator(), 'Default value for the attribute in ISO 8601 format. Cannot be set when attribute is required.', true) ->param('array', false, new Boolean(), 'Is attribute an array?', true) ->inject('response') ->inject('dbForProject') ->inject('database') ->inject('events') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, bool $encrypt, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { + + $filters = []; + + switch ($encrypt) { + case true: + $filters[] = 'encrypt'; + break; + case false: + $filters[] = 'decrypt'; + break; + } + + $filters[] = 'datetime'; $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, @@ -1551,7 +1669,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 From 967ac1b82c0f6d4f86c25139c076aa7a11366fe1 Mon Sep 17 00:00:00 2001 From: Bishwajeet Parhi Date: Tue, 30 May 2023 12:34:32 +0530 Subject: [PATCH 05/36] chore: remove nullable param --- app/controllers/api/databases.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 044b419b10..f2d516c393 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1110,7 +1110,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string ->inject('dbForProject') ->inject('database') ->inject('events') - ->action(function (string $databaseId, string $collectionId, string $key, ?int $size, bool $required, ?bool $encrypt, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { + ->action(function (string $databaseId, string $collectionId, string $key, ?int $size, bool $required, bool $encrypt, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { // Ensure attribute default is within required size $validator = new Text($size); From 08746d0390ba759e46033cf30e246d7c2939a43e Mon Sep 17 00:00:00 2001 From: Bishwajeet Parhi Date: Tue, 30 May 2023 12:47:55 +0530 Subject: [PATCH 06/36] chore: misc fixes --- app/controllers/api/databases.php | 81 +++++++------------------------ 1 file changed, 18 insertions(+), 63 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index f2d516c393..8bb0e4c04d 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1120,13 +1120,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string $filters = []; - switch ($encrypt) { - case true: - $filters[] = 'encrypt'; - break; - case false: - $filters[] = 'decrypt'; - break; + if ($encrypt) { + $filters[] = 'encrypt'; } $attribute = createAttribute($databaseId, $collectionId, new Document([ @@ -1176,13 +1171,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email' $filters = []; - switch ($encrypt) { - case true: - $filters[] = 'encrypt'; - break; - case false: - $filters[] = 'decrypt'; - break; + if ($encrypt) { + $filters[] = 'encrypt'; } $attribute = createAttribute($databaseId, $collectionId, new Document([ @@ -1248,13 +1238,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum') $filters = []; - switch ($encrypt) { - case true: - $filters[] = 'encrypt'; - break; - case false: - $filters[] = 'decrypt'; - break; + if ($encrypt) { + $filters[] = 'encrypt'; } $attribute = createAttribute($databaseId, $collectionId, new Document([ @@ -1306,13 +1291,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip') $filters = []; - switch ($encrypt) { - case true: - $filters[] = 'encrypt'; - break; - case false: - $filters[] = 'decrypt'; - break; + if ($encrypt) { + $filters[] = 'encrypt'; } $attribute = createAttribute($databaseId, $collectionId, new Document([ @@ -1363,13 +1343,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url') $filters = []; - switch ($encrypt) { - case true: - $filters[] = 'encrypt'; - break; - case false: - $filters[] = 'decrypt'; - break; + if ($encrypt) { + $filters[] = 'encrypt'; } $attribute = createAttribute($databaseId, $collectionId, new Document([ @@ -1438,13 +1413,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege $filters = []; - switch ($encrypt) { - case true: - $filters[] = 'encrypt'; - break; - case false: - $filters[] = 'decrypt'; - break; + if ($encrypt) { + $filters[] = 'encrypt'; } $attribute = createAttribute($databaseId, $collectionId, new Document([ @@ -1527,13 +1497,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float' $filters = []; - switch ($encrypt) { - case true: - $filters[] = 'encrypt'; - break; - case false: - $filters[] = 'decrypt'; - break; + if ($encrypt) { + $filters[] = 'encrypt'; } $attribute = createAttribute($databaseId, $collectionId, new Document([ @@ -1595,13 +1560,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolea $filters = []; - switch ($encrypt) { - case true: - $filters[] = 'encrypt'; - break; - case false: - $filters[] = 'decrypt'; - break; + if ($encrypt) { + $filters[] = 'encrypt'; } $attribute = createAttribute($databaseId, $collectionId, new Document([ @@ -1651,13 +1611,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti $filters = []; - switch ($encrypt) { - case true: - $filters[] = 'encrypt'; - break; - case false: - $filters[] = 'decrypt'; - break; + if ($encrypt) { + $filters[] = 'encrypt'; } $filters[] = 'datetime'; From 320579209eade08e50527f75a824c04f63cab996 Mon Sep 17 00:00:00 2001 From: Bishwajeet Parhi Date: Thu, 8 Jun 2023 16:34:57 +0530 Subject: [PATCH 07/36] add new unit tests [failing] --- .../Databases/DatabasesCustomServerTest.php | 290 ++++++++++++++++++ 1 file changed, 290 insertions(+) diff --git a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php index 5e734fbcb2..b24c50aa1c 100644 --- a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php +++ b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php @@ -593,6 +593,296 @@ class DatabasesCustomServerTest extends Scope $this->assertFalse($collection['body']['enabled']); } + /** + * @depends testGetDatabase + */ + public function testCreateEncryptedAttribute(array $data): array + { + + $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, + ]); + + $age = $this->client->call(Client::METHOD_POST, $attributesPath . '/integer', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'age', + 'min' => 0, + 'max' => 120, + 'required' => false, + 'encrypt' => true, + ]); + + $datetime = $this->client->call(Client::METHOD_POST, $attributesPath . '/datetime', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'birthDay', + 'required' => false, + 'encrypt' => true, + ]); + + $email = $this->client->call(Client::METHOD_POST, $attributesPath . '/email', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'email', + 'required' => false, + 'default' => 'default@example.com', + 'encrypt' => true, + ]); + + $enum = $this->client->call(Client::METHOD_POST, $attributesPath . '/enum', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'enum', + 'elements' => ['yes', 'no', 'maybe'], + 'required' => false, + 'default' => 'maybe', + 'encrypt' => true, + ]); + + $ip = $this->client->call(Client::METHOD_POST, $attributesPath . '/ip', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'ip', + 'required' => false, + 'default' => '192.0.2.0', + 'encrypt' => true, + ]); + + $url = $this->client->call(Client::METHOD_POST, $attributesPath . '/url', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'url', + 'required' => false, + 'default' => 'http://example.com', + 'encrypt' => true, + ]); + + $bmi = $this->client->call(Client::METHOD_POST, $attributesPath . '/float', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'bmi', + 'required' => false, + 'min' => 15.0, + 'max' => 35.0, + 'default' => 21.5, + 'encrypt' => true, + ]); + + $active = $this->client->call(Client::METHOD_POST, $attributesPath . '/boolean', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'active', + 'required' => false, + 'default' => 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']); + + $this->assertEquals(202, $age['headers']['status-code']); + $this->assertEquals('age', $age['body']['key']); + $this->assertEquals('integer', $age['body']['type']); + + $this->assertEquals(202, $active['headers']['status-code']); + $this->assertEquals('active', $active['body']['key']); + $this->assertEquals('boolean', $active['body']['type']); + + $this->assertEquals(202, $bmi['headers']['status-code']); + $this->assertEquals('bmi', $bmi['body']['key']); + $this->assertEquals('double', $bmi['body']['type']); + + $this->assertEquals(202, $datetime['headers']['status-code']); + $this->assertEquals('birthDay', $datetime['body']['key']); + $this->assertEquals('datetime', $datetime['body']['type']); + + $this->assertEquals(202, $email['headers']['status-code']); + $this->assertEquals('email', $email['body']['key']); + $this->assertEquals('string', $email['body']['type']); + + $this->assertEquals(202, $ip['headers']['status-code']); + $this->assertEquals('ip', $ip['body']['key']); + $this->assertEquals('string', $ip['body']['type']); + + $this->assertEquals(202, $url['headers']['status-code']); + $this->assertEquals('url', $url['body']['key']); + $this->assertEquals('string', $url['body']['type']); + + // Wait for database worker to finish creating attributes + sleep(5); + + // 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' => 'lorem', + 'lastName' => 'ipsum', + 'age' => 25, + 'bmi' => 24.2 + ], + 'permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $index = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $actors['body']['$id'] . '/indexes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'key_lastName', + 'type' => 'key', + 'attributes' => [ + 'lastName', + ], + ]); + + // Wait for database worker to finish creating index + sleep(2); + + $collection = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $actors['body']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), []); + + $unneededId = $ip['body']['key']; + + $this->assertEquals(200, $collection['headers']['status-code']); + $this->assertIsArray($collection['body']['attributes']); + $this->assertCount(10, $collection['body']['attributes']); + $this->assertEquals($collection['body']['attributes'][0]['key'], $firstName['body']['key']); + $this->assertEquals($collection['body']['attributes'][1]['key'], $lastName['body']['key']); + $this->assertEquals($collection['body']['attributes'][2]['key'], $age['body']['key']); + $this->assertEquals($collection['body']['attributes'][3]['key'], $datetime['body']['key']); + $this->assertEquals($collection['body']['attributes'][4]['key'], $email['body']['key']); + $this->assertEquals($collection['body']['attributes'][5]['key'], $enum['body']['key']); + $this->assertEquals($collection['body']['attributes'][6]['key'], $ip['body']['key']); + $this->assertEquals($collection['body']['attributes'][7]['key'], $url['body']['key']); + $this->assertEquals($collection['body']['attributes'][8]['key'], $bmi['body']['key']); + $this->assertEquals($collection['body']['attributes'][9]['key'], $active['body']['key']); + $this->assertCount(1, $collection['body']['indexes']); + $this->assertEquals($collection['body']['indexes'][0]['key'], $index['body']['key']); + + // Delete attribute + $attribute = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $actors ['body']['$id'] . '/attributes/' . $unneededId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(204, $attribute['headers']['status-code']); + + sleep(2); + + // 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->assertNotContains($unneededId, $document['body']); + + $collection = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $actors['body']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), []); + + $this->assertEquals(200, $collection['headers']['status-code']); + $this->assertIsArray($collection['body']['attributes']); + $this->assertCount(2, $collection['body']['attributes']); + $this->assertEquals($collection['body']['attributes'][0]['key'], $firstName['body']['key']); + $this->assertEquals($collection['body']['attributes'][1]['key'], $lastName['body']['key']); + + return [ + 'collectionId' => $actors['body']['$id'], + 'key' => $index['body']['key'], + 'databaseId' => $databaseId + ]; + } + public function testDeleteAttribute(): array { $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ From 39ea3adc9d18bf9e79c591eb37456c8bb25d542c Mon Sep 17 00:00:00 2001 From: Migueli Date: Mon, 12 Jun 2023 23:39:00 +0100 Subject: [PATCH 08/36] Added missing word to portuguese email template --- app/config/locale/translations/pt-pt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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}}", From 4efd178f406b0154ec3cdb97c62d0f30bc709b7f Mon Sep 17 00:00:00 2001 From: Bishwajeet Parhi Date: Wed, 21 Jun 2023 15:22:04 +0530 Subject: [PATCH 09/36] remove encrypt on non string attributes --- app/controllers/api/databases.php | 51 ++++++------------------------- 1 file changed, 10 insertions(+), 41 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 8509829f1e..0077c2fbdc 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1103,8 +1103,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string ->param('key', '', new Key(), 'Attribute Key.') ->param('size', null, new Range(1, APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH, Range::TYPE_INTEGER), 'Attribute size for text attributes, in number of characters.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('encrypt', false, new Boolean(), 'Encrypt attribue? Encrypting an attribute means that the attribute can not be queried.', true) - ->param('default', null, new Text(0,0), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true) + ->param('encrypt', false, new Boolean(), 'Encrypt attribute? Encrypting an attribute means that the attribute can not be queried.', true) + ->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) ->inject('response') ->inject('dbForProject') @@ -1160,7 +1160,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email' ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('encrypt', false, new Boolean(), 'Encrypt attribue? Encrypting an attribute means that the attribute can not be queried.', true) + ->param('encrypt', false, new Boolean(), 'Encrypt attribute? Encrypting an attribute means that the attribute can not be queried.', true) ->param('default', null, new Email(), '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) ->inject('response') @@ -1213,7 +1213,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum') ->param('key', '', new Key(), 'Attribute Key.') ->param('elements', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE, min: 0), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of elements in enumerated type. Uses length of longest element to determine size. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' elements are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('encrypt', false, new Boolean(), 'Encrypt attribue? Encrypting an attribute means that the attribute can not be queried.', true) + ->param('encrypt', false, new Boolean(), 'Encrypt attribute? Encrypting an attribute means that the attribute can not be queried.', true) ->param('default', null, new Text(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) ->inject('response') @@ -1280,7 +1280,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('encrypt', false, new Boolean(), 'Encrypt attribue? Encrypting an attribute means that the attribute can not be queried.', true) + ->param('encrypt', false, new Boolean(), 'Encrypt attribute? Encrypting an attribute means that the attribute can not be queried.', true) ->param('default', null, new IP(), '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) ->inject('response') @@ -1332,7 +1332,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('encrypt', false, new Boolean(), 'Encrypt attribue? Encrypting an attribute means that the attribute can not be queried.', true) + ->param('encrypt', false, new Boolean(), 'Encrypt attribute? Encrypting an attribute means that the attribute can not be queried.', true) ->param('default', null, new URL(), '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) ->inject('response') @@ -1384,7 +1384,6 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('encrypt', false, new Boolean(), 'Encrypt attribue? Encrypting an attribute means that the attribute can not be queried.', true) ->param('min', null, new Integer(), 'Minimum value to enforce on new documents', true) ->param('max', null, new Integer(), 'Maximum value to enforce on new documents', true) ->param('default', null, new Integer(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true) @@ -1393,7 +1392,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege ->inject('dbForProject') ->inject('database') ->inject('events') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, bool $encrypt, ?int $min, ?int $max, ?int $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { // Ensure attribute default is within range $min = (is_null($min)) ? PHP_INT_MIN : \intval($min); @@ -1411,12 +1410,6 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege $size = $max > 2147483647 ? 8 : 4; // Automatically create BigInt depending on max value - $filters = []; - - if ($encrypt) { - $filters[] = 'encrypt'; - } - $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, 'type' => Database::VAR_INTEGER, @@ -1429,7 +1422,6 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege 'min' => $min, 'max' => $max, ], - 'filters' => $filters, ]), $response, $dbForProject, $database, $events); $formatOptions = $attribute->getAttribute('formatOptions', []); @@ -1465,7 +1457,6 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float' ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('encrypt', false, new Boolean(), 'Encrypt attribue? Encrypting an attribute means that the attribute can not be queried.', true) ->param('min', null, new FloatValidator(), 'Minimum value to enforce on new documents', true) ->param('max', null, new FloatValidator(), 'Maximum value to enforce on new documents', true) ->param('default', null, new FloatValidator(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true) @@ -1474,7 +1465,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float' ->inject('dbForProject') ->inject('database') ->inject('events') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, bool $encrypt, ?float $min, ?float $max, ?float $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { // Ensure attribute default is within range $min = (is_null($min)) ? -PHP_FLOAT_MAX : \floatval($min); @@ -1495,12 +1486,6 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float' 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_FLOAT, @@ -1513,7 +1498,6 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float' 'min' => $min, 'max' => $max, ], - 'filters' => $filters, ]), $response, $dbForProject, $database, $events); $formatOptions = $attribute->getAttribute('formatOptions', []); @@ -1549,20 +1533,13 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolea ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('encrypt', false, new Boolean(), 'Encrypt attribue? Encrypting an attribute means that the attribute can not be queried.', true) ->param('default', null, new Boolean(), '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) ->inject('response') ->inject('dbForProject') ->inject('database') ->inject('events') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, bool $encrypt, ?bool $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { - - $filters = []; - - if ($encrypt) { - $filters[] = 'encrypt'; - } + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, @@ -1571,7 +1548,6 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolea 'required' => $required, 'default' => $default, 'array' => $array, - 'filters' => $filters ]), $response, $dbForProject, $database, $events); $response @@ -1600,20 +1576,13 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('encrypt', false, new Boolean(), 'Encrypt attribue? Encrypting an attribute means that the attribute can not be queried.', true) ->param('default', null, new DatetimeValidator(), 'Default value for the attribute in ISO 8601 format. Cannot be set when attribute is required.', true) ->param('array', false, new Boolean(), 'Is attribute an array?', true) ->inject('response') ->inject('dbForProject') ->inject('database') ->inject('events') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, bool $encrypt, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { - - $filters = []; - - if ($encrypt) { - $filters[] = 'encrypt'; - } + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { $filters[] = 'datetime'; From 7ce10023c0b8290a3ad7f088765ea2d5a05c3623 Mon Sep 17 00:00:00 2001 From: Bishwajeet Parhi Date: Wed, 21 Jun 2023 16:15:00 +0530 Subject: [PATCH 10/36] chore: add tests and remove encrypt param on url,enum,ip,email --- app/controllers/api/databases.php | 46 +------ .../Databases/DatabasesCustomServerTest.php | 126 +++--------------- 2 files changed, 22 insertions(+), 150 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 0077c2fbdc..fe7cd68cdb 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1160,20 +1160,13 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email' ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('encrypt', false, new Boolean(), 'Encrypt attribute? Encrypting an attribute means that the attribute can not be queried.', true) ->param('default', null, new Email(), '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) ->inject('response') ->inject('dbForProject') ->inject('database') ->inject('events') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, bool $encrypt, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { - - $filters = []; - - if ($encrypt) { - $filters[] = 'encrypt'; - } + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, @@ -1182,8 +1175,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email' 'required' => $required, 'default' => $default, 'array' => $array, - 'format' => APP_DATABASE_ATTRIBUTE_EMAIL, - 'filters' => $filters + 'format' => APP_DATABASE_ATTRIBUTE_EMAIL ]), $response, $dbForProject, $database, $events); $response @@ -1213,14 +1205,13 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum') ->param('key', '', new Key(), 'Attribute Key.') ->param('elements', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE, min: 0), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of elements in enumerated type. Uses length of longest element to determine size. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' elements are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('encrypt', false, new Boolean(), 'Encrypt attribute? Encrypting an attribute means that the attribute can not be queried.', true) ->param('default', null, new Text(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) ->inject('response') ->inject('dbForProject') ->inject('database') ->inject('events') - ->action(function (string $databaseId, string $collectionId, string $key, array $elements, ?bool $required, bool $encrypt, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { + ->action(function (string $databaseId, string $collectionId, string $key, array $elements, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { // use length of longest string as attribute size $size = 0; @@ -1236,12 +1227,6 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum') throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Default value not found in elements'); } - $filters = []; - - if ($encrypt) { - $filters[] = 'encrypt'; - } - $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, 'type' => Database::VAR_STRING, @@ -1250,8 +1235,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum') 'default' => $default, 'array' => $array, 'format' => APP_DATABASE_ATTRIBUTE_ENUM, - 'formatOptions' => ['elements' => $elements], - 'filters' => $filters + 'formatOptions' => ['elements' => $elements] ]), $response, $dbForProject, $database, $events); $response @@ -1280,20 +1264,13 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('encrypt', false, new Boolean(), 'Encrypt attribute? Encrypting an attribute means that the attribute can not be queried.', true) ->param('default', null, new IP(), '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) ->inject('response') ->inject('dbForProject') ->inject('database') ->inject('events') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, bool $encrypt, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { - - $filters = []; - - if ($encrypt) { - $filters[] = 'encrypt'; - } + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, @@ -1302,8 +1279,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip') 'required' => $required, 'default' => $default, 'array' => $array, - 'format' => APP_DATABASE_ATTRIBUTE_IP, - 'filters' => $filters, + 'format' => APP_DATABASE_ATTRIBUTE_IP ]), $response, $dbForProject, $database, $events); $response @@ -1332,20 +1308,13 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('encrypt', false, new Boolean(), 'Encrypt attribute? Encrypting an attribute means that the attribute can not be queried.', true) ->param('default', null, new URL(), '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) ->inject('response') ->inject('dbForProject') ->inject('database') ->inject('events') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, bool $encrypt, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { - - $filters = []; - - if ($encrypt) { - $filters[] = 'encrypt'; - } + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, @@ -1355,7 +1324,6 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url') 'default' => $default, 'array' => $array, 'format' => APP_DATABASE_ATTRIBUTE_URL, - 'filters' => $filters, ]), $response, $dbForProject, $database, $events); $response diff --git a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php index b24c50aa1c..d886f449ab 100644 --- a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php +++ b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php @@ -661,86 +661,16 @@ class DatabasesCustomServerTest extends Scope 'min' => 0, 'max' => 120, 'required' => false, - 'encrypt' => true, ]); - $datetime = $this->client->call(Client::METHOD_POST, $attributesPath . '/datetime', array_merge([ + $alias = $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' => 'birthDay', + 'key' => 'alias', + 'size' => 256, 'required' => false, - 'encrypt' => true, - ]); - - $email = $this->client->call(Client::METHOD_POST, $attributesPath . '/email', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'email', - 'required' => false, - 'default' => 'default@example.com', - 'encrypt' => true, - ]); - - $enum = $this->client->call(Client::METHOD_POST, $attributesPath . '/enum', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'enum', - 'elements' => ['yes', 'no', 'maybe'], - 'required' => false, - 'default' => 'maybe', - 'encrypt' => true, - ]); - - $ip = $this->client->call(Client::METHOD_POST, $attributesPath . '/ip', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'ip', - 'required' => false, - 'default' => '192.0.2.0', - 'encrypt' => true, - ]); - - $url = $this->client->call(Client::METHOD_POST, $attributesPath . '/url', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'url', - 'required' => false, - 'default' => 'http://example.com', - 'encrypt' => true, - ]); - - $bmi = $this->client->call(Client::METHOD_POST, $attributesPath . '/float', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'bmi', - 'required' => false, - 'min' => 15.0, - 'max' => 35.0, - 'default' => 21.5, - 'encrypt' => true, - ]); - - $active = $this->client->call(Client::METHOD_POST, $attributesPath . '/boolean', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'active', - 'required' => false, - 'default' => true, - 'encrypt' => true, ]); /** @@ -754,34 +684,14 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals('lastName', $lastName['body']['key']); $this->assertEquals('string', $lastName['body']['type']); + $this->assertEquals(202, $alias['headers']['status-code']); + $this->assertEquals('alias', $alias['body']['key']); + $this->assertEquals('string', $alias['body']['type']); + $this->assertEquals(202, $age['headers']['status-code']); $this->assertEquals('age', $age['body']['key']); $this->assertEquals('integer', $age['body']['type']); - $this->assertEquals(202, $active['headers']['status-code']); - $this->assertEquals('active', $active['body']['key']); - $this->assertEquals('boolean', $active['body']['type']); - - $this->assertEquals(202, $bmi['headers']['status-code']); - $this->assertEquals('bmi', $bmi['body']['key']); - $this->assertEquals('double', $bmi['body']['type']); - - $this->assertEquals(202, $datetime['headers']['status-code']); - $this->assertEquals('birthDay', $datetime['body']['key']); - $this->assertEquals('datetime', $datetime['body']['type']); - - $this->assertEquals(202, $email['headers']['status-code']); - $this->assertEquals('email', $email['body']['key']); - $this->assertEquals('string', $email['body']['type']); - - $this->assertEquals(202, $ip['headers']['status-code']); - $this->assertEquals('ip', $ip['body']['key']); - $this->assertEquals('string', $ip['body']['type']); - - $this->assertEquals(202, $url['headers']['status-code']); - $this->assertEquals('url', $url['body']['key']); - $this->assertEquals('string', $url['body']['type']); - // Wait for database worker to finish creating attributes sleep(5); @@ -793,10 +703,10 @@ class DatabasesCustomServerTest extends Scope ]), [ 'documentId' => ID::unique(), 'data' => [ - 'firstName' => 'lorem', - 'lastName' => 'ipsum', + 'firstName' => 'Jonah', + 'lastName' => 'Jameson', 'age' => 25, - 'bmi' => 24.2 + 'alias' => 'JJ' ], 'permissions' => [ Permission::read(Role::any()), @@ -826,24 +736,18 @@ class DatabasesCustomServerTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'] ]), []); - $unneededId = $ip['body']['key']; - $this->assertEquals(200, $collection['headers']['status-code']); $this->assertIsArray($collection['body']['attributes']); - $this->assertCount(10, $collection['body']['attributes']); + $this->assertCount(4, $collection['body']['attributes']); $this->assertEquals($collection['body']['attributes'][0]['key'], $firstName['body']['key']); $this->assertEquals($collection['body']['attributes'][1]['key'], $lastName['body']['key']); $this->assertEquals($collection['body']['attributes'][2]['key'], $age['body']['key']); - $this->assertEquals($collection['body']['attributes'][3]['key'], $datetime['body']['key']); - $this->assertEquals($collection['body']['attributes'][4]['key'], $email['body']['key']); - $this->assertEquals($collection['body']['attributes'][5]['key'], $enum['body']['key']); - $this->assertEquals($collection['body']['attributes'][6]['key'], $ip['body']['key']); - $this->assertEquals($collection['body']['attributes'][7]['key'], $url['body']['key']); - $this->assertEquals($collection['body']['attributes'][8]['key'], $bmi['body']['key']); - $this->assertEquals($collection['body']['attributes'][9]['key'], $active['body']['key']); + $this->assertEquals($collection['body']['attributes'][3]['key'], $alias['body']['key']); $this->assertCount(1, $collection['body']['indexes']); $this->assertEquals($collection['body']['indexes'][0]['key'], $index['body']['key']); + $unneededId = $alias['body']['key']; + // Delete attribute $attribute = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $actors ['body']['$id'] . '/attributes/' . $unneededId, array_merge([ 'content-type' => 'application/json', @@ -872,7 +776,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(200, $collection['headers']['status-code']); $this->assertIsArray($collection['body']['attributes']); - $this->assertCount(2, $collection['body']['attributes']); + $this->assertCount(3, $collection['body']['attributes']); $this->assertEquals($collection['body']['attributes'][0]['key'], $firstName['body']['key']); $this->assertEquals($collection['body']['attributes'][1]['key'], $lastName['body']['key']); From 09162e8880ac68a37fbb28c9f4c6448d09d4bc5b Mon Sep 17 00:00:00 2001 From: Bishwajeet Parhi Date: Wed, 21 Jun 2023 16:25:22 +0530 Subject: [PATCH 11/36] chore: fix failing tests --- tests/e2e/Services/Databases/DatabasesCustomServerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php index d886f449ab..b07e4dd0c6 100644 --- a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php +++ b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php @@ -594,7 +594,7 @@ class DatabasesCustomServerTest extends Scope } /** - * @depends testGetDatabase + * @depends testListCollections */ public function testCreateEncryptedAttribute(array $data): array { From ee30a3e9f451602f2d6cc619ba902aab4ca98727 Mon Sep 17 00:00:00 2001 From: Bishwajeet Parhi Date: Thu, 22 Jun 2023 09:59:09 +0530 Subject: [PATCH 12/36] chore: refactor tests and param positioning changes --- app/controllers/api/databases.php | 6 +- .../Databases/DatabasesCustomServerTest.php | 100 +----------------- 2 files changed, 8 insertions(+), 98 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index fe7cd68cdb..ff071ae11e 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1103,14 +1103,14 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string ->param('key', '', new Key(), 'Attribute Key.') ->param('size', null, new Range(1, APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH, Range::TYPE_INTEGER), 'Attribute size for text attributes, in number of characters.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('encrypt', false, new Boolean(), 'Encrypt attribute? Encrypting an attribute means that the attribute can not be queried.', true) ->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(), 'Encrypt attribute? Encrypting an attribute means that the attribute can not be queried.', true) ->inject('response') ->inject('dbForProject') ->inject('database') ->inject('events') - ->action(function (string $databaseId, string $collectionId, string $key, ?int $size, bool $required, bool $encrypt, ?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); @@ -1175,7 +1175,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email' 'required' => $required, 'default' => $default, 'array' => $array, - 'format' => APP_DATABASE_ATTRIBUTE_EMAIL + 'format' => APP_DATABASE_ATTRIBUTE_EMAIL, ]), $response, $dbForProject, $database, $events); $response diff --git a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php index b07e4dd0c6..190a52de7b 100644 --- a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php +++ b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php @@ -596,7 +596,7 @@ class DatabasesCustomServerTest extends Scope /** * @depends testListCollections */ - public function testCreateEncryptedAttribute(array $data): array + public function testCreateEncryptedAttribute(array $data): void { $databaseId = $data['databaseId']; @@ -652,26 +652,6 @@ class DatabasesCustomServerTest extends Scope 'encrypt' => true, ]); - $age = $this->client->call(Client::METHOD_POST, $attributesPath . '/integer', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'age', - 'min' => 0, - 'max' => 120, - 'required' => false, - ]); - - $alias = $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' => 'alias', - 'size' => 256, - 'required' => false, - ]); /** * Check Status of every Attribute @@ -684,16 +664,8 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals('lastName', $lastName['body']['key']); $this->assertEquals('string', $lastName['body']['type']); - $this->assertEquals(202, $alias['headers']['status-code']); - $this->assertEquals('alias', $alias['body']['key']); - $this->assertEquals('string', $alias['body']['type']); - - $this->assertEquals(202, $age['headers']['status-code']); - $this->assertEquals('age', $age['body']['key']); - $this->assertEquals('integer', $age['body']['type']); - // Wait for database worker to finish creating attributes - sleep(5); + 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([ @@ -705,8 +677,6 @@ class DatabasesCustomServerTest extends Scope 'data' => [ 'firstName' => 'Jonah', 'lastName' => 'Jameson', - 'age' => 25, - 'alias' => 'JJ' ], 'permissions' => [ Permission::read(Role::any()), @@ -715,50 +685,6 @@ class DatabasesCustomServerTest extends Scope ], ]); - $index = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $actors['body']['$id'] . '/indexes', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'key_lastName', - 'type' => 'key', - 'attributes' => [ - 'lastName', - ], - ]); - - // Wait for database worker to finish creating index - sleep(2); - - $collection = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $actors['body']['$id'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), []); - - $this->assertEquals(200, $collection['headers']['status-code']); - $this->assertIsArray($collection['body']['attributes']); - $this->assertCount(4, $collection['body']['attributes']); - $this->assertEquals($collection['body']['attributes'][0]['key'], $firstName['body']['key']); - $this->assertEquals($collection['body']['attributes'][1]['key'], $lastName['body']['key']); - $this->assertEquals($collection['body']['attributes'][2]['key'], $age['body']['key']); - $this->assertEquals($collection['body']['attributes'][3]['key'], $alias['body']['key']); - $this->assertCount(1, $collection['body']['indexes']); - $this->assertEquals($collection['body']['indexes'][0]['key'], $index['body']['key']); - - $unneededId = $alias['body']['key']; - - // Delete attribute - $attribute = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $actors ['body']['$id'] . '/attributes/' . $unneededId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->assertEquals(204, $attribute['headers']['status-code']); - - sleep(2); - // 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', @@ -766,25 +692,9 @@ class DatabasesCustomServerTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'] ])); - $this->assertNotContains($unneededId, $document['body']); - - $collection = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $actors['body']['$id'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), []); - - $this->assertEquals(200, $collection['headers']['status-code']); - $this->assertIsArray($collection['body']['attributes']); - $this->assertCount(3, $collection['body']['attributes']); - $this->assertEquals($collection['body']['attributes'][0]['key'], $firstName['body']['key']); - $this->assertEquals($collection['body']['attributes'][1]['key'], $lastName['body']['key']); - - return [ - 'collectionId' => $actors['body']['$id'], - 'key' => $index['body']['key'], - 'databaseId' => $databaseId - ]; + $this->assertEquals(200, $document['headers']['status-code']); + $this->assertEquals('Jonah', $document['body']['firstName']); + $this->assertEquals('Jameson', $document['body']['lastName']); } public function testDeleteAttribute(): array From cb35d2dbbe487135cbade856f473eb5fa74d927b Mon Sep 17 00:00:00 2001 From: Bishwajeet Parhi Date: Thu, 22 Jun 2023 11:53:29 +0530 Subject: [PATCH 13/36] chore: fix missing nullable required param --- app/controllers/api/databases.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index ff071ae11e..78875810ba 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1110,7 +1110,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string ->inject('dbForProject') ->inject('database') ->inject('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) { + ->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); From e98e1db04618a9073c9673c83811a8e797e8261b Mon Sep 17 00:00:00 2001 From: Bishwajeet Parhi Date: Thu, 22 Jun 2023 11:55:27 +0530 Subject: [PATCH 14/36] chore: fix trailing comma --- app/controllers/api/databases.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 78875810ba..9c38b11a72 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1235,7 +1235,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum') 'default' => $default, 'array' => $array, 'format' => APP_DATABASE_ATTRIBUTE_ENUM, - 'formatOptions' => ['elements' => $elements] + 'formatOptions' => ['elements' => $elements], ]), $response, $dbForProject, $database, $events); $response @@ -1279,7 +1279,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip') 'required' => $required, 'default' => $default, 'array' => $array, - 'format' => APP_DATABASE_ATTRIBUTE_IP + 'format' => APP_DATABASE_ATTRIBUTE_IP, ]), $response, $dbForProject, $database, $events); $response From ed8bdc66c8238c58e7014f9e0fc851508cee0a97 Mon Sep 17 00:00:00 2001 From: Bishwajeet Parhi Date: Fri, 23 Jun 2023 15:59:23 +0530 Subject: [PATCH 15/36] chore: fix indentation --- tests/e2e/Services/Databases/DatabasesCustomServerTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php index 190a52de7b..648a4de800 100644 --- a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php +++ b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php @@ -653,9 +653,9 @@ class DatabasesCustomServerTest extends Scope ]); - /** - * Check Status of every Attribute - */ + /** + * Check status of every attribute + */ $this->assertEquals(202, $firstName['headers']['status-code']); $this->assertEquals('firstName', $firstName['body']['key']); $this->assertEquals('string', $firstName['body']['type']); From f71fdfb0b9a47ce382fcc16e1c0e06b4b283040f Mon Sep 17 00:00:00 2001 From: Bishwajeet Parhi Date: Sat, 8 Jul 2023 20:58:41 +0530 Subject: [PATCH 16/36] chore: add encrypt param on update string attr + unit tests --- app/controllers/api/databases.php | 12 ++++++- .../Databases/DatabasesCustomServerTest.php | 31 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 9c38b11a72..4896c56ccc 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -351,6 +351,7 @@ function updateAttribute( id: $key, required: $required, default: $default, + filters: [$filter], formatOptions: $options ?? null ); } @@ -1788,10 +1789,18 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/strin ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') ->param('default', null, new Nullable(new Text(0, 0)), 'Default value for attribute when not provided. Cannot be set when attribute is required.') + ->param('encrypt', null, new Boolean(), 'Encrypt attribute? Encrypting an attribute means that the attribute can not be queried.', true) ->inject('response') ->inject('dbForProject') ->inject('events') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, Response $response, Database $dbForProject, Event $events) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?bool $encrypt, Response $response, Database $dbForProject, Event $events) { + + $filter = ''; + + if ($encrypt != null) { + $filter = $encrypt ? 'encrypt' : 'decrypt'; + } + $attribute = updateAttribute( databaseId: $databaseId, collectionId: $collectionId, @@ -1799,6 +1808,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/strin dbForProject: $dbForProject, events: $events, type: Database::VAR_STRING, + filter: empty($filter) ? null : $filter, default: $default, required: $required ); diff --git a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php index 648a4de800..c4d71d8428 100644 --- a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php +++ b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php @@ -695,6 +695,37 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(200, $document['headers']['status-code']); $this->assertEquals('Jonah', $document['body']['firstName']); $this->assertEquals('Jameson', $document['body']['lastName']); + + /** + * Update Attribute + */ + $updatedFirstname = $this->client->call(Client::METHOD_PATCH, $attributesPath . '/string/' . $firstName['body']['key'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'required' => false, + 'encrypt' => true, + 'default' => '' + ]); + + $this->assertEquals(200, $updatedFirstname['headers']['status-code']); + $this->assertEquals('firstName', $updatedFirstname['body']['key']); + $this->assertEquals('string', $updatedFirstname['body']['type']); + + $updatedLastName = $this->client->call(Client::METHOD_PATCH, $attributesPath . '/string/' . $lastName['body']['key'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), [ + 'required' => false, + 'encrypt' => false, + 'default' => '' + ]); + + $this->assertEquals(200, $updatedLastName['headers']['status-code']); + $this->assertEquals('lastName', $updatedLastName['body']['key']); + $this->assertEquals('string', $updatedLastName['body']['type']); } public function testDeleteAttribute(): array From 07df99c817e20d60692a235c0a3fc2735dc618dd Mon Sep 17 00:00:00 2001 From: Bishwajeet Parhi Date: Tue, 11 Jul 2023 19:16:22 +0530 Subject: [PATCH 17/36] update description --- app/controllers/api/databases.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 4896c56ccc..16718b788c 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1106,7 +1106,7 @@ 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(), 'Encrypt attribute? Encrypting an attribute means that the attribute can not be queried.', 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') From f160c55ecb644ec01f730a5aa342354e22df9fef Mon Sep 17 00:00:00 2001 From: Bishwajeet Parhi Date: Wed, 26 Jul 2023 11:25:25 +0530 Subject: [PATCH 18/36] Chore: remove encrypt param on update Atrribute and tests as well --- app/controllers/api/databases.php | 9 +----- .../Databases/DatabasesCustomServerTest.php | 31 ------------------- 2 files changed, 1 insertion(+), 39 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index abc13710ba..00e4247cbe 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1789,17 +1789,11 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/strin ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') ->param('default', null, new Nullable(new Text(0, 0)), 'Default value for attribute when not provided. Cannot be set when attribute is required.') - ->param('encrypt', null, new Boolean(), 'Encrypt attribute? Encrypting an attribute means that the attribute can not be queried.', true) ->inject('response') ->inject('dbForProject') ->inject('events') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?bool $encrypt, Response $response, Database $dbForProject, Event $events) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, Response $response, Database $dbForProject, Event $events) { - $filter = ''; - - if ($encrypt != null) { - $filter = $encrypt ? 'encrypt' : 'decrypt'; - } $attribute = updateAttribute( databaseId: $databaseId, @@ -1808,7 +1802,6 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/strin dbForProject: $dbForProject, events: $events, type: Database::VAR_STRING, - filter: empty($filter) ? null : $filter, default: $default, required: $required ); diff --git a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php index c4d71d8428..648a4de800 100644 --- a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php +++ b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php @@ -695,37 +695,6 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(200, $document['headers']['status-code']); $this->assertEquals('Jonah', $document['body']['firstName']); $this->assertEquals('Jameson', $document['body']['lastName']); - - /** - * Update Attribute - */ - $updatedFirstname = $this->client->call(Client::METHOD_PATCH, $attributesPath . '/string/' . $firstName['body']['key'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'required' => false, - 'encrypt' => true, - 'default' => '' - ]); - - $this->assertEquals(200, $updatedFirstname['headers']['status-code']); - $this->assertEquals('firstName', $updatedFirstname['body']['key']); - $this->assertEquals('string', $updatedFirstname['body']['type']); - - $updatedLastName = $this->client->call(Client::METHOD_PATCH, $attributesPath . '/string/' . $lastName['body']['key'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), [ - 'required' => false, - 'encrypt' => false, - 'default' => '' - ]); - - $this->assertEquals(200, $updatedLastName['headers']['status-code']); - $this->assertEquals('lastName', $updatedLastName['body']['key']); - $this->assertEquals('string', $updatedLastName['body']['type']); } public function testDeleteAttribute(): array From c821282228dd6bd6b39241296d77d4053a8876d0 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 1 Aug 2023 12:46:09 +0300 Subject: [PATCH 19/36] composer.lock --- composer.lock | 76 +++++++++++++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/composer.lock b/composer.lock index 4fae7f5619..2f2adcea38 100644 --- a/composer.lock +++ b/composer.lock @@ -2964,16 +2964,16 @@ }, { "name": "webonyx/graphql-php", - "version": "v14.11.9", + "version": "v14.11.10", "source": { "type": "git", "url": "https://github.com/webonyx/graphql-php.git", - "reference": "ff91c9f3cf241db702e30b2c42bcc0920e70ac70" + "reference": "d9c2fdebc6aa01d831bc2969da00e8588cffef19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/ff91c9f3cf241db702e30b2c42bcc0920e70ac70", - "reference": "ff91c9f3cf241db702e30b2c42bcc0920e70ac70", + "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/d9c2fdebc6aa01d831bc2969da00e8588cffef19", + "reference": "d9c2fdebc6aa01d831bc2969da00e8588cffef19", "shasum": "" }, "require": { @@ -2993,8 +2993,7 @@ "phpunit/phpunit": "^7.2 || ^8.5", "psr/http-message": "^1.0", "react/promise": "2.*", - "simpod/php-coveralls-mirror": "^3.0", - "squizlabs/php_codesniffer": "3.5.4" + "simpod/php-coveralls-mirror": "^3.0" }, "suggest": { "psr/http-message": "To use standard GraphQL server", @@ -3018,7 +3017,7 @@ ], "support": { "issues": "https://github.com/webonyx/graphql-php/issues", - "source": "https://github.com/webonyx/graphql-php/tree/v14.11.9" + "source": "https://github.com/webonyx/graphql-php/tree/v14.11.10" }, "funding": [ { @@ -3026,22 +3025,22 @@ "type": "open_collective" } ], - "time": "2023-01-06T12:12:50+00:00" + "time": "2023-07-05T14:23:37+00:00" } ], "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "0.33.1", + "version": "0.33.7", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "551cdae31a68b19874f10ca321b1d08cfa06a13f" + "reference": "9f5db4a637b23879ceacea9ed2d33b0486771ffc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/551cdae31a68b19874f10ca321b1d08cfa06a13f", - "reference": "551cdae31a68b19874f10ca321b1d08cfa06a13f", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/9f5db4a637b23879ceacea9ed2d33b0486771ffc", + "reference": "9f5db4a637b23879ceacea9ed2d33b0486771ffc", "shasum": "" }, "require": { @@ -3077,9 +3076,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/0.33.1" + "source": "https://github.com/appwrite/sdk-generator/tree/0.33.7" }, - "time": "2023-05-16T04:37:34+00:00" + "time": "2023-07-12T12:15:43+00:00" }, { "name": "doctrine/deprecations", @@ -3383,16 +3382,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.15.5", + "version": "v4.16.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e" + "reference": "19526a33fb561ef417e822e85f08a00db4059c17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/11e2663a5bc9db5d714eedb4277ee300403b4a9e", - "reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/19526a33fb561ef417e822e85f08a00db4059c17", + "reference": "19526a33fb561ef417e822e85f08a00db4059c17", "shasum": "" }, "require": { @@ -3433,9 +3432,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.5" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.16.0" }, - "time": "2023-05-19T20:20:00+00:00" + "time": "2023-06-25T14:52:30+00:00" }, { "name": "phar-io/manifest", @@ -3786,16 +3785,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.22.0", + "version": "1.23.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "ec58baf7b3c7f1c81b3b00617c953249fb8cf30c" + "reference": "a2b24135c35852b348894320d47b3902a94bc494" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/ec58baf7b3c7f1c81b3b00617c953249fb8cf30c", - "reference": "ec58baf7b3c7f1c81b3b00617c953249fb8cf30c", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a2b24135c35852b348894320d47b3902a94bc494", + "reference": "a2b24135c35852b348894320d47b3902a94bc494", "shasum": "" }, "require": { @@ -3827,22 +3826,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.22.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.23.0" }, - "time": "2023-06-01T12:35:21+00:00" + "time": "2023-07-23T22:17:56+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.26", + "version": "9.2.27", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1" + "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", - "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b0a88255cb70d52653d80c890bd7f38740ea50d1", + "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1", "shasum": "" }, "require": { @@ -3898,7 +3897,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26" + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.27" }, "funding": [ { @@ -3906,7 +3906,7 @@ "type": "github" } ], - "time": "2023-03-06T12:58:08+00:00" + "time": "2023-07-26T13:44:30+00:00" }, { "name": "phpunit/php-file-iterator", @@ -5581,16 +5581,16 @@ }, { "name": "twig/twig", - "version": "v3.6.1", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd" + "reference": "5cf942bbab3df42afa918caeba947f1b690af64b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd", - "reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/5cf942bbab3df42afa918caeba947f1b690af64b", + "reference": "5cf942bbab3df42afa918caeba947f1b690af64b", "shasum": "" }, "require": { @@ -5636,7 +5636,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.6.1" + "source": "https://github.com/twigphp/Twig/tree/v3.7.0" }, "funding": [ { @@ -5648,7 +5648,7 @@ "type": "tidelift" } ], - "time": "2023-06-08T12:52:13+00:00" + "time": "2023-07-26T07:16:09+00:00" } ], "aliases": [], From aaa2f18bf93ad8a94f3b6f1d98600d77b91d7e05 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 1 Aug 2023 13:23:39 +0300 Subject: [PATCH 20/36] uid varchar --- composer.lock | 2 +- src/Appwrite/Migration/Version/V19.php | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/composer.lock b/composer.lock index b01599337a..24d9f2ca0c 100644 --- a/composer.lock +++ b/composer.lock @@ -5677,5 +5677,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.2.0" } diff --git a/src/Appwrite/Migration/Version/V19.php b/src/Appwrite/Migration/Version/V19.php index 75cd472ebb..c31b4fa9e4 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,20 @@ 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 +173,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); } } } From e62052546037520105f4ebd21ed11b0befe211e3 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 1 Aug 2023 13:24:28 +0300 Subject: [PATCH 21/36] Extra line --- src/Appwrite/Migration/Version/V19.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Appwrite/Migration/Version/V19.php b/src/Appwrite/Migration/Version/V19.php index c31b4fa9e4..39522aff0b 100644 --- a/src/Appwrite/Migration/Version/V19.php +++ b/src/Appwrite/Migration/Version/V19.php @@ -150,7 +150,6 @@ class V19 extends Migration { try { $table = "`{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}_{$collectionName}`"; - $this->pdo->prepare(" ALTER TABLE {$table} CHANGE COLUMN `_uid` `_uid` VARCHAR(255) NOT NULL ; From 0fe59d457b08fbbf588a128e8a410e618d48308e Mon Sep 17 00:00:00 2001 From: Safwan Parkar Date: Tue, 1 Aug 2023 22:57:59 +0400 Subject: [PATCH 22/36] fixed stale team memberships on user --- app/controllers/api/teams.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index a06ab6b2a0..fe58129f1a 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -343,11 +343,17 @@ App::delete('/v1/teams/:teamId') Query::limit(2000), // TODO fix members limit ]); - // TODO delete all members individually from the user object foreach ($memberships as $membership) { if (!$dbForProject->deleteDocument('memberships', $membership->getId())) { throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove membership for team from DB'); } + + $user = $dbForProject->getDocument('users', $membership->getAttribute('userId')); + $user->setAttribute('memberships', array_values(array_filter( + $user->getAttribute('memberships', []), + fn($um) => $um['teamId'] !== $membership->getAttribute('teamId')) + )); + $dbForProject->updateDocument('users', $user->getId(), $user); } if (!$dbForProject->deleteDocument('teams', $teamId)) { From a45c62ab24c8a9e415a46290e968a70a3741bc12 Mon Sep 17 00:00:00 2001 From: Safwan Parkar Date: Tue, 1 Aug 2023 23:24:46 +0400 Subject: [PATCH 23/36] run composer scripts --- app/controllers/api/teams.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index fe58129f1a..f34187ea9a 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -350,9 +350,9 @@ App::delete('/v1/teams/:teamId') $user = $dbForProject->getDocument('users', $membership->getAttribute('userId')); $user->setAttribute('memberships', array_values(array_filter( - $user->getAttribute('memberships', []), - fn($um) => $um['teamId'] !== $membership->getAttribute('teamId')) - )); + $user->getAttribute('memberships', []), + fn($um) => $um['teamId'] !== $membership->getAttribute('teamId') + ))); $dbForProject->updateDocument('users', $user->getId(), $user); } From c5233d9ece9f9c0a82c6121a367d3f63ce032dd2 Mon Sep 17 00:00:00 2001 From: Safwan Parkar Date: Wed, 2 Aug 2023 12:18:21 +0400 Subject: [PATCH 24/36] removed unnecessary code --- app/controllers/api/teams.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index f34187ea9a..2a2611cc5c 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -347,13 +347,6 @@ App::delete('/v1/teams/:teamId') if (!$dbForProject->deleteDocument('memberships', $membership->getId())) { throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove membership for team from DB'); } - - $user = $dbForProject->getDocument('users', $membership->getAttribute('userId')); - $user->setAttribute('memberships', array_values(array_filter( - $user->getAttribute('memberships', []), - fn($um) => $um['teamId'] !== $membership->getAttribute('teamId') - ))); - $dbForProject->updateDocument('users', $user->getId(), $user); } if (!$dbForProject->deleteDocument('teams', $teamId)) { From 2bc2061f091eb4e3ba358292057dac1db02c4273 Mon Sep 17 00:00:00 2001 From: Safwan Parkar Date: Wed, 2 Aug 2023 18:44:43 +0400 Subject: [PATCH 25/36] fix showing of stale team memberships --- app/controllers/api/users.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index d84d83ff77..fa92739510 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -530,6 +530,10 @@ App::get('/v1/users/:userId/memberships') $memberships = array_map(function ($membership) use ($dbForProject, $user) { $team = $dbForProject->getDocument('teams', $membership->getAttribute('teamId')); + if ($team->isEmpty()) { + throw new Exception(Exception::TEAM_NOT_FOUND); + } + $membership ->setAttribute('teamName', $team->getAttribute('name')) ->setAttribute('userName', $user->getAttribute('name')) From a14d89eb3cc3cc7fb28d154682b33565f990d0b1 Mon Sep 17 00:00:00 2001 From: Safwan Parkar Date: Thu, 3 Aug 2023 21:39:18 +0400 Subject: [PATCH 26/36] remove error-causing condition --- app/controllers/api/users.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index fa92739510..d84d83ff77 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -530,10 +530,6 @@ App::get('/v1/users/:userId/memberships') $memberships = array_map(function ($membership) use ($dbForProject, $user) { $team = $dbForProject->getDocument('teams', $membership->getAttribute('teamId')); - if ($team->isEmpty()) { - throw new Exception(Exception::TEAM_NOT_FOUND); - } - $membership ->setAttribute('teamName', $team->getAttribute('name')) ->setAttribute('userName', $user->getAttribute('name')) From d142620c5e9ec7f3125e6d39f0c4c7d302bfc62a Mon Sep 17 00:00:00 2001 From: Safwan Parkar Date: Thu, 3 Aug 2023 22:08:27 +0400 Subject: [PATCH 27/36] invalidate cached document of user - cache caused stale data in memberships --- app/controllers/api/teams.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 2a2611cc5c..2b2f3b6920 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -344,9 +344,11 @@ App::delete('/v1/teams/:teamId') ]); foreach ($memberships as $membership) { + // Memberships are deleted here instead of in the worker to make sure user permisions are updated instantly if (!$dbForProject->deleteDocument('memberships', $membership->getId())) { throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove membership for team from DB'); } + $dbForProject->deleteCachedDocument('users', $membership->getAttribute('userId')); } if (!$dbForProject->deleteDocument('teams', $teamId)) { From 06e1191063435bb184a356aa571827301554f623 Mon Sep 17 00:00:00 2001 From: Safwan Parkar Date: Fri, 4 Aug 2023 00:34:01 +0400 Subject: [PATCH 28/36] added test --- tests/e2e/Services/Teams/TeamsBase.php | 63 ++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/e2e/Services/Teams/TeamsBase.php b/tests/e2e/Services/Teams/TeamsBase.php index 8e19bede17..7d1e51c08c 100644 --- a/tests/e2e/Services/Teams/TeamsBase.php +++ b/tests/e2e/Services/Teams/TeamsBase.php @@ -428,4 +428,67 @@ trait TeamsBase $this->assertEquals($user['headers']['status-code'], 400); } + + public function testTeamDeleteUpdatesUserMembership() + { + $users = []; + $team = null; + + $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'teamId' => ID::unique(), + 'name' => 'Demo' + ]); + + $this->assertEquals(201, $team['headers']['status-code']); + $this->assertNotEmpty($team['body']['$id']); + $this->assertEquals('Demo', $team['body']['name']); + $this->assertGreaterThan(-1, $team['body']['total']); + $this->assertIsInt($team['body']['total']); + + for ($i = 0; $i < 5; $i++) { + $mem = $this->client->call(Client::METHOD_POST, '/teams/' . $team['body']['$id'] . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'email' => 'email' . $i . '@example.com', + 'roles' => ['admin', 'editor'], + 'name' => 'User ' . $i, + 'url' => 'http://localhost:5000/join-us#title' + ]); + + $this->assertEquals(201, $mem['headers']['status-code']); + $this->assertNotEmpty($mem['body']['$id']); + $this->assertNotEmpty($mem['body']['userId']); + $this->assertEquals('User ' . $i, $mem['body']['userName']); + $this->assertEquals('email' . $i . '@example.com', $mem['body']['userEmail']); + $this->assertNotEmpty($mem['body']['teamId']); + $this->assertCount(2, $mem['body']['roles']); + } + + $this->client->call(Client::METHOD_DELETE, '/teams/' . $team['body']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + foreach ($users as $user) { + $user = $this->client->call(Client::METHOD_GET, '/users/' . $user['body']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $user['headers']['status-code']); + $this->assertEquals(0, $user['body']['total']); + $this->assertEquals([], $user['body']['memberships']); + } + + $team = $this->client->call(Client::METHOD_GET, '/teams/' . $team['body']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(404, $team['headers']['status-code']); + } } From 451f4bee19d31a1408049885a2f8fe6d3fc3faa5 Mon Sep 17 00:00:00 2001 From: Safwan Parkar Date: Fri, 4 Aug 2023 15:40:42 +0400 Subject: [PATCH 29/36] Fixed an incorrect test for team deletion This commit contains changes in 3 places. - First I changed the placement of an informative comment in the DELETE TEAM controller, and moved it outside of the loop. I did this when Steven pointed out that the behaviour I describe in the comment is for the whole loop. - The second change is the removal of my first test. I was facing quite a few issues with creating users in the test, and ended up using the CREATE TEAM MEMBERSHIP to perform 2 actions at once -> create a new user if one doesn't exist with the provided email, and create a membership for the user. Before this approach, I had quite a bit of code that didn't work, and it seems like I removed some things that weren't supposed to be removed, and didn't change variable names where necessary. Anyway, I figured that the problem has something to do with the user being created on the client side, so I moved the test to the server side. - The new test I implemented does the same thing as my previous failed test, but in more detailed and distinct steps. The test first creates 5 new users inside of a loop, and pushes each new user's ID to an array called 'new_users' if the response is as expected. Then a new team is created. The next step is to create memberships for all 5 users. If all these steps pass, the new team that was just created, is deleted, and we check to make sure the new users have 0 team memberships each. Formatter and linter showed no errors. Tests were successful on localhost. --- app/controllers/api/teams.php | 2 +- tests/e2e/Services/Teams/TeamsBase.php | 63 ------------- tests/e2e/Services/Teams/TeamsBaseServer.php | 98 ++++++++++++++++++++ 3 files changed, 99 insertions(+), 64 deletions(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 2b2f3b6920..5c5c128f57 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -343,8 +343,8 @@ App::delete('/v1/teams/:teamId') Query::limit(2000), // TODO fix members limit ]); + // Memberships are deleted here instead of in the worker to make sure user permisions are updated instantly foreach ($memberships as $membership) { - // Memberships are deleted here instead of in the worker to make sure user permisions are updated instantly if (!$dbForProject->deleteDocument('memberships', $membership->getId())) { throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove membership for team from DB'); } diff --git a/tests/e2e/Services/Teams/TeamsBase.php b/tests/e2e/Services/Teams/TeamsBase.php index 7d1e51c08c..8e19bede17 100644 --- a/tests/e2e/Services/Teams/TeamsBase.php +++ b/tests/e2e/Services/Teams/TeamsBase.php @@ -428,67 +428,4 @@ trait TeamsBase $this->assertEquals($user['headers']['status-code'], 400); } - - public function testTeamDeleteUpdatesUserMembership() - { - $users = []; - $team = null; - - $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'teamId' => ID::unique(), - 'name' => 'Demo' - ]); - - $this->assertEquals(201, $team['headers']['status-code']); - $this->assertNotEmpty($team['body']['$id']); - $this->assertEquals('Demo', $team['body']['name']); - $this->assertGreaterThan(-1, $team['body']['total']); - $this->assertIsInt($team['body']['total']); - - for ($i = 0; $i < 5; $i++) { - $mem = $this->client->call(Client::METHOD_POST, '/teams/' . $team['body']['$id'] . '/memberships', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'email' => 'email' . $i . '@example.com', - 'roles' => ['admin', 'editor'], - 'name' => 'User ' . $i, - 'url' => 'http://localhost:5000/join-us#title' - ]); - - $this->assertEquals(201, $mem['headers']['status-code']); - $this->assertNotEmpty($mem['body']['$id']); - $this->assertNotEmpty($mem['body']['userId']); - $this->assertEquals('User ' . $i, $mem['body']['userName']); - $this->assertEquals('email' . $i . '@example.com', $mem['body']['userEmail']); - $this->assertNotEmpty($mem['body']['teamId']); - $this->assertCount(2, $mem['body']['roles']); - } - - $this->client->call(Client::METHOD_DELETE, '/teams/' . $team['body']['$id'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - foreach ($users as $user) { - $user = $this->client->call(Client::METHOD_GET, '/users/' . $user['body']['$id'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $user['headers']['status-code']); - $this->assertEquals(0, $user['body']['total']); - $this->assertEquals([], $user['body']['memberships']); - } - - $team = $this->client->call(Client::METHOD_GET, '/teams/' . $team['body']['$id'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(404, $team['headers']['status-code']); - } } diff --git a/tests/e2e/Services/Teams/TeamsBaseServer.php b/tests/e2e/Services/Teams/TeamsBaseServer.php index aa1be49f41..569170f890 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 { @@ -281,4 +282,101 @@ trait TeamsBaseServer $this->assertIsInt($response['body']['total']); $this->assertEquals(true, $dateValidator->isValid($response['body']['$createdAt'])); } + + public function testTeamDeleteUpdatesUserMembership() + { + $new_users = []; + + /** + * Create 5 new users and add their IDs to an array + */ + for ($i = 0; $i < 5; $i++) { + $user = $this->client->call(Client::METHOD_POST, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'userId' => ID::unique(), + 'email' => 'newuser' . $i . '@localhost.test', + 'password' => 'password', + 'name' => 'New User ' . $i, + ], false); + + $user_body = json_decode($user['body'], true); + + $this->assertEquals(201, $user['headers']['status-code']); + $this->assertEquals('newuser' . $i . '@localhost.test', $user_body['email']); + $this->assertEquals('New User ' . $i, $user_body['name']); + $this->assertEquals($user_body['status'], true); + + array_push($new_users, $user_body['$id']); + } + + /** + * Create a new team + */ + $new_team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'teamId' => ID::unique(), + 'name' => 'New Team Test', + 'roles' => ['admin', 'editor'], + ]); + + $this->assertEquals(201, $new_team['headers']['status-code']); + $this->assertNotEmpty($new_team['body']['$id']); + $this->assertEquals('New Team Test', $new_team['body']['name']); + $this->assertGreaterThan(-1, $new_team['body']['total']); + $this->assertIsInt($new_team['body']['total']); + $this->assertArrayHasKey('prefs', $new_team['body']); + + /** + * Create team memberships for each of the new users + */ + for ($i = 0; $i < 5; $i++) { + $new_membership = $this->client->call(Client::METHOD_POST, '/teams/' . $new_team['body']['$id'] . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'email' => 'newuser' . $i . '@localhost.test', + 'name' => 'New User ' . $i, + 'roles' => ['admin', 'editor'], + 'url' => 'http://localhost:5000/join-us#title' + ]); + + $this->assertEquals(201, $new_membership['headers']['status-code']); + $this->assertNotEmpty($new_membership['body']['$id']); + $this->assertNotEmpty($new_membership['body']['userId']); + $this->assertEquals('New User ' . $i, $new_membership['body']['userName']); + $this->assertEquals('newuser' . $i . '@localhost.test', $new_membership['body']['userEmail']); + $this->assertNotEmpty($new_membership['body']['teamId']); + $this->assertCount(2, $new_membership['body']['roles']); + $dateValidator = new DatetimeValidator(); + $this->assertEquals(true, $dateValidator->isValid($new_membership['body']['joined'])); + $this->assertEquals(true, $new_membership['body']['confirm']); + } + + /** + * Delete the team + */ + $team_del_response = $this->client->call(Client::METHOD_DELETE, '/teams/' . $new_team['body']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(204, $team_del_response['headers']['status-code']); + + /** + * Check that the team memberships for each of the new users has been deleted + */ + for ($i = 0; $i < 5; $i++) { + $membership = $this->client->call(Client::METHOD_GET, '/users/' . $new_users[$i] . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $membership['headers']['status-code']); + $this->assertEquals(0, $membership['body']['total']); + } + } } From 0295c6ec1b2919385e516d4b86441fee5ff8e9df Mon Sep 17 00:00:00 2001 From: Safwan Parkar Date: Fri, 4 Aug 2023 23:17:41 +0400 Subject: [PATCH 30/36] improve test by removing user creation loop The CREATE TEAM MEMBERSHIP endpoint requires the email of the user to be added to the team. If the user does not exist in the project, a new user is created with the specified email and added to the team. The first version of the test creates 5 users, and then adds them to the newly created team. This process is more streamlined now, by using the CREATE TEAM MEMBERSHIPS behaviour to create a user on the go and create a membership for them immediately after. I also change the way I add user IDs to the array, by using the shorthand notation instead of the `array_push` function. --- tests/e2e/Services/Teams/TeamsBaseServer.php | 30 ++++---------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/tests/e2e/Services/Teams/TeamsBaseServer.php b/tests/e2e/Services/Teams/TeamsBaseServer.php index 569170f890..88bee1d62b 100644 --- a/tests/e2e/Services/Teams/TeamsBaseServer.php +++ b/tests/e2e/Services/Teams/TeamsBaseServer.php @@ -285,32 +285,9 @@ trait TeamsBaseServer public function testTeamDeleteUpdatesUserMembership() { + // Array to store the IDs of newly created users $new_users = []; - /** - * Create 5 new users and add their IDs to an array - */ - for ($i = 0; $i < 5; $i++) { - $user = $this->client->call(Client::METHOD_POST, '/users', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'userId' => ID::unique(), - 'email' => 'newuser' . $i . '@localhost.test', - 'password' => 'password', - 'name' => 'New User ' . $i, - ], false); - - $user_body = json_decode($user['body'], true); - - $this->assertEquals(201, $user['headers']['status-code']); - $this->assertEquals('newuser' . $i . '@localhost.test', $user_body['email']); - $this->assertEquals('New User ' . $i, $user_body['name']); - $this->assertEquals($user_body['status'], true); - - array_push($new_users, $user_body['$id']); - } - /** * Create a new team */ @@ -331,7 +308,8 @@ trait TeamsBaseServer $this->assertArrayHasKey('prefs', $new_team['body']); /** - * Create team memberships for each of the new users + * Use the Create Team Membership endpoint + * to create 5 new users and add them to the team immediately */ for ($i = 0; $i < 5; $i++) { $new_membership = $this->client->call(Client::METHOD_POST, '/teams/' . $new_team['body']['$id'] . '/memberships', array_merge([ @@ -354,6 +332,8 @@ trait TeamsBaseServer $dateValidator = new DatetimeValidator(); $this->assertEquals(true, $dateValidator->isValid($new_membership['body']['joined'])); $this->assertEquals(true, $new_membership['body']['confirm']); + + $new_users[] = $new_membership['body']['userId']; } /** From 4a9af13f167e938da417bf2a1eedf6cb39215e99 Mon Sep 17 00:00:00 2001 From: Safwan Parkar Date: Fri, 4 Aug 2023 23:41:29 +0400 Subject: [PATCH 31/36] run formatter and linter Run the composer format and lint commands, which I forgot to run before. --- tests/e2e/Services/Teams/TeamsBaseServer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Teams/TeamsBaseServer.php b/tests/e2e/Services/Teams/TeamsBaseServer.php index 88bee1d62b..5950824da3 100644 --- a/tests/e2e/Services/Teams/TeamsBaseServer.php +++ b/tests/e2e/Services/Teams/TeamsBaseServer.php @@ -308,7 +308,7 @@ trait TeamsBaseServer $this->assertArrayHasKey('prefs', $new_team['body']); /** - * Use the Create Team Membership endpoint + * Use the Create Team Membership endpoint * to create 5 new users and add them to the team immediately */ for ($i = 0; $i < 5; $i++) { From 2f3e14932a6cf3f169d8c43e62022d335d142774 Mon Sep 17 00:00:00 2001 From: Bishwajeet Parhi Date: Tue, 8 Aug 2023 11:34:02 +0530 Subject: [PATCH 32/36] fix: Tests --- app/controllers/api/databases.php | 1 - composer.lock | 73 ++++++++++++++++--------------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 00e4247cbe..6f5bb378b4 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -351,7 +351,6 @@ function updateAttribute( id: $key, required: $required, default: $default, - filters: [$filter], formatOptions: $options ?? null ); } diff --git a/composer.lock b/composer.lock index 5e6640b9fb..fe2ef22cdb 100644 --- a/composer.lock +++ b/composer.lock @@ -607,16 +607,16 @@ }, { "name": "guzzlehttp/promises", - "version": "2.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6" + "reference": "111166291a0f8130081195ac4556a5587d7f1b5d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/3a494dc7dc1d7d12e511890177ae2d0e6c107da6", - "reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6", + "url": "https://api.github.com/repos/guzzle/promises/zipball/111166291a0f8130081195ac4556a5587d7f1b5d", + "reference": "111166291a0f8130081195ac4556a5587d7f1b5d", "shasum": "" }, "require": { @@ -670,7 +670,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.0" + "source": "https://github.com/guzzle/promises/tree/2.0.1" }, "funding": [ { @@ -686,20 +686,20 @@ "type": "tidelift" } ], - "time": "2023-05-21T13:50:22+00:00" + "time": "2023-08-03T15:11:55+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.5.0", + "version": "2.6.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "b635f279edd83fc275f822a1188157ffea568ff6" + "reference": "8bd7c33a0734ae1c5d074360512beb716bef3f77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/b635f279edd83fc275f822a1188157ffea568ff6", - "reference": "b635f279edd83fc275f822a1188157ffea568ff6", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/8bd7c33a0734ae1c5d074360512beb716bef3f77", + "reference": "8bd7c33a0734ae1c5d074360512beb716bef3f77", "shasum": "" }, "require": { @@ -786,7 +786,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.5.0" + "source": "https://github.com/guzzle/psr7/tree/2.6.0" }, "funding": [ { @@ -802,7 +802,7 @@ "type": "tidelift" } ], - "time": "2023-04-17T16:11:26+00:00" + "time": "2023-08-03T15:06:02+00:00" }, { "name": "influxdb/influxdb-php", @@ -3791,16 +3791,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.22.1", + "version": "1.23.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "65c39594fbd8c67abfc68bb323f86447bab79cc0" + "reference": "846ae76eef31c6d7790fac9bc399ecee45160b26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/65c39594fbd8c67abfc68bb323f86447bab79cc0", - "reference": "65c39594fbd8c67abfc68bb323f86447bab79cc0", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/846ae76eef31c6d7790fac9bc399ecee45160b26", + "reference": "846ae76eef31c6d7790fac9bc399ecee45160b26", "shasum": "" }, "require": { @@ -3832,22 +3832,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.22.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.23.1" }, - "time": "2023-06-29T20:46:06+00:00" + "time": "2023-08-03T16:32:59+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.26", + "version": "9.2.27", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1" + "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", - "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b0a88255cb70d52653d80c890bd7f38740ea50d1", + "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1", "shasum": "" }, "require": { @@ -3903,7 +3903,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26" + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.27" }, "funding": [ { @@ -3911,7 +3912,7 @@ "type": "github" } ], - "time": "2023-03-06T12:58:08+00:00" + "time": "2023-07-26T13:44:30+00:00" }, { "name": "phpunit/php-file-iterator", @@ -4763,16 +4764,16 @@ }, { "name": "sebastian/global-state", - "version": "5.0.5", + "version": "5.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + "reference": "bde739e7565280bda77be70044ac1047bc007e34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", + "reference": "bde739e7565280bda77be70044ac1047bc007e34", "shasum": "" }, "require": { @@ -4815,7 +4816,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" }, "funding": [ { @@ -4823,7 +4824,7 @@ "type": "github" } ], - "time": "2022-02-14T08:28:10+00:00" + "time": "2023-08-02T09:26:13+00:00" }, { "name": "sebastian/lines-of-code", @@ -5586,16 +5587,16 @@ }, { "name": "twig/twig", - "version": "v3.6.1", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd" + "reference": "5cf942bbab3df42afa918caeba947f1b690af64b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd", - "reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/5cf942bbab3df42afa918caeba947f1b690af64b", + "reference": "5cf942bbab3df42afa918caeba947f1b690af64b", "shasum": "" }, "require": { @@ -5641,7 +5642,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.6.1" + "source": "https://github.com/twigphp/Twig/tree/v3.7.0" }, "funding": [ { @@ -5653,7 +5654,7 @@ "type": "tidelift" } ], - "time": "2023-06-08T12:52:13+00:00" + "time": "2023-07-26T07:16:09+00:00" } ], "aliases": [], From 0ca90cee6c2764b2b8ef1383b9dadef0991f7f19 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 14 Aug 2023 19:30:11 -0400 Subject: [PATCH 33/36] Fix test --- tests/e2e/Services/Teams/TeamsBaseServer.php | 77 -------------------- 1 file changed, 77 deletions(-) diff --git a/tests/e2e/Services/Teams/TeamsBaseServer.php b/tests/e2e/Services/Teams/TeamsBaseServer.php index 85f3616b60..648bb99f49 100644 --- a/tests/e2e/Services/Teams/TeamsBaseServer.php +++ b/tests/e2e/Services/Teams/TeamsBaseServer.php @@ -278,81 +278,4 @@ trait TeamsBaseServer $this->assertIsInt($response['body']['total']); $this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['$createdAt'])); } - - public function testTeamDeleteUpdatesUserMembership() - { - // Array to store the IDs of newly created users - $new_users = []; - - /** - * Create a new team - */ - $new_team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'teamId' => ID::unique(), - 'name' => 'New Team Test', - 'roles' => ['admin', 'editor'], - ]); - - $this->assertEquals(201, $new_team['headers']['status-code']); - $this->assertNotEmpty($new_team['body']['$id']); - $this->assertEquals('New Team Test', $new_team['body']['name']); - $this->assertGreaterThan(-1, $new_team['body']['total']); - $this->assertIsInt($new_team['body']['total']); - $this->assertArrayHasKey('prefs', $new_team['body']); - - /** - * Use the Create Team Membership endpoint - * to create 5 new users and add them to the team immediately - */ - for ($i = 0; $i < 5; $i++) { - $new_membership = $this->client->call(Client::METHOD_POST, '/teams/' . $new_team['body']['$id'] . '/memberships', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'email' => 'newuser' . $i . '@localhost.test', - 'name' => 'New User ' . $i, - 'roles' => ['admin', 'editor'], - 'url' => 'http://localhost:5000/join-us#title' - ]); - - $this->assertEquals(201, $new_membership['headers']['status-code']); - $this->assertNotEmpty($new_membership['body']['$id']); - $this->assertNotEmpty($new_membership['body']['userId']); - $this->assertEquals('New User ' . $i, $new_membership['body']['userName']); - $this->assertEquals('newuser' . $i . '@localhost.test', $new_membership['body']['userEmail']); - $this->assertNotEmpty($new_membership['body']['teamId']); - $this->assertCount(2, $new_membership['body']['roles']); - $dateValidator = new DatetimeValidator(); - $this->assertEquals(true, $dateValidator->isValid($new_membership['body']['joined'])); - $this->assertEquals(true, $new_membership['body']['confirm']); - - $new_users[] = $new_membership['body']['userId']; - } - - /** - * Delete the team - */ - $team_del_response = $this->client->call(Client::METHOD_DELETE, '/teams/' . $new_team['body']['$id'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(204, $team_del_response['headers']['status-code']); - - /** - * Check that the team memberships for each of the new users has been deleted - */ - for ($i = 0; $i < 5; $i++) { - $membership = $this->client->call(Client::METHOD_GET, '/users/' . $new_users[$i] . '/memberships', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $membership['headers']['status-code']); - $this->assertEquals(0, $membership['body']['total']); - } - } } From 8820d422be6524a13fe8e2de13ac2e98cab5022c Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 14 Aug 2023 20:45:05 -0400 Subject: [PATCH 34/36] Update app/controllers/api/databases.php --- app/controllers/api/databases.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 4e9a5ad518..50f3d7ea7a 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1750,7 +1750,6 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/strin ->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, From 07c985ebc3544e1a42643db7420c83bf5e12d496 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 17 Aug 2023 17:37:52 -0400 Subject: [PATCH 35/36] Fix missing params from redeploy event --- app/controllers/api/functions.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index b3375340f2..69db25ef49 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -1273,7 +1273,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 +1299,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(); }); From 8de0eefbaa6839738bdb9e4d65913ad4c6c06b74 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 17 Aug 2023 17:38:05 -0400 Subject: [PATCH 36/36] Lint --- tests/e2e/Services/Functions/FunctionsCustomServerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index df033e63c9..928bba969e 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -733,7 +733,7 @@ class FunctionsCustomServerTest extends Scope ], $this->getHeaders()), [ // Testing default value, should be 'async' => false ]); - + $this->assertEquals(201, $execution['headers']['status-code']); $this->assertEquals('completed', $execution['body']['status']); $this->assertEquals(200, $execution['body']['responseStatusCode']);