Mercurial > libervia-backend
diff libervia/backend/plugins/plugin_xep_0100.py @ 4071:4b842c1fb686
refactoring: renamed `sat` package to `libervia.backend`
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 02 Jun 2023 11:49:51 +0200 |
parents | sat/plugins/plugin_xep_0100.py@524856bd7b19 |
children | 0d7bb4df2343 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libervia/backend/plugins/plugin_xep_0100.py Fri Jun 02 11:49:51 2023 +0200 @@ -0,0 +1,265 @@ +#!/usr/bin/env python3 + + +# SAT plugin for managing gateways (xep-0100) +# Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org) + +# 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 <http://www.gnu.org/licenses/>. + +from libervia.backend.core.i18n import _, D_ +from libervia.backend.core.constants import Const as C +from libervia.backend.core import exceptions +from libervia.backend.tools import xml_tools +from libervia.backend.core.log import getLogger + +log = getLogger(__name__) +from twisted.words.protocols.jabber import jid +from twisted.internet import reactor, defer + +PLUGIN_INFO = { + C.PI_NAME: "Gateways Plugin", + C.PI_IMPORT_NAME: "XEP-0100", + C.PI_TYPE: "XEP", + C.PI_PROTOCOLS: ["XEP-0100"], + C.PI_DEPENDENCIES: ["XEP-0077"], + C.PI_MAIN: "XEP_0100", + C.PI_DESCRIPTION: _("""Implementation of Gateways protocol"""), +} + +WARNING_MSG = D_( + """Be careful ! Gateways allow you to use an external IM (legacy IM), so you can see your contact as XMPP contacts. +But when you do this, all your messages go throught the external legacy IM server, it is a huge privacy issue (i.e.: all your messages throught the gateway can be monitored, recorded, analysed by the external server, most of time a private company).""" +) + +GATEWAY_TIMEOUT = 10 # time to wait before cancelling a gateway disco info, in seconds + +TYPE_DESCRIPTIONS = { + "irc": D_("Internet Relay Chat"), + "xmpp": D_("XMPP"), + "qq": D_("Tencent QQ"), + "simple": D_("SIP/SIMPLE"), + "icq": D_("ICQ"), + "yahoo": D_("Yahoo! Messenger"), + "gadu-gadu": D_("Gadu-Gadu"), + "aim": D_("AOL Instant Messenger"), + "msn": D_("Windows Live Messenger"), +} + + +class XEP_0100(object): + def __init__(self, host): + log.info(_("Gateways plugin initialization")) + self.host = host + self.__gateways = {} # dict used to construct the answer to gateways_find. Key = target jid + host.bridge.add_method( + "gateways_find", + ".plugin", + in_sign="ss", + out_sign="s", + method=self._find_gateways, + ) + host.bridge.add_method( + "gateway_register", + ".plugin", + in_sign="ss", + out_sign="s", + method=self._gateway_register, + ) + self.__menu_id = host.register_callback(self._gateways_menu, with_data=True) + self.__selected_id = host.register_callback( + self._gateway_selected_cb, with_data=True + ) + host.import_menu( + (D_("Service"), D_("Gateways")), + self._gateways_menu, + security_limit=1, + help_string=D_("Find gateways"), + ) + + def _gateways_menu(self, data, profile): + """ XMLUI activated by menu: return Gateways UI + + @param profile: %(doc_profile)s + """ + client = self.host.get_client(profile) + try: + jid_ = jid.JID( + data.get(xml_tools.form_escape("external_jid"), client.jid.host) + ) + except RuntimeError: + raise exceptions.DataError(_("Invalid JID")) + d = self.gateways_find(jid_, profile) + d.addCallback(self._gateways_result_2_xmlui, jid_) + d.addCallback(lambda xmlui: {"xmlui": xmlui.toXml()}) + return d + + def _gateways_result_2_xmlui(self, result, entity): + xmlui = xml_tools.XMLUI(title=_("Gateways manager (%s)") % entity.full()) + xmlui.addText(_(WARNING_MSG)) + xmlui.addDivider("dash") + adv_list = xmlui.change_container( + "advanced_list", + columns=3, + selectable="single", + callback_id=self.__selected_id, + ) + for success, gateway_data in result: + if not success: + fail_cond, disco_item = gateway_data + xmlui.addJid(disco_item.entity) + xmlui.addText(_("Failed (%s)") % fail_cond) + xmlui.addEmpty() + else: + jid_, data = gateway_data + for datum in data: + identity, name = datum + adv_list.set_row_index(jid_.full()) + xmlui.addJid(jid_) + xmlui.addText(name) + xmlui.addText(self._get_identity_desc(identity)) + adv_list.end() + xmlui.addDivider("blank") + xmlui.change_container("advanced_list", columns=3) + xmlui.addLabel(_("Use external XMPP server")) + xmlui.addString("external_jid") + xmlui.addButton(self.__menu_id, _("Go !"), fields_back=("external_jid",)) + return xmlui + + def _gateway_selected_cb(self, data, profile): + try: + target_jid = jid.JID(data["index"]) + except (KeyError, RuntimeError): + log.warning(_("No gateway index selected")) + return {} + + d = self.gateway_register(target_jid, profile) + d.addCallback(lambda xmlui: {"xmlui": xmlui.toXml()}) + return d + + def _get_identity_desc(self, identity): + """ Return a human readable description of identity + @param identity: tuple as returned by Disco identities (category, type) + + """ + category, type_ = identity + if category != "gateway": + log.error( + _( + 'INTERNAL ERROR: identity category should always be "gateway" in _getTypeString, got "%s"' + ) + % category + ) + try: + return _(TYPE_DESCRIPTIONS[type_]) + except KeyError: + return _("Unknown IM") + + def _registration_successful(self, jid_, profile): + """Called when in_band registration is ok, we must now follow the rest of procedure""" + log.debug(_("Registration successful, doing the rest")) + self.host.contact_add(jid_, profile_key=profile) + self.host.presence_set(jid_, profile_key=profile) + + def _gateway_register(self, target_jid_s, profile_key=C.PROF_KEY_NONE): + d = self.gateway_register(jid.JID(target_jid_s), profile_key) + d.addCallback(lambda xmlui: xmlui.toXml()) + return d + + def gateway_register(self, target_jid, profile_key=C.PROF_KEY_NONE): + """Register gateway using in-band registration, then log-in to gateway""" + profile = self.host.memory.get_profile_name(profile_key) + assert profile + d = self.host.plugins["XEP-0077"].in_band_register( + target_jid, self._registration_successful, profile + ) + return d + + def _infos_received(self, dl_result, items, target, client): + """Find disco infos about entity, to check if it is a gateway""" + + ret = [] + for idx, (success, result) in enumerate(dl_result): + if not success: + if isinstance(result.value, defer.CancelledError): + msg = _("Timeout") + else: + try: + msg = result.value.condition + except AttributeError: + msg = str(result) + ret.append((success, (msg, items[idx]))) + else: + entity = items[idx].entity + gateways = [ + (identity, result.identities[identity]) + for identity in result.identities + if identity[0] == "gateway" + ] + if gateways: + log.info( + _("Found gateway [%(jid)s]: %(identity_name)s") + % { + "jid": entity.full(), + "identity_name": " - ".join( + [gateway[1] for gateway in gateways] + ), + } + ) + ret.append((success, (entity, gateways))) + else: + log.info( + _("Skipping [%(jid)s] which is not a gateway") + % {"jid": entity.full()} + ) + return ret + + def _items_received(self, disco, target, client): + """Look for items with disco protocol, and ask infos for each one""" + + if len(disco._items) == 0: + log.debug(_("No gateway found")) + return [] + + _defers = [] + for item in disco._items: + log.debug(_("item found: %s") % item.entity) + _defers.append(client.disco.requestInfo(item.entity)) + dl = defer.DeferredList(_defers) + dl.addCallback( + self._infos_received, items=disco._items, target=target, client=client + ) + reactor.callLater(GATEWAY_TIMEOUT, dl.cancel) + return dl + + def _find_gateways(self, target_jid_s, profile_key): + target_jid = jid.JID(target_jid_s) + profile = self.host.memory.get_profile_name(profile_key) + if not profile: + raise exceptions.ProfileUnknownError + d = self.gateways_find(target_jid, profile) + d.addCallback(self._gateways_result_2_xmlui, target_jid) + d.addCallback(lambda xmlui: xmlui.toXml()) + return d + + def gateways_find(self, target, profile): + """Find gateways in the target JID, using discovery protocol + """ + client = self.host.get_client(profile) + log.debug( + _("find gateways (target = %(target)s, profile = %(profile)s)") + % {"target": target.full(), "profile": profile} + ) + d = client.disco.requestItems(target) + d.addCallback(self._items_received, target=target, client=client) + return d