comparison src/browser/sat_browser/plugin_sec_otr.py @ 589:a5019e62c3e9 frontends_multi_profiles

browser side: big refactoring to base Libervia on QuickFrontend, first draft: /!\ not finished, partially working and highly instable - add collections module with an OrderedDict like class - SatWebFrontend inherit from QuickApp - general sat_frontends tools.jid module is used - bridge/json methods have moved to json module - UniBox is partially removed (should be totally removed before merge to trunk) - Signals are now register with the generic registerSignal method (which is called mainly in QuickFrontend) - the generic getOrCreateWidget method from QuickWidgetsManager is used instead of Libervia's specific methods - all Widget are now based more or less directly on QuickWidget - with the new QuickWidgetsManager.getWidgets method, it's no more necessary to check all widgets which are instance of a particular class - ChatPanel and related moved to chat module - MicroblogPanel and related moved to blog module - global and overcomplicated send method has been disabled: each class should manage its own sending - for consistency with other frontends, former ContactPanel has been renamed to ContactList and vice versa - for the same reason, ChatPanel has been renamed to Chat - for compatibility with QuickFrontend, a fake profile is used in several places, it is set to C.PROF_KEY_NONE (real profile is managed server side for obvious security reasons) - changed default url for web panel to SàT website, and contact address to generic SàT contact address - ContactList is based on QuickContactList, UI changes are done in update method - bride call (now json module) have been greatly improved, in particular call can be done in the same way as for other frontends (bridge.method_name(arg1, arg2, ..., callback=cb, errback=eb). Blocking method must be called like async methods due to javascript architecture - in bridge calls, a callback can now exists without errback - hard reload on BridgeSignals remote error has been disabled, a better option should be implemented - use of constants where that make sens, some style improvments - avatars are temporarily disabled - lot of code disabled, will be fixed or removed before merge - various other changes, check diff for more details server side: manage remote exception on getEntityData, removed getProfileJid call, added getWaitingConf, added getRoomsSubjects
author Goffi <goffi@goffi.org>
date Sat, 24 Jan 2015 01:45:39 +0100
parents 0a06cf833f5a
children 1c0d5a87c554
comparison
equal deleted inserted replaced
585:bade589dbd5a 589:a5019e62c3e9
27 from sat.core.log import getLogger 27 from sat.core.log import getLogger
28 from sat.core import exceptions 28 from sat.core import exceptions
29 log = getLogger(__name__) 29 log = getLogger(__name__)
30 30
31 from constants import Const as C 31 from constants import Const as C
32 import jid 32 from sat_frontends.tools import jid
33 import otrjs_wrapper as otr 33 import otrjs_wrapper as otr
34 import dialog 34 import dialog
35 import panels 35 import panels
36 36
37 37
125 def __init__(self, host, account, other_jid): 125 def __init__(self, host, account, other_jid):
126 """ 126 """
127 127
128 @param host (satWebFrontend) 128 @param host (satWebFrontend)
129 @param account (Account) 129 @param account (Account)
130 @param other_jid (JID): JID of the person your chat correspondent 130 @param other_jid (jid.JID): JID of the person your chat correspondent
131 """ 131 """
132 super(Context, self).__init__(account, other_jid) 132 super(Context, self).__init__(account, other_jid)
133 self.host = host 133 self.host = host
134 134
135 def getPolicy(self, key): 135 def getPolicy(self, key):
155 if not encrypted: 155 if not encrypted:
156 log.warning("A plain-text message has been handled by otr.js") 156 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)) 157 log.debug("message received (was %s): %s" % ('encrypted' if encrypted else 'plain', msg))
158 if not encrypted: 158 if not encrypted:
159 if self.state == otr.context.STATE_ENCRYPTED: 159 if self.state == otr.context.STATE_ENCRYPTED:
160 log.warning(u"Received unencrypted message in an encrypted context (from %(jid)s)" % {'jid': self.peer.full()}) 160 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, {}) 161 self.host.newMessageCb(self.peer, RECEIVE_PLAIN_IN_ENCRYPTED_CONTEXT, C.MESS_TYPE_INFO, self.host.whoami, {})
162 self.host.newMessageCb(self.peer, msg, "chat", self.host.whoami, {}) 162 self.host.newMessageCb(self.peer, msg, "chat", self.host.whoami, {})
163 163
164 def sendMessageCb(self, msg, meta=None): 164 def sendMessageCb(self, msg, meta=None):
165 assert isinstance(self.peer, jid.JID) 165 assert isinstance(self.peer, jid.JID)
166 log.debug("message to send%s: %s" % ((' (attached meta data: %s)' % meta) if meta else '', msg)) 166 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.full(), msg, '', 'chat', {'send_only': 'true'}) 167 self.host.bridge.call('sendMessage', (None, self.host.sendError), self.peer, msg, '', 'chat', {'send_only': 'true'})
168 168
169 def messageErrorCb(self, error): 169 def messageErrorCb(self, error):
170 log.error('error occured: %s' % error) 170 log.error('error occured: %s' % error)
171 171
172 def setStateCb(self, msg_state, status): 172 def setStateCb(self, msg_state, status):
173 if status == otr.context.STATUS_AKE_INIT: 173 if status == otr.context.STATUS_AKE_INIT:
174 return 174 return
175 175
176 other_jid_s = self.peer.full() 176 other_jid_s = self.peer
177 feedback = _(u"Error: the state of the conversation with %s is unknown!") 177 feedback = _(u"Error: the state of the conversation with %s is unknown!")
178 trust = self.getCurrentTrust() 178 trust = self.getCurrentTrust()
179 179
180 if status == otr.context.STATUS_SEND_QUERY: 180 if status == otr.context.STATUS_SEND_QUERY:
181 feedback = QUERY_ENCRYPTED if msg_state == otr.context.STATE_ENCRYPTED else QUERY_NOT_ENCRYPTED 181 feedback = QUERY_ENCRYPTED if msg_state == otr.context.STATE_ENCRYPTED else QUERY_NOT_ENCRYPTED
194 194
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)}) 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)})
196 196
197 def setCurrentTrust(self, new_trust='', act='asked', type_='trust'): 197 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)) 198 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.full()) 199 title = (AUTH_OTHER_TITLE if act == "asked" else AUTH_US_TITLE).format(jid=self.peer)
200 old_trust = self.getCurrentTrust() 200 old_trust = self.getCurrentTrust()
201 if type_ == 'abort': 201 if type_ == 'abort':
202 msg = AUTH_ABORTED_TXT 202 msg = AUTH_ABORTED_TXT
203 elif new_trust: 203 elif new_trust:
204 if act == "asked": 204 if act == "asked":
234 234
235 def setTrust(confirm): 235 def setTrust(confirm):
236 self.setCurrentTrust('fingerprint' if confirm else '') 236 self.setCurrentTrust('fingerprint' if confirm else '')
237 237
238 text = (AUTH_INFO_TXT + "<i>" + AUTH_FINGERPRINT_TXT + "</i>" + AUTH_FINGERPRINT_VERIFY).format(you=self.host.whoami, your_fp=priv_key.fingerprint(), other=self.peer, other_fp=other_key.fingerprint(), eol=DIALOG_EOL) 238 text = (AUTH_INFO_TXT + "<i>" + AUTH_FINGERPRINT_TXT + "</i>" + AUTH_FINGERPRINT_VERIFY).format(you=self.host.whoami, your_fp=priv_key.fingerprint(), other=self.peer, other_fp=other_key.fingerprint(), eol=DIALOG_EOL)
239 title = AUTH_OTHER_TITLE.format(jid=self.peer.full()) 239 title = AUTH_OTHER_TITLE.format(jid=self.peer)
240 dialog.ConfirmDialog(setTrust, text, title, AddStyleName="maxWidthLimit").show() 240 dialog.ConfirmDialog(setTrust, text, title, AddStyleName="maxWidthLimit").show()
241 241
242 def smpAuthCb(self, type_, data, act=None): 242 def smpAuthCb(self, type_, data, act=None):
243 """OTR v3 authentication using the socialist millionaire protocol. 243 """OTR v3 authentication using the socialist millionaire protocol.
244 244
259 # fingerprints, we will reach this code... that's wrong, this method is for SMP! 259 # fingerprints, we will reach this code... that's wrong, this method is for SMP!
260 # There's probably a bug to fix in otr.js. Do it together with the issue that 260 # There's probably a bug to fix in otr.js. Do it together with the issue that
261 # make us need the dirty self.smpAuthAbort. 261 # make us need the dirty self.smpAuthAbort.
262 else: 262 else:
263 log.error("FIXME: unmanaged ambiguous 'act' value in Context.smpAuthCb!") 263 log.error("FIXME: unmanaged ambiguous 'act' value in Context.smpAuthCb!")
264 title = (AUTH_OTHER_TITLE if act == "asked" else AUTH_US_TITLE).format(jid=self.peer.full()) 264 title = (AUTH_OTHER_TITLE if act == "asked" else AUTH_US_TITLE).format(jid=self.peer)
265 if type_ == 'question': 265 if type_ == 'question':
266 if act == 'asked': 266 if act == 'asked':
267 def cb(question, answer=None): 267 def cb(question, answer=None):
268 if question is False or not answer: # dialog cancelled or the answer is empty 268 if question is False or not answer: # dialog cancelled or the answer is empty
269 return 269 return
295 295
296 296
297 class Account(otr.context.Account): 297 class Account(otr.context.Account):
298 298
299 def __init__(self, host): 299 def __init__(self, host):
300 log.debug(u"new account: %s" % host.whoami.full()) 300 log.debug(u"new account: %s" % host.whoami)
301 if not host.whoami.resource: 301 if not host.whoami.resource:
302 log.warning("Account created without resource") 302 log.warning("Account created without resource")
303 super(Account, self).__init__(host.whoami) 303 super(Account, self).__init__(host.whoami)
304 self.host = host 304 self.host = host
305 305
335 return self.contexts[other_jid] 335 return self.contexts[other_jid]
336 336
337 def getContextForUser(self, other_jid, start=True): 337 def getContextForUser(self, other_jid, start=True):
338 """Get the context for the given JID 338 """Get the context for the given JID
339 339
340 @param other_jid (JID): your correspondent 340 @param other_jid (jid.JID): your correspondent
341 @param start (bool): start non-existing context if True 341 @param start (bool): start non-existing context if True
342 @return: Context 342 @return: Context
343 """ 343 """
344 log.debug(u"getContextForUser [%s]" % other_jid) 344 log.debug(u"getContextForUser [%s]" % other_jid)
345 if not other_jid.resource: 345 if not other_jid.resource:
346 log.error("getContextForUser called with a bare jid") 346 log.error("getContextForUser called with a bare jid")
347 running_sessions = [jid.bareJID() for jid in self.contexts.keys() if self.contexts[jid].state == otr.context.STATE_ENCRYPTED] 347 running_sessions = [jid_.bareJID() for jid_ in self.contexts.keys() if self.contexts[jid_].state == otr.context.STATE_ENCRYPTED]
348 if start or (other_jid in running_sessions): 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")) 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.full(), users_ml=users_ml) 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() 351 dialog.InfoDialog(RESOURCE_ISSUE_TITLE, text, AddStyleName="maxWidthLimit").show()
352 return None # never start an OTR session with a bare JID 352 return None # never start an OTR session with a bare JID
353 if start: 353 if start:
354 return self.startContext(other_jid) 354 return self.startContext(other_jid)
355 else: 355 else:
378 return OTR_MSG_STATES[state][1 if trust else 0] 378 return OTR_MSG_STATES[state][1 if trust else 0]
379 379
380 def infoTextCallback(self, other_jid, cb): 380 def infoTextCallback(self, other_jid, cb):
381 """Get the current info text for a conversation and run a callback. 381 """Get the current info text for a conversation and run a callback.
382 382
383 @param other_jid (JID): JID of the correspondant 383 @param other_jid (jid.JID): JID of the correspondant
384 @paam cb (callable): method to be called with the computed info text 384 @paam cb (callable): method to be called with the computed info text
385 """ 385 """
386 def gotResource(other_jid): 386 def gotResource(other_jid):
387 otrctx = self.context_manager.getContextForUser(other_jid, start=False) 387 otrctx = self.context_manager.getContextForUser(other_jid, start=False)
388 if otrctx is None: 388 if otrctx is None:
412 412
413 def profileDisconnected(self): 413 def profileDisconnected(self):
414 for context in self.context_manager.contexts.values(): 414 for context in self.context_manager.contexts.values():
415 context.disconnect() 415 context.disconnect()
416 416
417 def fixResource(self, jid, cb): 417 def fixResource(self, jid_, cb):
418 # FIXME: it's dirty, but libervia doesn't manage resources correctly now, refactoring is planed 418 # FIXME: it's dirty, but libervia doesn't manage resources correctly now, refactoring is planed
419 if jid.resource: 419 if jid_.resource:
420 self.last_resources[jid.bare] = jid.resource 420 self.last_resources[jid_.bare] = jid_.resource
421 cb(jid) 421 cb(jid_)
422 elif jid.bare in self.last_resources: 422 elif jid_.bare in self.last_resources:
423 jid.setResource(self.last_resources[jid.bare]) 423 # FIXME: to be removed: must use new resource system
424 cb(jid) 424 # jid_.setResource(self.last_resources[jid_.bare])
425 cb(jid_)
425 else: 426 else:
426 def gotResource(resource): 427 pass # FIXME: to be removed: must use new resource system
427 if resource: 428 # def gotResource(resource):
428 jid.setResource(resource) 429 # if resource:
429 self.last_resources[jid.bare] = jid.resource 430 # jid_.setResource(resource)
430 cb(jid) 431 # self.last_resources[jid_.bare] = jid_.resource
431 self.host.bridge.call('getLastResource', gotResource, jid.full()) 432 # cb(jid_)
433 #
434 # self.host.bridge.call('getLastResource', gotResource, jid_)
432 435
433 def messageReceivedTrigger(self, from_jid, msg, msg_type, to_jid, extra): 436 def messageReceivedTrigger(self, from_jid, msg, msg_type, to_jid, extra):
434 if msg_type == C.MESS_TYPE_INFO: 437 if msg_type == C.MESS_TYPE_INFO:
435 return True 438 return True
436 439
439 return True 442 return True
440 443
441 def decrypt(context): 444 def decrypt(context):
442 context.receiveMessage(msg) 445 context.receiveMessage(msg)
443 446
444 def cb(jid): 447 def cb(jid_):
445 otrctx = self.context_manager.getContextForUser(jid, start=False) 448 otrctx = self.context_manager.getContextForUser(jid_, start=False)
446 if otrctx is None: 449 if otrctx is None:
447 def confirm(confirm): 450 def confirm(confirm):
448 if confirm: 451 if confirm:
449 self.host.getOrCreateLiberviaWidget(panels.ChatPanel, {'item': jid}) 452 self.host.getOrCreateLiberviaWidget(panels.ChatPanel, {'item': jid_})
450 decrypt(self.context_manager.startContext(jid)) 453 decrypt(self.context_manager.startContext(jid_))
451 else: 454 else:
452 # FIXME: plain text messages with whitespaces would be lost here when WHITESPACE_START_AKE is True 455 # FIXME: plain text messages with whitespaces would be lost here when WHITESPACE_START_AKE is True
453 pass 456 pass
454 key = self.context_manager.account.privkey 457 key = self.context_manager.account.privkey
455 msg = QUERY_RECEIVED + QUERY_SLOWDOWN + (QUERY_KEY if key else QUERY_NO_KEY) + QUERY_CONFIRM 458 msg = QUERY_RECEIVED + QUERY_SLOWDOWN + (QUERY_KEY if key else QUERY_NO_KEY) + QUERY_CONFIRM
456 dialog.ConfirmDialog(confirm, msg.format(jid=jid.full(), eol=DIALOG_EOL), QUERY_TITLE, AddStyleName="maxWidthLimit").show() 459 dialog.ConfirmDialog(confirm, msg.format(jid=jid_, eol=DIALOG_EOL), QUERY_TITLE, AddStyleName="maxWidthLimit").show()
457 else: # do not ask if the context exist 460 else: # do not ask if the context exist
458 decrypt(otrctx) 461 decrypt(otrctx)
459 462
460 other_jid = to_jid if from_jid.bare == self.host.whoami.bare else from_jid 463 other_jid = to_jid if from_jid.bare == self.host.whoami.bare else from_jid
461 self.fixResource(other_jid, cb) 464 self.fixResource(other_jid, cb)
462 return False # interrupt the main process 465 return False # interrupt the main process
463 466
464 def sendMessageTrigger(self, to_jid, msg, msg_type, extra): 467 def sendMessageTrigger(self, to_jid, msg, msg_type, extra):
465 def cb(jid): 468 def cb(jid_):
466 otrctx = self.context_manager.getContextForUser(jid, start=False) 469 otrctx = self.context_manager.getContextForUser(jid_, start=False)
467 if otrctx is not None and otrctx.state != otr.context.STATE_PLAINTEXT: 470 if otrctx is not None and otrctx.state != otr.context.STATE_PLAINTEXT:
468 if otrctx.state == otr.context.STATE_ENCRYPTED: 471 if otrctx.state == otr.context.STATE_ENCRYPTED:
469 log.debug(u"encrypting message") 472 log.debug(u"encrypting message")
470 otrctx.sendMessage(msg) 473 otrctx.sendMessage(msg)
471 self.host.newMessageCb(self.host.whoami, msg, msg_type, jid, extra) 474 self.host.newMessageCb(self.host.whoami, msg, msg_type, jid_, extra)
472 else: 475 else:
473 feedback = SEND_PLAIN_IN_FINISHED_CONTEXT 476 feedback = SEND_PLAIN_IN_FINISHED_CONTEXT
474 dialog.InfoDialog(FINISHED_CONTEXT_TITLE.format(jid=to_jid.full()), feedback, AddStyleName="maxWidthLimit").show() 477 dialog.InfoDialog(FINISHED_CONTEXT_TITLE.format(jid=to_jid), feedback, AddStyleName="maxWidthLimit").show()
475 else: 478 else:
476 log.debug(u"sending message unencrypted") 479 log.debug(u"sending message unencrypted")
477 self.host.bridge.call('sendMessage', (None, self.host.sendError), to_jid.full(), msg, '', msg_type, extra) 480 self.host.bridge.call('sendMessage', (None, self.host.sendError), to_jid, msg, '', msg_type, extra)
478 481
479 if msg_type == 'groupchat': 482 if msg_type == 'groupchat':
480 return True 483 return True
481 self.fixResource(to_jid, cb) 484 self.fixResource(to_jid, cb)
482 return False # interrupt the main process 485 return False # interrupt the main process
487 return True 490 return True
488 491
489 def endSession(self, other_jid, profile, finish=False): 492 def endSession(self, other_jid, profile, finish=False):
490 """Finish or disconnect an OTR session 493 """Finish or disconnect an OTR session
491 494
492 @param other_jid (JID): str 495 @param other_jid (jid.JID): str
493 @param finish: if True, finish the session but do not disconnect it 496 @param finish: if True, finish the session but do not disconnect it
494 @return: True if the session has been finished or disconnected, False if there was nothing to do 497 @return: True if the session has been finished or disconnected, False if there was nothing to do
495 """ 498 """
496 def cb(other_jid): 499 def cb(other_jid):
497 def not_available(): 500 def not_available():
498 if not finish: 501 if not finish:
499 self.host.newMessageCb(other_jid, END_PLAIN.format(jid=other_jid.full()), C.MESS_TYPE_INFO, self.host.whoami, {}) 502 self.host.newMessageCb(other_jid, END_PLAIN.format(jid=other_jid), C.MESS_TYPE_INFO, self.host.whoami, {})
500 503
501 priv_key = self.context_manager.account.privkey 504 priv_key = self.context_manager.account.privkey
502 if priv_key is None: 505 if priv_key is None:
503 not_available() 506 not_available()
504 return 507 return
524 def query(other_jid): 527 def query(other_jid):
525 otrctx = self.context_manager.getContextForUser(other_jid) 528 otrctx = self.context_manager.getContextForUser(other_jid)
526 if otrctx: 529 if otrctx:
527 otrctx.sendQueryMessage() 530 otrctx.sendQueryMessage()
528 531
529 def cb(jid): 532 def cb(jid_):
530 key = self.context_manager.account.privkey 533 key = self.context_manager.account.privkey
531 if key is None: 534 if key is None:
532 def confirm(confirm): 535 def confirm(confirm):
533 if confirm: 536 if confirm:
534 query(jid) 537 query(jid_)
535 msg = QUERY_SEND + QUERY_SLOWDOWN + QUERY_NO_KEY + QUERY_CONFIRM 538 msg = QUERY_SEND + QUERY_SLOWDOWN + QUERY_NO_KEY + QUERY_CONFIRM
536 dialog.ConfirmDialog(confirm, msg.format(jid=jid.full(), eol=DIALOG_EOL), QUERY_TITLE, AddStyleName="maxWidthLimit").show() 539 dialog.ConfirmDialog(confirm, msg.format(jid=jid_, eol=DIALOG_EOL), QUERY_TITLE, AddStyleName="maxWidthLimit").show()
537 else: # on query reception we ask always, if we initiate we just ask the first time 540 else: # on query reception we ask always, if we initiate we just ask the first time
538 query(jid) 541 query(jid_)
539 542
540 try: 543 try:
541 other_jid = menu_data['jid'] 544 other_jid = menu_data['jid']
542 if other_jid.bare not in self.host.contact_panel.connected: 545 if other_jid.bare not in self.host.contact_panel.connected:
543 dialog.InfoDialog(ACTION_NA_TITLE, ACTION_NA, AddStyleName="maxWidthLimit").show() 546 dialog.InfoDialog(ACTION_NA_TITLE, ACTION_NA, AddStyleName="maxWidthLimit").show()