From 5116af037ba31bc30d7eaa7aeb5e3b0ba9ea2d76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Thu, 13 Apr 2017 22:58:51 +0200 Subject: [PATCH 1/5] Add Encryption Plugin API - Add extension points when receiving/sending a message - Add extension point for setting the lock image - Add extension point after clicking the send button - Add extension point when typing - Add a new menu button to choose the desired encryption - Extend the PluginManager to hold a list of encryption plugins --- data/gui/chat_control.ui | 30 +++++++++++++++--- data/gui/message_window.ui | 2 +- src/chat_control.py | 33 +++++++++++++++++++ src/chat_control_base.py | 40 +++++++++++++++++++++++- src/common/config.py | 1 + src/common/connection.py | 10 +++++- src/common/connection_handlers.py | 14 +++++++++ src/common/connection_handlers_events.py | 1 + src/gui_menu_builder.py | 11 +++++++ src/plugins/gajimplugin.py | 10 ++++++ src/plugins/pluginmanager.py | 16 ++++++++++ 11 files changed, 160 insertions(+), 8 deletions(-) diff --git a/data/gui/chat_control.ui b/data/gui/chat_control.ui index 7301c02a3..323998e5b 100644 --- a/data/gui/chat_control.ui +++ b/data/gui/chat_control.ui @@ -939,6 +939,26 @@ 11 + + + True + True + True + none + + + True + False + channel-secure-symbolic.symbolic + + + + + False + True + 12 + + False @@ -1039,7 +1059,7 @@ audio-mic-volume-low False True - 12 + 13 @@ -1050,12 +1070,9 @@ audio-mic-volume-low True True - 13 + 14 - - - _Send @@ -1077,6 +1094,9 @@ audio-mic-volume-low 15 + + + False diff --git a/data/gui/message_window.ui b/data/gui/message_window.ui index abc5613f1..0aab2b026 100644 --- a/data/gui/message_window.ui +++ b/data/gui/message_window.ui @@ -64,7 +64,7 @@ - + False 480 440 diff --git a/src/chat_control.py b/src/chat_control.py index 83eecc57f..0fb0706b2 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -94,6 +94,7 @@ class ChatControl(ChatControlBase): self.last_recv_message_id = None self.last_recv_message_marks = None self.last_message_timestamp = None + # for muc use: # widget = self.xml.get_object('muc_window_actions_button') self.actions_button = self.xml.get_object('message_window_actions_button') @@ -302,6 +303,11 @@ class ChatControl(ChatControlBase): True) self.update_ui() + self.set_lock_image() + + self.encryption_menu = self.xml.get_object('encryption_menu') + self.encryption_menu.set_menu_model( + gui_menu_builder.get_encryption_menu(self.contact)) # restore previous conversation self.restore_conversation() self.msg_textview.grab_focus() @@ -907,6 +913,21 @@ class ChatControl(ChatControlBase): self._show_lock_image(self.gpg_is_active, 'OpenPGP', self.gpg_is_active, loggable, True) + def set_lock_image(self): + visible = self.encryption != 'disabled' + loggable = self.session and self.session.is_loggable() + + encryption_state = {'visible': visible, + 'enc_type': self.encryption, + 'enc_enabled': False, + 'chat_logged': loggable, + 'authenticated': False} + + gajim.plugin_manager.gui_extension_point( + 'encryption_state' + self.encryption, self, encryption_state) + + self._show_lock_image(**encryption_state) + def _show_lock_image(self, visible, enc_type='', enc_enabled=False, chat_logged=False, authenticated=False): """ @@ -940,6 +961,8 @@ class ChatControl(ChatControlBase): self.lock_image.set_sensitive(enc_enabled) def _on_authentication_button_clicked(self, widget): + gajim.plugin_manager.gui_extension_point( + 'encryption_dialog' + self.encryption, self) if self.gpg_is_active: dialogs.GPGInfoWindow(self, self.parent_win.window) elif self.session and self.session.enable_encryption: @@ -950,6 +973,14 @@ class ChatControl(ChatControlBase): """ Send a message to contact """ + + if self.encryption: + self.sendmessage = True + gajim.plugin_manager.gui_extension_point( + 'send_message' + self.encryption, self) + if not self.sendmessage: + return + message = helpers.remove_invalid_xml_chars(message) if message in ('', None, '\n'): return None @@ -1481,6 +1512,8 @@ class ChatControl(ChatControlBase): def _on_message_tv_buffer_changed(self, textbuffer): super()._on_message_tv_buffer_changed(textbuffer) if textbuffer.get_char_count(): + gajim.plugin_manager.gui_extension_point( + 'typing' + self.encryption, self) e2e_is_active = self.session and \ self.session.enable_encryption e2e_pref = gajim.config.get_per('accounts', self.account, diff --git a/src/chat_control_base.py b/src/chat_control_base.py index a81533b6a..248d4990f 100644 --- a/src/chat_control_base.py +++ b/src/chat_control_base.py @@ -34,6 +34,7 @@ from gi.repository import Gdk from gi.repository import Pango from gi.repository import GObject from gi.repository import GLib +from gi.repository import Gio import gtkgui_helpers import message_control import dialogs @@ -395,6 +396,10 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): self._on_window_motion_notify) self.handlers[id_] = parent_win.window + self.encryption = 'disabled' + self.set_encryption_state() + self.add_window_actions() + # PluginSystem: adding GUI extension point for ChatControlBase # instance object (also subclasses, eg. ChatControl or GroupchatControl) gajim.plugin_manager.gui_extension_point('chat_control_base', self) @@ -412,6 +417,38 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): # to properly use the super, because of the old code. CommandTools.__init__(self) + def add_window_actions(self): + action = Gio.SimpleAction.new_stateful( + "%s-encryptiongroup" % self.contact.jid, + GLib.VariantType.new("s"), + GLib.Variant("s", self.encryption)) + action.connect("change-state", self.activate_encryption) + self.parent_win.window.add_action(action) + + def activate_encryption(self, action, param): + encryption = param.get_string() + if self.encryption == encryption: + return + + if encryption != 'disabled': + plugin = gajim.plugin_manager.encryption_plugins[encryption] + if not plugin.activate_encryption(self): + return + action.set_state(param) + gajim.config.set_per( + 'contacts', self.contact.jid, 'encryption', encryption) + self.encryption = encryption + self.set_lock_image() + + def set_encryption_state(self): + enc = gajim.config.get_per('contacts', self.contact.jid, 'encryption') + if enc not in gajim.plugin_manager.encryption_plugins: + self.encryption = 'disabled' + gajim.config.set_per( + 'contacts', self.contact.jid, 'encryption', 'disabled') + else: + self.encryption = enc + def set_speller(self): # now set the one the user selected per_type = 'contacts' @@ -723,7 +760,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): keyID=keyID, type_=type_, chatstate=chatstate, msg_id=msg_id, resource=resource, user_nick=self.user_nick, xhtml=xhtml, label=label, callback=_cb, callback_args=[callback] + callback_args, - control=self, attention=attention, correction_msg=correction_msg, automatic_message=False)) + control=self, attention=attention, correction_msg=correction_msg, + automatic_message=False, encryption=self.encryption)) # Record the history of sent messages self.save_message(message, 'sent') diff --git a/src/common/config.py b/src/common/config.py index 3411bbb4c..f0c1aecb0 100644 --- a/src/common/config.py +++ b/src/common/config.py @@ -481,6 +481,7 @@ class Config: }, {}), 'contacts': ({ 'gpg_enabled': [ opt_bool, False, _('Is OpenPGP enabled for this contact?')], + 'encryption': [ opt_str, '', _('Encryption used for this contact.')], 'autonegotiate_esessions': [opt_bool, False, _('Should Gajim automatically start an encrypted session with this contact when possible?')], 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')], }, {}), diff --git a/src/common/connection.py b/src/common/connection.py index efc77bba9..36104e34a 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -499,7 +499,7 @@ class CommonConnection: self._push_stanza_message_outgoing(obj, msg_iq) - def _push_stanza_message_outgoing(self, obj, msg_iq): + def _push_stanza_message_outgoing(self, obj, msg_iq): obj.conn = self if isinstance(msg_iq, list): for iq in msg_iq: @@ -2136,6 +2136,14 @@ class Connection(CommonConnection, ConnectionHandlers): def _nec_stanza_message_outgoing(self, obj): if obj.conn.name != self.name: return + encryption = gajim.config.get_per('contacts', obj.jid, 'encryption') + if encryption != 'disabled': + gajim.plugin_manager.gui_extension_point( + 'encrypt' + encryption, self, obj, self.send_message) + else: + self.send_message(obj) + + def send_message(self, obj): obj.msg_id = self.connection.send(obj.msg_iq, now=obj.now) gajim.nec.push_incoming_event(MessageSentEvent( diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index 7252f9046..2c45ec661 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -1091,6 +1091,12 @@ class ConnectionHandlersBase: def _nec_message_received(self, obj): if obj.conn.name != self.name: return + + gajim.plugin_manager.gui_extension_point( + 'decrypt', self, obj, self._on_message_received) + if not obj.encrypted: + self._on_message_received(obj) + if obj.encrypted == 'xep200': try: obj.stanza = obj.session.decrypt_stanza(obj.stanza) @@ -1153,6 +1159,14 @@ class ConnectionHandlersBase: gajim.nec.push_incoming_event(MamDecryptedMessageReceivedEvent(None, conn=self, msg_obj=obj)) + def _on_message_received(self, obj): + if isinstance(obj, MessageReceivedEvent): + gajim.nec.push_incoming_event( + DecryptedMessageReceivedEvent(None, conn=self, msg_obj=obj)) + else: + gajim.nec.push_incoming_event( + MamDecryptedMessageReceivedEvent(None, conn=self, msg_obj=obj)) + def _nec_decrypted_message_received(self, obj): if obj.conn.name != self.name: return diff --git a/src/common/connection_handlers_events.py b/src/common/connection_handlers_events.py index 286f935fb..f5489600b 100644 --- a/src/common/connection_handlers_events.py +++ b/src/common/connection_handlers_events.py @@ -2796,6 +2796,7 @@ class MessageOutgoingEvent(nec.NetworkOutgoingEvent): self.attention = False self.correction_msg = None self.automatic_message = True + self.encryption = '' def get_full_jid(self): if self.resource: diff --git a/src/gui_menu_builder.py b/src/gui_menu_builder.py index 835b78578..6db2a8bb1 100644 --- a/src/gui_menu_builder.py +++ b/src/gui_menu_builder.py @@ -781,3 +781,14 @@ def build_bookmark_menu(account): label = menu.get_item_attribute_value(1, 'label').get_string() menu.remove(1) menu.insert_submenu(1, label, bookmark_menu) + + +def get_encryption_menu(contact): + menu = Gio.Menu() + menu.append( + 'Disabled', 'win.{}-encryptiongroup::{}'.format(contact.jid, 'disabled')) + for encryption in gajim.plugin_manager.encryption_plugins: + menu_action = 'win.{}-encryptiongroup::{}'.format( + contact.jid, encryption) + menu.append(encryption, menu_action) + return menu diff --git a/src/plugins/gajimplugin.py b/src/plugins/gajimplugin.py index 2a78e4977..18a8a75b0 100644 --- a/src/plugins/gajimplugin.py +++ b/src/plugins/gajimplugin.py @@ -58,6 +58,16 @@ class GajimPlugin(object): :todo: decide whether we really need this one, because class name (with module name) can act as such short name + ''' + encryption_name = '' + ''' + Name of the encryption scheme. + + The name that Gajim displays in the encryption menu. + Leave empty if the plugin is not an encryption plugin. + + :type: str + ''' version = '' ''' diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py index eeeafcb04..dc7e867eb 100644 --- a/src/plugins/pluginmanager.py +++ b/src/plugins/pluginmanager.py @@ -101,6 +101,12 @@ class PluginManager(metaclass=Singleton): ''' Registered handlers of GUI extension points. ''' + + self.encryption_plugins = {} + ''' + Registered names with instances of encryption Plugins. + ''' + for path in [gajim.PLUGINS_DIRS[1], gajim.PLUGINS_DIRS[0]]: pc = PluginManager.scan_dir_for_plugins(path) self.add_plugins(pc) @@ -291,6 +297,10 @@ class PluginManager(metaclass=Singleton): elif issubclass(event_class, nec.NetworkOutgoingEvent): gajim.nec.unregister_outgoing_event(event_class) + def _remove_name_from_encryption_plugins(self, plugin): + if plugin.encryption_name: + del self.encryption_plugins[plugin.encryption_name] + @log_calls('PluginManager') def activate_plugin(self, plugin): ''' @@ -300,6 +310,7 @@ class PluginManager(metaclass=Singleton): if not plugin.active and plugin.activatable: self._add_gui_extension_points_handlers_from_plugin(plugin) + self._add_encryption_name_from_plugin(plugin) self._handle_all_gui_extension_points_with_plugin(plugin) self._register_events_handlers_in_ged(plugin) self._register_network_events_in_nec(plugin) @@ -339,6 +350,7 @@ class PluginManager(metaclass=Singleton): self._remove_events_handler_from_ged(plugin) self._remove_network_events_from_nec(plugin) + self._remove_name_from_encryption_plugins(plugin) # removing plug-in from active plug-ins list plugin.deactivate() @@ -357,6 +369,10 @@ class PluginManager(metaclass=Singleton): self.gui_extension_points_handlers.setdefault(gui_extpoint_name, []).append(gui_extpoint_handlers) + def _add_encryption_name_from_plugin(self, plugin): + if plugin.encryption_name: + self.encryption_plugins[plugin.encryption_name] = plugin + @log_calls('PluginManager') def _handle_all_gui_extension_points_with_plugin(self, plugin): for gui_extpoint_name, gui_extpoint_handlers in \ From 3fd35a041e40c27d4002866933cb02e7d892b1a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Sun, 16 Apr 2017 17:16:41 +0200 Subject: [PATCH 2/5] Remove PGP encryption/decryption from core --- data/gui/contact_context_menu.ui | 8 -- src/chat_control.py | 115 ++---------------- src/command_system/implementation/standard.py | 5 - src/common/config.py | 1 - src/common/connection.py | 83 ++----------- src/common/connection_handlers.py | 65 +--------- src/common/connection_handlers_events.py | 54 ++++---- src/dialogs.py | 58 --------- src/groupchat_control.py | 5 +- src/gui_menu_builder.py | 18 +-- 10 files changed, 52 insertions(+), 360 deletions(-) diff --git a/data/gui/contact_context_menu.ui b/data/gui/contact_context_menu.ui index 85f1c0387..0ef415bea 100644 --- a/data/gui/contact_context_menu.ui +++ b/data/gui/contact_context_menu.ui @@ -51,14 +51,6 @@ False - - - True - False - Toggle Open_PGP Encryption - True - - True diff --git a/src/chat_control.py b/src/chat_control.py index 0fb0706b2..07ff8adba 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -90,7 +90,6 @@ class ChatControl(ChatControlBase): ChatControlBase.__init__(self, self.TYPE_ID, parent_win, 'chat_control', contact, acct, resource) - self.gpg_is_active = False self.last_recv_message_id = None self.last_recv_message_marks = None self.last_message_timestamp = None @@ -282,25 +281,6 @@ class ChatControl(ChatControlBase): # Enable encryption if needed self.no_autonegotiation = False e2e_is_active = self.session and self.session.enable_encryption - gpg_pref = gajim.config.get_per('contacts', contact.jid, 'gpg_enabled') - - # try GPG first - if not e2e_is_active and gpg_pref and \ - gajim.config.get_per('accounts', self.account, 'keyid') and \ - gajim.connections[self.account].USE_GPG: - self.gpg_is_active = True - gajim.encrypted_chats[self.account].append(contact.jid) - msg = _('OpenPGP encryption enabled') - ChatControlBase.print_conversation_line(self, msg, 'status', '', - None) - - if self.session: - self.session.loggable = gajim.config.get_per('accounts', - self.account, 'log_encrypted_sessions') - # GPG is always authenticated as we use GPG's WoT - self._show_lock_image(self.gpg_is_active, 'OpenPGP', - self.gpg_is_active, self.session and self.session.is_loggable(), - True) self.update_ui() self.set_lock_image() @@ -350,7 +330,8 @@ class ChatControl(ChatControlBase): send_button = self.xml.get_object('send_button') send_button.set_sensitive(True) # Formatting - if self.contact.supports(NS_XHTML_IM) and not self.gpg_is_active: + # TODO: find out what encryption allows for xhtml and which not + if self.contact.supports(NS_XHTML_IM): self._formattings_button.set_sensitive(True) self._formattings_button.set_tooltip_text(_( 'Show a list of formattings')) @@ -862,57 +843,6 @@ class ChatControl(ChatControlBase): def on_video_button_toggled(self, widget): self.on_jingle_button_toggled(widget, 'video') - def _toggle_gpg(self): - if not self.gpg_is_active and not self.contact.keyID: - dialogs.ErrorDialog(_('No OpenPGP key assigned'), - _('No OpenPGP key is assigned to this contact. So you cannot ' - 'encrypt messages with OpenPGP.')) - return - ec = gajim.encrypted_chats[self.account] - if self.gpg_is_active: - # Disable encryption - ec.remove(self.contact.jid) - self.gpg_is_active = False - loggable = False - msg = _('OpenPGP encryption disabled') - ChatControlBase.print_conversation_line(self, msg, 'status', '', - None) - if self.session: - self.session.loggable = True - - else: - # Enable encryption - ec.append(self.contact.jid) - self.gpg_is_active = True - msg = _('OpenPGP encryption enabled') - ChatControlBase.print_conversation_line(self, msg, 'status', '', - None) - - loggable = gajim.config.get_per('accounts', self.account, - 'log_encrypted_sessions') - - if self.session: - self.session.loggable = loggable - - loggable = self.session.is_loggable() - else: - loggable = loggable and gajim.config.should_log(self.account, - self.contact.jid) - - if loggable: - msg = _('Session WILL be logged') - else: - msg = _('Session WILL NOT be logged') - - ChatControlBase.print_conversation_line(self, msg, - 'status', '', None) - - gajim.config.set_per('contacts', self.contact.jid, - 'gpg_enabled', self.gpg_is_active) - - self._show_lock_image(self.gpg_is_active, 'OpenPGP', - self.gpg_is_active, loggable, True) - def set_lock_image(self): visible = self.encryption != 'disabled' loggable = self.session and self.session.is_loggable() @@ -963,9 +893,7 @@ class ChatControl(ChatControlBase): def _on_authentication_button_clicked(self, widget): gajim.plugin_manager.gui_extension_point( 'encryption_dialog' + self.encryption, self) - if self.gpg_is_active: - dialogs.GPGInfoWindow(self, self.parent_win.window) - elif self.session and self.session.enable_encryption: + if self.session and self.session.enable_encryption: dialogs.ESessionInfoWindow(self.session, self.parent_win.window) def send_message(self, message, keyID='', chatstate=None, xhtml=None, @@ -989,12 +917,11 @@ class ChatControl(ChatControlBase): encrypted = bool(self.session) and self.session.enable_encryption - keyID = '' - if self.gpg_is_active: - keyID = contact.keyID - encrypted = True - if not keyID: - keyID = 'UNKNOWN' + keyID = contact.keyID + encrypted = True + + chatstates_on = gajim.config.get('outgoing_chat_state_notifications') != \ + 'disabled' chatstate_to_send = None if contact is not None: @@ -1123,20 +1050,6 @@ class ChatControl(ChatControlBase): msg = _('The following message was NOT encrypted') ChatControlBase.print_conversation_line(self, msg, 'status', '', tim) - else: - # GPG encryption - if encrypted and not self.gpg_is_active: - msg = _('The following message was encrypted') - ChatControlBase.print_conversation_line(self, msg, 'status', - '', tim) - # turn on OpenPGP if this was in fact a XEP-0027 encrypted - # message - if encrypted == 'xep27': - self._toggle_gpg() - elif not encrypted and self.gpg_is_active: - msg = _('The following message was NOT encrypted') - ChatControlBase.print_conversation_line(self, msg, 'status', - '', tim) if not frm: kind = 'incoming' name = contact.get_shown_name() @@ -1146,7 +1059,7 @@ class ChatControl(ChatControlBase): else: kind = 'outgoing' name = self.get_our_nick() - if not xhtml and not (encrypted and self.gpg_is_active) and \ + if not xhtml and not encrypted and \ gajim.config.get('rst_formatting_outgoing_messages'): from common.rst_xhtml_generator import create_xhtml xhtml = create_xhtml(text) @@ -1223,9 +1136,8 @@ class ChatControl(ChatControlBase): def prepare_context_menu(self, hide_buttonbar_items=False): """ Set compact view menuitem active state sets active and sensitivity state - for toggle_gpg_menuitem sets sensitivity for history_menuitem (False for - tranasports) and file_transfer_menuitem and hide()/show() for - add_to_roster_menuitem + for history_menuitem (False for tranasports) and file_transfer_menuitem + and hide()/show() for add_to_roster_menuitem """ if gajim.jid_is_transport(self.contact.jid): menu = gui_menu_builder.get_transport_menu(self.contact, @@ -1520,7 +1432,7 @@ class ChatControl(ChatControlBase): 'enable_esessions') and gajim.config.get_per('accounts', self.account, 'autonegotiate_esessions') and gajim.config.get_per( 'contacts', self.contact.jid, 'autonegotiate_esessions') - want_e2e = not e2e_is_active and not self.gpg_is_active \ + want_e2e = not e2e_is_active and self.encryption == '' \ and e2e_pref if want_e2e and not self.no_autonegotiation \ @@ -1717,9 +1629,6 @@ class ChatControl(ChatControlBase): def _on_contact_information_menuitem_activate(self, widget): gajim.interface.roster.on_info(widget, self.contact, self.account) - def _on_toggle_gpg_menuitem_activate(self, widget): - self._toggle_gpg() - def _on_convert_to_gc_menuitem_activate(self, widget): """ User wants to invite some friends to chat diff --git a/src/command_system/implementation/standard.py b/src/command_system/implementation/standard.py index f7e1daa91..4caba25d7 100644 --- a/src/command_system/implementation/standard.py +++ b/src/command_system/implementation/standard.py @@ -179,11 +179,6 @@ class StandardCommonChatCommands(CommandContainer): def clear(self): self.conv_textview.clear() - @command - @doc(_("Toggle the OpenPGP encryption")) - def gpg(self): - self._toggle_gpg() - @command @doc(_("Send a ping to the contact")) def ping(self): diff --git a/src/common/config.py b/src/common/config.py index f0c1aecb0..b1c096d8a 100644 --- a/src/common/config.py +++ b/src/common/config.py @@ -480,7 +480,6 @@ class Config: 'state_muc_directed_msg_color': [ opt_color, 'red2' ], }, {}), 'contacts': ({ - 'gpg_enabled': [ opt_bool, False, _('Is OpenPGP enabled for this contact?')], 'encryption': [ opt_str, '', _('Encryption used for this contact.')], 'autonegotiate_esessions': [opt_bool, False, _('Should Gajim automatically start an encrypted session with this contact when possible?')], 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')], diff --git a/src/common/connection.py b/src/common/connection.py index 36104e34a..e9f1ab2bb 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -296,65 +296,9 @@ class CommonConnection: if not obj.message and obj.chatstate is None and obj.form_node is None: return - if obj.keyID and self.USE_GPG: - self._encrypt_message(obj) - return - self._build_message_stanza(obj) - def _encrypt_message(self, obj): - obj.xhtml = None - if obj.keyID == 'UNKNOWN': - error = _('Neither the remote presence is signed, nor a key was ' - 'assigned.') - elif obj.keyID.endswith('MISMATCH'): - error = _('The contact\'s key (%s) does not match the key assigned ' - 'in Gajim.' % obj.keyID[:8]) - else: - myKeyID = gajim.config.get_per('accounts', self.name, 'keyid') - key_list = [obj.keyID, myKeyID] - def _on_encrypted(output): - msgenc, error = output - if error.startswith('NOT_TRUSTED'): - def _on_always_trust(answer): - if answer: - gajim.thread_interface( - self.gpg.encrypt, [obj.message, key_list, True], - _on_encrypted, []) - else: - self._finished_encrypt(obj, msgenc=msgenc, - error=error) - gajim.nec.push_incoming_event(GPGTrustKeyEvent(None, - conn=self, keyID=error.split(' ')[-1], - callback=_on_always_trust)) - else: - self._finished_encrypt(obj, msgenc=msgenc, error=error) - gajim.thread_interface( - self.gpg.encrypt, [obj.message, key_list, False], - _on_encrypted, []) - return - self._finished_encrypt(obj, error=error) - - def _finished_encrypt(self, obj, msgenc=None, error=None): - if error: - gajim.nec.push_incoming_event( - MessageNotSentEvent( - None, conn=self, jid=obj.jid, message=obj.message, - error=error, time_=time.time(), session=obj.session)) - return - self._build_message_stanza(obj, msgenc) - - def _build_message_stanza(self, obj, msgenc=None): - if msgenc: - msgtxt = '[This message is *encrypted* (See :XEP:`27`]' - lang = os.getenv('LANG') - if lang is not None and not lang.startswith('en'): - # we're not english: one in locale and one en - msgtxt = _('[This message is *encrypted* (See :XEP:`27`]') + \ - ' (' + msgtxt + ')' - else: - msgtxt = obj.message - + def _build_message_stanza(self, obj): if obj.jid == gajim.get_jid_from_account(self.name): fjid = obj.jid else: @@ -368,17 +312,10 @@ class CommonConnection: namespace=nbxmpp.NS_CORRECT) id2 = self.connection.getAnID() obj.correction_msg.setID(id2) - obj.correction_msg.setBody(msgtxt) + obj.correction_msg.setBody(obj.message) if obj.xhtml: obj.correction_msg.setXHTML(obj.xhtml) - if msgenc: - encrypted_tag = obj.correction_msg.getTag( - 'x', namespace=nbxmpp.NS_ENCRYPTED) - obj.correction_msg.delChild(encrypted_tag) - obj.correction_msg.setTag( - 'x', namespace=nbxmpp.NS_ENCRYPTED).setData(msgenc) - if obj.session: obj.session.last_send = time.time() @@ -390,22 +327,19 @@ class CommonConnection: return if obj.type_ == 'chat': - msg_iq = nbxmpp.Message(body=msgtxt, typ=obj.type_, + msg_iq = nbxmpp.Message(body=obj.message, typ=obj.type_, xhtml=obj.xhtml) else: if obj.subject: - msg_iq = nbxmpp.Message(body=msgtxt, typ='normal', + msg_iq = nbxmpp.Message(body=obj.message, typ='normal', subject=obj.subject, xhtml=obj.xhtml) else: - msg_iq = nbxmpp.Message(body=msgtxt, typ='normal', + msg_iq = nbxmpp.Message(body=obj.message, typ='normal', xhtml=obj.xhtml) if obj.msg_id: msg_iq.setID(obj.msg_id) - if msgenc: - msg_iq.setTag('x', namespace=nbxmpp.NS_ENCRYPTED).setData(msgenc) - if obj.form_node: msg_iq.addChild(node=obj.form_node) if obj.label: @@ -460,7 +394,7 @@ class CommonConnection: if obj.chatstate and contact and contact.supports(nbxmpp.NS_CHATSTATES): msg_iq.setTag(obj.chatstate, namespace=nbxmpp.NS_CHATSTATES) only_chatste = False - if not msgtxt: + if not obj.message: only_chatste = True if only_chatste and not obj.session.enable_encryption: msg_iq.setTag('no-store', @@ -468,8 +402,9 @@ class CommonConnection: # XEP-0184 if obj.jid != gajim.get_jid_from_account(self.name): - if msgtxt and gajim.config.get_per('accounts', self.name, - 'request_receipt'): + request = gajim.config.get_per('accounts', self.name, + 'request_receipt') + if obj.message and request: msg_iq.setTag('request', namespace=nbxmpp.NS_RECEIPTS) if obj.forward_from: diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index 2c45ec661..ec25feaf9 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -897,7 +897,7 @@ class ConnectionHandlersBase: gajim.ged.register_event_handler('message-received', ged.CORE, self._nec_message_received) gajim.ged.register_event_handler('mam-message-received', ged.CORE, - self._nec_mam_message_received) + self._nec_message_received) gajim.ged.register_event_handler('decrypted-message-received', ged.CORE, self._nec_decrypted_message_received) @@ -911,7 +911,7 @@ class ConnectionHandlersBase: gajim.ged.remove_event_handler('message-received', ged.CORE, self._nec_message_received) gajim.ged.remove_event_handler('mam-message-received', ged.CORE, - self._nec_mam_message_received) + self._nec_message_received) gajim.ged.remove_event_handler('decrypted-message-received', ged.CORE, self._nec_decrypted_message_received) @@ -1080,14 +1080,6 @@ class ConnectionHandlersBase: if sess.enable_encryption: sess.terminate_e2e() - def decrypt_thread(self, encmsg, keyID, obj): - decmsg = self.gpg.decrypt(encmsg, keyID) - decmsg = self.connection.Dispatcher.replace_non_character(decmsg) - # \x00 chars are not allowed in C (so in GTK) - obj.msgtxt = decmsg.replace('\x00', '') - obj.encrypted = 'xep27' - self.gpg_messages_to_decrypt.remove([encmsg, keyID, obj]) - def _nec_message_received(self, obj): if obj.conn.name != self.name: return @@ -1106,59 +1098,6 @@ class ConnectionHandlersBase: conn=self, msg_obj=obj)) return - if obj.enc_tag and self.USE_GPG: - encmsg = obj.enc_tag.getData() - - keyID = gajim.config.get_per('accounts', self.name, 'keyid') - if keyID: - self.gpg_messages_to_decrypt.append([encmsg, keyID, obj]) - if len(self.gpg_messages_to_decrypt) == 1: - gajim.thread_interface(self.decrypt_thread, [encmsg, keyID, - obj], self._on_message_decrypted, [obj]) - return - gajim.nec.push_incoming_event(DecryptedMessageReceivedEvent(None, - conn=self, msg_obj=obj)) - - def _nec_mam_message_received(self, obj): - if obj.conn.name != self.name: - return - if obj.enc_tag and self.USE_GPG: - encmsg = obj.enc_tag.getData() - - keyID = gajim.config.get_per('accounts', self.name, 'keyid') - if keyID: - self.gpg_messages_to_decrypt.append([encmsg, keyID, obj]) - if len(self.gpg_messages_to_decrypt) == 1: - gajim.thread_interface(self.decrypt_thread, [encmsg, keyID, - obj], self._on_mam_message_decrypted, [obj]) - return - gajim.nec.push_incoming_event(MamDecryptedMessageReceivedEvent(None, - conn=self, msg_obj=obj)) - - def _on_message_decrypted(self, output, obj): - if len(self.gpg_messages_to_decrypt): - encmsg, keyID, obj2 = self.gpg_messages_to_decrypt[0] - if type(obj2) == MessageReceivedEvent: - cb = self._on_message_decrypted - else: - cb = self._on_mam_message_decrypted - gajim.thread_interface(self.decrypt_thread, [encmsg, keyID, obj2], - cb, [obj2]) - gajim.nec.push_incoming_event(DecryptedMessageReceivedEvent(None, - conn=self, msg_obj=obj)) - - def _on_mam_message_decrypted(self, output, obj): - if len(self.gpg_messages_to_decrypt): - encmsg, keyID, obj2 = self.gpg_messages_to_decrypt[0] - if type(obj2) == MessageReceivedEvent: - cb = self._on_message_decrypted - else: - cb = self._on_mam_message_decrypted - gajim.thread_interface(self.decrypt_thread, [encmsg, keyID, obj2], - cb, [obj2]) - gajim.nec.push_incoming_event(MamDecryptedMessageReceivedEvent(None, - conn=self, msg_obj=obj)) - def _on_message_received(self, obj): if isinstance(obj, MessageReceivedEvent): gajim.nec.push_incoming_event( diff --git a/src/common/connection_handlers_events.py b/src/common/connection_handlers_events.py index f5489600b..c30823ccc 100644 --- a/src/common/connection_handlers_events.py +++ b/src/common/connection_handlers_events.py @@ -1035,6 +1035,7 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): def init(self): self.additional_data = {} + self.encrypted = False def generate(self): if not self.stanza: @@ -1067,7 +1068,6 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): self.with_ = to_ self.direction = 'to' self.resource = gajim.get_resource_from_jid(self.msg_.getAttr('to')) - self.enc_tag = self.msg_.getTag('x', namespace=nbxmpp.NS_ENCRYPTED) return True class MamDecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): @@ -1216,33 +1216,31 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): conn=self.conn, stanza=forwarded)) return - self.enc_tag = self.stanza.getTag('x', namespace=nbxmpp.NS_ENCRYPTED) - if not self.enc_tag: - # Mediated invitation? - muc_user = self.stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER) - if muc_user: - if muc_user.getTag('decline'): - gajim.nec.push_incoming_event( - GcDeclineReceivedEvent( - None, conn=self.conn, - room_jid=self.fjid, stanza=muc_user)) - return - if muc_user.getTag('invite'): - gajim.nec.push_incoming_event( - GcInvitationReceivedEvent( - None, conn=self.conn, jid_from=self.fjid, - mediated=True, stanza=muc_user)) - return - else: - # Direct invitation? - direct = self.stanza.getTag( - 'x', namespace=nbxmpp.NS_CONFERENCE) - if direct: - gajim.nec.push_incoming_event( - GcInvitationReceivedEvent( - None, conn=self.conn, jid_from=self.fjid, - mediated=False, stanza=direct)) - return + # Mediated invitation? + muc_user = self.stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER) + if muc_user: + if muc_user.getTag('decline'): + gajim.nec.push_incoming_event( + GcDeclineReceivedEvent( + None, conn=self.conn, + room_jid=self.fjid, stanza=muc_user)) + return + if muc_user.getTag('invite'): + gajim.nec.push_incoming_event( + GcInvitationReceivedEvent( + None, conn=self.conn, jid_from=self.fjid, + mediated=True, stanza=muc_user)) + return + else: + # Direct invitation? + direct = self.stanza.getTag( + 'x', namespace=nbxmpp.NS_CONFERENCE) + if direct: + gajim.nec.push_incoming_event( + GcInvitationReceivedEvent( + None, conn=self.conn, jid_from=self.fjid, + mediated=False, stanza=direct)) + return self.thread_id = self.stanza.getThread() self.mtype = self.stanza.getType() diff --git a/src/dialogs.py b/src/dialogs.py index 549bb05e2..bfe84ba30 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -5391,64 +5391,6 @@ class ESessionInfoWindow: YesNoDialog(pritext, sectext, on_response_yes=on_yes, on_response_no=on_no, transient_for=self.window) -class GPGInfoWindow: - """ - Class for displaying information about a XEP-0116 encrypted session - """ - def __init__(self, control, transient_for=None): - xml = gtkgui_helpers.get_gtk_builder('esession_info_window.ui') - security_image = xml.get_object('security_image') - status_label = xml.get_object('verification_status_label') - info_label = xml.get_object('info_display') - verify_now_button = xml.get_object('verify_now_button') - self.window = xml.get_object('esession_info_window') - account = control.account - keyID = control.contact.keyID - error = None - - verify_now_button.set_no_show_all(True) - verify_now_button.hide() - - if keyID.endswith('MISMATCH'): - verification_status = _('''Contact's identity NOT verified''') - info = _('The contact\'s key (%s) does not match the key ' - 'assigned in Gajim.') % keyID[:8] - image = 'security-low' - elif not keyID: - # No key assigned nor a key is used by remote contact - verification_status = _('No OpenPGP key assigned') - info = _('No OpenPGP key is assigned to this contact. So you cannot' - ' encrypt messages.') - image = 'security-low' - else: - error = gajim.connections[account].gpg.encrypt('test', [keyID])[1] - if error: - verification_status = _('''Contact's identity NOT verified''') - info = _('OpenPGP key is assigned to this contact, but you ' - 'do not trust their key, so message cannot be ' - 'encrypted. Use your OpenPGP client to trust their key.') - image = 'security-low' - else: - verification_status = _('''Contact's identity verified''') - info = _('OpenPGP Key is assigned to this contact, and you ' - 'trust their key, so messages will be encrypted.') - image = 'security-high' - - status_label.set_markup('%s' % \ - verification_status) - info_label.set_markup(info) - - path = gtkgui_helpers.get_icon_path(image, 32) - security_image.set_from_file(path) - - self.window.set_transient_for(transient_for) - xml.connect_signals(self) - self.window.show_all() - - def on_close_button_clicked(self, widget): - self.window.destroy() - - class ResourceConflictDialog(TimeoutDialog, InputDialog): def __init__(self, title, text, resource, ok_handler): diff --git a/src/groupchat_control.py b/src/groupchat_control.py index facef7ece..7c62977de 100644 --- a/src/groupchat_control.py +++ b/src/groupchat_control.py @@ -257,9 +257,8 @@ class PrivateChatControl(ChatControl): def prepare_context_menu(self, hide_buttonbar_items=False): """ Set compact view menuitem active state sets active and sensitivity state - for toggle_gpg_menuitem sets sensitivity for history_menuitem (False for - tranasports) and file_transfer_menuitem and hide()/show() for - add_to_roster_menuitem + for history_menuitem (False for tranasports) and file_transfer_menuitem + and hide()/show() for add_to_roster_menuitem """ menu = gui_menu_builder.get_contact_menu(self.contact, self.account, use_multiple_contacts=False, show_start_chat=False, diff --git a/src/gui_menu_builder.py b/src/gui_menu_builder.py index 6db2a8bb1..f61ae68bd 100644 --- a/src/gui_menu_builder.py +++ b/src/gui_menu_builder.py @@ -246,7 +246,6 @@ control=None, gc_contact=None, is_anonymous=True): manage_contact_menuitem = xml.get_object('manage_contact') convert_to_gc_menuitem = xml.get_object('convert_to_groupchat_menuitem') encryption_separator = xml.get_object('encryption_separator') - toggle_gpg_menuitem = xml.get_object('toggle_gpg_menuitem') toggle_e2e_menuitem = xml.get_object('toggle_e2e_menuitem') last_separator = xml.get_object('last_separator') @@ -323,32 +322,17 @@ control=None, gc_contact=None, is_anonymous=True): items_to_hide.append(start_chat_menuitem) if not show_encryption or not control: - items_to_hide += [encryption_separator, toggle_gpg_menuitem, - toggle_e2e_menuitem] + items_to_hide += [encryption_separator, toggle_e2e_menuitem] else: e2e_is_active = control.session is not None and \ control.session.enable_encryption - # check if we support and use gpg - if not gajim.config.get_per('accounts', account, 'keyid') or \ - not gajim.connections[account].USE_GPG or gajim.jid_is_transport( - contact.jid): - toggle_gpg_menuitem.set_sensitive(False) - else: - toggle_gpg_menuitem.set_sensitive(control.gpg_is_active or \ - not e2e_is_active) - toggle_gpg_menuitem.set_active(control.gpg_is_active) - toggle_gpg_menuitem.connect('activate', - control._on_toggle_gpg_menuitem_activate) - # disable esessions if we or the other client don't support them if not gajim.HAVE_PYCRYPTO or not contact.supports(NS_ESESSION) or \ not gajim.config.get_per('accounts', account, 'enable_esessions'): toggle_e2e_menuitem.set_sensitive(False) else: toggle_e2e_menuitem.set_active(e2e_is_active) - toggle_e2e_menuitem.set_sensitive(e2e_is_active or \ - not control.gpg_is_active) toggle_e2e_menuitem.connect('activate', control._on_toggle_e2e_menuitem_activate) From cf07022cbd6057b5c5c75048e2102dc0db138659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Mon, 17 Apr 2017 19:58:41 +0200 Subject: [PATCH 3/5] Remove ESessions encryption/decryption from core --- data/gui/contact_context_menu.ui | 14 ----- src/chat_control.py | 52 ++++++---------- src/chat_control_base.py | 2 + src/common/config.py | 1 - src/common/connection.py | 18 ------ src/common/connection_handlers.py | 9 --- src/common/connection_handlers_events.py | 45 +------------- src/dialogs.py | 76 ------------------------ src/gui_menu_builder.py | 17 ------ 9 files changed, 20 insertions(+), 214 deletions(-) diff --git a/data/gui/contact_context_menu.ui b/data/gui/contact_context_menu.ui index 0ef415bea..98d846d40 100644 --- a/data/gui/contact_context_menu.ui +++ b/data/gui/contact_context_menu.ui @@ -45,20 +45,6 @@ True - - - True - False - - - - - True - False - Toggle End to End Encryption - True - - True diff --git a/src/chat_control.py b/src/chat_control.py index 07ff8adba..b1beb22f7 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -280,7 +280,6 @@ class ChatControl(ChatControlBase): # Enable encryption if needed self.no_autonegotiation = False - e2e_is_active = self.session and self.session.enable_encryption self.update_ui() self.set_lock_image() @@ -893,8 +892,6 @@ class ChatControl(ChatControlBase): def _on_authentication_button_clicked(self, widget): gajim.plugin_manager.gui_extension_point( 'encryption_dialog' + self.encryption, self) - if self.session and self.session.enable_encryption: - dialogs.ESessionInfoWindow(self.session, self.parent_win.window) def send_message(self, message, keyID='', chatstate=None, xhtml=None, process_commands=True, attention=False): @@ -914,11 +911,7 @@ class ChatControl(ChatControlBase): return None contact = self.contact - - encrypted = bool(self.session) and self.session.enable_encryption - keyID = contact.keyID - encrypted = True chatstates_on = gajim.config.get('outgoing_chat_state_notifications') != \ 'disabled' @@ -957,7 +950,7 @@ class ChatControl(ChatControlBase): ChatControlBase.send_message(self, message, keyID, type_='chat', chatstate=chatstate_to_send, xhtml=xhtml, callback=_on_sent, - callback_args=[message, encrypted, xhtml, self.get_seclabel()], + callback_args=[message, self.encryption, xhtml, self.get_seclabel()], process_commands=process_commands, attention=attention) @@ -1426,19 +1419,7 @@ class ChatControl(ChatControlBase): if textbuffer.get_char_count(): gajim.plugin_manager.gui_extension_point( 'typing' + self.encryption, self) - e2e_is_active = self.session and \ - self.session.enable_encryption - e2e_pref = gajim.config.get_per('accounts', self.account, - 'enable_esessions') and gajim.config.get_per('accounts', - self.account, 'autonegotiate_esessions') and gajim.config.get_per( - 'contacts', self.contact.jid, 'autonegotiate_esessions') - want_e2e = not e2e_is_active and self.encryption == '' \ - and e2e_pref - - if want_e2e and not self.no_autonegotiation \ - and gajim.HAVE_PYCRYPTO and self.contact.supports(NS_ESESSION): - self.begin_e2e_negotiation() - elif (not self.session or not self.session.status) and \ + if (not self.session or not self.session.status) and \ gajim.connections[self.account].archiving_136_supported: self.begin_archiving_negotiation() @@ -1635,22 +1616,23 @@ class ChatControl(ChatControlBase): """ dialogs.TransformChatToMUC(self.account, [self.contact.jid]) - def _on_toggle_e2e_menuitem_activate(self, widget): - if self.session and self.session.enable_encryption: - # e2e was enabled, disable it - jid = str(self.session.jid) - thread_id = self.session.thread_id - - self.session.terminate_e2e() - - gajim.connections[self.account].delete_session(jid, thread_id) - - # presumably the user had a good reason to shut it off, so - # disable autonegotiation too - self.no_autonegotiation = True - else: + def activate_esessions(self): + if not (self.session and self.session.enable_encryption): self.begin_e2e_negotiation() + def terminate_esessions(self): + # e2e was enabled, disable it + jid = str(self.session.jid) + thread_id = self.session.thread_id + + self.session.terminate_e2e() + + gajim.connections[self.account].delete_session(jid, thread_id) + + # presumably the user had a good reason to shut it off, so + # disable autonegotiation too + self.no_autonegotiation = True + def begin_negotiation(self): self.no_autonegotiation = True diff --git a/src/chat_control_base.py b/src/chat_control_base.py index 248d4990f..3b6cde993 100644 --- a/src/chat_control_base.py +++ b/src/chat_control_base.py @@ -434,6 +434,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): plugin = gajim.plugin_manager.encryption_plugins[encryption] if not plugin.activate_encryption(self): return + else: + self.terminate_esessions() action.set_state(param) gajim.config.set_per( 'contacts', self.contact.jid, 'encryption', encryption) diff --git a/src/common/config.py b/src/common/config.py index b1c096d8a..9c8585fed 100644 --- a/src/common/config.py +++ b/src/common/config.py @@ -481,7 +481,6 @@ class Config: }, {}), 'contacts': ({ 'encryption': [ opt_str, '', _('Encryption used for this contact.')], - 'autonegotiate_esessions': [opt_bool, False, _('Should Gajim automatically start an encrypted session with this contact when possible?')], 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')], }, {}), 'rooms': ({ diff --git a/src/common/connection.py b/src/common/connection.py index e9f1ab2bb..baf18bc45 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -319,10 +319,6 @@ class CommonConnection: if obj.session: obj.session.last_send = time.time() - # XEP-0200 - if obj.session.enable_encryption: - obj.correction_msg = obj.session.encrypt_stanza(obj.correction_msg) - self._push_stanza_message_outgoing(obj, obj.correction_msg) return @@ -418,20 +414,6 @@ class CommonConnection: obj.session.last_send = time.time() msg_iq.setThread(obj.session.thread_id) - # XEP-0200 - if obj.session.enable_encryption: - msg_iq = obj.session.encrypt_stanza(msg_iq) - if self.carbons_enabled: - msg_iq.addChild(name='private', - namespace=nbxmpp.NS_CARBONS) - msg_iq.addChild(name='no-permanent-store', - namespace=nbxmpp.NS_MSG_HINTS) - msg_iq.addChild(name='no-copy', - namespace=nbxmpp.NS_MSG_HINTS) - if only_chatste: - msg_iq.addChild(name='no-store', - namespace=nbxmpp.NS_MSG_HINTS) - self._push_stanza_message_outgoing(obj, msg_iq) def _push_stanza_message_outgoing(self, obj, msg_iq): diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index ec25feaf9..80f4cf19d 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -1089,15 +1089,6 @@ class ConnectionHandlersBase: if not obj.encrypted: self._on_message_received(obj) - if obj.encrypted == 'xep200': - try: - obj.stanza = obj.session.decrypt_stanza(obj.stanza) - obj.msgtxt = obj.stanza.getBody() - except Exception: - gajim.nec.push_incoming_event(FailedDecryptEvent(None, - conn=self, msg_obj=obj)) - return - def _on_message_received(self, obj): if isinstance(obj, MessageReceivedEvent): gajim.nec.push_incoming_event( diff --git a/src/common/connection_handlers_events.py b/src/common/connection_handlers_events.py index c30823ccc..ed2a9dd41 100644 --- a/src/common/connection_handlers_events.py +++ b/src/common/connection_handlers_events.py @@ -1122,6 +1122,7 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): self.get_id() self.forwarded = False self.sent = False + self.encrypted = False account = self.conn.name our_full_jid = gajim.get_jid_from_account(account, full=True) @@ -1281,52 +1282,8 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): self.session.last_receive = time_time() - # check if the message is a XEP-0020 feature negotiation request - if not self.forwarded and self.stanza.getTag('feature', - namespace=nbxmpp.NS_FEATURE): - if gajim.HAVE_PYCRYPTO: - feature = self.stanza.getTag(name='feature', - namespace=nbxmpp.NS_FEATURE) - form = nbxmpp.DataForm(node=feature.getTag('x')) - if not form: - return - - if not form.getField('FORM_TYPE'): - return - - if form['FORM_TYPE'] == 'urn:xmpp:ssn': - self.session.handle_negotiation(form) - else: - reply = self.stanza.buildReply() - reply.setType('error') - reply.addChild(feature) - err = nbxmpp.ErrorNode('service-unavailable', typ='cancel') - reply.addChild(node=err) - self.conn.connection.send(reply) - return - - if not self.forwarded and self.stanza.getTag('init', - namespace=nbxmpp.NS_ESESSION_INIT): - init = self.stanza.getTag(name='init', - namespace=nbxmpp.NS_ESESSION_INIT) - form = nbxmpp.DataForm(node=init.getTag('x')) - - self.session.handle_negotiation(form) - - return - self._generate_timestamp(self.stanza.getTimestamp()) - - self.encrypted = False - xep_200_encrypted = self.stanza.getTag('c', - namespace=nbxmpp.NS_STANZA_CRYPTO) - if xep_200_encrypted: - if self.forwarded: - # Ignore E2E forwarded encrypted messages - return False - self.encrypted = 'xep200' - return True class ZeroconfMessageReceivedEvent(MessageReceivedEvent): diff --git a/src/dialogs.py b/src/dialogs.py index bfe84ba30..48203b566 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -5315,82 +5315,6 @@ class DataFormWindow(Dialog): self.df_response_ok(form) self.destroy() -class ESessionInfoWindow: - """ - Class for displaying information about a XEP-0116 encrypted session - """ - def __init__(self, session, transient_for=None): - self.session = session - - self.xml = gtkgui_helpers.get_gtk_builder('esession_info_window.ui') - self.xml.connect_signals(self) - - self.security_image = self.xml.get_object('security_image') - self.verify_now_button = self.xml.get_object('verify_now_button') - self.button_label = self.xml.get_object('verification_status_label') - self.window = self.xml.get_object('esession_info_window') - self.update_info() - self.window.set_transient_for(transient_for) - - self.window.show_all() - - def update_info(self): - labeltext = _('''Your chat session with %(jid)s is encrypted.\n\nThis session's Short Authentication String is %(sas)s.''') % {'jid': self.session.jid, 'sas': self.session.sas} - - if self.session.verified_identity: - labeltext += '\n\n' + _('''You have already verified this contact's identity.''') - security_image = 'security-high' - if self.session.control: - self.session.control._show_lock_image(True, 'E2E', True, - self.session.is_loggable(), True) - - verification_status = _('''Contact's identity verified''') - self.window.set_title(verification_status) - self.xml.get_object('verification_status_label').set_markup( - '%s' % verification_status) - - self.xml.get_object('dialog-action_area1').set_no_show_all(True) - self.button_label.set_text(_('Verify again…')) - else: - if self.session.control: - self.session.control._show_lock_image(True, 'E2E', True, - self.session.is_loggable(), False) - labeltext += '\n\n' + _('''To be certain that only the expected person can read your messages or send you messages, you need to verify their identity by clicking the button below.''') - security_image = 'security-low' - - verification_status = _('''Contact's identity NOT verified''') - self.window.set_title(verification_status) - self.xml.get_object('verification_status_label').set_markup( - '%s' % verification_status) - - self.button_label.set_text(_('Verify…')) - - path = gtkgui_helpers.get_icon_path(security_image, 32) - self.security_image.set_from_file(path) - - self.xml.get_object('info_display').set_markup(labeltext) - - def on_close_button_clicked(self, widget): - self.window.destroy() - - def on_verify_now_button_clicked(self, widget): - pritext = _('''Have you verified the contact's identity?''') - sectext = _('''To prevent talking to an unknown person, you should speak to %(jid)s directly (in person or on the phone) and verify that they see the same Short Authentication String (SAS) as you.\n\nThis session's Short Authentication String is %(sas)s.''') % {'jid': self.session.jid, 'sas': self.session.sas} - sectext += '\n\n' + _('Did you talk to the remote contact and verify the SAS?') - - def on_yes(checked): - self.session._verified_srs_cb() - self.session.verified_identity = True - self.update_info() - - def on_no(): - self.session._unverified_srs_cb() - self.session.verified_identity = False - self.update_info() - - YesNoDialog(pritext, sectext, on_response_yes=on_yes, - on_response_no=on_no, transient_for=self.window) - class ResourceConflictDialog(TimeoutDialog, InputDialog): def __init__(self, title, text, resource, ok_handler): diff --git a/src/gui_menu_builder.py b/src/gui_menu_builder.py index f61ae68bd..f03d61a87 100644 --- a/src/gui_menu_builder.py +++ b/src/gui_menu_builder.py @@ -245,8 +245,6 @@ control=None, gc_contact=None, is_anonymous=True): 'remove_from_roster_menuitem') manage_contact_menuitem = xml.get_object('manage_contact') convert_to_gc_menuitem = xml.get_object('convert_to_groupchat_menuitem') - encryption_separator = xml.get_object('encryption_separator') - toggle_e2e_menuitem = xml.get_object('toggle_e2e_menuitem') last_separator = xml.get_object('last_separator') items_to_hide = [] @@ -321,21 +319,6 @@ control=None, gc_contact=None, is_anonymous=True): if not show_start_chat: items_to_hide.append(start_chat_menuitem) - if not show_encryption or not control: - items_to_hide += [encryption_separator, toggle_e2e_menuitem] - else: - e2e_is_active = control.session is not None and \ - control.session.enable_encryption - - # disable esessions if we or the other client don't support them - if not gajim.HAVE_PYCRYPTO or not contact.supports(NS_ESESSION) or \ - not gajim.config.get_per('accounts', account, 'enable_esessions'): - toggle_e2e_menuitem.set_sensitive(False) - else: - toggle_e2e_menuitem.set_active(e2e_is_active) - toggle_e2e_menuitem.connect('activate', - control._on_toggle_e2e_menuitem_activate) - if not show_buttonbar_items: items_to_hide += [history_menuitem, send_file_menuitem, information_menuitem, convert_to_gc_menuitem, last_separator] From ce7923e1645d70baf1e1b4d2dbeb866dc838ac03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Sat, 6 May 2017 13:54:43 +0200 Subject: [PATCH 4/5] Remove log and enabled status from lock tooltip - Log status is a feature that only works with xep-0136 which nobody uses anymore - Even then we can only trust in the server/user to not log the session - We should not imply that a client could control if messages are logged on the server or the other end, especially as this is about encryption and security we should be as accurate as possible - The lock image is only shown if a encryption was sucessfully activated, so the enabled flag is unnecessary --- src/chat_control.py | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/src/chat_control.py b/src/chat_control.py index b1beb22f7..2a39d5628 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -848,46 +848,31 @@ class ChatControl(ChatControlBase): encryption_state = {'visible': visible, 'enc_type': self.encryption, - 'enc_enabled': False, - 'chat_logged': loggable, 'authenticated': False} gajim.plugin_manager.gui_extension_point( 'encryption_state' + self.encryption, self, encryption_state) self._show_lock_image(**encryption_state) - - def _show_lock_image(self, visible, enc_type='', enc_enabled=False, - chat_logged=False, authenticated=False): + + def _show_lock_image(self, visible, enc_type='', + authenticated=False): """ Set lock icon visibility and create tooltip """ - #encryption %s active - status_string = enc_enabled and _('is') or _('is NOT') - #chat session %s be logged - logged_string = chat_logged and _('will') or _('will NOT') - if authenticated: - #About encrypted chat session authenticated_string = _('and authenticated') img_path = gtkgui_helpers.get_icon_path('security-high') else: - #About encrypted chat session authenticated_string = _('and NOT authenticated') img_path = gtkgui_helpers.get_icon_path('security-low') self.lock_image.set_from_file(img_path) - #status will become 'is' or 'is not', authentificaed will become - #'and authentificated' or 'and not authentificated', logged will become - #'will' or 'will not' - tooltip = _('%(type)s encryption %(status)s active %(authenticated)s.\n' - 'Your chat session %(logged)s be logged.') % {'type': enc_type, - 'status': status_string, 'authenticated': authenticated_string, - 'logged': logged_string} + tooltip = _('%(type)s encryption is active %(authenticated)s.') % {'type': enc_type, 'authenticated': authenticated_string} self.authentication_button.set_tooltip_text(tooltip) self.widget_set_visible(self.authentication_button, not visible) - self.lock_image.set_sensitive(enc_enabled) + self.lock_image.set_sensitive(visible) def _on_authentication_button_clicked(self, widget): gajim.plugin_manager.gui_extension_point( @@ -991,8 +976,8 @@ class ChatControl(ChatControlBase): msg = _('E2E encryption disabled') ChatControlBase.print_conversation_line(self, msg, 'status', '', None) - self._show_lock_image(e2e_is_active, 'E2E', e2e_is_active, self.session and \ - self.session.is_loggable(), self.session and self.session.verified_identity) + self._show_lock_image(e2e_is_active, 'E2E', + self.session and self.session.verified_identity) def print_session_details(self, old_session=None): if isinstance(self.session, EncryptedStanzaSession) or \ From 2e148a6133c18d15863003cbb09b31cc9c15d436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Sun, 7 May 2017 14:02:11 +0200 Subject: [PATCH 5/5] Add encryption API to groupchat_control --- data/gui/groupchat_control.ui | 79 ++++++++++++++++++++++++++++++----- src/chat_control.py | 2 +- src/chat_control_base.py | 6 ++- src/common/connection.py | 8 ++++ src/groupchat_control.py | 61 +++++++++++++++++++++++++++ src/gui_menu_builder.py | 19 ++++++--- src/roster_window.py | 2 +- 7 files changed, 157 insertions(+), 20 deletions(-) diff --git a/data/gui/groupchat_control.ui b/data/gui/groupchat_control.ui index 7ab14ecca..7a0d7e8f4 100644 --- a/data/gui/groupchat_control.ui +++ b/data/gui/groupchat_control.ui @@ -1,5 +1,5 @@ - + @@ -127,19 +127,56 @@ - + True - True - never - never - in + False + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 3 - + + ChatControl-AuthenticationButton + True + False + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + none + + + True + False + gtk-dialog-authentication + 1 + + + + + False + False + 0 + + + + + True + True + never + never + in + + + + + + True + True + 1 + False - True + False 1 @@ -214,12 +251,12 @@ True True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True Show a list of formattings none - False True @@ -364,12 +401,12 @@ True True + False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True Show advanced functions (Alt+D) none - False True @@ -397,7 +434,24 @@ - + + True + True + True + none + + + True + False + channel-secure-symbolic.symbolic + + + + + False + True + 10 + @@ -418,6 +472,9 @@ 11 + + + False diff --git a/src/chat_control.py b/src/chat_control.py index 2a39d5628..2b1e8fa05 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -286,7 +286,7 @@ class ChatControl(ChatControlBase): self.encryption_menu = self.xml.get_object('encryption_menu') self.encryption_menu.set_menu_model( - gui_menu_builder.get_encryption_menu(self.contact)) + gui_menu_builder.get_encryption_menu(self.contact, self.type_id)) # restore previous conversation self.restore_conversation() self.msg_textview.grab_focus() diff --git a/src/chat_control_base.py b/src/chat_control_base.py index 3b6cde993..299dda9ca 100644 --- a/src/chat_control_base.py +++ b/src/chat_control_base.py @@ -398,7 +398,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): self.encryption = 'disabled' self.set_encryption_state() - self.add_window_actions() + if self.parent_win: + self.add_window_actions() # PluginSystem: adding GUI extension point for ChatControlBase # instance object (also subclasses, eg. ChatControl or GroupchatControl) @@ -435,7 +436,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): if not plugin.activate_encryption(self): return else: - self.terminate_esessions() + if not self.widget_name == 'groupchat_control': + self.terminate_esessions() action.set_state(param) gajim.config.set_per( 'contacts', self.contact.jid, 'encryption', encryption) diff --git a/src/common/connection.py b/src/common/connection.py index baf18bc45..094625bb5 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -2672,6 +2672,14 @@ class Connection(CommonConnection, ConnectionHandlers): def _nec_gc_stanza_message_outgoing(self, obj): if obj.conn.name != self.name: return + encryption = gajim.config.get_per('contacts', obj.jid, 'encryption') + if encryption != 'disabled': + gajim.plugin_manager.gui_extension_point( + 'gc_encrypt' + encryption, self, obj, self.send_gc_message) + else: + self.send_gc_message(obj) + + def send_gc_message(self, obj): if obj.correction_msg: obj.msg_id = self.connection.send(obj.correction_msg) else: diff --git a/src/groupchat_control.py b/src/groupchat_control.py index 7c62977de..17f30a3c0 100644 --- a/src/groupchat_control.py +++ b/src/groupchat_control.py @@ -481,6 +481,19 @@ class GroupchatControl(ChatControlBase): self.form_widget = None + # Encryption + self.lock_image = self.xml.get_object('lock_image') + self.authentication_button = self.xml.get_object( + 'authentication_button') + id_ = self.authentication_button.connect('clicked', + self._on_authentication_button_clicked) + self.handlers[id_] = self.authentication_button + self.set_lock_image() + + self.encryption_menu = self.xml.get_object('encryption_menu') + self.encryption_menu.set_menu_model( + gui_menu_builder.get_encryption_menu(self.contact, self.type_id)) + gajim.ged.register_event_handler('gc-presence-received', ged.GUI1, self._nec_gc_presence_received) gajim.ged.register_event_handler('gc-message-received', ged.GUI1, @@ -508,6 +521,11 @@ class GroupchatControl(ChatControlBase): # instance object gajim.plugin_manager.gui_extension_point('groupchat_control', self) + def on_groupchat_maximize(self): + self.set_tooltip() + self.add_window_actions() + self.set_lock_image() + def set_tooltip(self): widget = self.xml.get_object('list_treeview') if widget.get_tooltip_window(): @@ -751,6 +769,42 @@ class GroupchatControl(ChatControlBase): for nick in gajim.contacts.get_nick_list(self.account, self.room_jid): self.draw_contact(nick) + def set_lock_image(self): + visible = self.encryption != 'disabled' + + encryption_state = {'visible': visible, + 'enc_type': self.encryption, + 'authenticated': False} + + gajim.plugin_manager.gui_extension_point( + 'encryption_state' + self.encryption, self, encryption_state) + + self._show_lock_image(**encryption_state) + + def _show_lock_image(self, visible, enc_type='', + authenticated=False): + """ + Set lock icon visibility and create tooltip + """ + if authenticated: + authenticated_string = _('and authenticated') + img_path = gtkgui_helpers.get_icon_path('security-high') + else: + authenticated_string = _('and NOT authenticated') + img_path = gtkgui_helpers.get_icon_path('security-low') + self.lock_image.set_from_file(img_path) + + tooltip = _('%(type)s encryption is active %(authenticated)s.') % { + 'type': enc_type, 'authenticated': authenticated_string} + + self.authentication_button.set_tooltip_text(tooltip) + self.widget_set_visible(self.authentication_button, not visible) + self.lock_image.set_sensitive(visible) + + def _on_authentication_button_clicked(self, widget): + gajim.plugin_manager.gui_extension_point( + 'encryption_dialog' + self.encryption, self) + def _change_style(self, model, path, iter_, option): model[iter_][Column.NICK] = model[iter_][Column.NICK] @@ -1966,6 +2020,13 @@ class GroupchatControl(ChatControlBase): if not message: return + if self.encryption: + self.sendmessage = True + gajim.plugin_manager.gui_extension_point( + 'send_message' + self.encryption, self) + if not self.sendmessage: + return + if process_commands and self.process_as_command(message): return diff --git a/src/gui_menu_builder.py b/src/gui_menu_builder.py index f03d61a87..58f892533 100644 --- a/src/gui_menu_builder.py +++ b/src/gui_menu_builder.py @@ -750,12 +750,21 @@ def build_bookmark_menu(account): menu.insert_submenu(1, label, bookmark_menu) -def get_encryption_menu(contact): +def get_encryption_menu(contact, type_id): menu = Gio.Menu() menu.append( - 'Disabled', 'win.{}-encryptiongroup::{}'.format(contact.jid, 'disabled')) - for encryption in gajim.plugin_manager.encryption_plugins: + 'Disabled', 'win.{}-encryptiongroup::{}'.format(contact.jid, + 'disabled')) + for name, plugin in gajim.plugin_manager.encryption_plugins.items(): + if type_id == 'gc': + if not hasattr(plugin, 'allow_groupchat'): + continue + if type_id == 'pm': + if not hasattr(plugin, 'allow_privatchat'): + continue menu_action = 'win.{}-encryptiongroup::{}'.format( - contact.jid, encryption) - menu.append(encryption, menu_action) + contact.jid, name) + menu.append(name, menu_action) + if menu.get_n_items() == 1: + return None return menu diff --git a/src/roster_window.py b/src/roster_window.py index 81766ef94..7d33908e8 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -3220,7 +3220,7 @@ class RosterWindow: ctrl._on_window_motion_notify) ctrl.handlers[id_] = mw.window ctrl.parent_win = mw - ctrl.set_tooltip() + ctrl.on_groupchat_maximize() mw.new_tab(ctrl) mw.set_active_tab(ctrl) mw.window.get_window().focus(Gtk.get_current_event_time())