# HG changeset patch # User Goffi # Date 1685359960 -7200 # Node ID 38819c69aa39f59e9140ec9d08c3e7a78ab8b837 # Parent 4c8bf67bfbeb2c5bbe4e8e918ca212cd22098405 plugin XEP-0353: update according to XEP changes + fix: - `XEP-0166_initiate_elt_built` trigger is now used to have the `` element built, so they can be used with there correct arguments in the jingle initiation message - add message processing hint as it is now specified in the XEP diff -r 4c8bf67bfbeb -r 38819c69aa39 sat/plugins/plugin_xep_0353.py --- a/sat/plugins/plugin_xep_0353.py Mon May 29 13:32:32 2023 +0200 +++ b/sat/plugins/plugin_xep_0353.py Mon May 29 13:32:40 2023 +0200 @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# SàT plugin for Jingle Message Initiation (XEP-0353) +# Libervia plugin for Jingle Message Initiation (XEP-0353) # Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org) # This program is free software: you can redistribute it and/or modify @@ -16,17 +16,19 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from zope.interface import implementer from twisted.internet import defer from twisted.internet import reactor -from twisted.words.protocols.jabber import xmlstream, jid, error +from twisted.words.protocols.jabber import error, jid +from twisted.words.protocols.jabber import xmlstream from twisted.words.xish import domish from wokkel import disco, iwokkel -from sat.core.i18n import _, D_ -from sat.core.constants import Const as C +from zope.interface import implementer + from sat.core import exceptions +from sat.core.constants import Const as C +from sat.core.core_types import SatXMPPEntity +from sat.core.i18n import D_, _ from sat.core.log import getLogger -from sat.tools import utils from sat.tools import xml_tools log = getLogger(__name__) @@ -40,7 +42,7 @@ C.PI_TYPE: "XEP", C.PI_MODES: [C.PLUG_MODE_CLIENT], C.PI_PROTOCOLS: ["XEP-0353"], - C.PI_DEPENDENCIES: ["XEP-0166"], + C.PI_DEPENDENCIES: ["XEP-0166", "XEP-0334"], C.PI_MAIN: "XEP_0353", C.PI_HANDLER: "yes", C.PI_DESCRIPTION: _("""Implementation of Jingle Message Initiation"""), @@ -48,13 +50,19 @@ class XEP_0353: - def __init__(self, host): log.info(_("plugin {name} initialization").format(name=PLUGIN_INFO[C.PI_NAME])) self.host = host host.register_namespace("jingle-message", NS_JINGLE_MESSAGE) self._j = host.plugins["XEP-0166"] - host.trigger.add("XEP-0166_initiate", self._on_initiate_trigger) + self._h = host.plugins["XEP-0334"] + host.trigger.add( + "XEP-0166_initiate_elt_built", + self._on_initiate_trigger, + # this plugin set the resource, we want it to happen first to other trigger + # can get the full peer JID + priority=host.trigger.MAX_PRIORITY, + ) host.trigger.add("message_received", self._on_message_received) def get_handler(self, client): @@ -66,22 +74,29 @@ def build_message_data(self, client, peer_jid, verb, session_id): mess_data = { - 'from': client.jid, - 'to': peer_jid, - 'uid': '', - 'message': {}, - 'type': C.MESS_TYPE_CHAT, - 'subject': {}, - 'extra': {} + "from": client.jid, + "to": peer_jid, + "uid": "", + "message": {}, + "type": C.MESS_TYPE_CHAT, + "subject": {}, + "extra": {}, } client.generate_message_xml(mess_data) - verb_elt = mess_data["xml"].addElement((NS_JINGLE_MESSAGE, verb)) + message_elt = mess_data["xml"] + verb_elt = message_elt.addElement((NS_JINGLE_MESSAGE, verb)) verb_elt["id"] = session_id + self._h.add_hint_elements(message_elt, [self._h.HINT_STORE]) return mess_data - async def _on_initiate_trigger(self, client, session, contents): - # FIXME: check that at least one resource of the peer_jid can handle the feature - peer_jid = session['peer_jid'] + async def _on_initiate_trigger( + self, + client: SatXMPPEntity, + session: dict, + iq_elt: domish.Element, + jingle_elt: domish.Element, + ) -> bool: + peer_jid = session["peer_jid"] if peer_jid.resource: return True @@ -103,43 +118,43 @@ # according to XEP-0353 §3.1 await client.presence.available(peer_jid) - mess_data = self.build_message_data(client, peer_jid, "propose", session['id']) - for content in contents: - content_data = self._j.get_content_data( - content) - try: - jingle_description_elt = ( - content_data.application.handler.jingle_description_elt - ) - except AttributeError: - log.debug( - "no jingle_description_elt set for " - f"{content_data.application.handler}" - ) - description_elt = domish.Element((content["app_ns"], "description")) - else: - description_elt = await utils.as_deferred( - jingle_description_elt, - client, session, content_data.content_name, *content_data.app_args, - **content_data.app_kwargs - ) - mess_data["xml"].propose.addChild(description_elt) + mess_data = self.build_message_data(client, peer_jid, "propose", session["id"]) + message_elt = mess_data["xml"] + for content_data in session["contents"].values(): + # we get the full element build by the application plugin + jingle_description_elt = content_data["application_data"]["desc_elt"] + # and copy it to only keep the root element, no children + description_elt = domish.Element( + (jingle_description_elt.uri, jingle_description_elt.name), + defaultUri=jingle_description_elt.defaultUri, + attribs=jingle_description_elt.attributes, + localPrefixes=jingle_description_elt.localPrefixes, + ) + message_elt.propose.addChild(description_elt) response_d = defer.Deferred() # we wait for 2 min before cancelling the session init - # response_d.addTimeout(2*60, reactor) # FIXME: let's application decide timeout? - response_d.addTimeout(2, reactor) - client._xep_0353_pending_sessions[session['id']] = response_d + response_d.addTimeout(2 * 60, reactor) + client._xep_0353_pending_sessions[session["id"]] = response_d await client.send_message_data(mess_data) try: accepting_jid = await response_d except defer.TimeoutError: - log.warning(_( - "Message initiation with {peer_jid} timed out" - ).format(peer_jid=peer_jid)) + log.warning( + _("Message initiation with {peer_jid} timed out").format( + peer_jid=peer_jid + ) + ) else: + if iq_elt["to"] != accepting_jid.userhost(): + raise exceptions.InternalError( + f" 'to' attribute ({iq_elt['to']!r}) must not differ " + f"from bare JID of the accepting entity ({accepting_jid!r}), this " + "may be a sign of an internal bug, a hack attempt, or a MITM attack!" + ) + iq_elt["to"] = accepting_jid.full() session["peer_jid"] = accepting_jid - del client._xep_0353_pending_sessions[session['id']] + del client._xep_0353_pending_sessions[session["id"]] return True async def _on_message_received(self, client, message_elt, post_treat): @@ -170,11 +185,11 @@ human_name = getattr(application.handler, "human_name", application.name) except (exceptions.NotFound, AttributeError): if app_ns.startswith("urn:xmpp:jingle:apps:"): - human_name = app_ns[21:].split(":", 1)[0].replace('-', ' ').title() + human_name = app_ns[21:].split(":", 1)[0].replace("-", " ").title() else: - splitted_ns = app_ns.split(':') + splitted_ns = app_ns.split(":") if len(splitted_ns) > 1: - human_name = splitted_ns[-2].replace('- ', ' ').title() + human_name = splitted_ns[-2].replace("- ", " ").title() else: human_name = app_ns @@ -185,28 +200,27 @@ ).format(peer_jid=peer_jid, human_name=human_name) confirm_title = D_("Invitation from an unknown contact") accept = await xml_tools.defer_confirm( - self.host, confirm_msg, confirm_title, profile=client.profile, + self.host, + confirm_msg, + confirm_title, + profile=client.profile, action_extra={ "type": C.META_TYPE_NOT_IN_ROSTER_LEAK, "session_id": session_id, "from_jid": peer_jid.full(), - } + }, ) if not accept: mess_data = self.build_message_data( - client, client.jid.userhostJID(), "reject", session_id) + client, client.jid.userhostJID(), "reject", session_id + ) await client.send_message_data(mess_data) # we don't sent anything to sender, to avoid leaking presence return False else: await client.presence.available(peer_jid) session_id = elt["id"] - # FIXME: accept is not used anymore in new specification, check it and remove it - mess_data = self.build_message_data( - client, client.jid.userhostJID(), "accept", session_id) - await client.send_message_data(mess_data) - mess_data = self.build_message_data( - client, peer_jid, "proceed", session_id) + mess_data = self.build_message_data(client, peer_jid, "proceed", session_id) await client.send_message_data(mess_data) return False @@ -224,8 +238,9 @@ response_d = client._xep_0353_pending_sessions[session_id] except KeyError: log.warning( - _("no pending session found with id {session_id}, did it timed out?") - .format(session_id=session_id) + _( + "no pending session found with id {session_id}, did it timed out?" + ).format(session_id=session_id) ) return True @@ -241,7 +256,6 @@ @implementer(iwokkel.IDisco) class Handler(xmlstream.XMPPHandler): - def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_JINGLE_MESSAGE)]