changeset cea2e7b54062 in /home/hg/repos/gajim-plugins
author: lovetox <forenjunkie(a)chello.at>
branches:
details:gajim-plugins?cmd=changeset;node=cea2e7b54062
description: Add GroupChat
diffstat:
omemo/__init__.py | 126 +++++++++++++++++++++++++++++++++++++--
omemo/omemo/liteaxolotlstore.py | 3 +
omemo/omemo/litesessionstore.py | 10 +++
omemo/omemo/state.py | 102 +++++++++++++++++++++++++++----
omemo/ui.py | 112 ++++++++++++++++++++++++++++++-----
5 files changed, 314 insertions(+), 39 deletions(-)
diffs (truncated from 565 to 300 lines):
diff -r 6b094611322a -r cea2e7b54062 omemo/__init__.py
--- a/omemo/__init__.py Mon Sep 19 20:08:04 2016 +0200
+++ b/omemo/__init__.py Sun Oct 09 02:20:56 2016 +0200
@@ -22,6 +22,7 @@
import os
import sqlite3
import shutil
+import message_control
from common import caps_cache, gajim, ged, configpaths
from common.pep import SUPPORTED_PERSONAL_USER_EVENTS
@@ -101,6 +102,7 @@
omemo_states = {}
ui_list = {}
+ groupchat = {}
@log_calls('OmemoPlugin')
def init(self):
@@ -119,15 +121,24 @@
(ged.PRECORE, self.handle_outgoing_stanza),
'message-outgoing':
(ged.PRECORE, self.handle_outgoing_event),
+ 'gc-stanza-message-outgoing':
+ (ged.PRECORE, self.handle_outgoing_gc_stanza),
+ 'gc-presence-received':
+ (ged.PRECORE, self.gc_presence_received),
+ 'gc-config-changed-received':
+ (ged.PRECORE, self.gc_config_changed_received),
}
self.config_dialog = ui.OMEMOConfigDialog(self)
self.gui_extension_points = {'chat_control': (self.connect_ui,
- self.disconnect_ui)}
+ self.disconnect_ui),
+ 'groupchat_control': (self.connect_ui,
+ self.disconnect_ui)}
SUPPORTED_PERSONAL_USER_EVENTS.append(DevicelistPEP)
self.plugin = self
self.announced = []
self.query_for_bundles = []
self.disabled_accounts = []
+ self.gc_message = {}
self.config_default_values = {'DISABLED_ACCOUNTS': ([], ''), }
@@ -335,8 +346,7 @@
if msg.stanza.getTag('openpgp', namespace=NS_PGP):
return
- if msg.stanza.getTag('encrypted', namespace=NS_OMEMO) and \
- msg.mtype == 'chat':
+ if msg.stanza.getTag('encrypted', namespace=NS_OMEMO):
log.debug(account + ' => OMEMO msg received')
state = self.get_omemo_state(account)
@@ -345,14 +355,29 @@
log.debug('message was forwarded doing magic')
else:
from_jid = str(msg.stanza.getFrom())
+
+ if msg.mtype == 'groupchat':
+ from_jid = self.groupchat[msg.jid][msg.resource]
+
self.print_msg_to_log(msg.stanza)
msg_dict = unpack_encrypted(msg.stanza.getTag
('encrypted', namespace=NS_OMEMO))
- msg_dict['sender_jid'] = gajim.get_jid_without_resource(from_jid)
- plaintext = state.decrypt_msg(msg_dict)
+
+ plaintext = ''
+ if msg_dict['sid'] == state.own_device_id:
+ if msg_dict['payload'] in self.gc_message:
+ plaintext = self.gc_message[msg_dict['payload']]
+ del self.gc_message[msg_dict['payload']]
+ else:
+ log.error(account + ' => Cant decrypt GroupChat Message '
+ 'from ' + from_jid)
+ else:
+ msg_dict['sender_jid'] = gajim. \
+ get_jid_without_resource(from_jid)
+ plaintext = state.decrypt_msg(msg_dict)
if not plaintext:
- return
+ return True
msg.msgtxt = plaintext
# Gajim bug: there must be a body or the message
@@ -365,7 +390,7 @@
self.ui_list[account][contact_jid].activate_omemo()
return False
- elif msg.stanza.getTag('body') and msg.mtype == 'chat':
+ elif msg.stanza.getTag('body'):
account = msg.conn.name
from_jid = str(msg.stanza.getFrom())
@@ -386,6 +411,86 @@
', Ui Warning not shown')
@log_calls('OmemoPlugin')
+ def gc_presence_received(self, event):
+ if not event.real_jid:
+ return
+ room = event.room_jid
+ account = event.conn.name
+ jid = gajim.get_jid_without_resource(event.real_jid)
+ nick = event.nick
+
+ if room not in self.groupchat:
+ self.groupchat[room] = {}
+
+ if nick not in self.groupchat[room]:
+ self.groupchat[room][nick] = jid
+
+ log.debug('PRESENCE RECEIVED')
+ log.debug(self.groupchat[room])
+
+ if '100' in event.status_code:
+ self.ui_list[account][room].sensitive(True)
+
+ @log_calls('OmemoPlugin')
+ def gc_config_changed_received(self, event):
+ log.debug('CONFIG CHANGE')
+ log.debug(event.room_jid)
+ log.debug(event.status_code)
+
+ def handle_outgoing_gc_stanza(self, event):
+ """ Manipulates the outgoing groupchat stanza
+
+ The body is getting encrypted
+
+ Parameters
+ ----------
+ event : StanzaMessageOutgoingEvent
+
+ Returns
+ -------
+ Return if encryption is not activated or any other
+ exception or error occurs
+ """
+ try:
+ if not event.msg_iq.getTag('body'):
+ return
+ account = event.conn.name
+ state = self.get_omemo_state(account)
+ full_jid = str(event.msg_iq.getAttr('to'))
+ to_jid = gajim.get_jid_without_resource(full_jid)
+ if not state.encryption.is_active(to_jid):
+ return
+ # Delete previous Message out of Correction Message Stanza
+ if event.msg_iq.getTag('replace', namespace=NS_CORRECT):
+ event.msg_iq.delChild('encrypted', attrs={'xmlns': NS_OMEMO})
+
+ plaintext = event.msg_iq.getBody().encode('utf8')
+ msg_dict = state.create_gc_msg(
+ gajim.get_jid_from_account(account), to_jid, plaintext)
+ if not msg_dict:
+ return True
+
+ self.gc_message[msg_dict['payload']] = plaintext
+ encrypted_node = OmemoMessage(msg_dict)
+ event.msg_iq.delChild('body')
+ event.msg_iq.addChild(node=encrypted_node)
+
+ # XEP-xxxx: Explicit Message Encryption
+ if not event.msg_iq.getTag('encrypted', attrs={'xmlns': NS_EME}):
+ eme_node = Node('encrypted', attrs={'xmlns': NS_EME,
+ 'name': 'OMEMO',
+ 'namespace': NS_OMEMO})
+ event.msg_iq.addChild(node=eme_node)
+
+ # Store Hint for MAM
+ store = Node('store', attrs={'xmlns': NS_HINTS})
+ event.msg_iq.addChild(node=store)
+ self.print_msg_to_log(event.msg_iq)
+ except Exception as e:
+ log.debug(e)
+ return True
+
+ @log_calls('OmemoPlugin')
def handle_outgoing_event(self, event):
""" Handles a message outgoing event
@@ -625,6 +730,11 @@
else:
log.warning(account + " => No devices for " + contact_jid)
+ if chat_control.type_id == message_control.TYPE_GC:
+ self.ui_list[account][contact_jid] = Ui(self, chat_control,
+ omemo_enabled, state)
+ self.ui_list[account][contact_jid].sensitive(False)
+
@log_calls('OmemoPlugin')
def disconnect_ui(self, chat_control):
""" Calls the removeUi method to remove all relatad UI objects.
@@ -663,7 +773,7 @@
if my_jid not in self.query_for_bundles:
devices_without_session = state \
- .devices_without_sessions(my_jid)
+ .devices_without_sessions(my_jid)
self.query_for_bundles.append(my_jid)
diff -r 6b094611322a -r cea2e7b54062 omemo/omemo/liteaxolotlstore.py
--- a/omemo/omemo/liteaxolotlstore.py Mon Sep 19 20:08:04 2016 +0200
+++ b/omemo/omemo/liteaxolotlstore.py Sun Oct 09 02:20:56 2016 +0200
@@ -148,6 +148,9 @@
def getSessionsFromJid(self, recipientId):
return self.sessionStore.getSessionsFromJid(recipientId)
+ def getSessionsFromJids(self, recipientId):
+ return self.sessionStore.getSessionsFromJids(recipientId)
+
def getAllSessions(self):
return self.sessionStore.getAllSessions()
diff -r 6b094611322a -r cea2e7b54062 omemo/omemo/litesessionstore.py
--- a/omemo/omemo/litesessionstore.py Mon Sep 19 20:08:04 2016 +0200
+++ b/omemo/omemo/litesessionstore.py Sun Oct 09 02:20:56 2016 +0200
@@ -99,6 +99,16 @@
result.append((row[0], row[1], row[2], row[3], row[4]))
return result
+ def getSessionsFromJids(self, recipientId):
+ q = "SELECT _id, recipient_id, device_id, record, active from sessions" \
+ " WHERE recipient_id IN ({})" \
+ .format(', '.join(['?'] * len(recipientId)))
+ c = self.dbConn.cursor()
+ result = []
+ for row in c.execute(q, recipientId):
+ result.append((row[0], row[1], row[2], row[3], row[4]))
+ return result
+
def setActiveState(self, deviceList, jid):
c = self.dbConn.cursor()
diff -r 6b094611322a -r cea2e7b54062 omemo/omemo/state.py
--- a/omemo/omemo/state.py Mon Sep 19 20:08:04 2016 +0200
+++ b/omemo/omemo/state.py Sun Oct 09 02:20:56 2016 +0200
@@ -266,6 +266,94 @@
log.debug('Finished encrypting message')
return result
+ def create_gc_msg(self, from_jid, jid, plaintext):
+ key = get_random_bytes(16)
+ iv = get_random_bytes(16)
+ encrypted_keys = {}
+ room = jid
+
+ devices_list = self.device_list_for(jid, True)
+
+ if len(devices_list) == 0:
+ log.error('No known devices')
+ return
+
+ for tup in devices_list:
+ self.get_session_cipher(tup[0], tup[1])
+
+ # Encrypt the message key with for each of receivers devices
+ for nick in self.plugin.groupchat[room]:
+ jid_to = self.plugin.groupchat[room][nick]
+ if jid_to == self.own_jid:
+ continue
+ for rid, cipher in self.session_ciphers[jid_to].items():
+ try:
+ if self.isTrusted(jid_to, rid) == TRUSTED:
+ encrypted_keys[rid] = cipher.encrypt(key). \
+ serialize()
+ else:
+ log.debug('Skipped Device because Trust is: ' +
+ str(self.isTrusted(jid_to, rid)))
+ except:
+ log.exception('ERROR:')
+ log.warning('Failed to find key for device ' +
+ str(rid))
+
+ if len(encrypted_keys) == 0:
+ log_msg = 'Encrypted keys empty'
+ log.error(log_msg)
+ raise NoValidSessions(log_msg)
+
+ my_other_devices = set(self.own_devices) - set({self.own_device_id})
+ # Encrypt the message key with for each of our own devices
+ for dev in my_other_devices:
+ try:
+ cipher = self.get_session_cipher(from_jid, dev)
+ if self.isTrusted(jid_to, dev) == TRUSTED:
+ encrypted_keys[dev] = cipher.encrypt(key).serialize()
+ else:
+ log.debug('Skipped own Device because Trust is: ' +
+ str(self.isTrusted(jid_to, dev)))
+ except:
+ log.warning('Failed to find key for device ' + str(dev))
+
+ payload = encrypt(key, iv, plaintext)
+
+ result = {'sid': self.own_device_id,
+ 'keys': encrypted_keys,