Mercurial > libervia-backend
changeset 807:be4c5e24dab9
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.
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 04 Feb 2014 18:26:03 +0100 |
parents | 5d6c45d6ee1b |
children | d035c662b357 |
files | frontends/src/primitivus/gateways.py frontends/src/primitivus/primitivus frontends/src/quick_frontend/quick_gateways.py frontends/src/wix/gateways.py frontends/src/wix/main_window.py src/core/sat_main.py src/plugins/plugin_xep_0077.py src/plugins/plugin_xep_0100.py |
diffstat | 8 files changed, 226 insertions(+), 521 deletions(-) [+] |
line wrap: on
line diff
--- a/frontends/src/primitivus/gateways.py Tue Feb 04 18:24:27 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,87 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Primitivus: a SAT frontend -# Copyright (C) 2009, 2010, 2011, 2012, 2013 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 sat.core.i18n import _ -import urwid -from urwid_satext import sat_widgets -from sat_frontends.quick_frontend.quick_gateways import QuickGatewaysManager -from sat.tools.jid import JID - - -class GatewaysManager(urwid.WidgetWrap, QuickGatewaysManager): - - def __init__(self, host, gateways, title=_("Gateways manager"), server=None): - QuickGatewaysManager.__init__(self, host, gateways, server) - if server: - title+=" (%s)" % server - widget_list = urwid.SimpleListWalker([]) - widget_list.append(urwid.Text(self.WARNING_MSG)) - widget_list.append(urwid.Divider('-')) - for gateway in gateways: - self.addGateway(widget_list,gateway, gateways[gateway]) - widget_list.append(urwid.Divider()) - self.ext_serv = sat_widgets.AdvancedEdit(_("Use external XMPP server: ")) - go_button = sat_widgets.CustomButton( _("GO !"),self.browseExternalServer) - ext_serv_col = urwid.Columns([self.ext_serv,('fixed',go_button.getSize(),go_button)]) - widget_list.append(ext_serv_col) - list_wid = urwid.ListBox(widget_list) - decorated = sat_widgets.LabelLine(list_wid, sat_widgets.SurroundedText(title)) - urwid.WidgetWrap.__init__(self, decorated) - - def browseExternalServer(self, button): - """Open the gateway manager on given server""" - server = self.ext_serv.get_edit_text() - if not server: - popup = sat_widgets.Alert(_("Error"), _("You must enter an external server JID"), ok_cb=self.host.removePopUp) - self.host.showPopUp(popup) - return - action_id = self.host.bridge.findGateways(server, self.host.profile) - self.host.current_action_ids.add(action_id) - self.host.current_action_ids_cb[action_id] = self.host.onGatewaysFound - self.host.removeWindow() - - def addGateway(self, widget_list, gateway, param): - - widget_col = [] - widget_col.append(('weight',4,urwid.Text(unicode(param['name'])))) #FIXME: unicode to be remove when DBus bridge will not give dbus.String anymore - - #Then the transport type message - widget_col.append(('weight',6,urwid.Text(self.getGatewayDesc(param['type'])))) - - #The buttons - - reg_button = sat_widgets.CustomButton( _("Register"), self.onRegister) - reg_button.gateway_jid = JID(gateway) - widget_col.append(('fixed',reg_button.getSize(),reg_button)) - unreg_button = sat_widgets.CustomButton( _("Unregister"), self.onUnregister) - unreg_button.gateway_jid = JID(gateway) - widget_col.append(('fixed',unreg_button.getSize(),unreg_button)) - widget_list.append(urwid.Columns(widget_col,1)) - - def onRegister(self, button): - """Called when register button is clicked""" - gateway_jid = button.gateway_jid - action_id = self.host.bridge.in_band_register(gateway_jid, self.host.profile) - self.host.current_action_ids.add(action_id) - - def onUnregister(self, button): - """Called when unregister button is clicked""" - gateway_jid = button.gateway_jid - action_id = self.host.bridge.gatewayRegister("CANCEL",gateway_jid, None, self.host.profile) - self.host.current_action_ids.add(action_id)
--- a/frontends/src/primitivus/primitivus Tue Feb 04 18:24:27 2014 +0100 +++ b/frontends/src/primitivus/primitivus Tue Feb 04 18:26:03 2014 +0100 @@ -28,7 +28,6 @@ from sat_frontends.primitivus.profile_manager import ProfileManager from sat_frontends.primitivus.contact_list import ContactList from sat_frontends.primitivus.chat import Chat -from sat_frontends.primitivus.gateways import GatewaysManager from sat_frontends.primitivus.xmlui import XMLUI from sat_frontends.primitivus.progress import Progress from sat_frontends.primitivus.notify import Notify @@ -294,7 +293,6 @@ menu.addMenu(contact, _("Remove contact"), self.onRemoveContactRequest) communication = _("Communication") menu.addMenu(communication, _("Join room"), self.onJoinRoomRequest, 'meta j') - menu.addMenu(communication, _("Find Gateways"), self.onFindGatewaysRequest, 'meta g') #additionals menus #FIXME: do this in a more generic way (in quickapp) add_menus = self.bridge.getMenus('', Const.NO_SECURITY_LIMIT) @@ -496,6 +494,7 @@ self.showPopUp(pop_up_widget) def actionResult(self, type, id, data, profile): + # FIXME: to be removed if not self.check_profile(profile): return @@ -588,12 +587,6 @@ pop_up_widget = sat_widgets.InputDialog(_("Entering a MUC room"), _("Please enter MUC's JID"), default_txt = 'room@muc_service.server.tld', cancel_cb=self.removePopUp, ok_cb=self.onJoinRoom) self.showPopUp(pop_up_widget) - def onFindGatewaysRequest(self, e): - debug(_("Find gateways request")) - id = self.bridge.findGateways(self.profiles[self.profile]['whoami'].domain, self.profile) - self.current_action_ids.add(id) - self.current_action_ids_cb[id] = self.onGatewaysFound - def onAddContactRequest(self, menu): pop_up_widget = sat_widgets.InputDialog(_("Adding a contact"), _("Please enter new contact JID"), default_txt = 'name@server.tld', cancel_cb=self.removePopUp, ok_cb=self.onAddContact) self.showPopUp(pop_up_widget) @@ -611,13 +604,6 @@ #MISC CALLBACKS# - def onGatewaysFound(self, data): - """Called when SàT has found the server gateways""" - target = data['__private__']['target'] - del data['__private__'] - gatewayManager = GatewaysManager(self, data, server=target) - self.addWindow(gatewayManager) - def chatStateReceived(self, from_jid_s, state, profile): """Signal observer to display a contact chat state @param from_jid_s: contact who sent his new state
--- a/frontends/src/quick_frontend/quick_gateways.py Tue Feb 04 18:24:27 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# helper class for making a SAT frontend -# Copyright (C) 2009, 2010, 2011, 2012, 2013 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 sat.core.i18n import _ -class QuickGatewaysManager(object): - - - def __init__(self, host, gateways, title=_("Gateways manager"), server=None): - self.WARNING_MSG = _(u"""Be careful ! Gateways allow you to use an external IM (legacy IM), so you can see your contact as jabber 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, analyzed by the external server, most of time a private company).""") - self.host = host - - def getGatewayDesc(self, gat_type): - """Return a human readable description of gateway type - @param gat_type: type of gateway, as given by SàT""" - desc = _('Unknown IM') - - if gat_type == 'irc': - desc = "Internet Relay Chat" - elif gat_type == 'xmpp': - desc = "XMPP" - elif gat_type == 'qq': - desc = "Tencent QQ" - elif gat_type == 'simple': - desc = "SIP/SIMPLE" - elif gat_type == 'icq': - desc = "ICQ" - elif gat_type == 'yahoo': - desc = "Yahoo! Messenger" - elif gat_type == 'gadu-gadu': - desc = "Gadu-Gadu" - elif gat_type == 'aim': - desc = "AOL Instant Messenger" - elif gat_type == 'msn': - desc = 'Windows Live Messenger' - - return desc
--- a/frontends/src/wix/gateways.py Tue Feb 04 18:24:27 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,168 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# wix: a SAT frontend -# Copyright (C) 2009, 2010, 2011, 2012, 2013 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 sat.core.i18n import _ -import wx -import pdb -from xml.dom import minidom -from logging import debug, info, error -from sat.tools.jid import JID -from sat_frontends.quick_frontend.quick_gateways import QuickGatewaysManager - -class GatewaysManager(wx.Frame,QuickGatewaysManager): - - def __init__(self, host, gateways, title=_("Gateways manager"), server=None): - wx.Frame.__init__(self, None, title=title) - QuickGatewaysManager.__init__(self, host, gateways, server) - - if server: - self.SetTitle(title+" (%s)" % server) - - #Fonts - self.normal_font = wx.Font(8, wx.DEFAULT, wx.NORMAL, wx.NORMAL) - self.bold_font = wx.Font(8, wx.DEFAULT, wx.NORMAL, wx.BOLD) - self.italic_font = wx.Font(8, wx.DEFAULT, wx.FONTSTYLE_ITALIC, wx.NORMAL) - self.button_font = wx.Font(6, wx.DEFAULT, wx.NORMAL, wx.BOLD) - - - self.modified = {} # dict of modified data (i.e. what we have to save) - self.ctl_list = {} # usefull to access ctrl, key = (name, category) - - self.sizer = wx.BoxSizer(wx.VERTICAL) - warning = wx.TextCtrl(self, -1, value=self.WARNING_MSG, style = wx.TE_MULTILINE | - wx.TE_READONLY | - wx.TE_LEFT) - warning.SetFont(self.bold_font) - self.sizer.Add(warning, 0, wx.EXPAND) - warning.ShowPosition(0) - self.panel = wx.Panel(self) - self.panel.sizer = wx.FlexGridSizer(cols=5) - self.panel.SetSizer(self.panel.sizer) - self.panel.SetAutoLayout(True) - self.sizer.Add(self.panel, 1, flag=wx.EXPAND) - self.SetSizer(self.sizer) - self.SetAutoLayout(True) - - #events - self.Bind(wx.EVT_CLOSE, self.onClose, self) - - self.MakeModal() - self.panel.sizer.Add(wx.Window(self.panel, -1)) - title_name = wx.StaticText(self.panel, -1, "Name") - title_name.SetFont(self.bold_font) - title_type = wx.StaticText(self.panel, -1, "Type") - title_type.SetFont(self.bold_font) - self.panel.sizer.Add(title_name) - self.panel.sizer.Add(title_type) - self.panel.sizer.Add(wx.Window(self.panel, -1)) - self.panel.sizer.Add(wx.Window(self.panel, -1)) - - for gateway in gateways: - self.addGateway(gateway, gateways[gateway]) - - self.ext_server_panel = wx.Panel(self) - self.ext_server_panel.sizer = wx.BoxSizer(wx.HORIZONTAL) - self.ext_server_panel.SetSizer(self.ext_server_panel.sizer) - self.ext_server_panel.SetAutoLayout(True) - self.sizer.Add(self.ext_server_panel, 0, flag=wx.EXPAND) - - ext_server_label = wx.StaticText(self.ext_server_panel, -1, _("Use external XMPP server: ")) - ext_server_label.SetFont(wx.ITALIC_FONT) - self.ext_server_text = wx.TextCtrl(self.ext_server_panel, -1) - ext_server_button = wx.Button(self.ext_server_panel, -1, _("GO !")) - self.ext_server_panel.Bind(wx.EVT_BUTTON, self.browseExternalServer, ext_server_button) - - self.ext_server_panel.sizer.Add(ext_server_label) - self.ext_server_panel.sizer.Add(self.ext_server_text, 1, flag=wx.EXPAND) - self.ext_server_panel.sizer.Add(ext_server_button) - - #self.panel.sizer.Fit(self) - self.sizer.Fit(self) - - self.Show() - - def browseExternalServer(self, event): - """Open the gateway manager on given server""" - server = self.ext_server_text.GetValue() - debug(_("Opening gateways manager on [%s]") % server) - id = self.host.bridge.findGateways(server, self.host.profile) - self.host.current_action_ids.add(id) - self.host.current_action_ids_cb[id] = self.host.onGatewaysFound - self.MakeModal(False) - self.Destroy() - - - def addGateway(self, gateway, param): - - #First The icon - isz = (16,16) - im_icon = wx.StaticBitmap(self.panel, -1, wx.ArtProvider.GetBitmap(wx.ART_GO_FORWARD, wx.ART_TOOLBAR, isz)) - - #Then the name - - label=wx.StaticText(self.panel, -1, param['name']) - label.SetFont(self.normal_font) - - #Then the transport type message - type_label_txt = self.getGatewayDesc(param['type']) - - type_label = wx.StaticText(self.panel, -1, type_label_txt) - type_label.SetFont(self.italic_font) - - #The buttons - def register_cb(event): - """Called when register button is clicked""" - gateway_jid = event.GetEventObject().gateway_jid - id = self.host.bridge.in_band_register(gateway_jid, self.host.profile) - self.host.current_action_ids.add(id) - self.MakeModal(False) - self.Destroy() - - def unregister_cb(event): - """Called when unregister button is clicked""" - gateway_jid = event.GetEventObject().gateway_jid - id = self.host.bridge.gatewayRegister("CANCEL",gateway_jid, None, self.host.profile) - self.host.current_action_ids.add(id) - self.MakeModal(False) - self.Destroy() - - reg_button = wx.Button(self.panel, -1, _("Register"), size=wx.Size(-1, 8)) - reg_button.SetFont(self.button_font) - reg_button.gateway_jid = JID(gateway) - self.panel.Bind(wx.EVT_BUTTON, register_cb, reg_button) - unreg_button = wx.Button(self.panel, -1, _("Unregister"), size=wx.Size(-1, 8)) - unreg_button.SetFont(self.button_font) - unreg_button.gateway_jid = JID(gateway) - self.panel.Bind(wx.EVT_BUTTON, unregister_cb, unreg_button) - - self.panel.sizer.Add(im_icon) - self.panel.sizer.Add(label) - self.panel.sizer.Add(type_label) - self.panel.sizer.Add(reg_button, 1, wx.EXPAND) - self.panel.sizer.Add(unreg_button, 1, wx.EXPAND) - - - def onClose(self, event): - """Close event""" - debug(_("close")) - self.MakeModal(False) - event.Skip() -
--- a/frontends/src/wix/main_window.py Tue Feb 04 18:24:27 2014 +0100 +++ b/frontends/src/wix/main_window.py Tue Feb 04 18:26:03 2014 +0100 @@ -25,7 +25,6 @@ from sat_frontends.wix.contact_list import ContactList from sat_frontends.wix.chat import Chat from sat_frontends.wix.xmlui import XMLUI -from sat_frontends.wix.gateways import GatewaysManager from sat_frontends.wix.profile import Profile from sat_frontends.wix.profile_manager import ProfileManager import os.path @@ -42,7 +41,7 @@ idREMOVE_CONTACT,\ idSHOW_PROFILE,\ idJOIN_ROOM,\ -idFIND_GATEWAYS = range(10) += range(9) class ChatList(QuickChatList): """This class manage the list of chat windows""" @@ -131,7 +130,6 @@ contactMenu.Append(idSHOW_PROFILE, _("&Show profile"), _(" Show contact's profile")) communicationMenu = wx.Menu() communicationMenu.Append(idJOIN_ROOM, _("&Join Room"),_(" Join a Multi-User Chat room")) - communicationMenu.Append(idFIND_GATEWAYS, _("&Find Gateways"),_(" Find gateways to legacy IM")) self.menuBar = wx.MenuBar() self.menuBar.Append(connectMenu,_("&General")) self.menuBar.Append(contactMenu,_("&Contacts")) @@ -176,8 +174,6 @@ wx.EVT_MENU(self, idREMOVE_CONTACT, self.onRemoveContact) wx.EVT_MENU(self, idSHOW_PROFILE, self.onShowProfile) wx.EVT_MENU(self, idJOIN_ROOM, self.onJoinRoom) - wx.EVT_MENU(self, idFIND_GATEWAYS, self.onFindGateways) - def newMessage(self, from_jid, to_jid, msg, _type, extra, profile): QuickApp.newMessage(self, from_jid, to_jid, msg, _type, extra, profile) @@ -511,18 +507,6 @@ else: error (_("'%s' is an invalid JID !"), room_jid) - def onFindGateways(self, e): - debug(_("Find Gateways request")) - _id = self.bridge.findGateways(self.profiles[self.profile]['whoami'].domain, self.profile) - self.current_action_ids.add(_id) - self.current_action_ids_cb[_id] = self.onGatewaysFound - - def onGatewaysFound(self, data): - """Called when SàT has found the server gateways""" - target = data['__private__']['target'] - del data['__private__'] - gatewayManager = GatewaysManager(self, data, server=target) - def onClose(self, e): QuickApp.onExit(self) info(_("Exiting..."))
--- a/src/core/sat_main.py Tue Feb 04 18:24:27 2014 +0100 +++ b/src/core/sat_main.py Tue Feb 04 18:26:03 2014 +0100 @@ -154,11 +154,11 @@ self.bridge.register("getParamsCategories", self.memory.getParamsCategories) self.bridge.register("paramsRegisterApp", self.memory.paramsRegisterApp) self.bridge.register("getHistory", self.memory.getHistory) - self.bridge.register("setPresence", self.setPresence) + self.bridge.register("setPresence", self._setPresence) self.bridge.register("subscription", self.subscription) - self.bridge.register("addContact", self.addContact) - self.bridge.register("updateContact", self.updateContact) - self.bridge.register("delContact", self.delContact) + self.bridge.register("addContact", self._addContact) + self.bridge.register("updateContact", self._updateContact) + self.bridge.register("delContact", self._delContact) self.bridge.register("isConnected", self.isConnected) self.bridge.register("launchAction", self.launchCallback) self.bridge.register("confirmationAnswer", self.confirmationAnswer) @@ -423,34 +423,6 @@ else: self.actionResult(action_id, "SUPPRESS", {}, profile) - def submitForm(self, action, target, fields, profile_key): - """submit a form - @param target: target jid where we are submitting - @param fields: list of tuples (name, value) - @return: tuple: (id, deferred) - """ - # FIXME: to be removed - - profile = self.memory.getProfileName(profile_key) - assert(profile) - to_jid = jid.JID(target) - - iq = compat.IQ(self.profiles[profile].xmlstream, 'set') - iq["to"] = target - iq["from"] = self.profiles[profile].jid.full() - query = iq.addElement(('jabber:iq:register', 'query')) - if action == 'SUBMIT': - form = tupleList2dataForm(fields) - query.addChild(form.toElement()) - elif action == 'CANCEL': - query.addElement('remove') - else: - error(_("FIXME FIXME FIXME: Unmanaged action (%s) in submitForm") % action) - raise NotImplementedError - - deferred = iq.send(target) - return (iq['id'], deferred) - ## Client management ## def setParam(self, name, value, category, security_limit, profile_key): @@ -591,13 +563,15 @@ extra=mess_data['extra'], profile=profile) - def setPresence(self, to="", show="", priority=0, statuses=None, profile_key='@NONE@'): + def _setPresence(self, to="", show="", priority=0, statuses=None, profile_key='@NONE@'): + return self.setPresence(jid.JID(to) if to else None, show, priority, statuses, profile_key) + + def setPresence(self, to_jid=None, show="", priority=0, statuses=None, profile_key='@NONE@'): """Send our presence information""" if statuses is None: statuses = {} profile = self.memory.getProfileName(profile_key) assert(profile) - to_jid = jid.JID(to) if to else None self.profiles[profile].presence.available(to_jid, show, statuses, priority) #XXX: FIXME: temporary fix to work around openfire 3.7.0 bug (presence is not broadcasted to generating resource) if '' in statuses: @@ -624,30 +598,36 @@ elif subs_type == "unsubscribed": self.profiles[profile].presence.unsubscribed(to_jid) - def addContact(self, to, profile_key): + def _addContact(self, to_jid_s, profile_key): + return self.addContact(jid.JID(to), profile_key) + + def addContact(self, to_jid, profile_key): """Add a contact in roster list""" profile = self.memory.getProfileName(profile_key) assert(profile) - to_jid = jid.JID(to) #self.profiles[profile].roster.addItem(to_jid) #XXX: disabled (cf http://wokkel.ik.nu/ticket/56)) self.profiles[profile].presence.subscribe(to_jid) - def updateContact(self, to, name, groups, profile_key): + def _updateContact(self, to_jid_s, name, groups, profile_key): + return self.updateContact(jid.JID(to_jid_s), name, groups, profile_key) + + def updateContact(self, to_jid, name, groups, profile_key): """update a contact in roster list""" profile = self.memory.getProfileName(profile_key) assert(profile) - to_jid = jid.JID(to) groups = set(groups) roster_item = RosterItem(to_jid) roster_item.name = name or None roster_item.groups = set(groups) self.profiles[profile].roster.updateItem(roster_item) - def delContact(self, to, profile_key): + def _delContact(self, to_jid_s, profile_key): + return self.delContact(jid.JID(to_jid_s), profile_key) + + def delContact(self, to_jid, profile_key): """Remove contact from roster list""" profile = self.memory.getProfileName(profile_key) assert(profile) - to_jid = jid.JID(to) self.profiles[profile].roster.removeItem(to_jid) self.profiles[profile].presence.unsubscribe(to_jid)
--- a/src/plugins/plugin_xep_0077.py Tue Feb 04 18:24:27 2014 +0100 +++ b/src/plugins/plugin_xep_0077.py Tue Feb 04 18:26:03 2014 +0100 @@ -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 @@ 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): - 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) - - 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): + def _regSuccess(self, answer, client, post_treat_cb): 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"]] + if post_treat_cb is not None: + post_treat_cb(jid.JID(answer['from']), client.profile) + return {} - 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
--- a/src/plugins/plugin_xep_0100.py Tue Feb 04 18:24:27 2014 +0100 +++ b/src/plugins/plugin_xep_0100.py Tue Feb 04 18:26:03 2014 +0100 @@ -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 @@ "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 @@ 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 _gatewaysMenu(self, data, profile): + """ XMLUI activated by menu: return Gateways UI + @param profile: %(doc_profile)s - def __inc_handled_items(self, request_id, profile): - self.__gateways[request_id]['__handled_items'] += 1 + """ + 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 - if self.__gateways[request_id]['__total_items'] == self.__gateways[request_id]['__handled_items']: - debug(_("All items checked for id [%s]") % str(request_id)) + 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 + + def _gatewaySelectedCb(self, data, profile): + try: + target_jid = jid.JID(data['index']) + except (KeyError, RuntimeError): + warning(_("No gateway index selected")) + return {} - 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) + d = self.gatewayRegister(target_jid, profile) + d.addCallback(lambda xmlui: {'xmlui': xmlui.toXml()}) + return d + + def _getIdentityDesc(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': + 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 discoInfo(self, disco, entity, request_id, 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(jid_, profile_key=profile) + self.host.setPresence(jid_, profile_key=profile) + + 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) + d = self.host.plugins["XEP-0077"].inBandRegister(target_jid, self._registrationSuccessful, profile) + return d + + def _infosReceived(self, dl_result, items, target, client): """Find disco infos about entity, to check if it is a gateway""" - 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] - } - - self.__inc_handled_items(request_id, profile) + 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 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 _itemsReceived(self, disco, target, client): + """Look for items with disco protocol, and ask infos for each one""" - 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 - - 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) + return [] - 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) + _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 registrationSuccessful(self, target, 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) - def gatewayRegister(self, action, target, fields, profile_key='@DEFAULT@'): - """Register gateway using in-band registration, then log-in to gateway""" + def _findGateways(self, target_jid_s, profile_key): + target_jid = jid.JID(target_jid_s) 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) + 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_key): + + def findGateways(self, target, profile): """Find gateways in the target JID, using discovery protocol - Return an id used for retrieving the list of gateways """ - profile = self.host.memory.getProfileName(profile_key) - client = self.host.getClient(profile_key) + 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