From 166c906d51ec10110eebc00a90e1916128816e7a Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 16 Dec 2021 19:12:06 +0100 Subject: [PATCH 1/2] fix: collection level permissions --- app/controllers/api/database.php | 60 ++++-- app/controllers/shared/api.php | 9 +- src/Appwrite/Messaging/Adapter/Realtime.php | 15 +- tests/e2e/Services/Database/DatabaseBase.php | 46 ++++- .../Database/DatabasePermissionsTeamTest.php | 2 +- .../Realtime/RealtimeCustomClientTest.php | 186 +++++++++++++++--- tests/unit/Messaging/MessagingTest.php | 49 ++++- 7 files changed, 311 insertions(+), 56 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 43b98dd5f3..b18fdb1788 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -1589,13 +1589,15 @@ App::post('/v1/database/collections/:collectionId/documents') ->inject('user') ->inject('audits') ->inject('usage') - ->action(function ($documentId, $collectionId, $data, $read, $write, $response, $dbForInternal, $dbForExternal, $user, $audits, $usage) { + ->inject('events') + ->action(function ($documentId, $collectionId, $data, $read, $write, $response, $dbForInternal, $dbForExternal, $user, $audits, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Database\Database $dbForExternal */ /** @var Utopia\Database\Document $user */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array @@ -1607,7 +1609,10 @@ App::post('/v1/database/collections/:collectionId/documents') throw new Exception('$id is not allowed for creating new documents, try update instead', 400); } - $collection = $dbForInternal->getDocument('collections', $collectionId); + /** + * Skip Authorization to get the collection. Needed in case of empty permissions for document level permissions. + */ + $collection = Authorization::skip(fn() => $dbForInternal->getDocument('collections', $collectionId)); if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); @@ -1659,6 +1664,8 @@ App::post('/v1/database/collections/:collectionId/documents') throw new Exception('Document already exists', 409); } + $events->setParam('collection', $collection->getArrayCopy()); + $usage ->setParam('database.documents.create', 1) ->setParam('collectionId', $collectionId) @@ -1703,7 +1710,10 @@ App::get('/v1/database/collections/:collectionId/documents') /** @var Utopia\Database\Database $dbForExternal */ /** @var Appwrite\Stats\Stats $usage */ - $collection = $dbForInternal->getDocument('collections', $collectionId); + /** + * Skip Authorization to get the collection. Needed in case of empty permissions for document level permissions. + */ + $collection = Authorization::skip(fn() => $dbForInternal->getDocument('collections', $collectionId)); if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); @@ -1739,11 +1749,11 @@ App::get('/v1/database/collections/:collectionId/documents') if ($collection->getAttribute('permission') === 'collection') { /** @var Document[] $documents */ - $documents = Authorization::skip(function() use ($dbForExternal, $collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes, $cursorDocument, $cursorDirection) { - return $dbForExternal->find($collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes, $cursorDocument ?? null, $cursorDirection); - }); + $documents = Authorization::skip(fn() => $dbForExternal->find($collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes, $cursorDocument ?? null, $cursorDirection)); + $sum = Authorization::skip(fn() => $dbForExternal->count($collectionId, $queries, APP_LIMIT_COUNT)); } else { $documents = $dbForExternal->find($collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes, $cursorDocument ?? null, $cursorDirection); + $sum = $dbForExternal->count($collectionId, $queries, APP_LIMIT_COUNT); } $usage @@ -1752,7 +1762,7 @@ App::get('/v1/database/collections/:collectionId/documents') ; $response->dynamic(new Document([ - 'sum' => $dbForExternal->count($collectionId, $queries, APP_LIMIT_COUNT), + 'sum' => $sum, 'documents' => $documents, ]), Response::MODEL_DOCUMENT_LIST); }); @@ -1779,7 +1789,10 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId') /** @var Utopia\Database\Database $$dbForInternal */ /** @var Utopia\Database\Database $dbForExternal */ - $collection = $dbForInternal->getDocument('collections', $collectionId); + /** + * Skip Authorization to get the collection. Needed in case of empty permissions for document level permissions. + */ + $collection = Authorization::skip(fn() => $dbForInternal->getDocument('collections', $collectionId)); if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); @@ -1930,14 +1943,19 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') ->inject('dbForExternal') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $documentId, $data, $read, $write, $response, $dbForInternal, $dbForExternal, $audits, $usage) { + ->inject('events') + ->action(function ($collectionId, $documentId, $data, $read, $write, $response, $dbForInternal, $dbForExternal, $audits, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Database\Database $dbForExternal */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ - $collection = $dbForInternal->getDocument('collections', $collectionId); + /** + * Skip Authorization to get the collection. Needed in case of empty permissions for document level permissions. + */ + $collection = Authorization::skip(fn() => $dbForInternal->getDocument('collections', $collectionId)); if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); @@ -1949,9 +1967,12 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') if (!$validator->isValid($collection->getWrite())) { throw new Exception('Unauthorized permissions', 401); } + + $document = Authorization::skip(fn() => $dbForExternal->getDocument($collectionId, $documentId)); + } else { + $document = $dbForExternal->getDocument($collectionId, $documentId); } - $document = $dbForExternal->getDocument($collectionId, $documentId); if ($document->isEmpty()) { throw new Exception('Document not found', 404); @@ -2009,7 +2030,9 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') catch (StructureException $exception) { throw new Exception($exception->getMessage(), 400); } - + + $events->setParam('collection', $collection->getArrayCopy()); + $usage ->setParam('database.documents.update', 1) ->setParam('collectionId', $collectionId) @@ -2050,7 +2073,10 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ - $collection = $dbForInternal->getDocument('collections', $collectionId); + /** + * Skip Authorization to get the collection. Needed in case of empty permissions for document level permissions. + */ + $collection = Authorization::skip(fn() => $dbForInternal->getDocument('collections', $collectionId)); if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); @@ -2077,7 +2103,12 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') throw new Exception('No document found', 404); } - $dbForExternal->deleteDocument($collectionId, $documentId); + if ($collection->getAttribute('permission') === 'collection') { + Authorization::skip(fn() => $dbForExternal->deleteDocument($collectionId, $documentId)); + } else { + $dbForExternal->deleteDocument($collectionId, $documentId); + } + $dbForExternal->deleteCachedDocument($collectionId, $documentId); $usage @@ -2087,6 +2118,7 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') $events ->setParam('eventData', $response->output($document, Response::MODEL_DOCUMENT)) + ->setParam('collection', $collection->getArrayCopy()); ; $audits diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index b1087f28e6..a6ad8a276e 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -207,7 +207,14 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits if ($project->getId() !== 'console') { $payload = new Document($response->getPayload()); - $target = Realtime::fromPayload($events->getParam('event'), $payload, $project); + $collection = new Document($events->getParam('collection')); + + $target = Realtime::fromPayload( + event: $events->getParam('event'), + payload: $payload, + project: $project, + collection: $collection + ); Realtime::send( $target['projectId'] ?? $project->getId(), diff --git a/src/Appwrite/Messaging/Adapter/Realtime.php b/src/Appwrite/Messaging/Adapter/Realtime.php index 8fb3ad4aa5..59708ec084 100644 --- a/src/Appwrite/Messaging/Adapter/Realtime.php +++ b/src/Appwrite/Messaging/Adapter/Realtime.php @@ -240,7 +240,7 @@ class Realtime extends Adapter * @param Document|null $project * @return array */ - public static function fromPayload(string $event, Document $payload, Document $project = null): array + public static function fromPayload(string $event, Document $payload, Document $project = null, Document $collection = null): array { $channels = []; $roles = []; @@ -275,12 +275,6 @@ class Realtime extends Adapter $channels[] = 'teams.' . $payload->getId(); $roles = ['team:' . $payload->getId()]; - break; - case strpos($event, 'database.collections.') === 0: - $channels[] = 'collections'; - $channels[] = 'collections.' . $payload->getId(); - $roles = $payload->getRead(); - break; case strpos($event, 'database.attributes.') === 0: case strpos($event, 'database.indexes.') === 0: @@ -290,10 +284,15 @@ class Realtime extends Adapter break; case strpos($event, 'database.documents.') === 0: + if ($collection->isEmpty()) { + throw new \Exception('Collection need to be passed to to Realtime for Document events in the Database.'); + } + $channels[] = 'documents'; $channels[] = 'collections.' . $payload->getAttribute('$collection') . '.documents'; $channels[] = 'documents.' . $payload->getId(); - $roles = $payload->getRead(); + + $roles = ($collection->getAttribute('permission') === 'collection') ? $collection->getRead() : $payload->getRead(); break; case strpos($event, 'storage.') === 0: diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index 2f852d0f5e..1d52cfdc49 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -19,8 +19,8 @@ trait DatabaseBase ]), [ 'collectionId' => 'unique()', 'name' => 'Movies', - 'read' => ['role:all'], - 'write' => ['role:all'], + 'read' => [], + 'write' => [], 'permission' => 'document', ]); @@ -113,8 +113,8 @@ trait DatabaseBase ]), [ 'collectionId' => 'unique()', 'name' => 'Response Models', - 'read' => ['role:all'], - 'write' => ['role:all'], + 'read' => [], + 'write' => [], 'permission' => 'document', ]); @@ -1229,8 +1229,8 @@ trait DatabaseBase ]), [ 'collectionId' => 'unique()', 'name' => 'invalidDocumentStructure', - 'read' => ['role:all'], - 'write' => ['role:all'], + 'read' => [], + 'write' => [], 'permission' => 'document', ]); @@ -1829,13 +1829,41 @@ trait DatabaseBase $this->assertEquals(201, $document1['headers']['status-code']); + $document2 = $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', + ], + 'read' => [], + 'write' => [$user], + ]); + + $this->assertEquals(201, $document2['headers']['status-code']); + + $document3 = $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', + ], + 'read' => [], + 'write' => [], + ]); + + $this->assertEquals(201, $document3['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']); + $this->assertEquals(3, $documents['body']['sum']); + $this->assertCount(3, $documents['body']['documents']); /* * Test for Failure @@ -1894,7 +1922,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], ])); - $this->assertEquals(404, $documents['headers']['status-code']); + $this->assertEquals(401, $documents['headers']['status-code']); } /** diff --git a/tests/e2e/Services/Database/DatabasePermissionsTeamTest.php b/tests/e2e/Services/Database/DatabasePermissionsTeamTest.php index 218c9cd78a..29534d999a 100644 --- a/tests/e2e/Services/Database/DatabasePermissionsTeamTest.php +++ b/tests/e2e/Services/Database/DatabasePermissionsTeamTest.php @@ -160,7 +160,7 @@ class DatabasePermissionsTeamTest extends Scope if ($success) { $this->assertCount(1, $documents['body']['documents']); } else { - $this->assertEquals(404, $documents['headers']['status-code']); + $this->assertEquals(401, $documents['headers']['status-code']); } } diff --git a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php index ed10aefa59..0dd6ccaeb3 100644 --- a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php @@ -77,9 +77,7 @@ class RealtimeCustomClientTest extends Scope 'files', 'files.1', 'collections', - 'collections.1', 'collections.1.documents', - 'collections.2', 'collections.2.documents', 'documents', 'documents.1', @@ -93,15 +91,13 @@ class RealtimeCustomClientTest extends Scope $this->assertEquals('connected', $response['type']); $this->assertNotEmpty($response['data']); $this->assertNotEmpty($response['data']['user']); - $this->assertCount(12, $response['data']['channels']); + $this->assertCount(10, $response['data']['channels']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); $this->assertContains('files', $response['data']['channels']); $this->assertContains('files.1', $response['data']['channels']); $this->assertContains('collections', $response['data']['channels']); - $this->assertContains('collections.1', $response['data']['channels']); $this->assertContains('collections.1.documents', $response['data']['channels']); - $this->assertContains('collections.2', $response['data']['channels']); $this->assertContains('collections.2.documents', $response['data']['channels']); $this->assertContains('documents', $response['data']['channels']); $this->assertContains('documents.1', $response['data']['channels']); @@ -561,24 +557,11 @@ class RealtimeCustomClientTest extends Scope ]), [ 'collectionId' => 'unique()', 'name' => 'Actors', - 'read' => ['role:all'], - 'write' => ['role:all'], - 'permission' => 'collection' + 'read' => [], + 'write' => [], + 'permission' => 'document' ]); - $response = json_decode($client->receive(), true); - - $this->assertArrayHasKey('type', $response); - $this->assertArrayHasKey('data', $response); - $this->assertEquals('event', $response['type']); - $this->assertNotEmpty($response['data']); - $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(2, $response['data']['channels']); - $this->assertContains('collections', $response['data']['channels']); - $this->assertContains('collections.' . $actors['body']['$id'], $response['data']['channels']); - $this->assertEquals('database.collections.create', $response['data']['event']); - $this->assertNotEmpty($response['data']['payload']); - $data = ['actorsId' => $actors['body']['$id']]; $name = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/attributes/string', array_merge([ @@ -662,7 +645,6 @@ class RealtimeCustomClientTest extends Scope $this->assertEquals($response['data']['payload']['name'], 'Chris Evans 2'); - /** * Test Document Delete */ @@ -703,6 +685,166 @@ class RealtimeCustomClientTest extends Scope $client->close(); } + public function testChannelDatabaseCollectionPermissions() + { + $user = $this->getUser(); + $session = $user['session'] ?? ''; + $projectId = $this->getProject()['$id']; + + $client = $this->getWebsocket(['documents', 'collections'], [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_'.$projectId.'=' . $session + ]); + + $response = json_decode($client->receive(), true); + + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('connected', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertContains('documents', $response['data']['channels']); + $this->assertContains('collections', $response['data']['channels']); + $this->assertNotEmpty($response['data']['user']); + $this->assertEquals($user['$id'], $response['data']['user']['$id']); + + /** + * Test Collection Create + */ + $actors = $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' => 'Actors', + 'read' => ['role:all'], + 'write' => ['role:all'], + 'permission' => 'collection' + ]); + + $data = ['actorsId' => $actors['body']['$id']]; + + $name = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'attributeId' => 'name', + 'size' => 256, + 'required' => true, + ]); + + $this->assertEquals($name['headers']['status-code'], 201); + $this->assertEquals($name['body']['key'], 'name'); + $this->assertEquals($name['body']['type'], 'string'); + $this->assertEquals($name['body']['size'], 256); + $this->assertEquals($name['body']['required'], true); + + sleep(2); + + /** + * Test Document Create + */ + $document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'unique()', + 'data' => [ + 'name' => 'Chris Evans' + ], + 'read' => [], + 'write' => [], + ]); + + $response = json_decode($client->receive(), true); + + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(3, $response['data']['channels']); + $this->assertContains('documents', $response['data']['channels']); + $this->assertContains('documents.' . $document['body']['$id'], $response['data']['channels']); + $this->assertContains('collections.' . $actors['body']['$id'] . '.documents', $response['data']['channels']); + $this->assertEquals('database.documents.create', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); + $this->assertEquals($response['data']['payload']['name'], 'Chris Evans'); + + $data['documentId'] = $document['body']['$id']; + + /** + * Test Document Update + */ + $document = $this->client->call(Client::METHOD_PATCH, '/database/collections/' . $data['actorsId'] . '/documents/' . $data['documentId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'data' => [ + 'name' => 'Chris Evans 2' + ], + 'read' => [], + 'write' => [], + ]); + + $response = json_decode($client->receive(), true); + + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(3, $response['data']['channels']); + $this->assertContains('documents', $response['data']['channels']); + $this->assertContains('documents.' . $data['documentId'], $response['data']['channels']); + $this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']); + $this->assertEquals('database.documents.update', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); + + $this->assertEquals($response['data']['payload']['name'], 'Chris Evans 2'); + + /** + * Test Document Delete + */ + $document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'unique()', + 'data' => [ + 'name' => 'Bradley Cooper' + ], + 'read' => [], + 'write' => [], + ]); + + $client->receive(); + + $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $data['actorsId'] . '/documents/' . $document['body']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $response = json_decode($client->receive(), true); + + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(3, $response['data']['channels']); + $this->assertContains('documents', $response['data']['channels']); + $this->assertContains('documents.' . $document['body']['$id'], $response['data']['channels']); + $this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']); + $this->assertEquals('database.documents.delete', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); + $this->assertEquals($response['data']['payload']['name'], 'Bradley Cooper'); + + $client->close(); + } + public function testChannelFiles() { $user = $this->getUser(); diff --git a/tests/unit/Messaging/MessagingTest.php b/tests/unit/Messaging/MessagingTest.php index dd258d3ae6..a0cad0bae9 100644 --- a/tests/unit/Messaging/MessagingTest.php +++ b/tests/unit/Messaging/MessagingTest.php @@ -2,7 +2,7 @@ namespace Appwrite\Tests; -use Appwrite\Database\Document; +use Utopia\Database\Document; use Appwrite\Messaging\Adapter\Realtime; use PHPUnit\Framework\TestCase; @@ -195,4 +195,51 @@ class MessagingTest extends TestCase $this->assertArrayHasKey('account', $channels); $this->assertArrayNotHasKey('account.456', $channels); } + + public function testFromPayloadCollectionLevelPermissions(): void + { + /** + * Test Collection Level Permissions + */ + $result = Realtime::fromPayload( + event: 'database.documents.create', + payload: new Document([ + '$id' => 'test', + '$collection' => 'collection', + '$read' => ['role:admin'], + '$write' => ['role:admin'] + ]), + collection: new Document([ + '$id' => 'collection', + '$read' => ['role:all'], + '$write' => ['role:all'], + 'permission' => 'collection' + ]) + ); + + $this->assertContains('role:all', $result['roles']); + $this->assertNotContains('role:admin', $result['roles']); + + /** + * Test Document Level Permissions + */ + $result = Realtime::fromPayload( + event: 'database.documents.create', + payload: new Document([ + '$id' => 'test', + '$collection' => 'collection', + '$read' => ['role:all'], + '$write' => ['role:all'] + ]), + collection: new Document([ + '$id' => 'collection', + '$read' => ['role:admin'], + '$write' => ['role:admin'], + 'permission' => 'document' + ]) + ); + + $this->assertContains('role:all', $result['roles']); + $this->assertNotContains('role:admin', $result['roles']); + } } From 7566c5368eae98cbb2996a489e8c5c70d532615d Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 16 Dec 2021 19:50:06 +0100 Subject: [PATCH 2/2] fix: events middleware --- app/controllers/shared/api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index a6ad8a276e..ef1e15a4d9 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -207,7 +207,7 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits if ($project->getId() !== 'console') { $payload = new Document($response->getPayload()); - $collection = new Document($events->getParam('collection')); + $collection = new Document($events->getParam('collection') ?? []); $target = Realtime::fromPayload( event: $events->getParam('event'),