diff sat/plugins/plugin_comp_ap_gateway/__init__.py @ 3869:c0bcbcf5b4b7

component AP gateway: handle `Like` and `Undo`/`Like` activities: rel 370
author Goffi <goffi@goffi.org>
date Thu, 21 Jul 2022 18:07:35 +0200
parents 59fbb66b2923
children 03761f8ba8bb
line wrap: on
line diff
--- a/sat/plugins/plugin_comp_ap_gateway/__init__.py	Thu Jul 21 18:05:20 2022 +0200
+++ b/sat/plugins/plugin_comp_ap_gateway/__init__.py	Thu Jul 21 18:07:35 2022 +0200
@@ -293,7 +293,7 @@
         if self._pa.isAttachmentNode(itemsEvent.nodeIdentifier):
             await self.convertAndPostAttachments(
                 client, ap_account, itemsEvent.sender, itemsEvent.nodeIdentifier,
-                itemsEvent.items, publisher=itemsEvent.sender
+                itemsEvent.items
             )
         else:
             await self.convertAndPostItems(
@@ -581,7 +581,7 @@
         if self.client is None:
             raise exceptions.InternalError("Client is not set yet")
 
-        if jid_.host == self.client.jid.userhost():
+        if self.isVirtualJID(jid_):
             # this is an proxy JID to an AP Actor
             return self._e.unescape(jid_.user)
 
@@ -786,6 +786,10 @@
         """
         return url.startswith(self.base_ap_url)
 
+    def isVirtualJID(self, jid_: jid.JID) -> bool:
+        """Tell if a JID is an AP actor mapped through this gateway"""
+        return jid_.host == self.client.jid.userhost()
+
     def buildSignatureHeader(self, values: Dict[str, str]) -> str:
         """Build key="<value>" signature header from signature data"""
         fields = []
@@ -983,12 +987,16 @@
         for item in items:
             if item.name == "item":
                 mb_data = await self._m.item2mbdata(client, item, service, node)
-                if subscribe_extra_nodes:
+                author_jid = jid.JID(mb_data["author_jid"])
+                if subscribe_extra_nodes and not self.isVirtualJID(author_jid):
                     # we subscribe automatically to comment nodes if any
                     recipient_jid = self.getLocalJIDFromAccount(ap_account)
                     recipient_client = self.client.getVirtualClient(recipient_jid)
                     for comment_data in mb_data.get("comments", []):
                         comment_service = jid.JID(comment_data["service"])
+                        if self.isVirtualJID(comment_service):
+                            log.debug(f"ignoring virtual comment service: {comment_data}")
+                            continue
                         comment_node = comment_data["node"]
                         await self._p.subscribe(
                             recipient_client, comment_service, comment_node
@@ -1019,7 +1027,7 @@
         service: jid.JID,
         node: str,
         items: List[domish.Element],
-        publisher: Optional[jid.JID]
+        publisher: Optional[jid.JID] = None
     ) -> None:
         """Convert XMPP item attachments to AP activities and post them to actor inbox
 
@@ -1027,8 +1035,14 @@
         @param service: JID of the (virtual) pubsub service where the item has been
             published
         @param node: (virtual) node corresponding where the item has been published
-        @param subscribe_extra_nodes: if True, extra data nodes will be automatically
-        subscribed, that is comment nodes if present and attachments nodes.
+            subscribed, that is comment nodes if present and attachments nodes.
+        @param items: attachments items
+        @param publisher: publisher of the attachments item (it's NOT the PEP/Pubsub
+            service, it's the publisher of the item). To be filled only when the publisher
+            is known for sure, otherwise publisher will be determined either if
+            "publisher" attribute is set by pubsub service, or as a last resort, using
+            item's ID (which MUST be publisher bare JID according to pubsub-attachments
+            specification).
         """
         if len(items) != 1:
             log.warning(
@@ -1040,11 +1054,29 @@
         inbox = await self.getAPInboxFromId(actor_id)
 
         item_elt = items[0]
+        item_id = item_elt["id"]
+
+        if publisher is None:
+            item_pub_s = item_elt.getAttribute("publisher")
+            publisher = jid.JID(item_pub_s) if item_pub_s else jid.JID(item_id)
+
+        if publisher.userhost() != item_id:
+            log.warning(
+                "attachments item ID must be publisher's bare JID, ignoring: "
+                f"{item_elt.toXml()}"
+            )
+            return
+
+        if self.isVirtualJID(publisher):
+            log.debug(f"ignoring item coming from local virtual JID {publisher}")
+            return
+
         if publisher is not None:
             item_elt["publisher"] = publisher.userhost()
+
         item_service, item_node, item_id = self._pa.attachmentNode2Item(node)
         item_account = await self.getAPAccountFromJidAndNode(item_service, item_node)
-        if item_service.host == self.client.jid.userhost():
+        if self.isVirtualJID(item_service):
             # it's a virtual JID mapping to an external AP actor, we can use the
             # item_id directly
             item_url = item_id
@@ -1072,11 +1104,11 @@
             except IndexError:
                 # no known element was present in attachments
                 old_attachment = {}
-        sender_account = await self.getAPAccountFromJidAndNode(
-            client.jid,
+        publisher_account = await self.getAPAccountFromJidAndNode(
+            publisher,
             None
         )
-        sender_actor_id = self.buildAPURL(TYPE_ACTOR, sender_account)
+        publisher_actor_id = self.buildAPURL(TYPE_ACTOR, publisher_account)
         try:
             attachments = self._pa.items2attachmentData(client, [item_elt])[0]
         except IndexError:
@@ -1088,22 +1120,22 @@
                 # new "noticed" attachment, we translate to "Like" activity
                 activity_id = self.buildAPURL("like", item_account, item_id)
                 like = self.createActivity(
-                    TYPE_LIKE, sender_actor_id, item_url, activity_id=activity_id
+                    TYPE_LIKE, publisher_actor_id, item_url, activity_id=activity_id
                 )
                 like["to"] = [NS_AP_PUBLIC]
-                await self.signAndPost(inbox, sender_actor_id, like)
+                await self.signAndPost(inbox, publisher_actor_id, like)
         else:
             if "noticed" in old_attachment:
                 # "noticed" attachment has been removed, we undo the "Like" activity
                 activity_id = self.buildAPURL("like", item_account, item_id)
                 like = self.createActivity(
-                    TYPE_LIKE, sender_actor_id, item_url, activity_id=activity_id
+                    TYPE_LIKE, publisher_actor_id, item_url, activity_id=activity_id
                 )
                 like["to"] = [NS_AP_PUBLIC]
-                undo = self.createActivity("Undo", sender_actor_id, like)
-                await self.signAndPost(inbox, sender_actor_id, undo)
+                undo = self.createActivity("Undo", publisher_actor_id, like)
+                await self.signAndPost(inbox, publisher_actor_id, undo)
 
-        if service.user and service.host == self.client.jid.userhost():
+        if service.user and self.isVirtualJID(service):
             # the item is on a virtual service, we need to store it in cache
             log.debug("storing attachments item in cache")
             cached_node = await self.host.memory.storage.getPubsubNode(
@@ -1720,7 +1752,7 @@
         rep_item = parsed_url["item"]
         activity_id = self.buildAPURL("item", repeater.userhost(), mb_data["id"])
 
-        if rep_service.host == self.client.jid.userhost():
+        if self.isVirtualJID(rep_service):
             # it's an AP actor linked through this gateway
             # in this case we can simply use the item ID
             if not rep_item.startswith("https:"):
@@ -1825,7 +1857,7 @@
             target_ap_account = await self.getAPAccountFromJidAndNode(
                 service, node
             )
-            if service.host == self.client.jid.userhost():
+            if self.isVirtualJID(service):
                 # service is a proxy JID for AP account
                 actor_data = await self.getAPActorDataFromAccount(target_ap_account)
                 followers = actor_data.get("followers")
@@ -1836,7 +1868,7 @@
                 ap_object["cc"] = [followers]
             if self._m.isCommentNode(node):
                 parent_item = self._m.getParentItem(node)
-                if service.host == self.client.jid.userhost():
+                if self.isVirtualJID(service):
                     # the publication is on a virtual node (i.e. an XMPP node managed by
                     # this gateway and linking to an ActivityPub actor)
                     ap_object["inReplyTo"] = parent_item