diff --git a/packages/syft/src/syft/store/kv_document_store.py b/packages/syft/src/syft/store/kv_document_store.py index d7131f52be2..bc1c6d2ea37 100644 --- a/packages/syft/src/syft/store/kv_document_store.py +++ b/packages/syft/src/syft/store/kv_document_store.py @@ -450,6 +450,7 @@ def _delete( ActionObjectWRITE(uid=qk.value, credentials=credentials) ): _obj = self.data.pop(qk.value) + self.permissions.pop(qk.value) self._delete_unique_keys_for(_obj) self._delete_search_keys_for(_obj) return Ok(SyftSuccess(message="Deleted")) diff --git a/packages/syft/src/syft/store/mongo_client.py b/packages/syft/src/syft/store/mongo_client.py index f5c8ea47b05..c20b056777f 100644 --- a/packages/syft/src/syft/store/mongo_client.py +++ b/packages/syft/src/syft/store/mongo_client.py @@ -73,7 +73,7 @@ class MongoStoreClientConfig(StoreClientConfig): Controls how long (in milliseconds) the driver will wait to find an available, appropriate server to carry out a database operation; while it is waiting, multiple server monitoring operations may be carried out, each controlled by `connectTimeoutMS`. - Defaults to ``30000`` (30 seconds). + Defaults to ``120000`` (120 seconds). `waitQueueTimeoutMS`: (integer or None) How long (in milliseconds) a thread will wait for a socket from the pool if the pool has no free sockets. Defaults to ``None`` (no timeout). @@ -108,7 +108,7 @@ class MongoStoreClientConfig(StoreClientConfig): timeoutMS: int = 0 socketTimeoutMS: int = 0 connectTimeoutMS: int = 20000 - serverSelectionTimeoutMS: int = 30000 + serverSelectionTimeoutMS: int = 120000 waitQueueTimeoutMS: Optional[int] = None heartbeatFrequencyMS: int = 10000 appname: str = "pysyft" diff --git a/packages/syft/src/syft/store/mongo_document_store.py b/packages/syft/src/syft/store/mongo_document_store.py index 46664512647..05b5a94d16e 100644 --- a/packages/syft/src/syft/store/mongo_document_store.py +++ b/packages/syft/src/syft/store/mongo_document_store.py @@ -3,6 +3,7 @@ from typing import Dict from typing import List from typing import Optional +from typing import Set from typing import Type # third party @@ -229,6 +230,8 @@ def _set( add_permissions: Optional[List[ActionObjectPermission]] = None, ignore_duplicates: bool = False, ) -> Result[SyftObject, str]: + # TODO: Refactor this function since now it's doing both set and + # update at the same time write_permission = ActionObjectWRITE(uid=obj.id, credentials=credentials) can_write = self.has_permission(write_permission) @@ -379,21 +382,37 @@ def _get_all_from_store( def _delete( self, credentials: SyftVerifyKey, qk: QueryKey, has_permission: bool = False ) -> Result[SyftSuccess, Err]: + if not ( + has_permission + or self.has_permission( + ActionObjectWRITE(uid=qk.value, credentials=credentials) + ) + ): + return Err(f"You don't have permission to delete object with qk: {qk}") + collection_status = self.collection if collection_status.is_err(): return collection_status collection: MongoCollection = collection_status.ok() - if has_permission or self.has_permission( - ActionObjectWRITE(uid=qk.value, credentials=credentials) - ): - qks = QueryKeys(qks=qk) - result = collection.delete_one(filter=qks.as_dict_mongo) - - if result.deleted_count == 1: - return Ok(SyftSuccess(message="Deleted")) + collection_permissions_status = self.permissions + if collection_permissions_status.is_err(): + return collection_permissions_status + collection_permissions: MongoCollection = collection_permissions_status.ok() - return Err(f"Failed to delete object with qk: {qk}") + qks = QueryKeys(qks=qk) + # delete the object + result = collection.delete_one(filter=qks.as_dict_mongo) + # delete the object's permission + result_permission = collection_permissions.delete_one(filter=qks.as_dict_mongo) + if result.deleted_count == 1 and result_permission.deleted_count == 1: + return Ok(SyftSuccess(message="Object and its permission are deleted")) + elif result.deleted_count == 0: + return Err(f"Failed to delete object with qk: {qk}") + else: + return Err( + f"Object with qk: {qk} was deleted, but failed to delete its corresponding permission" + ) def has_permission(self, permission: ActionObjectPermission) -> bool: """Check if the permission is inside the permission collection""" @@ -402,10 +421,6 @@ def has_permission(self, permission: ActionObjectPermission) -> bool: return False collection_permissions: MongoCollection = collection_permissions_status.ok() - # TODO: fix for other admins - if self.root_verify_key.verify == permission.credentials.verify: - return True - permissions: Optional[Dict] = collection_permissions.find_one( {"_id": permission.uid} ) @@ -413,6 +428,10 @@ def has_permission(self, permission: ActionObjectPermission) -> bool: if permissions is None: return False + # TODO: fix for other admins + if self.root_verify_key.verify == permission.credentials.verify: + return True + if permission.permission_string in permissions["permissions"]: return True @@ -441,7 +460,7 @@ def add_permission(self, permission: ActionObjectPermission) -> Result[None, Err {"_id": permission.uid} ) if permissions is None: - # Permission doesn't exits, add a new one + # Permission doesn't exist, add a new one collection_permissions.insert_one( { "_id": permission.uid, @@ -450,7 +469,7 @@ def add_permission(self, permission: ActionObjectPermission) -> Result[None, Err ) else: # update the permissions with the new permission string - permission_strings: set = permissions["permissions"] + permission_strings: Set = permissions["permissions"] permission_strings.add(permission.permission_string) collection_permissions.update_one( {"_id": permission.uid}, {"$set": {"permissions": permission_strings}} @@ -472,11 +491,18 @@ def remove_permission( ) if permissions is None: return Err(f"permission with UID {permission.uid} not found!") - permissions_strings: set = permissions["permissions"] - permissions_strings.remove(permission.permission_string) - collection_permissions.update_one( - {"_id": permission.uid}, {"$set": {"permissions": permissions_strings}} - ) + permissions_strings: Set = permissions["permissions"] + if permission.permission_string in permissions_strings: + permissions_strings.remove(permission.permission_string) + if len(permissions_strings) > 0: + collection_permissions.update_one( + {"_id": permission.uid}, + {"$set": {"permissions": permissions_strings}}, + ) + else: + collection_permissions.delete_one({"_id": permission.uid}) + else: + return Err(f"the permission {permission.permission_string} does not exist!") def take_ownership( self, uid: UID, credentials: SyftVerifyKey diff --git a/packages/syft/tests/conftest.py b/packages/syft/tests/conftest.py index 53bc1c2260b..734faf9d5a5 100644 --- a/packages/syft/tests/conftest.py +++ b/packages/syft/tests/conftest.py @@ -46,6 +46,11 @@ def guest_client(worker): return worker.guest_client +@pytest.fixture(autouse=True) +def guest_verify_key(worker): + return worker.guest_client.credentials.verify_key + + @pytest.fixture(autouse=True) def guest_domain_client(root_domain_client): return root_domain_client.guest() diff --git a/packages/syft/tests/syft/stores/kv_document_store_test.py b/packages/syft/tests/syft/stores/kv_document_store_test.py index 956a5077a7e..3e66d753a96 100644 --- a/packages/syft/tests/syft/stores/kv_document_store_test.py +++ b/packages/syft/tests/syft/stores/kv_document_store_test.py @@ -101,7 +101,7 @@ def test_kv_store_partition_delete( assert len(kv_store_partition.all(root_verify_key).ok()) == len(objs) - # random object + # can't delete a random object since it was not added obj = MockSyftObject(data="bogus") key = kv_store_partition.settings.store_key.with_obj(obj) res = kv_store_partition.delete(root_verify_key, key) @@ -114,10 +114,13 @@ def test_kv_store_partition_delete( res = kv_store_partition.delete(root_verify_key, key) assert res.is_ok() assert len(kv_store_partition.all(root_verify_key).ok()) == len(objs) - idx - 1 + # check that the corresponding permissions were also deleted + assert len(kv_store_partition.data) == len(kv_store_partition.permissions) res = kv_store_partition.delete(root_verify_key, key) assert res.is_err() assert len(kv_store_partition.all(root_verify_key).ok()) == len(objs) - idx - 1 + assert len(kv_store_partition.data) == len(kv_store_partition.permissions) assert len(kv_store_partition.all(root_verify_key).ok()) == 0 diff --git a/packages/syft/tests/syft/stores/mongo_document_store_test.py b/packages/syft/tests/syft/stores/mongo_document_store_test.py index 968a94ce0fa..599d387bb9e 100644 --- a/packages/syft/tests/syft/stores/mongo_document_store_test.py +++ b/packages/syft/tests/syft/stores/mongo_document_store_test.py @@ -1,15 +1,27 @@ # stdlib import sys from threading import Thread +from typing import List +from typing import Set from typing import Tuple # third party from joblib import Parallel from joblib import delayed +from pymongo.collection import Collection as MongoCollection import pytest +from result import Err # syft absolute +from syft.node.credentials import SyftVerifyKey +from syft.service.action.action_permissions import ActionObjectPermission +from syft.service.action.action_permissions import ActionPermission +from syft.service.action.action_store import ActionObjectEXECUTE +from syft.service.action.action_store import ActionObjectOWNER +from syft.service.action.action_store import ActionObjectREAD +from syft.service.action.action_store import ActionObjectWRITE from syft.store.document_store import PartitionSettings +from syft.store.document_store import QueryKey from syft.store.document_store import QueryKeys from syft.store.mongo_client import MongoStoreClientConfig from syft.store.mongo_document_store import MongoStoreConfig @@ -17,12 +29,20 @@ # relative from .store_constants_test import generate_db_name +from .store_constants_test import test_verify_key_string_hacker from .store_fixtures_test import mongo_store_partition_fn from .store_mocks_test import MockObjectType from .store_mocks_test import MockSyftObject REPEATS = 20 +PERMISSIONS = [ + ActionObjectOWNER, + ActionObjectREAD, + ActionObjectWRITE, + ActionObjectEXECUTE, +] + @pytest.mark.skipif( sys.platform != "linux", reason="pytest_mock_resources + docker issues on Windows" @@ -34,6 +54,7 @@ def test_mongo_store_partition_sanity( assert res.is_ok() assert hasattr(mongo_store_partition, "_collection") + assert hasattr(mongo_store_partition, "_permissions") def test_mongo_store_partition_init_failed(root_verify_key) -> None: @@ -231,7 +252,7 @@ def test_mongo_store_partition_update( res = mongo_store_partition.update(root_verify_key, key, obj_new) assert res.is_ok() - # The ID should stay the same on update, unly the values are updated. + # The ID should stay the same on update, only the values are updated. assert ( len( mongo_store_partition.all( @@ -599,3 +620,434 @@ def _kv_cbk(tid: int) -> None: ).ok() ) assert stored_cnt == 0 + + +@pytest.mark.skipif( + sys.platform == "win32", reason="pytest_mock_resources + docker issues on Windows" +) +def test_mongo_store_partition_permissions_collection( + mongo_store_partition: MongoStorePartition, +) -> None: + res = mongo_store_partition.init_store() + assert res.is_ok() + + collection_permissions_status = mongo_store_partition.permissions + assert not collection_permissions_status.is_err() + collection_permissions = collection_permissions_status.ok() + assert isinstance(collection_permissions, MongoCollection) + + +@pytest.mark.skipif( + sys.platform == "win32", reason="pytest_mock_resources + docker issues on Windows" +) +def test_mongo_store_partition_add_remove_permission( + root_verify_key: SyftVerifyKey, mongo_store_partition: MongoStorePartition +) -> None: + """ + Test the add_permission and remove_permission functions of MongoStorePartition + """ + # setting up + res = mongo_store_partition.init_store() + assert res.is_ok() + permissions_collection: MongoCollection = mongo_store_partition.permissions.ok() + obj = MockSyftObject(data=1) + + # add the first permission + obj_read_permission = ActionObjectPermission( + uid=obj.id, permission=ActionPermission.READ, credentials=root_verify_key + ) + mongo_store_partition.add_permission(obj_read_permission) + find_res_1 = permissions_collection.find_one({"_id": obj_read_permission.uid}) + assert find_res_1 is not None + assert len(find_res_1["permissions"]) == 1 + assert find_res_1["permissions"] == { + obj_read_permission.permission_string, + } + + # add the second permission + obj_write_permission = ActionObjectPermission( + uid=obj.id, permission=ActionPermission.WRITE, credentials=root_verify_key + ) + mongo_store_partition.add_permission(obj_write_permission) + + find_res_2 = permissions_collection.find_one({"_id": obj.id}) + assert find_res_2 is not None + assert len(find_res_2["permissions"]) == 2 + assert find_res_2["permissions"] == { + obj_read_permission.permission_string, + obj_write_permission.permission_string, + } + + # add duplicated permission + mongo_store_partition.add_permission(obj_write_permission) + find_res_3 = permissions_collection.find_one({"_id": obj.id}) + assert len(find_res_3["permissions"]) == 2 + assert find_res_3["permissions"] == find_res_2["permissions"] + + # remove the write permission + mongo_store_partition.remove_permission(obj_write_permission) + find_res_4 = permissions_collection.find_one({"_id": obj.id}) + assert len(find_res_4["permissions"]) == 1 + assert find_res_1["permissions"] == { + obj_read_permission.permission_string, + } + + # remove a non-existent permission + remove_res = mongo_store_partition.remove_permission( + ActionObjectPermission( + uid=obj.id, permission=ActionPermission.OWNER, credentials=root_verify_key + ) + ) + assert isinstance(remove_res, Err) + find_res_5 = permissions_collection.find_one({"_id": obj.id}) + assert len(find_res_5["permissions"]) == 1 + assert find_res_1["permissions"] == { + obj_read_permission.permission_string, + } + + # there is only one permission object + assert permissions_collection.count_documents({}) == 1 + + # add permissions in a loop + new_permissions = [] + for idx in range(1, REPEATS + 1): + new_obj = MockSyftObject(data=idx) + new_obj_read_permission = ActionObjectPermission( + uid=new_obj.id, + permission=ActionPermission.READ, + credentials=root_verify_key, + ) + new_permissions.append(new_obj_read_permission) + mongo_store_partition.add_permission(new_obj_read_permission) + assert permissions_collection.count_documents({}) == 1 + idx + + # remove all the permissions added in the loop + for permission in new_permissions: + mongo_store_partition.remove_permission(permission) + + assert permissions_collection.count_documents({}) == 1 + + +@pytest.mark.skipif( + sys.platform == "win32", reason="pytest_mock_resources + docker issues on Windows" +) +def test_mongo_store_partition_add_permissions( + root_verify_key: SyftVerifyKey, + guest_verify_key: SyftVerifyKey, + mongo_store_partition: MongoStorePartition, +) -> None: + res = mongo_store_partition.init_store() + assert res.is_ok() + permissions_collection: MongoCollection = mongo_store_partition.permissions.ok() + obj = MockSyftObject(data=1) + + # add multiple permissions for the first object + permission_1 = ActionObjectPermission( + uid=obj.id, permission=ActionPermission.WRITE, credentials=root_verify_key + ) + permission_2 = ActionObjectPermission( + uid=obj.id, permission=ActionPermission.OWNER, credentials=root_verify_key + ) + permission_3 = ActionObjectPermission( + uid=obj.id, permission=ActionPermission.READ, credentials=guest_verify_key + ) + permissions: List[ActionObjectPermission] = [ + permission_1, + permission_2, + permission_3, + ] + mongo_store_partition.add_permissions(permissions) + + # check if the permissions have been added properly + assert permissions_collection.count_documents({}) == 1 + find_res = permissions_collection.find_one({"_id": obj.id}) + assert find_res is not None + assert len(find_res["permissions"]) == 3 + + # add permissions for the second object + obj_2 = MockSyftObject(data=2) + permission_4 = ActionObjectPermission( + uid=obj_2.id, permission=ActionPermission.READ, credentials=root_verify_key + ) + permission_5 = ActionObjectPermission( + uid=obj_2.id, permission=ActionPermission.WRITE, credentials=root_verify_key + ) + mongo_store_partition.add_permissions([permission_4, permission_5]) + + assert permissions_collection.count_documents({}) == 2 + find_res_2 = permissions_collection.find_one({"_id": obj_2.id}) + assert find_res_2 is not None + assert len(find_res_2["permissions"]) == 2 + + +@pytest.mark.skipif( + sys.platform == "win32", reason="pytest_mock_resources + docker issues on Windows" +) +@pytest.mark.parametrize("permission", PERMISSIONS) +def test_mongo_store_partition_has_permission( + root_verify_key: SyftVerifyKey, + guest_verify_key: SyftVerifyKey, + mongo_store_partition: MongoStorePartition, + permission: ActionObjectPermission, +) -> None: + hacker_verify_key = SyftVerifyKey.from_string(test_verify_key_string_hacker) + + res = mongo_store_partition.init_store() + assert res.is_ok() + + # root permission + obj = MockSyftObject(data=1) + permission_root = permission(uid=obj.id, credentials=root_verify_key) + permission_client = permission(uid=obj.id, credentials=guest_verify_key) + permission_hacker = permission(uid=obj.id, credentials=hacker_verify_key) + mongo_store_partition.add_permission(permission_root) + # only the root user has access to this permission + assert mongo_store_partition.has_permission(permission_root) + assert not mongo_store_partition.has_permission(permission_client) + assert not mongo_store_partition.has_permission(permission_hacker) + + # client permission for another object + obj_2 = MockSyftObject(data=2) + permission_client_2 = permission(uid=obj_2.id, credentials=guest_verify_key) + permission_root_2 = permission(uid=obj_2.id, credentials=root_verify_key) + permisson_hacker_2 = permission(uid=obj_2.id, credentials=hacker_verify_key) + mongo_store_partition.add_permission(permission_client_2) + # the root (admin) and guest client should have this permission + assert mongo_store_partition.has_permission(permission_root_2) + assert mongo_store_partition.has_permission(permission_client_2) + assert not mongo_store_partition.has_permission(permisson_hacker_2) + + # remove permissions + mongo_store_partition.remove_permission(permission_root) + assert not mongo_store_partition.has_permission(permission_root) + assert not mongo_store_partition.has_permission(permission_client) + assert not mongo_store_partition.has_permission(permission_hacker) + + mongo_store_partition.remove_permission(permission_client_2) + assert not mongo_store_partition.has_permission(permission_root_2) + assert not mongo_store_partition.has_permission(permission_client_2) + assert not mongo_store_partition.has_permission(permisson_hacker_2) + + +@pytest.mark.skipif( + sys.platform == "win32", reason="pytest_mock_resources + docker issues on Windows" +) +@pytest.mark.parametrize("permission", PERMISSIONS) +def test_mongo_store_partition_take_ownership( + root_verify_key: SyftVerifyKey, + guest_verify_key: SyftVerifyKey, + mongo_store_partition: MongoStorePartition, + permission: ActionObjectPermission, +) -> None: + res = mongo_store_partition.init_store() + assert res.is_ok() + + hacker_verify_key = SyftVerifyKey.from_string(test_verify_key_string_hacker) + obj = MockSyftObject(data=1) + + # the guest client takes ownership of obj + mongo_store_partition.take_ownership(uid=obj.id, credentials=guest_verify_key) + assert mongo_store_partition.has_permission( + permission(uid=obj.id, credentials=guest_verify_key) + ) + # the root client will also has the permission + assert mongo_store_partition.has_permission( + permission(uid=obj.id, credentials=root_verify_key) + ) + assert not mongo_store_partition.has_permission( + permission(uid=obj.id, credentials=hacker_verify_key) + ) + + # hacker or root try to take ownership of the obj and will fail + res = mongo_store_partition.take_ownership( + uid=obj.id, credentials=hacker_verify_key + ) + res_2 = mongo_store_partition.take_ownership( + uid=obj.id, credentials=root_verify_key + ) + assert res.is_err() + assert res_2.is_err() + assert res.value == res_2.value == f"UID: {obj.id} already owned." + + # another object + obj_2 = MockSyftObject(data=2) + # root client takes ownership + mongo_store_partition.take_ownership(uid=obj_2.id, credentials=root_verify_key) + assert mongo_store_partition.has_permission( + permission(uid=obj_2.id, credentials=root_verify_key) + ) + assert not mongo_store_partition.has_permission( + permission(uid=obj_2.id, credentials=guest_verify_key) + ) + assert not mongo_store_partition.has_permission( + permission(uid=obj_2.id, credentials=hacker_verify_key) + ) + + +@pytest.mark.skipif( + sys.platform == "win32", reason="pytest_mock_resources + docker issues on Windows" +) +def test_mongo_store_partition_permissions_set( + root_verify_key: SyftVerifyKey, + guest_verify_key: SyftVerifyKey, + mongo_store_partition: MongoStorePartition, +) -> None: + """ + Test the permissions functionalities when using MongoStorePartition._set function + """ + hacker_verify_key = SyftVerifyKey.from_string(test_verify_key_string_hacker) + res = mongo_store_partition.init_store() + assert res.is_ok() + + # set the object to mongo_store_partition.collection + obj = MockSyftObject(data=1) + res = mongo_store_partition.set(root_verify_key, obj, ignore_duplicates=False) + assert res.is_ok() + assert res.ok() == obj + + # check if the corresponding permissions has been added to the permissions + # collection after the root client claim it + pemissions_collection = mongo_store_partition.permissions.ok() + assert isinstance(pemissions_collection, MongoCollection) + permissions = pemissions_collection.find_one({"_id": obj.id}) + assert permissions is not None + assert isinstance(permissions["permissions"], Set) + assert len(permissions["permissions"]) == 4 + for permission in PERMISSIONS: + assert mongo_store_partition.has_permission( + permission(uid=obj.id, credentials=root_verify_key) + ) + + # the hacker tries to set duplicated object but should not be able to claim it + res_2 = mongo_store_partition.set(guest_verify_key, obj, ignore_duplicates=True) + assert res_2.is_ok() + for permission in PERMISSIONS: + assert not mongo_store_partition.has_permission( + permission(uid=obj.id, credentials=hacker_verify_key) + ) + assert mongo_store_partition.has_permission( + permission(uid=obj.id, credentials=root_verify_key) + ) + + +@pytest.mark.skipif( + sys.platform == "win32", reason="pytest_mock_resources + docker issues on Windows" +) +def test_mongo_store_partition_permissions_get_all( + root_verify_key: SyftVerifyKey, + guest_verify_key: SyftVerifyKey, + mongo_store_partition: MongoStorePartition, +) -> None: + res = mongo_store_partition.init_store() + assert res.is_ok() + hacker_verify_key = SyftVerifyKey.from_string(test_verify_key_string_hacker) + # set several objects for the root and guest client + num_root_objects: int = 5 + num_guest_objects: int = 3 + for i in range(num_root_objects): + obj = MockSyftObject(data=i) + mongo_store_partition.set( + credentials=root_verify_key, obj=obj, ignore_duplicates=False + ) + for i in range(num_guest_objects): + obj = MockSyftObject(data=i) + mongo_store_partition.set( + credentials=guest_verify_key, obj=obj, ignore_duplicates=False + ) + + assert ( + len(mongo_store_partition.all(root_verify_key).ok()) + == num_root_objects + num_guest_objects + ) + assert len(mongo_store_partition.all(guest_verify_key).ok()) == num_guest_objects + assert len(mongo_store_partition.all(hacker_verify_key).ok()) == 0 + + +@pytest.mark.skipif( + sys.platform == "win32", reason="pytest_mock_resources + docker issues on Windows" +) +def test_mongo_store_partition_permissions_delete( + root_verify_key: SyftVerifyKey, + guest_verify_key: SyftVerifyKey, + mongo_store_partition: MongoStorePartition, +) -> None: + res = mongo_store_partition.init_store() + assert res.is_ok() + collection: MongoCollection = mongo_store_partition.collection.ok() + pemissions_collection: MongoCollection = mongo_store_partition.permissions.ok() + hacker_verify_key = SyftVerifyKey.from_string(test_verify_key_string_hacker) + + # the root client set an object + obj = MockSyftObject(data=1) + mongo_store_partition.set( + credentials=root_verify_key, obj=obj, ignore_duplicates=False + ) + qk: QueryKey = mongo_store_partition.settings.store_key.with_obj(obj) + # guest or hacker can't delete it + assert not mongo_store_partition.delete(guest_verify_key, qk).is_ok() + assert not mongo_store_partition.delete(hacker_verify_key, qk).is_ok() + # only the root client can delete it + assert mongo_store_partition.delete(root_verify_key, qk).is_ok() + # check if the object and its permission have been deleted + assert collection.count_documents({}) == 0 + assert pemissions_collection.count_documents({}) == 0 + + # the guest client set an object + obj_2 = MockSyftObject(data=2) + mongo_store_partition.set( + credentials=guest_verify_key, obj=obj_2, ignore_duplicates=False + ) + qk_2: QueryKey = mongo_store_partition.settings.store_key.with_obj(obj_2) + # the hacker can't delete it + assert not mongo_store_partition.delete(hacker_verify_key, qk_2).is_ok() + # the guest client can delete it + assert mongo_store_partition.delete(guest_verify_key, qk_2).is_ok() + assert collection.count_documents({}) == 0 + assert pemissions_collection.count_documents({}) == 0 + + # the guest client set another object + obj_3 = MockSyftObject(data=3) + mongo_store_partition.set( + credentials=guest_verify_key, obj=obj_3, ignore_duplicates=False + ) + qk_3: QueryKey = mongo_store_partition.settings.store_key.with_obj(obj_3) + # the root client also has the permission to delete it + assert mongo_store_partition.delete(root_verify_key, qk_3).is_ok() + assert collection.count_documents({}) == 0 + assert pemissions_collection.count_documents({}) == 0 + + +@pytest.mark.skipif( + sys.platform == "win32", reason="pytest_mock_resources + docker issues on Windows" +) +def test_mongo_store_partition_permissions_update( + root_verify_key: SyftVerifyKey, + guest_verify_key: SyftVerifyKey, + mongo_store_partition: MongoStorePartition, +) -> None: + res = mongo_store_partition.init_store() + assert res.is_ok() + # the root client set an object + obj = MockSyftObject(data=1) + mongo_store_partition.set( + credentials=root_verify_key, obj=obj, ignore_duplicates=False + ) + assert len(mongo_store_partition.all(credentials=root_verify_key).ok()) == 1 + + qk: QueryKey = mongo_store_partition.settings.store_key.with_obj(obj) + permsissions: MongoCollection = mongo_store_partition.permissions.ok() + + for v in range(REPEATS): + # the guest client should not have permission to update obj + obj_new = MockSyftObject(data=v) + res = mongo_store_partition.update( + credentials=guest_verify_key, qk=qk, obj=obj_new + ) + assert res.is_err() + # the root client has the permission to update obj + res = mongo_store_partition.update( + credentials=root_verify_key, qk=qk, obj=obj_new + ) + assert res.is_ok() + # the id of the object in the permission collection should not be changed + assert permsissions.find_one(qk.as_dict_mongo)["_id"] == obj.id