comparison sat/plugins/plugin_xep_0100.py @ 2562:26edcf3a30eb

core, setup: huge cleaning: - moved directories from src and frontends/src to sat and sat_frontends, which is the recommanded naming convention - move twisted directory to root - removed all hacks from setup.py, and added missing dependencies, it is now clean - use https URL for website in setup.py - removed "Environment :: X11 Applications :: GTK", as wix is deprecated and removed - renamed sat.sh to sat and fixed its installation - added python_requires to specify Python version needed - replaced glib2reactor which use deprecated code by gtk3reactor sat can now be installed directly from virtualenv without using --system-site-packages anymore \o/
author Goffi <goffi@goffi.org>
date Mon, 02 Apr 2018 19:44:50 +0200
parents src/plugins/plugin_xep_0100.py@0046283a285d
children 56f94936df1e
comparison
equal deleted inserted replaced
2561:bd30dc3ffe5a 2562:26edcf3a30eb
1 #!/usr/bin/env python2
2 # -*- coding: utf-8 -*-
3
4 # SAT plugin for managing gateways (xep-0100)
5 # Copyright (C) 2009-2018 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 sat.core.i18n import _, D_
21 from sat.core.constants import Const as C
22 from sat.core import exceptions
23 from sat.tools import xml_tools
24 from sat.core.log import getLogger
25 log = getLogger(__name__)
26 from twisted.words.protocols.jabber import jid
27 from twisted.internet import reactor, defer
28
29 PLUGIN_INFO = {
30 C.PI_NAME: "Gateways Plugin",
31 C.PI_IMPORT_NAME: "XEP-0100",
32 C.PI_TYPE: "XEP",
33 C.PI_PROTOCOLS: ["XEP-0100"],
34 C.PI_DEPENDENCIES: ["XEP-0077"],
35 C.PI_MAIN: "XEP_0100",
36 C.PI_DESCRIPTION: _("""Implementation of Gateways protocol""")
37 }
38
39 WARNING_MSG = D_(u"""Be careful ! Gateways allow you to use an external IM (legacy IM), so you can see your contact as XMPP contacts.
40 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).""")
41
42 GATEWAY_TIMEOUT = 10 # time to wait before cancelling a gateway disco info, in seconds
43
44 TYPE_DESCRIPTIONS = {'irc': D_("Internet Relay Chat"),
45 'xmpp': D_("XMPP"),
46 'qq': D_("Tencent QQ"),
47 'simple': D_("SIP/SIMPLE"),
48 'icq': D_("ICQ"),
49 'yahoo': D_("Yahoo! Messenger"),
50 'gadu-gadu': D_("Gadu-Gadu"),
51 'aim': D_("AOL Instant Messenger"),
52 'msn': D_("Windows Live Messenger"),
53 }
54
55
56 class XEP_0100(object):
57
58 def __init__(self, host):
59 log.info(_("Gateways plugin initialization"))
60 self.host = host
61 self.__gateways = {} # dict used to construct the answer to findGateways. Key = target jid
62 host.bridge.addMethod("findGateways", ".plugin", in_sign='ss', out_sign='s', method=self._findGateways)
63 host.bridge.addMethod("gatewayRegister", ".plugin", in_sign='ss', out_sign='s', method=self._gatewayRegister)
64 self.__menu_id = host.registerCallback(self._gatewaysMenu, with_data=True)
65 self.__selected_id = host.registerCallback(self._gatewaySelectedCb, with_data=True)
66 host.importMenu((D_("Service"), D_("Gateways")), self._gatewaysMenu, security_limit=1, help_string=D_("Find gateways"))
67
68 def _gatewaysMenu(self, data, profile):
69 """ XMLUI activated by menu: return Gateways UI
70
71 @param profile: %(doc_profile)s
72 """
73 client = self.host.getClient(profile)
74 try:
75 jid_ = jid.JID(data.get(xml_tools.formEscape('external_jid'), client.jid.host))
76 except RuntimeError:
77 raise exceptions.DataError(_("Invalid JID"))
78 d = self.findGateways(jid_, profile)
79 d.addCallback(self._gatewaysResult2XMLUI, jid_)
80 d.addCallback(lambda xmlui: {'xmlui': xmlui.toXml()})
81 return d
82
83 def _gatewaysResult2XMLUI(self, result, entity):
84 xmlui = xml_tools.XMLUI(title=_('Gateways manager (%s)') % entity.full())
85 xmlui.addText(_(WARNING_MSG))
86 xmlui.addDivider('dash')
87 adv_list = xmlui.changeContainer('advanced_list', columns=3, selectable='single', callback_id=self.__selected_id)
88 for success, gateway_data in result:
89 if not success:
90 fail_cond, disco_item = gateway_data
91 xmlui.addJid(disco_item.entity)
92 xmlui.addText(_('Failed (%s)') % fail_cond)
93 xmlui.addEmpty()
94 else:
95 jid_, data = gateway_data
96 for datum in data:
97 identity, name = datum
98 adv_list.setRowIndex(jid_.full())
99 xmlui.addJid(jid_)
100 xmlui.addText(name)
101 xmlui.addText(self._getIdentityDesc(identity))
102 adv_list.end()
103 xmlui.addDivider('blank')
104 xmlui.changeContainer('advanced_list', columns=3)
105 xmlui.addLabel(_('Use external XMPP server'))
106 xmlui.addString('external_jid')
107 xmlui.addButton(self.__menu_id, _(u'Go !'), fields_back=('external_jid',))
108 return xmlui
109
110 def _gatewaySelectedCb(self, data, profile):
111 try:
112 target_jid = jid.JID(data['index'])
113 except (KeyError, RuntimeError):
114 log.warning(_("No gateway index selected"))
115 return {}
116
117 d = self.gatewayRegister(target_jid, profile)
118 d.addCallback(lambda xmlui: {'xmlui': xmlui.toXml()})
119 return d
120
121 def _getIdentityDesc(self, identity):
122 """ Return a human readable description of identity
123 @param identity: tuple as returned by Disco identities (category, type)
124
125 """
126 category, type_ = identity
127 if category != 'gateway':
128 log.error(_(u'INTERNAL ERROR: identity category should always be "gateway" in _getTypeString, got "%s"') % category)
129 try:
130 return _(TYPE_DESCRIPTIONS[type_])
131 except KeyError:
132 return _("Unknown IM")
133
134 def _registrationSuccessful(self, jid_, profile):
135 """Called when in_band registration is ok, we must now follow the rest of procedure"""
136 log.debug(_("Registration successful, doing the rest"))
137 self.host.addContact(jid_, profile_key=profile)
138 self.host.setPresence(jid_, profile_key=profile)
139
140 def _gatewayRegister(self, target_jid_s, profile_key=C.PROF_KEY_NONE):
141 d = self.gatewayRegister(jid.JID(target_jid_s), profile_key)
142 d.addCallback(lambda xmlui: xmlui.toXml())
143 return d
144
145 def gatewayRegister(self, target_jid, profile_key=C.PROF_KEY_NONE):
146 """Register gateway using in-band registration, then log-in to gateway"""
147 profile = self.host.memory.getProfileName(profile_key)
148 assert(profile)
149 d = self.host.plugins["XEP-0077"].inBandRegister(target_jid, self._registrationSuccessful, profile)
150 return d
151
152 def _infosReceived(self, dl_result, items, target, client):
153 """Find disco infos about entity, to check if it is a gateway"""
154
155 ret = []
156 for idx, (success, result) in enumerate(dl_result):
157 if not success:
158 if isinstance(result.value, defer.CancelledError):
159 msg = _("Timeout")
160 else:
161 try:
162 msg = result.value.condition
163 except AttributeError:
164 msg = str(result)
165 ret.append((success, (msg, items[idx])))
166 else:
167 entity = items[idx].entity
168 gateways = [(identity, result.identities[identity]) for identity in result.identities if identity[0] == 'gateway']
169 if gateways:
170 log.info(_(u"Found gateway [%(jid)s]: %(identity_name)s") % {'jid': entity.full(), 'identity_name': ' - '.join([gateway[1] for gateway in gateways])})
171 ret.append((success, (entity, gateways)))
172 else:
173 log.info(_(u"Skipping [%(jid)s] which is not a gateway") % {'jid': entity.full()})
174 return ret
175
176 def _itemsReceived(self, disco, target, client):
177 """Look for items with disco protocol, and ask infos for each one"""
178
179 if len(disco._items) == 0:
180 log.debug(_("No gateway found"))
181 return []
182
183 _defers = []
184 for item in disco._items:
185 log.debug(_(u"item found: %s") % item.entity)
186 _defers.append(client.disco.requestInfo(item.entity))
187 dl = defer.DeferredList(_defers)
188 dl.addCallback(self._infosReceived, items=disco._items, target=target, client=client)
189 reactor.callLater(GATEWAY_TIMEOUT, dl.cancel)
190 return dl
191
192 def _findGateways(self, target_jid_s, profile_key):
193 target_jid = jid.JID(target_jid_s)
194 profile = self.host.memory.getProfileName(profile_key)
195 if not profile:
196 raise exceptions.ProfileUnknownError
197 d = self.findGateways(target_jid, profile)
198 d.addCallback(self._gatewaysResult2XMLUI, target_jid)
199 d.addCallback(lambda xmlui: xmlui.toXml())
200 return d
201
202 def findGateways(self, target, profile):
203 """Find gateways in the target JID, using discovery protocol
204 """
205 client = self.host.getClient(profile)
206 log.debug(_(u"find gateways (target = %(target)s, profile = %(profile)s)") % {'target': target.full(), 'profile': profile})
207 d = client.disco.requestItems(target)
208 d.addCallback(self._itemsReceived, target=target, client=client)
209 return d