Mercurial > libervia-backend
comparison libervia/backend/plugins/plugin_sec_otr.py @ 4270:0d7bb4df2343
Reformatted code base using black.
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 19 Jun 2024 18:44:57 +0200 |
parents | 4b842c1fb686 |
children |
comparison
equal
deleted
inserted
replaced
4269:64a85ce8be70 | 4270:0d7bb4df2343 |
---|---|
107 @param msg_str(str): encrypted message body | 107 @param msg_str(str): encrypted message body |
108 @param appdata(None, dict): None for signal message, | 108 @param appdata(None, dict): None for signal message, |
109 message data when an encrypted message is going to be sent | 109 message data when an encrypted message is going to be sent |
110 """ | 110 """ |
111 assert isinstance(self.peer, jid.JID) | 111 assert isinstance(self.peer, jid.JID) |
112 msg = msg_str.decode('utf-8') | 112 msg = msg_str.decode("utf-8") |
113 client = self.user.client | 113 client = self.user.client |
114 log.debug("injecting encrypted message to {to}".format(to=self.peer)) | 114 log.debug("injecting encrypted message to {to}".format(to=self.peer)) |
115 if appdata is None: | 115 if appdata is None: |
116 mess_data = { | 116 mess_data = { |
117 "from": client.jid, | 117 "from": client.jid, |
122 "type": "chat", | 122 "type": "chat", |
123 "extra": {}, | 123 "extra": {}, |
124 "timestamp": time.time(), | 124 "timestamp": time.time(), |
125 } | 125 } |
126 client.generate_message_xml(mess_data) | 126 client.generate_message_xml(mess_data) |
127 xml = mess_data['xml'] | 127 xml = mess_data["xml"] |
128 self._p_carbons.set_private(xml) | 128 self._p_carbons.set_private(xml) |
129 self._p_hints.add_hint_elements(xml, [ | 129 self._p_hints.add_hint_elements( |
130 self._p_hints.HINT_NO_COPY, | 130 xml, [self._p_hints.HINT_NO_COPY, self._p_hints.HINT_NO_PERMANENT_STORE] |
131 self._p_hints.HINT_NO_PERMANENT_STORE]) | 131 ) |
132 client.send(mess_data["xml"]) | 132 client.send(mess_data["xml"]) |
133 else: | 133 else: |
134 message_elt = appdata["xml"] | 134 message_elt = appdata["xml"] |
135 assert message_elt.name == "message" | 135 assert message_elt.name == "message" |
136 message_elt.addElement("body", content=msg) | 136 message_elt.addElement("body", content=msg) |
151 # we have to check value because potr code says that a 2-tuples should be | 151 # we have to check value because potr code says that a 2-tuples should be |
152 # returned while in practice it's either None or u"trusted" | 152 # returned while in practice it's either None or u"trusted" |
153 trusted = self.getCurrentTrust() | 153 trusted = self.getCurrentTrust() |
154 if trusted is None: | 154 if trusted is None: |
155 return False | 155 return False |
156 elif trusted == 'trusted': | 156 elif trusted == "trusted": |
157 return True | 157 return True |
158 else: | 158 else: |
159 log.error("Unexpected getCurrentTrust() value: {value}".format( | 159 log.error("Unexpected getCurrentTrust() value: {value}".format(value=trusted)) |
160 value=trusted)) | |
161 return False | 160 return False |
162 | 161 |
163 def set_state(self, state): | 162 def set_state(self, state): |
164 client = self.user.client | 163 client = self.user.client |
165 old_state = self.state | 164 old_state = self.state |
289 def host(self): | 288 def host(self): |
290 return self.parent.host | 289 return self.parent.host |
291 | 290 |
292 def start_context(self, other_jid): | 291 def start_context(self, other_jid): |
293 assert isinstance(other_jid, jid.JID) | 292 assert isinstance(other_jid, jid.JID) |
294 context = self.contexts.setdefault( | 293 context = self.contexts.setdefault(other_jid, Context(self, other_jid)) |
295 other_jid, Context(self, other_jid) | |
296 ) | |
297 return context | 294 return context |
298 | 295 |
299 def get_context_for_user(self, other): | 296 def get_context_for_user(self, other): |
300 log.debug("get_context_for_user [%s]" % other) | 297 log.debug("get_context_for_user [%s]" % other) |
301 if not other.resource: | 298 if not other.resource: |
312 self.skipped_profiles = ( | 309 self.skipped_profiles = ( |
313 set() | 310 set() |
314 ) # FIXME: OTR should not be skipped per profile, this need to be refactored | 311 ) # FIXME: OTR should not be skipped per profile, this need to be refactored |
315 self._p_hints = host.plugins["XEP-0334"] | 312 self._p_hints = host.plugins["XEP-0334"] |
316 self._p_carbons = host.plugins["XEP-0280"] | 313 self._p_carbons = host.plugins["XEP-0280"] |
317 host.trigger.add("message_received", self.message_received_trigger, priority=100000) | 314 host.trigger.add( |
315 "message_received", self.message_received_trigger, priority=100000 | |
316 ) | |
318 host.trigger.add("sendMessage", self.send_message_trigger, priority=100000) | 317 host.trigger.add("sendMessage", self.send_message_trigger, priority=100000) |
319 host.trigger.add("send_message_data", self._send_message_data_trigger) | 318 host.trigger.add("send_message_data", self._send_message_data_trigger) |
320 host.bridge.add_method( | 319 host.bridge.add_method( |
321 "skip_otr", ".plugin", in_sign="s", out_sign="", method=self._skip_otr | 320 "skip_otr", ".plugin", in_sign="s", out_sign="", method=self._skip_otr |
322 ) # FIXME: must be removed, must be done on per-message basis | 321 ) # FIXME: must be removed, must be done on per-message basis |
372 ctxMng = client._otr_context_manager = ContextManager(self, client) | 371 ctxMng = client._otr_context_manager = ContextManager(self, client) |
373 client._otr_data = persistent.PersistentBinaryDict(NS_OTR, client.profile) | 372 client._otr_data = persistent.PersistentBinaryDict(NS_OTR, client.profile) |
374 yield client._otr_data.load() | 373 yield client._otr_data.load() |
375 encrypted_priv_key = client._otr_data.get(PRIVATE_KEY, None) | 374 encrypted_priv_key = client._otr_data.get(PRIVATE_KEY, None) |
376 if encrypted_priv_key is not None: | 375 if encrypted_priv_key is not None: |
377 priv_key = self.host.memory.decrypt_value( | 376 priv_key = self.host.memory.decrypt_value(encrypted_priv_key, client.profile) |
378 encrypted_priv_key, client.profile | |
379 ) | |
380 ctxMng.account.privkey = potr.crypt.PK.parsePrivateKey( | 377 ctxMng.account.privkey = potr.crypt.PK.parsePrivateKey( |
381 unhexlify(priv_key.encode('utf-8')) | 378 unhexlify(priv_key.encode("utf-8")) |
382 )[0] | 379 )[0] |
383 else: | 380 else: |
384 ctxMng.account.privkey = None | 381 ctxMng.account.privkey = None |
385 ctxMng.account.load_trusts() | 382 ctxMng.account.load_trusts() |
386 | 383 |
403 def get_trust_ui(self, client, entity_jid): | 400 def get_trust_ui(self, client, entity_jid): |
404 if not entity_jid.resource: | 401 if not entity_jid.resource: |
405 entity_jid.resource = self.host.memory.main_resource_get( | 402 entity_jid.resource = self.host.memory.main_resource_get( |
406 client, entity_jid | 403 client, entity_jid |
407 ) # FIXME: temporary and unsecure, must be changed when frontends | 404 ) # FIXME: temporary and unsecure, must be changed when frontends |
408 # are refactored | 405 # are refactored |
409 ctxMng = client._otr_context_manager | 406 ctxMng = client._otr_context_manager |
410 otrctx = ctxMng.get_context_for_user(entity_jid) | 407 otrctx = ctxMng.get_context_for_user(entity_jid) |
411 priv_key = ctxMng.account.privkey | 408 priv_key = ctxMng.account.privkey |
412 | 409 |
413 if priv_key is None: | 410 if priv_key is None: |
515 """Start or refresh an OTR session | 512 """Start or refresh an OTR session |
516 | 513 |
517 @param to_jid(jid.JID): jid to start encrypted session with | 514 @param to_jid(jid.JID): jid to start encrypted session with |
518 """ | 515 """ |
519 encrypted_session = client.encryption.getSession(to_jid.userhostJID()) | 516 encrypted_session = client.encryption.getSession(to_jid.userhostJID()) |
520 if encrypted_session and encrypted_session['plugin'].namespace != NS_OTR: | 517 if encrypted_session and encrypted_session["plugin"].namespace != NS_OTR: |
521 raise exceptions.ConflictError(_( | 518 raise exceptions.ConflictError( |
522 "Can't start an OTR session, there is already an encrypted session " | 519 _( |
523 "with {name}").format(name=encrypted_session['plugin'].name)) | 520 "Can't start an OTR session, there is already an encrypted session " |
521 "with {name}" | |
522 ).format(name=encrypted_session["plugin"].name) | |
523 ) | |
524 if not to_jid.resource: | 524 if not to_jid.resource: |
525 to_jid.resource = self.host.memory.main_resource_get( | 525 to_jid.resource = self.host.memory.main_resource_get( |
526 client, to_jid | 526 client, to_jid |
527 ) # FIXME: temporary and unsecure, must be changed when frontends | 527 ) # FIXME: temporary and unsecure, must be changed when frontends |
528 # are refactored | 528 # are refactored |
529 otrctx = client._otr_context_manager.get_context_for_user(to_jid) | 529 otrctx = client._otr_context_manager.get_context_for_user(to_jid) |
530 query = otrctx.sendMessage(0, b"?OTRv?") | 530 query = otrctx.sendMessage(0, b"?OTRv?") |
531 otrctx.inject(query) | 531 otrctx.inject(query) |
532 | 532 |
533 def _otr_session_end(self, menu_data, profile): | 533 def _otr_session_end(self, menu_data, profile): |
549 """End an OTR session""" | 549 """End an OTR session""" |
550 if not to_jid.resource: | 550 if not to_jid.resource: |
551 to_jid.resource = self.host.memory.main_resource_get( | 551 to_jid.resource = self.host.memory.main_resource_get( |
552 client, to_jid | 552 client, to_jid |
553 ) # FIXME: temporary and unsecure, must be changed when frontends | 553 ) # FIXME: temporary and unsecure, must be changed when frontends |
554 # are refactored | 554 # are refactored |
555 otrctx = client._otr_context_manager.get_context_for_user(to_jid) | 555 otrctx = client._otr_context_manager.get_context_for_user(to_jid) |
556 otrctx.disconnect() | 556 otrctx.disconnect() |
557 return {} | 557 return {} |
558 | 558 |
559 def _otr_authenticate(self, menu_data, profile): | 559 def _otr_authenticate(self, menu_data, profile): |
586 to_jid = jid.JID(menu_data["jid"]) | 586 to_jid = jid.JID(menu_data["jid"]) |
587 if not to_jid.resource: | 587 if not to_jid.resource: |
588 to_jid.resource = self.host.memory.main_resource_get( | 588 to_jid.resource = self.host.memory.main_resource_get( |
589 client, to_jid | 589 client, to_jid |
590 ) # FIXME: temporary and unsecure, must be changed when frontends | 590 ) # FIXME: temporary and unsecure, must be changed when frontends |
591 # are refactored | 591 # are refactored |
592 except KeyError: | 592 except KeyError: |
593 log.error(_("jid key is not present !")) | 593 log.error(_("jid key is not present !")) |
594 return defer.fail(exceptions.DataError) | 594 return defer.fail(exceptions.DataError) |
595 | 595 |
596 ctxMng = client._otr_context_manager | 596 ctxMng = client._otr_context_manager |
604 # we end all sessions | 604 # we end all sessions |
605 for context in list(ctxMng.contexts.values()): | 605 for context in list(ctxMng.contexts.values()): |
606 context.disconnect() | 606 context.disconnect() |
607 ctxMng.account.privkey = None | 607 ctxMng.account.privkey = None |
608 ctxMng.account.getPrivkey() # as account.privkey is None, getPrivkey | 608 ctxMng.account.getPrivkey() # as account.privkey is None, getPrivkey |
609 # will generate a new key, and save it | 609 # will generate a new key, and save it |
610 return { | 610 return { |
611 "xmlui": xml_tools.note( | 611 "xmlui": xml_tools.note( |
612 D_("Your private key has been dropped") | 612 D_("Your private key has been dropped") |
613 ).toXml() | 613 ).toXml() |
614 } | 614 } |
628 from_jid = data["from"] | 628 from_jid = data["from"] |
629 log.debug("_received_treatment [from_jid = %s]" % from_jid) | 629 log.debug("_received_treatment [from_jid = %s]" % from_jid) |
630 otrctx = client._otr_context_manager.get_context_for_user(from_jid) | 630 otrctx = client._otr_context_manager.get_context_for_user(from_jid) |
631 | 631 |
632 try: | 632 try: |
633 message = ( | 633 message = next( |
634 next(iter(data["message"].values())) | 634 iter(data["message"].values()) |
635 ) # FIXME: Q&D fix for message refactoring, message is now a dict | 635 ) # FIXME: Q&D fix for message refactoring, message is now a dict |
636 res = otrctx.receiveMessage(message.encode("utf-8")) | 636 res = otrctx.receiveMessage(message.encode("utf-8")) |
637 except (potr.context.UnencryptedMessage, potr.context.NotOTRMessage): | 637 except (potr.context.UnencryptedMessage, potr.context.NotOTRMessage): |
638 # potr has a bug with Python 3 and test message against str while bytes are | 638 # potr has a bug with Python 3 and test message against str while bytes are |
639 # expected, resulting in a NoOTRMessage raised instead of UnencryptedMessage; | 639 # expected, resulting in a NoOTRMessage raised instead of UnencryptedMessage; |
640 # so we catch NotOTRMessage as a workaround | 640 # so we catch NotOTRMessage as a workaround |
641 # TODO: report this upstream | 641 # TODO: report this upstream |
642 encrypted = False | 642 encrypted = False |
643 if otrctx.state == potr.context.STATE_ENCRYPTED: | 643 if otrctx.state == potr.context.STATE_ENCRYPTED: |
644 log.warning( | 644 log.warning( |
645 "Received unencrypted message in an encrypted context (from {jid})" | 645 "Received unencrypted message in an encrypted context (from {jid})".format( |
646 .format(jid=from_jid.full()) | 646 jid=from_jid.full() |
647 ) | |
647 ) | 648 ) |
648 | 649 |
649 feedback = ( | 650 feedback = ( |
650 D_( | 651 D_( |
651 "WARNING: received unencrypted data in a supposedly encrypted " | 652 "WARNING: received unencrypted data in a supposedly encrypted " |
690 del data["history"] | 691 del data["history"] |
691 except KeyError: | 692 except KeyError: |
692 pass | 693 pass |
693 # TODO: add skip history as an option, but by default we don't skip it | 694 # TODO: add skip history as an option, but by default we don't skip it |
694 # data[u'history'] = C.HISTORY_SKIP # we send the decrypted message to | 695 # data[u'history'] = C.HISTORY_SKIP # we send the decrypted message to |
695 # frontends, but we don't want it in | 696 # frontends, but we don't want it in |
696 # history | 697 # history |
697 else: | 698 else: |
698 raise failure.Failure( | 699 raise failure.Failure( |
699 exceptions.CancelError("Cancelled by OTR") | 700 exceptions.CancelError("Cancelled by OTR") |
700 ) # no message at all (no history, no signal) | 701 ) # no message at all (no history, no signal) |
701 | 702 |
735 if client.is_component: | 736 if client.is_component: |
736 return True | 737 return True |
737 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT: | 738 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT: |
738 # OTR is not possible in group chats | 739 # OTR is not possible in group chats |
739 return True | 740 return True |
740 from_jid = jid.JID(message_elt['from']) | 741 from_jid = jid.JID(message_elt["from"]) |
741 if not from_jid.resource or from_jid.userhostJID() == client.jid.userhostJID(): | 742 if not from_jid.resource or from_jid.userhostJID() == client.jid.userhostJID(): |
742 # OTR is only usable when resources are present | 743 # OTR is only usable when resources are present |
743 return True | 744 return True |
744 if client.profile in self.skipped_profiles: | 745 if client.profile in self.skipped_profiles: |
745 post_treat.addCallback(self._received_treatment_for_skipped_profiles) | 746 post_treat.addCallback(self._received_treatment_for_skipped_profiles) |
749 | 750 |
750 def _send_message_data_trigger(self, client, mess_data): | 751 def _send_message_data_trigger(self, client, mess_data): |
751 if client.is_component: | 752 if client.is_component: |
752 return True | 753 return True |
753 encryption = mess_data.get(C.MESS_KEY_ENCRYPTION) | 754 encryption = mess_data.get(C.MESS_KEY_ENCRYPTION) |
754 if encryption is None or encryption['plugin'].namespace != NS_OTR: | 755 if encryption is None or encryption["plugin"].namespace != NS_OTR: |
755 return | 756 return |
756 to_jid = mess_data['to'] | 757 to_jid = mess_data["to"] |
757 if not to_jid.resource: | 758 if not to_jid.resource: |
758 to_jid.resource = self.host.memory.main_resource_get( | 759 to_jid.resource = self.host.memory.main_resource_get( |
759 client, to_jid | 760 client, to_jid |
760 ) # FIXME: temporary and unsecure, must be changed when frontends | 761 ) # FIXME: temporary and unsecure, must be changed when frontends |
761 otrctx = client._otr_context_manager.get_context_for_user(to_jid) | 762 otrctx = client._otr_context_manager.get_context_for_user(to_jid) |
775 message_elt.children.remove(child) | 776 message_elt.children.remove(child) |
776 if body is None: | 777 if body is None: |
777 log.warning("No message found") | 778 log.warning("No message found") |
778 else: | 779 else: |
779 self._p_carbons.set_private(message_elt) | 780 self._p_carbons.set_private(message_elt) |
780 self._p_hints.add_hint_elements(message_elt, [ | 781 self._p_hints.add_hint_elements( |
781 self._p_hints.HINT_NO_COPY, | 782 message_elt, |
782 self._p_hints.HINT_NO_PERMANENT_STORE]) | 783 [self._p_hints.HINT_NO_COPY, self._p_hints.HINT_NO_PERMANENT_STORE], |
784 ) | |
783 otrctx.sendMessage(0, str(body).encode("utf-8"), appdata=mess_data) | 785 otrctx.sendMessage(0, str(body).encode("utf-8"), appdata=mess_data) |
784 else: | 786 else: |
785 feedback = D_( | 787 feedback = D_( |
786 "Your message was not sent because your correspondent closed the " | 788 "Your message was not sent because your correspondent closed the " |
787 "encrypted conversation on his/her side. " | 789 "encrypted conversation on his/her side. " |
789 ) | 791 ) |
790 log.warning(_("Message discarded because closed encryption channel")) | 792 log.warning(_("Message discarded because closed encryption channel")) |
791 client.feedback(to_jid, feedback) | 793 client.feedback(to_jid, feedback) |
792 raise failure.Failure(exceptions.CancelError("Cancelled by OTR plugin")) | 794 raise failure.Failure(exceptions.CancelError("Cancelled by OTR plugin")) |
793 | 795 |
794 def send_message_trigger(self, client, mess_data, pre_xml_treatments, | 796 def send_message_trigger( |
795 post_xml_treatments): | 797 self, client, mess_data, pre_xml_treatments, post_xml_treatments |
798 ): | |
796 if client.is_component: | 799 if client.is_component: |
797 return True | 800 return True |
798 if mess_data["type"] == "groupchat": | 801 if mess_data["type"] == "groupchat": |
799 return True | 802 return True |
800 | 803 |
828 if not entity.resource: | 831 if not entity.resource: |
829 try: | 832 try: |
830 entity.resource = self.host.memory.main_resource_get( | 833 entity.resource = self.host.memory.main_resource_get( |
831 client, entity | 834 client, entity |
832 ) # FIXME: temporary and unsecure, must be changed when frontends | 835 ) # FIXME: temporary and unsecure, must be changed when frontends |
833 # are refactored | 836 # are refactored |
834 except exceptions.UnknownEntityError: | 837 except exceptions.UnknownEntityError: |
835 return True # entity was not connected | 838 return True # entity was not connected |
836 if entity in client._otr_context_manager.contexts: | 839 if entity in client._otr_context_manager.contexts: |
837 otrctx = client._otr_context_manager.get_context_for_user(entity) | 840 otrctx = client._otr_context_manager.get_context_for_user(entity) |
838 otrctx.disconnect() | 841 otrctx.disconnect() |