comparison 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
comparison
equal deleted inserted replaced
4070:d10748475025 4071:4b842c1fb686
1 #!/usr/bin/env python3
2
3
4 # SAT plugin for managing gateways (xep-0100)
5 # Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org)
6
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
16
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 from libervia.backend.core.i18n import _, D_
21 from libervia.backend.core.constants import Const as C
22 from libervia.backend.core import exceptions
23 from libervia.backend.tools import xml_tools
24 from libervia.backend.core.log import getLogger
25
26 log = getLogger(__name__)
27 from twisted.words.protocols.jabber import jid
28 from twisted.internet import reactor, defer
29
30 PLUGIN_INFO = {
31 C.PI_NAME: "Gateways Plugin",
32 C.PI_IMPORT_NAME: "XEP-0100",
33 C.PI_TYPE: "XEP",
34 C.PI_PROTOCOLS: ["XEP-0100"],
35 C.PI_DEPENDENCIES: ["XEP-0077"],
36 C.PI_MAIN: "XEP_0100",
37 C.PI_DESCRIPTION: _("""Implementation of Gateways protocol"""),
38 }
39
40 WARNING_MSG = D_(
41 """Be careful ! Gateways allow you to use an external IM (legacy IM), so you can see your contact as XMPP contacts.
42 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)."""
43 )
44
45 GATEWAY_TIMEOUT = 10 # time to wait before cancelling a gateway disco info, in seconds
46
47 TYPE_DESCRIPTIONS = {
48 "irc": D_("Internet Relay Chat"),
49 "xmpp": D_("XMPP"),
50 "qq": D_("Tencent QQ"),
51 "simple": D_("SIP/SIMPLE"),
52 "icq": D_("ICQ"),
53 "yahoo": D_("Yahoo! Messenger"),
54 "gadu-gadu": D_("Gadu-Gadu"),
55 "aim": D_("AOL Instant Messenger"),
56 "msn": D_("Windows Live Messenger"),
57 }
58
59
60 class XEP_0100(object):
61 def __init__(self, host):
62 log.info(_("Gateways plugin initialization"))
63 self.host = host
64 self.__gateways = {} # dict used to construct the answer to gateways_find. Key = target jid
65 host.bridge.add_method(
66 "gateways_find",
67 ".plugin",
68 in_sign="ss",
69 out_sign="s",
70 method=self._find_gateways,
71 )
72 host.bridge.add_method(
73 "gateway_register",
74 ".plugin",
75 in_sign="ss",
76 out_sign="s",
77 method=self._gateway_register,
78 )
79 self.__menu_id = host.register_callback(self._gateways_menu, with_data=True)
80 self.__selected_id = host.register_callback(
81 self._gateway_selected_cb, with_data=True
82 )
83 host.import_menu(
84 (D_("Service"), D_("Gateways")),
85 self._gateways_menu,
86 security_limit=1,
87 help_string=D_("Find gateways"),
88 )
89
90 def _gateways_menu(self, data, profile):
91 """ XMLUI activated by menu: return Gateways UI
92
93 @param profile: %(doc_profile)s
94 """
95 client = self.host.get_client(profile)
96 try:
97 jid_ = jid.JID(
98 data.get(xml_tools.form_escape("external_jid"), client.jid.host)
99 )
100 except RuntimeError:
101 raise exceptions.DataError(_("Invalid JID"))
102 d = self.gateways_find(jid_, profile)
103 d.addCallback(self._gateways_result_2_xmlui, jid_)
104 d.addCallback(lambda xmlui: {"xmlui": xmlui.toXml()})
105 return d
106
107 def _gateways_result_2_xmlui(self, result, entity):
108 xmlui = xml_tools.XMLUI(title=_("Gateways manager (%s)") % entity.full())
109 xmlui.addText(_(WARNING_MSG))
110 xmlui.addDivider("dash")
111 adv_list = xmlui.change_container(
112 "advanced_list",
113 columns=3,
114 selectable="single",
115 callback_id=self.__selected_id,
116 )
117 for success, gateway_data in result:
118 if not success:
119 fail_cond, disco_item = gateway_data
120 xmlui.addJid(disco_item.entity)
121 xmlui.addText(_("Failed (%s)") % fail_cond)
122 xmlui.addEmpty()
123 else:
124 jid_, data = gateway_data
125 for datum in data:
126 identity, name = datum
127 adv_list.set_row_index(jid_.full())
128 xmlui.addJid(jid_)
129 xmlui.addText(name)
130 xmlui.addText(self._get_identity_desc(identity))
131 adv_list.end()
132 xmlui.addDivider("blank")
133 xmlui.change_container("advanced_list", columns=3)
134 xmlui.addLabel(_("Use external XMPP server"))
135 xmlui.addString("external_jid")
136 xmlui.addButton(self.__menu_id, _("Go !"), fields_back=("external_jid",))
137 return xmlui
138
139 def _gateway_selected_cb(self, data, profile):
140 try:
141 target_jid = jid.JID(data["index"])
142 except (KeyError, RuntimeError):
143 log.warning(_("No gateway index selected"))
144 return {}
145
146 d = self.gateway_register(target_jid, profile)
147 d.addCallback(lambda xmlui: {"xmlui": xmlui.toXml()})
148 return d
149
150 def _get_identity_desc(self, identity):
151 """ Return a human readable description of identity
152 @param identity: tuple as returned by Disco identities (category, type)
153
154 """
155 category, type_ = identity
156 if category != "gateway":
157 log.error(
158 _(
159 'INTERNAL ERROR: identity category should always be "gateway" in _getTypeString, got "%s"'
160 )
161 % category
162 )
163 try:
164 return _(TYPE_DESCRIPTIONS[type_])
165 except KeyError:
166 return _("Unknown IM")
167
168 def _registration_successful(self, jid_, profile):
169 """Called when in_band registration is ok, we must now follow the rest of procedure"""
170 log.debug(_("Registration successful, doing the rest"))
171 self.host.contact_add(jid_, profile_key=profile)
172 self.host.presence_set(jid_, profile_key=profile)
173
174 def _gateway_register(self, target_jid_s, profile_key=C.PROF_KEY_NONE):
175 d = self.gateway_register(jid.JID(target_jid_s), profile_key)
176 d.addCallback(lambda xmlui: xmlui.toXml())
177 return d
178
179 def gateway_register(self, target_jid, profile_key=C.PROF_KEY_NONE):
180 """Register gateway using in-band registration, then log-in to gateway"""
181 profile = self.host.memory.get_profile_name(profile_key)
182 assert profile
183 d = self.host.plugins["XEP-0077"].in_band_register(
184 target_jid, self._registration_successful, profile
185 )
186 return d
187
188 def _infos_received(self, dl_result, items, target, client):
189 """Find disco infos about entity, to check if it is a gateway"""
190
191 ret = []
192 for idx, (success, result) in enumerate(dl_result):
193 if not success:
194 if isinstance(result.value, defer.CancelledError):
195 msg = _("Timeout")
196 else:
197 try:
198 msg = result.value.condition
199 except AttributeError:
200 msg = str(result)
201 ret.append((success, (msg, items[idx])))
202 else:
203 entity = items[idx].entity
204 gateways = [
205 (identity, result.identities[identity])
206 for identity in result.identities
207 if identity[0] == "gateway"
208 ]
209 if gateways:
210 log.info(
211 _("Found gateway [%(jid)s]: %(identity_name)s")
212 % {
213 "jid": entity.full(),
214 "identity_name": " - ".join(
215 [gateway[1] for gateway in gateways]
216 ),
217 }
218 )
219 ret.append((success, (entity, gateways)))
220 else:
221 log.info(
222 _("Skipping [%(jid)s] which is not a gateway")
223 % {"jid": entity.full()}
224 )
225 return ret
226
227 def _items_received(self, disco, target, client):
228 """Look for items with disco protocol, and ask infos for each one"""
229
230 if len(disco._items) == 0:
231 log.debug(_("No gateway found"))
232 return []
233
234 _defers = []
235 for item in disco._items:
236 log.debug(_("item found: %s") % item.entity)
237 _defers.append(client.disco.requestInfo(item.entity))
238 dl = defer.DeferredList(_defers)
239 dl.addCallback(
240 self._infos_received, items=disco._items, target=target, client=client
241 )
242 reactor.callLater(GATEWAY_TIMEOUT, dl.cancel)
243 return dl
244
245 def _find_gateways(self, target_jid_s, profile_key):
246 target_jid = jid.JID(target_jid_s)
247 profile = self.host.memory.get_profile_name(profile_key)
248 if not profile:
249 raise exceptions.ProfileUnknownError
250 d = self.gateways_find(target_jid, profile)
251 d.addCallback(self._gateways_result_2_xmlui, target_jid)
252 d.addCallback(lambda xmlui: xmlui.toXml())
253 return d
254
255 def gateways_find(self, target, profile):
256 """Find gateways in the target JID, using discovery protocol
257 """
258 client = self.host.get_client(profile)
259 log.debug(
260 _("find gateways (target = %(target)s, profile = %(profile)s)")
261 % {"target": target.full(), "profile": profile}
262 )
263 d = client.disco.requestItems(target)
264 d.addCallback(self._items_received, target=target, client=client)
265 return d