diff src/plugins/plugin_sec_otr.py @ 2125:ca82c97db195

plugin sec OTR: fixed OTR: - fixed bridge calls - use deferred translation - unicode string fixes - various other improvments
author Goffi <goffi@goffi.org>
date Thu, 26 Jan 2017 20:24:58 +0100
parents 200cd707a46d
children aa94f33fd2ad
line wrap: on
line diff
--- a/src/plugins/plugin_sec_otr.py	Sun Jan 15 22:41:22 2017 +0100
+++ b/src/plugins/plugin_sec_otr.py	Thu Jan 26 20:24:58 2017 +0100
@@ -37,9 +37,10 @@
 
 NS_OTR = "otr_plugin"
 PRIVATE_KEY = "PRIVATE KEY"
-MAIN_MENU = D_('OTR')
-AUTH_TXT = D_("To authenticate your correspondent, you need to give your below fingerprint *BY AN EXTERNAL CANAL* (i.e. not in this chat), and check that the one he gives you is the same as below. If there is a mismatch, there can be a spy between you!")
-DROP_TXT = D_("You private key is used to encrypt messages for your correspondent, nobody except you must know it, if you are in doubt, you should drop it!\n\nAre you sure you want to drop your private key?")
+OTR_MENU = D_(u'OTR')
+AUTH_TXT = D_(u"To authenticate your correspondent, you need to give your below fingerprint *BY AN EXTERNAL CANAL* (i.e. not in this chat), and check that the one he gives you is the same as below. If there is a mismatch, there can be a spy between you!")
+DROP_TXT = D_(u"You private key is used to encrypt messages for your correspondent, nobody except you must know it, if you are in doubt, you should drop it!\n\nAre you sure you want to drop your private key?")
+NO_LOG = D_(u"/!\\Your history is not logged anymore, and most of advanced features are disabled !")  # FIXME: not used at the moment
 
 DEFAULT_POLICY_FLAGS = {
   'ALLOW_V1':False,
@@ -47,6 +48,7 @@
   'REQUIRE_ENCRYPTION':True,
 }
 
+
 PLUGIN_INFO = {
     "name": "OTR",
     "import_name": "OTR",
@@ -55,7 +57,7 @@
     "dependencies": [],
     "main": "OTR",
     "handler": "no",
-    "description": _("""Implementation of OTR""")
+    "description": _(u"""Implementation of OTR""")
 }
 
 
@@ -103,20 +105,28 @@
             trusted_str = _(u"trusted") if trusted else _(u"untrusted")
 
             if old_state == potr.context.STATE_ENCRYPTED:
-                feedback = _(u"%(trusted)s OTR conversation with %(other_jid)s REFRESHED") % {'trusted': trusted_str, 'other_jid': self.peer.full()}
+                feedback = D_(u"{trusted} OTR conversation with {other_jid} REFRESHED").format(
+                    trusted = trusted_str,
+                    other_jid = self.peer.full())
             else:
-                feedback = _(u"%(trusted)s Encrypted OTR conversation started with %(other_jid)s\n/!\\ Your history is not logged anymore, and most of advanced features are disabled !") % {'trusted': trusted_str, 'other_jid': self.peer.full()}
+                feedback = D_(u"{trusted} Encrypted OTR conversation started with {other_jid}\n{no_log}").format(
+                    trusted = trusted_str,
+                    other_jid = self.peer.full(),
+                    no_log = NO_LOG)
         elif state == potr.context.STATE_FINISHED:
-            feedback = _(u"OTR conversation with %(other_jid)s is FINISHED") % {'other_jid': self.peer.full()}
+            feedback = D_(u"OTR conversation with {other_jid} is FINISHED").format(other_jid = self.peer.full())
         else:
-            log.error(_(u"Unknown OTR state"))
+            log.error(D_(u"Unknown OTR state"))
             return
 
         client = self.user.client
