comparison 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
comparison
equal deleted inserted replaced
249:aaf5e34ff765 250:eb14b8d30cba
71 from twisted.internet import defer, reactor 71 from twisted.internet import defer, reactor
72 from twisted.words.protocols.jabber.error import StanzaError 72 from twisted.words.protocols.jabber.error import StanzaError
73 from twisted.words.protocols.jabber.jid import JID, InvalidFormat 73 from twisted.words.protocols.jabber.jid import JID, InvalidFormat
74 from twisted.words.xish import utility 74 from twisted.words.xish import utility
75 75
76 from wokkel import disco 76 from wokkel import disco, data_form
77 from wokkel.iwokkel import IPubSubResource 77 from wokkel.iwokkel import IPubSubResource
78 from wokkel.pubsub import PubSubResource, PubSubError 78 from wokkel.pubsub import PubSubResource, PubSubError
79 79
80 from sat_pubsub import error, iidavoll 80 from sat_pubsub import error, iidavoll, const
81 from sat_pubsub.iidavoll import IBackendService, ILeafNode 81 from sat_pubsub.iidavoll import IBackendService, ILeafNode
82
82 83
83 def _getAffiliation(node, entity): 84 def _getAffiliation(node, entity):
84 d = node.getAffiliation(entity) 85 d = node.getAffiliation(entity)
85 d.addCallback(lambda affiliation: (node, affiliation)) 86 d.addCallback(lambda affiliation: (node, affiliation))
86 return d 87 return d
139 140
140 def supportsPublisherAffiliation(self): 141 def supportsPublisherAffiliation(self):
141 return True 142 return True
142 143
143 144
145 def supportsGroupBlog(self):
146 return True
147
148
144 def supportsOutcastAffiliation(self): 149 def supportsOutcastAffiliation(self):
145 return True 150 return True
146 151
147 152
148 def supportsPersistentItems(self): 153 def supportsPersistentItems(self):
186 191
187 d = node.getAffiliation(requestor) 192 d = node.getAffiliation(requestor)
188 d.addCallback(check, node) 193 d.addCallback(check, node)
189 return d 194 return d
190 195
196 def parseItemConfig(self, item):
197 """Get and remove item configuration information
198 @param item:
199 """
200 item_config = None
201 access_model = const.VAL_DEFAULT
202 for i in range(len(item.children)):
203 elt = item.children[i]
204 if not (elt.uri,elt.name)==(data_form.NS_X_DATA,'x'):
205 continue
206 form = data_form.Form.fromElement(elt)
207 if (form.formNamespace == const.NS_ITEM_CONFIG):
208 item_config = form
209 del item.children[i] #we need to remove the config from item
210 break
211
212 if item_config:
213 access_model = item_config.get(const.OPT_ACCESS_MODEL, const.VAL_DEFAULT)
214
215 return (access_model, item_config)
216
191 217
192 def publish(self, nodeIdentifier, items, requestor): 218 def publish(self, nodeIdentifier, items, requestor):
193 d = self.storage.getNode(nodeIdentifier) 219 d = self.storage.getNode(nodeIdentifier)
194 d.addCallback(self._checkAuth, requestor) 220 d.addCallback(self._checkAuth, requestor)
221 #FIXME: owner and publisher are not necessarly the same. So far we use only owner to get roster.
222 #FIXME: in addition, there can be several owners: that is not managed yet
195 d.addCallback(self._doPublish, items, requestor) 223 d.addCallback(self._doPublish, items, requestor)
196 return d 224 return d
197 225
198 226
199 def _doPublish(self, node, items, requestor): 227 def _doPublish(self, node, items, requestor):
207 if items and not persistItems and not deliverPayloads: 235 if items and not persistItems and not deliverPayloads:
208 raise error.ItemForbidden() 236 raise error.ItemForbidden()
209 elif not items and (persistItems or deliverPayloads): 237 elif not items and (persistItems or deliverPayloads):
210 raise error.ItemRequired() 238 raise error.ItemRequired()
211 239
212 if persistItems or deliverPayloads: 240 parsed_items = []
213 for item in items: 241 for item in items:
242 if persistItems or deliverPayloads:
214 item.uri = None 243 item.uri = None
215 item.defaultUri = None 244 item.defaultUri = None
216 if not item.getAttribute("id"): 245 if not item.getAttribute("id"):
217 item["id"] = str(uuid.uuid4()) 246 item["id"] = str(uuid.uuid4())
247 access_model, item_config = self.parseItemConfig(item)
248 parsed_items.append((access_model, item_config, item))
218 249
219 if persistItems: 250 if persistItems:
220 d = node.storeItems(items, requestor) 251 d = node.storeItems(parsed_items, requestor)
221 else: 252 else:
222 d = defer.succeed(None) 253 d = defer.succeed(None)
223 254
224 d.addCallback(self._doNotify, node.nodeIdentifier, items, 255 d.addCallback(self._doNotify, node, parsed_items,
225 deliverPayloads) 256 deliverPayloads)
226 return d 257 return d
227 258
228 259
229 def _doNotify(self, result, nodeIdentifier, items, deliverPayloads): 260 def _doNotify(self, result, node, items, deliverPayloads):
230 if items and not deliverPayloads: 261 if items and not deliverPayloads:
231 for item in items: 262 for access_model, item_config, item in items:
232 item.children = [] 263 item.children = []
233 264
234 self.dispatch({'items': items, 'nodeIdentifier': nodeIdentifier}, 265 self.dispatch({'items': items, 'node': node},
235 '//event/pubsub/notify') 266 '//event/pubsub/notify')
236 267
237 268
238 def getNotifications(self, nodeIdentifier, items): 269 def getNotifications(self, nodeIdentifier, items):
239 270
282 return d 313 return d
283 314
284 315
285 def _doSubscribe(self, result, subscriber): 316 def _doSubscribe(self, result, subscriber):
286 node, affiliation = result 317 node, affiliation = result
318 #FIXME: must check node's access_model before subscribing
287 319
288 if affiliation == 'outcast': 320 if affiliation == 'outcast':
289 raise error.Forbidden() 321 raise error.Forbidden()
290 322
291 def trapExists(failure): 323 def trapExists(failure):
351 def createNode(self, nodeIdentifier, requestor): 383 def createNode(self, nodeIdentifier, requestor):
352 if not nodeIdentifier: 384 if not nodeIdentifier:
353 nodeIdentifier = 'generic/%s' % uuid.uuid4() 385 nodeIdentifier = 'generic/%s' % uuid.uuid4()
354 386
355 if self.supportsCreatorCheck(): 387 if self.supportsCreatorCheck():
388 groupblog = nodeIdentifier.startswith(const.NS_GROUPBLOG_PREFIX)
356 try: 389 try:
357 nodeIdentifierJID = JID(nodeIdentifier) 390 nodeIdentifierJID = JID(nodeIdentifier[len(const.NS_GROUPBLOG_PREFIX):] if groupblog else nodeIdentifier)
358 except InvalidFormat: 391 except InvalidFormat:
359 is_user_jid = False 392 is_user_jid = False
360 else: 393 else:
361 is_user_jid = bool(nodeIdentifierJID.user) 394 is_user_jid = bool(nodeIdentifierJID.user)
362 395
665 self.features.append("persistent-items") 698 self.features.append("persistent-items")
666 699
667 if self.backend.supportsPublisherAffiliation(): 700 if self.backend.supportsPublisherAffiliation():
668 self.features.append("publisher-affiliation") 701 self.features.append("publisher-affiliation")
669 702
703 if self.backend.supportsGroupBlog():
704 self.features.append("groupblog")
705
670 706
671 def _notify(self, data): 707 def _notify(self, data):
672 items = data['items'] 708 items = data['items']
673 nodeIdentifier = data['nodeIdentifier'] 709 node = data['node']
710
711 def _notifyAllowed(result):
712 """Check access of subscriber for each item,
713 and notify only allowed ones"""
714 notifications, roster = result
715
716 #we filter items not allowed for the subscribers
717 notifications_filtered = []
718
719 for subscriber, subscriptions, items in notifications:
720 allowed_items = [] #we keep only item which subscriber can access
721
722 for access_model, item_config, item in items:
723 if access_model == 'open':
724 allowed_items.append(item)
725 elif access_model == 'roster':
726 _subscriber = subscriber.userhost()
727 if not _subscriber in roster:
728 continue
729 #the subscriber is known, is he in the right group ?
730 authorized_groups = item_config[const.OPT_ROSTER_GROUPS_ALLOWED]
731 if roster[_subscriber].groups.intersection(authorized_groups):
732 allowed_items.append(item)
733
734 else: #unknown access_model
735 raise NotImplementedError
736
737 notifications_filtered.append((subscriber, subscriptions, allowed_items))
738
739 return self.pubsubService.notifyPublish(
740 self.serviceJID,
741 node.nodeIdentifier,
742 notifications_filtered)
743
744
674 if 'subscription' not in data: 745 if 'subscription' not in data:
675 d = self.backend.getNotifications(nodeIdentifier, items) 746 d1 = self.backend.getNotifications(node.nodeIdentifier, items)
676 else: 747 else:
677 subscription = data['subscription'] 748 subscription = data['subscription']
678 d = defer.succeed([(subscription.subscriber, [subscription], 749 d1 = defer.succeed([(subscription.subscriber, [subscription],
679 items)]) 750 items)])
680 d.addCallback(lambda notifications: self.pubsubService.notifyPublish( 751
681 self.serviceJID, 752 d2 = node.getNodeOwner()
682 nodeIdentifier, 753 d2.addCallback(self.backend.roster.getRoster)
683 notifications)) 754
755 d = defer.gatherResults([d1, d2])
756 d.addCallback(_notifyAllowed)
684 757
685 758
686 def _preDelete(self, data): 759 def _preDelete(self, data):
687 nodeIdentifier = data['nodeIdentifier'] 760 nodeIdentifier = data['nodeIdentifier']
688 redirectURI = data.get('redirectURI', None) 761 redirectURI = data.get('redirectURI', None)