comparison sat_pubsub/backend.py @ 349:20b82fb8de02

backend: check nodes/items permission on disco#items: - move node access check workflow from getItemsData to a new checkNodeAccess method - only accessible items are returned to an entity when doing a disco#items on a node - for PEP, nodes with presence access model are not returned if entity has not presence subscription from the node owner - all nodes are returned in normal pubsub service - new NotLeafNodeError exception when an action need to be done on Leaf node and it is not the case - /!\ access it not fully checked : items access models are not handled for items id in disco#items, and whitelist nodes are returned regardless if requestor is in the white list or not. Furthermore, publisher-roster access is not handled for nodes.
author Goffi <goffi@goffi.org>
date Sun, 27 Aug 2017 20:33:39 +0200
parents 3bbab2173ebc
children efbdca10f0fb
comparison
equal deleted inserted replaced
348:d1f63ae1eaf4 349:20b82fb8de02
188 # FIXME: manage pep and recipient 188 # FIXME: manage pep and recipient
189 d = self.storage.getNode(nodeIdentifier, pep, recipient) 189 d = self.storage.getNode(nodeIdentifier, pep, recipient)
190 d.addCallback(lambda node: node.getType()) 190 d.addCallback(lambda node: node.getType())
191 return d 191 return d
192 192
193 193 def _getNodesIds(self, subscribed, pep, recipient):
194 def getNodes(self, pep): 194 # TODO: filter whitelist nodes
195 return self.storage.getNodeIds(pep) 195 # TODO: handle publisher-roster (should probably be renamed to owner-roster for nodes)
196 if not subscribed:
197 allowed_accesses = {'open', 'whitelist'}
198 else:
199 allowed_accesses = {'open', 'presence', 'whitelist'}
200 return self.storage.getNodeIds(pep, recipient, allowed_accesses)
201
202 def getNodes(self, requestor, pep, recipient):
203 if pep:
204 d = self.privilege.isSubscribedFrom(requestor, recipient)
205 d.addCallback(self._getNodesIds, pep, recipient)
206 return d
207 return self.storage.getNodeIds(pep, recipient)
196 208
197 209
198 def getNodeMetaData(self, nodeIdentifier, pep, recipient=None): 210 def getNodeMetaData(self, nodeIdentifier, pep, recipient=None):
199 # FIXME: manage pep and recipient 211 # FIXME: manage pep and recipient
200 d = self.storage.getNode(nodeIdentifier, pep, recipient) 212 d = self.storage.getNode(nodeIdentifier, pep, recipient)
659 d = defer.gatherResults(d_list, consumeErrors=True) 671 d = defer.gatherResults(d_list, consumeErrors=True)
660 d.addCallback(lambda _: None) 672 d.addCallback(lambda _: None)
661 d.addErrback(self.unwrapFirstError) 673 d.addErrback(self.unwrapFirstError)
662 return d 674 return d
663 675
664
665 def getItemsIds(self, nodeIdentifier, authorized_groups, unrestricted, maxItems=None, ext_data=None, pep=False, recipient=None):
666 d = self.storage.getNode(nodeIdentifier, pep, recipient)
667 d.addCallback(lambda node: node.getItemsIds(authorized_groups,
668 unrestricted,
669 maxItems,
670 ext_data))
671 return d
672
673
674 def getItems(self, nodeIdentifier, requestor, recipient, maxItems=None,
675 itemIdentifiers=None, ext_data=None):
676 d = self.getItemsData(nodeIdentifier, requestor, recipient, maxItems, itemIdentifiers, ext_data)
677 d.addCallback(lambda items_data: [item_data.item for item_data in items_data])
678 return d
679
680 @defer.inlineCallbacks 676 @defer.inlineCallbacks
681 def getOwnerRoster(self, node, owners=None): 677 def checkNodeAccess(self, node, requestor):
682 if owners is None: 678 """check if a requestor can access data of a node
683 owners = yield node.getOwners() 679
684 680 @param node(Node): node to check
685 if len(owners) != 1: 681 @param requestor(jid.JID): entity who want to access node
686 log.msg('publisher-roster access is not allowed with more than 1 owner') 682 @return (tuple): permissions data with:
687 return 683 - owner(bool): True if requestor is owner of the node
688 684 - roster(None, ): roster of the requestor
689 owner_jid = owners[0] 685 None if not needed/available
690 686 - access_model(str): access model of the node
691 try: 687 @raise error.Forbidden: access is not granted
692 roster = yield self.privilege.getRoster(owner_jid) 688 @raise error.NotLeafNodeError: this node is not a leaf
693 except Exception as e: 689 """
694 log.msg("Error while getting roster of {owner_jid}: {msg}".format(
695 owner_jid = owner_jid.full(),
696 msg = e))
697 return
698 defer.returnValue(roster)
699
700 @defer.inlineCallbacks
701 def getItemsData(self, nodeIdentifier, requestor, recipient, maxItems=None,
702 itemIdentifiers=None, ext_data=None):
703 """like getItems but return the whole ItemData"""
704 if maxItems == 0:
705 log.msg("WARNING: maxItems=0 on items retrieval")
706 defer.returnValue([])
707
708 if ext_data is None:
709 ext_data = {}
710 node = yield self.storage.getNode(nodeIdentifier, ext_data.get('pep', False), recipient)
711 node, affiliation = yield _getAffiliation(node, requestor) 690 node, affiliation = yield _getAffiliation(node, requestor)
712 691
713 if not iidavoll.ILeafNode.providedBy(node): 692 if not iidavoll.ILeafNode.providedBy(node):
714 defer.returnValue([]) 693 raise error.NotLeafNodeError()
715 694
716 if affiliation == 'outcast': 695 if affiliation == 'outcast':
717 raise error.Forbidden() 696 raise error.Forbidden()
718
719 697
720 # node access check 698 # node access check
721 owner = affiliation == 'owner' 699 owner = affiliation == 'owner'
722 access_model = node.getAccessModel() 700 access_model = node.getAccessModel()
723 roster = None 701 roster = None
724 702
725 if access_model == const.VAL_AMODEL_OPEN or owner: 703 if access_model == const.VAL_AMODEL_OPEN or owner:
726 pass 704 pass
727 elif access_model == const.VAL_AMODEL_PUBLISHER_ROSTER: 705 elif access_model == const.VAL_AMODEL_PUBLISHER_ROSTER:
706 # FIXME: publisher roster should be used, not owner
728 roster = yield self.getOwnerRoster(node) 707 roster = yield self.getOwnerRoster(node)
729 708
730 if roster is None: 709 if roster is None:
731 raise error.Forbidden() 710 raise error.Forbidden()
732 711
748 if affiliation not in ('owner', 'publisher', 'member'): 727 if affiliation not in ('owner', 'publisher', 'member'):
749 raise error.Forbidden() 728 raise error.Forbidden()
750 else: 729 else:
751 raise Exception(u"Unknown access_model") 730 raise Exception(u"Unknown access_model")
752 731
732 defer.returnValue((affiliation, owner, roster, access_model))
733
734 @defer.inlineCallbacks
735 def getItemsIds(self, nodeIdentifier, requestor, authorized_groups, unrestricted, maxItems=None, ext_data=None, pep=False, recipient=None):
736 # FIXME: items access model are not checked
737 # TODO: check items access model
738 node = yield self.storage.getNode(nodeIdentifier, pep, recipient)
739 affiliation, owner, roster, access_model = yield self.checkNodeAccess(node, requestor)
740 ids = yield node.getItemsIds(authorized_groups,
741 unrestricted,
742 maxItems,
743 ext_data)
744 defer.returnValue(ids)
745
746 def getItems(self, nodeIdentifier, requestor, recipient, maxItems=None,
747 itemIdentifiers=None, ext_data=None):
748 d = self.getItemsData(nodeIdentifier, requestor, recipient, maxItems, itemIdentifiers, ext_data)
749 d.addCallback(lambda items_data: [item_data.item for item_data in items_data])
750 return d
751
752 @defer.inlineCallbacks
753 def getOwnerRoster(self, node, owners=None):
754 # FIXME: roster of publisher, not owner, must be used
755 if owners is None:
756 owners = yield node.getOwners()
757
758 if len(owners) != 1:
759 log.msg('publisher-roster access is not allowed with more than 1 owner')
760 return
761
762 owner_jid = owners[0]
763
764 try:
765 roster = yield self.privilege.getRoster(owner_jid)
766 except Exception as e:
767 log.msg("Error while getting roster of {owner_jid}: {msg}".format(
768 owner_jid = owner_jid.full(),
769 msg = e))
770 return
771 defer.returnValue(roster)
772
773 @defer.inlineCallbacks
774 def getItemsData(self, nodeIdentifier, requestor, recipient, maxItems=None,
775 itemIdentifiers=None, ext_data=None):
776 """like getItems but return the whole ItemData"""
777 if maxItems == 0:
778 log.msg("WARNING: maxItems=0 on items retrieval")
779 defer.returnValue([])
780
781 if ext_data is None:
782 ext_data = {}
783 node = yield self.storage.getNode(nodeIdentifier, ext_data.get('pep', False), recipient)
784 try:
785 affiliation, owner, roster, access_model = yield self.checkNodeAccess(node, requestor)
786 except error.NotLeafNodeError:
787 defer.returnValue([])
788
753 # at this point node access is checked 789 # at this point node access is checked
754 790
755 if owner: 791 if owner:
756 # requestor_groups is only used in restricted access 792 # requestor_groups is only used in restricted access
757 requestor_groups = None 793 requestor_groups = None
758 else: 794 else:
759 if roster is None: 795 if roster is None:
796 # FIXME: publisher roster should be used, not owner
760 roster = yield self.getOwnerRoster(node) 797 roster = yield self.getOwnerRoster(node)
761 if roster is None: 798 if roster is None:
762 roster = {} 799 roster = {}
763 roster_item = roster.get(requestor.userhostJID()) 800 roster_item = roster.get(requestor.userhostJID())
764 requestor_groups = tuple(roster_item.groups) if roster_item else tuple() 801 requestor_groups = tuple(roster_item.groups) if roster_item else tuple()
1213 access_list = item_data.config 1250 access_list = item_data.config
1214 if access_model == const.VAL_AMODEL_OPEN: 1251 if access_model == const.VAL_AMODEL_OPEN:
1215 allowed_items.append(item) 1252 allowed_items.append(item)
1216 elif access_model == const.VAL_AMODEL_PUBLISHER_ROSTER: 1253 elif access_model == const.VAL_AMODEL_PUBLISHER_ROSTER:
1217 if owner_roster is None: 1254 if owner_roster is None:
1255 # FIXME: publisher roster should be used, not owner
1218 owner_roster= yield self.getOwnerRoster(node, owners) 1256 owner_roster= yield self.getOwnerRoster(node, owners)
1219 if owner_roster is None: 1257 if owner_roster is None:
1220 owner_roster = {} 1258 owner_roster = {}
1221 if not subscriber_bare in owner_roster: 1259 if not subscriber_bare in owner_roster:
1222 continue 1260 continue
1284 d.addErrback(trapNotFound) 1322 d.addErrback(trapNotFound)
1285 d.addErrback(self._mapErrors) 1323 d.addErrback(self._mapErrors)
1286 return d 1324 return d
1287 1325
1288 def getNodes(self, requestor, service, nodeIdentifier): 1326 def getNodes(self, requestor, service, nodeIdentifier):
1327 """return nodes for disco#items
1328
1329 Pubsub/PEP nodes will be returned if disco node is not specified
1330 else Pubsub/PEP items will be returned
1331 (according to what requestor can access)
1332 """
1289 try: 1333 try:
1290 pep = service.pep 1334 pep = service.pep
1291 except AttributeError: 1335 except AttributeError:
1292 pep = False 1336 pep = False
1293 1337
1294 if service.resource: 1338 if service.resource:
1295 return defer.succeed([]) 1339 return defer.succeed([])
1296 1340
1297 if nodeIdentifier: 1341 if nodeIdentifier:
1298 d = self.backend.getItemsIds(nodeIdentifier, 1342 d = self.backend.getItemsIds(nodeIdentifier,
1343 requestor,
1299 [], 1344 [],
1300 requestor.userhostJID() == service, 1345 requestor.userhostJID() == service,
1301 None, 1346 None,
1302 None, 1347 None,
1303 pep, 1348 pep,
1304 service) 1349 service)
1350 # items must be set as name, not node
1351 d.addCallback(lambda items: [(None, item) for item in items])
1305 1352
1306 else: 1353 else:
1307 d = self.backend.getNodes(pep) 1354 d = self.backend.getNodes(requestor.userhostJID(),
1355 pep,
1356 service)
1308 return d.addErrback(self._mapErrors) 1357 return d.addErrback(self._mapErrors)
1309 1358
1310 1359
1311 def getConfigurationOptions(self): 1360 def getConfigurationOptions(self):
1312 return self.backend.nodeOptions 1361 return self.backend.nodeOptions