Welcome to mirror list, hosted at ThFree Co, Russian Federation.

dev.gajim.org/gajim/gajim-plugins.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilipp Hörist <forenjunkie@chello.at>2017-11-10 23:21:53 +0300
committerPhilipp Hörist <forenjunkie@chello.at>2017-11-11 01:35:48 +0300
commitce480aa5d799d6f30f5a63be396efdabee94ec23 (patch)
treebe9f5eaacd5307f144daa65d775191e5c2a08382 /omemo/omemoplugin.py
parent18598abf24152eb1ae74ee0708bc3087bd6d4656 (diff)
[omemo] Refactor whole plugin
- create a OMEMOConnection class to mimic more how Gajim does things
Diffstat (limited to 'omemo/omemoplugin.py')
-rw-r--r--omemo/omemoplugin.py1014
1 files changed, 77 insertions, 937 deletions
diff --git a/omemo/omemoplugin.py b/omemo/omemoplugin.py
index 1f20e5f..fa2dfd4 100644
--- a/omemo/omemoplugin.py
+++ b/omemo/omemoplugin.py
@@ -21,35 +21,18 @@ the Gajim-OMEMO plugin. If not, see <http://www.gnu.org/licenses/>.
'''
import logging
-import os
-import sqlite3
-import shutil
-import nbxmpp
import binascii
import threading
-import time
from gi.repository import GLib
-from nbxmpp.simplexml import Node
-from nbxmpp import NS_ADDRESS
from gajim import dialogs
-from gajim.common import caps_cache, app, ged, configpaths
+from gajim.common import app, ged
from gajim.common.pep import SUPPORTED_PERSONAL_USER_EVENTS
from gajim.plugins import GajimPlugin
from gajim.groupchat_control import GroupchatControl
-from .xmpp import (
- NS_NOTIFY, NS_OMEMO, NS_EME, BundleInformationAnnouncement,
- BundleInformationQuery, DeviceListAnnouncement, DevicelistQuery,
- DevicelistPEP, OmemoMessage, successful, unpack_device_bundle,
- unpack_device_list_update, unpack_encrypted)
-
-from gajim.common.connection_handlers_events import (
- MessageReceivedEvent, MamMessageReceivedEvent, MessageNotSentEvent)
-
-
-IQ_CALLBACK = {}
+from omemo.xmpp import DevicelistPEP
CRYPTOGRAPHY_MISSING = 'You are missing Python-Cryptography'
AXOLOTL_MISSING = 'You are missing Python-Axolotl or use an outdated version'
@@ -57,61 +40,40 @@ PROTOBUF_MISSING = 'OMEMO cant import Google Protobuf, you can find help in ' \
'the GitHub Wiki'
ERROR_MSG = ''
-NS_HINTS = 'urn:xmpp:hints'
-DB_DIR_OLD = app.gajimpaths.data_root
-DB_DIR_NEW = configpaths.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')
try:
- from .file_decryption import FileDecryption
-except Exception as e:
- log.exception(e)
+ from omemo.file_decryption import FileDecryption
+except Exception as error:
+ log.exception(error)
ERROR_MSG = CRYPTOGRAPHY_MISSING
try:
import google.protobuf
-except Exception as e:
- log.error(e)
+except Exception as error:
+ log.error(error)
ERROR_MSG = PROTOBUF_MISSING
try:
import axolotl
-except Exception as e:
- log.error(e)
+except Exception as error:
+ log.error(error)
ERROR_MSG = AXOLOTL_MISSING
if not ERROR_MSG:
try:
- from .omemo.state import OmemoState
- from .ui import OMEMOConfigDialog, FingerprintWindow
- except Exception as e:
- log.error(e)
- ERROR_MSG = 'Error: ' + str(e)
+ from omemo.omemo_connection import OMEMOConnection
+ from omemo.ui import OMEMOConfigDialog, FingerprintWindow
+ except Exception as error:
+ log.error(error)
+ ERROR_MSG = 'Error: %s' % error
# pylint: disable=no-init
# pylint: disable=attribute-defined-outside-init
class OmemoPlugin(GajimPlugin):
-
- omemo_states = {}
- groupchat = {}
- temp_groupchat = {}
-
def init(self):
""" Init """
if ERROR_MSG:
@@ -122,21 +84,16 @@ class OmemoPlugin(GajimPlugin):
self.encryption_name = 'OMEMO'
self.allow_groupchat = True
self.events_handlers = {
- 'pep-received': (ged.PRECORE, self.handle_device_list_update),
- 'raw-iq-received': (ged.PRECORE, self.handle_iq_received),
'signed-in': (ged.PRECORE, self.signed_in),
- 'gc-presence-received': (ged.PRECORE, self.gc_presence_received),
- 'gc-config-changed-received':
- (ged.PRECORE, self.gc_config_changed_received),
- 'muc-admin-received': (ged.PRECORE, self.room_memberlist_received),
}
self.config_dialog = OMEMOConfigDialog(self)
self.gui_extension_points = {
- 'hyperlink_handler': (self.file_decryption, None),
+ 'hyperlink_handler': (self._file_decryption, None),
'encrypt' + self.encryption_name: (self._encrypt_message, None),
- 'gc_encrypt' + self.encryption_name: (self._gc_encrypt_message, None),
- 'decrypt': (self.message_received, None),
+ 'gc_encrypt' + self.encryption_name: (
+ self._gc_encrypt_message, None),
+ 'decrypt': (self._message_received, None),
'send_message' + self.encryption_name: (
self.before_sendmessage, None),
'encryption_dialog' + self.encryption_name: (
@@ -145,11 +102,9 @@ class OmemoPlugin(GajimPlugin):
self.encryption_state, None)}
SUPPORTED_PERSONAL_USER_EVENTS.append(DevicelistPEP)
- self.announced = []
- self.query_for_bundles = []
self.disabled_accounts = []
- self.gc_message = {}
self.windowinstances = {}
+ self.connections = {}
self.config_default_values = {'DISABLED_ACCOUNTS': ([], ''), }
@@ -162,66 +117,6 @@ class OmemoPlugin(GajimPlugin):
schemes += ' aesgcm://'
app.config.set('uri_schemes', schemes)
- def migrate_dbpath(self, account, my_jid):
- old_dbpath = os.path.join(DB_DIR_OLD, 'omemo_' + account + '.db')
- new_dbpath = os.path.join(DB_DIR_NEW, 'omemo_' + my_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_state(self, account):
- """ 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
- """
- if account in self.disabled_accounts:
- return
- if account not in self.omemo_states:
- my_jid = app.get_jid_from_account(account)
- db_path = self.migrate_dbpath(account, my_jid)
-
- conn = sqlite3.connect(db_path, check_same_thread=False)
- self.omemo_states[account] = OmemoState(my_jid, conn, account,
- self)
-
- return self.omemo_states[account]
-
- def file_decryption(self, url, kind, instance, window):
- FileDecryption(self).hyperlink_handler(url, kind, instance, window)
-
- def encrypt_file(self, file, account, callback):
- thread = threading.Thread(target=self._encrypt_file_thread,
- args=(file, account, callback))
- thread.daemon = True
- thread.start()
-
- def _encrypt_file_thread(self, file, account, callback):
- state = self.get_omemo_state(account)
- encrypted_data, key, iv = state.encrypt_file(file.get_data(full=True))
- file.encrypted = True
- file.size = len(encrypted_data)
- file.user_data = binascii.hexlify(iv + key).decode('utf-8')
- file.data = encrypted_data
- if file.event.isSet():
- return
- GLib.idle_add(callback, file)
-
def signed_in(self, event):
""" Method called on SignIn
@@ -232,57 +127,64 @@ class OmemoPlugin(GajimPlugin):
account = event.conn.name
if account in self.disabled_accounts:
return
- log.debug(account +
- ' => Announce Support after Sign In')
- self.query_for_bundles = []
- self.announced = []
- self.announced.append(account)
- self.publish_bundle(account)
- self.query_own_devicelist(account)
+ if account not in self.connections:
+ self.connections[account] = OMEMOConnection(account, self)
+ self.connections[account].signed_in(event)
def activate(self):
""" Method called when the Plugin is activated in the PluginManager
"""
- self.query_for_bundles = []
- # Publish bundle information and Entity Caps
for account in app.connections:
if account in self.disabled_accounts:
- log.debug(account +
- ' => Account is disabled')
continue
- if NS_NOTIFY not in app.gajim_optional_features[account]:
- app.gajim_optional_features[account].append(NS_NOTIFY)
- self._compute_caps_hash(account)
- if account not in self.announced:
- if app.account_is_connected(account):
- log.debug(account +
- ' => Announce Support after Plugin Activation')
- self.announced.append(account)
- self.publish_bundle(account)
- self.query_own_devicelist(account)
+ self.connections[account] = OMEMOConnection(account, self)
+ self.connections[account].activate()
def deactivate(self):
""" Method called when the Plugin is deactivated in the PluginManager
-
- Removes OMEMO from the Entity Capabilities list
"""
- for account in app.connections:
- if account in self.disabled_accounts:
- continue
- if NS_NOTIFY in app.gajim_optional_features[account]:
- app.gajim_optional_features[account].remove(NS_NOTIFY)
- self._compute_caps_hash(account)
+ for account in self.connections:
+ self.connections[account].deactivate()
def activate_encryption(self, chat_control):
if isinstance(chat_control, GroupchatControl):
if chat_control.room_jid not in self.groupchat:
dialogs.ErrorDialog(
- _('Bad Configuration'),
- _('To use OMEMO in a Groupchat, the Groupchat should be'
- ' non-anonymous and members-only.'))
+ _('Bad Configuration'),
+ _('To use OMEMO in a Groupchat, the Groupchat should be'
+ ' non-anonymous and members-only.'))
return False
return True
+ def _message_received(self, conn, obj, callback):
+ self.connections[conn.name].message_received(conn, obj, callback)
+
+ def _gc_encrypt_message(self, conn, obj, callback):
+ self.connections[conn.name].gc_encrypt_message(conn, obj, callback)
+
+ def _encrypt_message(self, conn, obj, callback):
+ self.connections[conn.name].encrypt_message(conn, obj, callback)
+
+ def _file_decryption(self, url, kind, instance, window):
+ FileDecryption(self).hyperlink_handler(url, kind, instance, window)
+
+ def encrypt_file(self, file, account, callback):
+ thread = threading.Thread(target=self._encrypt_file_thread,
+ args=(file, account, callback))
+ thread.daemon = True
+ thread.start()
+
+ def _encrypt_file_thread(self, file, account, callback):
+ omemo = self.get_omemo(account)
+ encrypted_data, key, iv = omemo.encrypt_file(file.get_data(full=True))
+ file.encrypted = True
+ file.size = len(encrypted_data)
+ file.user_data = binascii.hexlify(iv + key).decode('utf-8')
+ file.data = encrypted_data
+ if file.event.isSet():
+ return
+ GLib.idle_add(callback, file)
+
@staticmethod
def encryption_state(chat_control, state):
state['visible'] = True
@@ -291,6 +193,9 @@ class OmemoPlugin(GajimPlugin):
def on_encryption_button_clicked(self, chat_control):
self.show_fingerprint_window(chat_control)
+ def get_omemo(self, account):
+ return self.connections[account].omemo
+
def before_sendmessage(self, chat_control):
account = chat_control.account
contact = chat_control.contact
@@ -303,37 +208,38 @@ class OmemoPlugin(GajimPlugin):
real_jid = self.groupchat[room][nick]
if real_jid == own_jid:
continue
- if not self.are_keys_missing(account, real_jid):
+ if not self.connections[account].are_keys_missing(real_jid):
missing = False
if missing:
- log.debug(account + ' => No Trusted Fingerprints for ' + room)
+ log.debug('%s => No Trusted Fingerprints for %s',
+ account, room)
self.no_trusted_fingerprints_warning(chat_control)
else:
- if self.are_keys_missing(account, contact.jid):
- log.debug(account + ' => No Trusted Fingerprints for ' +
- contact.jid)
+ if self.connections[account].are_keys_missing(contact.jid):
+ log.debug('%s => No Trusted Fingerprints for %s',
+ account, contact.jid)
self.no_trusted_fingerprints_warning(chat_control)
chat_control.sendmessage = False
else:
- log.debug(account + ' => Sending Message to ' +
- contact.jid)
+ log.debug('%s => Sending Message to %s',
+ account, contact.jid)
def new_fingerprints_available(self, chat_control):
jid = chat_control.contact.jid
account = chat_control.account
- state = self.get_omemo_state(account)
+ omemo = self.get_omemo(account)
if isinstance(chat_control, GroupchatControl):
room_jid = chat_control.room_jid
if room_jid in self.groupchat:
for nick in self.groupchat[room_jid]:
real_jid = self.groupchat[room_jid][nick]
- fingerprints = state.store. \
+ fingerprints = omemo.store. \
getNewFingerprints(real_jid)
if fingerprints:
self.show_fingerprint_window(
chat_control, fingerprints)
elif not isinstance(chat_control, GroupchatControl):
- fingerprints = state.store.getNewFingerprints(jid)
+ fingerprints = omemo.store.getNewFingerprints(jid)
if fingerprints:
self.show_fingerprint_window(
chat_control, fingerprints)
@@ -341,7 +247,7 @@ class OmemoPlugin(GajimPlugin):
def show_fingerprint_window(self, chat_control, fingerprints=None):
contact = chat_control.contact
account = chat_control.account
- state = self.get_omemo_state(account)
+ omemo = self.get_omemo(account)
transient = chat_control.parent_win.window
if 'dialog' not in self.windowinstances:
if isinstance(chat_control, GroupchatControl):
@@ -354,782 +260,16 @@ class OmemoPlugin(GajimPlugin):
self.windowinstances)
self.windowinstances['dialog'].show_all()
if fingerprints:
- log.debug(account +
- ' => Showing Fingerprint Prompt for ' +
- contact.jid)
- state.store.setShownFingerprints(fingerprints)
+ log.debug('%s => Showing Fingerprint Prompt for %s',
+ account, contact.jid)
+ omemo.store.setShownFingerprints(fingerprints)
else:
self.windowinstances['dialog'].update_context_list()
if fingerprints:
- state.store.setShownFingerprints(fingerprints)
-
- @staticmethod
- def _compute_caps_hash(account):
- """ Computes the hash for Entity Capabilities and publishes it """
- app.caps_hash[account] = caps_cache.compute_caps_hash(
- [app.gajim_identity],
- app.gajim_common_features +
- app.gajim_optional_features[account])
- # re-send presence with new hash
- connected = app.connections[account].connected
- if connected > 1 and app.SHOW_LIST[connected] != 'invisible':
- app.connections[account].change_status(
- app.SHOW_LIST[connected], app.connections[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
- """
- account = msg.conn.name
- if account in self.disabled_accounts:
- return
-
- omemo_encrypted_tag = msg.msg_.getTag('encrypted', namespace=NS_OMEMO)
- if omemo_encrypted_tag:
- log.debug(account + ' => OMEMO MAM msg received')
-
- state = self.get_omemo_state(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 = state.decrypt_msg(msg_dict)
-
- if not plaintext:
- msg.encrypted = 'drop'
- return
-
- self.print_msg_to_log(msg.msg_)
-
- msg.msgtxt = plaintext
- msg.encrypted = self.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
- """
- account = msg.conn.name
- if account in self.disabled_accounts:
- return
-
- if msg.stanza.getTag('encrypted', namespace=NS_OMEMO):
- log.debug(account + ' => OMEMO msg received')
-
- state = self.get_omemo_state(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=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 = state.store. \
- getJidFromDevice(msg_dict['sid'])
- if not from_jid:
- log.error(account +
- ' => Cant decrypt GroupChat Message '
- 'from ' + 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'] == 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 own GroupChat '
- 'Message')
- msg.encrypted = 'drop'
- return
- else:
- msg_dict['sender_jid'] = app. \
- get_jid_without_resource(from_jid)
- plaintext = state.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.encryption_name
-
- def room_memberlist_received(self, event):
- account = event.conn.name
- if account in self.disabled_accounts:
- 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: ' + jid)
-
- def gc_presence_received(self, event):
- account = event.conn.name
- if account in self.disabled_accounts:
- 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: ' + 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)
-
- app.connections[account].get_affiliation_list(room, 'owner')
- app.connections[account].get_affiliation_list(room, 'admin')
- app.connections[account].get_affiliation_list(room, 'member')
-
- def gc_config_changed_received(self, event):
- account = event.conn.name
- room = event.room_jid
- if account in self.disabled_accounts:
- return
- 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
- ----------
- event : StanzaMessageOutgoingEvent
-
- Returns
- -------
- Return if encryption is not activated or any other
- exception or error occurs
- """
- account = event.conn.name
- try:
- if account in self.disabled_accounts:
- raise OMEMOError('Account disabled in OMEMO config')
-
- self.cleanup_stanza(event)
-
- if not event.message:
- callback(event)
- return
-
- state = self.get_omemo_state(account)
- to_jid = app.get_jid_without_resource(event.jid)
- own_jid = app.get_jid_from_account(account)
-
- msg_dict = state.create_gc_msg(
- 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
-
- The body is getting encrypted
-
- Parameters
- ----------
- event : StanzaMessageOutgoingEvent
-
- Returns
- -------
- Return if encryption is not activated or any other
- exception or error occurs
- """
- account = event.conn.name
- try:
- if account in self.disabled_accounts:
- raise OMEMOError('Account disabled in OMEMO config')
-
- self.cleanup_stanza(event)
-
- if not event.message:
- callback(event)
- return
-
- state = self.get_omemo_state(account)
- to_jid = app.get_jid_without_resource(event.jid)
- own_jid = app.get_jid_from_account(account)
-
- plaintext = event.message.encode('utf8')
- msg_dict = state.create_msg(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.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
-
-
- See also
- --------
- 4.2 Discovering peer support
- http://conversations.im/xeps/multi-end.html#usecases-discovering
- """
-
- account = event.conn.name
- if account in self.disabled_accounts:
- return False
-
- if event.pep_type != 'headline':
- 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(account +
- ' => Received empty or invalid Devicelist from: ' +
- contact_jid)
- return False
-
- state = self.get_omemo_state(account)
- my_jid = app.get_jid_from_account(account)
-
- if contact_jid == my_jid:
- log.info(account + ' => Received own device list:' + str(
- devices_list))
- state.set_own_devices(devices_list)
- state.store.sessionStore.setActiveState(devices_list, my_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 state.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(account)
- else:
- log.info(account + ' => Received device list for ' +
- contact_jid + ':' + str(devices_list))
- state.set_devices(contact_jid, devices_list)
- state.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, account, new=False):
- """ Get all currently known own active device ids and publish them
-
- Parameters
- ----------
- account : str
- the account name
-
- new : bool
- if True, a devicelist with only one
- (the current id of this instance) device id is pushed
- """
- state = self.get_omemo_state(account)
- if new:
- devices_list = [state.own_device_id]
- else:
- devices_list = state.own_devices
- devices_list.append(state.own_device_id)
- devices_list = list(set(devices_list))
- state.set_own_devices(devices_list)
-
- log.debug(account + ' => Publishing own Devices: ' + str(
- devices_list))
- iq = DeviceListAnnouncement(devices_list)
- app.connections[account].connection.send(iq)
- id_ = str(iq.getAttr('id'))
- IQ_CALLBACK[id_] = lambda event: log.debug(event)
-
- def are_keys_missing(self, account, contact_jid):
- """ Checks if devicekeys are missing and querys the
- bundles
-
- Parameters
- ----------
- account : str
- the account name
- contact_jid : str
- bare jid of the contact
-
- Returns
- -------
- bool
- Returns True if there are no trusted Fingerprints
- """
- state = self.get_omemo_state(account)
- my_jid = app.get_jid_from_account(account)
-
- # Fetch Bundles of own other Devices
- if my_jid not in self.query_for_bundles:
-
- devices_without_session = state \
- .devices_without_sessions(my_jid)
-
- self.query_for_bundles.append(my_jid)
-
- if devices_without_session:
- for device_id in devices_without_session:
- self.fetch_device_bundle_information(account, my_jid,
- device_id)
-
- # Fetch Bundles of contacts devices
- if contact_jid not in self.query_for_bundles:
-
- devices_without_session = state \
- .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(account, contact_jid,
- device_id)
-
- if state.getTrustedFingerprints(contact_jid):
- return False
- return True
-
- @staticmethod
- def handle_iq_received(event):
- """ Method called when an IQ is received
-
- Parameters
- ----------
- event : RawIqReceived
- """
- id_ = str(event.stanza.getAttr("id"))
- if id_ in IQ_CALLBACK:
- try:
- IQ_CALLBACK[id_](event.stanza)
- except:
- raise
- finally:
- del IQ_CALLBACK[id_]
-
- def fetch_device_bundle_information(self, account, jid, device_id):
- """ Fetch bundle information for specified jid, key, and create axolotl
- session on success.
-
- Parameters
- ----------
- account : str
- The account name
- jid : str
- The jid to query for bundle information
- device_id : int
- The device_id for which we are missing an axolotl session
- """
- log.info(account + ' => Fetch bundle device ' + str(device_id) +
- '#' + jid)
- iq = BundleInformationQuery(jid, device_id)
- iq_id = str(iq.getAttr('id'))
- IQ_CALLBACK[iq_id] = \
- lambda stanza: self.session_from_prekey_bundle(account,
- stanza, jid,
- device_id)
- app.connections[account].connection.send(iq)
-
- def session_from_prekey_bundle(self, account, stanza,
- recipient_id, 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.
-
- See also
- --------
-
- 4.4 Building a session:
- http://conversations.im/xeps/multi-end.html#usecases-building
-
- Parameters:
- -----------
- account : str
- The account name
- stanza
- The stanza object received from callback
- recipient_id : str
- The recipient jid
- device_id : int
- The device_id for which the bundle was queried
-
- """
- state = self.get_omemo_state(account)
- bundle_dict = unpack_device_bundle(stanza, device_id)
- if not bundle_dict:
- log.warning('Failed to build Session with ' + recipient_id)
- return
-
- if state.build_session(recipient_id, device_id, bundle_dict):
- log.info(account + ' => session created for: ' + recipient_id)
- # Trigger dialog to trust new Fingerprints if
- # the Chat Window is Open
- ctrl = app.interface.msg_win_mgr.get_control(
- recipient_id, account)
- if ctrl:
- self.new_fingerprints_available(ctrl)
-
- def query_own_devicelist(self, account):
- """ Query own devicelist from the server.
-
- Parameters
- ----------
- account : str
- the account name
- """
- my_jid = app.get_jid_from_account(account)
- iq = DevicelistQuery(my_jid)
- app.connections[account].connection.send(iq)
- log.info(account + ' => Querry own devicelist ...')
- id_ = str(iq.getAttr("id"))
- IQ_CALLBACK[id_] = lambda stanza: \
- self.handle_devicelist_result(account, stanza)
-
- def publish_bundle(self, account):
- """ Publish our bundle information to the PEP node.
-
- Parameters
- ----------
- account : str
- the account name
-
- See also
- --------
- 4.3 Announcing bundle information:
- http://conversations.im/xeps/multi-end.html#usecases-announcing
- """
- state = self.get_omemo_state(account)
- iq = BundleInformationAnnouncement(state.bundle, state.own_device_id)
- app.connections[account].connection.send(iq)
- id_ = str(iq.getAttr("id"))
- log.info(account + " => Publishing bundle ...")
- IQ_CALLBACK[id_] = lambda stanza: \
- self.handle_publish_result(account, stanza)
-
- @staticmethod
- def handle_publish_result(account, stanza):
- """ Log if publishing our bundle was successful
-
- Parameters
- ----------
- account : str
- the account name
- stanza
- The stanza object received from callback
- """
- if successful(stanza):
- log.info(account + ' => Publishing bundle was successful')
- else:
- log.error(account + ' => Publishing bundle was NOT successful')
-
- def handle_devicelist_result(self, account, stanza):
- """ If query was successful add own device to the list.
-
- Parameters
- ----------
- account : str
- the account name
- stanza
- The stanza object received from callback
- """
-
- my_jid = app.get_jid_from_account(account)
- state = self.get_omemo_state(account)
-
- if successful(stanza):
- devices_list = list(set(unpack_device_list_update(stanza, account)))
- if not devices_list:
- log.error(account + ' => Devicelistquery was NOT successful')
- self.publish_own_devices_list(account, new=True)
- return False
- contact_jid = stanza.getAttr('from')
- if isinstance(contact_jid, nbxmpp.JID):
- contact_jid = str(contact_jid)
- if contact_jid == my_jid:
- state.set_own_devices(devices_list)
- state.store.sessionStore.setActiveState(devices_list, my_jid)
- log.info(account + ' => Devicelistquery was successful')
- # 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 state.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(account)
- else:
- log.error(account + ' => Devicelistquery was NOT successful')
- self.publish_own_devices_list(account, new=True)
-
- def clear_device_list(self, account):
- """ Clears the local devicelist of our own devices and publishes
- a new one including only the current ID of this device
-
- Parameters
- ----------
- account : str
- the account name
- """
- connection = app.connections[account].connection
- if not connection:
- return
- state = self.get_omemo_state(account)
- devices_list = [state.own_device_id]
- state.set_own_devices(devices_list)
-
- log.info(account + ' => Clearing devices_list ' + str(devices_list))
- iq = DeviceListAnnouncement(devices_list)
- connection.send(iq)
- id_ = str(iq.getAttr('id'))
- IQ_CALLBACK[id_] = lambda event: log.info(event)
-
- @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)
+ omemo.store.setShownFingerprints(fingerprints)
@staticmethod
def no_trusted_fingerprints_warning(chat_control):
msg = "To send an encrypted message, you have to " \
"first trust the fingerprint of your contact!"
chat_control.print_conversation_line(msg, 'status', '', None)
-
- def omemo_enable_for(self, jid, account):
- """ Used by the UI to enable OMEMO for a specified contact.
-
- To activate OMEMO check first if a Ui Object exists for the
- Contact. If it exists use Ui.activate_omemo(). Only if there
- is no Ui Object for the contact this method is to be used.
-
- Parameters
- ----------
- jid : str
- bare jid
- account : str
- the account name
- """
- state = self.get_omemo_state(account)
- state.encryption.activate(jid)
-
- def omemo_disable_for(self, jid, account):
- """ Used by the UI to disable OMEMO for a specified contact.
-
- WARNING - OMEMO should only be disabled through
- User interaction with the UI.
-
- Parameters
- ----------
- jid : str
- bare jid
- account : str
- the account name
- """
- state = self.get_omemo_state(account)
- state.encryption.deactivate(jid)
-
-
-class OMEMOError(Exception):
- pass