diff --git a/omemo/config_dialog.ui b/omemo/config_dialog.ui index 7c254aa..a5b6b10 100644 --- a/omemo/config_dialog.ui +++ b/omemo/config_dialog.ui @@ -1,5 +1,5 @@ - + @@ -20,51 +20,116 @@ - - - - - - - - - - - - - - True True - + True False - 12 - 10 + 18 + vertical + 6 - - True + False - 5 - - - True + 6 + False + + False - <b>Account:</b> - True + 6 + end + + + False - True + False 0 + + + False + start + 6 + + + True + False + True + start + For verification via QR-Code you have to install + True + + + False + True + 0 + + + + + True + False + start + python-qrcode + True + + + True + True + 1 + + + + + False + False + 0 + + + + + False + True + 0 + + + + + True + False + start + 6 + 12 + + + True + False + end + Acc_ount + True + account_combobox + + + + 0 + 0 + + + 200 True False + True + start account_store @@ -75,52 +140,129 @@ - False - True - 1 + 1 + 0 - - - False - True - 0 - - - - - True - False - - 110 + True False - Own Fingerprint: - 0 - - - + end + 6 + Own _Fingerprint + True + fingerprint_label + - False - True - 0 + 0 + 1 + 200 + 30 True False + start + 6 True True - False - True - 1 + 1 + 1 + + + True + False + end + Own _Device ID + True + ID + + + + 0 + 2 + + + + + 200 + 30 + True + False + start + True + 0 + + + 1 + 2 + + + + + False + True + Scan this QR-Code with your mobile device for easy verification + start + 6 + 6 + gtk-missing-image + 1 + + + 1 + 3 + + + + + True + False + OMEMO + start + omemo.png + + + 0 + 3 + + + + + True + False + start + Note: Fingerprints of your contacts are managed in the message window. + True + + + + + + + 1 + 4 + + + + + False @@ -128,81 +270,10 @@ 1 - - - True - False - - - 110 - True - False - Own Device ID: - 0 - - - - - - False - False - 0 - - - - - True - False - 0 - - - False - True - 1 - - - - - False - False - 2 - - - - - False - True - start - gtk-missing-image - 1 - - - True - True - 3 - - - - - False - True - For Verification QRCode please install python-qrcode - 0 - - - - - - - True - True - 4 - - - + True False Own Fingerprints @@ -212,156 +283,27 @@ - + True False - 12 - 10 + 18 + vertical + 6 - - 200 - True - True - - - 300 - True - True - fingerprint_store - 0 - 3 - - - - - - - True - Name - True - True - 1 - - - - 1 - - - - - - - True - Trust - - - - 2 - - - - - - - True - Fingerprint - - - - 3 - - - - - - - - - True - True - 0 - - - - - True - False - 5 - - - Trust/Revoke Fingerprint - 200 - True - True - True - - - - False - False - 0 - - - - - Delete Fingerprint - 200 - True - True - True - - - - False - False - 1 - - - - - False - False - 1 - - - - - 1 - - - - - True - False - Known Fingerprints - - - 1 - False - - - - - True - False - 12 - 10 - - + 25 True False + center Published Devices - 0 - - - - + False True - 7 0 @@ -369,19 +311,23 @@ True True + center never + 300 True False deviceid_store 0 + horizontal Device ID + True @@ -400,18 +346,26 @@ - + True False - 5 + center + 6 + 12 - Clear Devices - 160 + _Clear Devices True True True + This clears your device list from the server. +Clearing the device list helps you to remove unused devices from the encryption process. +It is advised to go online with all of your actively used devices after clearing. + True + False @@ -438,40 +392,79 @@ False - False + True 2 - 2 + 1 - + True False Clear Devices - 2 + 1 False - + True False + 18 + vertical + 6 - - 30 + True False - You have to restart Gajim for changes to take effect ! - - - - + warning + + + False + 6 + end + + + + + + False + False + 0 + + + + + False + + + 30 + True + False + You have to restart Gajim for changes to take effect ! + + + + False + True + 0 + + + + + False + False + 0 + + False @@ -480,137 +473,126 @@ - + True False - 12 - 5 + 6 + 12 + True - + + 200 True - False + True + out - + True True + True + account_store + horizontal + + + - - True - True - account_store - - - + + Active Accounts + 0.5 - - Active Accounts - 0.5 - - - - 0 - - - + + + 0 + - - True - True - 0 - - - - - Disable Account - True - True - True - - - - False - False - 1 - - True - True - 0 + 0 + 0 - + True - False + True + out - + True True + True + disabled_account_store + horizontal + + + - - True - True - disabled_account_store - - - + + Disabled Accounts + 0.5 - - Disabled Accounts - 0.5 - - - - 0 - - - + + + 0 + - - True - True - 0 - - - - - Activate Account - True - True - True - - - - False - False - 1 - - True - True - 1 + 1 + 0 + + + + + _Disable Account + True + True + True + center + True + + + + + 0 + 1 + + + + + _Enable Account + True + True + True + center + True + + + + 1 + 1 - True + False True 1 - 3 + 2 @@ -620,22 +602,23 @@ Disable Accounts - 3 + 2 False - - True - False - - - True - False - Copy to clipboard - True - - - + + + + + + + + + + + + + diff --git a/omemo/download_progress_dialog.ui b/omemo/download_progress_dialog.ui index d7021b7..69a4aaf 100644 --- a/omemo/download_progress_dialog.ui +++ b/omemo/download_progress_dialog.ui @@ -1,21 +1,25 @@ - + - + True + 18 Download False center-on-parent True go-down dialog + + + 250 True False - 6 + 12 True @@ -55,6 +59,20 @@ 0 + + + True + False + OMEMO + 6 + omemo.png + + + False + True + 0 + + True diff --git a/omemo/fpr_dialog.ui b/omemo/fpr_dialog.ui deleted file mode 100644 index c75e700..0000000 --- a/omemo/fpr_dialog.ui +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - - - - - - - - - - - - True - False - 18 - 18 - 18 - 18 - vertical - 12 - - - True - False - 6 - - - True - False - Own Fingerprint: - - - - - - False - True - 0 - - - - - True - False - <tt>-------- -------- -------- -------- -------- </tt> - True - True - - - False - False - 1 - - - - - False - True - 0 - - - - - True - False - vertical - 12 - - - 200 - True - True - - - True - True - True - fingerprint_store - False - 0 - 3 - - - - - - - True - Name - True - True - 1 - - - - 1 - - - - - - - True - Trust - - - - 2 - - - - - - - True - Fingerprint - - - - 3 - - - - - - - - - True - True - 0 - - - - - True - False - start - - - Trust/Revoke Fingerprint - 200 - True - True - True - - - - False - False - 0 - - - - - False - True - 1 - - - - - True - True - 1 - - - - - True - False - - - True - False - Copy to clipboard - True - - - - - diff --git a/omemo/gtk/__init__.py b/omemo/gtk/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/omemo/gtk/key.py b/omemo/gtk/key.py new file mode 100644 index 0000000..ad2922c --- /dev/null +++ b/omemo/gtk/key.py @@ -0,0 +1,349 @@ +# Copyright (C) 2018 Philipp Hörist +# +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Gajim. If not, see . + +import logging +import binascii +import textwrap + +from gi.repository import Gtk +from gi.repository import GdkPixbuf + +from gajim.common import app +from gajim.plugins.plugins_i18n import _ + +from omemo.gtk.util import DialogButton, ButtonAction +from omemo.gtk.util import NewConfirmationDialog +from omemo.gtk.util import Trust + +log = logging.getLogger('gajim.plugin_system.omemo') + +TRUST_DATA = { + Trust.NOT_TRUSTED: ('dialog-error-symbolic', + _('Not Trusted'), + 'error-color'), + Trust.UNKNOWN: ('security-low-symbolic', + _('Not Decided'), + 'warning-color'), + Trust.VERIFIED: ('security-high-symbolic', + _('Trusted'), + 'success-color') +} + + +class KeyDialog(Gtk.Dialog): + def __init__(self, plugin, contact, transient, windowinstances, + groupchat=False): + flags = Gtk.DialogFlags.DESTROY_WITH_PARENT + super().__init__(_('OMEMO Fingerprints'), None, flags) + + self.set_transient_for(transient) + self.set_resizable(True) + self.set_default_size(-1, 400) + + self.get_style_context().add_class('omemo-key-dialog') + + self._groupchat = groupchat + self._contact = contact + self._windowinstances = windowinstances + self._account = self._contact.account.name + self._plugin = plugin + self._con = plugin.connections[self._account] + self.omemostate = self._plugin.get_omemo(self._account) + self._own_jid = app.get_jid_from_account(self._account) + + # Header + jid = self._contact.jid + self._header = Gtk.Label(_('Fingerprints for %s') % jid) + self._header.get_style_context().add_class('bold') + self._header.get_style_context().add_class('dim-label') + + # Fingerprints list + self._listbox = Gtk.ListBox() + self._listbox.set_selection_mode(Gtk.SelectionMode.NONE) + + self._scrolled = Gtk.ScrolledWindow() + self._scrolled.set_policy(Gtk.PolicyType.NEVER, + Gtk.PolicyType.AUTOMATIC) + self._scrolled.add(self._listbox) + + # Own fingerprint + self._label = Gtk.Label(_('Own Fingerprint')) + self._label.get_style_context().add_class('bold') + self._label.get_style_context().add_class('dim-label') + + self._omemo_logo = Gtk.Image() + omemo_img_path = self._plugin.local_file_path('omemo.png') + omemo_pixbuf = GdkPixbuf.Pixbuf.new_from_file(omemo_img_path) + self._omemo_logo.set_from_pixbuf(omemo_pixbuf) + + ownfpr = binascii.hexlify(self.omemostate.store.getIdentityKeyPair() + .getPublicKey().serialize()).decode('utf-8') + ownfpr_format = KeyRow._format_fingerprint(ownfpr[2:]) + self._ownfpr = Gtk.Label(ownfpr_format) + self._ownfpr.get_style_context().add_class('omemo-mono') + self._ownfpr.set_selectable(True) + + self._ownfpr_box = Gtk.Box(spacing=12) + self._ownfpr_box.set_halign(Gtk.Align.CENTER) + self._ownfpr_box.pack_start(self._omemo_logo, True, True, 0) + self._ownfpr_box.pack_start(self._ownfpr, True, True, 0) + + box = self.get_content_area() + box.set_orientation(Gtk.Orientation.VERTICAL) + box.set_spacing(12) + box.pack_start(self._header, False, True, 0) + box.pack_start(self._scrolled, True, True, 0) + box.pack_start(self._label, False, True, 0) + box.pack_start(self._ownfpr_box, False, True, 0) + + self.update() + self.connect('destroy', self._on_destroy) + self.show_all() + + def update(self): + self._listbox.foreach(lambda row: self._listbox.remove(row)) + self._load_fingerprints(self._own_jid) + self._load_fingerprints(self._contact.jid, self._groupchat is True) + + def _load_fingerprints(self, contact_jid, groupchat=False): + from axolotl.state.sessionrecord import SessionRecord + state = self.omemostate + + if groupchat: + contact_jids = [] + for nick in self._con.groupchat[contact_jid]: + real_jid = self._con.groupchat[contact_jid][nick] + if real_jid == self._own_jid: + continue + contact_jids.append(real_jid) + session_db = state.store.getSessionsFromJids(contact_jids) + else: + session_db = state.store.getSessionsFromJid(contact_jid) + + for item in session_db: + _id, jid, deviceid, record, active = item + + active = bool(active) + + identity_key = SessionRecord(serialized=record). \ + getSessionState().getRemoteIdentityKey() + fpr = binascii.hexlify(identity_key.getPublicKey().serialize()).decode('utf-8') + fpr = fpr[2:] + trust = state.store.isTrustedIdentity(jid, identity_key) + + log.info('Load: %s %s', fpr, trust) + self._listbox.add(KeyRow(jid, deviceid, fpr, trust, active)) + + def _on_destroy(self, *args): + del self._windowinstances['dialog'] + + +class KeyRow(Gtk.ListBoxRow): + def __init__(self, jid, deviceid, fpr, trust, active): + Gtk.ListBoxRow.__init__(self) + self.set_activatable(False) + + self.active = active + self.trust = trust + self.jid = jid + self.deviceid = deviceid + + box = Gtk.Box() + box.set_spacing(12) + + self._trust_button = TrustButton(self) + box.add(self._trust_button) + + label_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + jid_label = Gtk.Label(jid) + jid_label.get_style_context().add_class('dim-label') + jid_label.set_selectable(False) + jid_label.set_halign(Gtk.Align.START) + jid_label.set_valign(Gtk.Align.START) + jid_label.set_hexpand(True) + label_box.add(jid_label) + + fingerprint = Gtk.Label( + label=self._format_fingerprint(fpr)) + fingerprint.get_style_context().add_class('omemo-mono') + if not active: + fingerprint.get_style_context().add_class('omemo-inactive-color') + fingerprint.set_selectable(True) + fingerprint.set_halign(Gtk.Align.START) + fingerprint.set_valign(Gtk.Align.START) + fingerprint.set_hexpand(True) + label_box.add(fingerprint) + + box.add(label_box) + + self.add(box) + self.show_all() + + def delete_fingerprint(self, *args): + def _remove(): + state = self.get_toplevel().omemostate + record = state.store.loadSession(self.jid, self.deviceid) + identity_key = record.getSessionState().getRemoteIdentityKey() + + state.store.deleteSession(self.jid, self.deviceid) + state.store.deleteIdentity(self.jid, identity_key) + self.get_parent().remove(self) + self.destroy() + + buttons = { + Gtk.ResponseType.CANCEL: DialogButton(_('Cancel')), + Gtk.ResponseType.OK: DialogButton(_('Delete'), + _remove, + ButtonAction.DESTRUCTIVE), + } + + NewConfirmationDialog( + _('Delete Fingerprint'), + _('Doing so will permanently delete this Fingerprint'), + buttons, + transient_for=self.get_toplevel()) + + def set_trust(self): + icon_name, tooltip, css_class = TRUST_DATA[self.trust] + image = self._trust_button.get_child() + image.set_from_icon_name(icon_name, Gtk.IconSize.MENU) + image.get_style_context().add_class(css_class) + image.set_tooltip_text(tooltip) + + state = self.get_toplevel().omemostate + record = state.store.loadSession(self.jid, self.deviceid) + identity_key = record.getSessionState().getRemoteIdentityKey() + state.store.setTrust(identity_key, self.trust) + + @staticmethod + def _format_fingerprint(fingerprint): + fplen = len(fingerprint) + wordsize = fplen // 8 + buf = '' + for w in range(0, fplen, wordsize): + buf += '{0} '.format(fingerprint[w:w + wordsize]) + buf = textwrap.fill(buf, width=36) + return buf.rstrip().upper() + + +class TrustButton(Gtk.MenuButton): + def __init__(self, row): + Gtk.MenuButton.__init__(self) + self._row = row + self._css_class = '' + self.set_popover(TrustPopver(row)) + self.set_valign(Gtk.Align.CENTER) + self.update() + + def update(self): + icon_name, tooltip, css_class = TRUST_DATA[self._row.trust] + image = self.get_child() + image.set_from_icon_name(icon_name, Gtk.IconSize.MENU) + # Remove old color from icon + image.get_style_context().remove_class(self._css_class) + + if not self._row.active: + css_class = 'omemo-inactive-color' + tooltip = '%s - %s' % (_('Inactive'), tooltip) + + image.get_style_context().add_class(css_class) + self._css_class = css_class + self.set_tooltip_text(tooltip) + + +class TrustPopver(Gtk.Popover): + def __init__(self, row): + Gtk.Popover.__init__(self) + self._row = row + self._listbox = Gtk.ListBox() + self._listbox.set_selection_mode(Gtk.SelectionMode.NONE) + if row.trust != Trust.VERIFIED: + self._listbox.add(VerifiedOption()) + if row.trust != Trust.NOT_TRUSTED: + self._listbox.add(NotTrustedOption()) + self._listbox.add(DeleteOption()) + self.add(self._listbox) + self._listbox.show_all() + self._listbox.connect('row-activated', self._activated) + self.get_style_context().add_class('omemo-trust-popover') + + def _activated(self, listbox, row): + self.popdown() + if row.type_ is None: + self._row.delete_fingerprint() + else: + self._row.trust = row.type_ + self._row.set_trust() + self.get_relative_to().update() + self.update() + + def update(self): + self._listbox.foreach(lambda row: self._listbox.remove(row)) + if self._row.trust != Trust.VERIFIED: + self._listbox.add(VerifiedOption()) + if self._row.trust != Trust.NOT_TRUSTED: + self._listbox.add(NotTrustedOption()) + self._listbox.add(DeleteOption()) + + +class MenuOption(Gtk.ListBoxRow): + def __init__(self): + Gtk.ListBoxRow.__init__(self) + box = Gtk.Box() + box.set_spacing(6) + + image = Gtk.Image.new_from_icon_name(self.icon, + Gtk.IconSize.MENU) + label = Gtk.Label(label=self.label) + image.get_style_context().add_class(self.color) + + box.add(image) + box.add(label) + self.add(box) + self.show_all() + + +class VerifiedOption(MenuOption): + + type_ = Trust.VERIFIED + icon = 'security-high-symbolic' + label = _('Trusted') + color = 'success-color' + + def __init__(self): + MenuOption.__init__(self) + + +class NotTrustedOption(MenuOption): + + type_ = Trust.NOT_TRUSTED + icon = 'dialog-error-symbolic' + label = _('Not Trusted') + color = 'error-color' + + def __init__(self): + MenuOption.__init__(self) + + +class DeleteOption(MenuOption): + + type_ = None + icon = 'user-trash-symbolic' + label = _('Delete') + color = '' + + def __init__(self): + MenuOption.__init__(self) diff --git a/omemo/gtk/style.css b/omemo/gtk/style.css new file mode 100644 index 0000000..27a1d61 --- /dev/null +++ b/omemo/gtk/style.css @@ -0,0 +1,17 @@ +.omemo-dark-success-color { color: darker(@success_color); } +.omemo-inactive-color { color: @unfocused_borders; } + +.omemo-mono { font-size: 12px; font-family: monospace; } + +.omemo-key-dialog > box { margin: 18px; } + +.omemo-key-dialog scrolledwindow row { + border-bottom: 1px solid; + border-color: @unfocused_borders; + padding: 10px 20px 10px 10px; +} +.omemo-key-dialog scrolledwindow row:last-child { border-bottom: 0px} + +.omemo-key-dialog scrolledwindow { border: 1px solid; border-color:@unfocused_borders; } + +.omemo-trust-popover row { padding: 10px 15px 10px 10px; } diff --git a/omemo/gtk/util.py b/omemo/gtk/util.py new file mode 100644 index 0000000..f6a42e5 --- /dev/null +++ b/omemo/gtk/util.py @@ -0,0 +1,73 @@ +# This file is part of Gajim-OMEMO. +# +# Gajim-OMEMO is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim-OMEMO is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Gajim-OMEMO. If not, see . + + +from collections import namedtuple +from enum import IntEnum +from enum import Enum + +from gi.repository import Gtk + +DialogButton = namedtuple('DialogButton', 'text callback action') +DialogButton.__new__.__defaults__ = (None, None) # type: ignore + + +class ButtonAction(Enum): + DESTRUCTIVE = 'destructive-action' + SUGGESTED = 'suggested-action' + + +class Trust(IntEnum): + NOT_TRUSTED = 0 + VERIFIED = 1 + UNKNOWN = 2 + + +class NewConfirmationDialog(Gtk.MessageDialog): + def __init__(self, text, sec_text, buttons, transient_for=None): + Gtk.MessageDialog.__init__(self, + transient_for=transient_for, + message_type=Gtk.MessageType.QUESTION, + text=text) + + self._buttons = buttons + + for response, button in buttons.items(): + self.add_button(button.text, response) + if button.action is not None: + widget = self.get_widget_for_response(response) + widget.get_style_context().add_class(button.action.value) + + self.format_secondary_markup(sec_text) + + self.connect('response', self._on_response) + + self.run() + + def _on_response(self, dialog, response): + if response == Gtk.ResponseType.DELETE_EVENT: + # Look if DELETE_EVENT is mapped to another response + response = self._buttons.get(response, None) + if response is None: + # If DELETE_EVENT was not mapped we assume CANCEL + response = Gtk.ResponseType.CANCEL + + button = self._buttons.get(response, None) + if button is None: + self.destroy() + return + + if button.callback is not None: + button.callback() + self.destroy() diff --git a/omemo/omemoplugin.py b/omemo/omemoplugin.py index 32a6804..9706942 100644 --- a/omemo/omemoplugin.py +++ b/omemo/omemoplugin.py @@ -24,8 +24,11 @@ import logging import binascii import threading from enum import IntEnum, unique +from pathlib import Path from gi.repository import GLib +from gi.repository import Gtk +from gi.repository import Gdk from gajim import dialogs from gajim.common import app, ged @@ -34,6 +37,7 @@ from gajim.plugins import GajimPlugin from gajim.groupchat_control import GroupchatControl from omemo.xmpp import DevicelistPEP +from omemo.gtk.key import KeyDialog CRYPTOGRAPHY_MISSING = 'You are missing Python-Cryptography' AXOLOTL_MISSING = 'You are missing Python-Axolotl or use an outdated version' @@ -65,7 +69,7 @@ except Exception as error: if not ERROR_MSG: try: from omemo.omemo_connection import OMEMOConnection - from omemo.ui import OMEMOConfigDialog, FingerprintWindow + from omemo.ui import OMEMOConfigDialog except Exception as error: log.error(error) ERROR_MSG = 'Error: %s' % error @@ -125,6 +129,25 @@ class OmemoPlugin(GajimPlugin): schemes += ' aesgcm://' app.config.set('uri_schemes', schemes) + self._load_css() + + def _load_css(self): + path = Path(__file__).parent / 'gtk' / 'style.css' + try: + with open(path, "r") as f: + css = f.read() + except Exception as exc: + log.error('Error loading css: %s', exc) + return + + try: + provider = Gtk.CssProvider() + provider.load_from_data(bytes(css.encode('utf-8'))) + Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), + provider, 610) + except Exception: + log.exception('Error loading application css') + def signed_in(self, event): """ Method called on SignIn @@ -290,16 +313,15 @@ class OmemoPlugin(GajimPlugin): if 'dialog' not in self.windowinstances: is_groupchat = isinstance(chat_control, GroupchatControl) self.windowinstances['dialog'] = \ - FingerprintWindow(self, contact, transient, - self.windowinstances, groupchat=is_groupchat) - self.windowinstances['dialog'].show_all() + KeyDialog(self, contact, transient, + self.windowinstances, groupchat=is_groupchat) if fingerprints: log.debug('%s => Showing Fingerprint Prompt for %s', account, contact.jid) omemo.store.setShownFingerprints(fingerprints) else: self.windowinstances['dialog'].present() - self.windowinstances['dialog'].update_context_list() + self.windowinstances['dialog'].update() if fingerprints: omemo.store.setShownFingerprints(fingerprints) diff --git a/omemo/ui.py b/omemo/ui.py index 18e0fec..67edf4a 100644 --- a/omemo/ui.py +++ b/omemo/ui.py @@ -23,6 +23,7 @@ the Gajim-OMEMO plugin. If not, see . import binascii import logging import os +import textwrap from enum import IntEnum, unique from gi.repository import Gtk, GdkPixbuf, Gdk @@ -74,19 +75,16 @@ class OMEMOConfigDialog(GajimPluginConfigDialog): log.debug('Disabled Accounts:') log.debug(self.disabled_accounts) - self.fpr_model = self.B.get_object('fingerprint_store') self.device_model = self.B.get_object('deviceid_store') - self.fpr_view = self.B.get_object('fingerprint_view') - self.disabled_acc_store = self.B.get_object('disabled_account_store') self.account_store = self.B.get_object('account_store') self.active_acc_view = self.B.get_object('active_accounts_view') self.disabled_acc_view = self.B.get_object('disabled_accounts_view') - vbox = self.get_content_area() - vbox.pack_start(self.B.get_object('notebook1'), True, True, 0) + box = self.get_content_area() + box.pack_start(self.B.get_object('notebook1'), True, True, 0) self.B.connect_signals(self) @@ -178,67 +176,6 @@ class OMEMOConfigDialog(GajimPluginConfigDialog): self.plugin.config['DISABLED_ACCOUNTS'] = self.disabled_accounts self.update_account_combobox() - def delfpr_button_clicked(self, button, *args): - active = self.B.get_object('account_combobox').get_active() - account = self.account_store[active][0] - - state = self.plugin.get_omemo(account) - - mod, paths = self.fpr_view.get_selection().get_selected_rows() - - def on_yes(checked): - record = state.store.loadSession(jid, deviceid) - identity_key = record.getSessionState().getRemoteIdentityKey() - - state.store.deleteSession(jid, deviceid) - state.store.deleteIdentity(jid, identity_key) - self.update_context_list() - - for path in paths: - it = mod.get_iter(path) - jid, fpr, deviceid = mod.get(it, 1, 3, 4) - fpr = fpr[31:-12] - - YesNoDialog( - _('Delete Fingerprint?'), - _('Do you want to delete the ' - 'fingerprint of {jid} on your account {account}?' - '\n\n{fingerprint}').format(jid=jid, account=account, fingerprint=fpr), - on_response_yes=on_yes, transient_for=self) - - def trust_button_clicked_cb(self, button, *args): - active = self.B.get_object('account_combobox').get_active() - account = self.account_store[active][0] - - state = self.plugin.get_omemo(account) - - mod, paths = self.fpr_view.get_selection().get_selected_rows() - - def on_yes(checked, identity_key): - state.store.setTrust(identity_key, State.TRUSTED) - self.update_context_list() - - def on_no(identity_key): - state.store.setTrust(identity_key, State.UNTRUSTED) - self.update_context_list() - - for path in paths: - it = mod.get_iter(path) - jid, fpr, deviceid = mod.get(it, 1, 3, 4) - fpr = fpr[31:-12] - - record = state.store.loadSession(jid, deviceid) - identity_key = record.getSessionState().getRemoteIdentityKey() - - YesNoDialog( - _('Trust / Revoke Fingerprint?'), - _('Do you want to trust the fingerprint of {jid} ' - 'on your account {account}?\n\n' - '{fingerprint}').format(jid=jid, account=account, fingerprint=fpr), - on_response_yes=(on_yes, identity_key), - on_response_no=(on_no, identity_key), - transient_for=self) - def cleardevice_button_clicked_cb(self, button, *args): active = self.B.get_object('account_combobox').get_active() account = self.account_store[active][0] @@ -248,48 +185,13 @@ class OMEMOConfigDialog(GajimPluginConfigDialog): def refresh_button_clicked_cb(self, button, *args): self.update_context_list() - def fpr_button_pressed_cb(self, tw, event): - if event.button == 3: - pthinfo = tw.get_path_at_pos(int(event.x), int(event.y)) - - if pthinfo is None: - # only show the popup when we right clicked on list content - # ie. don't show it when we click at empty rows - return False - - # if the row under the mouse is already selected, we keep the - # selection, otherwise we only select the new item - keep_selection = tw.get_selection().path_is_selected(pthinfo[0]) - - pop = self.B.get_object('fprclipboard_menu') - pop.popup(None, None, None, None, event.button, event.time) - - # keep_selection=True -> no further processing of click event - # keep_selection=False-> further processing -> GTK usually selects - # the item below the cursor - return keep_selection - - def clipboard_button_cb(self, menuitem): - mod, paths = self.fpr_view.get_selection().get_selected_rows() - - fprs = [] - for path in paths: - it = mod.get_iter(path) - jid, fpr = mod.get(it, 1, 3) - fprs.append('%s: %s' % (jid, fpr[31:-12])) - clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) - clipboard.set_text('\n'.join(fprs), -1) - def update_context_list(self): - self.fpr_model.clear() self.device_model.clear() self.qrcode = self.B.get_object('qrcode') self.qrinfo = self.B.get_object('qrinfo') if len(self.account_store) == 0: self.B.get_object('ID').set_markup('') self.B.get_object('fingerprint_label').set_markup('') - self.B.get_object('trust_button').set_sensitive(False) - self.B.get_object('delfprbutton').set_sensitive(False) self.B.get_object('refresh').set_sensitive(False) self.B.get_object('cleardevice_button').set_sensitive(False) self.B.get_object('qrcode').clear() @@ -298,8 +200,6 @@ class OMEMOConfigDialog(GajimPluginConfigDialog): account = self.account_store[active][0] # Set buttons active - self.B.get_object('trust_button').set_sensitive(True) - self.B.get_object('delfprbutton').set_sensitive(True) self.B.get_object('refresh').set_sensitive(True) if account == 'Local': self.B.get_object('cleardevice_button').set_sensitive(False) @@ -313,40 +213,10 @@ class OMEMOConfigDialog(GajimPluginConfigDialog): ownfpr = binascii.hexlify(state.store.getIdentityKeyPair() .getPublicKey().serialize()).decode('utf-8') - human_ownfpr = human_hash(ownfpr[2:]) + human_ownfpr = self.human_hash(ownfpr[2:]) self.B.get_object('fingerprint_label').set_markup('%s' % human_ownfpr) - # Set Fingerprint List - trust_str = {0: 'False', 1: 'True', 2: 'Undecided'} - session_db = state.store.getAllSessions() - - for item in session_db: - color = {0: '#FF0040', # red - 1: '#2EFE2E', # green - 2: '#FF8000'} # orange - - _id, jid, deviceid, record, active = item - - active = bool(active) - - identity_key = SessionRecord(serialized=record). \ - getSessionState().getRemoteIdentityKey() - fpr = binascii.hexlify( - identity_key.getPublicKey().serialize()).decode('utf-8') - fpr = human_hash(fpr[2:]) - - trust = state.store.isTrustedIdentity(jid, identity_key) - - if not active: - color[trust] = '#585858' # grey - - self.fpr_model.append( - (_id, jid, trust_str[trust], - '{}'. - format(color[trust], fpr), - deviceid)) - # Set Device ID List for item in state.own_devices: self.device_model.append([item]) @@ -358,175 +228,17 @@ class OMEMOConfigDialog(GajimPluginConfigDialog): pixbuf = GdkPixbuf.Pixbuf.new_from_file(path) self.qrcode.set_from_pixbuf(pixbuf) self.qrcode.show() - self.qrinfo.hide() + self.qrinfo.set_revealed(False) else: - self.qrinfo.show() + self.qrinfo.set_revealed(True) self.qrcode.hide() - -class FingerprintWindow(Gtk.Dialog): - def __init__(self, plugin, contact, parent, windowinstances, - groupchat=False): - self.groupchat = groupchat - self.contact = contact - self.windowinstances = windowinstances - self.account = self.contact.account.name - self.plugin = plugin - self.con = plugin.connections[self.account] - self.omemostate = self.plugin.get_omemo(self.account) - self.own_jid = app.get_jid_from_account(self.account) - Gtk.Dialog.__init__(self, - title=(_('Fingerprints for %s')) % contact.jid, - parent=parent, - flags=Gtk.DialogFlags.DESTROY_WITH_PARENT) - - self.connect('destroy', self._on_destroy) - - self.GTK_BUILDER_FILE_PATH = \ - self.plugin.local_file_path('fpr_dialog.ui') - self.xml = Gtk.Builder() - self.xml.add_from_file(self.GTK_BUILDER_FILE_PATH) - self.xml.set_translation_domain('gajim_plugins') - - self.fpr_model = self.xml.get_object('fingerprint_store') - - self.fpr_view = self.xml.get_object('fingerprint_view') - self.fpr_view_own = self.xml.get_object('fingerprint_view_own') - - self.notebook = self.xml.get_object('fingerprint_box') - vbox = self.get_content_area() - vbox.pack_start(self.notebook, True, True, 0) - - self.xml.connect_signals(self) - - # Set own Fingerprint Label - ownfpr = binascii.hexlify(self.omemostate.store.getIdentityKeyPair() - .getPublicKey().serialize()).decode('utf-8') - ownfpr = human_hash(ownfpr[2:]) - self.xml.get_object('fingerprint_label_own').set_markup('%s' - % ownfpr) - self.update_context_list() - - self.show_all() - - def _on_destroy(self, *args): - del self.windowinstances['dialog'] - - def trust_button_clicked_cb(self, button, *args): - state = self.omemostate - - mod, paths = self.fpr_view.get_selection().get_selected_rows() - - def on_yes(checked, identity_key): - state.store.setTrust(identity_key, State.TRUSTED) - self.update_context_list() - - def on_no(identity_key): - state.store.setTrust(identity_key, State.UNTRUSTED) - self.update_context_list() - - for path in paths: - it = mod.get_iter(path) - jid, fpr, deviceid = mod.get(it, 1, 3, 4) - fpr = fpr[31:-12] - - record = state.store.loadSession(jid, deviceid) - identity_key = record.getSessionState().getRemoteIdentityKey() - - YesNoDialog( - _('Trust / Revoke Fingerprint?'), - _('Do you want to trust the fingerprint of {jid} ' - 'on your account {account}?\n\n' - '{fingerprint}').format(jid=jid, - account=self.account, - fingerprint=fpr), - on_response_yes=(on_yes, identity_key), - on_response_no=(on_no, identity_key), - transient_for=self) - - def fpr_button_pressed_cb(self, tw, event): - if event.button == 3: - pthinfo = tw.get_path_at_pos(int(event.x), int(event.y)) - - if pthinfo is None: - # only show the popup when we right clicked on list content - # ie. don't show it when we click at empty rows - return False - - # if the row under the mouse is already selected, we keep the - # selection, otherwise we only select the new item - keep_selection = tw.get_selection().path_is_selected(pthinfo[0]) - - pop = self.xml.get_object('fprclipboard_menu') - pop.popup(None, None, None, None, event.button, event.time) - - # keep_selection=True -> no further processing of click event - # keep_selection=False-> further processing -> GTK usually selects - # the item below the cursor - return keep_selection - - def clipboard_button_cb(self, menuitem): - mod, paths = self.fpr_view.get_selection().get_selected_rows() - - fprs = [] - for path in paths: - it = mod.get_iter(path) - jid, fpr = mod.get(it, 1, 3) - fprs.append('%s: %s' % (jid, fpr[31:-12])) - clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) - clipboard.set_text('\n'.join(fprs), -1) - - def update_context_list(self, *args): - self.fpr_model.clear() - self._load_fingerprints(self.own_jid) - self._load_fingerprints(self.contact.jid, self.groupchat is True) - - def _load_fingerprints(self, contact_jid, groupchat=False): - state = self.omemostate - - trust_str = {0: 'False', 1: 'True', 2: 'Undecided'} - if groupchat: - contact_jids = [] - for nick in self.con.groupchat[contact_jid]: - real_jid = self.con.groupchat[contact_jid][nick] - if real_jid == self.own_jid: - continue - contact_jids.append(real_jid) - session_db = state.store.getSessionsFromJids(contact_jids) - else: - session_db = state.store.getSessionsFromJid(contact_jid) - - for item in session_db: - color = {0: '#FF0040', # red - 1: '#2EFE2E', # green - 2: '#FF8000'} # orange - - _id, jid, deviceid, record, active = item - - active = bool(active) - - identity_key = SessionRecord(serialized=record). \ - getSessionState().getRemoteIdentityKey() - fpr = binascii.hexlify(identity_key.getPublicKey().serialize()).decode('utf-8') - fpr = human_hash(fpr[2:]) - - trust = state.store.isTrustedIdentity(jid, identity_key) - - if not active: - color[trust] = '#585858' # grey - - self.fpr_model.append( - (_id, jid, trust_str[trust], - '{}'. - format(color[trust], fpr), - deviceid)) - - -def human_hash(fpr): - fpr = fpr.upper() - fplen = len(fpr) - wordsize = fplen // 8 - buf = '' - for w in range(0, fplen, wordsize): - buf += '{0} '.format(fpr[w:w + wordsize]) - return buf.rstrip() + def human_hash(self, fpr): + fpr = fpr.upper() + fplen = len(fpr) + wordsize = fplen // 8 + buf = '' + for w in range(0, fplen, wordsize): + buf += '{0} '.format(fpr[w:w + wordsize]) + buf = textwrap.fill(buf, width=36) + return buf.rstrip()