From 5a1ab3b059cea6dccf8a8f5ff9ddc40c939eace7 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 11 Aug 2021 21:05:19 -0400 Subject: [PATCH 01/12] Add enforce param to collections --- app/controllers/api/database.php | 41 ++++++++++++++++++++++++++++++++ composer.lock | 12 +++++----- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index dd275c9c2e..c02d8d1087 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -15,6 +15,7 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Query; use Utopia\Database\Adapter\MariaDB; +use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Key; use Utopia\Database\Validator\Permissions; use Utopia\Database\Validator\QueryValidator; @@ -1138,6 +1139,14 @@ App::post('/v1/database/collections/:collectionId/documents') throw new Exception('Collection not found', 404); } + // Check collection permissions when enforced + if ($collection->getAttribute('enforce') === 'collection') { + $validator = new Authorization($collection, 'write'); + if (!$validator->isValid($collection->getWrite())) { + throw new Exception('Unauthorized permissions', 401); + } + } + $data['$collection'] = $collection->getId(); // Adding this param to make API easier for developers $data['$id'] = $documentId == 'unique()' ? $dbForExternal->getId() : $documentId; $data['$read'] = (is_null($read) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $read ?? []; // By default set read permissions for user @@ -1195,6 +1204,14 @@ App::get('/v1/database/collections/:collectionId/documents') throw new Exception('Collection not found', 404); } + // Check collection permissions when enforced + if ($collection->getAttribute('enforce') === 'collection') { + $validator = new Authorization($collection, 'read'); + if (!$validator->isValid($collection->getRead())) { + throw new Exception('Unauthorized permissions', 401); + } + } + $queries = \array_map(function ($query) { return Query::parse($query); }, $queries); @@ -1247,6 +1264,14 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId') throw new Exception('Collection not found', 404); } + // Check collection permissions when enforced + if ($collection->getAttribute('enforce') === 'collection') { + $validator = new Authorization($collection, 'read'); + if (!$validator->isValid($collection->getRead())) { + throw new Exception('Unauthorized permissions', 401); + } + } + $document = $dbForExternal->getDocument($collectionId, $documentId); if ($document->isEmpty()) { @@ -1289,6 +1314,14 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') throw new Exception('Collection not found', 404); } + // Check collection permissions when enforced + if ($collection->getAttribute('enforce') === 'collection') { + $validator = new Authorization($collection, 'write'); + if (!$validator->isValid($collection->getWrite())) { + throw new Exception('Unauthorized permissions', 401); + } + } + $document = $dbForExternal->getDocument($collectionId, $documentId); if ($document->isEmpty()) { @@ -1361,6 +1394,14 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') throw new Exception('Collection not found', 404); } + // Check collection permissions when enforced + if ($collection->getAttribute('enforce') === 'collection') { + $validator = new Authorization($collection, 'write'); + if (!$validator->isValid($collection->getWrite())) { + throw new Exception('Unauthorized permissions', 401); + } + } + $document = $dbForExternal->getDocument($collectionId, $documentId); if ($document->isEmpty()) { diff --git a/composer.lock b/composer.lock index a5f2a3e062..8dac7bcc6d 100644 --- a/composer.lock +++ b/composer.lock @@ -355,16 +355,16 @@ }, { "name": "composer/package-versions-deprecated", - "version": "1.11.99.2", + "version": "1.11.99.3", "source": { "type": "git", "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c" + "reference": "fff576ac850c045158a250e7e27666e146e78d18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/c6522afe5540d5fc46675043d3ed5a45a740b27c", - "reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/fff576ac850c045158a250e7e27666e146e78d18", + "reference": "fff576ac850c045158a250e7e27666e146e78d18", "shasum": "" }, "require": { @@ -408,7 +408,7 @@ "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", "support": { "issues": "https://github.com/composer/package-versions-deprecated/issues", - "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.2" + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.3" }, "funding": [ { @@ -424,7 +424,7 @@ "type": "tidelift" } ], - "time": "2021-05-24T07:46:03+00:00" + "time": "2021-08-17T13:49:14+00:00" }, { "name": "dragonmantank/cron-expression", From 7db7e39f78f9ea30cab29bd357862750f34a0d45 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 11 Aug 2021 21:07:46 -0400 Subject: [PATCH 02/12] Test enforce collection permissions --- tests/e2e/Services/Database/DatabaseBase.php | 63 ++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index 3e109ca33b..f62e9d902d 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -1089,4 +1089,67 @@ trait DatabaseBase return $data; } + + public function testEnforceCollectionPermissions() + { + $user = 'user:' . $this->getUser()['$id']; + $collection = $this->client->call(Client::METHOD_POST, '/database/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => 'unique()', + 'name' => 'enforceCollectionPermissions', + 'enforce' => 'collection', + 'read' => [$user], + 'write' => [$user] + ]); + + var_dump($collection); + + $this->assertEquals($collection['headers']['status-code'], 201); + $this->assertEquals($collection['body']['name'], 'enforceCollectionPermissions'); + $this->assertEquals($collection['body']['enforce'], 'collection'); + + $collectionId = $collection['body']['$id']; + + $attribute = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'attributeId' => 'attribute', + 'size' => 64, + 'required' => true, + ]); + + $this->assertEquals($attribute['headers']['status-code'], 201); + $this->assertEquals($attribute['body']['$id'], 'attribute'); + + // wait for db to add attribute + sleep(3); + + $document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'unique()', + 'data' => [ + 'attribute' => 'documen1', + ], + 'read' => [], + 'write' => [], + ]); + + $this->assertEquals($document['headers']['status-code'], 201); + + $documents = $this->client->call(Client::METHOD_GET, '/database/collections/' . $collectionId . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + // var_dump($documents); + + $this->assertCount(1, $documents['body']['documents']); + } } \ No newline at end of file From 0e68b4a1e4d0307f78ef05e8818edcabcdd9360e Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 11 Aug 2021 21:25:51 -0400 Subject: [PATCH 03/12] Remove var dumps --- tests/e2e/Services/Database/DatabaseBase.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index f62e9d902d..3d1041272d 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -1105,8 +1105,6 @@ trait DatabaseBase 'write' => [$user] ]); - var_dump($collection); - $this->assertEquals($collection['headers']['status-code'], 201); $this->assertEquals($collection['body']['name'], 'enforceCollectionPermissions'); $this->assertEquals($collection['body']['enforce'], 'collection'); @@ -1148,8 +1146,6 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - // var_dump($documents); - $this->assertCount(1, $documents['body']['documents']); } } \ No newline at end of file From 2b057c06176fbd1ea43fa71657909ba0bbe088c8 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 11 Aug 2021 21:26:31 -0400 Subject: [PATCH 04/12] Skip authorization on document routes if collection permissions are met --- app/controllers/api/database.php | 49 ++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index c02d8d1087..9b62bc85b7 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -1153,7 +1153,14 @@ App::post('/v1/database/collections/:collectionId/documents') $data['$write'] = (is_null($write) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $write ?? []; // By default set write permissions for user try { - $document = $dbForExternal->createDocument($collectionId, new Document($data)); + if ($collection->getAttribute('enforce') === 'collection') { + /** @var Document $document */ + $document = Authorization::skip(function() use ($dbForExternal, $collectionId, $data) { + return $dbForExternal->createDocument($collectionId, new Document($data)); + }); + } else { + $document = $dbForExternal->createDocument($collectionId, new Document($data)); + } } catch (StructureException $exception) { throw new Exception($exception->getMessage(), 400); @@ -1227,13 +1234,22 @@ App::get('/v1/database/collections/:collectionId/documents') $afterDocument = $dbForExternal->getDocument($collectionId, $after); if ($afterDocument->isEmpty()) { - throw new Exception("Document '{$after}' for the 'after' value not found.", 400); + throw new Exception("Document \'{$after}\' for the \'after\' value not found.", 400); } } + if ($collection->getAttribute('enforce') === 'collection') { + /** @var Document[] $documents */ + $documents = Authorization::skip(function() use ($dbForExternal, $collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes, $afterDocument) { + return $dbForExternal->find($collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes, $afterDocument ?? null); + }); + } else { + $documents = $dbForExternal->find($collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes, $afterDocument ?? null); + } + $response->dynamic(new Document([ 'sum' => $dbForExternal->count($collectionId, $queries, APP_LIMIT_COUNT), - 'documents' => $dbForExternal->find($collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes, $afterDocument ?? null), + 'documents' => $documents, ]), Response::MODEL_DOCUMENT_LIST); }); @@ -1272,7 +1288,14 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId') } } - $document = $dbForExternal->getDocument($collectionId, $documentId); + if ($collection->getAttribute('enforce') === 'collection') { + /** @var Document $document */ + $document = Authorization::skip(function() use ($dbForExternal, $collectionId, $documentId) { + return $dbForExternal->getDocument($collectionId, $documentId); + }); + } else { + $document = $dbForExternal->getDocument($collectionId, $documentId); + } if ($document->isEmpty()) { throw new Exception('No document found', 404); @@ -1346,7 +1369,14 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') $data['$write'] = (is_null($write)) ? ($document->getWrite() ?? []) : $write; // By default inherit write permissions try { - $document = $dbForExternal->updateDocument($collection->getId(), $document->getId(), new Document($data)); + if ($collection->getAttribute('enforce') === 'collection') { + /** @var Document $document */ + $document = Authorization::skip(function() use ($dbForExternal, $collection, $document, $data) { + return $dbForExternal->updateDocument($collection->getId(), $document->getId(), new Document($data)); + }); + } else { + $document = $dbForExternal->updateDocument($collection->getId(), $document->getId(), new Document($data)); + } } catch (AuthorizationException $exception) { throw new Exception('Unauthorized permissions', 401); @@ -1402,7 +1432,14 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') } } - $document = $dbForExternal->getDocument($collectionId, $documentId); + if ($collection->getAttribute('enforce') === 'collection') { + /** @var Document $document */ + $document = Authorization::skip(function() use ($dbForExternal, $collectionId, $documentId) { + return $dbForExternal->getDocument($collectionId, $documentId); + }); + } else { + $document = $dbForExternal->getDocument($collectionId, $documentId); + } if ($document->isEmpty()) { throw new Exception('No document found', 404); From 95f505b61657ff66a177c20b8fed55f3aa2ecfbe Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 13 Aug 2021 16:20:54 -0400 Subject: [PATCH 05/12] Add tests for collection permissions --- tests/e2e/Services/Database/DatabaseBase.php | 106 +++++++++++++++++-- 1 file changed, 98 insertions(+), 8 deletions(-) diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index 3d1041272d..43d7f51b0a 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -654,6 +654,8 @@ trait DatabaseBase 'required' => false, ]); + sleep(2); + $ip = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/ip', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -663,6 +665,8 @@ trait DatabaseBase 'required' => false, ]); + sleep(2); + $url = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/url', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -673,6 +677,8 @@ trait DatabaseBase 'required' => false, ]); + sleep(2); + $range = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/integer', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -684,6 +690,8 @@ trait DatabaseBase 'max' => 10, ]); + sleep(2); + // TODO@kodumbeats min and max are rounded in error message $floatRange = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/float', array_merge([ 'content-type' => 'application/json', @@ -696,6 +704,8 @@ trait DatabaseBase 'max' => 1.4, ]); + sleep(2); + // TODO@kodumbeats float validator rejects 0.0 and 1.0 as floats // $probability = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/float', array_merge([ // 'content-type' => 'application/json', @@ -718,6 +728,8 @@ trait DatabaseBase 'max' => 10, ]); + sleep(2); + $lowerBound = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/integer', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1111,6 +1123,8 @@ trait DatabaseBase $collectionId = $collection['body']['$id']; + sleep(2); + $attribute = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/string', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1121,31 +1135,107 @@ trait DatabaseBase 'required' => true, ]); - $this->assertEquals($attribute['headers']['status-code'], 201); - $this->assertEquals($attribute['body']['$id'], 'attribute'); + $this->assertEquals(201, $attribute['headers']['status-code'], 201); + $this->assertEquals('attribute', $attribute['body']['$id']); // wait for db to add attribute - sleep(3); + sleep(2); - $document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/documents', array_merge([ + $index = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/indexes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'indexId' => 'key_attribute', + 'type' => 'key', + 'attributes' => [$attribute['body']['$id']], + ]); + + $this->assertEquals(201, $index['headers']['status-code']); + $this->assertEquals('key_attribute', $index['body']['$id']); + + // wait for db to add attribute + sleep(2); + + $document1 = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/documents', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'documentId' => 'unique()', 'data' => [ - 'attribute' => 'documen1', + 'attribute' => 'one', ], - 'read' => [], - 'write' => [], + 'read' => [$user], + 'write' => [$user], ]); - $this->assertEquals($document['headers']['status-code'], 201); + $this->assertEquals(201, $document1['headers']['status-code']); $documents = $this->client->call(Client::METHOD_GET, '/database/collections/' . $collectionId . '/documents', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); + $this->assertEquals(1, $documents['body']['sum']); $this->assertCount(1, $documents['body']['documents']); + + /* + * Test for Failure + */ + + // Remove write permission + $collection = $this->client->call(Client::METHOD_PUT, '/database/collections/' . $collectionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'name' => 'enforceCollectionPermissions', + 'enforce' => 'collection', + 'read' => [$user], + 'write' => [] + ]); + + $this->assertEquals(200, $collection['headers']['status-code']); + + $badDocument = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'unique()', + 'data' => [ + 'attribute' => 'bad', + ], + 'read' => [$user], + 'write' => [$user], + ]); + + if($this->getSide() == 'client') { + $this->assertEquals(401, $badDocument['headers']['status-code']); + } + + if($this->getSide() == 'server') { + $this->assertEquals(201, $badDocument['headers']['status-code']); + } + + // Remove read permission + $collection = $this->client->call(Client::METHOD_PUT, '/database/collections/' . $collectionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'name' => 'enforceCollectionPermissions', + 'enforce' => 'collection', + 'read' => [], + 'write' => [] + ]); + + $this->assertEquals(200, $collection['headers']['status-code']); + + $documents = $this->client->call(Client::METHOD_GET, '/database/collections/' . $collectionId . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ])); + + $this->assertEquals(401, $documents['headers']['status-code']); } } \ No newline at end of file From a33faab21393ceb2c3ab6cafb8fc5a931c1bb2a3 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 27 Aug 2021 21:45:15 -0400 Subject: [PATCH 06/12] Enforce attribute refactored to permission --- app/controllers/api/database.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 9b62bc85b7..14feb5df37 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -1140,7 +1140,7 @@ App::post('/v1/database/collections/:collectionId/documents') } // Check collection permissions when enforced - if ($collection->getAttribute('enforce') === 'collection') { + if ($collection->getAttribute('permission') === 'collection') { $validator = new Authorization($collection, 'write'); if (!$validator->isValid($collection->getWrite())) { throw new Exception('Unauthorized permissions', 401); @@ -1153,7 +1153,7 @@ App::post('/v1/database/collections/:collectionId/documents') $data['$write'] = (is_null($write) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $write ?? []; // By default set write permissions for user try { - if ($collection->getAttribute('enforce') === 'collection') { + if ($collection->getAttribute('permission') === 'collection') { /** @var Document $document */ $document = Authorization::skip(function() use ($dbForExternal, $collectionId, $data) { return $dbForExternal->createDocument($collectionId, new Document($data)); @@ -1212,7 +1212,7 @@ App::get('/v1/database/collections/:collectionId/documents') } // Check collection permissions when enforced - if ($collection->getAttribute('enforce') === 'collection') { + if ($collection->getAttribute('permission') === 'collection') { $validator = new Authorization($collection, 'read'); if (!$validator->isValid($collection->getRead())) { throw new Exception('Unauthorized permissions', 401); @@ -1238,7 +1238,7 @@ App::get('/v1/database/collections/:collectionId/documents') } } - if ($collection->getAttribute('enforce') === 'collection') { + if ($collection->getAttribute('permission') === 'collection') { /** @var Document[] $documents */ $documents = Authorization::skip(function() use ($dbForExternal, $collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes, $afterDocument) { return $dbForExternal->find($collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes, $afterDocument ?? null); @@ -1281,14 +1281,14 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId') } // Check collection permissions when enforced - if ($collection->getAttribute('enforce') === 'collection') { + if ($collection->getAttribute('permission') === 'collection') { $validator = new Authorization($collection, 'read'); if (!$validator->isValid($collection->getRead())) { throw new Exception('Unauthorized permissions', 401); } } - if ($collection->getAttribute('enforce') === 'collection') { + if ($collection->getAttribute('permission') === 'collection') { /** @var Document $document */ $document = Authorization::skip(function() use ($dbForExternal, $collectionId, $documentId) { return $dbForExternal->getDocument($collectionId, $documentId); @@ -1338,7 +1338,7 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') } // Check collection permissions when enforced - if ($collection->getAttribute('enforce') === 'collection') { + if ($collection->getAttribute('permission') === 'collection') { $validator = new Authorization($collection, 'write'); if (!$validator->isValid($collection->getWrite())) { throw new Exception('Unauthorized permissions', 401); @@ -1369,7 +1369,7 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') $data['$write'] = (is_null($write)) ? ($document->getWrite() ?? []) : $write; // By default inherit write permissions try { - if ($collection->getAttribute('enforce') === 'collection') { + if ($collection->getAttribute('permission') === 'collection') { /** @var Document $document */ $document = Authorization::skip(function() use ($dbForExternal, $collection, $document, $data) { return $dbForExternal->updateDocument($collection->getId(), $document->getId(), new Document($data)); @@ -1425,14 +1425,14 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') } // Check collection permissions when enforced - if ($collection->getAttribute('enforce') === 'collection') { + if ($collection->getAttribute('permission') === 'collection') { $validator = new Authorization($collection, 'write'); if (!$validator->isValid($collection->getWrite())) { throw new Exception('Unauthorized permissions', 401); } } - if ($collection->getAttribute('enforce') === 'collection') { + if ($collection->getAttribute('permission') === 'collection') { /** @var Document $document */ $document = Authorization::skip(function() use ($dbForExternal, $collectionId, $documentId) { return $dbForExternal->getDocument($collectionId, $documentId); From 4af81db28072263f651a14a6aa7d7f0daace9f00 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 27 Aug 2021 21:45:42 -0400 Subject: [PATCH 07/12] Authorization validator only accepts one argument --- app/controllers/api/database.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 14feb5df37..517651c3e3 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -1141,7 +1141,7 @@ App::post('/v1/database/collections/:collectionId/documents') // Check collection permissions when enforced if ($collection->getAttribute('permission') === 'collection') { - $validator = new Authorization($collection, 'write'); + $validator = new Authorization('write'); if (!$validator->isValid($collection->getWrite())) { throw new Exception('Unauthorized permissions', 401); } @@ -1213,7 +1213,7 @@ App::get('/v1/database/collections/:collectionId/documents') // Check collection permissions when enforced if ($collection->getAttribute('permission') === 'collection') { - $validator = new Authorization($collection, 'read'); + $validator = new Authorization('read'); if (!$validator->isValid($collection->getRead())) { throw new Exception('Unauthorized permissions', 401); } @@ -1234,7 +1234,7 @@ App::get('/v1/database/collections/:collectionId/documents') $afterDocument = $dbForExternal->getDocument($collectionId, $after); if ($afterDocument->isEmpty()) { - throw new Exception("Document \'{$after}\' for the \'after\' value not found.", 400); + throw new Exception("Document '{$after}' for the 'after' value not found.", 400); } } @@ -1282,7 +1282,7 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId') // Check collection permissions when enforced if ($collection->getAttribute('permission') === 'collection') { - $validator = new Authorization($collection, 'read'); + $validator = new Authorization('read'); if (!$validator->isValid($collection->getRead())) { throw new Exception('Unauthorized permissions', 401); } @@ -1339,7 +1339,7 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') // Check collection permissions when enforced if ($collection->getAttribute('permission') === 'collection') { - $validator = new Authorization($collection, 'write'); + $validator = new Authorization('write'); if (!$validator->isValid($collection->getWrite())) { throw new Exception('Unauthorized permissions', 401); } @@ -1426,7 +1426,7 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') // Check collection permissions when enforced if ($collection->getAttribute('permission') === 'collection') { - $validator = new Authorization($collection, 'write'); + $validator = new Authorization('write'); if (!$validator->isValid($collection->getWrite())) { throw new Exception('Unauthorized permissions', 401); } From e9541a9269af49c63d01e454b41dce8e4d8f7282 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 27 Aug 2021 21:46:33 -0400 Subject: [PATCH 08/12] Fix tests --- app/controllers/api/database.php | 1 + tests/e2e/Services/Database/DatabaseBase.php | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 517651c3e3..4519da1be4 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -1230,6 +1230,7 @@ App::get('/v1/database/collections/:collectionId/documents') throw new Exception($validator->getDescription(), 400); } + $afterDocument = null; if (!empty($after)) { $afterDocument = $dbForExternal->getDocument($collectionId, $after); diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index 43d7f51b0a..e260f1477a 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -1112,14 +1112,14 @@ trait DatabaseBase ]), [ 'collectionId' => 'unique()', 'name' => 'enforceCollectionPermissions', - 'enforce' => 'collection', + 'permission' => 'collection', 'read' => [$user], 'write' => [$user] ]); $this->assertEquals($collection['headers']['status-code'], 201); $this->assertEquals($collection['body']['name'], 'enforceCollectionPermissions'); - $this->assertEquals($collection['body']['enforce'], 'collection'); + $this->assertEquals($collection['body']['permission'], 'collection'); $collectionId = $collection['body']['$id']; @@ -1136,7 +1136,7 @@ trait DatabaseBase ]); $this->assertEquals(201, $attribute['headers']['status-code'], 201); - $this->assertEquals('attribute', $attribute['body']['$id']); + $this->assertEquals('attribute', $attribute['body']['key']); // wait for db to add attribute sleep(2); @@ -1148,11 +1148,11 @@ trait DatabaseBase ]), [ 'indexId' => 'key_attribute', 'type' => 'key', - 'attributes' => [$attribute['body']['$id']], + 'attributes' => [$attribute['body']['key']], ]); $this->assertEquals(201, $index['headers']['status-code']); - $this->assertEquals('key_attribute', $index['body']['$id']); + $this->assertEquals('key_attribute', $index['body']['key']); // wait for db to add attribute sleep(2); @@ -1190,7 +1190,7 @@ trait DatabaseBase 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ 'name' => 'enforceCollectionPermissions', - 'enforce' => 'collection', + 'permission' => 'collection', 'read' => [$user], 'write' => [] ]); @@ -1224,7 +1224,7 @@ trait DatabaseBase 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ 'name' => 'enforceCollectionPermissions', - 'enforce' => 'collection', + 'permission' => 'collection', 'read' => [], 'write' => [] ]); @@ -1236,6 +1236,6 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], ])); - $this->assertEquals(401, $documents['headers']['status-code']); + $this->assertEquals(404, $documents['headers']['status-code']); } } \ No newline at end of file From 6b7059c034a8096e6067e71ec5fc10f038bf094e Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 1 Sep 2021 10:57:20 -0400 Subject: [PATCH 09/12] Fix missing lines from conflict resolution --- tests/e2e/Services/Database/DatabaseBase.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index ba0da3d2e4..96339b1fd2 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -1159,6 +1159,9 @@ trait DatabaseBase sleep(2); $document1 = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ 'documentId' => 'unique()', 'data' => [ 'attribute' => 'one', From e1c7e4908b60a93daf8d6f9e8046d92ef640968b Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 9 Sep 2021 12:52:56 -0400 Subject: [PATCH 10/12] Remove unneeded var --- app/controllers/api/database.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 1d2b7c9977..633e6f2325 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -1230,7 +1230,6 @@ App::get('/v1/database/collections/:collectionId/documents') throw new Exception($validator->getDescription(), 400); } - $afterDocument = null; if (!empty($after)) { $afterDocument = $dbForExternal->getDocument($collectionId, $after); From 0960a54169364e4f219f6d1db57716c612996405 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 5 Oct 2021 10:10:40 -0400 Subject: [PATCH 11/12] Clean up unneeded sleeps in db tests --- tests/e2e/Services/Database/DatabaseBase.php | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index 96339b1fd2..4111aca118 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -654,8 +654,6 @@ trait DatabaseBase 'required' => false, ]); - sleep(2); - $ip = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/ip', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -665,8 +663,6 @@ trait DatabaseBase 'required' => false, ]); - sleep(2); - $url = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/url', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -677,8 +673,6 @@ trait DatabaseBase 'required' => false, ]); - sleep(2); - $range = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/integer', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -690,8 +684,6 @@ trait DatabaseBase 'max' => 10, ]); - sleep(2); - // TODO@kodumbeats min and max are rounded in error message $floatRange = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/float', array_merge([ 'content-type' => 'application/json', @@ -704,8 +696,6 @@ trait DatabaseBase 'max' => 1.4, ]); - sleep(2); - // TODO@kodumbeats float validator rejects 0.0 and 1.0 as floats // $probability = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/float', array_merge([ // 'content-type' => 'application/json', @@ -728,8 +718,6 @@ trait DatabaseBase 'max' => 10, ]); - sleep(2); - $lowerBound = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/integer', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -766,7 +754,7 @@ trait DatabaseBase // $this->assertEquals('Minimum value must be lesser than maximum value', $invalidRange['body']['message']); // wait for worker to add attributes - sleep(2); + sleep(3); $collection = $this->client->call(Client::METHOD_GET, '/database/collections/' . $collectionId, array_merge([ 'content-type' => 'application/json', From cdaec34c6a5427c0926dfd6b02dceb6f040bb541 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 5 Oct 2021 16:36:34 +0200 Subject: [PATCH 12/12] fix usage worker --- app/tasks/usage.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/tasks/usage.php b/app/tasks/usage.php index 4ff5e8cad9..701037fcca 100644 --- a/app/tasks/usage.php +++ b/app/tasks/usage.php @@ -342,7 +342,7 @@ $cli do { // list projects try { $attempts++; - $projects = $dbForConsole->find('projects', [], 100, orderAfter:$latestProject); + $projects = $dbForConsole->find('projects', [], 100, cursor:$latestProject); break; // leave the do-while if successful } catch (\Exception $e) { Console::warning("Console DB not ready yet. Retrying ({$attempts})..."); @@ -472,7 +472,7 @@ $cli do { // Loop over all the parent collection document for each sub collection $dbForProject->setNamespace("project_{$projectId}_{$options['namespace']}"); - $parents = $dbForProject->find($collection, [], 100, orderAfter:$latestParent); // Get all the parents for the sub collections for example for documents, this will get all the collections + $parents = $dbForProject->find($collection, [], 100, cursor:$latestParent); // Get all the parents for the sub collections for example for documents, this will get all the collections if (empty($parents)) { continue;