Merge branch 'extensionpoints' into 'master'

New Encryption Plugin API

See merge request !96
This commit is contained in:
Philipp Hörist 2017-05-07 23:06:14 +02:00
commit 8ec3ec8a58
17 changed files with 373 additions and 601 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

@ -45,28 +45,6 @@
<property name="use_underline">True</property> <property name="use_underline">True</property>
</object> </object>
</child> </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> <child>
<object class="GtkSeparatorMenuItem" id="menuitem3"> <object class="GtkSeparatorMenuItem" id="menuitem3">
<property name="visible">True</property> <property name="visible">True</property>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 --> <!-- Generated with glade 3.20.0 -->
<interface> <interface>
<requires lib="gtk+" version="3.12"/> <requires lib="gtk+" version="3.12"/>
<object class="GtkImage" id="image1"> <object class="GtkImage" id="image1">
@ -127,19 +127,56 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkScrolledWindow" id="message_scrolledwindow"> <object class="GtkBox" id="hbox">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">False</property>
<property name="hscrollbar_policy">never</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="vscrollbar_policy">never</property> <property name="spacing">3</property>
<property name="shadow_type">in</property>
<child> <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> </child>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">False</property>
<property name="position">1</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
@ -214,12 +251,12 @@
<object class="GtkButton" id="formattings_button"> <object class="GtkButton" id="formattings_button">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</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="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="has_tooltip">True</property>
<property name="tooltip_text" translatable="yes">Show a list of formattings</property> <property name="tooltip_text" translatable="yes">Show a list of formattings</property>
<property name="relief">none</property> <property name="relief">none</property>
<property name="focus_on_click">False</property>
<child> <child>
<object class="GtkImage" id="image11"> <object class="GtkImage" id="image11">
<property name="visible">True</property> <property name="visible">True</property>
@ -364,12 +401,12 @@
<object class="GtkButton" id="muc_window_actions_button"> <object class="GtkButton" id="muc_window_actions_button">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</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="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="has_tooltip">True</property>
<property name="tooltip_text" translatable="yes">Show advanced functions (Alt+D)</property> <property name="tooltip_text" translatable="yes">Show advanced functions (Alt+D)</property>
<property name="relief">none</property> <property name="relief">none</property>
<property name="focus_on_click">False</property>
<child> <child>
<object class="GtkImage" id="image1344"> <object class="GtkImage" id="image1344">
<property name="visible">True</property> <property name="visible">True</property>
@ -397,7 +434,24 @@
</packing> </packing>
</child> </child>
<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>
<child> <child>
<object class="GtkButton" id="send_button"> <object class="GtkButton" id="send_button">
@ -418,6 +472,9 @@
<property name="position">11</property> <property name="position">11</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

