Mercurial > libervia-web
comparison src/browser/sat_browser/plugin_sec_otr.py @ 663:423182fea41c frontends_multi_profiles
browser_side: fixes OTR using the new resource system and proper triggers (send and receive message) or listener (presence update)
author | souliane <souliane@mailoo.org> |
---|---|
date | Tue, 03 Mar 2015 07:21:50 +0100 |
parents | ebb602d8b3f2 |
children | 8449a5db0602 |
comparison
equal
deleted
inserted
replaced
662:ebb602d8b3f2 | 663:423182fea41c |
---|---|
21 """ | 21 """ |
22 This file is adapted from sat.plugins.plugin.sec_otr. It offers browser-side OTR encryption using otr.js. | 22 This file is adapted from sat.plugins.plugin.sec_otr. It offers browser-side OTR encryption using otr.js. |
23 The text messages to display are mostly taken from the Pidgin OTR plugin (GPL 2.0, see http://otr.cypherpunks.ca). | 23 The text messages to display are mostly taken from the Pidgin OTR plugin (GPL 2.0, see http://otr.cypherpunks.ca). |
24 """ | 24 """ |
25 | 25 |
26 from sat.core.log import getLogger | |
27 log = getLogger(__name__) | |
28 | |
26 from sat.core.i18n import _, D_ | 29 from sat.core.i18n import _, D_ |
27 from sat.core.log import getLogger | |
28 from sat.core import exceptions | 30 from sat.core import exceptions |
29 log = getLogger(__name__) | 31 from sat.tools.misc import TriggerManager |
30 | 32 |
31 from constants import Const as C | 33 from constants import Const as C |
32 from sat_frontends.tools import jid | 34 from sat_frontends.tools import jid |
33 import otrjs_wrapper as otr | 35 import otrjs_wrapper as otr |
34 import dialog | 36 import dialog |
37 | 39 |
38 NS_OTR = "otr_plugin" | 40 NS_OTR = "otr_plugin" |
39 PRIVATE_KEY = "PRIVATE KEY" | 41 PRIVATE_KEY = "PRIVATE KEY" |
40 MAIN_MENU = D_('OTR encryption') | 42 MAIN_MENU = D_('OTR encryption') |
41 DIALOG_EOL = "<br />" | 43 DIALOG_EOL = "<br />" |
42 DIALOG_USERS_ML = D_("<a href='mailto:users@salut-a-toi.org?subject={subject}&body=Please give us some hints about how to reproduce the bug (your browser name and version, what you did and what happened)'>users@salut-a-toi.org</a>") | |
43 | 44 |
44 AUTH_TRUSTED = D_("Verified") | 45 AUTH_TRUSTED = D_("Verified") |
45 AUTH_UNTRUSTED = D_("Unverified") | 46 AUTH_UNTRUSTED = D_("Unverified") |
46 AUTH_OTHER_TITLE = D_("Authentication of {jid}") | 47 AUTH_OTHER_TITLE = D_("Authentication of {jid}") |
47 AUTH_US_TITLE = D_("Authentication to {jid}") | 48 AUTH_US_TITLE = D_("Authentication to {jid}") |
70 QUERY_ENCRYPTED = D_('Attempting to refresh the OTR conversation with {jid}...') | 71 QUERY_ENCRYPTED = D_('Attempting to refresh the OTR conversation with {jid}...') |
71 QUERY_NOT_ENCRYPTED = D_('Attempting to start an OTR conversation with {jid}...') | 72 QUERY_NOT_ENCRYPTED = D_('Attempting to start an OTR conversation with {jid}...') |
72 AKE_ENCRYPTED = D_(" conversation with {jid} started. Your client is not logging this conversation.") | 73 AKE_ENCRYPTED = D_(" conversation with {jid} started. Your client is not logging this conversation.") |
73 AKE_NOT_ENCRYPTED = D_("ERROR: successfully ake'd with {jid} but the conversation is not encrypted!") | 74 AKE_NOT_ENCRYPTED = D_("ERROR: successfully ake'd with {jid} but the conversation is not encrypted!") |
74 END_ENCRYPTED = D_("ERROR: the OTR session ended but the context is still supposedly encrypted!") | 75 END_ENCRYPTED = D_("ERROR: the OTR session ended but the context is still supposedly encrypted!") |
75 END_PLAIN = D_("Your conversation with {jid} is no more or hasn't been encrypted.") | 76 END_PLAIN_NO_MORE = D_("Your conversation with {jid} is no more encrypted.") |
77 END_PLAIN_HAS_NOT = D_("Your conversation with {jid} hasn't been encrypted.") | |
76 END_FINISHED = D_("{jid} has ended his or her private conversation with you; you should do the same.") | 78 END_FINISHED = D_("{jid} has ended his or her private conversation with you; you should do the same.") |
77 | 79 |
78 KEY_TITLE = D_('Private key') | 80 KEY_TITLE = D_('Private key') |
79 KEY_NA_TITLE = D_("No private key") | 81 KEY_NA_TITLE = D_("No private key") |
80 KEY_NA_TXT = D_("You don't have any private key yet.") | 82 KEY_NA_TXT = D_("You don't have any private key yet.") |
90 QUERY_KEY = D_("You already have a private key, but to start the conversation will still require a couple of seconds.{eol}{eol}") | 92 QUERY_KEY = D_("You already have a private key, but to start the conversation will still require a couple of seconds.{eol}{eol}") |
91 QUERY_CONFIRM = D_("Press OK to start now the encryption.") | 93 QUERY_CONFIRM = D_("Press OK to start now the encryption.") |
92 | 94 |
93 ACTION_NA_TITLE = D_("Impossible action") | 95 ACTION_NA_TITLE = D_("Impossible action") |
94 ACTION_NA = D_("Your correspondent must be connected to start an OTR conversation with him.") | 96 ACTION_NA = D_("Your correspondent must be connected to start an OTR conversation with him.") |
95 RESOURCE_ISSUE_TITLE = D_("Security issue") | |
96 RESOURCE_ISSUE = D_("Your correspondent's resource is unknown!{eol}{eol}You should stop any OTR conversation with {jid} to avoid sending him unencrypted messages in an encrypted context.{eol}{eol}Please report the bug to the users mailing list: {users_ml}.") | |
97 | 97 |
98 DEFAULT_POLICY_FLAGS = { | 98 DEFAULT_POLICY_FLAGS = { |
99 'ALLOW_V2': True, | 99 'ALLOW_V2': True, |
100 'ALLOW_V3': True, | 100 'ALLOW_V3': True, |
101 'REQUIRE_ENCRYPTION': False, | 101 'REQUIRE_ENCRYPTION': False, |
102 'SEND_WHITESPACE_TAG': False, # FIXME: we need to complete sendMessageTrigger before turning this to True | 102 'SEND_WHITESPACE_TAG': False, # FIXME: we need to complete sendMessageTg before turning this to True |
103 'WHITESPACE_START_AKE': False, # FIXME: we need to complete messageReceivedTrigger before turning this to True | 103 'WHITESPACE_START_AKE': False, # FIXME: we need to complete newMessageTg before turning this to True |
104 } | 104 } |
105 | 105 |
106 # list a couple of texts or htmls (untrusted, trusted) for each state | 106 # list a couple of texts or htmls (untrusted, trusted) for each state |
107 OTR_MSG_STATES = { | 107 OTR_MSG_STATES = { |
108 otr.context.STATE_PLAINTEXT: [ | 108 otr.context.STATE_PLAINTEXT: [ |
118 '<img src="media/icons/silk/lock_break.png" /><img src="media/icons/silk/key.png" />' | 118 '<img src="media/icons/silk/lock_break.png" /><img src="media/icons/silk/key.png" />' |
119 ] | 119 ] |
120 } | 120 } |
121 | 121 |
122 | 122 |
123 unicode = str # FIXME: pyjamas workaround | |
124 | |
125 | |
126 class NotConnectedEntity(Exception): | |
127 pass | |
128 | |
129 | |
123 class Context(otr.context.Context): | 130 class Context(otr.context.Context): |
124 | 131 |
125 def __init__(self, host, account, other_jid): | 132 def __init__(self, host, account, other_jid): |
126 """ | 133 """ |
127 | 134 |
156 log.warning("A plain-text message has been handled by otr.js") | 163 log.warning("A plain-text message has been handled by otr.js") |
157 log.debug("message received (was %s): %s" % ('encrypted' if encrypted else 'plain', msg)) | 164 log.debug("message received (was %s): %s" % ('encrypted' if encrypted else 'plain', msg)) |
158 if not encrypted: | 165 if not encrypted: |
159 if self.state == otr.context.STATE_ENCRYPTED: | 166 if self.state == otr.context.STATE_ENCRYPTED: |
160 log.warning(u"Received unencrypted message in an encrypted context (from %(jid)s)" % {'jid': self.peer}) | 167 log.warning(u"Received unencrypted message in an encrypted context (from %(jid)s)" % {'jid': self.peer}) |
161 self.host.newMessageCb(self.peer, RECEIVE_PLAIN_IN_ENCRYPTED_CONTEXT, C.MESS_TYPE_INFO, self.host.whoami, {}) | 168 self.host.newMessageHandler(unicode(self.peer), RECEIVE_PLAIN_IN_ENCRYPTED_CONTEXT, C.MESS_TYPE_INFO, unicode(self.host.whoami), {}) |
162 self.host.newMessageCb(self.peer, msg, "chat", self.host.whoami, {}) | 169 self.host.newMessageHandler(unicode(self.peer), msg, C.MESS_TYPE_CHAT, unicode(self.host.whoami), {}) |
163 | 170 |
164 def sendMessageCb(self, msg, meta=None): | 171 def sendMessageCb(self, msg, meta=None): |
165 assert isinstance(self.peer, jid.JID) | 172 assert isinstance(self.peer, jid.JID) |
166 log.debug("message to send%s: %s" % ((' (attached meta data: %s)' % meta) if meta else '', msg)) | 173 log.debug("message to send%s: %s" % ((' (attached meta data: %s)' % meta) if meta else '', msg)) |
167 self.host.bridge.call('sendMessage', (None, self.host.sendError), self.peer, msg, '', 'chat', {'send_only': 'true'}) | 174 self.host.bridge.call('sendMessage', (None, self.host.sendError), unicode(self.peer), msg, '', C.MESS_TYPE_CHAT, {'send_only': 'true'}) |
168 | 175 |
169 def messageErrorCb(self, error): | 176 def messageErrorCb(self, error): |
170 log.error('error occured: %s' % error) | 177 log.error('error occured: %s' % error) |
171 | 178 |
172 def setStateCb(self, msg_state, status): | 179 def setStateCb(self, msg_state, status): |
184 trusted_str = AUTH_TRUSTED if trust else AUTH_UNTRUSTED | 191 trusted_str = AUTH_TRUSTED if trust else AUTH_UNTRUSTED |
185 feedback = (trusted_str + AKE_ENCRYPTED) if msg_state == otr.context.STATE_ENCRYPTED else AKE_NOT_ENCRYPTED | 192 feedback = (trusted_str + AKE_ENCRYPTED) if msg_state == otr.context.STATE_ENCRYPTED else AKE_NOT_ENCRYPTED |
186 | 193 |
187 elif status == otr.context.STATUS_END_OTR: | 194 elif status == otr.context.STATUS_END_OTR: |
188 if msg_state == otr.context.STATE_PLAINTEXT: | 195 if msg_state == otr.context.STATE_PLAINTEXT: |
189 feedback = END_PLAIN | 196 feedback = END_PLAIN_NO_MORE |
190 elif msg_state == otr.context.STATE_ENCRYPTED: | 197 elif msg_state == otr.context.STATE_ENCRYPTED: |
191 log.error(END_ENCRYPTED) | 198 log.error(END_ENCRYPTED) |
192 elif msg_state == otr.context.STATE_FINISHED: | 199 elif msg_state == otr.context.STATE_FINISHED: |
193 feedback = END_FINISHED | 200 feedback = END_FINISHED |
194 | 201 |
195 self.host.newMessageCb(self.peer, feedback.format(jid=other_jid_s), C.MESS_TYPE_INFO, self.host.whoami, {'header_info': OTR.getInfoText(msg_state, trust)}) | 202 self.host.newMessageHandler(unicode(self.peer), feedback.format(jid=other_jid_s), C.MESS_TYPE_INFO, unicode(self.host.whoami), {'header_info': OTR.getInfoText(msg_state, trust)}) |
196 | 203 |
197 def setCurrentTrust(self, new_trust='', act='asked', type_='trust'): | 204 def setCurrentTrust(self, new_trust='', act='asked', type_='trust'): |
198 log.debug("setCurrentTrust: trust={trust}, act={act}, type={type}".format(type=type_, trust=new_trust, act=act)) | 205 log.debug("setCurrentTrust: trust={trust}, act={act}, type={type}".format(type=type_, trust=new_trust, act=act)) |
199 title = (AUTH_OTHER_TITLE if act == "asked" else AUTH_US_TITLE).format(jid=self.peer) | 206 title = (AUTH_OTHER_TITLE if act == "asked" else AUTH_US_TITLE).format(jid=self.peer) |
200 old_trust = self.getCurrentTrust() | 207 old_trust = self.getCurrentTrust() |
213 if act != "asked": | 220 if act != "asked": |
214 return | 221 return |
215 otr.context.Context.setCurrentTrust(self, new_trust) | 222 otr.context.Context.setCurrentTrust(self, new_trust) |
216 if old_trust != new_trust: | 223 if old_trust != new_trust: |
217 feedback = AUTH_STATUS.format(state=(AUTH_TRUSTED if new_trust else AUTH_UNTRUSTED).lower()) | 224 feedback = AUTH_STATUS.format(state=(AUTH_TRUSTED if new_trust else AUTH_UNTRUSTED).lower()) |
218 self.host.newMessageCb(self.peer, feedback, C.MESS_TYPE_INFO, self.host.whoami, {'header_info': OTR.getInfoText(self.state, new_trust)}) | 225 self.host.newMessageHandler(unicode(self.peer), feedback, C.MESS_TYPE_INFO, unicode(self.host.whoami), {'header_info': OTR.getInfoText(self.state, new_trust)}) |
219 | 226 |
220 def fingerprintAuthCb(self): | 227 def fingerprintAuthCb(self): |
221 """OTR v2 authentication using manual fingerprint comparison""" | 228 """OTR v2 authentication using manual fingerprint comparison""" |
222 priv_key = self.user.privkey | 229 priv_key = self.user.privkey |
223 | 230 |
325 self.host = host | 332 self.host = host |
326 self.account = Account(host) | 333 self.account = Account(host) |
327 self.contexts = {} | 334 self.contexts = {} |
328 | 335 |
329 def startContext(self, other_jid): | 336 def startContext(self, other_jid): |
330 assert isinstance(other_jid, jid.JID) | 337 assert isinstance(other_jid, jid.JID) # never start an OTR session with a bare JID |
331 # FIXME upstream: apparently pyjamas doesn't implement setdefault well, it ignores JID.__hash__ redefinition | 338 # FIXME upstream: apparently pyjamas doesn't implement setdefault well, it ignores JID.__hash__ redefinition |
332 #context = self.contexts.setdefault(other_jid, Context(self.host, self.account, other_jid)) | 339 #context = self.contexts.setdefault(other_jid, Context(self.host, self.account, other_jid)) |
333 if other_jid not in self.contexts: | 340 if other_jid not in self.contexts: |
334 self.contexts[other_jid] = Context(self.host, self.account, other_jid) | 341 self.contexts[other_jid] = Context(self.host, self.account, other_jid) |
335 return self.contexts[other_jid] | 342 return self.contexts[other_jid] |
339 | 346 |
340 @param other_jid (jid.JID): your correspondent | 347 @param other_jid (jid.JID): your correspondent |
341 @param start (bool): start non-existing context if True | 348 @param start (bool): start non-existing context if True |
342 @return: Context | 349 @return: Context |
343 """ | 350 """ |
351 try: | |
352 other_jid = self.fixResource(other_jid) | |
353 except NotConnectedEntity: | |
354 log.debug(u"getContextForUser [%s]: not connected!" % other_jid) | |
355 return None | |
344 log.debug(u"getContextForUser [%s]" % other_jid) | 356 log.debug(u"getContextForUser [%s]" % other_jid) |
345 if not other_jid.resource: | |
346 log.error("getContextForUser called with a bare jid") | |
347 running_sessions = [jid_.bare for jid_ in self.contexts.keys() if self.contexts[jid_].state == otr.context.STATE_ENCRYPTED] | |
348 if start or (other_jid in running_sessions): | |
349 users_ml = DIALOG_USERS_ML.format(subject=D_("OTR issue in Libervia: getContextForUser called with a bare jid in an encrypted context")) | |
350 text = RESOURCE_ISSUE.format(eol=DIALOG_EOL, jid=other_jid, users_ml=users_ml) | |
351 dialog.InfoDialog(RESOURCE_ISSUE_TITLE, text, AddStyleName="maxWidthLimit").show() | |
352 return None # never start an OTR session with a bare JID | |
353 if start: | 357 if start: |
354 return self.startContext(other_jid) | 358 return self.startContext(other_jid) |
355 else: | 359 else: |
356 return self.contexts.get(other_jid, None) | 360 return self.contexts.get(other_jid, None) |
357 | 361 |
362 def fixResource(self, other_jid): | |
363 """Return the full JID in case the resource of the given JID is missing. | |
364 | |
365 @param other_jid (jid.JID): JID to check | |
366 @return jid.JID | |
367 """ | |
368 if other_jid.resource: | |
369 return other_jid | |
370 clist = self.host.contact_list | |
371 if clist.getCache(other_jid.bare, C.PRESENCE_SHOW) is None: | |
372 raise NotConnectedEntity | |
373 return clist.getFullJid(other_jid) | |
374 | |
358 | 375 |
359 class OTR(object): | 376 class OTR(object): |
360 | 377 |
361 def __init__(self, host): | 378 def __init__(self, host): |
362 log.info(_(u"OTR plugin initialization")) | 379 log.info(_(u"OTR plugin initialization")) |
363 self.host = host | 380 self.host = host |
364 self.context_manager = None | 381 self.context_manager = None |
365 self.last_resources = {} | |
366 self.host.bridge._registerMethods(["skipOTR"]) | 382 self.host.bridge._registerMethods(["skipOTR"]) |
383 self.host.trigger.add("newMessage", self.newMessageTg, priority=TriggerManager.MAX_PRIORITY) | |
384 self.host.trigger.add("sendMessage", self.sendMessageTg, priority=TriggerManager.MAX_PRIORITY) | |
367 | 385 |
368 @classmethod | 386 @classmethod |
369 def getInfoText(self, state=otr.context.STATE_PLAINTEXT, trust=''): | 387 def getInfoText(self, state=otr.context.STATE_PLAINTEXT, trust=''): |
370 """Get the widget info text for a certain message state and trust. | 388 """Get the widget info text for a certain message state and trust. |
371 | 389 |
375 """ | 393 """ |
376 if not state: | 394 if not state: |
377 state = OTR_MSG_STATES.keys()[0] | 395 state = OTR_MSG_STATES.keys()[0] |
378 return OTR_MSG_STATES[state][1 if trust else 0] | 396 return OTR_MSG_STATES[state][1 if trust else 0] |
379 | 397 |
380 def infoTextCallback(self, other_jid, cb): | 398 def getInfoTextForUser(self, other_jid): |
381 """Get the current info text for a conversation and run a callback. | 399 """Get the current info text for a conversation. |
382 | 400 |
383 @param other_jid (jid.JID): JID of the correspondant | 401 @param other_jid (jid.JID): JID of the correspondant |
384 @paam cb (callable): method to be called with the computed info text | 402 """ |
385 """ | 403 otrctx = self.context_manager.getContextForUser(other_jid, start=False) |
386 def gotResource(other_jid): | 404 if otrctx is None: |
387 otrctx = self.context_manager.getContextForUser(other_jid, start=False) | 405 return OTR.getInfoText() |
388 if otrctx is None: | 406 else: |
389 cb(OTR.getInfoText()) | 407 return OTR.getInfoText(otrctx.state, otrctx.getCurrentTrust()) |
390 else: | |
391 cb(OTR.getInfoText(otrctx.state, otrctx.getCurrentTrust())) | |
392 | |
393 self.fixResource(other_jid, gotResource) | |
394 | 408 |
395 def inhibitMenus(self): | 409 def inhibitMenus(self): |
396 """Tell the caller which dynamic menus should be inhibited""" | 410 """Tell the caller which dynamic menus should be inhibited""" |
397 return ["OTR"] # menu categories name to inhibit | 411 return ["OTR"] # menu categories name to inhibit |
398 | 412 |
408 self.context_manager = ContextManager(self.host) | 422 self.context_manager = ContextManager(self.host) |
409 # TODO: retrieve the encrypted private key from a HTML5 persistent storage, | 423 # TODO: retrieve the encrypted private key from a HTML5 persistent storage, |
410 # decrypt it, parse it with otr.crypt.PK.parsePrivateKey(privkey) and | 424 # decrypt it, parse it with otr.crypt.PK.parsePrivateKey(privkey) and |
411 # assign it to self.context_manager.account.privkey | 425 # assign it to self.context_manager.account.privkey |
412 | 426 |
427 # FIXME: workaround for a pyjamas issue: calling hash on a class method always return a different value if that method is defined directly within the class (with the "def" keyword) | |
428 self.presenceListener = self.onPresenceUpdate | |
429 self.host.addListener('presence', self.presenceListener, [C.PROF_KEY_NONE]) | |
430 | |
413 def profileDisconnected(self): | 431 def profileDisconnected(self): |
414 for context in self.context_manager.contexts.values(): | 432 for context in self.context_manager.contexts.values(): |
415 context.disconnect() | 433 context.disconnect() |
416 | 434 self.host.removeListener('presence', self.presenceListener) |
417 def fixResource(self, jid_, cb): | 435 |
418 # FIXME: it's dirty, but libervia doesn't manage resources correctly now, refactoring is planed | 436 def newMessageTg(self, from_jid, msg, msg_type, to_jid, extra, profile): |
419 if jid_.resource: | 437 if msg_type != C.MESS_TYPE_CHAT: |
420 self.last_resources[jid_.bare] = jid_.resource | |
421 cb(jid_) | |
422 elif jid_.bare in self.last_resources: | |
423 # FIXME: to be removed: must use new resource system | |
424 # jid_.setResource(self.last_resources[jid_.bare]) | |
425 cb(jid_) | |
426 else: | |
427 pass # FIXME: to be removed: must use new resource system | |
428 # def gotResource(resource): | |
429 # if resource: | |
430 # jid_.setResource(resource) | |
431 # self.last_resources[jid_.bare] = jid_.resource | |
432 # cb(jid_) | |
433 # | |
434 # self.host.bridge.call('getLastResource', gotResource, jid_) | |
435 | |
436 def messageReceivedTrigger(self, from_jid, msg, msg_type, to_jid, extra): | |
437 if msg_type == C.MESS_TYPE_INFO: | |
438 return True | 438 return True |
439 | 439 |
440 tag = otr.proto.checkForOTR(msg) | 440 tag = otr.proto.checkForOTR(msg) |
441 if tag is None or (tag == otr.context.WHITESPACE_TAG and not DEFAULT_POLICY_FLAGS['WHITESPACE_START_AKE']): | 441 if tag is None or (tag == otr.context.WHITESPACE_TAG and not DEFAULT_POLICY_FLAGS['WHITESPACE_START_AKE']): |
442 return True | 442 return True |
443 | 443 |
444 def decrypt(context): | |
445 context.receiveMessage(msg) | |
446 | |
447 def cb(jid_): | |
448 otrctx = self.context_manager.getContextForUser(jid_, start=False) | |
449 if otrctx is None: | |
450 def confirm(confirm): | |
451 if confirm: | |
452 self.host.displayWidget(chat.Chat, jid_) | |
453 decrypt(self.context_manager.startContext(jid_)) | |
454 else: | |
455 # FIXME: plain text messages with whitespaces would be lost here when WHITESPACE_START_AKE is True | |
456 pass | |
457 key = self.context_manager.account.privkey | |
458 msg = QUERY_RECEIVED + QUERY_SLOWDOWN + (QUERY_KEY if key else QUERY_NO_KEY) + QUERY_CONFIRM | |
459 dialog.ConfirmDialog(confirm, msg.format(jid=jid_, eol=DIALOG_EOL), QUERY_TITLE, AddStyleName="maxWidthLimit").show() | |
460 else: # do not ask if the context exist | |
461 decrypt(otrctx) | |
462 | |
463 other_jid = to_jid if from_jid.bare == self.host.whoami.bare else from_jid | 444 other_jid = to_jid if from_jid.bare == self.host.whoami.bare else from_jid |
464 self.fixResource(other_jid, cb) | 445 otrctx = self.context_manager.getContextForUser(other_jid, start=False) |
446 if otrctx is None: | |
447 def confirm(confirm): | |
448 if confirm: | |
449 self.host.displayWidget(chat.Chat, other_jid) | |
450 self.context_manager.startContext(other_jid).receiveMessage(msg) | |
451 else: | |
452 # FIXME: plain text messages with whitespaces would be lost here when WHITESPACE_START_AKE is True | |
453 pass | |
454 key = self.context_manager.account.privkey | |
455 question = QUERY_RECEIVED + QUERY_SLOWDOWN + (QUERY_KEY if key else QUERY_NO_KEY) + QUERY_CONFIRM | |
456 dialog.ConfirmDialog(confirm, question.format(jid=other_jid, eol=DIALOG_EOL), QUERY_TITLE, AddStyleName="maxWidthLimit").show() | |
457 else: # do not ask for user confirmation if the context exist | |
458 otrctx.receiveMessage(msg) | |
459 | |
465 return False # interrupt the main process | 460 return False # interrupt the main process |
466 | 461 |
467 def sendMessageTrigger(self, to_jid, msg, msg_type, extra): | 462 def sendMessageTg(self, to_jid, message, subject, mess_type, extra, callback, errback, profile_key): |
468 def cb(jid_): | 463 if mess_type != C.MESS_TYPE_CHAT: |
469 otrctx = self.context_manager.getContextForUser(jid_, start=False) | 464 return True |
470 if otrctx is not None and otrctx.state != otr.context.STATE_PLAINTEXT: | 465 |
471 if otrctx.state == otr.context.STATE_ENCRYPTED: | 466 otrctx = self.context_manager.getContextForUser(to_jid, start=False) |
472 log.debug(u"encrypting message") | 467 if otrctx is not None and otrctx.state != otr.context.STATE_PLAINTEXT: |
473 otrctx.sendMessage(msg) | 468 if otrctx.state == otr.context.STATE_ENCRYPTED: |
474 self.host.newMessageCb(self.host.whoami, msg, msg_type, jid_, extra) | 469 log.debug(u"encrypting message") |
475 else: | 470 otrctx.sendMessage(message) |
476 feedback = SEND_PLAIN_IN_FINISHED_CONTEXT | 471 self.host.newMessageHandler(unicode(self.host.whoami), message, mess_type, unicode(to_jid), extra) |
477 dialog.InfoDialog(FINISHED_CONTEXT_TITLE.format(jid=to_jid), feedback, AddStyleName="maxWidthLimit").show() | |
478 else: | 472 else: |
479 log.debug(u"sending message unencrypted") | 473 feedback = SEND_PLAIN_IN_FINISHED_CONTEXT |
480 self.host.bridge.call('sendMessage', (None, self.host.sendError), to_jid, msg, '', msg_type, extra) | 474 dialog.InfoDialog(FINISHED_CONTEXT_TITLE.format(jid=to_jid), feedback, AddStyleName="maxWidthLimit").show() |
481 | 475 return False # interrupt the main process |
482 if msg_type == 'groupchat': | 476 |
483 return True | 477 log.debug(u"sending message unencrypted") |
484 self.fixResource(to_jid, cb) | 478 return True |
485 return False # interrupt the main process | 479 |
486 | 480 def onPresenceUpdate(self, entity, show, priority, statuses, profile): |
487 def presenceReceivedTrigger(self, entity, show, priority, statuses): | |
488 if show == C.PRESENCE_UNAVAILABLE: | 481 if show == C.PRESENCE_UNAVAILABLE: |
489 self.endSession(entity, finish=True) | 482 self.endSession(entity, finish=True) |
490 return True | 483 |
491 | 484 def endSession(self, other_jid, finish=False): |
492 def endSession(self, other_jid, profile, finish=False): | |
493 """Finish or disconnect an OTR session | 485 """Finish or disconnect an OTR session |
494 | 486 |
495 @param other_jid (jid.JID): contact JID | 487 @param other_jid (jid.JID): other JID |
496 @param finish: if True, finish the session but do not disconnect it | 488 @param finish: if True, finish the session but do not disconnect it |
497 @return: True if the session has been finished or disconnected, False if there was nothing to do | 489 @return: True if the session has been finished or disconnected, False if there was nothing to do |
498 """ | 490 """ |
499 def cb(other_jid): | 491 # checking for private key existence is not needed, context checking is enough |
500 def not_available(): | 492 otrctx = self.context_manager.getContextForUser(other_jid, start=False) |
501 if not finish: | 493 if otrctx is None or otrctx.state == otr.context.STATE_PLAINTEXT: |
502 self.host.newMessageCb(other_jid, END_PLAIN.format(jid=other_jid), C.MESS_TYPE_INFO, self.host.whoami, {}) | 494 if not finish: |
503 | 495 self.host.newMessageHandler(unicode(other_jid), END_PLAIN_HAS_NOT.format(jid=other_jid), C.MESS_TYPE_INFO, unicode(self.host.whoami), {}) |
504 priv_key = self.context_manager.account.privkey | 496 return |
505 if priv_key is None: | 497 if finish: |
506 not_available() | 498 otrctx.finish() |
507 return | 499 else: |
508 | 500 otrctx.disconnect() |
509 otrctx = self.context_manager.getContextForUser(other_jid, start=False) | |
510 if otrctx is None: | |
511 not_available() | |
512 return | |
513 if finish: | |
514 otrctx.finish() | |
515 else: | |
516 otrctx.disconnect() | |
517 | |
518 self.fixResource(other_jid, cb) | |
519 | 501 |
520 # Menu callbacks | 502 # Menu callbacks |
521 | 503 |
522 def _startRefresh(self, menu_data): | 504 def _startRefresh(self, menu_data): |
523 """Start or refresh an OTR session | 505 """Start or refresh an OTR session |
527 def query(other_jid): | 509 def query(other_jid): |
528 otrctx = self.context_manager.getContextForUser(other_jid) | 510 otrctx = self.context_manager.getContextForUser(other_jid) |
529 if otrctx: | 511 if otrctx: |
530 otrctx.sendQueryMessage() | 512 otrctx.sendQueryMessage() |
531 | 513 |
532 def cb(jid_): | 514 other_jid = menu_data['jid'] |
533 key = self.context_manager.account.privkey | 515 clist = self.host.contact_list |
534 if key is None: | 516 if clist.getCache(other_jid.bare, C.PRESENCE_SHOW) is None: |
535 def confirm(confirm): | 517 dialog.InfoDialog(ACTION_NA_TITLE, ACTION_NA, AddStyleName="maxWidthLimit").show() |
536 if confirm: | 518 return |
537 query(jid_) | 519 |
538 msg = QUERY_SEND + QUERY_SLOWDOWN + QUERY_NO_KEY + QUERY_CONFIRM | 520 key = self.context_manager.account.privkey |
539 dialog.ConfirmDialog(confirm, msg.format(jid=jid_, eol=DIALOG_EOL), QUERY_TITLE, AddStyleName="maxWidthLimit").show() | 521 if key is None: |
540 else: # on query reception we ask always, if we initiate we just ask the first time | 522 def confirm(confirm): |
541 query(jid_) | 523 if confirm: |
542 | 524 query(other_jid) |
543 try: | 525 msg = QUERY_SEND + QUERY_SLOWDOWN + QUERY_NO_KEY + QUERY_CONFIRM |
544 other_jid = menu_data['jid'] | 526 dialog.ConfirmDialog(confirm, msg.format(jid=other_jid, eol=DIALOG_EOL), QUERY_TITLE, AddStyleName="maxWidthLimit").show() |
545 contact_list = self.host.contact_list | 527 else: # on query reception we ask always, if we initiate we just ask the first time |
546 if contact_list.getCache(other_jid.bare, C.PRESENCE_SHOW) is None: | 528 query(other_jid) |
547 dialog.InfoDialog(ACTION_NA_TITLE, ACTION_NA, AddStyleName="maxWidthLimit").show() | |
548 return | |
549 self.fixResource(other_jid, cb) | |
550 except KeyError: | |
551 log.error(_("jid key is not present !")) | |
552 | 529 |
553 def _endSession(self, menu_data): | 530 def _endSession(self, menu_data): |
554 """End an OTR session | 531 """End an OTR session |
555 | 532 |
556 @param menu_data: %(menu_data)s | 533 @param menu_data: %(menu_data)s |
557 """ | 534 """ |
558 try: | 535 self.endSession(menu_data['jid']) |
559 other_jid = menu_data['jid'] | |
560 except KeyError: | |
561 log.error(_("jid key is not present !")) | |
562 return None | |
563 self.endSession(other_jid) | |
564 | 536 |
565 def _authenticate(self, menu_data, profile): | 537 def _authenticate(self, menu_data, profile): |
566 """Authenticate other user and see our own fingerprint | 538 """Authenticate other user and see our own fingerprint |
567 | 539 |
568 @param menu_data: %(menu_data)s | 540 @param menu_data: %(menu_data)s |
569 @param profile: %(doc_profile)s | 541 @param profile: %(doc_profile)s |
570 """ | 542 """ |
571 def not_available(): | 543 def not_available(): |
572 dialog.InfoDialog(AUTH_TRUST_NA_TITLE, AUTH_TRUST_NA_TXT, AddStyleName="maxWidthLimit").show() | 544 dialog.InfoDialog(AUTH_TRUST_NA_TITLE, AUTH_TRUST_NA_TXT, AddStyleName="maxWidthLimit").show() |
573 | 545 |
574 priv_key = self.context_manager.account.privkey | 546 to_jid = menu_data['jid'] |
575 if priv_key is None: | 547 |
548 # checking for private key existence is not needed, context checking is enough | |
549 otrctx = self.context_manager.getContextForUser(to_jid, start=False) | |
550 if otrctx is None or otrctx.state != otr.context.STATE_ENCRYPTED: | |
576 not_available() | 551 not_available() |
577 return | 552 return |
578 | 553 otr_version = otrctx.getUsedVersion() |
579 def cb(to_jid): | 554 if otr_version == otr.context.OTR_VERSION_2: |
580 otrctx = self.context_manager.getContextForUser(to_jid, start=False) | 555 otrctx.fingerprintAuthCb() |
581 if otrctx is None: | 556 elif otr_version == otr.context.OTR_VERSION_3: |
582 not_available() | 557 otrctx.smpAuthCb('question', None, 'asked') |
583 return | 558 else: |
584 otr_version = otrctx.getUsedVersion() | 559 not_available() |
585 if otr_version == otr.context.OTR_VERSION_2: | |
586 otrctx.fingerprintAuthCb() | |
587 elif otr_version == otr.context.OTR_VERSION_3: | |
588 otrctx.smpAuthCb('question', None, 'asked') | |
589 else: | |
590 not_available() | |
591 | |
592 try: | |
593 to_jid = menu_data['jid'] | |
594 self.fixResource(to_jid, cb) | |
595 except KeyError: | |
596 log.error(_("jid key is not present !")) | |
597 return None | |
598 | 560 |
599 def _dropPrivkey(self, menu_data, profile): | 561 def _dropPrivkey(self, menu_data, profile): |
600 """Drop our private Key | 562 """Drop our private Key |
601 | 563 |
602 @param menu_data: %(menu_data)s | 564 @param menu_data: %(menu_data)s |
606 if priv_key is None: | 568 if priv_key is None: |
607 # we have no private key yet | 569 # we have no private key yet |
608 dialog.InfoDialog(KEY_NA_TITLE, KEY_NA_TXT, AddStyleName="maxWidthLimit").show() | 570 dialog.InfoDialog(KEY_NA_TITLE, KEY_NA_TXT, AddStyleName="maxWidthLimit").show() |
609 return | 571 return |
610 | 572 |
611 def cb(to_jid): | 573 def dropKey(confirm): |
612 def dropKey(confirm): | 574 if confirm: |
613 if confirm: | 575 # we end all sessions |
614 # we end all sessions | 576 for context in self.context_manager.contexts.values(): |
615 for context in self.context_manager.contexts.values(): | 577 context.disconnect() |
616 context.disconnect() | 578 self.context_manager.contexts.clear() |
617 self.context_manager.contexts.clear() | 579 self.context_manager.account.privkey = None |
618 self.context_manager.account.privkey = None | 580 dialog.InfoDialog(KEY_TITLE, KEY_DROPPED_TXT, AddStyleName="maxWidthLimit").show() |
619 dialog.InfoDialog(KEY_TITLE, KEY_DROPPED_TXT, AddStyleName="maxWidthLimit").show() | 581 |
620 | 582 dialog.ConfirmDialog(dropKey, KEY_DROP_TXT.format(eol=DIALOG_EOL), KEY_DROP_TITLE, AddStyleName="maxWidthLimit").show() |
621 dialog.ConfirmDialog(dropKey, KEY_DROP_TXT.format(eol=DIALOG_EOL), KEY_DROP_TITLE, AddStyleName="maxWidthLimit").show() | |
622 | |
623 try: | |
624 to_jid = menu_data['jid'] | |
625 self.fixResource(to_jid, cb) | |
626 except KeyError: | |
627 log.error(_("jid key is not present !")) | |
628 return None |