quick frontend (app, chat): encryption handling:
- new QuickApp.ENCRYPTION_HANDLERS class attribute, if True (default), encryption handlers are set - encryption plugins are retrieved on startup and cached in QuickApp.encryption_plugins list - QuickChat's encrypted boolean attribute indicate if a session is currently encrypted. This is updated automatically if ENCRYPTION_HANDLERS is set - if ENCRYPTION_HANDLERS is set, messageEncryptionStarted and messageEncryptionStopped are called when suitable.
This commit is contained in:
parent
3fc6de121a
commit
1cb7461f99
2 changed files with 121 additions and 57 deletions
|
|
@ -142,8 +142,12 @@ class ProfileManager(object):
|
|||
def _plug_profile_getFeaturesCb(self, features):
|
||||
self.host.features = features
|
||||
# FIXME: we don't use cached value at the moment, but keep the code for later use
|
||||
# it was previously used for avatars, but as we don't get full path here, it's better to request later
|
||||
# self.host.bridge.getEntitiesData([], ProfileManager.cache_keys_to_get, profile=self.profile, callback=self._plug_profile_gotCachedValues, errback=self._plug_profile_failedCachedValues)
|
||||
# it was previously used for avatars, but as we don't get full path here,
|
||||
# it's better to request later
|
||||
# self.host.bridge.getEntitiesData([], ProfileManager.cache_keys_to_get,
|
||||
# profile=self.profile,
|
||||
# callback=self._plug_profile_gotCachedValues,
|
||||
# errback=self._plug_profile_failedCachedValues)
|
||||
self._plug_profile_gotCachedValues({})
|
||||
|
||||
def _plug_profile_failedCachedValues(self, failure):
|
||||
|
|
@ -273,13 +277,15 @@ class QuickApp(object):
|
|||
|
||||
MB_HANDLER = True # Set to False if the frontend doesn't manage microblog
|
||||
AVATARS_HANDLER = True # set to False if avatars are not used
|
||||
ENCRYPTION_HANDLERS = True # set to False if encryption is handled separatly
|
||||
|
||||
def __init__(self, bridge_factory, xmlui, check_options=None, connect_bridge=True):
|
||||
"""Create a frontend application
|
||||
|
||||
@param bridge_factory: method to use to create the Bridge
|
||||
@param xmlui: xmlui module
|
||||
@param check_options: method to call to check options (usually command line arguments)
|
||||
@param check_options: method to call to check options (usually command line
|
||||
arguments)
|
||||
"""
|
||||
self.xmlui = xmlui
|
||||
self.menus = quick_menus.QuickMenusManager(self)
|
||||
|
|
@ -289,7 +295,8 @@ class QuickApp(object):
|
|||
set()
|
||||
) # profiles currently being plugged, used to (un)lock contact list updates
|
||||
self.ready_profiles = set() # profiles which are connected and ready
|
||||
self.signals_cache = {} # used to keep signal received between start of plug_profile and when the profile is actualy ready
|
||||
self.signals_cache = {} # used to keep signal received between start of
|
||||
# plug_profile and when the profile is actualy ready
|
||||
self.contact_lists = quick_contact_list.QuickContactListHandler(self)
|
||||
self.widgets = quick_widgets.QuickWidgetsManager(self)
|
||||
if check_options is not None:
|
||||
|
|
@ -303,7 +310,8 @@ class QuickApp(object):
|
|||
) # widget currently selected (must be filled by frontend)
|
||||
|
||||
# listeners
|
||||
self._listeners = {} # key: listener type ("avatar", "selected", etc), value: list of callbacks
|
||||
self._listeners = {} # key: listener type ("avatar", "selected", etc),
|
||||
# value: list of callbacks
|
||||
|
||||
# triggers
|
||||
self.trigger = (
|
||||
|
|
@ -320,6 +328,7 @@ class QuickApp(object):
|
|||
self._notifications = OrderedDict()
|
||||
self.features = None
|
||||
self.ns_map = {} # map of short name to namespaces
|
||||
self.encryption_plugins = []
|
||||
|
||||
def connectBridge(self):
|
||||
self.bridge.bridgeConnect(callback=self._bridgeCb, errback=self._bridgeEb)
|
||||
|
|
@ -330,10 +339,20 @@ class QuickApp(object):
|
|||
def _namespacesGetEb(self, failure_):
|
||||
log.error(_(u"Can't get namespaces map: {msg}").format(msg=failure_))
|
||||
|
||||
def _encryptionPluginsGetCb(self, plugins):
|
||||
self.encryption_plugins = plugins
|
||||
|
||||
def _encryptionPluginsGetEb(self, failure_):
|
||||
log.warning(_(u"Can't retrieve encryption plugins: {msg}").format(msg=failure_))
|
||||
|
||||
def onBridgeConnected(self):
|
||||
self.bridge.namespacesGet(
|
||||
callback=self._namespacesGetCb, errback=self._namespacesGetEb
|
||||
)
|
||||
callback=self._namespacesGetCb, errback=self._namespacesGetEb)
|
||||
# we cache available encryption plugins, as we'll use them on earch
|
||||
# new chat widget
|
||||
self.bridge.encryptionPluginsGet(
|
||||
callback=self._encryptionPluginsGetCb,
|
||||
errback=self._encryptionPluginsGetEb)
|
||||
|
||||
def _bridgeCb(self):
|
||||
self.registerSignal("connected")
|
||||
|
|
@ -341,6 +360,9 @@ class QuickApp(object):
|
|||
self.registerSignal("actionNew")
|
||||
self.registerSignal("newContact")
|
||||
self.registerSignal("messageNew")
|
||||
if self.ENCRYPTION_HANDLERS:
|
||||
self.registerSignal("messageEncryptionStarted")
|
||||
self.registerSignal("messageEncryptionStopped")
|
||||
self.registerSignal("presenceUpdate")
|
||||
self.registerSignal("subscribe")
|
||||
self.registerSignal("paramUpdate")
|
||||
|
|
@ -395,9 +417,11 @@ class QuickApp(object):
|
|||
"""Register a handler for a signal
|
||||
|
||||
@param function_name (str): name of the signal to handle
|
||||
@param handler (instancemethod): method to call when the signal arrive, None for calling an automatically named handler (function_name + 'Handler')
|
||||
@param handler (instancemethod): method to call when the signal arrive,
|
||||
None for calling an automatically named handler (function_name + 'Handler')
|
||||
@param iface (str): interface of the bridge to use ('core' or 'plugin')
|
||||
@param with_profile (boolean): True if the signal concerns a specific profile, in that case the profile name has to be passed by the caller
|
||||
@param with_profile (boolean): True if the signal concerns a specific profile,
|
||||
in that case the profile name has to be passed by the caller
|
||||
"""
|
||||
log.debug(u"registering signal {name}".format(name=function_name))
|
||||
if handler is None:
|
||||
|
|
@ -415,7 +439,8 @@ class QuickApp(object):
|
|||
if profile is not None:
|
||||
if not self.check_profile(profile):
|
||||
if profile in self.profiles:
|
||||
# profile is not ready but is in self.profiles, that's mean that it's being connecting and we need to cache the signal
|
||||
# profile is not ready but is in self.profiles, that's mean that
|
||||
# it's being connecting and we need to cache the signal
|
||||
self.signals_cache.setdefault(profile, []).append(
|
||||
(function_name, handler, args, kwargs)
|
||||
)
|
||||
|
|
@ -427,7 +452,8 @@ class QuickApp(object):
|
|||
def addListener(self, type_, callback, profiles_filter=None):
|
||||
"""Add a listener for an event
|
||||
|
||||
/!\ don't forget to remove listener when not used anymore (e.g. if you delete a widget)
|
||||
/!\ don't forget to remove listener when not used anymore (e.g. if you delete a
|
||||
widget)
|
||||
@param type_: type of event, can be:
|
||||
- avatar: called when avatar data is updated
|
||||
args: (entity, avatar file, profile)
|
||||
|
|
@ -444,7 +470,8 @@ class QuickApp(object):
|
|||
type_: same as in [sat.core.sat_main.SAT.importMenu]
|
||||
path: same as in [sat.core.sat_main.SAT.importMenu]
|
||||
path_i18n: translated path (or None if the item is removed)
|
||||
item: instance of quick_menus.MenuItemBase or None if the item is removed
|
||||
item: instance of quick_menus.MenuItemBase or None if the item is
|
||||
removed
|
||||
- gotMenus: called only once when menu are available (no arg)
|
||||
- progressFinished: called when a progressing action has just finished
|
||||
args: (progress_id, metadata, profile)
|
||||
|
|
@ -491,18 +518,22 @@ class QuickApp(object):
|
|||
return profile in self.ready_profiles
|
||||
|
||||
def postInit(self, profile_manager):
|
||||
"""Must be called after initialization is done, do all automatic task (auto plug profile)
|
||||
"""Must be called after initialization is done, do all automatic task
|
||||
|
||||
@param profile_manager: instance of a subclass of Quick_frontend.QuickProfileManager
|
||||
(auto plug profile)
|
||||
@param profile_manager: instance of a subclass of
|
||||
Quick_frontend.QuickProfileManager
|
||||
"""
|
||||
if self.options and self.options.profile:
|
||||
profile_manager.autoconnect([self.options.profile])
|
||||
|
||||
def profilePlugged(self, profile):
|
||||
"""Method called when the profile is fully plugged, to launch frontend specific workflow
|
||||
"""Method called when the profile is fully plugged
|
||||
|
||||
/!\ if you override the method and don't call the parent, be sure to add the profile to ready_profiles !
|
||||
if you don't, all signals will stay in cache
|
||||
This will launch frontend specific workflow
|
||||
|
||||
/!\ if you override the method and don't call the parent, be sure to add the
|
||||
profile to ready_profiles ! if you don't, all signals will stay in cache
|
||||
|
||||
@param profile(unicode): %(doc_profile)s
|
||||
"""
|
||||
|
|
@ -608,9 +639,8 @@ class QuickApp(object):
|
|||
groups = list(groups)
|
||||
self.contact_lists[profile].setContact(entity, groups, attributes, in_roster=True)
|
||||
|
||||
def messageNewHandler(
|
||||
self, uid, timestamp, from_jid_s, to_jid_s, msg, subject, type_, extra, profile
|
||||
):
|
||||
def messageNewHandler(self, uid, timestamp, from_jid_s, to_jid_s, msg, subject, type_,
|
||||
extra, profile):
|
||||
from_jid = jid.JID(from_jid_s)
|
||||
to_jid = jid.JID(to_jid_s)
|
||||
if not self.trigger.point(
|
||||
|
|
@ -631,7 +661,8 @@ class QuickApp(object):
|
|||
target = to_jid if from_me else from_jid
|
||||
contact_list = self.contact_lists[profile]
|
||||
if target.resource and not contact_list.isRoom(target.bare):
|
||||
# we avoid resource locking, but we must keep resource for private MUC messages
|
||||
# we avoid resource locking, but we must keep resource for private MUC
|
||||
# messages
|
||||
target = target.bare
|
||||
# we want to be sure to have at least one QuickChat instance
|
||||
self.widgets.getOrCreateWidget(
|
||||
|
|
@ -658,6 +689,21 @@ class QuickApp(object):
|
|||
uid, timestamp, from_jid, target, msg, subject, type_, extra, profile
|
||||
)
|
||||
|
||||
def messageEncryptionStartedHandler(self, destinee_jid_s, plugin_data, profile):
|
||||
destinee_jid = jid.JID(destinee_jid_s)
|
||||
plugin_data = data_format.deserialise(plugin_data)
|
||||
for widget in self.widgets.getWidgets(quick_chat.QuickChat,
|
||||
target=destinee_jid.bare,
|
||||
profiles=(profile,)):
|
||||
widget.messageEncryptionStarted(plugin_data)
|
||||
|
||||
def messageEncryptionStoppedHandler(self, destinee_jid_s, plugin_data, profile):
|
||||
destinee_jid = jid.JID(destinee_jid_s)
|
||||
for widget in self.widgets.getWidgets(quick_chat.QuickChat,
|
||||
target=destinee_jid.bare,
|
||||
profiles=(profile,)):
|
||||
widget.messageEncryptionStopped(plugin_data)
|
||||
|
||||
def messageStateHandler(self, uid, status, profile):
|
||||
for widget in self.widgets.getWidgets(quick_chat.QuickChat, profiles=(profile,)):
|
||||
widget.onMessageState(uid, status, profile)
|
||||
|
|
@ -680,7 +726,8 @@ class QuickApp(object):
|
|||
if callback is None:
|
||||
callback = (
|
||||
lambda dummy=None: None
|
||||
) # FIXME: optional argument is here because pyjamas doesn't support callback without arg with json proxy
|
||||
) # FIXME: optional argument is here because pyjamas doesn't support callback
|
||||
# without arg with json proxy
|
||||
if errback is None:
|
||||
errback = lambda failure: self.showDialog(
|
||||
failure.fullname, failure.message, "error"
|
||||
|
|
@ -716,7 +763,8 @@ class QuickApp(object):
|
|||
def presenceUpdateHandler(self, entity_s, show, priority, statuses, profile):
|
||||
log.debug(
|
||||
_(
|
||||
u"presence update for %(entity)s (show=%(show)s, priority=%(priority)s, statuses=%(statuses)s) [profile:%(profile)s]"
|
||||
u"presence update for %(entity)s (show=%(show)s, priority=%(priority)s, "
|
||||
u"statuses=%(statuses)s) [profile:%(profile)s]"
|
||||
)
|
||||
% {
|
||||
"entity": entity_s,
|
||||
|
|
@ -1083,7 +1131,8 @@ class QuickApp(object):
|
|||
):
|
||||
"""Handle backend action
|
||||
|
||||
@param action_data(dict): action dict as sent by launchAction or returned by an UI action
|
||||
@param action_data(dict): action dict as sent by launchAction or returned by an
|
||||
UI action
|
||||
@param callback(None, callback): if not None, callback to use on XMLUI answer
|
||||
@param ui_show_cb(None, callback): if not None, method to call to show the XMLUI
|
||||
@param user_action(bool): if True, the action is a result of a user interaction
|
||||
|
|
@ -1216,7 +1265,8 @@ class QuickApp(object):
|
|||
with current vCard based implementation, it's better to keep True
|
||||
except if we request avatars for roster items
|
||||
@param hash_only(bool): if True avatar hash is returned, else full path
|
||||
@param ignore_cache(bool): if False, won't check local cache and will request backend in every case
|
||||
@param ignore_cache(bool): if False, won't check local cache and will request
|
||||
backend in every case
|
||||
@return (unicode, None): avatar full path (None if no avatar found)
|
||||
"""
|
||||
contact_list = self.contact_lists[profile]
|
||||
|
|
@ -1238,8 +1288,8 @@ class QuickApp(object):
|
|||
),
|
||||
errback=lambda failure: self._avatarGetEb(failure, entity, contact_list),
|
||||
)
|
||||
# we set avatar to empty string to avoid requesting several time the same avatar
|
||||
# while we are waiting for avatarGet result
|
||||
# we set avatar to empty string to avoid requesting several time the same
|
||||
# avatar while we are waiting for avatarGet result
|
||||
contact_list.setCache(entity, "avatar", "")
|
||||
return avatar
|
||||
|
||||
|
|
|
|||
|
|
@ -19,14 +19,14 @@
|
|||
|
||||
from sat.core.i18n import _
|
||||
from sat.core.log import getLogger
|
||||
|
||||
log = getLogger(__name__)
|
||||
from sat.tools.common import data_format
|
||||
from sat.core import exceptions
|
||||
from sat_frontends.quick_frontend import quick_widgets
|
||||
from sat_frontends.quick_frontend.constants import Const as C
|
||||
from collections import OrderedDict
|
||||
from sat_frontends.tools import jid
|
||||
import time
|
||||
log = getLogger(__name__)
|
||||
|
||||
try:
|
||||
from locale import getlocale
|
||||
|
|
@ -56,19 +56,8 @@ except (
|
|||
class Message(object):
|
||||
"""Message metadata"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parent,
|
||||
uid,
|
||||
timestamp,
|
||||
from_jid,
|
||||
to_jid,
|
||||
msg,
|
||||
subject,
|
||||
type_,
|
||||
extra,
|
||||
profile,
|
||||
):
|
||||
def __init__(self, parent, uid, timestamp, from_jid, to_jid, msg, subject, type_,
|
||||
extra, profile):
|
||||
self.parent = parent
|
||||
self.profile = profile
|
||||
self.uid = uid
|
||||
|
|
@ -269,18 +258,11 @@ class Occupant(object):
|
|||
class QuickChat(quick_widgets.QuickWidget):
|
||||
visible_states = ["chat_state"] # FIXME: to be removed, used only in quick_games
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host,
|
||||
target,
|
||||
type_=C.CHAT_ONE2ONE,
|
||||
nick=None,
|
||||
occupants=None,
|
||||
subject=None,
|
||||
profiles=None,
|
||||
):
|
||||
def __init__(self, host, target, type_=C.CHAT_ONE2ONE, nick=None, occupants=None,
|
||||
subject=None, profiles=None):
|
||||
"""
|
||||
@param type_: can be C.CHAT_ONE2ONE for single conversation or C.CHAT_GROUP for chat à la IRC
|
||||
@param type_: can be C.CHAT_ONE2ONE for single conversation or C.CHAT_GROUP for
|
||||
chat à la IRC
|
||||
"""
|
||||
self.lang = "" # default language to use for messages
|
||||
quick_widgets.QuickWidget.__init__(self, host, target, profiles=profiles)
|
||||
|
|
@ -290,6 +272,7 @@ class QuickChat(quick_widgets.QuickWidget):
|
|||
assert type_ in (C.CHAT_ONE2ONE, C.CHAT_GROUP)
|
||||
self.current_target = target
|
||||
self.type = type_
|
||||
self.encrypted = False # True if this session is currently encrypted
|
||||
if type_ == C.CHAT_GROUP:
|
||||
if target.resource:
|
||||
raise exceptions.InternalError(
|
||||
|
|
@ -332,6 +315,8 @@ class QuickChat(quick_widgets.QuickWidget):
|
|||
self.historyPrint(profile=self.profile)
|
||||
if self.subject is not None:
|
||||
self.setSubject(self.subject)
|
||||
if self.host.ENCRYPTION_HANDLERS:
|
||||
self.getEncryptionState()
|
||||
|
||||
def onDelete(self):
|
||||
if self.host.AVATARS_HANDLER:
|
||||
|
|
@ -565,9 +550,27 @@ class QuickChat(quick_widgets.QuickWidget):
|
|||
errback=_historyGetEb,
|
||||
)
|
||||
|
||||
def messageNew(
|
||||
self, uid, timestamp, from_jid, to_jid, msg, subject, type_, extra, profile
|
||||
):
|
||||
def messageEncryptionGetCb(self, session_data):
|
||||
if session_data:
|
||||
session_data = data_format.deserialise(session_data)
|
||||
self.messageEncryptionStarted(session_data)
|
||||
|
||||
def messageEncryptionGetEb(self, failure_):
|
||||
log.error(_(u"Can't get encryption state: {reason}").format(reason=failure_))
|
||||
|
||||
def getEncryptionState(self):
|
||||
"""Retrieve encryption state with current target.
|
||||
|
||||
Once state is retrieved, default messageEncryptionStarted will be called if
|
||||
suitable
|
||||
"""
|
||||
self.host.bridge.messageEncryptionGet(self.target, self.profile,
|
||||
callback=self.messageEncryptionGetCb,
|
||||
errback=self.messageEncryptionGetEb)
|
||||
|
||||
|
||||
def messageNew(self, uid, timestamp, from_jid, to_jid, msg, subject, type_, extra,
|
||||
profile):
|
||||
if self._locked:
|
||||
self._cache[uid] = (
|
||||
uid,
|
||||
|
|
@ -583,7 +586,8 @@ class QuickChat(quick_widgets.QuickWidget):
|
|||
return
|
||||
if self.type == C.CHAT_GROUP:
|
||||
if to_jid.resource and type_ != C.MESS_TYPE_GROUPCHAT:
|
||||
# we have a private message, we forward it to a private conversation widget
|
||||
# we have a private message, we forward it to a private conversation
|
||||
# widget
|
||||
chat_widget = self.getOrCreatePrivateWidget(to_jid)
|
||||
chat_widget.messageNew(
|
||||
uid, timestamp, from_jid, to_jid, msg, subject, type_, extra, profile
|
||||
|
|
@ -612,6 +616,16 @@ class QuickChat(quick_widgets.QuickWidget):
|
|||
log.warning(u"Delayed message received after history, this should not happen")
|
||||
self.createMessage(message)
|
||||
|
||||
def messageEncryptionStarted(self, session_data):
|
||||
self.encrypted = True
|
||||
log.debug(_(u"message encryption started with {target} using {encryption}").format(
|
||||
target=self.target, encryption=session_data[u'name']))
|
||||
|
||||
def messageEncryptionStopped(self, session_data):
|
||||
self.encrypted = False
|
||||
log.debug(_(u"message encryption stopped with {target} (was using {encryption})")
|
||||
.format(target=self.target, encryption=session_data[u'name']))
|
||||
|
||||
def createMessage(self, message, append=False):
|
||||
"""Must be implemented by frontend to create and show a new message widget
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue