changeset 419:794593086517

backend: publish-options implementation: - removed some old code - new ConstraintFailed exception - publishing options implementation, following XEP-0060 §7.1.5 - first use of async/await syntax, used to simplify "publish" method
author Goffi <goffi@goffi.org>
date Sat, 28 Dec 2019 19:56:47 +0100
parents 89736353f6be
children 7a43c039c261
files sat_pubsub/backend.py sat_pubsub/error.py
diffstat 2 files changed, 60 insertions(+), 59 deletions(-) [+]
line wrap: on
line diff
--- a/sat_pubsub/backend.py	Mon Nov 18 20:49:38 2019 +0100
+++ b/sat_pubsub/backend.py	Sat Dec 28 19:56:47 2019 +0100
@@ -1,5 +1,4 @@
 #!/usr/bin/env python3
-#-*- coding: utf-8 -*-
 #
 # Copyright (c) 2012-2019 Jérôme Poisson
 # Copyright (c) 2013-2016 Adrien Cossa
@@ -70,7 +69,6 @@
 from twisted.python import components, log
 from twisted.internet import defer, reactor
 from twisted.words.protocols.jabber.error import StanzaError
-# from twisted.words.protocols.jabber.jid import JID, InvalidFormat
 from twisted.words.xish import domish, utility
 
 from wokkel import disco
@@ -209,6 +207,7 @@
 
     def supportsPublishOptions(self):
         return True
+
     def supportsPublisherAffiliation(self):
         return True
 
@@ -284,10 +283,11 @@
                             raise error.Forbidden()
                         return (affiliation, node)
 
-                    d.addCallback(lambda ignore: node.isSubscribed(requestor))
+                    d.addCallback(lambda __: node.isSubscribed(requestor))
                     d.addCallback(checkSubscription)
             elif publish_model != const.VAL_PMODEL_OPEN:
-                raise ValueError('Unexpected value') # publish_model must be publishers (default), subscribers or open.
+                # publish_model must be publishers (default), subscribers or open.
+                raise ValueError('Unexpected value')
 
             return d
 
@@ -395,17 +395,10 @@
         d.addCallback(doCheck)
         return d
 
-    def publish(self, nodeIdentifier, items, requestor, pep, recipient):
-        d = self.storage.getNode(nodeIdentifier, pep, recipient)
-        d.addCallback(self._checkAuth, requestor)
-        #FIXME: owner and publisher are not necessarly the same. So far we use only owner to get roster.
-        #FIXME: in addition, there can be several owners: that is not managed yet
-        d.addCallback(self._doPublish, items, requestor, pep, recipient)
-        return d
+    async def publish(self, nodeIdentifier, items, requestor, options, pep, recipient):
+        node = await self.storage.getNode(nodeIdentifier, pep, recipient)
+        affiliation, node = await self._checkAuth(node, requestor)
 
-    @defer.inlineCallbacks
-    def _doPublish(self, result, items, requestor, pep, recipient):
-        affiliation, node = result
         if node.nodeType == 'collection':
             raise error.NoPublishing()
 
@@ -413,6 +406,20 @@
         persistItems = configuration[const.OPT_PERSIST_ITEMS]
         deliverPayloads = configuration[const.OPT_DELIVER_PAYLOADS]
 
+        # we check that publish-options are satisfied
+        for field, value in options.items():
+            try:
+               current_value = configuration[field]
+            except KeyError:
+                raise error.ConstraintFailed(
+                    f"publish-options {field!r} doesn't exist in node configuration"
+                )
+            if current_value != value:
+                raise error.ConstraintFailed(
+                    f"configuration field {field!r} has a value of {current_value!r} "
+                    f"which doesn't fit publish-options expected {value!r}"
+                )
+
         if items and not persistItems and not deliverPayloads:
             raise error.ItemForbidden()
         elif not items and (persistItems or deliverPayloads):
@@ -428,7 +435,7 @@
                 item.uri = None
                 item.defaultUri = None
                 if not item.getAttribute("id"):
-                    item["id"] = yield node.getNextId()
+                    item["id"] = await node.getNextId()
                     new_item = True
                     if ret_payload is None:
                         ret_pubsub_elt = domish.Element((pubsub.NS_PUBSUB, 'pubsub'))
@@ -456,7 +463,7 @@
 
                 if affiliation == 'owner' or self.isAdmin(requestor):
                     if configuration[const.OPT_CONSISTENT_PUBLISHER]:
-                        pub_map = yield node.getItemsPublishers(itemIdentifiers)
+                        pub_map = await node.getItemsPublishers(itemIdentifiers)
                         if pub_map:
                             # if we have existing items, we replace publishers with
                             # original one to stay consistent
@@ -476,13 +483,13 @@
                 else:
                     # we don't want a publisher to overwrite the item
                     # of an other publisher
-                    yield self._checkOverwrite(node, itemIdentifiers, requestor)
+                    await self._checkOverwrite(node, itemIdentifiers, requestor)
 
             # TODO: check conflict and recalculate max id if serial_ids is set
-            yield node.storeItems(items_data, requestor)
+            await node.storeItems(items_data, requestor)
 
