diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index f20f151f4..6d2aac8ef 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1001,7 +1001,7 @@ public function updateDocument(Document $collection, string $id, Document $docum $stmtRemovePermissions = $this->getPDO()->prepare($sql); $stmtRemovePermissions->bindValue(':_uid', $id); if ($this->sharedTables) { - $stmtRemovePermissions->bindValue(':_tenant', $this->tenant); + $stmtRemovePermissions->bindValue(':_tenant', $document->getTenant()); } $values = []; @@ -1026,7 +1026,7 @@ public function updateDocument(Document $collection, string $id, Document $docum $stmtAddPermissions = $this->getPDO()->prepare($sql); $stmtAddPermissions->bindValue(":_uid", $newUid); if ($this->sharedTables) { - $stmtAddPermissions->bindValue(":_tenant", $this->tenant); + $stmtAddPermissions->bindValue(":_tenant", $document->getTenant()); } foreach ($binds as $key => $permission) { diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 0271db502..124112326 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -1127,7 +1127,7 @@ public function updateDocument(Document $collection, string $id, Document $docum $stmtRemovePermissions = $this->getPDO()->prepare($sql); $stmtRemovePermissions->bindValue(':_uid', $id); if ($this->sharedTables) { - $stmtRemovePermissions->bindValue(':_tenant', $this->tenant); + $stmtRemovePermissions->bindValue(':_tenant', $document->getTenant()); } $values = []; @@ -1152,7 +1152,7 @@ public function updateDocument(Document $collection, string $id, Document $docum $stmtAddPermissions = $this->getPDO()->prepare($sql); $stmtAddPermissions->bindValue(":_uid", $newUid); if ($this->sharedTables) { - $stmtAddPermissions->bindValue(':_tenant', $this->tenant); + $stmtAddPermissions->bindValue(':_tenant', $document->getTenant()); } foreach ($binds as $key => $permission) { diff --git a/src/Database/Adapter/SQLite.php b/src/Database/Adapter/SQLite.php index 92cf01569..3880aec16 100644 --- a/src/Database/Adapter/SQLite.php +++ b/src/Database/Adapter/SQLite.php @@ -1269,7 +1269,7 @@ public function updateDocument(Document $collection, string $id, Document $docum $attributes['_uid'] = $document->getId(); if ($this->sharedTables) { - $attributes['_tenant'] = $this->tenant; + $attributes['_tenant'] = $document->getTenant(); } $name = $this->filter($collection); @@ -1289,7 +1289,7 @@ public function updateDocument(Document $collection, string $id, Document $docum $stmtRemovePermissions = $this->getPDO()->prepare($sql); $stmtRemovePermissions->bindValue(':_uid', $id); if ($this->sharedTables) { - $stmtRemovePermissions->bindValue(':_tenant', $this->tenant); + $stmtRemovePermissions->bindValue(':_tenant', $document->getTenant()); } $values = []; @@ -1314,7 +1314,7 @@ public function updateDocument(Document $collection, string $id, Document $docum $stmtAddPermissions = $this->getPDO()->prepare($sql); $stmtAddPermissions->bindValue(":_uid", $newUid); if ($this->sharedTables) { - $stmtAddPermissions->bindValue(":_tenant", $this->tenant); + $stmtAddPermissions->bindValue(":_tenant", $document->getTenant()); } foreach ($binds as $key => $permission) { diff --git a/tests/e2e/Adapter/Scopes/PermissionTests.php b/tests/e2e/Adapter/Scopes/PermissionTests.php index a97f9691c..97e55633f 100644 --- a/tests/e2e/Adapter/Scopes/PermissionTests.php +++ b/tests/e2e/Adapter/Scopes/PermissionTests.php @@ -3,6 +3,7 @@ namespace Tests\E2E\Adapter\Scopes; use Exception; +use Utopia\Database\Adapter\SQL; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception as DatabaseException; @@ -10,9 +11,55 @@ use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; +use Utopia\Database\Query; trait PermissionTests { + public function testUpdatingASharedDefinitionKeepsItsPermissionRowsTenantless(): void + { + /** @var Database $database */ + $database = $this->getDatabase(); + + // Only the SQL adapters keep permissions in a side table that carries + // its own tenant column; Mongo stores them on the document itself. + if (!$database->getSharedTables() || !$database->getAdapter() instanceof SQL) { + $this->expectNotToPerformAssertions(); + return; + } + + $tenant = $database->getTenant(); + $collection = 'sharedDefinitionPerms'; + + try { + // A shared pool's system collections are created once with no + // tenant, so every tenant on the pool reads the one definition. + $database->setTenant(null); + $database->createCollection($collection, [], [], [Permission::read(Role::any())], false); + + // A per-project pass rewrites that definition while it holds one + // project's tenant. The rows it writes belong to the document, not + // to whoever happened to be selected. + $database->setTenant(989); + $database->updateDocument(Database::METADATA, $collection, new Document([ + '$id' => $collection, + '$permissions' => [Permission::read(Role::any()), Permission::update(Role::any())], + ])); + + // Permission filtering reads the permission rows, so tenanting them + // to 989 hides the shared definition from every other tenant. + $database->setTenant(990); + $found = $database->find(Database::METADATA, [Query::equal('$id', [$collection])]); + + $this->assertCount( + 1, + $found, + 'A shared definition updated under one tenant must stay visible to the rest of the pool.', + ); + } finally { + $database->setTenant($tenant); + } + } + public function testUnsetPermissions(): void { /** @var Database $database */