Mercurial > libervia-backend
comparison sat/plugins/plugin_xep_0384.py @ 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 |
parents | 003b8b4b56a7 |
children | 11afbbce40d1 |
comparison
equal
deleted
inserted
replaced
2816:114cdde9ff96 | 2817:0ab62dd3cf05 |
---|---|
781 ## PEP node events callbacks | 781 ## PEP node events callbacks |
782 | 782 |
783 @defer.inlineCallbacks | 783 @defer.inlineCallbacks |
784 def onNewDevices(self, itemsEvent, profile): | 784 def onNewDevices(self, itemsEvent, profile): |
785 client = self.host.getClient(profile) | 785 client = self.host.getClient(profile) |
786 cache = client._xep_0384_cache | |
787 omemo_session = client._xep_0384_session | 786 omemo_session = client._xep_0384_session |
788 entity = itemsEvent.sender | 787 entity = itemsEvent.sender |
789 entity_cache = cache.setdefault(entity, {}) | 788 |
790 devices = self.parseDevices(itemsEvent.items) | 789 devices = self.parseDevices(itemsEvent.items) |
791 omemo_session.newDeviceList(entity, devices) | 790 omemo_session.newDeviceList(entity, devices) |
792 missing_devices = devices.difference(entity_cache.keys()) | 791 |
793 if missing_devices: | |
794 bundles, bundles_not_found = yield self.getBundles( | |
795 client, entity, missing_devices) | |
796 entity_cache.update(bundles) | |
797 if bundles_not_found and entity == client.jid.userhostJID(): | |
798 # we have devices announced in our own public list | |
799 # with missing bundles | |
800 own_device = client._xep_0384_device_id | |
801 if own_device in bundles_not_found: | |
802 log.warning(_(u"Our own device has no attached bundle, fixing it")) | |
803 bundles_not_found.remove(own_device) | |
804 yield self.setBundle(client, omemo_session.public_bundle, own_device) | |
805 | |
806 if bundles_not_found: | |
807 # some announced devices have no bundle, we update our public | |
808 # list to remove missing devices. | |
809 log.warning(_( | |
810 u"Some devices have missing bundles, cleaning out public " | |
811 u"devices list")) | |
812 existing_devices = devices - bundles_not_found | |
813 yield self.setDevices(client, existing_devices) | |
814 # we check that our device has not been removed from the list | |
815 if entity == client.jid.userhostJID(): | 792 if entity == client.jid.userhostJID(): |
816 own_device = client._xep_0384_device_id | 793 own_device = client._xep_0384_device_id |
817 if own_device not in devices: | 794 if own_device not in devices: |
818 log.warning(_(u"Our own device is missing from devices list, fixing it")) | 795 log.warning(_(u"Our own device is missing from devices list, fixing it")) |
819 devices.add(own_device) | 796 devices.add(own_device) |
820 yield self.setDevices(client, devices) | 797 yield self.setDevices(client, devices) |
821 | |
822 | 798 |
823 ## triggers | 799 ## triggers |
824 | 800 |
825 @defer.inlineCallbacks | 801 @defer.inlineCallbacks |
826 def handleProblems(self, client, entity, bundles, problems): | 802 def handleProblems(self, client, entity, bundles, problems): |
835 this dict will list devices where problems can be ignored | 811 this dict will list devices where problems can be ignored |
836 (those devices won't receive the encrypted data) | 812 (those devices won't receive the encrypted data) |
837 """ | 813 """ |
838 # FIXME: not all problems are handled yet | 814 # FIXME: not all problems are handled yet |
839 untrusted = {} | 815 untrusted = {} |
816 missing_bundles = {} | |
840 expect_problems = {} | 817 expect_problems = {} |
818 cache = client._xep_0384_cache | |
841 for problem in problems: | 819 for problem in problems: |
842 if isinstance(problem, omemo_excpt.UntrustedException): | 820 if isinstance(problem, omemo_excpt.UntrustedException): |
843 untrusted[unicode(hash(problem))] = problem | 821 untrusted[unicode(hash(problem))] = problem |
822 if isinstance(problem, omemo_excpt.MissingBundleException): | |
823 pb_entity = jid.JID(problem.bare_jid) | |
824 entity_cache = cache.setdefault(pb_entity, {}) | |
825 entity_bundles = bundles.setdefault(pb_entity, {}) | |
826 if problem.device in entity_cache: | |
827 entity_bundles[problem.device] = entity_cache[problem.device] | |
828 else: | |
829 found_bundles, missing = yield self.getBundles( | |
830 client, pb_entity, [problem.device]) | |
831 entity_cache.update(bundles) | |
832 entity_bundles.update(found_bundles) | |
833 if problem.device in missing: | |
834 missing_bundles.setdefault(pb_entity, set()).add( | |
835 problem.device) | |
836 expect_problems.setdefault(problem.bare_jid, set()).add( | |
837 problem.device) | |
844 elif isinstance(problem, omemo_excpt.NoEligibleDevicesException): | 838 elif isinstance(problem, omemo_excpt.NoEligibleDevicesException): |
845 pass | 839 pass |
840 | |
841 for peer_jid, devices in missing_bundles.iteritems(): | |
842 devices_s = [unicode(d) for d in devices] | |
843 log.warning( | |
844 _(u"Can't retrieve bundle for device(s) {devices} of entity {peer}, " | |
845 u"the message will not be readable on this/those device(s)").format( | |
846 devices=u", ".join(devices_s), peer=peer_jid.full())) | |
847 client.feedback( | |
848 entity, | |
849 D_(u"You're destinee {peer} has missing encryption data on some of " | |
850 u"his/her device(s) (bundle on device {devices}), the message won't " | |
851 u"be readable on this/those device.").format( | |
852 peer=peer_jid.full(), devices=u", ".join(devices_s))) | |
846 | 853 |
847 if untrusted: | 854 if untrusted: |
848 trust_data = {} | 855 trust_data = {} |
849 for trust_id, data in untrusted.iteritems(): | 856 for trust_id, data in untrusted.iteritems(): |
850 trust_data[trust_id] = { | 857 trust_data[trust_id] = { |
871 defer.returnValue(expect_problems) | 878 defer.returnValue(expect_problems) |
872 | 879 |
873 @defer.inlineCallbacks | 880 @defer.inlineCallbacks |
874 def encryptMessage(self, client, entity_bare_jid, message): | 881 def encryptMessage(self, client, entity_bare_jid, message): |
875 omemo_session = client._xep_0384_session | 882 omemo_session = client._xep_0384_session |
876 cache = client._xep_0384_cache | |
877 try: | |
878 bundles = {entity_bare_jid: cache[entity_bare_jid]} | |
879 except KeyError: | |
880 # No devices know for this entity, let try to find them. | |
881 # This can happen if the entity is not in our roster, or doesn't handle OMEMO | |
882 # or if we haven't received the devices from PEP yet. | |
883 try: | |
884 devices = yield self.getDevices(client, entity_bare_jid) | |
885 bundles, __ = yield self.getBundles(client, entity_bare_jid, devices) | |
886 except Exception as e: | |
887 raise exceptions.NotFound( | |
888 _(u"Can retrieve bundles for {entity}: {reason}" .format( | |
889 entity=entity_bare_jid.full(), reason=e))) | |
890 else: | |
891 cache[entity_bare_jid] = bundles | |
892 bundles = {entity_bare_jid: bundles} | |
893 | |
894 own_jid = client.jid.userhostJID() | |
895 if entity_bare_jid != own_jid: | |
896 # message will be copied to our devices, so we need to add our own bundles | |
897 bundles[own_jid] = cache[own_jid] | |
898 | |
899 try: | 883 try: |
900 # first try may fail, in case of e.g. trust issue or missing bundle | 884 # first try may fail, in case of e.g. trust issue or missing bundle |
901 encrypted = yield omemo_session.encryptMessage( | 885 encrypted = yield omemo_session.encryptMessage( |
902 entity_bare_jid, | 886 entity_bare_jid, |
903 message, | 887 message) |
904 bundles) | |
905 except omemo_excpt.EncryptionProblemsException as e: | 888 except omemo_excpt.EncryptionProblemsException as e: |
906 # we know the problem to solve, we can try to fix them | 889 # we know the problem to solve, we can try to fix them |
907 expect_problems = yield self.handleProblems(client, entity_bare_jid, bundles, e.problems) | 890 bundles = {} |
891 expect_problems = yield self.handleProblems(client, entity_bare_jid, bundles, | |
892 e.problems) | |
908 # and try an encryption again. | 893 # and try an encryption again. |
909 try: | 894 try: |
910 encrypted = yield omemo_session.encryptMessage( | 895 encrypted = yield omemo_session.encryptMessage( |
911 entity_bare_jid, | 896 entity_bare_jid, |
912 message, | 897 message, |
930 # no OMEMO message here | 915 # no OMEMO message here |
931 defer.returnValue(True) | 916 defer.returnValue(True) |
932 | 917 |
933 # we have an encrypted message let's decrypt it | 918 # we have an encrypted message let's decrypt it |
934 from_jid = jid.JID(message_elt['from']) | 919 from_jid = jid.JID(message_elt['from']) |
920 if from_jid.userhostJID() == client.jid.userhostJID(): | |
921 feedback_jid = jid.JID(message_elt['to']) | |
922 else: | |
923 feedback_jid = from_jid | |
935 try: | 924 try: |
936 omemo_session = client._xep_0384_session | 925 omemo_session = client._xep_0384_session |
937 except AttributeError: | 926 except AttributeError: |
938 # on startup, message can ve received before session actually exists | 927 # on startup, message can ve received before session actually exists |
939 # so we need to synchronise here | 928 # so we need to synchronise here |
963 u"for our device (device_id: {device_id}, fingerprint: " | 952 u"for our device (device_id: {device_id}, fingerprint: " |
964 u"{fingerprint}): {xml}").format( | 953 u"{fingerprint}): {xml}").format( |
965 device_id=device_id, | 954 device_id=device_id, |
966 fingerprint=omemo_session.public_bundle.ik.encode('hex'), | 955 fingerprint=omemo_session.public_bundle.ik.encode('hex'), |
967 xml=encrypted_elt.toXml())) | 956 xml=encrypted_elt.toXml())) |
957 user_msg = (D_(u"An OMEMO message from {sender} has not been encrypted for " | |
958 u"our device, we can't decrypt it").format( | |
959 sender=from_jid.full())) | |
960 extra = {C.MESS_EXTRA_INFO: C.EXTRA_INFO_DECR_ERR} | |
961 client.feedback(feedback_jid, user_msg, extra) | |
968 defer.returnValue(False) | 962 defer.returnValue(False) |
969 except ValueError as e: | 963 except ValueError as e: |
970 log.warning(_(u"Invalid recipient ID: {msg}".format(msg=e))) | 964 log.warning(_(u"Invalid recipient ID: {msg}".format(msg=e))) |
971 defer.returnValue(False) | 965 defer.returnValue(False) |
972 is_pre_key = C.bool(key_elt.getAttribute('prekey', 'false')) | 966 is_pre_key = C.bool(key_elt.getAttribute('prekey', 'false')) |
998 log.warning(_(u"Can't decrypt message: {reason}\n{xml}").format( | 992 log.warning(_(u"Can't decrypt message: {reason}\n{xml}").format( |
999 reason=e, xml=message_elt.toXml())) | 993 reason=e, xml=message_elt.toXml())) |
1000 user_msg = (D_(u"An OMEMO message from {sender} can't be decrypted: {reason}") | 994 user_msg = (D_(u"An OMEMO message from {sender} can't be decrypted: {reason}") |
1001 .format(sender=from_jid.full(), reason=e)) | 995 .format(sender=from_jid.full(), reason=e)) |
1002 extra = {C.MESS_EXTRA_INFO: C.EXTRA_INFO_DECR_ERR} | 996 extra = {C.MESS_EXTRA_INFO: C.EXTRA_INFO_DECR_ERR} |
1003 client.feedback(from_jid, user_msg, extra) | 997 client.feedback(feedback_jid, user_msg, extra) |
1004 defer.returnValue(False) | 998 defer.returnValue(False) |
1005 if omemo_session.republish_bundle: | 999 finally: |
1006 # we don't wait for the Deferred (i.e. no yield) on purpose | 1000 if omemo_session.republish_bundle: |
1007 # there is no need to block the whole message workflow while | 1001 # we don't wait for the Deferred (i.e. no yield) on purpose |
1008 # updating the bundle | 1002 # there is no need to block the whole message workflow while |
1009 self.setBundle(client, omemo_session.public_bundle, device_id) | 1003 # updating the bundle |
1004 self.setBundle(client, omemo_session.public_bundle, device_id) | |
1010 | 1005 |
1011 message_elt.children.remove(encrypted_elt) | 1006 message_elt.children.remove(encrypted_elt) |
1012 if plaintext: | 1007 if plaintext: |
1013 message_elt.addElement("body", content=plaintext.decode('utf-8')) | 1008 message_elt.addElement("body", content=plaintext.decode('utf-8')) |
1014 post_treat.addCallback(client.encryption.markAsEncrypted) | 1009 post_treat.addCallback(client.encryption.markAsEncrypted) |