-        yield self._doNotify(node, items_data, deliverPayloads, pep, recipient)
-        defer.returnValue(ret_payload)
+        self._doNotify(node, items_data, deliverPayloads, pep, recipient)
+        return ret_payload
 
     def _doNotify(self, node, items_data, deliverPayloads, pep, recipient):
         if items_data and not deliverPayloads:
@@ -624,33 +631,16 @@
     def supportsAutoCreate(self):
         return True
 
-    def supportsCreatorCheck(self):
-        return True
-
     def supportsInstantNodes(self):
         return True
 
-    def createNode(self, nodeIdentifier, requestor, options = None, pep=False, recipient=None):
+    def createNode(self, nodeIdentifier, requestor, options=None, pep=False, recipient=None):
         if not nodeIdentifier:
             nodeIdentifier = 'generic/%s' % uuid.uuid4()
 
         if not options:
             options = {}
 
-        # if self.supportsCreatorCheck():
-        #     groupblog = nodeIdentifier.startswith(const.NS_GROUPBLOG_PREFIX)
-        #     try:
-        #         nodeIdentifierJID = JID(nodeIdentifier[len(const.NS_GROUPBLOG_PREFIX):] if groupblog else nodeIdentifier)
-        #     except InvalidFormat:
-        #         is_user_jid = False
-        #     else:
-        #         is_user_jid = bool(nodeIdentifierJID.user)
-
-        #     if is_user_jid and nodeIdentifierJID.userhostJID() != requestor.userhostJID():
-        #         #we have an user jid node, but not created by the owner of this jid
-        #         print "Wrong creator"
-        #         raise error.Forbidden()
-
         nodeType = 'leaf'
         config = self.storage.getDefaultConfiguration(nodeType)
         config['pubsub#node_type'] = nodeType
@@ -1309,6 +1299,7 @@
         error.NoPublishing: ('feature-not-implemented',
                              'unsupported',
                              'publish'),
+        error.ConstraintFailed: ('conflict', 'precondition-not-met', None),
     }
 
     def __init__(self, backend):
@@ -1321,11 +1312,6 @@
         self.backend.registerRetractNotifier(self._notifyRetract)
         self.backend.registerPreDelete(self._preDelete)
 
-        # FIXME: to be removed, it's not useful anymore as PEP is now used
-        # if self.backend.supportsCreatorCheck():
-        #     self.features.append("creator-jid-check")  #SàT custom feature: Check that a node (which correspond to
-                                                       #                    a jid in this server) is created by the right jid
-
         if self.backend.supportsAutoCreate():
             self.features.append("auto-create")
 
@@ -1347,9 +1333,10 @@
         if self.backend.supportsGroupBlog():
             self.features.append("groupblog")
 
-
-        # if self.backend.supportsPublishModel():       #XXX: this feature is not really described in XEP-0060, we just can see it in examples
-        #     self.features.append("publish_model")     #     but it's necessary for microblogging comments (see XEP-0277)
+        # XXX: this feature is not really described in XEP-0060, we just can see it in
+        #      examples but it's necessary for microblogging comments (see XEP-0277)
+        if self.backend.supportsPublishModel():
+            self.features.append("publish_model")
 
     def getFullItem(self, item_data):
         """ Attach item configuration to this item
@@ -1607,16 +1594,20 @@
             print("Auto-creating node %s" % (request.nodeIdentifier,))
             d = self.backend.createNode(request.nodeIdentifier,
                                         request.sender,
+                                        request.options,
                                         pep=self._isPep(request),
                                         recipient=request.recipient)
-            d.addCallback(lambda ignore,
-                                 request: self.backend.publish(request.nodeIdentifier,
-                                                               request.items,
-                                                               request.sender,
-                                                               self._isPep(request),
-                                                               request.recipient,
-                                                              ),
-                          request)
+            d.addCallback(
+                lambda __, request: defer.ensureDeferred(self.backend.publish(
+                    request.nodeIdentifier,
+                    request.items,
+                    request.sender,
+                    request.options,
+                    self._isPep(request),
+                    request.recipient,
+                )),
+                request,
+            )
             return d
 
         return failure
@@ -1628,11 +1619,14 @@
             return False
 
     def publish(self, request):
-        d = self.backend.publish(request.nodeIdentifier,
-                                 request.items,
-                                 request.sender,
-                                 self._isPep(request),
-                                 request.recipient)
+        d = defer.ensureDeferred(self.backend.publish(
+            request.nodeIdentifier,
+            request.items,
+            request.sender,
+            request.options,
+            self._isPep(request),
+            request.recipient
+        ))
         d.addErrback(self._publish_errb, request)
         return d.addErrback(self._mapErrors)
 
--- a/sat_pubsub/error.py	Mon Nov 18 20:49:38 2019 +0100
+++ b/sat_pubsub/error.py	Sat Dec 28 19:56:47 2019 +0100
@@ -148,5 +148,12 @@
     This node does not support publishing.
     """
 
+
 class BadAccessTypeError(Error):
     pass
+
+
+class ConstraintFailed(Error):
+    """
+    A requirement is not fulfilled
+    """