# HG changeset patch # User Goffi # Date 1547402179 -3600 # Node ID 77b52dbda89aca8a1efd99765d04d2f019b3a390 # Parent 7c490bb608477b4809e38e9bbc453e35c0b23704 pubsub_admin: Pubsub Admin experimental protocol first draft: This protocol allows an admin to publish items where the publisher is specified. diff -r 7c490bb60847 -r 77b52dbda89a src/pubsub_admin.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pubsub_admin.py Sun Jan 13 18:56:19 2019 +0100 @@ -0,0 +1,125 @@ +#!/usr/bin/python +#-*- coding: utf-8 -*- + +# Copyright (c) 2019 Jérôme Poisson +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +""" +Pubsub Admin experimental protocol implementation + +""" + +from zope.interface import implements +from twisted.python import log +from twisted.internet import defer +from twisted.words.protocols.jabber import jid, error, xmlstream +from wokkel.subprotocols import XMPPHandler +from wokkel import disco, iwokkel, pubsub + +NS_PUBSUB_ADMIN = u"https://salut-a-toi.org/spec/pubsub_admin:0" +ADMIN_REQUEST = '/iq[@type="set"]/admin[@xmlns="{}"]'.format(NS_PUBSUB_ADMIN) + + +class PubsubAdminHandler(XMPPHandler): + implements(iwokkel.IDisco) + + def __init__(self, backend): + super(PubsubAdminHandler, self).__init__() + self.backend = backend + + def connectionInitialized(self): + self.xmlstream.addObserver(ADMIN_REQUEST, self.onAdminRequest) + + def sendError(self, iq_elt, condition=u'bad-request'): + stanza_error = error.StanzaError(u'bad-request') + iq_error = stanza_error.toResponse(iq_elt) + self.parent.xmlstream.send(iq_error) + + @defer.inlineCallbacks + def onAdminRequest(self, iq_elt): + """Pubsub Admin request received""" + iq_elt.handled = True + try: + pep = bool(iq_elt.delegated) + except AttributeError: + pep = False + + # is the sender really an admin? + admins = self.backend.config[u'admins_jids_list'] + from_jid = jid.JID(iq_elt[u'from']) + if from_jid.userhostJID() not in admins: + log.msg("WARNING: admin request done by non admin entity {from_jid}" + .format(from_jid=from_jid.full())) + self.sendError(iq_elt, u'forbidden') + return + + # alright, we can proceed + recipient = jid.JID(iq_elt[u'to']) + admin_elt = iq_elt.admin + try: + pubsub_elt = next(admin_elt.elements(pubsub.NS_PUBSUB, u'pubsub')) + publish_elt = next(pubsub_elt.elements(pubsub.NS_PUBSUB, u'publish')) + except StopIteration: + self.sendError(iq_elt) + return + try: + node = publish_elt[u'node'] + except KeyError: + self.sendError(iq_elt) + return + + # we prepare the result IQ request, we will fill it with item ids + iq_result_elt = xmlstream.toResponse(iq_elt, u'result') + result_admin_elt = iq_result_elt.addElement((NS_PUBSUB_ADMIN, u'admin')) + result_pubsub_elt = result_admin_elt.addElement((pubsub.NS_PUBSUB, u'pubsub')) + result_publish_elt = result_pubsub_elt.addElement(u'publish') + result_publish_elt[u'node'] = node + + # now we can send the items + for item in publish_elt.elements(pubsub.NS_PUBSUB, u'item'): + try: + requestor = jid.JID(item.attributes.pop(u'publisher')) + except Exception as e: + log.msg(u"WARNING: invalid jid in publisher ({requestor}): {msg}" + .format(requestor=requestor, msg=e)) + self.sendError(iq_elt) + return + except KeyError: + requestor = from_jid + + # we don't use a DeferredList because we want to be sure that + # each request is done in order + payload = yield self.backend.publish( + nodeIdentifier=node, + items=[item], + requestor=requestor, + pep=pep, + recipient=recipient) + + result_item_elt = result_publish_elt.addElement(u'item') + # either the id was given and it is available in item + # either it's a new item, and we can retrieve it from return payload + try: + result_item_elt[u'id'] = item[u'id'] + except KeyError: + result_item_elt = payload.publish.item[u'id'] + + self.xmlstream.send(iq_result_elt) + + def getDiscoInfo(self, requestor, service, nodeIdentifier=''): + return [disco.DiscoFeature(NS_PUBSUB_ADMIN)] + + def getDiscoItems(self, requestor, service, nodeIdentifier=''): + return [] diff -r 7c490bb60847 -r 77b52dbda89a src/twisted/plugins/pubsub.py --- a/src/twisted/plugins/pubsub.py Sun Jan 13 18:56:19 2019 +0100 +++ b/src/twisted/plugins/pubsub.py Sun Jan 13 18:56:19 2019 +0100 @@ -71,6 +71,7 @@ from sat_pubsub import const from sat_pubsub import mam as pubsub_mam +from sat_pubsub import pubsub_admin from sat_pubsub.backend import BackendService, ExtraDiscoHandler from sat_pubsub.schema import SchemaHandler from sat_pubsub.privilege import PrivilegesHandler @@ -251,6 +252,9 @@ mam_s.addFilter(data_form.Field(var=const.MAM_FILTER_CATEGORY)) mam_s.setHandlerParent(cs) + pa = pubsub_admin.PubsubAdminHandler(bs) + pa.setHandlerParent(cs) + sh = SchemaHandler() sh.setHandlerParent(cs)