@ -90,10 +90,10 @@ class ChatControl(ChatControlBase):
ChatControlBase.__init__(self, self.TYPE_ID, parent_win, ChatControlBase.__init__(self, self.TYPE_ID, parent_win,
'chat_control', contact, acct, resource) 'chat_control', contact, acct, resource)
self.gpg_is_active = False
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')
@ -280,28 +280,13 @@ class ChatControl(ChatControlBase):
# Enable encryption if needed # Enable encryption if needed
self.no_autonegotiation = False 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.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 # restore previous conversation
self.restore_conversation() self.restore_conversation()
self.msg_textview.grab_focus() self.msg_textview.grab_focus()
@ -344,7 +329,8 @@ class ChatControl(ChatControlBase):
send_button = self.xml.get_object('send_button') send_button = self.xml.get_object('send_button')
send_button.set_sensitive(True) send_button.set_sensitive(True)
# Formatting # 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_sensitive(True)
self._formattings_button.set_tooltip_text(_( self._formattings_button.set_tooltip_text(_(
'Show a list of formattings')) 'Show a list of formattings'))
@ -856,114 +842,64 @@ class ChatControl(ChatControlBase):
def on_video_button_toggled(self, widget): def on_video_button_toggled(self, widget):
self.on_jingle_button_toggled(widget, 'video') self.on_jingle_button_toggled(widget, 'video')
def _toggle_gpg(self): def set_lock_image(self):
if not self.gpg_is_active and not self.contact.keyID: visible = self.encryption != 'disabled'
dialogs.ErrorDialog(_('No OpenPGP key assigned'), loggable = self.session and self.session.is_loggable()
_('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: encryption_state = {'visible': visible,
# Enable encryption 'enc_type': self.encryption,
ec.append(self.contact.jid) 'authenticated': False}
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, gajim.plugin_manager.gui_extension_point(
'log_encrypted_sessions') 'encryption_state' + self.encryption, self, encryption_state)
if self.session: self._show_lock_image(**encryption_state)
self.session.loggable = loggable
loggable = self.session.is_loggable() def _show_lock_image(self, visible, enc_type='',
else: authenticated=False):
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 _show_lock_image(self, visible, enc_type='', enc_enabled=False,
chat_logged=False, authenticated=False):
""" """
Set lock icon visibility and create tooltip 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: if authenticated:
#About encrypted chat session
authenticated_string = _('and authenticated') authenticated_string = _('and authenticated')
img_path = gtkgui_helpers.get_icon_path('security-high') img_path = gtkgui_helpers.get_icon_path('security-high')
else: else:
#About encrypted chat session
authenticated_string = _('and NOT authenticated') authenticated_string = _('and NOT authenticated')
img_path = gtkgui_helpers.get_icon_path('security-low') img_path = gtkgui_helpers.get_icon_path('security-low')
self.lock_image.set_from_file(img_path) self.lock_image.set_from_file(img_path)
#status will become 'is' or 'is not', authentificaed will become tooltip = _('%(type)s encryption is active %(authenticated)s.') % {'type': enc_type, 'authenticated': authenticated_string}
#'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}
self.authentication_button.set_tooltip_text(tooltip) self.authentication_button.set_tooltip_text(tooltip)
self.widget_set_visible(self.authentication_button, not visible) 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): def _on_authentication_button_clicked(self, widget):
if self.gpg_is_active: gajim.plugin_manager.gui_extension_point(
dialogs.GPGInfoWindow(self, self.parent_win.window) 'encryption_dialog' + self.encryption, self)
elif 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, def send_message(self, message, keyID='', chatstate=None, xhtml=None,
process_commands=True, attention=False): process_commands=True, attention=False):
""" """
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
contact = self.contact contact = self.contact
keyID = contact.keyID
encrypted = bool(self.session) and self.session.enable_encryption chatstates_on = gajim.config.get('outgoing_chat_state_notifications') != \
'disabled'
keyID = ''
if self.gpg_is_active:
keyID = contact.keyID
encrypted = True
if not keyID:
keyID = 'UNKNOWN'
chatstate_to_send = None chatstate_to_send = None
if contact is not None: if contact is not None:
@ -999,7 +935,7 @@ class ChatControl(ChatControlBase):
ChatControlBase.send_message(self, message, keyID, type_='chat', ChatControlBase.send_message(self, message, keyID, type_='chat',
chatstate=chatstate_to_send, xhtml=xhtml, callback=_on_sent, 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, process_commands=process_commands,
attention=attention) attention=attention)
@ -1040,8 +976,8 @@ class ChatControl(ChatControlBase):
msg = _('E2E encryption disabled') msg = _('E2E encryption disabled')
ChatControlBase.print_conversation_line(self, msg, 'status', '', None) ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
self._show_lock_image(e2e_is_active, 'E2E', e2e_is_active, self.session and \ self._show_lock_image(e2e_is_active, 'E2E',
self.session.is_loggable(), self.session and self.session.verified_identity) self.session and self.session.verified_identity)
def print_session_details(self, old_session=None): def print_session_details(self, old_session=None):
if isinstance(self.session, EncryptedStanzaSession) or \ if isinstance(self.session, EncryptedStanzaSession) or \
@ -1092,20 +1028,6 @@ class ChatControl(ChatControlBase):
msg = _('The following message was NOT encrypted') msg = _('The following message was NOT encrypted')
ChatControlBase.print_conversation_line(self, msg, 'status', ChatControlBase.print_conversation_line(self, msg, 'status',
'', tim) '', 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: if not frm:
kind = 'incoming' kind = 'incoming'
name = contact.get_shown_name() name = contact.get_shown_name()
@ -1115,7 +1037,7 @@ class ChatControl(ChatControlBase):
else: else:
kind = 'outgoing' kind = 'outgoing'
name = self.get_our_nick() 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'): gajim.config.get('rst_formatting_outgoing_messages'):
from common.rst_xhtml_generator import create_xhtml from common.rst_xhtml_generator import create_xhtml
xhtml = create_xhtml(text) xhtml = create_xhtml(text)
@ -1192,9 +1114,8 @@ class ChatControl(ChatControlBase):
def prepare_context_menu(self, hide_buttonbar_items=False): def prepare_context_menu(self, hide_buttonbar_items=False):
""" """
Set compact view menuitem active state sets active and sensitivity state Set compact view menuitem active state sets active and sensitivity state
for toggle_gpg_menuitem sets sensitivity for history_menuitem (False for for history_menuitem (False for tranasports) and file_transfer_menuitem
tranasports) and file_transfer_menuitem and hide()/show() for and hide()/show() for add_to_roster_menuitem
add_to_roster_menuitem
""" """
if gajim.jid_is_transport(self.contact.jid): if gajim.jid_is_transport(self.contact.jid):
menu = gui_menu_builder.get_transport_menu(self.contact, menu = gui_menu_builder.get_transport_menu(self.contact,
@ -1481,19 +1402,9 @@ 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():
e2e_is_active = self.session and \ gajim.plugin_manager.gui_extension_point(
self.session.enable_encryption 'typing' + self.encryption, self)
e2e_pref = gajim.config.get_per('accounts', self.account, if (not self.session or not self.session.status) and \
'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.connections[self.account].archiving_136_supported: gajim.connections[self.account].archiving_136_supported:
self.begin_archiving_negotiation() self.begin_archiving_negotiation()
@ -1684,31 +1595,29 @@ class ChatControl(ChatControlBase):
def _on_contact_information_menuitem_activate(self, widget): def _on_contact_information_menuitem_activate(self, widget):
gajim.interface.roster.on_info(widget, self.contact, self.account) 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): def _on_convert_to_gc_menuitem_activate(self, widget):
""" """
User wants to invite some friends to chat User wants to invite some friends to chat
""" """
dialogs.TransformChatToMUC(self.account, [self.contact.jid]) dialogs.TransformChatToMUC(self.account, [self.contact.jid])
def _on_toggle_e2e_menuitem_activate(self, widget): def activate_esessions(self):
if self.session and self.session.enable_encryption: if not (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:
self.begin_e2e_negotiation() 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): def begin_negotiation(self):
self.no_autonegotiation = True self.no_autonegotiation = True

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,11 @@ 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()
if self.parent_win:
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 +418,41 @@ 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
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')
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 +764,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

@ -179,11 +179,6 @@ class StandardCommonChatCommands(CommandContainer):
def clear(self): def clear(self):
self.conv_textview.clear() self.conv_textview.clear()
@command
@doc(_("Toggle the OpenPGP encryption"))
def gpg(self):
self._toggle_gpg()
@command @command
@doc(_("Send a ping to the contact")) @doc(_("Send a ping to the contact"))
def ping(self): def ping(self):

View file

@ -480,8 +480,7 @@ class Config:
'state_muc_directed_msg_color': [ opt_color, 'red2' ], 'state_muc_directed_msg_color': [ opt_color, 'red2' ],
}, {}), }, {}),
'contacts': ({ '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')], 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')],
}, {}), }, {}),
'rooms': ({ 'rooms': ({

View file

@ -296,65 +296,9 @@ class CommonConnection:
if not obj.message and obj.chatstate is None and obj.form_node is None: if not obj.message and obj.chatstate is None and obj.form_node is None:
return return
if obj.keyID and self.USE_GPG:
self._encrypt_message(obj)
return
self._build_message_stanza(obj) self._build_message_stanza(obj)
def _encrypt_message(self, obj): def _build_message_stanza(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
if obj.jid == gajim.get_jid_from_account(self.name): if obj.jid == gajim.get_jid_from_account(self.name):
fjid = obj.jid fjid = obj.jid
else: else:
@ -368,44 +312,30 @@ class CommonConnection:
namespace=nbxmpp.NS_CORRECT) namespace=nbxmpp.NS_CORRECT)
id2 = self.connection.getAnID() id2 = self.connection.getAnID()
obj.correction_msg.setID(id2) obj.correction_msg.setID(id2)
obj.correction_msg.setBody(msgtxt) obj.correction_msg.setBody(obj.message)
if obj.xhtml: if obj.xhtml:
obj.correction_msg.setXHTML(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: if obj.session:
obj.session.last_send = time.time() 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) self._push_stanza_message_outgoing(obj, obj.correction_msg)
return return
if obj.type_ == 'chat': 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) xhtml=obj.xhtml)
else: else:
if obj.subject: 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) subject=obj.subject, xhtml=obj.xhtml)
else: else:
msg_iq = nbxmpp.Message(body=msgtxt, typ='normal', msg_iq = nbxmpp.Message(body=obj.message, typ='normal',
xhtml=obj.xhtml) xhtml=obj.xhtml)
if obj.msg_id: if obj.msg_id:
msg_iq.setID(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: if obj.form_node:
msg_iq.addChild(node=obj.form_node) msg_iq.addChild(node=obj.form_node)
if obj.label: if obj.label:
@ -460,7 +390,7 @@ class CommonConnection:
if obj.chatstate and contact and contact.supports(nbxmpp.NS_CHATSTATES): if obj.chatstate and contact and contact.supports(nbxmpp.NS_CHATSTATES):
msg_iq.setTag(obj.chatstate, namespace=nbxmpp.NS_CHATSTATES) msg_iq.setTag(obj.chatstate, namespace=nbxmpp.NS_CHATSTATES)
only_chatste = False only_chatste = False
if not msgtxt: if not obj.message:
only_chatste = True only_chatste = True
if only_chatste and not obj.session.enable_encryption: if only_chatste and not obj.session.enable_encryption:
msg_iq.setTag('no-store', msg_iq.setTag('no-store',
@ -468,8 +398,9 @@ class CommonConnection:
# XEP-0184 # XEP-0184
if obj.jid != gajim.get_jid_from_account(self.name): if obj.jid != gajim.get_jid_from_account(self.name):
if msgtxt and gajim.config.get_per('accounts', self.name, request = gajim.config.get_per('accounts', self.name,
'request_receipt'): 'request_receipt')
if obj.message and request:
msg_iq.setTag('request', namespace=nbxmpp.NS_RECEIPTS) msg_iq.setTag('request', namespace=nbxmpp.NS_RECEIPTS)
if obj.forward_from: if obj.forward_from:
@ -483,20 +414,6 @@ class CommonConnection:
obj.session.last_send = time.time() obj.session.last_send = time.time()
msg_iq.setThread(obj.session.thread_id) 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) 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):
@ -2136,6 +2053,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(
@ -2747,6 +2672,14 @@ class Connection(CommonConnection, ConnectionHandlers):
def _nec_gc_stanza_message_outgoing(self, obj): def _nec_gc_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(
'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: if obj.correction_msg:
obj.msg_id = self.connection.send(obj.correction_msg) obj.msg_id = self.connection.send(obj.correction_msg)
else: else:

View file

@ -897,7 +897,7 @@ class ConnectionHandlersBase:
gajim.ged.register_event_handler('message-received', ged.CORE, gajim.ged.register_event_handler('message-received', ged.CORE,
self._nec_message_received) self._nec_message_received)
gajim.ged.register_event_handler('mam-message-received', ged.CORE, 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, gajim.ged.register_event_handler('decrypted-message-received', ged.CORE,
self._nec_decrypted_message_received) self._nec_decrypted_message_received)
@ -911,7 +911,7 @@ class ConnectionHandlersBase:
gajim.ged.remove_event_handler('message-received', ged.CORE, gajim.ged.remove_event_handler('message-received', ged.CORE,
self._nec_message_received) self._nec_message_received)
gajim.ged.remove_event_handler('mam-message-received', ged.CORE, 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, gajim.ged.remove_event_handler('decrypted-message-received', ged.CORE,
self._nec_decrypted_message_received) self._nec_decrypted_message_received)
@ -1080,78 +1080,22 @@ class ConnectionHandlersBase:
if sess.enable_encryption: if sess.enable_encryption:
sess.terminate_e2e() 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): def _nec_message_received(self, obj):
if obj.conn.name != self.name: if obj.conn.name != self.name:
return return
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
if obj.enc_tag and self.USE_GPG: gajim.plugin_manager.gui_extension_point(
encmsg = obj.enc_tag.getData() 'decrypt', self, obj, self._on_message_received)
if not obj.encrypted:
self._on_message_received(obj)
keyID = gajim.config.get_per('accounts', self.name, 'keyid') def _on_message_received(self, obj):
if keyID: if isinstance(obj, MessageReceivedEvent):
self.gpg_messages_to_decrypt.append([encmsg, keyID, obj]) gajim.nec.push_incoming_event(
if len(self.gpg_messages_to_decrypt) == 1: DecryptedMessageReceivedEvent(None, conn=self, msg_obj=obj))
gajim.thread_interface(self.decrypt_thread, [encmsg, keyID, else:
obj], self._on_message_decrypted, [obj]) gajim.nec.push_incoming_event(
return MamDecryptedMessageReceivedEvent(None, conn=self, msg_obj=obj))
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 _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:

View file

@ -1035,6 +1035,7 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
def init(self): def init(self):
self.additional_data = {} self.additional_data = {}
self.encrypted = False
def generate(self): def generate(self):
if not self.stanza: if not self.stanza:
@ -1067,7 +1068,6 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
self.with_ = to_ self.with_ = to_
self.direction = 'to' self.direction = 'to'
self.resource = gajim.get_resource_from_jid(self.msg_.getAttr('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 return True
class MamDecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): class MamDecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
@ -1122,6 +1122,7 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
self.get_id() self.get_id()
self.forwarded = False self.forwarded = False
self.sent = False self.sent = False
self.encrypted = False
account = self.conn.name account = self.conn.name
our_full_jid = gajim.get_jid_from_account(account, full=True) our_full_jid = gajim.get_jid_from_account(account, full=True)
@ -1216,33 +1217,31 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
conn=self.conn, stanza=forwarded)) conn=self.conn, stanza=forwarded))
return return
self.enc_tag = self.stanza.getTag('x', namespace=nbxmpp.NS_ENCRYPTED) # Mediated invitation?
if not self.enc_tag: muc_user = self.stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER)
# Mediated invitation? if muc_user:
muc_user = self.stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER) if muc_user.getTag('decline'):
if muc_user: gajim.nec.push_incoming_event(
if muc_user.getTag('decline'): GcDeclineReceivedEvent(
gajim.nec.push_incoming_event( None, conn=self.conn,
GcDeclineReceivedEvent( room_jid=self.fjid, stanza=muc_user))
None, conn=self.conn, return
room_jid=self.fjid, stanza=muc_user)) if muc_user.getTag('invite'):
return gajim.nec.push_incoming_event(
if muc_user.getTag('invite'): GcInvitationReceivedEvent(
gajim.nec.push_incoming_event( None, conn=self.conn, jid_from=self.fjid,
GcInvitationReceivedEvent( mediated=True, stanza=muc_user))
None, conn=self.conn, jid_from=self.fjid, return
mediated=True, stanza=muc_user)) else:
return # Direct invitation?
else: direct = self.stanza.getTag(
# Direct invitation? 'x', namespace=nbxmpp.NS_CONFERENCE)
direct = self.stanza.getTag( if direct:
'x', namespace=nbxmpp.NS_CONFERENCE) gajim.nec.push_incoming_event(
if direct: GcInvitationReceivedEvent(
gajim.nec.push_incoming_event( None, conn=self.conn, jid_from=self.fjid,
GcInvitationReceivedEvent( mediated=False, stanza=direct))
None, conn=self.conn, jid_from=self.fjid, return
mediated=False, stanza=direct))
return
self.thread_id = self.stanza.getThread() self.thread_id = self.stanza.getThread()
self.mtype = self.stanza.getType() self.mtype = self.stanza.getType()
@ -1283,52 +1282,8 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
self.session.last_receive = time_time() 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._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 return True
class ZeroconfMessageReceivedEvent(MessageReceivedEvent): class ZeroconfMessageReceivedEvent(MessageReceivedEvent):
@ -2796,6 +2751,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

@ -5315,140 +5315,6 @@ class DataFormWindow(Dialog):
self.df_response_ok(form) self.df_response_ok(form)
self.destroy() 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 <b>%(jid)s</b> is encrypted.\n\nThis session's Short Authentication String is <b>%(sas)s</b>.''') % {'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(
'<b><span size="x-large">%s</span></b>' % 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 <b>only</b> 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(
'<b><span size="x-large">%s</span></b>' % 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 <b>%(jid)s</b> 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 <b>%(sas)s</b>.''') % {'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 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) <b>does not match</b> 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 <b>you '
'do not trust their key</b>, so message <b>cannot</b> 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('<b><span size="x-large">%s</span></b>' % \
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): class ResourceConflictDialog(TimeoutDialog, InputDialog):
def __init__(self, title, text, resource, ok_handler): def __init__(self, title, text, resource, ok_handler):

View file

@ -257,9 +257,8 @@ class PrivateChatControl(ChatControl):
def prepare_context_menu(self, hide_buttonbar_items=False): def prepare_context_menu(self, hide_buttonbar_items=False):
""" """
Set compact view menuitem active state sets active and sensitivity state Set compact view menuitem active state sets active and sensitivity state
for toggle_gpg_menuitem sets sensitivity for history_menuitem (False for for history_menuitem (False for tranasports) and file_transfer_menuitem
tranasports) and file_transfer_menuitem and hide()/show() for and hide()/show() for add_to_roster_menuitem
add_to_roster_menuitem
""" """
menu = gui_menu_builder.get_contact_menu(self.contact, self.account, menu = gui_menu_builder.get_contact_menu(self.contact, self.account,
use_multiple_contacts=False, show_start_chat=False, use_multiple_contacts=False, show_start_chat=False,
@ -482,6 +481,19 @@ class GroupchatControl(ChatControlBase):
self.form_widget = None 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, gajim.ged.register_event_handler('gc-presence-received', ged.GUI1,
self._nec_gc_presence_received) self._nec_gc_presence_received)
gajim.ged.register_event_handler('gc-message-received', ged.GUI1, gajim.ged.register_event_handler('gc-message-received', ged.GUI1,
@ -509,6 +521,11 @@ class GroupchatControl(ChatControlBase):
# instance object # instance object
gajim.plugin_manager.gui_extension_point('groupchat_control', self) 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): def set_tooltip(self):
widget = self.xml.get_object('list_treeview') widget = self.xml.get_object('list_treeview')
if widget.get_tooltip_window(): if widget.get_tooltip_window():
@ -752,6 +769,42 @@ class GroupchatControl(ChatControlBase):
for nick in gajim.contacts.get_nick_list(self.account, self.room_jid): for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
self.draw_contact(nick) 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): def _change_style(self, model, path, iter_, option):
model[iter_][Column.NICK] = model[iter_][Column.NICK] model[iter_][Column.NICK] = model[iter_][Column.NICK]
@ -1967,6 +2020,13 @@ class GroupchatControl(ChatControlBase):
if not message: if not message:
return 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): if process_commands and self.process_as_command(message):
return return

View file

@ -245,9 +245,6 @@ control=None, gc_contact=None, is_anonymous=True):
'remove_from_roster_menuitem') 'remove_from_roster_menuitem')
manage_contact_menuitem = xml.get_object('manage_contact') manage_contact_menuitem = xml.get_object('manage_contact')
convert_to_gc_menuitem = xml.get_object('convert_to_groupchat_menuitem') 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') last_separator = xml.get_object('last_separator')
items_to_hide = [] items_to_hide = []
@ -322,36 +319,6 @@ control=None, gc_contact=None, is_anonymous=True):
if not show_start_chat: if not show_start_chat:
items_to_hide.append(start_chat_menuitem) 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]
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)
if not show_buttonbar_items: if not show_buttonbar_items:
items_to_hide += [history_menuitem, send_file_menuitem, items_to_hide += [history_menuitem, send_file_menuitem,
information_menuitem, convert_to_gc_menuitem, last_separator] information_menuitem, convert_to_gc_menuitem, last_separator]
@ -781,3 +748,23 @@ 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, type_id):
menu = Gio.Menu()
menu.append(
'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, name)
menu.append(name, menu_action)
if menu.get_n_items() == 1:
return None
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 \

View file

@ -3220,7 +3220,7 @@ class RosterWindow:
ctrl._on_window_motion_notify) ctrl._on_window_motion_notify)
ctrl.handlers[id_] = mw.window ctrl.handlers[id_] = mw.window
ctrl.parent_win = mw ctrl.parent_win = mw
ctrl.set_tooltip() ctrl.on_groupchat_maximize()
mw.new_tab(ctrl) mw.new_tab(ctrl)
mw.set_active_tab(ctrl) mw.set_active_tab(ctrl)
mw.window.get_window().focus(Gtk.get_current_event_time()) mw.window.get_window().focus(Gtk.get_current_event_time())