plugin invitation: pubsub invitations:

- new Pubsub invitation plugin, to have a generic way to manage invitation on Pubsub based
  features
- `invitePreflight` and `onInvitationPreflight` method can be implemented to customise
  invitation for a namespace
- refactored events invitations to use the new plugin
- a Pubsub invitation can now be for a whole node instead of a specific item
- if invitation is for a node, a namespace can be specified to indicate what this node is
  about. It is then added in `extra` data
- an element (domish.Element) can be added in `extra` data, it will then be added in the
  invitation
- some code modernisation
This commit is contained in:
Goffi 2021-02-19 15:50:22 +01:00
commit 795931373a

View file

@ -16,15 +16,18 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from typing import Optional
from zope.interface import implementer
from twisted.internet import defer
from twisted.words.protocols.jabber import jid
from twisted.words.protocols.jabber.xmlstream import XMPPHandler
from wokkel import disco, iwokkel
from sat.core.i18n import _
from sat.core import exceptions
from sat.core.constants import Const as C
from sat.core.log import getLogger
from twisted.internet import defer
from twisted.words.protocols.jabber import jid
from wokkel import disco, iwokkel
from zope.interface import implementer
from twisted.words.protocols.jabber.xmlstream import XMPPHandler
from sat.core.xmpp import SatXMPPEntity
from sat.tools import utils
log = getLogger(__name__)
@ -123,16 +126,25 @@ class Invitation(object):
invitation_elt['thumb_url'] = thumb_url
return mess_data, invitation_elt
def sendPubsubInvitation(self, client, invitee_jid, service, node,
item_id, name, extra):
def sendPubsubInvitation(
self,
client: SatXMPPEntity,
invitee_jid: jid.JID,
service: jid.JID,
node: str,
item_id: Optional[str],
name: Optional[str],
extra: Optional[dict]
) -> None:
"""Send an pubsub invitation in a <message> stanza
@param invitee_jid(jid.JID): entitee to send invitation to
@param service(jid.JID): pubsub service
@param node(unicode): pubsub node
@param item_id(unicode): pubsub id
@param name(unicode, None): see [_generateBaseInvitation]
@param extra(dict, None): see [_generateBaseInvitation]
@param invitee_jid: entitee to send invitation to
@param service: pubsub service
@param node: pubsub node
@param item_id: pubsub id
None when the invitation is for a whole node
@param name: see [_generateBaseInvitation]
@param extra: see [_generateBaseInvitation]
"""
if extra is None:
extra = {}
@ -141,8 +153,22 @@ class Invitation(object):
pubsub_elt = invitation_elt.addElement("pubsub")
pubsub_elt["service"] = service.full()
pubsub_elt["node"] = node
pubsub_elt["item"] = item_id
return client.send(mess_data["xml"])
if item_id is None:
try:
namespace = extra.pop("namespace")
except KeyError:
raise exceptions.DataError('"namespace" key is missing in "extra" data')
node_data_elt = pubsub_elt.addElement("node_data")
node_data_elt["namespace"] = namespace
try:
node_data_elt.addChild(extra["element"])
except KeyError:
pass
else:
pubsub_elt["item"] = item_id
if "element" in extra:
invitation_elt.addChild(extra.pop("element"))
client.send(mess_data["xml"])
async def sendFileSharingInvitation(
self, client, invitee_jid, service, repos_type=None, namespace=None, path=None,
@ -207,53 +233,61 @@ class Invitation(object):
file_sharing_elt["path"] = path
client.send(mess_data["xml"])
@defer.inlineCallbacks
def _parsePubsubElt(self, client, pubsub_elt):
async def _parsePubsubElt(self, client, pubsub_elt):
try:
service = jid.JID(pubsub_elt["service"])
node = pubsub_elt["node"]
item_id = pubsub_elt.getAttribute("item")
except (RuntimeError, KeyError):
log.warning(_("Bad invitation, ignoring"))
raise exceptions.DataError
raise exceptions.DataError("Bad invitation, ignoring")
try:
items, metadata = yield self._p.getItems(client, service, node,
item_ids=[item_id])
except Exception as e:
log.warning(_("Can't get item linked with invitation: {reason}").format(
reason=e))
try:
item_elt = items[0]
except IndexError:
log.warning(_("Invitation was linking to a non existing item"))
raise exceptions.DataError
item_id = pubsub_elt.getAttribute("item")
try:
namespace = item_elt.firstChildElement().uri
except Exception as e:
log.warning(_("Can't retrieve namespace of invitation: {reason}").format(
reason = e))
raise exceptions.DataError
if item_id is not None:
try:
items, metadata = await self._p.getItems(
client, service, node, item_ids=[item_id]
)
except Exception as e:
log.warning(_("Can't get item linked with invitation: {reason}").format(
reason=e))
try:
item_elt = items[0]
except IndexError:
log.warning(_("Invitation was linking to a non existing item"))
raise exceptions.DataError
args = [service, node, item_id, item_elt]
defer.returnValue((namespace, args))
try:
namespace = item_elt.firstChildElement().uri
except Exception as e:
log.warning(_("Can't retrieve namespace of invitation: {reason}").format(
reason = e))
raise exceptions.DataError
def _parseFileSharingElt(self, client, file_sharing_elt):
args = [service, node, item_id, item_elt]
else:
try:
node_data_elt = next(pubsub_elt.elements((NS_INVITATION, "node_data")))
except StopIteration:
raise exceptions.DataError("Bad invitation, ignoring")
namespace = node_data_elt['namespace']
args = [service, node, None, node_data_elt]
return namespace, args
async def _parseFileSharingElt(self, client, file_sharing_elt):
try:
service = jid.JID(file_sharing_elt["service"])
except (RuntimeError, KeyError):
log.warning(_("Bad invitation, ignoring"))
raise exceptions.DataError
repos_type = file_sharing_elt.getAttribute("type", "files")
namespace = file_sharing_elt.getAttribute("namespace")
sharing_ns = file_sharing_elt.getAttribute("namespace")
path = file_sharing_elt.getAttribute("path")
args = [service, repos_type, namespace, path]
args = [service, repos_type, sharing_ns, path]
ns_fis = self.host.getNamespace("fis")
return ns_fis, args
@defer.inlineCallbacks
def onInvitation(self, message_elt, client):
async def onInvitation(self, message_elt, client):
log.debug("invitation received [{profile}]".format(profile=client.profile))
invitation_elt = message_elt.invitation
@ -275,7 +309,7 @@ class Invitation(object):
xml = elt.toXml()))
continue
try:
namespace, args = yield method(client, elt)
namespace, args = await method(client, elt)
except exceptions.DataError:
log.warning("Can't parse invitation element: {xml}".format(
xml = elt.toXml()))
@ -288,7 +322,7 @@ class Invitation(object):
'No handler for namespace "{namespace}", invitation ignored')
.format(namespace=namespace))
else:
cb(client, name, extra, *args)
await utils.asDeferred(cb, client, namespace, name, extra, *args)
@implementer(iwokkel.IDisco)
@ -299,7 +333,10 @@ class PubsubInvitationHandler(XMPPHandler):
def connectionInitialized(self):
self.xmlstream.addObserver(
INVITATION, self.plugin_parent.onInvitation, client=self.parent
INVITATION,
lambda message_elt: defer.ensureDeferred(
self.plugin_parent.onInvitation(message_elt, client=self.parent)
),
)
def getDiscoInfo(self, requestor, target, nodeIdentifier=""):