[omemo] Refactor Plugin

This commit is contained in:
Philipp Hörist
2019-02-17 16:58:34 +01:00
parent 87ece2397e
commit 6fd32591fc
8 changed files with 579 additions and 699 deletions

View File

@@ -22,9 +22,11 @@ import logging
import nbxmpp
from nbxmpp.protocol import NodeProcessed
from nbxmpp.protocol import JID
from nbxmpp.util import is_error_result
from nbxmpp.const import StatusCode
from nbxmpp.const import PresenceType
from nbxmpp.const import Affiliation
from nbxmpp.structs import StanzaHandler
from nbxmpp.modules.omemo import create_omemo_message
@@ -42,6 +44,7 @@ from omemo.backend.state import SelfMessage
from omemo.backend.state import MessageNotForDevice
from omemo.backend.state import DecryptionFailed
from omemo.backend.state import DuplicateMessage
from omemo.backend.state import SenderNotTrusted
from omemo.modules.util import prepare_stanza
@@ -97,29 +100,35 @@ class OMEMO(BaseModule):
self._register_pubsub_handler(self._devicelist_notification_received)
self.available = True
# self.plugin = plugin
self.own_jid = self._con.get_own_jid().getStripped()
self.omemo = self.__get_omemo()
self.groupchat = {}
self.temp_groupchat = {}
self.gc_message = {}
self.query_for_bundles = []
self.query_for_devicelists = []
self._own_jid = self._con.get_own_jid().getStripped()
self._backend = self._get_backend()
self._omemo_groupchats = set()
self._muc_temp_store = {}
self._query_for_bundles = []
self._query_for_devicelists = []
def get_own_jid(self, stripped=False):
if stripped:
return self._con.get_own_jid().getStripped()
return self._con.get_own_jid()
def __get_omemo(self):
@property
def backend(self):
return self._backend
def _get_backend(self):
data_dir = configpaths.get('MY_DATA')
db_path = os.path.join(data_dir, 'omemo_' + self.own_jid + '.db')
return OmemoState(self.own_jid, db_path, self._account, self)
db_path = os.path.join(data_dir, 'omemo_' + self._own_jid + '.db')
return OmemoState(self._own_jid, db_path, self._account, self)
def is_omemo_groupchat(self, room_jid):
return room_jid in self._omemo_groupchats
def on_signed_in(self):
log.info('%s => Announce Support after Sign In', self._account)
self.query_for_bundles = []
self._query_for_bundles = []
self.set_bundle()
self.request_devicelist()
@@ -133,14 +142,14 @@ class OMEMO(BaseModule):
if app.account_is_connected(self._account):
log.info('%s => Announce Support after Plugin Activation',
self._account)
self.query_for_bundles = []
self._query_for_bundles = []
self.set_bundle()
self.request_devicelist()
def deactivate(self):
""" Method called when the Plugin is deactivated in the PluginManager
"""
self.query_for_bundles = []
self._query_for_bundles = []
@staticmethod
def update_caps(account):
@@ -148,6 +157,38 @@ class OMEMO(BaseModule):
if node not in app.gajim_optional_features[account]:
app.gajim_optional_features[account].append(node)
def encrypt_message(self, conn, event, callback, groupchat):
if not event.message:
callback(event)
return
to_jid = app.get_jid_without_resource(event.jid)
omemo_message = self.backend.encrypt(to_jid, event.message)
if omemo_message is None:
app.nec.push_incoming_event(
NetworkEvent('message-not-sent',
conn=conn,
jid=event.jid,
message=event.message,
error=_('Encryption error'),
time_=time.time(),
session=event.session))
return
create_omemo_message(event.msg_iq, omemo_message,
node_whitelist=ALLOWED_TAGS)
if groupchat:
self._muc_temp_store[omemo_message.payload] = event.message
else:
event.xhtml = None
event.encrypted = ENCRYPTION_NAME
event.additional_data['encrypted'] = {'name': ENCRYPTION_NAME}
self._debug_print_stanza(event.msg_iq)
callback(event)
def _message_received(self, _con, stanza, properties):
if not properties.is_omemo:
return
@@ -165,28 +206,31 @@ class OMEMO(BaseModule):
log.info('%s => Message received from: %s', self._account, from_jid)
try:
return self.omemo.decrypt_message(properties.omemo,
from_jid)
except (KeyExchangeMessage, DuplicateMessage):
plaintext, fingerprint = self.backend.decrypt_message(
properties.omemo, from_jid)
except (KeyExchangeMessage, DuplicateMessage, SenderNotTrusted):
raise NodeProcessed
except SelfMessage:
if properties.from_muc:
if properties.omemo.payload in self.gc_message:
plaintext = self.gc_message[properties.omemo.payload]
del self.gc_message[properties.omemo.payload]
return plaintext
log.warning("%s => Can't decrypt own GroupChat Message",
self._account)
raise NodeProcessed
if properties.omemo.payload in self._muc_temp_store:
plaintext = self._muc_temp_store[properties.omemo.payload]
fingerprint = self.backend.own_fingerprint
del self._muc_temp_store[properties.omemo.payload]
else:
log.warning("%s => Can't decrypt own GroupChat Message",
self._account)
return
else:
raise NodeProcessed
except (DecryptionFailed, MessageNotForDevice):
return
prepare_stanza(stanza, plaintext)
self._debug_print_stanza(stanza)
properties.encrypted = EncryptionData({'name': ENCRYPTION_NAME})
properties.encrypted = EncryptionData({'name': ENCRYPTION_NAME,
'fingerprint': fingerprint})
def _process_muc_message(self, properties):
room_jid = properties.jid.getBare()
@@ -195,18 +239,18 @@ class OMEMO(BaseModule):
# History Message from MUC
return properties.muc_ofrom.getBare()
try:
return self.groupchat[room_jid][resource]
except KeyError:
log.info('%s => Groupchat: Last resort trying to '
'find SID in DB', self._account)
from_jid = self.omemo.store.getJidFromDevice(properties.omemo.sid)
if not from_jid:
log.error("%s => Can't decrypt GroupChat Message "
"from %s", self._account, resource)
return
self.groupchat[room_jid][resource] = from_jid
return from_jid
contact = app.contacts.get_gc_contact(self._account, room_jid, resource)
if contact is not None:
return JID(contact.jid).getBare()
log.info('%s => Groupchat: Last resort trying to '
'find SID in DB', self._account)
from_jid = self.backend.storage.getJidFromDevice(properties.omemo.sid)
if not from_jid:
log.error("%s => Can't decrypt GroupChat Message "
"from %s", self._account, resource)
return
return from_jid
def _process_mam_message(self, properties):
log.info('%s => Message received, archive: %s',
@@ -228,7 +272,6 @@ class OMEMO(BaseModule):
return
room = properties.jid.getBare()
nick = properties.muc_nickname
status_codes = properties.muc_status_codes or []
jid = properties.muc_user.jid
@@ -237,41 +280,13 @@ class OMEMO(BaseModule):
return
jid = jid.getBare()
if properties.is_nickname_changed:
new_nick = properties.muc_user.nick
if room in self.groupchat:
if nick in self.groupchat[room]:
del self.groupchat[room][nick]
self.groupchat[room][new_nick] = jid
log.debug('Nick Change: old: %s, new: %s, jid: %s ',
nick, new_nick, jid)
log.debug('Members after Change: %s', self.groupchat[room])
else:
if nick in self.temp_groupchat[room]:
del self.temp_groupchat[room][nick]
self.temp_groupchat[room][new_nick] = jid
return
if room not in self.groupchat:
if room not in self.temp_groupchat:
self.temp_groupchat[room] = {}
if nick not in self.temp_groupchat[room]:
self.temp_groupchat[room][nick] = jid
if properties.muc_user.affiliation in (Affiliation.OUTCAST,
Affiliation.NONE):
self.backend.remove_muc_member(room, jid)
else:
# Check if we received JID over Memberlist
if jid in self.groupchat[room]:
del self.groupchat[room][jid]
# Add JID with Nick
if nick not in self.groupchat[room]:
self.groupchat[room][nick] = jid
log.debug('JID Added: %s', jid)
self.backend.add_muc_member(room, jid)
if room in self._omemo_groupchats:
if not self.is_contact_in_roster(jid):
# Query Devicelists from JIDs not in our Roster
log.info('%s not in Roster, query devicelist...', jid)
@@ -280,8 +295,7 @@ class OMEMO(BaseModule):
if properties.is_muc_self_presence:
if StatusCode.NON_ANONYMOUS in status_codes:
# non-anonymous Room (Full JID)
if room not in self.groupchat:
self.groupchat[room] = self.temp_groupchat[room]
self._omemo_groupchats.add(room)
log.info('OMEMO capable Room found: %s', room)
self.get_affiliation_list(room)
@@ -299,16 +313,6 @@ class OMEMO(BaseModule):
log.info('Affiliation request failed: %s', result)
return
log.info('Room %s Memberlist received', room_jid)
if room_jid not in self.groupchat:
self.groupchat[room_jid] = {}
def jid_known(jid):
for nick in self.groupchat[room_jid]:
if self.groupchat[room_jid][nick] == jid:
return True
return False
for user_jid in result.users:
try:
jid = helpers.parse_jid(user_jid)
@@ -316,10 +320,7 @@ class OMEMO(BaseModule):
log.warning('Invalid JID: %s, ignoring it', user_jid)
continue
if not jid_known(jid):
# Add JID with JID because we have no Nick yet
self.groupchat[room_jid][jid] = jid
log.info('JID Added: %s', jid)
self.backend.add_muc_member(room_jid, jid)
if not self.is_contact_in_roster(jid):
# Query Devicelists from JIDs not in our Roster
@@ -327,7 +328,7 @@ class OMEMO(BaseModule):
self.request_devicelist(jid)
def is_contact_in_roster(self, jid):
if jid == self.own_jid:
if jid == self._own_jid:
return True
contact = app.contacts.get_first_contact_from_jid(self._account, jid)
if contact is None:
@@ -338,73 +339,9 @@ class OMEMO(BaseModule):
room = event.jid.getBare()
status_codes = event.status_codes or []
if StatusCode.CONFIG_NON_ANONYMOUS in status_codes:
if room not in self.groupchat:
self.groupchat[room] = self.temp_groupchat[room]
self._omemo_groupchats.add(room)
log.info('Room config change: non-anonymous')
def gc_encrypt_message(self, conn, event, callback):
if event.conn.name != self._account:
return
if not event.message:
callback(event)
return
to_jid = app.get_jid_without_resource(event.jid)
try:
omemo_message = self.omemo.create_gc_msg(
self.own_jid, to_jid, event.message)
if omemo_message is None:
raise OMEMOError('Error while encrypting')
except OMEMOError as error:
log.error(error)
app.nec.push_incoming_event(
NetworkEvent(
'message-not-sent', conn=conn, jid=event.jid, message=event.message,
error=error, time_=time.time(), session=None))
return
self.gc_message[omemo_message.payload] = event.message
create_omemo_message(event.msg_iq, omemo_message,
node_whitelist=ALLOWED_TAGS)
self._debug_print_stanza(event.msg_iq)
callback(event)
def encrypt_message(self, conn, event, callback):
if event.conn.name != self._account:
return
if not event.message:
callback(event)
return
to_jid = app.get_jid_without_resource(event.jid)
try:
omemo_message = self.omemo.create_msg(to_jid, event.message)
if omemo_message is None:
raise OMEMOError('Error while encrypting')
except OMEMOError as error:
log.error(error)
app.nec.push_incoming_event(
NetworkEvent(
'message-not-sent', conn=conn, jid=event.jid, message=event.message,
error=error, time_=time.time(), session=event.session))
return
create_omemo_message(event.msg_iq, omemo_message,
node_whitelist=ALLOWED_TAGS)
self._debug_print_stanza(event.msg_iq)
event.xhtml = None
event.encrypted = ENCRYPTION_NAME
event.additional_data['encrypted'] = {'name': ENCRYPTION_NAME}
callback(event)
def are_keys_missing(self, contact_jid):
""" Checks if devicekeys are missing and queries the
bundles
@@ -421,36 +358,36 @@ class OMEMO(BaseModule):
"""
# Fetch Bundles of own other Devices
if self.own_jid not in self.query_for_bundles:
if self._own_jid not in self._query_for_bundles:
devices_without_session = self.omemo \
.devices_without_sessions(self.own_jid)
devices_without_session = self.backend \
.devices_without_sessions(self._own_jid)
self.query_for_bundles.append(self.own_jid)
self._query_for_bundles.append(self._own_jid)
if devices_without_session:
for device_id in devices_without_session:
self.request_bundle(self.own_jid, device_id)
self.request_bundle(self._own_jid, device_id)
# Fetch Bundles of contacts devices
if contact_jid not in self.query_for_bundles:
if contact_jid not in self._query_for_bundles:
devices_without_session = self.omemo \
devices_without_session = self.backend \
.devices_without_sessions(contact_jid)
self.query_for_bundles.append(contact_jid)
self._query_for_bundles.append(contact_jid)
if devices_without_session:
for device_id in devices_without_session:
self.request_bundle(contact_jid, device_id)
if self.omemo.getTrustedFingerprints(contact_jid):
if self.backend.has_trusted_keys(contact_jid):
return False
return True
def set_bundle(self):
self._nbxmpp('OMEMO').set_bundle(self.omemo.bundle,
self.omemo.own_device_id)
self._nbxmpp('OMEMO').set_bundle(self.backend.bundle,
self.backend.own_device)
def request_bundle(self, jid, device_id):
log.info('%s => Fetch device bundle %s %s',
@@ -469,7 +406,7 @@ class OMEMO(BaseModule):
self._account, jid, device_id, bundle)
return
if self.omemo.build_session(jid, device_id, bundle):
if self.backend.build_session(jid, device_id, bundle):
log.info('%s => session created for: %s',
self._account, jid)
# Trigger dialog to trust new Fingerprints if
@@ -480,35 +417,24 @@ class OMEMO(BaseModule):
app.nec.push_incoming_event(
NetworkEvent('omemo-new-fingerprint', chat_control=ctrl))
def set_devicelist(self, new=False):
""" Get all currently known own active device ids and publish them
Parameters
----------
new : bool
if True, a devicelist with only the id of this device
is published
"""
if new:
devicelist = [self.omemo.own_device_id]
else:
devicelist = self.omemo.own_devices
devicelist.append(self.omemo.own_device_id)
devicelist = list(set(devicelist))
self.omemo.set_own_devices(devicelist)
def set_devicelist(self):
log.info('%s => Publishing own devicelist: %s',
self._account, devicelist)
self._nbxmpp('OMEMO').set_devicelist(devicelist)
self._account, self.backend.devices_for_publish)
self._nbxmpp('OMEMO').set_devicelist(self.backend.devices_for_publish)
def clear_devicelist(self):
self.backend.update_devicelist(self._own_jid, [self.backend.own_device])
self.set_devicelist()
def request_devicelist(self, jid=None, fetch_bundle=False):
if jid in self.query_for_devicelists:
if jid in self._query_for_devicelists:
return
self._nbxmpp('OMEMO').request_devicelist(
jid,
callback=self._devicelist_received,
user_data=(jid, fetch_bundle))
self.query_for_devicelists.append(jid)
self._query_for_devicelists.append(jid)
def _devicelist_received(self, devicelist, user_data):
jid, fetch_bundle = user_data
@@ -528,35 +454,25 @@ class OMEMO(BaseModule):
self._process_devicelist_update(str(properties.jid), devicelist, False)
def _process_devicelist_update(self, jid, devicelist, fetch_bundle):
if jid is None or self._con.get_own_jid().bareMatch(jid):
log.info('%s => Received own device list: %s',
self._account, devicelist)
self.omemo.set_own_devices(devicelist)
self.omemo.store.setActiveState(devicelist, self.own_jid)
own_devices = jid is None or self._con.get_own_jid().bareMatch(jid)
if own_devices:
jid = self._own_jid
# remove contact from list, so on send button pressed
# we query for bundle and build a session
if jid in self.query_for_bundles:
self.query_for_bundles.remove(jid)
log.info('%s => Received device list for %s: %s',
self._account, jid, devicelist)
self.backend.update_devicelist(jid, devicelist)
if not self.omemo.own_device_id_published():
if jid in self._query_for_bundles:
self._query_for_bundles.remove(jid)
if own_devices:
if not self.backend.is_own_device_published:
# Our own device_id is not in the list, it could be
# overwritten by some other client
self.set_devicelist()
else:
log.info('%s => Received device list for %s: %s',
self._account, jid, devicelist)
self.omemo.set_devices(jid, devicelist)
self.omemo.store.setActiveState(devicelist, jid)
# remove contact from list, so on send button pressed
# we query for bundle and build a session
if jid in self.query_for_bundles:
self.query_for_bundles.remove(jid)
if fetch_bundle:
self.are_keys_missing(jid)
elif fetch_bundle:
self.are_keys_missing(jid)
@staticmethod
def _debug_print_stanza(stanza):
@@ -567,9 +483,5 @@ class OMEMO(BaseModule):
log.debug('-'*15)
class OMEMOError(Exception):
pass
def get_instance(*args, **kwargs):
return OMEMO(*args, **kwargs), 'OMEMO'