-        self.host.bridge.messageNew(client.jid.full(),
-                                    feedback,
+        self.host.bridge.messageNew(uid=unicode(uuid.uuid4()),
+                                    timestamp=time.time(),
+                                    from_jid=client.jid.full(),
+                                    to_jid=self.peer.full(),
+                                    message={u'': feedback},
+                                    subject={},
                                     mess_type=C.MESS_TYPE_INFO,
-                                    to_jid=self.peer.full(),
                                     extra={},
                                     profile=client.profile)
         # TODO: send signal to frontends
@@ -150,7 +160,7 @@
     def savePrivkey(self):
         log.debug(u"savePrivkey")
         if self.privkey is None:
-            raise exceptions.InternalError(_("Save is called but privkey is None !"))
+            raise exceptions.InternalError(_(u"Save is called but privkey is None !"))
         priv_key = self.privkey.serializePrivateKey().encode('hex')
         d = self.host.memory.encryptValue(priv_key, self.client.profile)
         def save_encrypted_key(encrypted_priv_key):
@@ -210,10 +220,10 @@
         host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=100000)
         host.trigger.add("messageSend", self.messageSendTrigger, priority=100000)
         host.bridge.addMethod("skipOTR", ".plugin", in_sign='s', out_sign='', method=self._skipOTR)  # FIXME: must be removed, must be done on per-message basis
-        host.importMenu((MAIN_MENU, D_("Start/Refresh")), self._startRefresh, security_limit=0, help_string=D_("Start or refresh an OTR session"), type_=C.MENU_SINGLE)
-        host.importMenu((MAIN_MENU, D_("End session")), self._endSession, security_limit=0, help_string=D_("Finish an OTR session"), type_=C.MENU_SINGLE)
-        host.importMenu((MAIN_MENU, D_("Authenticate")), self._authenticate, security_limit=0, help_string=D_("Authenticate user/see your fingerprint"), type_=C.MENU_SINGLE)
-        host.importMenu((MAIN_MENU, D_("Drop private key")), self._dropPrivKey, security_limit=0, type_=C.MENU_SINGLE)
+        host.importMenu((OTR_MENU, D_(u"Start/Refresh")), self._otrStartRefresh, security_limit=0, help_string=D_(u"Start or refresh an OTR session"), type_=C.MENU_SINGLE)
+        host.importMenu((OTR_MENU, D_(u"End session")), self._otrSessionEnd, security_limit=0, help_string=D_(u"Finish an OTR session"), type_=C.MENU_SINGLE)
+        host.importMenu((OTR_MENU, D_(u"Authenticate")), self._otrAuthenticate, security_limit=0, help_string=D_(u"Authenticate user/see your fingerprint"), type_=C.MENU_SINGLE)
+        host.importMenu((OTR_MENU, D_(u"Drop private key")), self._dropPrivKey, security_limit=0, type_=C.MENU_SINGLE)
         host.trigger.add("presenceReceived", self._presenceReceivedTrigger)
 
     def _fixPotr(self):
@@ -270,7 +280,7 @@
         except KeyError:
             pass
 
-    def _startRefresh(self, menu_data, profile):
+    def _otrStartRefresh(self, menu_data, profile):
         """Start or refresh an OTR session
 
         @param menu_data: %(menu_data)s
@@ -279,17 +289,24 @@
         client = self.host.getClient(profile)
         try:
             to_jid = jid.JID(menu_data['jid'])
-            if not to_jid.resource:
-                to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored
         except KeyError:
-            log.error(_("jid key is not present !"))
+            log.error(_(u"jid key is not present !"))
             return defer.fail(exceptions.DataError)
-        otrctx = self.context_managers[profile].getContextForUser(to_jid)
-        query = otrctx.messageSend(0, '?OTRv?')
-        otrctx.inject(query)
+        self.startRefresh(client, to_jid)
         return {}
 
-    def _endSession(self, menu_data, profile):
+    def startRefresh(self, client, to_jid):
+        """Start or refresh an OTR session
+
+        @param to_jid(jid.JID): jid to start encrypted session with
+        """
+        if not to_jid.resource:
+            to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored
+        otrctx = self.context_managers[client.profile].getContextForUser(to_jid)
+        query = otrctx.sendMessage(0, '?OTRv?')
+        otrctx.inject(query)
+
+    def _otrSessionEnd(self, menu_data, profile):
         """End an OTR session
 
         @param menu_data: %(menu_data)s
@@ -298,17 +315,22 @@
         client = self.host.getClient(profile)
         try:
             to_jid = jid.JID(menu_data['jid'])
-            if not to_jid.resource:
-                to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored
         except KeyError:
-            log.error(_("jid key is not present !"))
+            log.error(_(u"jid key is not present !"))
             return defer.fail(exceptions.DataError)
-        otrctx = self.context_managers[profile].getContextForUser(to_jid)
+        self.endSession(client, to_jid)
+        return {}
+
+    def endSession(self, client, to_jid):
+        """End an OTR session"""
+        if not to_jid.resource:
+            to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored
+        otrctx = self.context_managers[client.profile].getContextForUser(to_jid)
         otrctx.disconnect()
         return {}
 
-    def _authenticate(self, menu_data, profile):
-        """Authenticate other user and see our own fingerprint
+    def _otrAuthenticate(self, menu_data, profile):
+        """End an OTR session
 
         @param menu_data: %(menu_data)s
         @param profile: %(doc_profile)s
@@ -316,12 +338,16 @@
         client = self.host.getClient(profile)
         try:
             to_jid = jid.JID(menu_data['jid'])
-            if not to_jid.resource:
-                to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored
         except KeyError:
-            log.error(_("jid key is not present !"))
+            log.error(_(u"jid key is not present !"))
             return defer.fail(exceptions.DataError)
-        ctxMng = self.context_managers[profile]
+        return self.authenticate(client, to_jid)
+
+    def authenticate(self, client, to_jid):
+        """Authenticate other user and see our own fingerprint"""
+        if not to_jid.resource:
+            to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored
+        ctxMng = self.context_managers[client.profile]
         otrctx = ctxMng.getContextForUser(to_jid)
         priv_key = ctxMng.account.privkey
 
@@ -329,10 +355,10 @@
             # we have no private key yet
             dialog = xml_tools.XMLUI(C.XMLUI_DIALOG,
                      dialog_opt = {C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE,
-                                   C.XMLUI_DATA_MESS: _("You have no private key yet, start an OTR conversation to have one"),
+                                   C.XMLUI_DATA_MESS: _(u"You have no private key yet, start an OTR conversation to have one"),
                                    C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_WARNING
                                   },
-                     title = _("No private key"),
+                     title = _(u"No private key"),
                      )
             return {'xmlui': dialog.toXml()}
 
@@ -342,10 +368,10 @@
             # we have a private key, but not the fingerprint of our correspondent
             dialog = xml_tools.XMLUI(C.XMLUI_DIALOG,
                      dialog_opt = {C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE,
-                                   C.XMLUI_DATA_MESS: _("Your fingerprint is:\n{fingerprint}\n\nStart an OTR conversation to have your correspondent one.").format(fingerprint=priv_key),
+                                   C.XMLUI_DATA_MESS: _(u"Your fingerprint is:\n{fingerprint}\n\nStart an OTR conversation to have your correspondent one.").format(fingerprint=priv_key),
                                    C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_INFO
                                   },
-                     title = _("Fingerprint"),
+                     title = _(u"Fingerprint"),
                      )
             return {'xmlui': dialog.toXml()}
 
@@ -354,10 +380,10 @@
             data = xml_tools.XMLUIResult2DataFormResult(raw_data)
             if data['match'] == 'yes':
                 otrctx.setCurrentTrust('verified')
-                note_msg = _("Your correspondent {correspondent} is now TRUSTED")
+                note_msg = _(u"Your correspondent {correspondent} is now TRUSTED")
             else:
                 otrctx.setCurrentTrust('')
