diff sat_pubsub/backend.py @ 463:f520ac3164b0

privilege: improvment on last message sending on presence with `+notify`: - local entities subscribed to the presence of an other local entity which is connecting are now added to presence map. This helps getting their notification even if they didn't connect recently - nodes with `presence` access model are now also used for `+notify` - notifications are not sent anymore in case of status change if the resource was already present.
author Goffi <goffi@goffi.org>
date Fri, 15 Oct 2021 13:40:56 +0200
parents c9238fca1fb3
children 391aa65f72b2
line wrap: on
line diff
--- a/sat_pubsub/backend.py	Fri Oct 15 09:32:07 2021 +0200
+++ b/sat_pubsub/backend.py	Fri Oct 15 13:40:56 2021 +0200
@@ -62,7 +62,7 @@
 
 import copy
 import uuid
-from typing import Optional
+from typing import Optional, List, Tuple
 
 from zope.interface import implementer
 
@@ -277,12 +277,11 @@
             allowed_accesses = {'open', 'presence', 'whitelist'}
         return self.storage.getNodeIds(pep, recipient, allowed_accesses)
 
-    def getNodes(self, requestor, pep, recipient):
+    async def getNodes(self, requestor, pep, recipient):
         if pep:
-            d = self.privilege.isSubscribedFrom(requestor, recipient)
-            d.addCallback(self._getNodesIds, pep, recipient)
-            return d
-        return self.storage.getNodeIds(pep, recipient)
+            subscribed = await self.privilege.isSubscribedFrom(requestor, recipient)
+            return await self._getNodesIds(subscribed, pep, recipient)
+        return await self.storage.getNodeIds(pep, recipient)
 
     def getNodeMetaData(self, nodeIdentifier, pep, recipient=None):
         # FIXME: manage pep and recipient
@@ -1041,17 +1040,32 @@
 
         defer.returnValue((affiliation, owner, roster, access_model))
 
-    @defer.inlineCallbacks
-    def getItemsIds(self, nodeIdentifier, requestor, authorized_groups, unrestricted, maxItems=None, ext_data=None, pep=False, recipient=None):
+    async def getItemsIds(
+        self,
+        nodeIdentifier: str,
+        requestor: jid.JID,
+        authorized_groups: Optional[List[str]],
+        unrestricted: bool,
+        maxItems: Optional[int] = None,
+        ext_data: Optional[dict] = None,
+        pep: bool = False,
+        recipient: Optional[jid.JID] = None
+    ) -> List[str]:
+        """Retrieve IDs of items from a nodeIdentifier
+
+        Requestor permission is checked before retrieving items IDs
+        """
         # FIXME: items access model are not checked
         # TODO: check items access model
-        node = yield self.storage.getNode(nodeIdentifier, pep, recipient)
-        affiliation, owner, roster, access_model = yield self.checkNodeAccess(node, requestor)
-        ids = yield node.getItemsIds(authorized_groups,
-                                     unrestricted,
-                                     maxItems,
-                                     ext_data)
-        defer.returnValue(ids)
+        node = await self.storage.getNode(nodeIdentifier, pep, recipient)
+        await self.checkNodeAccess(node, requestor)
+        ids = await node.getItemsIds(
+            authorized_groups,
+            unrestricted,
+            maxItems,
+            ext_data
+        )
+        return ids
 
     def getItems(self, nodeIdentifier, requestor, recipient, maxItems=None,
                        itemIdentifiers=None, ext_data=None):
@@ -1455,14 +1469,18 @@
         else:
             return item
 
-    @defer.inlineCallbacks
-    def _notifyPublish(self, data):
+    def _notifyPublish(self, data: dict) -> defer.Deferred:
+        return defer.ensureDeferred(self.notifyPublish(data))
+
+    async def notifyPublish(self, data: dict) -> None:
         items_data = data['items_data']
         node = data['node']
         pep = data['pep']
         recipient = data['recipient']
 
-        owners, notifications_filtered = yield self._prepareNotify(items_data, node, data.get('subscription'), pep, recipient)
+        owners, notifications_filtered = await self._prepareNotify(
+            items_data, node, data.get('subscription'), pep, recipient
+        )
 
         # we notify the owners
         # FIXME: check if this comply with XEP-0060 (option needed ?)
@@ -1478,83 +1496,95 @@
                  [self.getFullItem(item_data) for item_data in items_data]))
 
         if pep:
-            defer.returnValue(self.backend.privilege.notifyPublish(
+            self.backend.privilege.notifyPublish(
                 recipient,
                 node.nodeIdentifier,
-                notifications_filtered))
+                notifications_filtered
+            )
 
         else:
-            defer.returnValue(self.pubsubService.notifyPublish(
+            self.pubsubService.notifyPublish(
                 self.serviceJID,
                 node.nodeIdentifier,
-                notifications_filtered))
+                notifications_filtered)
 
-    def _notifyRetract(self, data):
+    def _notifyRetract(self, data: dict) -> defer.Deferred:
+        return defer.ensureDeferred(self.notifyRetract(data))
+
+    async def notifyRetract(self, data: dict) -> None:
         items_data = data['items_data']
         node = data['node']
         pep = data['pep']
         recipient = data['recipient']
 
