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
This commit is contained in:
Goffi 2023-05-29 13:32:40 +02:00
commit 43a0621182

View file

@ -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 @@ PLUGIN_INFO = {
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 @@ PLUGIN_INFO = {
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 @@ class XEP_0353:
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 @@ class XEP_0353:
# 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 @@ class XEP_0353:
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 @@ class XEP_0353:
).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 @@ class XEP_0353:
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 @@ class XEP_0353:
@implementer(iwokkel.IDisco)
class Handler(xmlstream.XMPPHandler):
def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
return [disco.DiscoFeature(NS_JINGLE_MESSAGE)]