plugin XEP-0077, plugin XEP-0100, frontends: gateways have been entirely implemented in backend using the new refactored XMLUI and AdvancedListContainer. The now useless code has been removed from frontends.

This commit is contained in:
Goffi 2014-02-04 18:26:03 +01:00
commit c6aaaaa297
2 changed files with 227 additions and 522 deletions

View file

@ -18,12 +18,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from sat.core.i18n import _
from logging import debug, info, error
from sat.core import exceptions
from logging import debug, info, warning, error
from twisted.words.protocols.jabber import jid
from twisted.words.protocols.jabber.xmlstream import IQ
from sat.tools.xml_tools import dataForm2XMLUI
from sat.tools import xml_tools
from sat.memory import memory
from wokkel import data_form
from wokkel import data_form, compat
NS_REG = 'jabber:iq:register'
@ -44,97 +46,70 @@ class XEP_0077(object):
info(_("Plugin XEP_0077 initialization"))
self.host = host
self.triggers = {} # used by other protocol (e.g. XEP-0100) to finish registration. key = target_jid
host.bridge.addMethod("in_band_register", ".plugin", in_sign='ss', out_sign='s', method=self.in_band_register)
host.bridge.addMethod("in_band_submit", ".plugin", in_sign='ssa(ss)s', out_sign='s', method=self.in_band_submit)
host.bridge.addMethod("inBandRegister", ".plugin", in_sign='ss', out_sign='s',
method=self._inBandRegister,
async=True)
def addTrigger(self, target, cb, profile):
"""Add a callback which is called when registration to target is successful"""
self.triggers[target] = (cb, profile)
def reg_ok(self, answer, profile):
def _regOk(self, answer, client, post_treat_cb):
"""Called after the first get IQ"""
try:
x_elem = filter(lambda x: x.name == "x", answer.firstChildElement().elements())[0] # We only want the "x" element (data form)
except IndexError:
info(_("No data form found"))
#TODO: manage registration without data form
answer_data = {"reason": "unmanaged", "message": _("This gateway can't be managed by SàT, sorry :(")}
answer_type = "ERROR"
self.host.bridge.actionResult(answer_type, answer['id'], answer_data, profile)
return
query_elt = answer.elements(NS_REG, 'query').next()
except StopIteration:
raise exceptions.DataError("Can't find expected query element")
try:
x_elem = query_elt.elements(data_form.NS_X_DATA, 'x').next()
except StopIteration:
# XXX: it seems we have an old service which doesn't manage data forms
warning(_("Can't find data form"))
raise DataError(_("This gateway can't be managed by SàT, sorry :("))
def submitForm(data, profile):
form_elt = xml_tools.XMLUIResultToElt(data)
iq_elt = compat.IQ(client.xmlstream, 'set')
iq_elt['id'] = answer['id']
iq_elt['to'] = answer['from']
query_elt = iq_elt.addElement("query", NS_REG)
query_elt.addChild(form_elt)
d = iq_elt.send()
d.addCallback(self._regSuccess, client, post_treat_cb)
d.addErrback(self._regFailure, client)
return d
form = data_form.Form.fromElement(x_elem)
xml_data = dataForm2XMLUI(form, "").toXml()
self.host.bridge.actionResult("XMLUI", answer['id'], {"target": answer["from"], "type": "registration", "xml": xml_data}, profile)
submit_reg_id = self.host.registerCallback(submitForm, with_data=True, one_shot=True)
return xml_tools.dataForm2XMLUI(form, submit_reg_id)
def reg_err(self, failure, profile):
def _regErr(self, failure, client):
"""Called when something is wrong with registration"""
info(_("Registration failure: %s") % str(failure.value))
answer_data = {}
answer_data['reason'] = 'unknown'
answer_data = {"message": "%s [code: %s]" % (failure.value.condition, unicode(failure.value))}
answer_type = "ERROR"
self.host.bridge.actionResult(answer_type, failure.value.stanza['id'], answer_data, profile)
raise failure
def unregistrationAnswer(self, answer, profile):
def _regSuccess(self, answer, client, post_treat_cb):
debug(_("registration answer: %s") % answer.toXml())
answer_type = "SUCCESS"
answer_data = {"message": _("Your are now unregistred")}
self.host.bridge.actionResult(answer_type, answer['id'], answer_data, profile)
if post_treat_cb is not None:
post_treat_cb(jid.JID(answer['from']), client.profile)
return {}
def unregistrationFailure(self, failure, profile):
info(_("Unregistration failure: %s") % str(failure.value))
answer_type = "ERROR"
answer_data = {}
answer_data['reason'] = 'unknown'
answer_data = {"message": _("Unregistration failed: %s") % failure.value.condition}
self.host.bridge.actionResult(answer_type, failure.value.stanza['id'], answer_data, profile)
def registrationAnswer(self, answer, profile):
debug(_("registration answer: %s") % answer.toXml())
answer_type = "SUCCESS"
answer_data = {"message": _("Registration successfull")}
self.host.bridge.actionResult(answer_type, answer['id'], answer_data, profile)
if answer["from"] in self.triggers:
callback, profile = self.triggers[answer["from"]]
callback(answer["from"], profile)
del self.triggers[answer["from"]]
def registrationFailure(self, failure, profile):
def _regFailure(self, failure, client):
info(_("Registration failure: %s") % str(failure.value))
print failure.value.stanza.toXml()
answer_type = "ERROR"
answer_data = {}
if failure.value.condition == 'conflict':
answer_data['reason'] = 'conflict'
answer_data = {"message": _("Username already exists, please choose an other one")}
else:
answer_data['reason'] = 'unknown'
answer_data = {"message": _("Registration failed")}
self.host.bridge.actionResult(answer_type, failure.value.stanza['id'], answer_data, profile)
if failure.value.stanza["from"] in self.triggers:
del self.triggers[failure.value.stanza["from"]]
raise exceptions.ConflictError( _("Username already exists, please choose an other one"))
raise failure
def in_band_submit(self, action, target, fields, profile):
"""Submit a form for registration, using data_form"""
id, deferred = self.host.submitForm(action, target, fields, profile)
if action == 'CANCEL':
deferred.addCallbacks(self.unregistrationAnswer, self.unregistrationFailure, callbackArgs=[profile], errbackArgs=[profile])
else:
deferred.addCallbacks(self.registrationAnswer, self.registrationFailure, callbackArgs=[profile], errbackArgs=[profile])
return id
def _inBandRegister(self, to_jid_s, profile_key='@NONE@'):
return self.inBandRegister, jid.JID(to_jid_s, profile_key)
def in_band_register(self, target, profile_key='@DEFAULT@'):
def inBandRegister(self, to_jid, post_treat_cb=None, profile_key='@NONE@'):
"""register to a target JID"""
client = self.host.getClient(profile_key)
if not client:
error(_('Asking for an non-existant or not connected profile'))
return ""
to_jid = jid.JID(target)
raise exceptions.ProfileUnknownError
debug(_("Asking registration for [%s]") % to_jid.full())
reg_request = IQ(client.xmlstream, 'get')
reg_request["from"] = client.jid.full()
reg_request["to"] = to_jid.full()
reg_request.addElement('query', NS_REG)
reg_request.send(to_jid.full()).addCallbacks(self.reg_ok, self.reg_err, callbackArgs=[client.profile], errbackArgs=[client.profile])
return reg_request["id"]
d = reg_request.send(to_jid.full()).addCallbacks(self._regOk, self._regErr, callbackArgs=[client, post_treat_cb], errbackArgs=[client])
return d