-        def afterPrepare(result):
-            owners, notifications_filtered = result
-            #we add the owners
+        owners, notifications_filtered = await self._prepareNotify(
+            items_data, node, data.get('subscription'), pep, recipient
+        )
+
+        #we add the owners
 
-            for owner_jid in owners:
-                notifications_filtered.append(
-                    (owner_jid,
-                     {pubsub.Subscription(node.nodeIdentifier,
-                                          owner_jid,
-                                          'subscribed')},
-                     [item_data.item for item_data in items_data]))
+        for owner_jid in owners:
+            notifications_filtered.append(
+                (owner_jid,
+                 {pubsub.Subscription(node.nodeIdentifier,
+                                      owner_jid,
+                                      'subscribed')},
+                 [item_data.item for item_data in items_data]))
 
-            if pep:
-                return self.backend.privilege.notifyRetract(
-                    recipient,
-                    node.nodeIdentifier,
-                    notifications_filtered)
+        if pep:
+            return self.backend.privilege.notifyRetract(
+                recipient,
+                node.nodeIdentifier,
+                notifications_filtered)
 
-            else:
-                return self.pubsubService.notifyRetract(
-                    self.serviceJID,
-                    node.nodeIdentifier,
-                    notifications_filtered)
+        else:
+            return self.pubsubService.notifyRetract(
+                self.serviceJID,
+                node.nodeIdentifier,
+                notifications_filtered)
 
-        d = self._prepareNotify(items_data, node, data.get('subscription'), pep, recipient)
-        d.addCallback(afterPrepare)
-        return d
-
-    @defer.inlineCallbacks
-    def _prepareNotify(self, items_data, node, subscription=None, pep=None, recipient=None):
+    async def _prepareNotify(
+        self,
+        items_data: Tuple[domish.Element, str, dict],
+        node,
+        subscription: Optional[pubsub.Subscription] = None,
+        pep: bool = False,
+        recipient: Optional[jid.JID] = None
+    ) -> Tuple:
         """Do a bunch of permissions check and filter notifications
 
         The owner is not added to these notifications,
         it must be added by the calling method
-        @param items_data(tuple): must contain:
-            - item (domish.Element)
-            - access_model (unicode)
+        @param items_data: must contain:
+            - item
+            - access_model
             - access_list (dict as returned getItemsById, or item_config)
         @param node(LeafNode): node hosting the items
-        @param subscription(pubsub.Subscription, None): TODO
+        @param subscription: TODO
 
-        @return (tuple): will contain:
+        @return: will contain:
             - notifications_filtered
             - node_owner_jid
             - items_data
         """
         if subscription is None:
-            notifications = yield self.backend.getNotifications(node, items_data)
+            notifications = await self.backend.getNotifications(node, items_data)
         else:
             notifications = [(subscription.subscriber, [subscription], items_data)]
 
-        if pep and node.getConfiguration()[const.OPT_ACCESS_MODEL] in ('open', 'presence'):
+        if ((pep
+             and node.getConfiguration()[const.OPT_ACCESS_MODEL] in ('open', 'presence')
+           )):
             # for PEP we need to manage automatic subscriptions (cf. XEP-0163 ยง4)
             explicit_subscribers = {subscriber for subscriber, _, _ in notifications}
-            auto_subscribers = yield self.backend.privilege.getAutoSubscribers(recipient, node.nodeIdentifier, explicit_subscribers)
+            auto_subscribers = await self.backend.privilege.getAutoSubscribers(
+                recipient, node.nodeIdentifier, explicit_subscribers
+            )
             for sub_jid in auto_subscribers:
                  sub = pubsub.Subscription(node.nodeIdentifier, sub_jid, 'subscribed')
                  notifications.append((sub_jid, [sub], items_data))
 
-        owners = yield node.getOwners()
+        owners = await node.getOwners()
         owner_roster = None
 
         # now we check access of subscriber for each item, and keep only allowed ones
@@ -1585,7 +1615,7 @@
                 elif access_model == const.VAL_AMODEL_PUBLISHER_ROSTER:
                     if owner_roster is None:
                         # FIXME: publisher roster should be used, not owner
-                        owner_roster= yield self.backend.getOwnerRoster(node, owners)
+                        owner_roster= await self.backend.getOwnerRoster(node, owners)
                     if owner_roster is None:
                         owner_roster = {}
                     if not subscriber_bare in owner_roster:
@@ -1601,7 +1631,7 @@
             if allowed_items:
                 notifications_filtered.append((subscriber, subscriptions, allowed_items))
 
-        defer.returnValue((owners, notifications_filtered))
+        return (owners, notifications_filtered)
 
     async def _aNotifyDelete(self, data):
         nodeIdentifier = data['node'].nodeIdentifier
@@ -1753,7 +1783,7 @@
 
     def _publish_errb(self, failure, request):
         if failure.type == error.NodeNotFound and self.backend.supportsAutoCreate():
-            print("Auto-creating node %s" % (request.nodeIdentifier,))
+            print(f"Auto-creating node {request.nodeIdentifier}")
             d = self.backend.createNode(request.nodeIdentifier,
                                         request.sender,
                                         request.options,