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:
parent
7d313660eb
commit
c6aaaaa297
2 changed files with 227 additions and 522 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue