diff options
author | Philipp Hörist <forenjunkie@chello.at> | 2017-05-07 10:44:05 +0300 |
---|---|---|
committer | Philipp Hörist <forenjunkie@chello.at> | 2017-05-07 20:13:09 +0300 |
commit | b8ff3d195b92aae5f8af9409952b17db539f9320 (patch) | |
tree | cec5204be0503f60c4082e50594aa0b8396dd4cc /omemo | |
parent | de369fc6a0b33943cdf23f759844bfa6132094ed (diff) |
[omemo] Use Gajims encryption API
Diffstat (limited to 'omemo')
-rw-r--r-- | omemo/omemoplugin.py | 467 | ||||
-rw-r--r-- | omemo/ui.py | 349 |
2 files changed, 265 insertions, 551 deletions
diff --git a/omemo/omemoplugin.py b/omemo/omemoplugin.py index ca7c093..cbd454c 100644 --- a/omemo/omemoplugin.py +++ b/omemo/omemoplugin.py @@ -1,35 +1,40 @@ # -*- coding: utf-8 -*- -# -# Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de> -# Copyright 2015 Daniel Gultsch <daniel@cgultsch.de> -# -# This file is part of Gajim-OMEMO plugin. -# -# The Gajim-OMEMO plugin is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# Gajim-OMEMO is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR -# A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# the Gajim-OMEMO plugin. If not, see <http://www.gnu.org/licenses/>. -# + +''' +Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de> +Copyright 2015 Daniel Gultsch <daniel@cgultsch.de> +Copyright 2016 Philipp Hörist <philipp@hoerist.com> + +This file is part of Gajim-OMEMO plugin. + +The Gajim-OMEMO plugin is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) any +later version. + +Gajim-OMEMO is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +the Gajim-OMEMO plugin. If not, see <http://www.gnu.org/licenses/>. +''' import logging import os import sqlite3 import shutil import message_control +import nbxmpp + +from nbxmpp.simplexml import Node +from nbxmpp import NS_CORRECT, NS_ADDRESS +import dialogs from common import caps_cache, gajim, ged, configpaths from common.pep import SUPPORTED_PERSONAL_USER_EVENTS from plugins import GajimPlugin -from plugins.helpers import log_calls -from nbxmpp.simplexml import Node -from nbxmpp import NS_CORRECT, NS_ADDRESS +from groupchat_control import GroupchatControl from .xmpp import ( NS_NOTIFY, NS_OMEMO, NS_EME, BundleInformationAnnouncement, @@ -37,6 +42,9 @@ from .xmpp import ( DevicelistPEP, OmemoMessage, successful, unpack_device_bundle, unpack_device_list_update, unpack_encrypted) +from common.connection_handlers_events import ( + MessageReceivedEvent, MamMessageReceivedEvent) + IQ_CALLBACK = {} @@ -49,10 +57,22 @@ GAJIM_VERSION = 'OMEMO only works with the latest Gajim version, get the ' \ ERROR_MSG = '' NS_HINTS = 'urn:xmpp:hints' -NS_PGP = 'urn:xmpp:openpgp:0' DB_DIR_OLD = gajim.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: @@ -76,7 +96,7 @@ except Exception as e: if not ERROR_MSG: try: from .omemo.state import OmemoState - from .ui import Ui, OMEMOConfigDialog + from .ui import OMEMOConfigDialog, FingerprintWindow except Exception as e: log.error(e) ERROR_MSG = 'Error: ' + str(e) @@ -88,11 +108,9 @@ if not ERROR_MSG: class OmemoPlugin(GajimPlugin): omemo_states = {} - ui_list = {} groupchat = {} temp_groupchat = {} - @log_calls('OmemoPlugin') def init(self): """ Init """ if ERROR_MSG: @@ -100,18 +118,12 @@ class OmemoPlugin(GajimPlugin): self.available_text = ERROR_MSG self.config_dialog = None return + self.encryption_name = 'OMEMO' + self.allow_groupchat = True self.events_handlers = { - 'mam-message-received': (ged.PRECORE, self.mam_message_received), - 'message-received': (ged.PRECORE, self.message_received), '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), - 'stanza-message-outgoing': - (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), @@ -119,18 +131,25 @@ class OmemoPlugin(GajimPlugin): } self.config_dialog = OMEMOConfigDialog(self) - self.gui_extension_points = {'chat_control': (self.connect_ui, - self.disconnect_ui), - 'groupchat_control': (self.connect_ui, - self.disconnect_ui), - 'hyperlink_handler': (self.file_decryption, - None)} + self.gui_extension_points = { + '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), + 'send_message' + self.encryption_name: ( + self.before_sendmessage, None), + 'encryption_dialog' + self.encryption_name: ( + self.on_encryption_button_clicked, None), + 'encryption_state' + self.encryption_name: ( + self.encryption_state, None)} + SUPPORTED_PERSONAL_USER_EVENTS.append(DevicelistPEP) self.plugin = self self.announced = [] self.query_for_bundles = [] self.disabled_accounts = [] self.gc_message = {} + self.windowinstances = {} self.config_default_values = {'DISABLED_ACCOUNTS': ([], ''), } @@ -158,7 +177,6 @@ class OmemoPlugin(GajimPlugin): return new_dbpath - @log_calls('OmemoPlugin') def get_omemo_state(self, account): """ Returns the the OmemoState for the specified account. Creates the OmemoState if it does not exist yet. @@ -175,7 +193,6 @@ class OmemoPlugin(GajimPlugin): if account in self.disabled_accounts: return if account not in self.omemo_states: - self.deactivate_gajim_e2e(account) my_jid = gajim.get_jid_from_account(account) db_path = self.migrate_dbpath(account, my_jid) @@ -185,19 +202,9 @@ class OmemoPlugin(GajimPlugin): return self.omemo_states[account] - @staticmethod - def deactivate_gajim_e2e(account): - """ Deativates E2E encryption in Gajim """ - gajim.config.set_per('accounts', account, - 'autonegotiate_esessions', False) - gajim.config.set_per('accounts', account, - 'enable_esessions', False) - log.info(str(account) + " => Gajim E2E encryption disabled") - def file_decryption(self, url, kind, instance, window): FileDecryption(self).hyperlink_handler(url, kind, instance, window) - @log_calls('OmemoPlugin') def signed_in(self, event): """ Method called on SignIn @@ -216,7 +223,6 @@ class OmemoPlugin(GajimPlugin): self.publish_bundle(account) self.query_own_devicelist(account) - @log_calls('OmemoPlugin') def activate(self): """ Method called when the Plugin is activated in the PluginManager """ @@ -238,7 +244,6 @@ class OmemoPlugin(GajimPlugin): self.publish_bundle(account) self.query_own_devicelist(account) - @log_calls('OmemoPlugin') def deactivate(self): """ Method called when the Plugin is deactivated in the PluginManager @@ -251,6 +256,98 @@ class OmemoPlugin(GajimPlugin): gajim.gajim_optional_features[account].remove(NS_NOTIFY) self._compute_caps_hash(account) + 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.')) + return False + return True + + @staticmethod + def encryption_state(chat_control, state): + state['visible'] = True + state['authenticated'] = True + + def on_encryption_button_clicked(self, chat_control): + self.show_fingerprint_window(chat_control) + + def before_sendmessage(self, chat_control): + account = chat_control.account + contact = chat_control.contact + self.new_fingerprints_available(chat_control) + if isinstance(chat_control, GroupchatControl): + missing = True + own_jid = gajim.get_jid_from_account(account) + for nick in self.plugin.groupchat[self.room]: + real_jid = self.plugin.groupchat[self.room][nick] + if real_jid == own_jid: + continue + if not self.plugin.are_keys_missing(self.account, + real_jid): + missing = False + if missing: + log.debug(self.account + + ' => No Trusted Fingerprints for ' + + self.room) + self.no_trusted_fingerprints_warning() + else: + if self.are_keys_missing(account, contact.jid): + log.debug(account + ' => No Trusted Fingerprints for ' + + contact.jid) + self.no_trusted_fingerprints_warning(chat_control) + chat_control.sendmessage = False + else: + log.debug(account + ' => Sending Message to ' + + contact.jid) + + def new_fingerprints_available(self, chat_control): + jid = chat_control.contact.jid + account = chat_control.account + state = self.get_omemo_state(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. \ + getNewFingerprints(real_jid) + if fingerprints: + self.show_fingerprint_window( + chat_control, fingerprints) + elif not isinstance(chat_control, GroupchatControl): + fingerprints = state.store.getNewFingerprints(jid) + if fingerprints: + self.show_fingerprint_window( + chat_control, fingerprints) + + def show_fingerprint_window(self, chat_control, fingerprints=None): + contact = chat_control.contact + account = chat_control.account + state = self.get_omemo_state(account) + transient = chat_control.parent_win.window + if 'dialog' not in self.windowinstances: + if isinstance(chat_control, GroupchatControl): + self.windowinstances['dialog'] = \ + FingerprintWindow(self, contact, transient, + self.windowinstances, groupchat=True) + else: + self.windowinstances['dialog'] = \ + FingerprintWindow(self, contact, transient, + self.windowinstances) + self.windowinstances['dialog'].show_all() + if fingerprints: + log.debug(account + + ' => Showing Fingerprint Prompt for ' + + contact.jid) + state.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 """ @@ -264,8 +361,17 @@ class OmemoPlugin(GajimPlugin): gajim.connections[account].change_status( gajim.SHOW_LIST[connected], gajim.connections[account].status) - @log_calls('OmemoPlugin') - def mam_message_received(self, msg): + 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 @@ -283,9 +389,6 @@ class OmemoPlugin(GajimPlugin): if account in self.disabled_accounts: return - if msg.msg_.getTag('openpgp', namespace=NS_PGP): - return - omemo_encrypted_tag = msg.msg_.getTag('encrypted', namespace=NS_OMEMO) if omemo_encrypted_tag: log.debug(account + ' => OMEMO MAM msg received') @@ -302,31 +405,28 @@ class OmemoPlugin(GajimPlugin): plaintext = state.decrypt_msg(msg_dict) if not plaintext: + msg.encrypted = 'drop' return self.print_msg_to_log(msg.msg_) msg.msgtxt = plaintext - - contact_jid = msg.with_ - - if account in self.ui_list and \ - contact_jid in self.ui_list[account]: - self.ui_list[account][contact_jid].activate_omemo() - return False + msg.encrypted = 'OMEMO' + return elif msg.msg_.getTag('body'): account = msg.conn.name - jid = msg.with_ + from_jid = str(msg.msg_.getAttr('from')) + from_jid = gajim.get_jid_without_resource(from_jid) + state = self.get_omemo_state(account) - omemo_enabled = state.encryption.is_active(jid) + encryption = gajim.config.get_per('contacts', from_jid, 'encryption') - if omemo_enabled: + if encryption == 'OMEMO': msg.msgtxt = '**Unencrypted** ' + msg.msgtxt - @log_calls('OmemoPlugin') - def message_received(self, msg): + def _message_received(self, msg): """ Handles an incoming message Payload is decrypted and the plaintext is written into the @@ -344,9 +444,6 @@ class OmemoPlugin(GajimPlugin): if account in self.disabled_accounts: return - if msg.stanza.getTag('openpgp', namespace=NS_PGP): - return - if msg.stanza.getTag('encrypted', namespace=NS_OMEMO): log.debug(account + ' => OMEMO msg received') @@ -379,7 +476,8 @@ class OmemoPlugin(GajimPlugin): log.error(account + ' => Cant decrypt GroupChat Message ' 'from ' + msg.resource) - return True + msg.encrypted = 'drop' + return self.groupchat[msg.jid][msg.resource] = from_jid log.debug('GroupChat Message from: %s', from_jid) @@ -392,25 +490,21 @@ class OmemoPlugin(GajimPlugin): else: log.error(account + ' => Cant decrypt own GroupChat ' 'Message') + msg.encrypted = 'drop' else: msg_dict['sender_jid'] = gajim. \ get_jid_without_resource(from_jid) plaintext = state.decrypt_msg(msg_dict) if not plaintext: - return True + 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) - - if msg.mtype != 'groupchat': - contact_jid = gajim.get_jid_without_resource(from_jid) - if account in self.ui_list and \ - contact_jid in self.ui_list[account]: - self.ui_list[account][contact_jid].activate_omemo() - return False + msg.encrypted = 'OMEMO' elif msg.stanza.getTag('body'): account = msg.conn.name @@ -418,19 +512,15 @@ class OmemoPlugin(GajimPlugin): from_jid = str(msg.stanza.getFrom()) jid = gajim.get_jid_without_resource(from_jid) state = self.get_omemo_state(account) - omemo_enabled = state.encryption.is_active(jid) + encryption = gajim.config.get_per('contacts', jid, 'encryption') - if omemo_enabled: + if encryption == 'OMEMO': msg.msgtxt = '**Unencrypted** ' + msg.msgtxt - # msg.stanza.setBody(msg.msgtxt) + msg.stanza.setBody(msg.msgtxt) - try: - gui = self.ui_list[account].get(jid, None) - if gui and gui.encryption_active(): - gui.plain_warning() - except KeyError: - log.debug('No Ui present for ' + jid + - ', Ui Warning not shown') + ctrl = gajim.interface.msg_win_mgr.get_control(jid, account) + if ctrl: + self.plain_warning(ctrl) def room_memberlist_received(self, event): account = event.conn.name @@ -440,6 +530,9 @@ class OmemoPlugin(GajimPlugin): 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: @@ -452,7 +545,6 @@ class OmemoPlugin(GajimPlugin): self.groupchat[room][jid] = jid log.debug('JID Added: ' + jid) - @log_calls('OmemoPlugin') def gc_presence_received(self, event): account = event.conn.name if account in self.disabled_accounts: @@ -508,18 +600,19 @@ class OmemoPlugin(GajimPlugin): gajim.connections[account].get_affiliation_list(room, 'admin') gajim.connections[account].get_affiliation_list(room, 'member') - self.ui_list[account][room].sensitive(True) - - @log_calls('OmemoPlugin') 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 handle_outgoing_gc_stanza(self, event): + def _gc_encrypt_message(self, conn, event, callback): """ Manipulates the outgoing groupchat stanza The body is getting encrypted @@ -546,10 +639,7 @@ class OmemoPlugin(GajimPlugin): state = self.get_omemo_state(account) full_jid = str(event.msg_iq.getAttr('to')) to_jid = gajim.get_jid_without_resource(full_jid) - if to_jid not in self.groupchat: - return - 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}) @@ -562,9 +652,11 @@ class OmemoPlugin(GajimPlugin): if not msg_dict: return True + self.cleanup_stanza(event) + 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-0380: Explicit Message Encryption @@ -588,40 +680,12 @@ class OmemoPlugin(GajimPlugin): self.print_msg_to_log(event.correction_msg) else: self.print_msg_to_log(event.msg_iq) + callback(event) except Exception as e: log.debug(e) - return True - - @log_calls('OmemoPlugin') - def handle_outgoing_event(self, event): - """ Handles a message outgoing event - - In this event we have no stanza. XHTML is set to None - so that it doesnt make its way into the stanza - - Parameters - ---------- - event : MessageOutgoingEvent - - Returns - ------- - Return if encryption is not activated - """ - if event.type_ == 'normal': - return False - - account = event.account - if account in self.disabled_accounts: return - state = self.get_omemo_state(account) - - if not state.encryption.is_active(event.jid): - return False - - event.xhtml = None - @log_calls('OmemoPlugin') - def handle_outgoing_stanza(self, event): + def _encrypt_message(self, conn, event, callback): """ Manipulates the outgoing stanza The body is getting encrypted @@ -645,8 +709,6 @@ class OmemoPlugin(GajimPlugin): 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): @@ -660,23 +722,7 @@ class OmemoPlugin(GajimPlugin): return True encrypted_node = OmemoMessage(msg_dict) - - # Check if non-OMEMO resource is online - contacts = gajim.contacts.get_contacts(account, to_jid) - non_omemo_resource_online = False - for contact in contacts: - if contact.show == 'offline': - continue - if not contact.supports(NS_NOTIFY): - log.debug(contact.get_full_jid() + - ' => Contact doesnt support OMEMO, ' - 'adding Info Message to Body') - support_msg = 'You received a message encrypted with ' \ - 'OMEMO but your client doesnt support OMEMO.' - event.msg_iq.setBody(support_msg) - non_omemo_resource_online = True - if not non_omemo_resource_online: - event.msg_iq.delChild('body') + self.cleanup_stanza(event) event.msg_iq.addChild(node=encrypted_node) @@ -691,11 +737,25 @@ class OmemoPlugin(GajimPlugin): store = Node('store', attrs={'xmlns': NS_HINTS}) event.msg_iq.addChild(node=store) self.print_msg_to_log(event.msg_iq) + event.xhtml = None + + callback(event) except Exception as e: log.debug(e) - return True - @log_calls('OmemoPlugin') + @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. @@ -762,29 +822,10 @@ class OmemoPlugin(GajimPlugin): self.query_for_bundles.remove(contact_jid) # Enable Encryption on receiving first Device List - if not state.encryption.exist(contact_jid): - if account in self.ui_list and \ - contact_jid in self.ui_list[account]: - log.debug(account + - ' => Switch encryption ON automatically ...') - self.ui_list[account][contact_jid].activate_omemo() - else: - log.debug(account + - ' => Switch encryption ON automatically ...') - self.omemo_enable_for(contact_jid, account) - - if account in self.ui_list and \ - contact_jid not in self.ui_list[account]: - - chat_control = gajim.interface.msg_win_mgr.get_control( - contact_jid, account) - - if chat_control: - self.connect_ui(chat_control) + # TODO return True - @log_calls('OmemoPlugin') def publish_own_devices_list(self, account, new=False): """ Get all currently known own active device ids and publish them @@ -813,58 +854,6 @@ class OmemoPlugin(GajimPlugin): id_ = str(iq.getAttr('id')) IQ_CALLBACK[id_] = lambda event: log.debug(event) - @log_calls('OmemoPlugin') - def connect_ui(self, chat_control): - """ Method called from Gajim when a Chat Window is opened - - Parameters - ---------- - chat_control : ChatControl - Gajim ChatControl object - """ - account = chat_control.contact.account.name - if account in self.disabled_accounts: - return - contact_jid = chat_control.contact.jid - if account not in self.ui_list: - self.ui_list[account] = {} - state = self.get_omemo_state(account) - my_jid = gajim.get_jid_from_account(account) - omemo_enabled = state.encryption.is_active(contact_jid) - if omemo_enabled: - log.debug(account + " => Adding OMEMO ui for " + contact_jid) - self.ui_list[account][contact_jid] = Ui(self, chat_control, - omemo_enabled, state) - self.ui_list[account][contact_jid].new_fingerprints_available() - return - if contact_jid in state.device_ids or contact_jid == my_jid: - log.debug(account + " => Adding OMEMO ui for " + contact_jid) - self.ui_list[account][contact_jid] = Ui(self, chat_control, - omemo_enabled, state) - self.ui_list[account][contact_jid].new_fingerprints_available() - 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. - - Parameters - ---------- - chat_control : ChatControl - Gajim ChatControl object - """ - contact_jid = chat_control.contact.jid - account = chat_control.contact.account.name - if account in self.disabled_accounts: - return - self.ui_list[account][contact_jid].removeUi() - def are_keys_missing(self, account, contact_jid): """ Checks if devicekeys are missing and querys the bundles @@ -932,7 +921,6 @@ class OmemoPlugin(GajimPlugin): finally: del IQ_CALLBACK[id_] - @log_calls('OmemoPlugin') def fetch_device_bundle_information(self, account, jid, device_id): """ Fetch bundle information for specified jid, key, and create axolotl session on success. @@ -956,7 +944,6 @@ class OmemoPlugin(GajimPlugin): device_id) gajim.connections[account].connection.send(iq) - @log_calls('OmemoPlugin') def session_from_prekey_bundle(self, account, stanza, recipient_id, device_id): """ Starts a session from a PreKey bundle. @@ -995,12 +982,11 @@ class OmemoPlugin(GajimPlugin): log.info(account + ' => session created for: ' + recipient_id) # Trigger dialog to trust new Fingerprints if # the Chat Window is Open - if account in self.ui_list and \ - recipient_id in self.ui_list[account]: - self.ui_list[account][recipient_id]. \ - new_fingerprints_available() + ctrl = gajim.interface.msg_win_mgr.get_control( + recipient_id, account) + if ctrl: + self.new_fingerprints_available(ctrl) - @log_calls('OmemoPlugin') def query_own_devicelist(self, account): """ Query own devicelist from the server. @@ -1017,7 +1003,6 @@ class OmemoPlugin(GajimPlugin): IQ_CALLBACK[id_] = lambda stanza: \ self.handle_devicelist_result(account, stanza) - @log_calls('OmemoPlugin') def publish_bundle(self, account): """ Publish our bundle information to the PEP node. @@ -1055,7 +1040,6 @@ class OmemoPlugin(GajimPlugin): else: log.error(account + ' => Publishing bundle was NOT successful') - @log_calls('OmemoPlugin') def handle_devicelist_result(self, account, stanza): """ If query was successful add own device to the list. @@ -1094,7 +1078,6 @@ class OmemoPlugin(GajimPlugin): log.error(account + ' => Devicelistquery was NOT successful') self.publish_own_devices_list(account, new=True) - @log_calls('OmemoPlugin') 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 @@ -1126,7 +1109,18 @@ class OmemoPlugin(GajimPlugin): log.debug(stanzastr) log.debug('-'*15) - @log_calls('OmemoPlugin') + @staticmethod + def plain_warning(chat_control): + chat_control.print_conversation_line( + 'Received plaintext message! ' + + 'Your next message will still be encrypted!', 'status', '', None) + + @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. @@ -1144,7 +1138,6 @@ class OmemoPlugin(GajimPlugin): state = self.get_omemo_state(account) state.encryption.activate(jid) - @log_calls('OmemoPlugin') def omemo_disable_for(self, jid, account): """ Used by the UI to disable OMEMO for a specified contact. diff --git a/omemo/ui.py b/omemo/ui.py index 1da35f0..99c2db5 100644 --- a/omemo/ui.py +++ b/omemo/ui.py @@ -1,37 +1,32 @@ # -*- coding: utf-8 -*-
-#
-# Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
-# Copyright 2015 Daniel Gultsch <daniel@cgultsch.de>
-#
-# This file is part of Gajim-OMEMO plugin.
-#
-# The Gajim-OMEMO plugin is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by the Free
-# Software Foundation, either version 3 of the License, or (at your option) any
-# later version.
-#
-# Gajim-OMEMO is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along with
-# the Gajim-OMEMO plugin. If not, see <http://www.gnu.org/licenses/>.
-#
+
+'''
+Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
+Copyright 2015 Daniel Gultsch <daniel@cgultsch.de>
+Copyright 2016 Philipp Hörist <philipp@hoerist.com>
+
+This file is part of Gajim-OMEMO plugin.
+
+The Gajim-OMEMO plugin is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or (at your option) any
+later version.
+
+Gajim-OMEMO is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+the Gajim-OMEMO plugin. If not, see <http://www.gnu.org/licenses/>.
+'''
import binascii
import logging
import os
-import message_control
-
-from gi.repository import GObject, Gtk, GdkPixbuf, Gdk
+from enum import IntEnum, unique
-# pylint: disable=import-error
-import gtkgui_helpers
-from common import gajim
-from dialogs import YesNoDialog
-from plugins.gui import GajimPluginConfigDialog
+from gi.repository import Gtk, GdkPixbuf, Gdk
from axolotl.state.sessionrecord import SessionRecord
-from common import configpaths
log = logging.getLogger('gajim.plugin_system.omemo')
@@ -43,285 +38,17 @@ except Exception as e: log.exception('Error:')
log.error('python-qrcode or dependencies of it, are not available')
-# pylint: enable=import-error
-UNDECIDED = 2
-TRUSTED = 1
-UNTRUSTED = 0
-
-
-class OmemoButton(Gtk.Button):
- def __init__(self, plugin, chat_control, ui, enabled):
- super(OmemoButton, self).__init__(label=None, stock=None)
- self.chat_control = chat_control
-
- self.set_property('relief', Gtk.ReliefStyle.NONE)
- self.set_property('can-focus', False)
- self.set_sensitive(True)
-
- icon = Gtk.Image.new_from_file(
- plugin.local_file_path('omemo16x16.png'))
- self.set_image(icon)
- self.set_tooltip_text('OMEMO Encryption')
-
- self.connect('clicked', self.on_click)
-
- self.menu = OmemoMenu(ui, enabled)
-
- def on_click(self, widget):
- """
- Popup omemo menu
- """
- gtkgui_helpers.popup_emoticons_under_button(
- self.menu, widget, self.chat_control.parent_win)
-
- def set_omemo_state(self, state):
- self.menu.set_omemo_state(state)
-
-
-class OmemoMenu(Gtk.Menu):
- def __init__(self, ui, enabled):
- super(OmemoMenu, self).__init__()
- self.ui = ui
-
- self.item_omemo_state = Gtk.CheckMenuItem('Activate OMEMO')
- self.item_omemo_state.set_active(enabled)
- self.item_omemo_state.connect('activate', self.on_toggle_omemo)
- self.append(self.item_omemo_state)
-
- item = Gtk.ImageMenuItem('Fingerprints')
- icon = Gtk.Image.new_from_stock(Gtk.STOCK_DIALOG_AUTHENTICATION,
- Gtk.IconSize.MENU)
- item.set_image(icon)
- item.connect('activate', self.on_open_fingerprint_window)
- self.append(item)
-
- self.show_all()
-
- def on_toggle_omemo(self, widget):
- self.ui.set_omemo_state(widget.get_active())
-
- def on_open_fingerprint_window(self, widget):
- self.ui.show_fingerprint_window()
-
- def set_omemo_state(self, state):
- self.item_omemo_state.handler_block_by_func(self.on_toggle_omemo)
- self.item_omemo_state.set_active(state)
- self.item_omemo_state.handler_unblock_by_func(self.on_toggle_omemo)
-
-
-class Ui(object):
- def __init__(self, plugin, chat_control, enabled, state):
- self.contact = chat_control.contact
- self.chat_control = chat_control
- self.plugin = plugin
- self.state = state
- self.account = self.contact.account.name
- self.windowinstances = {}
-
- self.groupchat = False
- if chat_control.type_id == message_control.TYPE_GC:
- self.groupchat = True
- self.omemo_capable = False
- self.room = self.chat_control.room_jid
-
- self.display_omemo_state()
- self.refresh_auth_lock_icon()
-
- self.omemobutton = OmemoButton(plugin, chat_control, self, enabled)
-
- self.actions_hbox = chat_control.xml.get_object('actions_hbox')
- send_button = chat_control.xml.get_object('send_button')
- send_button_pos = self.actions_hbox.child_get_property(send_button,
- 'position')
- self.actions_hbox.add(self.omemobutton)
- self.actions_hbox.reorder_child(self.omemobutton, send_button_pos - 1)
- self.omemobutton.show_all()
-
- # add a OMEMO entry to the context/advanced menu
- self.chat_control.omemo_orig_prepare_context_menu = \
- self.chat_control.prepare_context_menu
-
- def omemo_prepare_context_menu(hide_buttonbar_items=False):
- menu = self.chat_control. \
- omemo_orig_prepare_context_menu(hide_buttonbar_items)
- submenu = OmemoMenu(self, self.encryption_active())
-
- item = Gtk.ImageMenuItem('OMEMO Encryption')
- icon_path = plugin.local_file_path('omemo16x16.png')
- item.set_image(Gtk.Image.new_from_file(icon_path))
- item.set_submenu(submenu)
-
- if self.groupchat:
- item.set_sensitive(self.omemo_capable)
-
- # at index 8 is the separator after the esession encryption entry
- menu.insert(item, 8)
- return menu
- self.chat_control.prepare_context_menu = omemo_prepare_context_menu
-
- # Hook into Send Button so we can check Stuff before sending
- self.chat_control.orig_send_message = \
- self.chat_control.send_message
-
- def omemo_send_message(message, keyID='', chatstate=None, xhtml=None,
- process_commands=True, attention=False):
- self.new_fingerprints_available()
- if self.encryption_active() and \
- self.plugin.are_keys_missing(self.account,
- self.contact.jid):
-
- log.debug(self.account + ' => No Trusted Fingerprints for ' +
- self.contact.jid)
- self.no_trusted_fingerprints_warning()
- else:
- self.chat_control.orig_send_message(message, keyID, chatstate,
- xhtml, process_commands,
- attention)
- log.debug(self.account + ' => Sending Message to ' +
- self.contact.jid)
-
- def omemo_send_gc_message(message, xhtml=None, process_commands=True):
- self.new_fingerprints_available()
- if self.encryption_active():
- missing = True
- own_jid = gajim.get_jid_from_account(self.account)
- for nick in self.plugin.groupchat[self.room]:
- real_jid = self.plugin.groupchat[self.room][nick]
- if real_jid == own_jid:
- continue
- if not self.plugin.are_keys_missing(self.account,
- real_jid):
- missing = False
- if missing:
- log.debug(self.account +
- ' => No Trusted Fingerprints for ' +
- self.room)
- self.no_trusted_fingerprints_warning()
- else:
- self.chat_control.orig_send_message(message, xhtml,
- process_commands)
- log.debug(self.account + ' => Sending Message to ' +
- self.room)
- else:
- self.chat_control.orig_send_message(message, xhtml,
- process_commands)
- log.debug(self.account + ' => Sending Message to ' +
- self.room)
-
- if self.groupchat:
- self.chat_control.send_message = omemo_send_gc_message
- else:
- self.chat_control.send_message = omemo_send_message
-
- def set_omemo_state(self, enabled):
- """
- Enable or disable OMEMO for this window's contact and update the
- window ui accordingly
- """
- if enabled:
- log.debug(self.contact.account.name + ' => Enable OMEMO for ' +
- self.contact.jid)
- self.plugin.omemo_enable_for(self.contact.jid,
- self.contact.account.name)
- self.refresh_auth_lock_icon()
- else:
- log.debug(self.contact.account.name + ' => Disable OMEMO for ' +
- self.contact.jid)
- self.plugin.omemo_disable_for(self.contact.jid,
- self.contact.account.name)
- self.refresh_auth_lock_icon()
-
- self.omemobutton.set_omemo_state(enabled)
- self.display_omemo_state()
-
- def sensitive(self, value):
- self.omemobutton.set_sensitive(value)
- self.omemo_capable = value
- if value:
- self.chat_control.prepare_context_menu
-
- def encryption_active(self):
- return self.state.encryption.is_active(self.contact.jid)
-
- def activate_omemo(self):
- if not self.encryption_active():
- self.set_omemo_state(True)
-
- def new_fingerprints_available(self):
- jid = self.contact.jid
- if self.groupchat and self.room in self.plugin.groupchat:
- for nick in self.plugin.groupchat[self.room]:
- real_jid = self.plugin.groupchat[self.room][nick]
- fingerprints = self.state.store. \
- getNewFingerprints(real_jid)
- if fingerprints:
- self.show_fingerprint_window(fingerprints)
- elif not self.groupchat:
- fingerprints = self.state.store.getNewFingerprints(jid)
- if fingerprints:
- self.show_fingerprint_window(fingerprints)
-
- def show_fingerprint_window(self, fingerprints=None):
- if 'dialog' not in self.windowinstances:
- if self.groupchat:
- self.windowinstances['dialog'] = \
- FingerprintWindow(self.plugin, self.contact,
- self.chat_control.parent_win.window,
- self.windowinstances, groupchat=True)
- else:
- self.windowinstances['dialog'] = \
- FingerprintWindow(self.plugin, self.contact,
- self.chat_control.parent_win.window,
- self.windowinstances)
- self.windowinstances['dialog'].show_all()
- if fingerprints:
- log.debug(self.account +
- ' => Showing Fingerprint Prompt for ' +
- self.contact.jid)
- self.state.store.setShownFingerprints(fingerprints)
- else:
- self.windowinstances['dialog'].update_context_list()
- if fingerprints:
- self.state.store.setShownFingerprints(fingerprints)
-
- def plain_warning(self):
- self.chat_control.print_conversation_line(
- 'Received plaintext message! ' +
- 'Your next message will still be encrypted!', 'status', '', None)
-
- def display_omemo_state(self):
- if self.encryption_active():
- msg = u'OMEMO encryption enabled'
- else:
- msg = u'OMEMO encryption disabled'
- self.chat_control.print_conversation_line(msg, 'status', '', None)
-
- def no_trusted_fingerprints_warning(self):
- msg = "To send an encrypted message, you have to " \
- "first trust the fingerprint of your contact!"
- self.chat_control.print_conversation_line(msg, 'status', '', None)
+from common import gajim
+from common import configpaths
+from dialogs import YesNoDialog
+from plugins.gui import GajimPluginConfigDialog
- def refresh_auth_lock_icon(self):
- if self.groupchat:
- return
- if self.encryption_active():
- if self.state.getUndecidedFingerprints(self.contact.jid):
- self.chat_control._show_lock_image(True, 'OMEMO', True, True,
- False)
- else:
- self.chat_control._show_lock_image(True, 'OMEMO', True, True,
- True)
- else:
- self.chat_control._show_lock_image(False, 'OMEMO', False, True,
- False)
- def removeUi(self):
- self.actions_hbox.remove(self.omemobutton)
- self.chat_control._show_lock_image(False, 'OMEMO', False, True,
- False)
- self.chat_control.prepare_context_menu = \
- self.chat_control.omemo_orig_prepare_context_menu
- self.chat_control.send_message = self.chat_control.orig_send_message
+@unique
+class State(IntEnum):
+ UNTRUSTED = 0
+ TRUSTED = 1
+ UNDECIDED = 2
class OMEMOConfigDialog(GajimPluginConfigDialog):
@@ -480,7 +207,7 @@ class OMEMOConfigDialog(GajimPluginConfigDialog): mod, paths = self.fpr_view.get_selection().get_selected_rows()
def on_yes(checked, identity_key):
- state.store.setTrust(identity_key, TRUSTED)
+ state.store.setTrust(identity_key, State.TRUSTED)
try:
if self.plugin.ui_list[account]:
self.plugin.ui_list[account][jid]. \
@@ -490,7 +217,7 @@ class OMEMOConfigDialog(GajimPluginConfigDialog): self.update_context_list()
def on_no(identity_key):
- state.store.setTrust(identity_key, UNTRUSTED)
+ state.store.setTrust(identity_key, State.UNTRUSTED)
try:
if jid in self.plugin.ui_list[account]:
self.plugin.ui_list[account][jid]. \
@@ -700,17 +427,11 @@ class FingerprintWindow(Gtk.Dialog): mod, paths = self.fpr_view.get_selection().get_selected_rows()
def on_yes(checked, identity_key):
- state.store.setTrust(identity_key, TRUSTED)
- if not self.groupchat:
- self.plugin.ui_list[self.account][self.contact.jid]. \
- refresh_auth_lock_icon()
+ state.store.setTrust(identity_key, State.TRUSTED)
self.update_context_list()
def on_no(identity_key):
- state.store.setTrust(identity_key, UNTRUSTED)
- if not self.groupchat:
- self.plugin.ui_list[self.account][self.contact.jid]. \
- refresh_auth_lock_icon()
+ state.store.setTrust(identity_key, State.UNTRUSTED)
self.update_context_list()
for path in paths:
|