diff sat_pubsub/backend.py @ 250:eb14b8d30cba

fine tuning per-item permissions
author Goffi <goffi@goffi.org>
date Sun, 24 Jun 2012 19:35:49 +0200
parents 50f6ee966da8
children 0a7d43b3dad6
line wrap: on
line diff
--- a/sat_pubsub/backend.py	Fri Jun 08 18:34:45 2012 +0200
+++ b/sat_pubsub/backend.py	Sun Jun 24 19:35:49 2012 +0200
@@ -73,13 +73,14 @@
 from twisted.words.protocols.jabber.jid import JID, InvalidFormat
 from twisted.words.xish import utility
 
-from wokkel import disco
+from wokkel import disco, data_form
 from wokkel.iwokkel import IPubSubResource
 from wokkel.pubsub import PubSubResource, PubSubError
 
-from sat_pubsub import error, iidavoll
+from sat_pubsub import error, iidavoll, const
 from sat_pubsub.iidavoll import IBackendService, ILeafNode
 
+
 def _getAffiliation(node, entity):
     d = node.getAffiliation(entity)
     d.addCallback(lambda affiliation: (node, affiliation))
@@ -141,6 +142,10 @@
         return True
 
 
+    def supportsGroupBlog(self):
+        return True
+
+
     def supportsOutcastAffiliation(self):
         return True
 
@@ -188,10 +193,33 @@
         d.addCallback(check, node)
         return d
 
+    def parseItemConfig(self, item):
+        """Get and remove item configuration information
+        @param item:
+        """
+        item_config = None
+        access_model = const.VAL_DEFAULT
+        for i in range(len(item.children)):
+            elt = item.children[i]
+            if not (elt.uri,elt.name)==(data_form.NS_X_DATA,'x'):
+                continue
+            form = data_form.Form.fromElement(elt)
+            if (form.formNamespace == const.NS_ITEM_CONFIG):
+                item_config = form
+                del item.children[i] #we need to remove the config from item
+                break
+
+        if item_config:
+            access_model = item_config.get(const.OPT_ACCESS_MODEL, const.VAL_DEFAULT)
+
+        return (access_model, item_config)
+
 
     def publish(self, nodeIdentifier, items, requestor):
         d = self.storage.getNode(nodeIdentifier)
         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)
         return d
 
@@ -209,29 +237,32 @@
         elif not items and (persistItems or deliverPayloads):
             raise error.ItemRequired()
 
-        if persistItems or deliverPayloads:
-            for item in items:
+        parsed_items = []
+        for item in items:
+            if persistItems or deliverPayloads:
                 item.uri = None
                 item.defaultUri = None
                 if not item.getAttribute("id"):
                     item["id"] = str(uuid.uuid4())
+            access_model, item_config = self.parseItemConfig(item)
+            parsed_items.append((access_model, item_config, item)) 
 
         if persistItems:
-            d = node.storeItems(items, requestor)
+            d = node.storeItems(parsed_items, requestor)
         else:
             d = defer.succeed(None)
 
-        d.addCallback(self._doNotify, node.nodeIdentifier, items,
+        d.addCallback(self._doNotify, node, parsed_items,
                       deliverPayloads)
         return d
 
 
-    def _doNotify(self, result, nodeIdentifier, items, deliverPayloads):
+    def _doNotify(self, result, node, items, deliverPayloads):
         if items and not deliverPayloads:
-            for item in items:
+            for access_model, item_config, item in items:
                 item.children = []
 
-        self.dispatch({'items': items, 'nodeIdentifier': nodeIdentifier},
+        self.dispatch({'items': items, 'node': node},
                       '//event/pubsub/notify')
 
 
@@ -284,6 +315,7 @@
 
     def _doSubscribe(self, result, subscriber):
         node, affiliation = result
+        #FIXME: must check node's access_model before subscribing
 
         if affiliation == 'outcast':
             raise error.Forbidden()
@@ -353,8 +385,9 @@
             nodeIdentifier = 'generic/%s' % uuid.uuid4()
 
         if self.supportsCreatorCheck():
+            groupblog = nodeIdentifier.startswith(const.NS_GROUPBLOG_PREFIX)
             try:
-                nodeIdentifierJID = JID(nodeIdentifier)
+                nodeIdentifierJID = JID(nodeIdentifier[len(const.NS_GROUPBLOG_PREFIX):] if groupblog else nodeIdentifier)
             except InvalidFormat:
                 is_user_jid = False
             else:
@@ -667,20 +700,60 @@
         if self.backend.supportsPublisherAffiliation():
             self.features.append("publisher-affiliation")
 
+        if self.backend.supportsGroupBlog():
+            self.features.append("groupblog")
+
 
     def _notify(self, data):
         items = data['items']
-        nodeIdentifier = data['nodeIdentifier']
+        node = data['node']
+        
+        def _notifyAllowed(result):
+            """Check access of subscriber for each item,
+            and notify only allowed ones"""
+            notifications, roster = result
+            
+            #we filter items not allowed for the subscribers
+            notifications_filtered = []
+
+            for subscriber, subscriptions, items in notifications:
+                allowed_items = [] #we keep only item which subscriber can access
+
+                for access_model, item_config, item in items:
+                    if access_model == 'open':
+                        allowed_items.append(item)
+                    elif access_model == 'roster':
+                        _subscriber = subscriber.userhost()
+                        if not _subscriber in roster:
+                            continue
+                        #the subscriber is known, is he in the right group ?
+                        authorized_groups = item_config[const.OPT_ROSTER_GROUPS_ALLOWED]
+                        if roster[_subscriber].groups.intersection(authorized_groups):
+                            allowed_items.append(item)
+                            
+                    else: #unknown access_model
+                        raise NotImplementedError
+
+                notifications_filtered.append((subscriber, subscriptions, allowed_items))
+            
+            return self.pubsubService.notifyPublish(
+                                                self.serviceJID,
+                                                node.nodeIdentifier,
+                                                notifications_filtered)
+
+        
         if 'subscription' not in data:
-            d = self.backend.getNotifications(nodeIdentifier, items)
+            d1 = self.backend.getNotifications(node.nodeIdentifier, items)
         else:
             subscription = data['subscription']
-            d = defer.succeed([(subscription.subscriber, [subscription],
+            d1 = defer.succeed([(subscription.subscriber, [subscription],
                                 items)])
-        d.addCallback(lambda notifications: self.pubsubService.notifyPublish(
-                                                self.serviceJID,
-                                                nodeIdentifier,
-                                                notifications))
+        
+        d2 = node.getNodeOwner()
+        d2.addCallback(self.backend.roster.getRoster)
+
+        d = defer.gatherResults([d1, d2])
+        d.addCallback(_notifyAllowed)
 
 
     def _preDelete(self, data):