diff sat/memory/encryption.py @ 2743:da59ff099b32

core (memory/encryption), plugin OTR: finished OTR integration in encryption: - messageEncryptionStart and messageEncryptionStop are now async - an encryption plugin can now have a startEncryption and endEncryption method, which are called automatically when suitable - trust is now put in "trusted" key of message_data, and markAsTrusted has been added, it is used in OTR - getTrustUI implemented for OTR, using legacy authenticate code - catch potr.crypt.InvalidParameterError on decryption - fixed some service OTR messages which were not correctly marked as not for storing, and thus were added to MAM archive/carbon copied - other bug fixes
author Goffi <goffi@goffi.org>
date Thu, 03 Jan 2019 21:00:00 +0100
parents e347e32aa07f
children 4b8271399f67
line wrap: on
line diff
--- a/sat/memory/encryption.py	Thu Jan 03 20:51:08 2019 +0100
+++ b/sat/memory/encryption.py	Thu Jan 03 21:00:00 2019 +0100
@@ -22,9 +22,13 @@
 from sat.core import exceptions
 from collections import namedtuple
 from sat.core.log import getLogger
+from sat.tools.common import data_format
+from twisted.internet import defer
+from twisted.python import failure
+import copy
 log = getLogger(__name__)
-from sat.tools.common import data_format
 
+log = getLogger(__name__)
 
 EncryptionPlugin = namedtuple("EncryptionPlugin", ("instance",
                                                    "name",
@@ -54,6 +58,12 @@
                 - getTrustUI(entity): return a XMLUI for trust management
                     entity(jid.JID): entity to manage
                     The returned XMLUI must be a form
+            if may have the following methods:
+                - startEncryption(entity): start encrypted session
+                    entity(jid.JID): entity to start encrypted session with
+                - stopEncryption(entity): start encrypted session
+                    entity(jid.JID): entity to stop encrypted session with
+            if they don't exists, those 2 methods will be ignored.
 
         @param name(unicode): human readable name of the encryption algorithm
         @param namespace(unicode): namespace of the encryption algorithm
@@ -125,6 +135,39 @@
 
         return data_format.serialise(bridge_data)
 
+    def _startEncryption(self, plugin, entity):
+        """Start encryption with a plugin
+
+        This method must be called just before adding a plugin session.
+        StartEncryptionn method of plugin will be called if it exists.
+        """
+        try:
+            start_encryption = plugin.instance.startEncryption
+        except AttributeError:
+            log.debug(u"No startEncryption method found for {plugin}".format(
+                plugin = plugin.namespace))
+            return defer.succeed(None)
+        else:
+            # we copy entity to avoid having the resource changed by stop_encryption
+            return defer.maybeDeferred(start_encryption, self.client, copy.copy(entity))
+
+    def _stopEncryption(self, plugin, entity):
+        """Stop encryption with a plugin
+
+        This method must be called just before removing a plugin session.
+        StopEncryptionn method of plugin will be called if it exists.
+        """
+        try:
+            stop_encryption = plugin.instance.stopEncryption
+        except AttributeError:
+            log.debug(u"No stopEncryption method found for {plugin}".format(
+                plugin = plugin.namespace))
+            return defer.succeed(None)
+        else:
+            # we copy entity to avoid having the resource changed by stop_encryption
+            return defer.maybeDeferred(stop_encryption, self.client, copy.copy(entity))
+
+    @defer.inlineCallbacks
     def start(self, entity, namespace=None, replace=False):
         """Start an encryption session with an entity
 
@@ -148,7 +191,7 @@
         bare_jid = entity.userhostJID()
         if bare_jid in self._sessions:
             # we have already an encryption session with this contact
-            former_plugin = self._sessions[bare_jid]['plugin']
+            former_plugin = self._sessions[bare_jid][u"plugin"]
             if former_plugin.namespace == namespace:
                 log.info(_(u"Session with {bare_jid} is already encrypted with {name}. "
                            u"Nothing to do.").format(
@@ -159,6 +202,7 @@
                 # there is a conflict, but replacement is requested
                 # so we stop previous encryption to use new one
                 del self._sessions[bare_jid]
+                yield self._stopEncryption(former_plugin, entity)
             else:
                 msg = (_(u"Session with {bare_jid} is already encrypted with {name}. "
                          u"Please stop encryption session before changing algorithm.")
@@ -182,6 +226,7 @@
         elif entity.resource:
             raise ValueError(_(u"{name} encryption must be used with bare jids."))
 
+        yield self._startEncryption(plugin, entity)
         self._sessions[entity.userhostJID()] = data
         log.info(_(u"Encryption session has been set for {entity_jid} with "
                    u"{encryption_name}").format(
@@ -202,6 +247,7 @@
 
         self.client.feedback(bare_jid, msg)
 
+    @defer.inlineCallbacks
     def stop(self, entity, namespace=None):
         """Stop an encryption session with an entity
 
@@ -212,8 +258,9 @@
         """
         session = self.getSession(entity.userhostJID())
         if not session:
-            raise exceptions.NotFound(_(u"There is no encryption session with this "
-                                        u"entity."))
+            raise failure.Failure(
+                exceptions.NotFound(_(u"There is no encryption session with this "
+                                      u"entity.")))
         plugin = session['plugin']
         if namespace is not None and plugin.namespace != namespace:
             raise exceptions.InternalError(_(
@@ -237,9 +284,16 @@
                                             u"entity."))
             else:
                 if not directed_devices:
-                    del session[u'directed_devices']
+                    # if we have no more directed device sessions,
+                    # we stop the whole session
+                    # see comment below for deleting session before stopping encryption
+                    del self._sessions[entity.userhostJID()]
+                    yield self._stopEncryption(plugin, entity)
         else:
-            del self._sessions[entity]
+            # plugin's stopEncryption may call stop again (that's the case with OTR)
+            # so we need to remove plugin from session before calling self._stopEncryption
+            del self._sessions[entity.userhostJID()]
+            yield self._stopEncryption(plugin, entity)
 
         log.info(_(u"encryption session stopped with entity {entity}").format(
             entity=entity.full()))
@@ -296,7 +350,7 @@
             raise NotImplementedError(
                 u"Encryption plugin doesn't handle trust management UI")
         else:
-            return get_trust_ui(self.client, entity_jid)
+            return defer.maybeDeferred(get_trust_ui, self.client, entity_jid)
 
     ## Triggers ##
 
@@ -324,6 +378,16 @@
         mess_data['encrypted'] = True
         return mess_data
 
+    def markAsTrusted(self, mess_data):
+        """Helper methor to mark a message as sent from a trusted 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['trusted'] = True
+        return mess_data
+
     def markAsUntrusted(self, mess_data):
         """Helper methor to mark a message as sent from an untrusted entity.
 
@@ -331,5 +395,5 @@
         the plugin
         @param mess_data(dict): message data as used in post treat workflow
         """
-        mess_data['untrusted'] = True
+        mess_data['trusted'] = False
         return mess_data