# HG changeset patch # User Goffi # Date 1546449750 -3600 # Node ID e347e32aa07fd651e3ab799a9776075d1269f29c # Parent e55f871fa9db6b2e1c509fec8c7ee8badf979ade core (memory/encryption): new encryptionNamespaceGet and encryptionTrustUIGet methods: - encryptionNamespaceGet retrieves algorithm namespace from its short name - encryptionTrustUIGet retrieves trust mangement XMLUI from encryption plugin - new markAsUntrusted internal helper method, to add untrusted flag to message data diff -r e55f871fa9db -r e347e32aa07f sat/bridge/bridge_constructor/bridge_template.ini --- a/sat/bridge/bridge_constructor/bridge_template.ini Thu Dec 27 11:40:04 2018 +0100 +++ b/sat/bridge/bridge_constructor/bridge_template.ini Wed Jan 02 18:22:30 2019 +0100 @@ -60,7 +60,7 @@ sig_in=sss doc=A message encryption session has been started doc_param_0=to_jid: JID of the recipient (bare jid if it's encrypted for all devices) -doc_param_1=encryption_data: [JSON_OBJ] data of the encryption algorithm used, encoded as a json object. +doc_param_1=encryption_data: (JSON_OBJ) data of the encryption algorithm used, encoded as a json object. it always has the following keys: - name: human readable name of the algorithm - namespace: namespace of the encryption plugin @@ -445,8 +445,8 @@ param_3_default="@NONE@" doc=Start an encryption session doc_param_0=to_jid: JID of the recipient (bare jid if it must be encrypted for all devices) -doc_param_1=encryption_ns: Namespace of the encryption algorithm to use -doc_param_2=replace: If True and an encryption session already exists, it will be replaced by this one +doc_param_1=namespace: namespace of the encryption algorithm to use +doc_param_2=replace: if True and an encryption session already exists, it will be replaced by this one else a ConflictError will be raised doc_param_3=%(doc_profile_key)s @@ -455,7 +455,7 @@ category=core sig_in=ss sig_out= -doc=Start an encryption session +doc=Stop an encryption session doc_param_0=to_jid: JID of the recipient (full jid if encryption must be stopped for one device only) doc_param_1=%(doc_profile_key)s @@ -467,13 +467,20 @@ doc=Retrieve encryption data for a given entity doc_param_0=to_jid: bare JID of the recipient doc_param_1=%(doc_profile_key)s -doc_return=[JSON_OBJ] empty string if session is unencrypted, else a json encoded objects. +doc_return=(JSON_OBJ) empty string if session is unencrypted, else a json encoded objects. In case of dict, following keys are always present: - name: human readable name of the encryption algorithm - namespace: namespace of the plugin following key can be present if suitable: - directed_devices: list or resource where session is encrypted +[encryptionNamespaceGet] +type=method +category=core +sig_in=s +sig_out=s +doc=Get algorithm namespace from its name + [encryptionPluginsGet] type=method category=core @@ -481,6 +488,18 @@ sig_out=aa{ss} doc=Retrieve registered plugins for encryption +[encryptionTrustUIGet] +async= +type=method +category=core +sig_in=sss +sig_out=s +doc=Get XMLUI to manage trust for given encryption algorithm +doc_param_0=to_jid: bare JID of entity to manage +doc_param_0=namespace: namespace of the algorithm to manage +doc_param_2=%(doc_profile_key)s +doc_return=(XMLUI) UI of the trust management + [setPresence] type=method category=core diff -r e55f871fa9db -r e347e32aa07f sat/bridge/dbus_bridge.py --- a/sat/bridge/dbus_bridge.py Thu Dec 27 11:40:04 2018 +0100 +++ b/sat/bridge/dbus_bridge.py Wed Jan 02 18:22:30 2019 +0100 @@ -272,12 +272,24 @@ return self._callback("disconnect", unicode(profile_key), callback=callback, errback=errback) @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, + in_signature='s', out_signature='s', + async_callbacks=None) + def encryptionNamespaceGet(self, arg_0): + return self._callback("encryptionNamespaceGet", unicode(arg_0)) + + @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, in_signature='', out_signature='aa{ss}', async_callbacks=None) def encryptionPluginsGet(self, ): return self._callback("encryptionPluginsGet", ) @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, + in_signature='sss', out_signature='s', + async_callbacks=('callback', 'errback')) + def encryptionTrustUIGet(self, namespace, arg_1, profile_key, callback=None, errback=None): + return self._callback("encryptionTrustUIGet", unicode(namespace), unicode(arg_1), unicode(profile_key), callback=callback, errback=errback) + + @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, in_signature='ss', out_signature='s', async_callbacks=None) def getConfig(self, section, name): @@ -412,8 +424,8 @@ @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, in_signature='ssbs', out_signature='', async_callbacks=None) - def messageEncryptionStart(self, to_jid, encryption_ns='', replace=False, profile_key="@NONE@"): - return self._callback("messageEncryptionStart", unicode(to_jid), unicode(encryption_ns), replace, unicode(profile_key)) + def messageEncryptionStart(self, to_jid, namespace='', replace=False, profile_key="@NONE@"): + return self._callback("messageEncryptionStart", unicode(to_jid), unicode(namespace), replace, unicode(profile_key)) @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, in_signature='ss', out_signature='', diff -r e55f871fa9db -r e347e32aa07f sat/core/sat_main.py --- a/sat/core/sat_main.py Thu Dec 27 11:40:04 2018 +0100 +++ b/sat/core/sat_main.py Wed Jan 02 18:22:30 2019 +0100 @@ -122,7 +122,10 @@ self._messageEncryptionStop) self.bridge.register_method("messageEncryptionGet", self._messageEncryptionGet) + self.bridge.register_method("encryptionNamespaceGet", + self._encryptionNamespaceGet) self.bridge.register_method("encryptionPluginsGet", self._encryptionPluginsGet) + self.bridge.register_method("encryptionTrustUIGet", self._encryptionTrustUIGet) self.bridge.register_method("getConfig", self._getConfig) self.bridge.register_method("setParam", self.setParam) self.bridge.register_method("getParamA", self.memory.getStringParamA) @@ -670,6 +673,26 @@ def registerEncryptionPlugin(self, *args, **kwargs): return encryption.EncryptionHandler.registerPlugin(*args, **kwargs) + def _messageEncryptionStart(self, to_jid_s, namespace, replace=False, + profile_key=C.PROF_KEY_NONE): + client = self.getClient(profile_key) + to_jid = jid.JID(to_jid_s) + return client.encryption.start(to_jid, namespace or None, replace) + + def _messageEncryptionStop(self, to_jid_s, profile_key=C.PROF_KEY_NONE): + client = self.getClient(profile_key) + to_jid = jid.JID(to_jid_s) + return client.encryption.stop(to_jid) + + def _messageEncryptionGet(self, to_jid_s, profile_key=C.PROF_KEY_NONE): + client = self.getClient(profile_key) + to_jid = jid.JID(to_jid_s) + session_data = client.encryption.getSession(to_jid) + return client.encryption.getBridgeData(session_data) + + def _encryptionNamespaceGet(self, name): + return encryption.EncryptionHandler.getNSFromName(name) + def _encryptionPluginsGet(self): plugins = encryption.EncryptionHandler.getPlugins() ret = [] @@ -681,6 +704,13 @@ }) return ret + def _encryptionTrustUIGet(self, to_jid_s, namespace, profile_key): + client = self.getClient(profile_key) + to_jid = jid.JID(to_jid_s) + d = client.encryption.getTrustUI(to_jid, namespace=namespace or None) + d.addCallback(lambda xmlui: xmlui.toXml()) + return d + ## XMPP methods ## def _messageSend(self, to_jid_s, message, subject=None, mess_type="auto", extra=None, @@ -697,23 +727,6 @@ {unicode(key): unicode(value) for key, value in extra.items()}, ) - def _messageEncryptionStart(self, to_jid_s, encryption_ns, replace=False, - profile_key=C.PROF_KEY_NONE): - client = self.getClient(profile_key) - to_jid = jid.JID(to_jid_s) - return client.encryption.start(to_jid, encryption_ns.strip() or None, replace) - - def _messageEncryptionStop(self, to_jid_s, profile_key=C.PROF_KEY_NONE): - client = self.getClient(profile_key) - to_jid = jid.JID(to_jid_s) - return client.encryption.stop(to_jid) - - def _messageEncryptionGet(self, to_jid_s, profile_key=C.PROF_KEY_NONE): - client = self.getClient(profile_key) - to_jid = jid.JID(to_jid_s) - session_data = client.encryption.getSession(to_jid) - return client.encryption.getBridgeData(session_data) - def _setPresence(self, to="", show="", statuses=None, profile_key=C.PROF_KEY_NONE): return self.setPresence(jid.JID(to) if to else None, show, statuses, profile_key) @@ -1051,7 +1064,7 @@ with_data(bool): True if the callback use the optional data dict force_id(unicode): id to avoid generated id. Can lead to name conflict, avoid if possible - one_shot(bool): True to delete callback once it have been called + one_shot(bool): True to delete callback once it has been called @return: id of the registered callback """ callback_id = kwargs.pop("force_id", None) diff -r e55f871fa9db -r e347e32aa07f sat/memory/encryption.py --- a/sat/memory/encryption.py Thu Dec 27 11:40:04 2018 +0100 +++ b/sat/memory/encryption.py Wed Jan 02 18:22:30 2019 +0100 @@ -51,8 +51,10 @@ @param plg_instance(object): instance of the plugin it must have the following methods: - - startEncryption(jid.JID): start an encryption session with a bare jid - - stopEncryption(jid.JID): stop an encryption session with a bare jid + - getTrustUI(entity): return a XMLUI for trust management + entity(jid.JID): entity to manage + The returned XMLUI must be a form + @param name(unicode): human readable name of the encryption algorithm @param namespace(unicode): namespace of the encryption algorithm @param priority(int): priority of this plugin to encrypt an message when not @@ -84,6 +86,15 @@ return cls.plugins @classmethod + def getPlugin(cls, namespace): + try: + return next(p for p in cls.plugins if p.namespace == namespace) + except StopIteration: + raise exceptions.NotFound(_( + u"Can't find requested encryption plugin: {namespace}").format( + namespace=namespace)) + + @classmethod def getNSFromName(cls, name): """Retrieve plugin namespace from its name @@ -94,7 +105,9 @@ for p in cls.plugins: if p.name.lower() == name.lower(): return p.namespace - raise exceptions.NotFound + raise exceptions.NotFound(_( + u"Can't find a plugin with the name \"{name}\".".format( + name=name))) def getBridgeData(self, session): """Retrieve session data serialized for bridge. @@ -117,7 +130,8 @@ @param entity(jid.JID): entity to start an encryption session with must be bare jid is the algorithm encrypt for all devices - @param namespace(unicode, None): namespace of the encryption algorithm to use + @param namespace(unicode, None): namespace of the encryption algorithm + to use. None to select automatically an algorithm @param replace(bool): if True and an encrypted session already exists, it will be replaced by the new one @@ -129,12 +143,7 @@ if namespace is None: plugin = self.plugins[0] else: - try: - plugin = next(p for p in self.plugins if p.namespace == namespace) - except StopIteration: - raise exceptions.NotFound(_( - u"Can't find requested encryption plugin: {namespace}").format( - namespace=namespace)) + plugin = self.getPlugin(namespace) bare_jid = entity.userhostJID() if bare_jid in self._sessions: @@ -142,7 +151,8 @@ former_plugin = self._sessions[bare_jid]['plugin'] if former_plugin.namespace == namespace: log.info(_(u"Session with {bare_jid} is already encrypted with {name}. " - u"Nothing to do.").format(bare_jid=bare_jid, name=plugin.name)) + u"Nothing to do.").format( + bare_jid=bare_jid, name=former_plugin.name)) return if replace: @@ -255,9 +265,39 @@ None if there is not encryption for this session with this jid """ if entity.resource: - raise exceptions.InternalError(u"Full jid given when expecting bare jid") + raise ValueError(u"Full jid given when expecting bare jid") return self._sessions.get(entity) + def getTrustUI(self, entity_jid, namespace=None): + """Retrieve encryption UI + + @param entity_jid(jid.JID): get the UI for this entity + must be a bare jid + @param namespace(unicode): namespace of the algorithm to manage + if None use current algorithm + @return D(xmlui): XMLUI for trust management + the xmlui is a form + None if there is not encryption for this session with this jid + @raise exceptions.NotFound: no algorithm/plugin found + @raise NotImplementedError: plugin doesn't handle UI management + """ + if namespace is None: + session = self.getSession(entity_jid) + if not session: + raise exceptions.NotFound( + u"No encryption session currently active for {entity_jid}" + .format(entity_jid=entity_jid.full())) + plugin = session['plugin'] + else: + plugin = self.getPlugin(namespace) + try: + get_trust_ui = plugin.instance.getTrustUI + except AttributeError: + raise NotImplementedError( + u"Encryption plugin doesn't handle trust management UI") + else: + return get_trust_ui(self.client, entity_jid) + ## Triggers ## def setEncryptionFlag(self, mess_data): @@ -283,3 +323,13 @@ """ mess_data['encrypted'] = True return mess_data + + def markAsUntrusted(self, mess_data): + """Helper methor to mark a message as sent from an untrusted entity. + + This should be used in the post_treat workflow of MessageReceived trigger of + the plugin + @param mess_data(dict): message data as used in post treat workflow + """ + mess_data['untrusted'] = True + return mess_data diff -r e55f871fa9db -r e347e32aa07f sat_frontends/bridge/dbus_bridge.py --- a/sat_frontends/bridge/dbus_bridge.py Thu Dec 27 11:40:04 2018 +0100 +++ b/sat_frontends/bridge/dbus_bridge.py Wed Jan 02 18:22:30 2019 +0100 @@ -245,6 +245,20 @@ error_handler = lambda err:errback(dbus_to_bridge_exception(err)) return self.db_core_iface.disconnect(profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler) + def encryptionNamespaceGet(self, arg_0, callback=None, errback=None): + if callback is None: + error_handler = None + else: + if errback is None: + errback = log.error + error_handler = lambda err:errback(dbus_to_bridge_exception(err)) + kwargs={} + if callback is not None: + kwargs['timeout'] = const_TIMEOUT + kwargs['reply_handler'] = callback + kwargs['error_handler'] = error_handler + return unicode(self.db_core_iface.encryptionNamespaceGet(arg_0, **kwargs)) + def encryptionPluginsGet(self, callback=None, errback=None): if callback is None: error_handler = None @@ -259,6 +273,15 @@ kwargs['error_handler'] = error_handler return self.db_core_iface.encryptionPluginsGet(**kwargs) + def encryptionTrustUIGet(self, namespace, arg_1, profile_key, callback=None, errback=None): + if callback is None: + error_handler = None + else: + if errback is None: + errback = log.error + error_handler = lambda err:errback(dbus_to_bridge_exception(err)) + return unicode(self.db_core_iface.encryptionTrustUIGet(namespace, arg_1, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)) + def getConfig(self, section, name, callback=None, errback=None): if callback is None: error_handler = None @@ -532,7 +555,7 @@ kwargs['error_handler'] = error_handler return unicode(self.db_core_iface.messageEncryptionGet(to_jid, profile_key, **kwargs)) - def messageEncryptionStart(self, to_jid, encryption_ns='', replace=False, profile_key="@NONE@", callback=None, errback=None): + def messageEncryptionStart(self, to_jid, namespace='', replace=False, profile_key="@NONE@", callback=None, errback=None): if callback is None: error_handler = None else: @@ -544,7 +567,7 @@ kwargs['timeout'] = const_TIMEOUT kwargs['reply_handler'] = callback kwargs['error_handler'] = error_handler - return self.db_core_iface.messageEncryptionStart(to_jid, encryption_ns, replace, profile_key, **kwargs) + return self.db_core_iface.messageEncryptionStart(to_jid, namespace, replace, profile_key, **kwargs) def messageEncryptionStop(self, to_jid, profile_key, callback=None, errback=None): if callback is None: diff -r e55f871fa9db -r e347e32aa07f sat_frontends/bridge/pb.py --- a/sat_frontends/bridge/pb.py Thu Dec 27 11:40:04 2018 +0100 +++ b/sat_frontends/bridge/pb.py Wed Jan 02 18:22:30 2019 +0100 @@ -214,6 +214,14 @@ errback = self._generic_errback d.addErrback(errback) + def encryptionNamespaceGet(self, arg_0, callback=None, errback=None): + d = self.root.callRemote("encryptionNamespaceGet", arg_0) + if callback is not None: + d.addCallback(callback) + if errback is None: + errback = self._generic_errback + d.addErrback(errback) + def encryptionPluginsGet(self, callback=None, errback=None): d = self.root.callRemote("encryptionPluginsGet") if callback is not None: @@ -222,6 +230,14 @@ errback = self._generic_errback d.addErrback(errback) + def encryptionTrustUIGet(self, namespace, arg_1, profile_key, callback=None, errback=None): + d = self.root.callRemote("encryptionTrustUIGet", namespace, arg_1, profile_key) + if callback is not None: + d.addCallback(callback) + if errback is None: + errback = self._generic_errback + d.addErrback(errback) + def getConfig(self, section, name, callback=None, errback=None): d = self.root.callRemote("getConfig", section, name) if callback is not None: @@ -398,8 +414,8 @@ errback = self._generic_errback d.addErrback(errback) - def messageEncryptionStart(self, to_jid, encryption_ns='', replace=False, profile_key="@NONE@", callback=None, errback=None): - d = self.root.callRemote("messageEncryptionStart", to_jid, encryption_ns, replace, profile_key) + def messageEncryptionStart(self, to_jid, namespace='', replace=False, profile_key="@NONE@", callback=None, errback=None): + d = self.root.callRemote("messageEncryptionStart", to_jid, namespace, replace, profile_key) if callback is not None: d.addCallback(lambda dummy: callback()) if errback is None: