changeset 2733:e347e32aa07f

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
author Goffi <goffi@goffi.org>
date Wed, 02 Jan 2019 18:22:30 +0100
parents e55f871fa9db
children 9702025f6dff
files sat/bridge/bridge_constructor/bridge_template.ini sat/bridge/dbus_bridge.py sat/core/sat_main.py sat/memory/encryption.py sat_frontends/bridge/dbus_bridge.py sat_frontends/bridge/pb.py
diffstat 6 files changed, 174 insertions(+), 41 deletions(-) [+]
line wrap: on
line diff
--- 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
--- 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='',
--- 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)
--- 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
--- 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:
--- 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: