Repository: OpenMined/PySyft

Issue:
Ability to Toggle Searchability on Data
## Description
Some data cant be set to searchable because the method only exists on send, and other situations would benefit from the ability to potentially toggle it if the user has the correct permissions.

## Definition of Done
All Pointers can be used to toggle the searchability flag through a method assuming the user has the permission to do so.


Discussion:

Resolving patch:
diff --git a/src/syft/core/node/common/service/obj_search_permission_service.py b/src/syft/core/node/common/service/obj_search_permission_service.py
--- a/src/syft/core/node/common/service/obj_search_permission_service.py
+++ b/src/syft/core/node/common/service/obj_search_permission_service.py
@@ -19,6 +19,7 @@
 from .....proto.core.node.common.service.object_search_permission_update_message_pb2 import (
     ObjectSearchPermissionUpdateMessage as ObjectSearchPermissionUpdateMessage_PB,
 )
+from ....common.group import VerifyAll
 from ....common.message import ImmediateSyftMessageWithoutReply
 from ....common.serde.deserialize import _deserialize
 from ....common.uid import UID
@@ -33,7 +34,7 @@ class ObjectSearchPermissionUpdateMessage(ImmediateSyftMessageWithoutReply):
     def __init__(
         self,
         add_instead_of_remove: bool,
-        target_verify_key: VerifyKey,
+        target_verify_key: Optional[VerifyKey],
         target_object_id: UID,
         address: Address,
         msg_id: Optional[UID] = None,
@@ -63,7 +64,9 @@ def _object2proto(self) -> ObjectSearchPermissionUpdateMessage_PB:
         return ObjectSearchPermissionUpdateMessage_PB(
             msg_id=self.id.serialize(),
             address=self.address.serialize(),
-            target_verify_key=bytes(self.target_verify_key),
+            target_verify_key=bytes(self.target_verify_key)
+            if self.target_verify_key
+            else None,
             target_object_id=self.target_object_id.serialize(),
             add_instead_of_remove=self.add_instead_of_remove,
         )
@@ -88,7 +91,9 @@ def _proto2object(
         return ObjectSearchPermissionUpdateMessage(
             msg_id=_deserialize(blob=proto.msg_id),
             address=_deserialize(blob=proto.address),
-            target_verify_key=VerifyKey(proto.target_verify_key),
+            target_verify_key=VerifyKey(proto.target_verify_key)
+            if proto.target_verify_key
+            else None,
             target_object_id=_deserialize(blob=proto.target_object_id),
             add_instead_of_remove=proto.add_instead_of_remove,
         )
@@ -122,10 +127,15 @@ def process(
         msg: ObjectSearchPermissionUpdateMessage,
         verify_key: VerifyKey,
     ) -> None:
+        target_verify_key = msg.target_verify_key or VerifyAll
         if msg.add_instead_of_remove:
-            node.store[msg.target_object_id].search_permissions[verify_key] = msg.id
+            node.store[msg.target_object_id].search_permissions[
+                target_verify_key
+            ] = msg.id
         else:
-            node.store[msg.target_object_id].search_permissions.pop(verify_key, None)
+            node.store[msg.target_object_id].search_permissions.pop(
+                target_verify_key, None
+            )
 
     @staticmethod
     def message_handler_types() -> List[Type[ObjectSearchPermissionUpdateMessage]]:
diff --git a/src/syft/core/node/common/service/obj_search_service.py b/src/syft/core/node/common/service/obj_search_service.py
--- a/src/syft/core/node/common/service/obj_search_service.py
+++ b/src/syft/core/node/common/service/obj_search_service.py
@@ -198,10 +198,9 @@ def process(
             for obj in node.store.get_objects_of_type(obj_type=object):
                 # if this tensor allows anyone to search for it, then one of its keys
                 # has an All() class in it.
-                contains_all_in_permissions = False
-                for key in obj.search_permissions.keys():
-                    if isinstance(key, VerifyAll):
-                        contains_all_in_permissions = True
+                contains_all_in_permissions = any(
+                    key is VerifyAll for key in obj.search_permissions.keys()
+                )
 
                 if (
                     verify_key in obj.search_permissions.keys()
diff --git a/src/syft/core/pointer/pointer.py b/src/syft/core/pointer/pointer.py
--- a/src/syft/core/pointer/pointer.py
+++ b/src/syft/core/pointer/pointer.py
@@ -89,6 +89,7 @@
 # third party
 from google.protobuf.reflection import GeneratedProtocolMessageType
 from loguru import logger
+from nacl.signing import VerifyKey
 
 # syft absolute
 import syft as sy
@@ -105,6 +106,9 @@
     GarbageCollectObjectAction,
 )
 from ..node.common.action.get_object_action import GetObjectAction
+from ..node.common.service.obj_search_permission_service import (
+    ObjectSearchPermissionUpdateMessage,
+)
 from ..store.storeable_object import StorableObject
 
 
@@ -440,6 +444,21 @@ def request(
                     # escape the while loop
                     return status
 
+    def make_searchable(self, target_verify_key: Optional[VerifyKey] = None) -> None:
+        """Make the object pointed at searchable for other people. If target_verify_key is not specified, the
+        object will be searchable by anyone.
+
+        :param target_verify_key: The verify_key of the client to which we want to give search permission.
+        :type target_verify_key: Optional[VerifyKey]
+        """
+        msg = ObjectSearchPermissionUpdateMessage(
+            add_instead_of_remove=True,
+            target_verify_key=target_verify_key,
+            target_object_id=self.id_at_location,
+            address=self.client.address,
+        )
+        self.client.send_immediate_msg_without_reply(msg=msg)
+
     def check_access(self, node: AbstractNode, request_id: UID) -> any:  # type: ignore
         """Method that checks the status of an already made request. There are three possible
         outcomes when requesting access:


Test patch:
diff --git a/tests/syft/core/node/common/service/obj_search_permission_service_test.py b/tests/syft/core/node/common/service/obj_search_permission_service_test.py
--- a/tests/syft/core/node/common/service/obj_search_permission_service_test.py
+++ b/tests/syft/core/node/common/service/obj_search_permission_service_test.py
@@ -52,7 +52,9 @@ def test_object_search_permissons_update_execute_add() -> None:
     )
 
     assert (
-        bob_phone.store[ptr.id_at_location].search_permissions[bob_phone.verify_key]
+        bob_phone.store[ptr.id_at_location].search_permissions[
+            bob_phone_client.verify_key
+        ]
         == msg.id
     )
 
@@ -77,6 +79,6 @@ def test_object_search_permissons_update_execute_remove() -> None:
     )
 
     assert (
-        bob_phone.verify_key
+        bob_phone_client.verify_key
         not in bob_phone.store[ptr.id_at_location].search_permissions
     )
diff --git a/tests/syft/core/pointer/__init__.py b/tests/syft/core/pointer/__init__.py
new file mode 100644
diff --git a/tests/syft/core/pointer/pointer_test.py b/tests/syft/core/pointer/pointer_test.py
new file mode 100644
--- /dev/null
+++ b/tests/syft/core/pointer/pointer_test.py
@@ -0,0 +1,25 @@
+# third party
+import pytest
+import torch as th
+
+# syft absolute
+import syft as sy
+
+
+@pytest.mark.parametrize("with_verify_key", [True, False])
+def test_make_searchable(with_verify_key: bool) -> None:
+    bob = sy.VirtualMachine(name="Bob")
+    root_client = bob.get_root_client()
+    client = bob.get_client()
+
+    ten = th.tensor([1, 2])
+    ptr = ten.send(root_client)
+
+    assert len(client.store) == 0
+
+    if with_verify_key:
+        ptr.make_searchable(target_verify_key=client.verify_key)
+    else:
+        ptr.make_searchable()
+
+    assert len(client.store) == 1