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] 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 \