Mercurial > libervia-pubsub
diff 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 |
line wrap: on
line diff
--- a/sat_pubsub/backend.py Sun Aug 27 20:26:38 2017 +0200 +++ b/sat_pubsub/backend.py Sun Aug 27 20:33:39 2017 +0200 @@ -190,9 +190,21 @@ d.addCallback(lambda node: node.getType()) return d + def _getNodesIds(self, subscribed, pep, recipient): + # TODO: filter whitelist nodes + # TODO: handle publisher-roster (should probably be renamed to owner-roster for nodes) + if not subscribed: + allowed_accesses = {'open', 'whitelist'} + else: + allowed_accesses = {'open', 'presence', 'whitelist'} + return self.storage.getNodeIds(pep, recipient, allowed_accesses) - def getNodes(self, pep): - return self.storage.getNodeIds(pep) + def getNodes(self, requestor, pep, recipient): + if pep: + d = self.privilege.isSubscribedFrom(requestor, recipient) + d.addCallback(self._getNodesIds, pep, recipient) + return d + return self.storage.getNodeIds(pep, recipient) def getNodeMetaData(self, nodeIdentifier, pep, recipient=None): @@ -661,15 +673,75 @@ d.addErrback(self.unwrapFirstError) return d + @defer.inlineCallbacks + def checkNodeAccess(self, node, requestor): + """check if a requestor can access data of a node - def getItemsIds(self, nodeIdentifier, authorized_groups, unrestricted, maxItems=None, ext_data=None, pep=False, recipient=None): - d = self.storage.getNode(nodeIdentifier, pep, recipient) - d.addCallback(lambda node: node.getItemsIds(authorized_groups, - unrestricted, - maxItems, - ext_data)) - return d + @param node(Node): node to check + @param requestor(jid.JID): entity who want to access node + @return (tuple): permissions data with: + - owner(bool): True if requestor is owner of the node + - roster(None, ): roster of the requestor + None if not needed/available + - access_model(str): access model of the node + @raise error.Forbidden: access is not granted + @raise error.NotLeafNodeError: this node is not a leaf + """ + node, affiliation = yield _getAffiliation(node, requestor) + + if not iidavoll.ILeafNode.providedBy(node): + raise error.NotLeafNodeError() + + if affiliation == 'outcast': + raise error.Forbidden() + + # node access check + owner = affiliation == 'owner' + access_model = node.getAccessModel() + roster = None + + if access_model == const.VAL_AMODEL_OPEN or owner: + pass + elif access_model == const.VAL_AMODEL_PUBLISHER_ROSTER: + # FIXME: publisher roster should be used, not owner + roster = yield self.getOwnerRoster(node) + + if roster is None: + raise error.Forbidden() + if requestor not in roster: + raise error.Forbidden() + + authorized_groups = yield node.getAuthorizedGroups() + + if not roster[requestor].groups.intersection(authorized_groups): + # requestor is in roster but not in one of the allowed groups + raise error.Forbidden() + elif access_model == const.VAL_AMODEL_WHITELIST: + affiliations = yield node.getAffiliations() + try: + affiliation = affiliations[requestor.userhostJID()] + except KeyError: + raise error.Forbidden() + else: + if affiliation not in ('owner', 'publisher', 'member'): + raise error.Forbidden() + else: + raise Exception(u"Unknown access_model") + + defer.returnValue((affiliation, owner, roster, access_model)) + + @defer.inlineCallbacks + def getItemsIds(self, nodeIdentifier, requestor, authorized_groups, unrestricted, maxItems=None, ext_data=None, pep=False, recipient=None): + # FIXME: items access model are not checked + # TODO: check items access model + node = yield self.storage.getNode(nodeIdentifier, pep, recipient) + affiliation, owner, roster, access_model = yield self.checkNodeAccess(node, requestor) + ids = yield node.getItemsIds(authorized_groups, + unrestricted, + maxItems, + ext_data) + defer.returnValue(ids) def getItems(self, nodeIdentifier, requestor, recipient, maxItems=None, itemIdentifiers=None, ext_data=None): @@ -679,6 +751,7 @@ @defer.inlineCallbacks def getOwnerRoster(self, node, owners=None): + # FIXME: roster of publisher, not owner, must be used if owners is None: owners = yield node.getOwners() @@ -708,48 +781,11 @@ if ext_data is None: ext_data = {} node = yield self.storage.getNode(nodeIdentifier, ext_data.get('pep', False), recipient) - node, affiliation = yield _getAffiliation(node, requestor) - - if not iidavoll.ILeafNode.providedBy(node): + try: + affiliation, owner, roster, access_model = yield self.checkNodeAccess(node, requestor) + except error.NotLeafNodeError: defer.returnValue([]) - if affiliation == 'outcast': - raise error.Forbidden() - - - # node access check - owner = affiliation == 'owner' - access_model = node.getAccessModel() - roster = None - - if access_model == const.VAL_AMODEL_OPEN or owner: - pass - elif access_model == const.VAL_AMODEL_PUBLISHER_ROSTER: - roster = yield self.getOwnerRoster(node) - - if roster is None: - raise error.Forbidden() - - if requestor not in roster: - raise error.Forbidden() - - authorized_groups = yield node.getAuthorizedGroups() - - if not roster[requestor].groups.intersection(authorized_groups): - # requestor is in roster but not in one of the allowed groups - raise error.Forbidden() - elif access_model == const.VAL_AMODEL_WHITELIST: - affiliations = yield node.getAffiliations() - try: - affiliation = affiliations[requestor.userhostJID()] - except KeyError: - raise error.Forbidden() - else: - if affiliation not in ('owner', 'publisher', 'member'): - raise error.Forbidden() - else: - raise Exception(u"Unknown access_model") - # at this point node access is checked if owner: @@ -757,6 +793,7 @@ requestor_groups = None else: if roster is None: + # FIXME: publisher roster should be used, not owner roster = yield self.getOwnerRoster(node) if roster is None: roster = {} @@ -1215,6 +1252,7 @@ allowed_items.append(item) elif access_model == const.VAL_AMODEL_PUBLISHER_ROSTER: if owner_roster is None: + # FIXME: publisher roster should be used, not owner owner_roster= yield self.getOwnerRoster(node, owners) if owner_roster is None: owner_roster = {} @@ -1286,6 +1324,12 @@ return d def getNodes(self, requestor, service, nodeIdentifier): + """return nodes for disco#items + + Pubsub/PEP nodes will be returned if disco node is not specified + else Pubsub/PEP items will be returned + (according to what requestor can access) + """ try: pep = service.pep except AttributeError: @@ -1296,15 +1340,20 @@ if nodeIdentifier: d = self.backend.getItemsIds(nodeIdentifier, + requestor, [], requestor.userhostJID() == service, None, None, pep, service) + # items must be set as name, not node + d.addCallback(lambda items: [(None, item) for item in items]) else: - d = self.backend.getNodes(pep) + d = self.backend.getNodes(requestor.userhostJID(), + pep, + service) return d.addErrback(self._mapErrors)