Mercurial > libervia-backend
comparison src/plugins/plugin_sec_otr.py @ 1955:633b5c21aefd
backend, frontend: messages refactoring (huge commit, not finished):
/!\ database schema has been modified, do a backup before updating
message have been refactored, here are the main changes:
- languages are now handled
- all messages have an uid (internal to SàT)
- message updating is anticipated
- subject is now first class
- new naming scheme is used newMessage => messageNew, getHistory => historyGet, sendMessage => messageSend
- minimal compatibility refactoring in quick_frontend/Primitivus, better refactoring should follow
- threads handling
- delayed messages are saved into history
- info messages may also be saved in history (e.g. to keep track of people joining/leaving a room)
- duplicate messages should be avoided
- historyGet return messages in right order, no need to sort again
- plugins have been updated to follow new features, some of them need to be reworked (e.g. OTR)
- XEP-0203 (Delayed Delivery) is now fully handled in core, the plugin just handle disco and creation of a delay element
- /!\ jp and Libervia are currently broken, as some features of Primitivus
It has been put in one huge commit to avoid breaking messaging between changes.
This is the main part of message refactoring, other commits will follow to take profit of the new features/behaviour.
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 24 May 2016 22:11:04 +0200 |
parents | 2daf7b4c6756 |
children | a2bc5089c2eb |
comparison
equal
deleted
inserted
replaced
1943:ccfe45302a5c | 1955:633b5c21aefd |
---|---|
30 from twisted.python import failure | 30 from twisted.python import failure |
31 from twisted.internet import defer | 31 from twisted.internet import defer |
32 from sat.memory import persistent | 32 from sat.memory import persistent |
33 import potr | 33 import potr |
34 import copy | 34 import copy |
35 import time | |
36 import uuid | |
35 | 37 |
36 NS_OTR = "otr_plugin" | 38 NS_OTR = "otr_plugin" |
37 PRIVATE_KEY = "PRIVATE KEY" | 39 PRIVATE_KEY = "PRIVATE KEY" |
38 MAIN_MENU = D_('OTR') | 40 MAIN_MENU = D_('OTR') |
39 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!") | 41 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!") |
71 def inject(self, msg_str, appdata=None): | 73 def inject(self, msg_str, appdata=None): |
72 assert isinstance(self.peer, jid.JID) | 74 assert isinstance(self.peer, jid.JID) |
73 msg = msg_str.decode('utf-8') | 75 msg = msg_str.decode('utf-8') |
74 client = self.user.client | 76 client = self.user.client |
75 log.debug(u'inject(%s, appdata=%s, to=%s)' % (msg, appdata, self.peer)) | 77 log.debug(u'inject(%s, appdata=%s, to=%s)' % (msg, appdata, self.peer)) |
76 mess_data = {'message': msg, | 78 mess_data = { |
79 'from': client.jid, | |
80 'to': self.peer, | |
81 'uid': unicode(uuid.uuid4()), | |
82 'message': {'': msg}, | |
83 'subject': {}, | |
77 'type': 'chat', | 84 'type': 'chat', |
78 'from': client.jid, | 85 'extra': {}, |
79 'to': self.peer, | 86 'timestamp': time.time(), |
80 'subject': None, | |
81 } | 87 } |
82 self.host.generateMessageXML(mess_data) | 88 self.host.generateMessageXML(mess_data) |
83 client.xmlstream.send(mess_data['xml']) | 89 client.xmlstream.send(mess_data['xml']) |
84 | 90 |
85 def setState(self, state): | 91 def setState(self, state): |
105 else: | 111 else: |
106 log.error(_(u"Unknown OTR state")) | 112 log.error(_(u"Unknown OTR state")) |
107 return | 113 return |
108 | 114 |
109 client = self.user.client | 115 client = self.user.client |
110 self.host.bridge.newMessage(client.jid.full(), | 116 self.host.bridge.messageNew(client.jid.full(), |
111 feedback, | 117 feedback, |
112 mess_type=C.MESS_TYPE_INFO, | 118 mess_type=C.MESS_TYPE_INFO, |
113 to_jid=self.peer.full(), | 119 to_jid=self.peer.full(), |
114 extra={}, | 120 extra={}, |
115 profile=client.profile) | 121 profile=client.profile) |
200 self._fixPotr() # FIXME: to be removed when potr will be fixed | 206 self._fixPotr() # FIXME: to be removed when potr will be fixed |
201 self.host = host | 207 self.host = host |
202 self.context_managers = {} | 208 self.context_managers = {} |
203 self.skipped_profiles = set() | 209 self.skipped_profiles = set() |
204 host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=100000) | 210 host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=100000) |
205 host.trigger.add("sendMessage", self.sendMessageTrigger, priority=100000) | 211 host.trigger.add("messageSend", self.messageSendTrigger, priority=100000) |
206 host.bridge.addMethod("skipOTR", ".plugin", in_sign='s', out_sign='', method=self._skipOTR) | 212 host.bridge.addMethod("skipOTR", ".plugin", in_sign='s', out_sign='', method=self._skipOTR) |
207 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) | 213 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) |
208 host.importMenu((MAIN_MENU, D_("End session")), self._endSession, security_limit=0, help_string=D_("Finish an OTR session"), type_=C.MENU_SINGLE) | 214 host.importMenu((MAIN_MENU, D_("End session")), self._endSession, security_limit=0, help_string=D_("Finish an OTR session"), type_=C.MENU_SINGLE) |
209 host.importMenu((MAIN_MENU, D_("Authenticate")), self._authenticate, security_limit=0, help_string=D_("Authenticate user/see your fingerprint"), type_=C.MENU_SINGLE) | 215 host.importMenu((MAIN_MENU, D_("Authenticate")), self._authenticate, security_limit=0, help_string=D_("Authenticate user/see your fingerprint"), type_=C.MENU_SINGLE) |
210 host.importMenu((MAIN_MENU, D_("Drop private key")), self._dropPrivKey, security_limit=0, type_=C.MENU_SINGLE) | 216 host.importMenu((MAIN_MENU, D_("Drop private key")), self._dropPrivKey, security_limit=0, type_=C.MENU_SINGLE) |
273 to_jid.resource = self.host.memory.getMainResource(to_jid, profile) # FIXME: temporary and unsecure, must be changed when frontends are refactored | 279 to_jid.resource = self.host.memory.getMainResource(to_jid, profile) # FIXME: temporary and unsecure, must be changed when frontends are refactored |
274 except KeyError: | 280 except KeyError: |
275 log.error(_("jid key is not present !")) | 281 log.error(_("jid key is not present !")) |
276 return defer.fail(exceptions.DataError) | 282 return defer.fail(exceptions.DataError) |
277 otrctx = self.context_managers[profile].getContextForUser(to_jid) | 283 otrctx = self.context_managers[profile].getContextForUser(to_jid) |
278 query = otrctx.sendMessage(0, '?OTRv?') | 284 query = otrctx.messageSend(0, '?OTRv?') |
279 otrctx.inject(query) | 285 otrctx.inject(query) |
280 return {} | 286 return {} |
281 | 287 |
282 def _endSession(self, menu_data, profile): | 288 def _endSession(self, menu_data, profile): |
283 """End an OTR session | 289 """End an OTR session |
404 log.debug(u"_receivedTreatment [from_jid = %s]" % from_jid) | 410 log.debug(u"_receivedTreatment [from_jid = %s]" % from_jid) |
405 otrctx = self.context_managers[profile].getContextForUser(from_jid) | 411 otrctx = self.context_managers[profile].getContextForUser(from_jid) |
406 encrypted = True | 412 encrypted = True |
407 | 413 |
408 try: | 414 try: |
409 res = otrctx.receiveMessage(data['body'].encode('utf-8')) | 415 message = data['message'].itervalues().next() # FIXME: Q&D fix for message refactoring, message is now a dict |
416 res = otrctx.receiveMessage(message.encode('utf-8')) | |
410 except potr.context.UnencryptedMessage: | 417 except potr.context.UnencryptedMessage: |
411 if otrctx.state == potr.context.STATE_ENCRYPTED: | 418 if otrctx.state == potr.context.STATE_ENCRYPTED: |
412 log.warning(u"Received unencrypted message in an encrypted context (from %(jid)s)" % {'jid': from_jid.full()}) | 419 log.warning(u"Received unencrypted message in an encrypted context (from %(jid)s)" % {'jid': from_jid.full()}) |
413 client = self.host.getClient(profile) | 420 client = self.host.getClient(profile) |
414 self.host.bridge.newMessage(from_jid.full(), | 421 self.host.bridge.messageNew(from_jid.full(), |
415 _(u"WARNING: received unencrypted data in a supposedly encrypted context"), | 422 _(u"WARNING: received unencrypted data in a supposedly encrypted context"), |
416 mess_type=C.MESS_TYPE_INFO, | 423 mess_type=C.MESS_TYPE_INFO, |
417 to_jid=client.jid.full(), | 424 to_jid=client.jid.full(), |
418 extra={}, | 425 extra={}, |
419 profile=client.profile) | 426 profile=client.profile) |
420 encrypted = False | 427 encrypted = False |
428 except StopIteration: | |
429 return data | |
421 | 430 |
422 if not encrypted: | 431 if not encrypted: |
423 return data | 432 return data |
424 else: | 433 else: |
425 if res[0] != None: | 434 if res[0] != None: |
426 # decrypted messages handling. | 435 # decrypted messages handling. |
427 # receiveMessage() will return a tuple, the first part of which will be the decrypted message | 436 # receiveMessage() will return a tuple, the first part of which will be the decrypted message |
428 data['body'] = res[0].decode('utf-8') | 437 data['message'] = {'':res[0].decode('utf-8')} # FIXME: Q&D fix for message refactoring, message is now a dict |
429 raise failure.Failure(exceptions.SkipHistory()) # we send the decrypted message to frontends, but we don't want it in history | 438 raise failure.Failure(exceptions.SkipHistory()) # we send the decrypted message to frontends, but we don't want it in history |
430 else: | 439 else: |
431 raise failure.Failure(exceptions.CancelError()) # no message at all (no history, no signal) | 440 raise failure.Failure(exceptions.CancelError()) # no message at all (no history, no signal) |
432 | 441 |
433 def _receivedTreatmentForSkippedProfiles(self, data, profile): | 442 def _receivedTreatmentForSkippedProfiles(self, data, profile): |
434 """This profile must be skipped because the frontend manages OTR itself, | 443 """This profile must be skipped because the frontend manages OTR itself, |
435 but we still need to check if the message must be stored in history or not""" | 444 but we still need to check if the message must be stored in history or not""" |
436 body = data['body'].encode('utf-8') | 445 try: |
437 if body.startswith(potr.proto.OTRTAG): | 446 message = data['message'].itervalues().next().encode('utf-8') # FIXME: Q&D fix for message refactoring, message is now a dict |
447 except StopIteration: | |
448 return data | |
449 if message.startswith(potr.proto.OTRTAG): | |
438 raise failure.Failure(exceptions.SkipHistory()) | 450 raise failure.Failure(exceptions.SkipHistory()) |
439 return data | 451 return data |
440 | 452 |
441 def MessageReceivedTrigger(self, message, post_treat, profile): | 453 def MessageReceivedTrigger(self, client, message, post_treat): |
454 profile = client.profile | |
442 if profile in self.skipped_profiles: | 455 if profile in self.skipped_profiles: |
443 post_treat.addCallback(self._receivedTreatmentForSkippedProfiles, profile) | 456 post_treat.addCallback(self._receivedTreatmentForSkippedProfiles, profile) |
444 else: | 457 else: |
445 post_treat.addCallback(self._receivedTreatment, profile) | 458 post_treat.addCallback(self._receivedTreatment, profile) |
446 return True | 459 return True |
447 | 460 |
448 def sendMessageTrigger(self, mess_data, pre_xml_treatments, post_xml_treatments, profile): | 461 def messageSendTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments): |
462 profile = client.profile | |
449 if profile in self.skipped_profiles: | 463 if profile in self.skipped_profiles: |
450 return True | 464 return True |
451 to_jid = copy.copy(mess_data['to']) | 465 to_jid = copy.copy(mess_data['to']) |
452 if mess_data['type'] != 'groupchat' and not to_jid.resource: | 466 if mess_data['type'] != 'groupchat' and not to_jid.resource: |
453 to_jid.resource = self.host.memory.getMainResource(to_jid, profile) # FIXME: it's dirty, but frontends don't manage resources correctly now, refactoring is planed | 467 to_jid.resource = self.host.memory.getMainResource(to_jid, profile) # FIXME: it's dirty, but frontends don't manage resources correctly now, refactoring is planed |
454 otrctx = self.context_managers[profile].getContextForUser(to_jid) | 468 otrctx = self.context_managers[profile].getContextForUser(to_jid) |
455 if mess_data['type'] != 'groupchat' and otrctx.state != potr.context.STATE_PLAINTEXT: | 469 if mess_data['type'] != 'groupchat' and otrctx.state != potr.context.STATE_PLAINTEXT: |
456 if otrctx.state == potr.context.STATE_ENCRYPTED: | 470 if otrctx.state == potr.context.STATE_ENCRYPTED: |
457 log.debug(u"encrypting message") | 471 log.debug(u"encrypting message") |
458 otrctx.sendMessage(0, mess_data['message'].encode('utf-8')) | 472 try: |
459 client = self.host.getClient(profile) | 473 msg = mess_data['message'][''] |
474 except KeyError: | |
475 try: | |
476 msg = mess_data['message'].itervalues().next() | |
477 except StopIteration: | |
478 log.warning(u"No message found") | |
479 return False | |
480 otrctx.sendMessage(0, msg.encode('utf-8')) | |
460 self.host.sendMessageToBridge(mess_data, client) | 481 self.host.sendMessageToBridge(mess_data, client) |
461 else: | 482 else: |
462 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.") | 483 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.") |
463 client = self.host.getClient(profile) | 484 self.host.bridge.messageNew(to_jid.full(), |
464 self.host.bridge.newMessage(to_jid.full(), | |
465 feedback, | 485 feedback, |
466 mess_type=C.MESS_TYPE_INFO, | 486 mess_type=C.MESS_TYPE_INFO, |
467 to_jid=client.jid.full(), | 487 to_jid=client.jid.full(), |
468 extra={}, | 488 extra={}, |
469 profile=client.profile) | 489 profile=client.profile) |