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
This commit is contained in:
Philipp Hörist 2017-04-13 22:58:51 +02:00
parent 3e6ecccc26
commit 5116af037b
11 changed files with 160 additions and 8 deletions

View file

@ -939,6 +939,26 @@
<property name="position">11</property> <property name="position">11</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkMenuButton" id="encryption_menu">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="relief">none</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">channel-secure-symbolic.symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">12</property>
</packing>
</child>
<child> <child>
<object class="GtkBox" id="audio_buttons_hbox"> <object class="GtkBox" id="audio_buttons_hbox">
<property name="can_focus">False</property> <property name="can_focus">False</property>
@ -1039,7 +1059,7 @@ audio-mic-volume-low</property>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">12</property> <property name="position">13</property>
</packing> </packing>
</child> </child>
<child> <child>
@ -1050,12 +1070,9 @@ audio-mic-volume-low</property>
<packing> <packing>
<property name="expand">True</property> <property name="expand">True</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">13</property> <property name="position">14</property>
</packing> </packing>
</child> </child>
<child>
<placeholder/>
</child>
<child> <child>
<object class="GtkButton" id="send_button"> <object class="GtkButton" id="send_button">
<property name="label" translatable="yes">_Send</property> <property name="label" translatable="yes">_Send</property>
@ -1077,6 +1094,9 @@ audio-mic-volume-low</property>
<property name="position">15</property> <property name="position">15</property>
</packing> </packing>
</child> </child>
<child>
<placeholder/>
</child>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>

View file

@ -64,7 +64,7 @@
</object> </object>
</child> </child>
</object> </object>
<object class="GtkWindow" id="message_window"> <object class="GtkApplicationWindow" id="message_window">
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="default_width">480</property> <property name="default_width">480</property>
<property name="default_height">440</property> <property name="default_height">440</property>

View file

@ -94,6 +94,7 @@ class ChatControl(ChatControlBase):
self.last_recv_message_id = None self.last_recv_message_id = None
self.last_recv_message_marks = None self.last_recv_message_marks = None
self.last_message_timestamp = None self.last_message_timestamp = None
# for muc use: # for muc use:
# widget = self.xml.get_object('muc_window_actions_button') # widget = self.xml.get_object('muc_window_actions_button')
self.actions_button = self.xml.get_object('message_window_actions_button') self.actions_button = self.xml.get_object('message_window_actions_button')
@ -302,6 +303,11 @@ class ChatControl(ChatControlBase):
True) True)
self.update_ui() 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 # restore previous conversation
self.restore_conversation() self.restore_conversation()
self.msg_textview.grab_focus() self.msg_textview.grab_focus()
@ -907,6 +913,21 @@ class ChatControl(ChatControlBase):
self._show_lock_image(self.gpg_is_active, 'OpenPGP', self._show_lock_image(self.gpg_is_active, 'OpenPGP',
self.gpg_is_active, loggable, True) 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, def _show_lock_image(self, visible, enc_type='', enc_enabled=False,
chat_logged=False, authenticated=False): chat_logged=False, authenticated=False):
""" """
@ -940,6 +961,8 @@ class ChatControl(ChatControlBase):
self.lock_image.set_sensitive(enc_enabled) self.lock_image.set_sensitive(enc_enabled)
def _on_authentication_button_clicked(self, widget): def _on_authentication_button_clicked(self, widget):
gajim.plugin_manager.gui_extension_point(
'encryption_dialog' + self.encryption, self)
if self.gpg_is_active: if self.gpg_is_active:
dialogs.GPGInfoWindow(self, self.parent_win.window) dialogs.GPGInfoWindow(self, self.parent_win.window)
elif self.session and self.session.enable_encryption: elif self.session and self.session.enable_encryption:
@ -950,6 +973,14 @@ class ChatControl(ChatControlBase):
""" """
Send a message to contact 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) message = helpers.remove_invalid_xml_chars(message)
if message in ('', None, '\n'): if message in ('', None, '\n'):
return None return None
@ -1481,6 +1512,8 @@ class ChatControl(ChatControlBase):
def _on_message_tv_buffer_changed(self, textbuffer): def _on_message_tv_buffer_changed(self, textbuffer):
super()._on_message_tv_buffer_changed(textbuffer) super()._on_message_tv_buffer_changed(textbuffer)
if textbuffer.get_char_count(): if textbuffer.get_char_count():
gajim.plugin_manager.gui_extension_point(
'typing' + self.encryption, self)
e2e_is_active = self.session and \ e2e_is_active = self.session and \
self.session.enable_encryption self.session.enable_encryption
e2e_pref = gajim.config.get_per('accounts', self.account, e2e_pref = gajim.config.get_per('accounts', self.account,

View file

@ -34,6 +34,7 @@ from gi.repository import Gdk
from gi.repository import Pango from gi.repository import Pango
from gi.repository import GObject from gi.repository import GObject
from gi.repository import GLib from gi.repository import GLib
from gi.repository import Gio
import gtkgui_helpers import gtkgui_helpers
import message_control import message_control
import dialogs import dialogs
@ -395,6 +396,10 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
self._on_window_motion_notify) self._on_window_motion_notify)
self.handlers[id_] = parent_win.window self.handlers[id_] = parent_win.window
self.encryption = 'disabled'
self.set_encryption_state()
self.add_window_actions()
# PluginSystem: adding GUI extension point for ChatControlBase # PluginSystem: adding GUI extension point for ChatControlBase
# instance object (also subclasses, eg. ChatControl or GroupchatControl) # instance object (also subclasses, eg. ChatControl or GroupchatControl)
gajim.plugin_manager.gui_extension_point('chat_control_base', self) 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. # to properly use the super, because of the old code.
CommandTools.__init__(self) 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): def set_speller(self):
# now set the one the user selected # now set the one the user selected
per_type = 'contacts' per_type = 'contacts'
@ -723,7 +760,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
keyID=keyID, type_=type_, chatstate=chatstate, msg_id=msg_id, keyID=keyID, type_=type_, chatstate=chatstate, msg_id=msg_id,
resource=resource, user_nick=self.user_nick, xhtml=xhtml, resource=resource, user_nick=self.user_nick, xhtml=xhtml,
label=label, callback=_cb, callback_args=[callback] + callback_args, 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 # Record the history of sent messages
self.save_message(message, 'sent') self.save_message(message, 'sent')