-                note_msg = _("Your correspondent {correspondent} is now UNTRUSTED")
+                note_msg = _(u"Your correspondent {correspondent} is now UNTRUSTED")
             note = xml_tools.XMLUI(C.XMLUI_DIALOG, dialog_opt = {
                                    C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE,
                                    C.XMLUI_DATA_MESS: note_msg.format(correspondent=otrctx.peer)}
@@ -370,11 +396,11 @@
         xmlui = xml_tools.XMLUI(C.XMLUI_FORM, title=_('Authentication (%s)') % to_jid.full(), submit_id=submit_id)
         xmlui.addText(_(AUTH_TXT))
         xmlui.addDivider()
-        xmlui.addText(_("Your own fingerprint is:\n{fingerprint}").format(fingerprint=priv_key))
-        xmlui.addText(_("Your correspondent fingerprint should be:\n{fingerprint}").format(fingerprint=other_fingerprint))
+        xmlui.addText(D_(u"Your own fingerprint is:\n{fingerprint}").format(fingerprint=priv_key))
+        xmlui.addText(D_(u"Your correspondent fingerprint should be:\n{fingerprint}").format(fingerprint=other_fingerprint))
         xmlui.addDivider('blank')
         xmlui.changeContainer('pairs')
-        xmlui.addLabel(_('Is your correspondent fingerprint the same as here ?'))
+        xmlui.addLabel(D_(u'Is your correspondent fingerprint the same as here ?'))
         xmlui.addList("match", [('yes', _('yes')),('no', _('no'))], ['yes' if trusted else 'no'])
         return {'xmlui': xmlui.toXml()}
 
@@ -390,12 +416,12 @@
             if not to_jid.resource:
                 to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored
         except KeyError:
-            log.error(_("jid key is not present !"))
+            log.error(_(u"jid key is not present !"))
             return defer.fail(exceptions.DataError)
 
         ctxMng = self.context_managers[profile]
         if ctxMng.account.privkey is None:
-            return {'xmlui': xml_tools.note(_("You don't have a private key yet !")).toXml()}
+            return {'xmlui': xml_tools.note(_(u"You don't have a private key yet !")).toXml()}
 
         def dropKey(data, profile):
             if C.bool(data['answer']):
@@ -404,7 +430,7 @@
                     context.disconnect()
                 ctxMng.account.privkey = None
                 ctxMng.account.getPrivkey()  # as account.privkey is None, getPrivkey will generate a new key, and save it
-                return {'xmlui': xml_tools.note(_("Your private key has been dropped")).toXml()}
+                return {'xmlui': xml_tools.note(_(u"Your private key has been dropped")).toXml()}
             return {}
 
         submit_id = self.host.registerCallback(dropKey, with_data=True, one_shot=True)
@@ -425,10 +451,15 @@
             if otrctx.state == potr.context.STATE_ENCRYPTED:
                 log.warning(u"Received unencrypted message in an encrypted context (from %(jid)s)" % {'jid': from_jid.full()})
                 client = self.host.getClient(profile)
-                self.host.bridge.messageNew(from_jid.full(),
-                                            _(u"WARNING: received unencrypted data in a supposedly encrypted context"),
+
+                feedback=D_(u"WARNING: received unencrypted data in a supposedly encrypted context"),
+                self.host.bridge.messageNew(uid=unicode(uuid.uuid4()),
+                                            timestamp=time.time(),
+                                            from_jid=from_jid.full(),
+                                            to_jid=client.jid.full(),
+                                            message={u'': feedback},
+                                            subject={},
                                             mess_type=C.MESS_TYPE_INFO,
-                                            to_jid=client.jid.full(),
                                             extra={},
                                             profile=client.profile)
             encrypted = False
@@ -487,11 +518,14 @@
                 otrctx.sendMessage(0, msg.encode('utf-8'))
                 self.host.messageSendToBridge(mess_data, client)
             else:
-                feedback = D_("Your message was not sent because your correspondent closed the encrypted conversation on his/her side. Either close your own side, or refresh the session.")
-                self.host.bridge.messageNew(to_jid.full(),
-                                            feedback,
+                feedback = D_(u"Your message was not sent because your correspondent closed the encrypted conversation on his/her side. Either close your own side, or refresh the session.")
+                self.host.bridge.messageNew(uid=unicode(uuid.uuid4()),
+                                            timestamp=time.time(),
+                                            from_jid=to_jid.full(),
+                                            to_jid=client.jid.full(),
+                                            message={u'': feedback},
+                                            subject={},
                                             mess_type=C.MESS_TYPE_INFO,
-                                            to_jid=client.jid.full(),
                                             extra={},
                                             profile=client.profile)
             return False