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>
</packing>
</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>
<object class="GtkBox" id="audio_buttons_hbox">
<property name="can_focus">False</property>
@ -1039,7 +1059,7 @@ audio-mic-volume-low</property>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">12</property>
<property name="position">13</property>
</packing>
</child>
<child>
@ -1050,12 +1070,9 @@ audio-mic-volume-low</property>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">13</property>
<property name="position">14</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<object class="GtkButton" id="send_button">
<property name="label" translatable="yes">_Send</property>
@ -1077,6 +1094,9 @@ audio-mic-volume-low</property>
<property name="position">15</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>

View File

@ -64,7 +64,7 @@
</object>
</child>
</object>
<object class="GtkWindow" id="message_window">
<object class="GtkApplicationWindow" id="message_window">
<property name="can_focus">False</property>
<property name="default_width">480</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_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,

View File

@ -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')

View File

@ -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')],
}, {}),

View File

@ -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(

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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 = ''
'''

View File

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