Mercurial > libervia-pubsub
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 |