Merge branch 'extensionpoints' into 'master'
New Encryption Plugin API See merge request !96
This commit is contained in:
commit
8ec3ec8a58
17 changed files with 373 additions and 601 deletions
|
@ -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>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?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 @@ class ChatControl(ChatControlBase):
|
|||
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 @@ class ChatControl(ChatControlBase):
|
|||
|
||||
# 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 @@ class ChatControl(ChatControlBase):
|
|||
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 @@ class ChatControl(ChatControlBase):
|
|||
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
|
||||
def set_lock_image(self):
|
||||
visible = self.encryption != 'disabled'
|
||||
loggable = self.session and self.session.is_loggable()
|
||||
|
||||
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)
|
||||
encryption_state = {'visible': visible,
|
||||
'enc_type': self.encryption,
|
||||
'authenticated': False}
|
||||
|
||||
loggable = gajim.config.get_per('accounts', self.account,
|
||||
'log_encrypted_sessions')
|
||||
gajim.plugin_manager.gui_extension_point(
|
||||
'encryption_state' + self.encryption, self, encryption_state)
|
||||
|
||||
if self.session:
|
||||
self.session.loggable = loggable
|
||||
self._show_lock_image(**encryption_state)
|
||||
|
||||
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)
|
||||
|
||||
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):
|
||||
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 @@ class ChatControl(ChatControlBase):
|
|||
|
||||
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 @@ class ChatControl(ChatControlBase):
|
|||
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 @@ class ChatControl(ChatControlBase):
|
|||
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 @@ class ChatControl(ChatControlBase):
|
|||
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 @@ class ChatControl(ChatControlBase):
|
|||
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 @@ class ChatControl(ChatControlBase):
|
|||
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,31 +1595,29 @@ class ChatControl(ChatControlBase):
|
|||
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
|
||||
|
||||
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:
|
||||
def activate_esessions(self):
|
||||
if not (self.session and self.session.enable_encryption):
|
||||
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):
|
||||
self.no_autonegotiation = True
|
||||
|
||||
|
|
|
@ -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,11 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
|||
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 @@ 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
|
||||
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):
|
||||
# now set the one the user selected
|
||||
per_type = 'contacts'
|
||||
|
@ -723,7 +764,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')
|
||||
|
|
|
@ -179,11 +179,6 @@ class StandardCommonChatCommands(CommandContainer):
|
|||
def clear(self):
|
||||
self.conv_textview.clear()
|
||||
|
||||
@command
|
||||
@doc(_("Toggle the OpenPGP encryption"))
|
||||
def gpg(self):
|
||||
self._toggle_gpg()
|
||||
|
||||
@command
|
||||
@doc(_("Send a ping to the contact"))
|
||||
def ping(self):
|
||||
|
|
|
@ -480,8 +480,7 @@ class Config:
|
|||
'state_muc_directed_msg_color': [ opt_color, 'red2' ],
|
||||
}, {}),
|
||||
'contacts': ({
|
||||
'gpg_enabled': [ opt_bool, False, _('Is OpenPGP enabled for this contact?')],
|
||||
'autonegotiate_esessions': [opt_bool, False, _('Should Gajim automatically start an encrypted session with this contact when possible?')],
|
||||
'encryption': [ opt_str, '', _('Encryption used for this contact.')],
|
||||
'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')],
|
||||
}, {}),
|
||||
'rooms': ({
|
||||
|
|
|
@ -296,65 +296,9 @@ class CommonConnection:
|
|||
if not obj.message and obj.chatstate is None and obj.form_node is None:
|
||||
return
|
||||
|
||||
if obj.keyID and self.USE_GPG:
|
||||
self._encrypt_message(obj)
|
||||
return
|
||||
|
||||
self._build_message_stanza(obj)
|
||||
|
||||
def _encrypt_message(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
|
||||
|
||||
def _build_message_stanza(self, obj):
|
||||
if obj.jid == gajim.get_jid_from_account(self.name):
|
||||
fjid = obj.jid
|
||||
else:
|
||||
|
@ -368,44 +312,30 @@ class CommonConnection:
|
|||
namespace=nbxmpp.NS_CORRECT)
|
||||
id2 = self.connection.getAnID()
|
||||
obj.correction_msg.setID(id2)
|
||||
obj.correction_msg.setBody(msgtxt)
|
||||
obj.correction_msg.setBody(obj.message)
|
||||
if 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:
|
||||
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)
|
||||
return
|
||||
|
||||
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)
|
||||
else:
|
||||
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)
|
||||
else:
|
||||
msg_iq = nbxmpp.Message(body=msgtxt, typ='normal',
|
||||
msg_iq = nbxmpp.Message(body=obj.message, typ='normal',
|
||||
xhtml=obj.xhtml)
|
||||
|
||||
if 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:
|
||||
msg_iq.addChild(node=obj.form_node)
|
||||
if obj.label:
|
||||
|
@ -460,7 +390,7 @@ class CommonConnection:
|
|||
if obj.chatstate and contact and contact.supports(nbxmpp.NS_CHATSTATES):
|
||||
msg_iq.setTag(obj.chatstate, namespace=nbxmpp.NS_CHATSTATES)
|
||||
only_chatste = False
|
||||
if not msgtxt:
|
||||
if not obj.message:
|
||||
only_chatste = True
|
||||
if only_chatste and not obj.session.enable_encryption:
|
||||
msg_iq.setTag('no-store',
|
||||
|
@ -468,8 +398,9 @@ class CommonConnection:
|
|||
|
||||
# XEP-0184
|
||||
if obj.jid != gajim.get_jid_from_account(self.name):
|
||||
if msgtxt and gajim.config.get_per('accounts', self.name,
|
||||
'request_receipt'):
|
||||
request = gajim.config.get_per('accounts', self.name,
|
||||
'request_receipt')
|
||||
if obj.message and request:
|
||||
msg_iq.setTag('request', namespace=nbxmpp.NS_RECEIPTS)
|
||||
|
||||
if obj.forward_from:
|
||||
|
@ -483,20 +414,6 @@ class CommonConnection:
|
|||
obj.session.last_send = time.time()
|
||||
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)
|
||||
|
||||
def _push_stanza_message_outgoing(self, obj, msg_iq):
|
||||
|
@ -2136,6 +2053,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(
|
||||
|
@ -2747,6 +2672,14 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
def _nec_gc_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(
|
||||
'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:
|
||||
obj.msg_id = self.connection.send(obj.correction_msg)
|
||||
else:
|
||||
|
|
|
@ -897,7 +897,7 @@ class ConnectionHandlersBase:
|
|||
gajim.ged.register_event_handler('message-received', ged.CORE,
|
||||
self._nec_message_received)
|
||||
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,
|
||||
self._nec_decrypted_message_received)
|
||||
|
||||
|
@ -911,7 +911,7 @@ class ConnectionHandlersBase:
|
|||
gajim.ged.remove_event_handler('message-received', ged.CORE,
|
||||
self._nec_message_received)
|
||||
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,
|
||||
self._nec_decrypted_message_received)
|
||||
|
||||
|
@ -1080,78 +1080,22 @@ class ConnectionHandlersBase:
|
|||
if sess.enable_encryption:
|
||||
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):
|
||||
if obj.conn.name != self.name:
|
||||
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:
|
||||
encmsg = obj.enc_tag.getData()
|
||||
gajim.plugin_manager.gui_extension_point(
|
||||
'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')
|
||||
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_message_decrypted, [obj])
|
||||
return
|
||||
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 _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:
|
||||
|
|
|
@ -1035,6 +1035,7 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
|
|||
|
||||
def init(self):
|
||||
self.additional_data = {}
|
||||
self.encrypted = False
|
||||
|
||||
def generate(self):
|
||||
if not self.stanza:
|
||||
|
@ -1067,7 +1068,6 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
|
|||
self.with_ = to_
|
||||
self.direction = '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
|
||||
|
||||
class MamDecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
|
||||
|
@ -1122,6 +1122,7 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
|
|||
self.get_id()
|
||||
self.forwarded = False
|
||||
self.sent = False
|
||||
self.encrypted = False
|
||||
account = self.conn.name
|
||||
|
||||
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))
|
||||
return
|
||||
|
||||
self.enc_tag = self.stanza.getTag('x', namespace=nbxmpp.NS_ENCRYPTED)
|
||||
if not self.enc_tag:
|
||||
# Mediated invitation?
|
||||
muc_user = self.stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER)
|
||||
if muc_user:
|
||||
if muc_user.getTag('decline'):
|
||||
gajim.nec.push_incoming_event(
|
||||
GcDeclineReceivedEvent(
|
||||
None, conn=self.conn,
|
||||
room_jid=self.fjid, stanza=muc_user))
|
||||
return
|
||||
if muc_user.getTag('invite'):
|
||||
gajim.nec.push_incoming_event(
|
||||
GcInvitationReceivedEvent(
|
||||
None, conn=self.conn, jid_from=self.fjid,
|
||||
mediated=True, stanza=muc_user))
|
||||
return
|
||||
else:
|
||||
# Direct invitation?
|
||||
direct = self.stanza.getTag(
|
||||
'x', namespace=nbxmpp.NS_CONFERENCE)
|
||||
if direct:
|
||||
gajim.nec.push_incoming_event(
|
||||
GcInvitationReceivedEvent(
|
||||
None, conn=self.conn, jid_from=self.fjid,
|
||||
mediated=False, stanza=direct))
|
||||
return
|
||||
# Mediated invitation?
|
||||
muc_user = self.stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER)
|
||||
if muc_user:
|
||||
if muc_user.getTag('decline'):
|
||||
gajim.nec.push_incoming_event(
|
||||
GcDeclineReceivedEvent(
|
||||
None, conn=self.conn,
|
||||
room_jid=self.fjid, stanza=muc_user))
|
||||
return
|
||||
if muc_user.getTag('invite'):
|
||||
gajim.nec.push_incoming_event(
|
||||
GcInvitationReceivedEvent(
|
||||
None, conn=self.conn, jid_from=self.fjid,
|
||||
mediated=True, stanza=muc_user))
|
||||
return
|
||||
else:
|
||||
# Direct invitation?
|
||||
direct = self.stanza.getTag(
|
||||
'x', namespace=nbxmpp.NS_CONFERENCE)
|
||||
if direct:
|
||||
gajim.nec.push_incoming_event(
|
||||
GcInvitationReceivedEvent(
|
||||
None, conn=self.conn, jid_from=self.fjid,
|
||||
mediated=False, stanza=direct))
|
||||
return
|
||||
|
||||
self.thread_id = self.stanza.getThread()
|
||||
self.mtype = self.stanza.getType()
|
||||
|
@ -1283,52 +1282,8 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
|
|||
|
||||
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.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
|
||||
|
||||
class ZeroconfMessageReceivedEvent(MessageReceivedEvent):
|
||||
|
@ -2796,6 +2751,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:
|
||||
|
|
134
src/dialogs.py
134
src/dialogs.py
|
@ -5315,140 +5315,6 @@ class DataFormWindow(Dialog):
|
|||
self.df_response_ok(form)
|
||||
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):
|
||||
def __init__(self, title, text, resource, ok_handler):
|
||||
|
|
|
@ -257,9 +257,8 @@ class PrivateChatControl(ChatControl):
|
|||
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
|
||||
"""
|
||||
menu = gui_menu_builder.get_contact_menu(self.contact, self.account,
|
||||
use_multiple_contacts=False, show_start_chat=False,
|
||||
|
@ -482,6 +481,19 @@ class GroupchatControl(ChatControlBase):
|
|||
|
||||
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,
|
||||
self._nec_gc_presence_received)
|
||||
gajim.ged.register_event_handler('gc-message-received', ged.GUI1,
|
||||
|
@ -509,6 +521,11 @@ class GroupchatControl(ChatControlBase):
|
|||
# instance object
|
||||
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):
|
||||
widget = self.xml.get_object('list_treeview')
|
||||
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):
|
||||
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):
|
||||
model[iter_][Column.NICK] = model[iter_][Column.NICK]
|
||||
|
||||
|
@ -1967,6 +2020,13 @@ class GroupchatControl(ChatControlBase):
|
|||
if not message:
|
||||
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):
|
||||
return
|
||||
|
||||
|
|
|
@ -245,9 +245,6 @@ control=None, gc_contact=None, is_anonymous=True):
|
|||
'remove_from_roster_menuitem')
|
||||
manage_contact_menuitem = xml.get_object('manage_contact')
|
||||
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')
|
||||
|
||||
items_to_hide = []
|
||||
|
@ -322,36 +319,6 @@ control=None, gc_contact=None, is_anonymous=True):
|
|||
if not show_start_chat:
|
||||
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:
|
||||
items_to_hide += [history_menuitem, send_file_menuitem,
|
||||
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()
|
||||
menu.remove(1)
|
||||
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
|
||||
|
|
|
@ -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 = ''
|
||||
'''
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -3220,7 +3220,7 @@ class RosterWindow:
|
|||
ctrl._on_window_motion_notify)
|
||||
ctrl.handlers[id_] = mw.window
|
||||
ctrl.parent_win = mw
|
||||
ctrl.set_tooltip()
|
||||
ctrl.on_groupchat_maximize()
|
||||
mw.new_tab(ctrl)
|
||||
mw.set_active_tab(ctrl)
|
||||
mw.window.get_window().focus(Gtk.get_current_event_time())
|
||||
|
|
Loading…
Add table
Reference in a new issue