Commit 8ec3ec8a authored by Philipp Hörist's avatar Philipp Hörist

Merge branch 'extensionpoints' into 'master'

New Encryption Plugin API

See merge request !96
parents 3e6ecccc 2e148a61
......@@ -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>
......
......@@ -45,28 +45,6 @@
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="encryption_separator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkCheckMenuItem" id="toggle_gpg_menuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Toggle Open_PGP Encryption</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkCheckMenuItem" id="toggle_e2e_menuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Toggle End to End Encryption</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="menuitem3">
<property name="visible">True</property>
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<!-- Generated with glade 3.20.0 -->
<interface>
<requires lib="gtk+" version="3.12"/>
<object class="GtkImage" id="image1">
......@@ -127,19 +127,56 @@
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="message_scrolledwindow">
<object class="GtkBox" id="hbox">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
<property name="vscrollbar_policy">never</property>
<property name="shadow_type">in</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="spacing">3</property>
<child>
<placeholder/>
<object class="GtkButton" id="authentication_button">
<property name="name">ChatControl-AuthenticationButton</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="no_show_all">True</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="lock_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-dialog-authentication</property>
<property name="icon_size">1</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="message_scrolledwindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
<property name="vscrollbar_policy">never</property>
<property name="shadow_type">in</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
......@@ -214,12 +251,12 @@
<object class="GtkButton" id="formattings_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="has_tooltip">True</property>
<property name="tooltip_text" translatable="yes">Show a list of formattings</property>
<property name="relief">none</property>
<property name="focus_on_click">False</property>
<child>
<object class="GtkImage" id="image11">
<property name="visible">True</property>
......@@ -364,12 +401,12 @@
<object class="GtkButton" id="muc_window_actions_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="has_tooltip">True</property>
<property name="tooltip_text" translatable="yes">Show advanced functions (Alt+D)</property>
<property name="relief">none</property>
<property name="focus_on_click">False</property>
<child>
<object class="GtkImage" id="image1344">
<property name="visible">True</property>
......@@ -397,7 +434,24 @@
</packing>
</child>
<child>
<placeholder/>
<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">10</property>
</packing>
</child>
<child>
<object class="GtkButton" id="send_button">
......@@ -418,6 +472,9 @@
<property name="position">11</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
......
......@@ -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>
......
......@@ -90,10 +90,10 @@ def __init__(self, parent_win, contact, acct, session, resource=None):
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
# for muc use:
# widget = self.xml.get_object('muc_window_actions_button')
self.actions_button = self.xml.get_object('message_window_actions_button')
......@@ -280,28 +280,13 @@ def __init__(self, parent_win, contact, acct, session, resource=None):
# 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()
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))
# restore previous conversation
self.restore_conversation()
self.msg_textview.grab_focus()
......@@ -344,7 +329,8 @@ def _update_toolbar(self):
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'))
......@@ -856,114 +842,64 @@ def on_audio_button_toggled(self, widget):
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
def set_lock_image(self):
visible = self.encryption != 'disabled'
loggable = self.session and self.session.is_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)
encryption_state = {'visible': visible,
'enc_type': self.encryption,
'authenticated': False}
gajim.config.set_per('contacts', self.contact.jid,
'gpg_enabled', self.gpg_is_active)
gajim.plugin_manager.gui_extension_point(
'encryption_state' + self.encryption, self, encryption_state)
self._show_lock_image(self.gpg_is_active, 'OpenPGP',
self.gpg_is_active, loggable, True)
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):
if self.gpg_is_active:
dialogs.GPGInfoWindow(self, self.parent_win.window)
elif self.session and self.session.enable_encryption:
dialogs.ESessionInfoWindow(self.session, self.parent_win.window)
gajim.plugin_manager.gui_extension_point(
'encryption_dialog' + self.encryption, self)
def send_message(self, message, keyID='', chatstate=None, xhtml=None,
process_commands=True, attention=False):
"""
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
contact = self.contact
keyID = contact.keyID
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'
chatstates_on = gajim.config.get('outgoing_chat_state_notifications') != \
'disabled'
chatstate_to_send = None
if contact is not None:
......@@ -999,7 +935,7 @@ def _on_sent(obj, msg_stanza, message, encrypted, xhtml, label):
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)
......@@ -1040,8 +976,8 @@ def print_esession_details(self):
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 \
......@@ -1092,20 +1028,6 @@ def print_conversation(self, text, frm='', tim=None, encrypted=False,
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()
......@@ -1115,7 +1037,7 @@ def print_conversation(self, text, frm='', tim=None, encrypted=False,
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)
......@@ -1192,9 +1114,8 @@ def get_tab_image(self, count_unread=True):
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,
......@@ -1481,19 +1402,9 @@ def _on_drag_data_received(self, widget, context, x, y, selection,
def _on_message_tv_buffer_changed(self, textbuffer):
super()._on_message_tv_buffer_changed(textbuffer)
if textbuffer.get_char_count():
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 not self.gpg_is_active \
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 \
gajim.plugin_manager.gui_extension_point(
'typing' + self.encryption, self)
if (not self.session or not self.session.status) and \
gajim.connections[self.account].archiving_136_supported:
self.begin_archiving_negotiation()
......@@ -1684,30 +1595,28 @@ def _on_add_to_roster_menuitem_activate(self, widget):
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
"""
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
def activate_esessions(self):
if not (self.session and self.session.enable_encryption):
self.begin_e2e_negotiation()
self.session.terminate_e2e()
def terminate_esessions(self):
# e2e was enabled, disable it
jid = str(self.session.jid)
thread_id = self.session.thread_id
gajim.connections[self.account].delete_session(jid, thread_id)
self.session.terminate_e2e()
# presumably the user had a good reason to shut it off, so
# disable autonegotiation too
self.no_autonegotiation = True
else:
self.begin_e2e_negotiation()
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
......
......@@ -34,6 +34,7 @@
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,11 @@ def __init__(self, type_id, parent_win, widget_name, contact, acct,
self._on_window_motion_notify)
self.handlers[id_] = parent_win.window
self.encryption = 'disabled'
self.set_encryption_state()
if self.parent_win:
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 +418,41 @@ def __init__(self, type_id, parent_win, widget_name, contact, acct,
# 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
else:
if not self.widget_name == 'groupchat_control':
self.terminate_esessions()
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')