diff sat/plugins/plugin_xep_0353.py @ 4055:38819c69aa39

plugin XEP-0353: update according to XEP changes + <description> fix: - `XEP-0166_initiate_elt_built` trigger is now used to have the `<description>` 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
author Goffi <goffi@goffi.org>
date Mon, 29 May 2023 13:32:40 +0200
parents c23cad65ae99
children
line wrap: on
line diff
--- 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 <http://www.gnu.org/licenses/>.
 
-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 <description> 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"<jingle> '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)]