diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index c7a4cf1b7..7c06c337b 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -426,7 +426,7 @@ public function updateDocuments(string $collection, Document $updates, array $do $attributes['_createdAt'] = $updates->getCreatedAt(); } - if (!empty($updates->getPermissions())) { + if ($updates->offsetExists('$permissions')) { $attributes['_permissions'] = json_encode($updates->getPermissions()); } @@ -484,7 +484,7 @@ public function updateDocuments(string $collection, Document $updates, array $do $affected = $stmt->rowCount(); // Permissions logic - if (!empty($updates->getPermissions())) { + if ($updates->offsetExists('$permissions')) { $removeQueries = []; $removeBindValues = []; @@ -492,7 +492,10 @@ public function updateDocuments(string $collection, Document $updates, array $do $addBindValues = []; foreach ($documents as $index => $document) { - // Permissions logic + if ($document->getAttribute('$skipPermissionsUpdate', false)) { + continue; + } + $sql = " SELECT _type, _permission FROM {$this->getSQLTable($name . '_perms')} diff --git a/src/Database/Database.php b/src/Database/Database.php index 772531bbb..14f9eb8ac 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -4385,13 +4385,16 @@ public function updateDocuments( if (!empty($cursor) && $cursor->getCollection() !== $collection->getId()) { throw new DatabaseException("Cursor document must be from the same Collection."); } + unset($updates['$id']); unset($updates['$tenant']); + if (($updates->getCreatedAt() === null || !$this->preserveDates)) { unset($updates['$createdAt']); } else { $updates['$createdAt'] = $updates->getCreatedAt(); } + if ($this->adapter->getSharedTables()) { $updates['$tenant'] = $this->adapter->getTenant(); } @@ -4441,8 +4444,27 @@ public function updateDocuments( break; } - $this->withTransaction(function () use ($collection, $updates, &$batch) { - foreach ($batch as &$document) { + $currentPermissions = $updates->getPermissions(); + sort($currentPermissions); + + $this->withTransaction(function () use ($collection, $updates, &$batch, $currentPermissions) { + foreach ($batch as $index => $document) { + + $skipPermissionsUpdate = true; + + if ($updates->offsetExists('$permissions')) { + if (!$document->offsetExists('$permissions')) { + throw new QueryException('Permission document missing in select'); + } + + $originalPermissions = $document->getPermissions(); + sort($originalPermissions); + + $skipPermissionsUpdate = ($originalPermissions === $currentPermissions); + } + + $document->setAttribute('$skipPermissionsUpdate', $skipPermissionsUpdate); + $new = new Document(\array_merge($document->getArrayCopy(), $updates->getArrayCopy())); if ($this->resolveRelationships) { @@ -4462,7 +4484,7 @@ public function updateDocuments( throw new ConflictException('Document was updated after the request timestamp'); } - $document = $this->encode($collection, $document); + $batch[$index] = $this->encode($collection, $document); } $this->adapter->updateDocuments( @@ -4473,6 +4495,8 @@ public function updateDocuments( }); foreach ($batch as $doc) { + $doc->removeAttribute('$skipPermissionsUpdate'); + $this->purgeCachedDocument($collection->getId(), $doc->getId()); $doc = $this->decode($collection, $doc); try { diff --git a/tests/e2e/Adapter/Scopes/PermissionTests.php b/tests/e2e/Adapter/Scopes/PermissionTests.php index 433396276..50e14c90c 100644 --- a/tests/e2e/Adapter/Scopes/PermissionTests.php +++ b/tests/e2e/Adapter/Scopes/PermissionTests.php @@ -14,6 +14,160 @@ trait PermissionTests { + public function testUnsetPermissions(): void + { + /** @var Database $database */ + $database = static::getDatabase(); + + $database->createCollection(__FUNCTION__); + $this->assertTrue($database->createAttribute( + collection: __FUNCTION__, + id: 'president', + type: Database::VAR_STRING, + size: 255, + required: false + )); + + $permissions = [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ]; + + $documents = []; + + for ($i = 0; $i < 3; $i++) { + $documents[] = new Document([ + '$permissions' => $permissions, + 'president' => 'Donald Trump' + ]); + } + + $results = []; + $count = $database->createDocuments(__FUNCTION__, $documents, onNext: function ($doc) use (&$results) { + $results[] = $doc; + }); + + $this->assertEquals(3, $count); + + foreach ($results as $result) { + $this->assertEquals('Donald Trump', $result->getAttribute('president')); + $this->assertEquals($permissions, $result->getPermissions()); + } + + /** + * No permissions passed, Check old is preserved + */ + $updates = new Document([ + 'president' => 'George Washington' + ]); + + $results = []; + $modified = $database->updateDocuments( + __FUNCTION__, + $updates, + onNext: function ($doc) use (&$results) { + $results[] = $doc; + } + ); + + $this->assertEquals(3, $modified); + + foreach ($results as $result) { + $this->assertEquals('George Washington', $result->getAttribute('president')); + $this->assertEquals($permissions, $result->getPermissions()); + } + + $documents = $database->find(__FUNCTION__); + + $this->assertEquals(3, count($documents)); + + foreach ($documents as $document) { + $this->assertEquals('George Washington', $document->getAttribute('president')); + $this->assertEquals($permissions, $document->getPermissions()); + } + + /** + * Change permissions remove delete + */ + $permissions = [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + ]; + + $updates = new Document([ + '$permissions' => $permissions, + 'president' => 'Joe biden' + ]); + + $results = []; + $modified = $database->updateDocuments( + __FUNCTION__, + $updates, + onNext: function ($doc) use (&$results) { + $results[] = $doc; + } + ); + + $this->assertEquals(3, $modified); + + foreach ($results as $result) { + $this->assertEquals('Joe biden', $result->getAttribute('president')); + $this->assertEquals($permissions, $result->getPermissions()); + $this->assertArrayNotHasKey('$skipPermissionsUpdate', $result); + } + + $documents = $database->find(__FUNCTION__); + + $this->assertEquals(3, count($documents)); + + foreach ($documents as $document) { + $this->assertEquals('Joe biden', $document->getAttribute('president')); + $this->assertEquals($permissions, $document->getPermissions()); + } + + /** + * Unset permissions + */ + $updates = new Document([ + '$permissions' => [], + 'president' => 'Richard Nixon' + ]); + + $results = []; + $modified = $database->updateDocuments( + __FUNCTION__, + $updates, + onNext: function ($doc) use (&$results) { + $results[] = $doc; + } + ); + + $this->assertEquals(3, $modified); + + foreach ($results as $result) { + $this->assertEquals('Richard Nixon', $result->getAttribute('president')); + $this->assertEquals([], $result->getPermissions()); + } + + $documents = $database->find(__FUNCTION__); + $this->assertEquals(0, count($documents)); + + Authorization::disable(); + $documents = $database->find(__FUNCTION__); + Authorization::reset(); + + $this->assertEquals(3, count($documents)); + + foreach ($documents as $document) { + $this->assertEquals('Richard Nixon', $document->getAttribute('president')); + $this->assertEquals([], $document->getPermissions()); + $this->assertArrayNotHasKey('$skipPermissionsUpdate', $document); + } + } + public function testCreateDocumentsEmptyPermission(): void { /** @var Database $database */