changeset 2817:0ab62dd3cf05

plugin XEP-0384: better bundle handling + misc improvments - public bundles are now retrieved only when needed (i.e. on MissingBundleException when encrypting a message) - a feedback is given to user when a message is received but it has not been encrypted for our device - fixed feedback on decryption error if message come from an other device of the client (i.e. it's a carbon copy) - setBundle if called in a "finally" if republish_bundle is set after decrypting a message
author Goffi <goffi@goffi.org>
date Thu, 28 Feb 2019 18:57:26 +0100 (2019-02-28)
parents 114cdde9ff96
children 9a7cb32836c6
files sat/plugins/plugin_xep_0384.py setup.py
diffstat 2 files changed, 53 insertions(+), 58 deletions(-) [+]
line wrap: on
line diff
--- a/sat/plugins/plugin_xep_0384.py	Thu Feb 28 18:57:06 2019 +0100
+++ b/sat/plugins/plugin_xep_0384.py	Thu Feb 28 18:57:26 2019 +0100
@@ -783,35 +783,12 @@
     @defer.inlineCallbacks
     def onNewDevices(self, itemsEvent, profile):
         client = self.host.getClient(profile)
-        cache = client._xep_0384_cache
         omemo_session = client._xep_0384_session
         entity = itemsEvent.sender
-        entity_cache = cache.setdefault(entity, {})
+
         devices = self.parseDevices(itemsEvent.items)
         omemo_session.newDeviceList(entity, devices)
-        missing_devices = devices.difference(entity_cache.keys())
-        if missing_devices:
-            bundles, bundles_not_found = yield self.getBundles(
-                client, entity, missing_devices)
-            entity_cache.update(bundles)
-            if bundles_not_found and entity == client.jid.userhostJID():
-                # we have devices announced in our own public list
-                # with missing bundles
-                own_device = client._xep_0384_device_id
-                if own_device in bundles_not_found:
-                    log.warning(_(u"Our own device has no attached bundle, fixing it"))
-                    bundles_not_found.remove(own_device)
-                    yield self.setBundle(client, omemo_session.public_bundle, own_device)
 
-                if bundles_not_found:
-                    # some announced devices have no bundle, we update our public
-                    # list to remove missing devices.
-                    log.warning(_(
-                        u"Some devices have missing bundles, cleaning out public "
-                        u"devices list"))
-                    existing_devices = devices - bundles_not_found
-                    yield self.setDevices(client, existing_devices)
-        # we check that our device has not been removed from the list
         if entity == client.jid.userhostJID():
             own_device = client._xep_0384_device_id
             if own_device not in devices:
@@ -819,7 +796,6 @@
                 devices.add(own_device)
                 yield self.setDevices(client, devices)
 
-
     ## triggers
 
     @defer.inlineCallbacks
@@ -837,13 +813,44 @@
         """
         # FIXME: not all problems are handled yet
         untrusted = {}
+        missing_bundles = {}
         expect_problems = {}
+        cache = client._xep_0384_cache
         for problem in problems:
             if isinstance(problem, omemo_excpt.UntrustedException):
                 untrusted[unicode(hash(problem))] = problem
+            if isinstance(problem, omemo_excpt.MissingBundleException):
+                pb_entity = jid.JID(problem.bare_jid)
+                entity_cache = cache.setdefault(pb_entity, {})
+                entity_bundles = bundles.setdefault(pb_entity, {})
+                if problem.device in entity_cache:
+                    entity_bundles[problem.device] = entity_cache[problem.device]
+                else:
+                    found_bundles, missing = yield self.getBundles(
+                        client, pb_entity, [problem.device])
+                    entity_cache.update(bundles)
+                    entity_bundles.update(found_bundles)
+                    if problem.device in missing:
+                        missing_bundles.setdefault(pb_entity, set()).add(
+                            problem.device)
+                        expect_problems.setdefault(problem.bare_jid, set()).add(
+                            problem.device)
             elif isinstance(problem, omemo_excpt.NoEligibleDevicesException):
                 pass
 
+        for peer_jid, devices in missing_bundles.iteritems():
+            devices_s = [unicode(d) for d in devices]
+            log.warning(
+                _(u"Can't retrieve bundle for device(s) {devices} of entity {peer}, "
+                  u"the message will not be readable on this/those device(s)").format(
+                    devices=u", ".join(devices_s), peer=peer_jid.full()))
+            client.feedback(
+                entity,
+                D_(u"You're destinee {peer} has missing encryption data on some of "
+                   u"his/her device(s) (bundle on device {devices}), the message won't  "
+                   u"be readable on this/those device.").format(
+                   peer=peer_jid.full(), devices=u", ".join(devices_s)))
+
         if untrusted:
             trust_data = {}
             for trust_id, data in untrusted.iteritems():
@@ -873,38 +880,16 @@
     @defer.inlineCallbacks
     def encryptMessage(self, client, entity_bare_jid, message):
         omemo_session = client._xep_0384_session
-        cache = client._xep_0384_cache
-        try:
-            bundles = {entity_bare_jid: cache[entity_bare_jid]}
-        except KeyError:
-            # No devices know for this entity, let try to find them.
-            # This can happen if the entity is not in our roster, or doesn't handle OMEMO
-            # or if we haven't received the devices from PEP yet.
-            try:
-                devices = yield self.getDevices(client, entity_bare_jid)
-                bundles, __ = yield self.getBundles(client, entity_bare_jid, devices)
-            except Exception as e:
-                raise exceptions.NotFound(
-                    _(u"Can retrieve bundles for {entity}: {reason}" .format(
-                    entity=entity_bare_jid.full(), reason=e)))
-            else:
-                cache[entity_bare_jid] = bundles
-                bundles = {entity_bare_jid: bundles}
-
-        own_jid = client.jid.userhostJID()
-        if entity_bare_jid != own_jid:
-            # message will be copied to our devices, so we need to add our own bundles
-            bundles[own_jid] = cache[own_jid]
-
         try:
             # first try may fail, in case of e.g. trust issue or missing bundle
             encrypted = yield omemo_session.encryptMessage(
                 entity_bare_jid,
-                message,
-                bundles)
+                message)
         except omemo_excpt.EncryptionProblemsException as e:
             # we know the problem to solve, we can try to fix them
-            expect_problems = yield self.handleProblems(client, entity_bare_jid, bundles, e.problems)
+            bundles = {}
+            expect_problems = yield self.handleProblems(client, entity_bare_jid, bundles,
+                                                        e.problems)
             # and try an encryption again.
             try:
                 encrypted = yield omemo_session.encryptMessage(
@@ -932,6 +917,10 @@
 
         # we have an encrypted message let's decrypt it
         from_jid = jid.JID(message_elt['from'])
+        if from_jid.userhostJID() == client.jid.userhostJID():
+            feedback_jid = jid.JID(message_elt['to'])
+        else:
+            feedback_jid = from_jid
         try:
             omemo_session = client._xep_0384_session
         except AttributeError:
@@ -965,6 +954,11 @@
                           device_id=device_id,
                           fingerprint=omemo_session.public_bundle.ik.encode('hex'),
                           xml=encrypted_elt.toXml()))
+            user_msg = (D_(u"An OMEMO message from {sender} has not been encrypted for "
+                           u"our device, we can't decrypt it").format(
+                           sender=from_jid.full()))
+            extra = {C.MESS_EXTRA_INFO: C.EXTRA_INFO_DECR_ERR}
+            client.feedback(feedback_jid, user_msg, extra)
             defer.returnValue(False)
         except ValueError as e:
             log.warning(_(u"Invalid recipient ID: {msg}".format(msg=e)))
@@ -1000,13 +994,14 @@
             user_msg = (D_(u"An OMEMO message from {sender} can't be decrypted: {reason}")
                 .format(sender=from_jid.full(), reason=e))
             extra = {C.MESS_EXTRA_INFO: C.EXTRA_INFO_DECR_ERR}
-            client.feedback(from_jid, user_msg, extra)
+            client.feedback(feedback_jid, user_msg, extra)
             defer.returnValue(False)
-        if omemo_session.republish_bundle:
-            # we don't wait for the Deferred (i.e. no yield) on purpose
-            # there is no need to block the whole message workflow while
-            # updating the bundle
-            self.setBundle(client, omemo_session.public_bundle, device_id)
+        finally:
+            if omemo_session.republish_bundle:
+                # we don't wait for the Deferred (i.e. no yield) on purpose
+                # there is no need to block the whole message workflow while
+                # updating the bundle
+                self.setBundle(client, omemo_session.public_bundle, device_id)
 
         message_elt.children.remove(encrypted_elt)
         if plaintext:
--- a/setup.py	Thu Feb 28 18:57:06 2019 +0100
+++ b/setup.py	Thu Feb 28 18:57:26 2019 +0100
@@ -50,7 +50,7 @@
     'urwid >= 1.2.0',
     'urwid-satext >= 0.6.1',
     'wokkel >= 0.7.1',
-    'omemo >= 0.10.3',
+    'omemo == 0.10.3',  # FIXME: we block to this version until bĂȘta
     'omemo_backend_signal',
 ]