View file

@ -17,11 +17,13 @@
# 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 sat.core.i18n import _
from logging import debug, info, error
from sat.core.i18n import _, D_
from sat.core import exceptions
from sat.tools import xml_tools
from logging import debug, info, warning, error
from twisted.words.protocols.jabber import client as jabber_client, jid
from twisted.words.protocols.jabber import error as jab_error
import twisted.internet.error
from twisted.internet import reactor, defer
PLUGIN_INFO = {
"name": "Gateways Plugin",
@ -33,6 +35,22 @@ PLUGIN_INFO = {
"description": _("""Implementation of Gateways protocol""")
}
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.
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):
@ -40,82 +58,155 @@ class XEP_0100(object):
info(_("Gateways plugin initialization"))
self.host = host
self.__gateways = {} # dict used to construct the answer to findGateways. Key = target jid
host.bridge.addMethod("findGateways", ".plugin", in_sign='ss', out_sign='s', method=self.findGateways)
host.bridge.addMethod("gatewayRegister", ".plugin", in_sign='ssa(ss)s', out_sign='s', method=self.gatewayRegister)
host.bridge.addMethod("findGateways", ".plugin", in_sign='ss', out_sign='s', method=self._findGateways)
host.bridge.addMethod("gatewayRegister", ".plugin", in_sign='ss', out_sign='s', method=self._gatewayRegister)
self.__menu_id = host.registerCallback(self._gatewaysMenu, with_data=True)
self.__selected_id = host.registerCallback(self._gatewaySelectedCb, with_data=True)
host.importMenu((D_("Service"), D_("gateways")), self._gatewaysMenu, help_string=D_("Find gateways"))
def __inc_handled_items(self, request_id, profile):
self.__gateways[request_id]['__handled_items'] += 1
if self.__gateways[request_id]['__total_items'] == self.__gateways[request_id]['__handled_items']:
debug(_("All items checked for id [%s]") % str(request_id))
def _gatewaysMenu(self, data, profile):
""" XMLUI activated by menu: return Gateways UI
@param profile: %(doc_profile)s
del self.__gateways[request_id]['__total_items']
del self.__gateways[request_id]['__handled_items']
self.host.actionResultExt(request_id, "DICT_DICT", self.__gateways[request_id], profile)
"""
client = self.host.getClient(profile)
try:
jid_ = jid.JID(data.get(xml_tools.formEscape('external_jid'), client.jid.host))
except RuntimeError:
raise exceptions.DataError(_("Invalid JID"))
d = self.findGateways(jid_, profile)
d.addCallback(self._gatewaysResult2XMLUI, jid_)
d.addCallback(lambda xmlui: {'xmlui': xmlui.toXml()})
return d
def discoInfo(self, disco, entity, request_id, profile):
"""Find disco infos about entity, to check if it is a gateway"""
def _gatewaysResult2XMLUI(self, result, entity):
xmlui = xml_tools.XMLUI(title=_('Gateways manager (%s)') % entity.full())
xmlui.addText(_(WARNING_MSG))
xmlui.addDivider('dash')
adv_list = xmlui.changeContainer('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.setRowIndex(jid_.full())
xmlui.addJid(jid_)
xmlui.addText(name)
xmlui.addText(self._getIdentityDesc(identity))
adv_list.end()
xmlui.addDivider('blank')
xmlui.changeContainer('advanced_list', columns=3)
xmlui.addLabel(_('Use external XMPP server'))
xmlui.addString('external_jid')
xmlui.addButton(self.__menu_id, _(u'Go !'), fields_back=('external_jid',))
return xmlui
for identity in disco.identities:
if identity[0] == 'gateway':
print (_("Found gateway (%(jid)s): %(identity)s") % {'jid': entity.full(), 'identity': disco.identities[identity]})
self.__gateways[request_id][entity.full()] = {
'name': disco.identities[identity],
'type': identity[1]
}
def _gatewaySelectedCb(self, data, profile):
try:
target_jid = jid.JID(data['index'])
except (KeyError, RuntimeError):
warning(_("No gateway index selected"))
return {}
self.__inc_handled_items(request_id, profile)
d = self.gatewayRegister(target_jid, profile)
d.addCallback(lambda xmlui: {'xmlui': xmlui.toXml()})
return d
def discoInfoErr(self, failure, entity, request_id, profile):
"""Something is going wrong with disco"""
failure.trap(jab_error.StanzaError, twisted.internet.error.ConnectionLost)
error(_("Error when discovering [%(jid)s]: %(error)s") % {'jid': entity.full(), 'error': failure.getErrorMessage()})
self.__inc_handled_items(request_id, profile)
def _getIdentityDesc(self, identity):
""" Return a human readable description of identity
@param identity: tuple as returned by Disco identities (category, type)
def discoItems(self, disco, request_id, target, client):
"""Look for items with disco protocol, and ask infos for each one"""
#FIXME: target is used as we can't find the original iq node (parent is None)
# an other way would avoid this useless parameter (is there a way with wokkel ?)
if len(disco._items) == 0:
debug(_("No gateway found"))
self.host.actionResultExt(request_id, "DICT_DICT", {})
return
"""
category, type_ = identity
if category != 'gateway':
error(_('INTERNAL ERROR: identity category should always be "gateway" in _getTypeString, got "%s"') % category)
try:
return _(TYPE_DESCRIPTIONS[type_])
except KeyError:
return _("Unknown IM")
self.__gateways[request_id] = {'__total_items': len(disco._items), '__handled_items': 0, '__private__': {'target': target.full()}}
for item in disco._items:
#TODO: need to set a timeout for theses requests
debug(_("item found: %s"), item.name)
client.disco.requestInfo(item.entity).addCallback(self.discoInfo, entity=item.entity, request_id=request_id, profile=client.profile).addErrback(self.discoInfoErr, entity=item.entity, request_id=request_id, profile=client.profile)
def discoItemsErr(self, failure, request_id, target, client):
"""Something is going wrong with disco"""
error(_("Error when discovering [%(target)s]: %(condition)s") % {'target': target.full(), 'condition': unicode(failure.value)})
message_data = {"reason": "connection error", "message": _(u"Error while trying to discover %(target)s gateways: %(error_mess)s") % {'target': target.full(), 'error_mess': unicode(failure.value)}}
self.host.bridge.actionResult("ERROR", request_id, message_data)
def registrationSuccessful(self, target, profile):
def _registrationSuccessful(self, jid_, profile):
"""Called when in_band registration is ok, we must now follow the rest of procedure"""
debug(_("Registration successful, doing the rest"))
self.host.addContact(target, profile)
self.host.setPresence(target, profile)
self.host.addContact(jid_, profile_key=profile)
self.host.setPresence(jid_, profile_key=profile)
def gatewayRegister(self, action, target, fields, profile_key='@DEFAULT@'):
def _gatewayRegister(self, target_jid_s, profile_key='@NONE@'):
d = self.gatewayRegister(jid.JID(target_jid_s), profile_key)
d.addCallback(lambda xmlui: xmlui.toXml())
return d
def gatewayRegister(self, target_jid, profile_key='@NONE@'):
"""Register gateway using in-band registration, then log-in to gateway"""
profile = self.host.memory.getProfileName(profile_key)
assert(profile) # FIXME: return an error here
if action == 'SUBMIT':
self.host.plugins["XEP-0077"].addTrigger(target, self.registrationSuccessful, profile)
return self.host.plugins["XEP-0077"].in_band_submit(action, target, fields, profile)
assert(profile)
d = self.host.plugins["XEP-0077"].inBandRegister(target_jid, self._registrationSuccessful, profile)
return d
def findGateways(self, target, profile_key):
"""Find gateways in the target JID, using discovery protocol
Return an id used for retrieving the list of gateways
"""
def _infosReceived(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 AttibuteError:
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:
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:
info(_("Skipping [%(jid)s] which is not a gateway") % {'jid': entity.full()})
return ret
def _itemsReceived(self, disco, target, client):
"""Look for items with disco protocol, and ask infos for each one"""
if len(disco._items) == 0:
debug(_("No gateway found"))
return []
_defers = []
for item in disco._items:
debug(_("item found: %s"), item.entity)
_defers.append(client.disco.requestInfo(item.entity))
dl = defer.DeferredList(_defers)
dl.addCallback(self._infosReceived, items=disco._items, target=target, client=client)
reactor.callLater(GATEWAY_TIMEOUT, dl.cancel)
return dl
def _findGateways(self, target_jid_s, profile_key):
target_jid = jid.JID(target_jid_s)
profile = self.host.memory.getProfileName(profile_key)
client = self.host.getClient(profile_key)
if not profile:
raise exceptions.ProfileUnknownError
d = self.findGateways(target_jid, profile)
d.addCallback(self._gatewaysResult2XMLUI, target_jid)
d.addCallback(lambda xmlui: xmlui.toXml())
return d
def findGateways(self, target, profile):
"""Find gateways in the target JID, using discovery protocol
"""
client = self.host.getClient(profile)
assert(client)
to_jid = jid.JID(target)
debug(_("find gateways (target = %(target)s, profile = %(profile)s)") % {'target': to_jid.full(), 'profile': profile})
request_id = self.host.get_next_id()
client.disco.requestItems(to_jid).addCallback(self.discoItems, request_id=request_id, target=to_jid, client=client).addErrback(self.discoItemsErr, request_id=request_id, target=to_jid, client=client)
return request_id
debug(_("find gateways (target = %(target)s, profile = %(profile)s)") % {'target': target.full(), 'profile': profile})
d = client.disco.requestItems(target)
d.addCallback(self._itemsReceived , target=target, client=client)
return d