View file

@ -481,6 +481,7 @@ class Config:
}, {}), }, {}),
'contacts': ({ 'contacts': ({
'gpg_enabled': [ opt_bool, False, _('Is OpenPGP enabled for this contact?')], '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?')], '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')], 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')],
}, {}), }, {}),

View file

@ -2136,6 +2136,14 @@ class Connection(CommonConnection, ConnectionHandlers):
def _nec_stanza_message_outgoing(self, obj): def _nec_stanza_message_outgoing(self, obj):
if obj.conn.name != self.name: if obj.conn.name != self.name:
return 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) obj.msg_id = self.connection.send(obj.msg_iq, now=obj.now)
gajim.nec.push_incoming_event(MessageSentEvent( gajim.nec.push_incoming_event(MessageSentEvent(

View file

@ -1091,6 +1091,12 @@ class ConnectionHandlersBase:
def _nec_message_received(self, obj): def _nec_message_received(self, obj):
if obj.conn.name != self.name: if obj.conn.name != self.name:
return 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': if obj.encrypted == 'xep200':
try: try:
obj.stanza = obj.session.decrypt_stanza(obj.stanza) obj.stanza = obj.session.decrypt_stanza(obj.stanza)
@ -1153,6 +1159,14 @@ class ConnectionHandlersBase:
gajim.nec.push_incoming_event(MamDecryptedMessageReceivedEvent(None, gajim.nec.push_incoming_event(MamDecryptedMessageReceivedEvent(None,
conn=self, msg_obj=obj)) 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): def _nec_decrypted_message_received(self, obj):
if obj.conn.name != self.name: if obj.conn.name != self.name:
return return

View file

@ -2796,6 +2796,7 @@ class MessageOutgoingEvent(nec.NetworkOutgoingEvent):
self.attention = False self.attention = False
self.correction_msg = None self.correction_msg = None
self.automatic_message = True self.automatic_message = True
self.encryption = ''
def get_full_jid(self): def get_full_jid(self):
if self.resource: if self.resource:

View file

@ -781,3 +781,14 @@ def build_bookmark_menu(account):
label = menu.get_item_attribute_value(1, 'label').get_string() label = menu.get_item_attribute_value(1, 'label').get_string()
menu.remove(1) menu.remove(1)
menu.insert_submenu(1, label, bookmark_menu) 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

View file

@ -58,6 +58,16 @@ class GajimPlugin(object):
:todo: decide whether we really need this one, because class name (with :todo: decide whether we really need this one, because class name (with
module name) can act as such short name 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 = '' version = ''
''' '''

View file

@ -101,6 +101,12 @@ class PluginManager(metaclass=Singleton):
''' '''
Registered handlers of GUI extension points. 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]]: for path in [gajim.PLUGINS_DIRS[1], gajim.PLUGINS_DIRS[0]]:
pc = PluginManager.scan_dir_for_plugins(path) pc = PluginManager.scan_dir_for_plugins(path)
self.add_plugins(pc) self.add_plugins(pc)
@ -291,6 +297,10 @@ class PluginManager(metaclass=Singleton):
elif issubclass(event_class, nec.NetworkOutgoingEvent): elif issubclass(event_class, nec.NetworkOutgoingEvent):
gajim.nec.unregister_outgoing_event(event_class) 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') @log_calls('PluginManager')
def activate_plugin(self, plugin): def activate_plugin(self, plugin):
''' '''
@ -300,6 +310,7 @@ class PluginManager(metaclass=Singleton):
if not plugin.active and plugin.activatable: if not plugin.active and plugin.activatable:
self._add_gui_extension_points_handlers_from_plugin(plugin) 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._handle_all_gui_extension_points_with_plugin(plugin)
self._register_events_handlers_in_ged(plugin) self._register_events_handlers_in_ged(plugin)
self._register_network_events_in_nec(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_events_handler_from_ged(plugin)
self._remove_network_events_from_nec(plugin) self._remove_network_events_from_nec(plugin)
self._remove_name_from_encryption_plugins(plugin)
# removing plug-in from active plug-ins list # removing plug-in from active plug-ins list
plugin.deactivate() plugin.deactivate()
@ -357,6 +369,10 @@ class PluginManager(metaclass=Singleton):
self.gui_extension_points_handlers.setdefault(gui_extpoint_name, self.gui_extension_points_handlers.setdefault(gui_extpoint_name,
[]).append(gui_extpoint_handlers) []).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') @log_calls('PluginManager')
def _handle_all_gui_extension_points_with_plugin(self, plugin): def _handle_all_gui_extension_points_with_plugin(self, plugin):
for gui_extpoint_name, gui_extpoint_handlers in \ for gui_extpoint_name, gui_extpoint_handlers in \