[omemo] Refactor whole plugin
- create a OMEMOConnection class to mimic more how Gajim does things
This commit is contained in:
@@ -52,18 +52,18 @@ UNDECIDED = 2
|
||||
|
||||
|
||||
class OmemoState:
|
||||
def __init__(self, own_jid, connection, account, plugin):
|
||||
def __init__(self, own_jid, db_con, account, xmpp_con):
|
||||
""" Instantiates an OmemoState object.
|
||||
|
||||
:param connection: an :py:class:`sqlite3.Connection`
|
||||
"""
|
||||
self.account = account
|
||||
self.plugin = plugin
|
||||
self.xmpp_con = xmpp_con
|
||||
self.session_ciphers = {}
|
||||
self.own_jid = own_jid
|
||||
self.device_ids = {}
|
||||
self.own_devices = []
|
||||
self.store = LiteAxolotlStore(connection)
|
||||
self.store = LiteAxolotlStore(db_con)
|
||||
self.encryption = self.store.encryptionStore
|
||||
for jid, device_id in self.store.getActiveDeviceTuples():
|
||||
if jid != own_jid:
|
||||
@@ -302,8 +302,8 @@ class OmemoState:
|
||||
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]
|
||||
for nick in self.xmpp_con.groupchat[room]:
|
||||
jid_to = self.xmpp_con.groupchat[room][nick]
|
||||
if jid_to == self.own_jid:
|
||||
continue
|
||||
if jid_to in encrypted_jids: # We already encrypted to this JID
|
||||
@@ -366,8 +366,8 @@ class OmemoState:
|
||||
if gc:
|
||||
room = jid
|
||||
devicelist = []
|
||||
for nick in self.plugin.groupchat[room]:
|
||||
jid_to = self.plugin.groupchat[room][nick]
|
||||
for nick in self.xmpp_con.groupchat[room]:
|
||||
jid_to = self.xmpp_con.groupchat[room][nick]
|
||||
if jid_to == self.own_jid:
|
||||
continue
|
||||
try:
|
||||
@@ -449,7 +449,7 @@ class OmemoState:
|
||||
key = sessionCipher.decryptPkmsg(preKeyWhisperMessage)
|
||||
# Publish new bundle after PreKey has been used
|
||||
# for building a new Session
|
||||
self.plugin.publish_bundle(self.account)
|
||||
self.xmpp_con.publish_bundle()
|
||||
self.add_device(recipient_id, device_id)
|
||||
return key
|
||||
except UntrustedIdentityException as e:
|
||||
|
||||
780
omemo/omemo_connection.py
Normal file
780
omemo/omemo_connection.py
Normal file
@@ -0,0 +1,780 @@
|
||||
import os
|
||||
import time
|
||||
import shutil
|
||||
import logging
|
||||
import sqlite3
|
||||
|
||||
import nbxmpp
|
||||
from nbxmpp.simplexml import Node
|
||||
|
||||
from gajim.common import app
|
||||
from gajim.common import ged
|
||||
from gajim.common import caps_cache
|
||||
from gajim.common.connection_handlers_events import (
|
||||
MessageReceivedEvent, MamMessageReceivedEvent, MessageNotSentEvent)
|
||||
|
||||
from omemo.xmpp import (
|
||||
NS_NOTIFY, NS_OMEMO, NS_EME, NS_HINTS, BundleInformationAnnouncement,
|
||||
BundleInformationQuery, DeviceListAnnouncement, DevicelistQuery,
|
||||
OmemoMessage, successful, unpack_device_bundle,
|
||||
unpack_device_list_update, unpack_encrypted)
|
||||
from omemo.omemo.state import OmemoState
|
||||
|
||||
DB_DIR_OLD = app.gajimpaths.data_root
|
||||
DB_DIR_NEW = app.gajimpaths['MY_DATA']
|
||||
|
||||
ALLOWED_TAGS = [('request', nbxmpp.NS_RECEIPTS),
|
||||
('active', nbxmpp.NS_CHATSTATES),
|
||||
('gone', nbxmpp.NS_CHATSTATES),
|
||||
('inactive', nbxmpp.NS_CHATSTATES),
|
||||
('paused', nbxmpp.NS_CHATSTATES),
|
||||
('composing', nbxmpp.NS_CHATSTATES),
|
||||
('no-store', nbxmpp.NS_MSG_HINTS),
|
||||
('store', nbxmpp.NS_MSG_HINTS),
|
||||
('no-copy', nbxmpp.NS_MSG_HINTS),
|
||||
('no-permanent-store', nbxmpp.NS_MSG_HINTS),
|
||||
('replace', nbxmpp.NS_CORRECT),
|
||||
('thread', None)]
|
||||
|
||||
log = logging.getLogger('gajim.plugin_system.omemo')
|
||||
|
||||
|
||||
class OMEMOConnection:
|
||||
def __init__(self, account, plugin):
|
||||
self.account = account
|
||||
self.plugin = plugin
|
||||
self.own_jid = self.get_own_jid(stripped=True)
|
||||
self.omemo = self.__get_omemo()
|
||||
|
||||
self.groupchat = {}
|
||||
self.temp_groupchat = {}
|
||||
self.gc_message = {}
|
||||
self.query_for_bundles = []
|
||||
|
||||
app.ged.register_event_handler('pep-received', ged.PRECORE,
|
||||
self.handle_device_list_update)
|
||||
app.ged.register_event_handler('signed-in', ged.PRECORE,
|
||||
self.signed_in)
|
||||
app.ged.register_event_handler('gc-presence-received', ged.PRECORE,
|
||||
self.gc_presence_received)
|
||||
app.ged.register_event_handler('gc-config-changed-received', ged.PRECORE,
|
||||
self.gc_config_changed_received)
|
||||
app.ged.register_event_handler('muc-admin-received', ged.PRECORE,
|
||||
self.room_memberlist_received)
|
||||
|
||||
def get_con(self):
|
||||
return app.connections[self.account]
|
||||
|
||||
def send_with_callback(self, stanza, callback, data=None):
|
||||
if data is None:
|
||||
self.get_con().connection.SendAndCallForResponse(stanza, callback)
|
||||
else:
|
||||
self.get_con().connection.SendAndCallForResponse(
|
||||
stanza, callback, data)
|
||||
|
||||
def get_own_jid(self, stripped=False):
|
||||
if stripped:
|
||||
return self.get_con().get_own_jid().getStripped()
|
||||
return self.get_con().get_own_jid()
|
||||
|
||||
def migrate_dbpath(self):
|
||||
old_dbpath = os.path.join(DB_DIR_OLD, 'omemo_' + self.account + '.db')
|
||||
new_dbpath = os.path.join(DB_DIR_NEW, 'omemo_' + self.own_jid + '.db')
|
||||
|
||||
if os.path.exists(old_dbpath):
|
||||
log.debug('Migrating DBName and Path ..')
|
||||
try:
|
||||
shutil.move(old_dbpath, new_dbpath)
|
||||
return new_dbpath
|
||||
except Exception:
|
||||
log.exception('Migration Error:')
|
||||
return old_dbpath
|
||||
|
||||
return new_dbpath
|
||||
|
||||
def __get_omemo(self):
|
||||
""" Returns the the OmemoState for the specified account.
|
||||
Creates the OmemoState if it does not exist yet.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
account : str
|
||||
the account name
|
||||
|
||||
Returns
|
||||
-------
|
||||
OmemoState
|
||||
"""
|
||||
db_path = self.migrate_dbpath()
|
||||
conn = sqlite3.connect(db_path, check_same_thread=False)
|
||||
return OmemoState(self.own_jid, conn, self.account, self)
|
||||
|
||||
def signed_in(self, event):
|
||||
""" Method called on SignIn
|
||||
|
||||
Parameters
|
||||
----------
|
||||
event : SignedInEvent
|
||||
"""
|
||||
if event.conn.name != self.account:
|
||||
return
|
||||
log.debug('%s => Announce Support after Sign In', self.account)
|
||||
self.query_for_bundles = []
|
||||
self.publish_bundle()
|
||||
self.query_own_devicelist()
|
||||
|
||||
def activate(self):
|
||||
""" Method called when the Plugin is activated in the PluginManager
|
||||
"""
|
||||
# self.query_for_bundles = []
|
||||
|
||||
if NS_NOTIFY not in app.gajim_optional_features[self.account]:
|
||||
app.gajim_optional_features[self.account].append(NS_NOTIFY)
|
||||
self._compute_caps_hash()
|
||||
if app.account_is_connected(self.account):
|
||||
log.debug('%s => Announce Support after Plugin Activation',
|
||||
self.account)
|
||||
self.query_for_bundles = []
|
||||
self.publish_bundle()
|
||||
self.query_own_devicelist()
|
||||
|
||||
def deactivate(self):
|
||||
""" Method called when the Plugin is deactivated in the PluginManager
|
||||
|
||||
Removes OMEMO from the Entity Capabilities list
|
||||
"""
|
||||
if NS_NOTIFY in app.gajim_optional_features[self.account]:
|
||||
app.gajim_optional_features[self.account].remove(NS_NOTIFY)
|
||||
self._compute_caps_hash()
|
||||
|
||||
def _compute_caps_hash(self):
|
||||
""" Computes the hash for Entity Capabilities and publishes it """
|
||||
app.caps_hash[self.account] = caps_cache.compute_caps_hash(
|
||||
[app.gajim_identity],
|
||||
app.gajim_common_features +
|
||||
app.gajim_optional_features[self.account])
|
||||
# re-send presence with new hash
|
||||
connected = app.connections[self.account].connected
|
||||
if connected > 1 and app.SHOW_LIST[connected] != 'invisible':
|
||||
app.connections[self.account].change_status(
|
||||
app.SHOW_LIST[connected], app.connections[self.account].status)
|
||||
|
||||
def message_received(self, conn, obj, callback):
|
||||
if obj.encrypted:
|
||||
return
|
||||
if isinstance(obj, MessageReceivedEvent):
|
||||
self._message_received(obj)
|
||||
elif isinstance(obj, MamMessageReceivedEvent):
|
||||
self._mam_message_received(obj)
|
||||
if obj.encrypted == 'OMEMO':
|
||||
callback(obj)
|
||||
|
||||
def _mam_message_received(self, msg):
|
||||
""" Handles an incoming MAM message
|
||||
|
||||
Payload is decrypted and the plaintext is written into the
|
||||
event object. Afterwards the event is passed on further to Gajim.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
msg : MamMessageReceivedEvent
|
||||
|
||||
Returns
|
||||
-------
|
||||
Return means that the Event is passed on to Gajim
|
||||
"""
|
||||
if msg.conn.name != self.account:
|
||||
return
|
||||
omemo_encrypted_tag = msg.msg_.getTag('encrypted', namespace=NS_OMEMO)
|
||||
if omemo_encrypted_tag:
|
||||
log.debug('%s => OMEMO MAM msg received', self.account)
|
||||
|
||||
from_jid = str(msg.msg_.getAttr('from'))
|
||||
from_jid = app.get_jid_without_resource(from_jid)
|
||||
|
||||
msg_dict = unpack_encrypted(omemo_encrypted_tag)
|
||||
|
||||
msg_dict['sender_jid'] = from_jid
|
||||
|
||||
plaintext = self.omemo.decrypt_msg(msg_dict)
|
||||
|
||||
if not plaintext:
|
||||
msg.encrypted = 'drop'
|
||||
return
|
||||
|
||||
self.print_msg_to_log(msg.msg_)
|
||||
|
||||
msg.msgtxt = plaintext
|
||||
msg.encrypted = self.plugin.encryption_name
|
||||
return
|
||||
|
||||
def _message_received(self, msg):
|
||||
""" Handles an incoming message
|
||||
|
||||
Payload is decrypted and the plaintext is written into the
|
||||
event object. Afterwards the event is passed on further to Gajim.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
msg : MessageReceivedEvent
|
||||
|
||||
Returns
|
||||
-------
|
||||
Return means that the Event is passed on to Gajim
|
||||
"""
|
||||
if msg.conn.name != self.account:
|
||||
return
|
||||
if msg.stanza.getTag('encrypted', namespace=NS_OMEMO):
|
||||
log.debug('%s => OMEMO msg received', self.account)
|
||||
|
||||
if msg.forwarded and msg.sent:
|
||||
from_jid = str(msg.stanza.getTo()) # why gajim? why?
|
||||
log.debug('message was forwarded doing magic')
|
||||
else:
|
||||
from_jid = str(msg.stanza.getFrom())
|
||||
|
||||
self.print_msg_to_log(msg.stanza)
|
||||
msg_dict = unpack_encrypted(msg.stanza.getTag
|
||||
('encrypted', namespace=NS_OMEMO))
|
||||
|
||||
if msg.mtype == 'groupchat':
|
||||
address_tag = msg.stanza.getTag('addresses',
|
||||
namespace=nbxmpp.NS_ADDRESS)
|
||||
if address_tag: # History Message from MUC
|
||||
from_jid = address_tag.getTag(
|
||||
'address', attrs={'type': 'ofrom'}).getAttr('jid')
|
||||
else:
|
||||
try:
|
||||
from_jid = self.groupchat[msg.jid][msg.resource]
|
||||
except KeyError:
|
||||
log.debug('Groupchat: Last resort trying to '
|
||||
'find SID in DB')
|
||||
from_jid = self.omemo.store. \
|
||||
getJidFromDevice(msg_dict['sid'])
|
||||
if not from_jid:
|
||||
log.error('%s => Cant decrypt GroupChat Message '
|
||||
'from %s', self.account, msg.resource)
|
||||
msg.encrypted = 'drop'
|
||||
return
|
||||
self.groupchat[msg.jid][msg.resource] = from_jid
|
||||
|
||||
log.debug('GroupChat Message from: %s', from_jid)
|
||||
|
||||
plaintext = ''
|
||||
if msg_dict['sid'] == self.omemo.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('%s => Cant decrypt own GroupChat Message',
|
||||
self.account)
|
||||
msg.encrypted = 'drop'
|
||||
return
|
||||
else:
|
||||
msg_dict['sender_jid'] = app. \
|
||||
get_jid_without_resource(from_jid)
|
||||
plaintext = self.omemo.decrypt_msg(msg_dict)
|
||||
|
||||
if not plaintext:
|
||||
msg.encrypted = 'drop'
|
||||
return
|
||||
|
||||
msg.msgtxt = plaintext
|
||||
# Gajim bug: there must be a body or the message
|
||||
# gets dropped from history
|
||||
msg.stanza.setBody(plaintext)
|
||||
msg.encrypted = self.plugin.encryption_name
|
||||
|
||||
def room_memberlist_received(self, event):
|
||||
if event.conn.name != self.account:
|
||||
return
|
||||
log.debug('Room %s Memberlist received: %s',
|
||||
event.fjid, event.users_dict)
|
||||
room = event.fjid
|
||||
|
||||
if room not in self.groupchat:
|
||||
self.groupchat[room] = {}
|
||||
|
||||
def jid_known(jid):
|
||||
for nick in self.groupchat[room]:
|
||||
if self.groupchat[room][nick] == jid:
|
||||
return True
|
||||
return False
|
||||
|
||||
for jid in event.users_dict:
|
||||
if not jid_known(jid):
|
||||
# Add JID with JID because we have no Nick yet
|
||||
self.groupchat[room][jid] = jid
|
||||
log.debug('JID Added: %s', jid)
|
||||
|
||||
def gc_presence_received(self, event):
|
||||
if event.conn.name != self.account:
|
||||
return
|
||||
if not hasattr(event, 'real_jid') or not event.real_jid:
|
||||
return
|
||||
|
||||
room = event.room_jid
|
||||
jid = app.get_jid_without_resource(event.real_jid)
|
||||
nick = event.nick
|
||||
|
||||
if '303' in event.status_code: # Nick Changed
|
||||
if room in self.groupchat:
|
||||
if nick in self.groupchat[room]:
|
||||
del self.groupchat[room][nick]
|
||||
self.groupchat[room][event.new_nick] = jid
|
||||
log.debug('Nick Change: old: %s, new: %s, jid: %s ',
|
||||
nick, event.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][event.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
|
||||
|
||||
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)
|
||||
|
||||
if '100' in event.status_code: # non-anonymous Room (Full JID)
|
||||
|
||||
if room not in self.groupchat:
|
||||
self.groupchat[room] = self.temp_groupchat[room]
|
||||
|
||||
log.debug('OMEMO capable Room found: %s', room)
|
||||
|
||||
self.get_con().get_affiliation_list(room, 'owner')
|
||||
self.get_con().get_affiliation_list(room, 'admin')
|
||||
self.get_con().get_affiliation_list(room, 'member')
|
||||
|
||||
def gc_config_changed_received(self, event):
|
||||
if event.conn.name != self.account:
|
||||
return
|
||||
room = event.room_jid
|
||||
if '172' in event.status_code:
|
||||
if room not in self.groupchat:
|
||||
self.groupchat[room] = self.temp_groupchat[room]
|
||||
log.debug('CONFIG CHANGE')
|
||||
log.debug(event.room_jid)
|
||||
log.debug(event.status_code)
|
||||
|
||||
def gc_encrypt_message(self, conn, event, callback):
|
||||
""" Manipulates the outgoing groupchat stanza
|
||||
|
||||
The body is getting encrypted
|
||||
|
||||
Parameters
|
||||
----------
|
||||
conn : nbxmpp.NonBlockingClient
|
||||
|
||||
event : GcStanzaMessageOutgoingEvent
|
||||
|
||||
callback: func
|
||||
The callback. Its only called if the stanza was encrypted.
|
||||
This prevents any accidental sending of unencrypted messages.
|
||||
|
||||
"""
|
||||
if event.conn.name != self.account:
|
||||
return
|
||||
try:
|
||||
self.cleanup_stanza(event)
|
||||
|
||||
if not event.message:
|
||||
callback(event)
|
||||
return
|
||||
|
||||
to_jid = app.get_jid_without_resource(event.jid)
|
||||
|
||||
msg_dict = self.omemo.create_gc_msg(
|
||||
self.own_jid, to_jid, event.message.encode('utf8'))
|
||||
if not msg_dict:
|
||||
raise OMEMOError('Error while encrypting')
|
||||
|
||||
except OMEMOError as error:
|
||||
log.error(error)
|
||||
app.nec.push_incoming_event(
|
||||
MessageNotSentEvent(
|
||||
None, conn=conn, jid=event.jid, message=event.message,
|
||||
error=error, time_=time.time(), session=None))
|
||||
return
|
||||
|
||||
self.gc_message[msg_dict['payload']] = event.message
|
||||
encrypted_node = OmemoMessage(msg_dict)
|
||||
|
||||
event.msg_iq.addChild(node=encrypted_node)
|
||||
|
||||
# XEP-0380: Explicit Message Encryption
|
||||
eme_node = Node('encryption', attrs={'xmlns': NS_EME,
|
||||
'name': 'OMEMO',
|
||||
'namespace': NS_OMEMO})
|
||||
event.msg_iq.addChild(node=eme_node)
|
||||
|
||||
# Add Message for devices that dont support OMEMO
|
||||
support_msg = 'You received a message encrypted with ' \
|
||||
'OMEMO but your client doesnt support OMEMO.'
|
||||
event.msg_iq.setBody(support_msg)
|
||||
|
||||
# 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)
|
||||
callback(event)
|
||||
|
||||
def encrypt_message(self, conn, event, callback):
|
||||
""" Manipulates the outgoing stanza
|
||||
|
||||
Encrypt the body
|
||||
|
||||
Parameters
|
||||
----------
|
||||
conn : nbxmpp.NonBlockingClient
|
||||
|
||||
event : StanzaMessageOutgoingEvent
|
||||
|
||||
callback: func
|
||||
The callback. Its only called if the stanza was encrypted.
|
||||
This prevents any accidental sending of unencrypted messages.
|
||||
"""
|
||||
if event.conn.name != self.account:
|
||||
return
|
||||
try:
|
||||
self.cleanup_stanza(event)
|
||||
|
||||
if not event.message:
|
||||
callback(event)
|
||||
return
|
||||
|
||||
to_jid = app.get_jid_without_resource(event.jid)
|
||||
|
||||
plaintext = event.message.encode('utf8')
|
||||
msg_dict = self.omemo.create_msg(self.own_jid, to_jid, plaintext)
|
||||
if not msg_dict:
|
||||
raise OMEMOError('Error while encrypting')
|
||||
|
||||
except OMEMOError as error:
|
||||
log.error(error)
|
||||
app.nec.push_incoming_event(
|
||||
MessageNotSentEvent(
|
||||
None, conn=conn, jid=event.jid, message=event.message,
|
||||
error=error, time_=time.time(), session=event.session))
|
||||
return
|
||||
|
||||
encrypted_node = OmemoMessage(msg_dict)
|
||||
event.msg_iq.addChild(node=encrypted_node)
|
||||
|
||||
# XEP-0380: Explicit Message Encryption
|
||||
eme_node = Node('encryption', 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)
|
||||
event.xhtml = None
|
||||
event.encrypted = self.plugin.encryption_name
|
||||
callback(event)
|
||||
|
||||
@staticmethod
|
||||
def cleanup_stanza(obj):
|
||||
''' We make sure only allowed tags are in the stanza '''
|
||||
|
||||
stanza = nbxmpp.Message(
|
||||
to=obj.msg_iq.getTo(),
|
||||
typ=obj.msg_iq.getType())
|
||||
stanza.setThread(obj.msg_iq.getThread())
|
||||
for tag, ns in ALLOWED_TAGS:
|
||||
node = obj.msg_iq.getTag(tag, namespace=ns)
|
||||
if node:
|
||||
stanza.addChild(node=node)
|
||||
obj.msg_iq = stanza
|
||||
|
||||
def handle_device_list_update(self, event):
|
||||
""" Check if the passed event is a device list update and store the new
|
||||
device ids.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
event : PEPReceivedEvent
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if the given event was a valid device list update event
|
||||
"""
|
||||
|
||||
if event.conn.name != self.account:
|
||||
return
|
||||
|
||||
if event.pep_type != 'omemo-devicelist':
|
||||
return False
|
||||
|
||||
devices_list = list(set(unpack_device_list_update(event.stanza,
|
||||
event.conn.name)))
|
||||
contact_jid = app.get_jid_without_resource(event.fjid)
|
||||
if not devices_list:
|
||||
log.error('%s => Received empty or invalid Devicelist from: %s',
|
||||
self.account, contact_jid)
|
||||
return False
|
||||
|
||||
if self.get_own_jid().bareMatch(contact_jid):
|
||||
log.info('%s => Received own device list: %s',
|
||||
self.account, devices_list)
|
||||
self.omemo.set_own_devices(devices_list)
|
||||
self.omemo.store.sessionStore.setActiveState(
|
||||
devices_list, self.own_jid)
|
||||
|
||||
# remove contact from list, so on send button pressed
|
||||
# we query for bundle and build a session
|
||||
if contact_jid in self.query_for_bundles:
|
||||
self.query_for_bundles.remove(contact_jid)
|
||||
|
||||
if not self.omemo.own_device_id_published():
|
||||
# Our own device_id is not in the list, it could be
|
||||
# overwritten by some other client
|
||||
self.publish_own_devices_list()
|
||||
else:
|
||||
log.info('%s => Received device list for %s: %s',
|
||||
self.account, contact_jid, devices_list)
|
||||
self.omemo.set_devices(contact_jid, devices_list)
|
||||
self.omemo.store.sessionStore.setActiveState(
|
||||
devices_list, contact_jid)
|
||||
|
||||
# remove contact from list, so on send button pressed
|
||||
# we query for bundle and build a session
|
||||
if contact_jid in self.query_for_bundles:
|
||||
self.query_for_bundles.remove(contact_jid)
|
||||
|
||||
# Enable Encryption on receiving first Device List
|
||||
# TODO
|
||||
|
||||
return True
|
||||
|
||||
def publish_own_devices_list(self, new=False):
|
||||
""" Get all currently known own active device ids and publish them
|
||||
|
||||
Parameters
|
||||
----------
|
||||
new : bool
|
||||
if True, a devicelist with only one
|
||||
(the current id of this instance) device id is pushed
|
||||
"""
|
||||
if new:
|
||||
devices_list = [self.omemo.own_device_id]
|
||||
else:
|
||||
devices_list = self.omemo.own_devices
|
||||
devices_list.append(self.omemo.own_device_id)
|
||||
devices_list = list(set(devices_list))
|
||||
self.omemo.set_own_devices(devices_list)
|
||||
|
||||
log.debug('%s => Publishing own Devices: %s',
|
||||
self.account, devices_list)
|
||||
device_announce = DeviceListAnnouncement(devices_list)
|
||||
self.send_with_callback(device_announce,
|
||||
self.device_list_publish_result)
|
||||
|
||||
@staticmethod
|
||||
def device_list_publish_result(stanza):
|
||||
log.debug(stanza)
|
||||
|
||||
def are_keys_missing(self, contact_jid):
|
||||
""" Checks if devicekeys are missing and querys the
|
||||
bundles
|
||||
|
||||
Parameters
|
||||
----------
|
||||
contact_jid : str
|
||||
bare jid of the contact
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
Returns True if there are no trusted Fingerprints
|
||||
"""
|
||||
|
||||
# Fetch Bundles of own other Devices
|
||||
if self.own_jid not in self.query_for_bundles:
|
||||
|
||||
devices_without_session = self.omemo \
|
||||
.devices_without_sessions(self.own_jid)
|
||||
|
||||
self.query_for_bundles.append(self.own_jid)
|
||||
|
||||
if devices_without_session:
|
||||
for device_id in devices_without_session:
|
||||
self.fetch_device_bundle_information(self.own_jid,
|
||||
device_id)
|
||||
|
||||
# Fetch Bundles of contacts devices
|
||||
if contact_jid not in self.query_for_bundles:
|
||||
|
||||
devices_without_session = self.omemo \
|
||||
.devices_without_sessions(contact_jid)
|
||||
|
||||
self.query_for_bundles.append(contact_jid)
|
||||
|
||||
if devices_without_session:
|
||||
for device_id in devices_without_session:
|
||||
self.fetch_device_bundle_information(contact_jid,
|
||||
device_id)
|
||||
|
||||
if self.omemo.getTrustedFingerprints(contact_jid):
|
||||
return False
|
||||
return True
|
||||
|
||||
def fetch_device_bundle_information(self, jid, device_id):
|
||||
""" Fetch bundle information for specified jid, key, and create axolotl
|
||||
session on success.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
jid : str
|
||||
The jid to query for bundle information
|
||||
device_id : int
|
||||
The device id for which we want the bundle
|
||||
"""
|
||||
log.info('%s => Fetch bundle device %s#%s',
|
||||
self.account, device_id, jid)
|
||||
bundle_query = BundleInformationQuery(jid, device_id)
|
||||
self.send_with_callback(bundle_query,
|
||||
self.session_from_prekey_bundle,
|
||||
{'jid': jid, 'device_id': device_id})
|
||||
|
||||
def session_from_prekey_bundle(self, conn, stanza, jid, device_id):
|
||||
""" Starts a session from a PreKey bundle.
|
||||
This method tries to build an axolotl session when a PreKey bundle
|
||||
is fetched.
|
||||
If a session can not be build it will fail silently but log the a
|
||||
warning.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
conn : nbxmpp.NonBlockingClient
|
||||
|
||||
stanza : nbxmpp.Iq
|
||||
The stanza
|
||||
jid : str
|
||||
Jid of the contact
|
||||
device_id : int
|
||||
The device id
|
||||
"""
|
||||
|
||||
bundle_dict = unpack_device_bundle(stanza, device_id)
|
||||
if not bundle_dict:
|
||||
log.warning('Failed to build Session with %s', jid)
|
||||
return
|
||||
|
||||
if self.omemo.build_session(jid, device_id, bundle_dict):
|
||||
log.info('%s => session created for: %s',
|
||||
self.account, jid)
|
||||
# Trigger dialog to trust new Fingerprints if
|
||||
# the Chat Window is Open
|
||||
ctrl = app.interface.msg_win_mgr.get_control(
|
||||
jid, self.account)
|
||||
if ctrl:
|
||||
self.plugin.new_fingerprints_available(ctrl)
|
||||
|
||||
def query_own_devicelist(self):
|
||||
""" Query own devicelist from the server """
|
||||
|
||||
device_query = DevicelistQuery(self.own_jid)
|
||||
log.info('%s => Querry own devicelist ...', self.account)
|
||||
self.send_with_callback(device_query, self.handle_devicelist_result)
|
||||
|
||||
def publish_bundle(self):
|
||||
""" Publish our bundle information to the PEP node """
|
||||
|
||||
bundle_announce = BundleInformationAnnouncement(
|
||||
self.omemo.bundle, self.omemo.own_device_id)
|
||||
log.info('%s => Publishing bundle ...', self.account)
|
||||
self.send_with_callback(bundle_announce, self.handle_publish_result)
|
||||
|
||||
def handle_publish_result(self, stanza):
|
||||
""" Log if publishing our bundle was successful
|
||||
|
||||
Parameters
|
||||
----------
|
||||
stanza : nbxmpp.Iq
|
||||
The stanza
|
||||
"""
|
||||
if successful(stanza):
|
||||
log.info('%s => Publishing bundle was successful', self.account)
|
||||
else:
|
||||
log.error('%s => Publishing bundle was NOT successful',
|
||||
self.account)
|
||||
|
||||
def handle_devicelist_result(self, stanza):
|
||||
""" If query was successful add own device to the list.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
stanza : nbxmpp.Iq
|
||||
The stanza
|
||||
"""
|
||||
|
||||
if successful(stanza):
|
||||
devices_list = list(set(unpack_device_list_update(stanza, self.account)))
|
||||
if not devices_list:
|
||||
log.error('%s => Devicelistquery was NOT successful',
|
||||
self.account)
|
||||
self.publish_own_devices_list(new=True)
|
||||
return False
|
||||
|
||||
self.omemo.set_own_devices(devices_list)
|
||||
self.omemo.store.sessionStore.setActiveState(
|
||||
devices_list, self.own_jid)
|
||||
log.info('%s => Devicelistquery was successful', self.account)
|
||||
if not self.omemo.own_device_id_published():
|
||||
# Our own device_id is not in the list, it could be
|
||||
# overwritten by some other client
|
||||
self.publish_own_devices_list()
|
||||
else:
|
||||
log.error('%s => Devicelistquery was NOT successful', self.account)
|
||||
self.publish_own_devices_list(new=True)
|
||||
|
||||
def clear_device_list(self):
|
||||
""" Overwrite the current devicelist on the server with only
|
||||
our device id.
|
||||
"""
|
||||
if not app.account_is_connected(self.account):
|
||||
return
|
||||
devices_list = [self.omemo.own_device_id]
|
||||
self.omemo.set_own_devices(devices_list)
|
||||
|
||||
log.info('%s => Clearing devices_list %s', self.account, devices_list)
|
||||
device_announce = DeviceListAnnouncement(devices_list)
|
||||
self.send_with_callback(device_announce, self.clear_device_list_result)
|
||||
|
||||
@staticmethod
|
||||
def clear_device_list_result(stanza):
|
||||
log.info(stanza)
|
||||
|
||||
@staticmethod
|
||||
def print_msg_to_log(stanza):
|
||||
""" Prints a stanza in a fancy way to the log """
|
||||
|
||||
log.debug('-'*15)
|
||||
stanzastr = '\n' + stanza.__str__(fancy=True)
|
||||
stanzastr = stanzastr[0:-1]
|
||||
log.debug(stanzastr)
|
||||
log.debug('-'*15)
|
||||
|
||||
|
||||
class OMEMOError(Exception):
|
||||
pass
|
||||
1014
omemo/omemoplugin.py
1014
omemo/omemoplugin.py
File diff suppressed because it is too large
Load Diff
@@ -174,7 +174,7 @@ class OMEMOConfigDialog(GajimPluginConfigDialog):
|
||||
active = self.B.get_object('account_combobox').get_active()
|
||||
account = self.account_store[active][0]
|
||||
|
||||
state = self.plugin.get_omemo_state(account)
|
||||
state = self.plugin.get_omemo(account)
|
||||
|
||||
mod, paths = self.fpr_view.get_selection().get_selected_rows()
|
||||
|
||||
@@ -202,7 +202,7 @@ class OMEMOConfigDialog(GajimPluginConfigDialog):
|
||||
active = self.B.get_object('account_combobox').get_active()
|
||||
account = self.account_store[active][0]
|
||||
|
||||
state = self.plugin.get_omemo_state(account)
|
||||
state = self.plugin.get_omemo(account)
|
||||
|
||||
mod, paths = self.fpr_view.get_selection().get_selected_rows()
|
||||
|
||||
@@ -311,7 +311,7 @@ class OMEMOConfigDialog(GajimPluginConfigDialog):
|
||||
self.B.get_object('cleardevice_button').set_sensitive(True)
|
||||
|
||||
# Set FPR Label and DeviceID
|
||||
state = self.plugin.get_omemo_state(account)
|
||||
state = self.plugin.get_omemo(account)
|
||||
deviceid = state.own_device_id
|
||||
self.B.get_object('ID').set_markup('<tt>%s</tt>' % deviceid)
|
||||
|
||||
@@ -373,7 +373,7 @@ class FingerprintWindow(Gtk.Dialog):
|
||||
self.windowinstances = windowinstances
|
||||
self.account = self.contact.account.name
|
||||
self.plugin = plugin
|
||||
self.omemostate = self.plugin.get_omemo_state(self.account)
|
||||
self.omemostate = self.plugin.get_omemo(self.account)
|
||||
self.own_jid = app.get_jid_from_account(self.account)
|
||||
Gtk.Dialog.__init__(self,
|
||||
title=('Fingerprints for %s') % contact.jid,
|
||||
|
||||
@@ -39,6 +39,7 @@ NS_OMEMO = 'eu.siacs.conversations.axolotl'
|
||||
NS_DEVICE_LIST = NS_OMEMO + '.devicelist'
|
||||
NS_NOTIFY = NS_DEVICE_LIST + '+notify'
|
||||
NS_BUNDLES = NS_OMEMO + '.bundles:'
|
||||
NS_HINTS = 'urn:xmpp:hints'
|
||||
log = logging.getLogger('gajim.plugin_system.omemo')
|
||||
|
||||
|
||||
@@ -149,7 +150,7 @@ class DevicelistQuery(Iq):
|
||||
|
||||
|
||||
class DevicelistPEP(AbstractPEP):
|
||||
type_ = 'headline'
|
||||
type_ = 'omemo-devicelist'
|
||||
namespace = NS_DEVICE_LIST
|
||||
|
||||
def _extract_info(self, items):
|
||||
|
||||
Reference in New Issue
Block a user