[omemo] Use Gajims encryption API

This commit is contained in:
Philipp Hörist
2017-05-07 09:44:05 +02:00
parent de369fc6a0
commit b8ff3d195b
2 changed files with 266 additions and 552 deletions

View File

@@ -1,35 +1,40 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
#
# Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de> '''
# Copyright 2015 Daniel Gultsch <daniel@cgultsch.de> Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
# Copyright 2015 Daniel Gultsch <daniel@cgultsch.de>
# This file is part of Gajim-OMEMO plugin. Copyright 2016 Philipp Hörist <philipp@hoerist.com>
#
# The Gajim-OMEMO plugin is free software: you can redistribute it and/or modify This file is part of Gajim-OMEMO plugin.
# it under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any The Gajim-OMEMO plugin is free software: you can redistribute it and/or modify
# later version. it under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# Gajim-OMEMO is distributed in the hope that it will be useful, but WITHOUT ANY later version.
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details. 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
# You should have received a copy of the GNU General Public License along with A PARTICULAR PURPOSE. See the GNU General Public License for more details.
# the Gajim-OMEMO plugin. If not, see <http://www.gnu.org/licenses/>.
# You should have received a copy of the GNU General Public License along with
the Gajim-OMEMO plugin. If not, see <http://www.gnu.org/licenses/>.
'''
import logging import logging
import os import os
import sqlite3 import sqlite3
import shutil import shutil
import message_control import message_control
import nbxmpp
from nbxmpp.simplexml import Node
from nbxmpp import NS_CORRECT, NS_ADDRESS
import dialogs
from common import caps_cache, gajim, ged, configpaths from common import caps_cache, gajim, ged, configpaths
from common.pep import SUPPORTED_PERSONAL_USER_EVENTS from common.pep import SUPPORTED_PERSONAL_USER_EVENTS
from plugins import GajimPlugin from plugins import GajimPlugin
from plugins.helpers import log_calls from groupchat_control import GroupchatControl
from nbxmpp.simplexml import Node
from nbxmpp import NS_CORRECT, NS_ADDRESS
from .xmpp import ( from .xmpp import (
NS_NOTIFY, NS_OMEMO, NS_EME, BundleInformationAnnouncement, NS_NOTIFY, NS_OMEMO, NS_EME, BundleInformationAnnouncement,
@@ -37,6 +42,9 @@ from .xmpp import (
DevicelistPEP, OmemoMessage, successful, unpack_device_bundle, DevicelistPEP, OmemoMessage, successful, unpack_device_bundle,
unpack_device_list_update, unpack_encrypted) unpack_device_list_update, unpack_encrypted)
from common.connection_handlers_events import (
MessageReceivedEvent, MamMessageReceivedEvent)
IQ_CALLBACK = {} IQ_CALLBACK = {}
@@ -49,10 +57,22 @@ GAJIM_VERSION = 'OMEMO only works with the latest Gajim version, get the ' \
ERROR_MSG = '' ERROR_MSG = ''
NS_HINTS = 'urn:xmpp:hints' NS_HINTS = 'urn:xmpp:hints'
NS_PGP = 'urn:xmpp:openpgp:0'
DB_DIR_OLD = gajim.gajimpaths.data_root DB_DIR_OLD = gajim.gajimpaths.data_root
DB_DIR_NEW = configpaths.gajimpaths['MY_DATA'] DB_DIR_NEW = configpaths.gajimpaths['MY_DATA']
ALLOWED_TAGS = [('request', nbxmpp.NS_RECEIPTS),
('active', nbxmpp.NS_CHATSTATES),
('gone', nbxmpp.NS_CHATSTATES),
('inactive', nbxmpp.NS_CHATSTATES),
('paused', nbxmpp.NS_CHATSTATES),
('composing', nbxmpp.NS_CHATSTATES),
('no-store', nbxmpp.NS_MSG_HINTS),
('store', nbxmpp.NS_MSG_HINTS),
('no-copy', nbxmpp.NS_MSG_HINTS),
('no-permanent-store', nbxmpp.NS_MSG_HINTS),
('replace', nbxmpp.NS_CORRECT),
('thread', None)]
log = logging.getLogger('gajim.plugin_system.omemo') log = logging.getLogger('gajim.plugin_system.omemo')
try: try:
@@ -76,7 +96,7 @@ except Exception as e:
if not ERROR_MSG: if not ERROR_MSG:
try: try:
from .omemo.state import OmemoState from .omemo.state import OmemoState
from .ui import Ui, OMEMOConfigDialog from .ui import OMEMOConfigDialog, FingerprintWindow
except Exception as e: except Exception as e:
log.error(e) log.error(e)
ERROR_MSG = 'Error: ' + str(e) ERROR_MSG = 'Error: ' + str(e)
@@ -88,11 +108,9 @@ if not ERROR_MSG:
class OmemoPlugin(GajimPlugin): class OmemoPlugin(GajimPlugin):
omemo_states = {} omemo_states = {}
ui_list = {}
groupchat = {} groupchat = {}
temp_groupchat = {} temp_groupchat = {}
@log_calls('OmemoPlugin')
def init(self): def init(self):
""" Init """ """ Init """
if ERROR_MSG: if ERROR_MSG:
@@ -100,18 +118,12 @@ class OmemoPlugin(GajimPlugin):
self.available_text = ERROR_MSG self.available_text = ERROR_MSG
self.config_dialog = None self.config_dialog = None
return return
self.encryption_name = 'OMEMO'
self.allow_groupchat = True
self.events_handlers = { self.events_handlers = {
'mam-message-received': (ged.PRECORE, self.mam_message_received),
'message-received': (ged.PRECORE, self.message_received),
'pep-received': (ged.PRECORE, self.handle_device_list_update), 'pep-received': (ged.PRECORE, self.handle_device_list_update),
'raw-iq-received': (ged.PRECORE, self.handle_iq_received), 'raw-iq-received': (ged.PRECORE, self.handle_iq_received),
'signed-in': (ged.PRECORE, self.signed_in), 'signed-in': (ged.PRECORE, self.signed_in),
'stanza-message-outgoing':
(ged.PRECORE, self.handle_outgoing_stanza),
'message-outgoing':
(ged.PRECORE, self.handle_outgoing_event),
'gc-stanza-message-outgoing':
(ged.PRECORE, self.handle_outgoing_gc_stanza),
'gc-presence-received': (ged.PRECORE, self.gc_presence_received), 'gc-presence-received': (ged.PRECORE, self.gc_presence_received),
'gc-config-changed-received': 'gc-config-changed-received':
(ged.PRECORE, self.gc_config_changed_received), (ged.PRECORE, self.gc_config_changed_received),
@@ -119,18 +131,25 @@ class OmemoPlugin(GajimPlugin):
} }
self.config_dialog = OMEMOConfigDialog(self) self.config_dialog = OMEMOConfigDialog(self)
self.gui_extension_points = {'chat_control': (self.connect_ui, self.gui_extension_points = {
self.disconnect_ui), 'hyperlink_handler': (self.file_decryption, None),
'groupchat_control': (self.connect_ui, 'encrypt' + self.encryption_name: (self._encrypt_message, None),
self.disconnect_ui), 'gc_encrypt' + self.encryption_name: (self._gc_encrypt_message, None),
'hyperlink_handler': (self.file_decryption, 'decrypt': (self.message_received, None),
None)} 'send_message' + self.encryption_name: (
self.before_sendmessage, None),
'encryption_dialog' + self.encryption_name: (
self.on_encryption_button_clicked, None),
'encryption_state' + self.encryption_name: (
self.encryption_state, None)}
SUPPORTED_PERSONAL_USER_EVENTS.append(DevicelistPEP) SUPPORTED_PERSONAL_USER_EVENTS.append(DevicelistPEP)
self.plugin = self self.plugin = self
self.announced = [] self.announced = []
self.query_for_bundles = [] self.query_for_bundles = []
self.disabled_accounts = [] self.disabled_accounts = []
self.gc_message = {} self.gc_message = {}
self.windowinstances = {}
self.config_default_values = {'DISABLED_ACCOUNTS': ([], ''), } self.config_default_values = {'DISABLED_ACCOUNTS': ([], ''), }
@@ -158,7 +177,6 @@ class OmemoPlugin(GajimPlugin):
return new_dbpath return new_dbpath
@log_calls('OmemoPlugin')
def get_omemo_state(self, account): def get_omemo_state(self, account):
""" Returns the the OmemoState for the specified account. """ Returns the the OmemoState for the specified account.
Creates the OmemoState if it does not exist yet. Creates the OmemoState if it does not exist yet.
@@ -175,7 +193,6 @@ class OmemoPlugin(GajimPlugin):
if account in self.disabled_accounts: if account in self.disabled_accounts:
return return
if account not in self.omemo_states: if account not in self.omemo_states:
self.deactivate_gajim_e2e(account)
my_jid = gajim.get_jid_from_account(account) my_jid = gajim.get_jid_from_account(account)
db_path = self.migrate_dbpath(account, my_jid) db_path = self.migrate_dbpath(account, my_jid)
@@ -185,19 +202,9 @@ class OmemoPlugin(GajimPlugin):
return self.omemo_states[account] return self.omemo_states[account]
@staticmethod
def deactivate_gajim_e2e(account):
""" Deativates E2E encryption in Gajim """
gajim.config.set_per('accounts', account,
'autonegotiate_esessions', False)
gajim.config.set_per('accounts', account,
'enable_esessions', False)
log.info(str(account) + " => Gajim E2E encryption disabled")
def file_decryption(self, url, kind, instance, window): def file_decryption(self, url, kind, instance, window):
FileDecryption(self).hyperlink_handler(url, kind, instance, window) FileDecryption(self).hyperlink_handler(url, kind, instance, window)
@log_calls('OmemoPlugin')
def signed_in(self, event): def signed_in(self, event):
""" Method called on SignIn """ Method called on SignIn
@@ -216,7 +223,6 @@ class OmemoPlugin(GajimPlugin):
self.publish_bundle(account) self.publish_bundle(account)
self.query_own_devicelist(account) self.query_own_devicelist(account)
@log_calls('OmemoPlugin')
def activate(self): def activate(self):
""" Method called when the Plugin is activated in the PluginManager """ Method called when the Plugin is activated in the PluginManager
""" """
@@ -238,7 +244,6 @@ class OmemoPlugin(GajimPlugin):
self.publish_bundle(account) self.publish_bundle(account)
self.query_own_devicelist(account) self.query_own_devicelist(account)
@log_calls('OmemoPlugin')
def deactivate(self): def deactivate(self):
""" Method called when the Plugin is deactivated in the PluginManager """ Method called when the Plugin is deactivated in the PluginManager
@@ -251,6 +256,98 @@ class OmemoPlugin(GajimPlugin):
gajim.gajim_optional_features[account].remove(NS_NOTIFY) gajim.gajim_optional_features[account].remove(NS_NOTIFY)
self._compute_caps_hash(account) self._compute_caps_hash(account)
def activate_encryption(self, chat_control):
if isinstance(chat_control, GroupchatControl):
if chat_control.room_jid not in self.groupchat:
dialogs.ErrorDialog(
_('Bad Configuration'),
_('To use OMEMO in a Groupchat, the Groupchat should be'
' non-anonymous and members-only.'))
return False
return True
@staticmethod
def encryption_state(chat_control, state):
state['visible'] = True
state['authenticated'] = True
def on_encryption_button_clicked(self, chat_control):
self.show_fingerprint_window(chat_control)
def before_sendmessage(self, chat_control):
account = chat_control.account
contact = chat_control.contact
self.new_fingerprints_available(chat_control)
if isinstance(chat_control, GroupchatControl):
missing = True
own_jid = gajim.get_jid_from_account(account)
for nick in self.plugin.groupchat[self.room]:
real_jid = self.plugin.groupchat[self.room][nick]
if real_jid == own_jid:
continue
if not self.plugin.are_keys_missing(self.account,
real_jid):
missing = False
if missing:
log.debug(self.account +
' => No Trusted Fingerprints for ' +
self.room)
self.no_trusted_fingerprints_warning()
else:
if self.are_keys_missing(account, contact.jid):
log.debug(account + ' => No Trusted Fingerprints for ' +
contact.jid)
self.no_trusted_fingerprints_warning(chat_control)
chat_control.sendmessage = False
else:
log.debug(account + ' => Sending Message to ' +
contact.jid)
def new_fingerprints_available(self, chat_control):
jid = chat_control.contact.jid
account = chat_control.account
state = self.get_omemo_state(account)
if isinstance(chat_control, GroupchatControl):
room_jid = chat_control.room_jid
if room_jid in self.groupchat:
for nick in self.groupchat[room_jid]:
real_jid = self.groupchat[room_jid][nick]
fingerprints = state.store. \
getNewFingerprints(real_jid)
if fingerprints:
self.show_fingerprint_window(
chat_control, fingerprints)
elif not isinstance(chat_control, GroupchatControl):
fingerprints = state.store.getNewFingerprints(jid)
if fingerprints:
self.show_fingerprint_window(
chat_control, fingerprints)
def show_fingerprint_window(self, chat_control, fingerprints=None):
contact = chat_control.contact
account = chat_control.account
state = self.get_omemo_state(account)
transient = chat_control.parent_win.window
if 'dialog' not in self.windowinstances:
if isinstance(chat_control, GroupchatControl):
self.windowinstances['dialog'] = \
FingerprintWindow(self, contact, transient,
self.windowinstances, groupchat=True)
else:
self.windowinstances['dialog'] = \
FingerprintWindow(self, contact, transient,
self.windowinstances)
self.windowinstances['dialog'].show_all()
if fingerprints:
log.debug(account +
' => Showing Fingerprint Prompt for ' +
contact.jid)
state.store.setShownFingerprints(fingerprints)
else:
self.windowinstances['dialog'].update_context_list()
if fingerprints:
state.store.setShownFingerprints(fingerprints)
@staticmethod @staticmethod
def _compute_caps_hash(account): def _compute_caps_hash(account):
""" Computes the hash for Entity Capabilities and publishes it """ """ Computes the hash for Entity Capabilities and publishes it """
@@ -264,8 +361,17 @@ class OmemoPlugin(GajimPlugin):
gajim.connections[account].change_status( gajim.connections[account].change_status(
gajim.SHOW_LIST[connected], gajim.connections[account].status) gajim.SHOW_LIST[connected], gajim.connections[account].status)
@log_calls('OmemoPlugin') def message_received(self, conn, obj, callback):
def mam_message_received(self, msg): if obj.encrypted:
return
if isinstance(obj, MessageReceivedEvent):
self._message_received(obj)
elif isinstance(obj, MamMessageReceivedEvent):
self._mam_message_received(obj)
if obj.encrypted == 'OMEMO':
callback(obj)
def _mam_message_received(self, msg):
""" Handles an incoming MAM message """ Handles an incoming MAM message
Payload is decrypted and the plaintext is written into the Payload is decrypted and the plaintext is written into the
@@ -283,9 +389,6 @@ class OmemoPlugin(GajimPlugin):
if account in self.disabled_accounts: if account in self.disabled_accounts:
return return
if msg.msg_.getTag('openpgp', namespace=NS_PGP):
return
omemo_encrypted_tag = msg.msg_.getTag('encrypted', namespace=NS_OMEMO) omemo_encrypted_tag = msg.msg_.getTag('encrypted', namespace=NS_OMEMO)
if omemo_encrypted_tag: if omemo_encrypted_tag:
log.debug(account + ' => OMEMO MAM msg received') log.debug(account + ' => OMEMO MAM msg received')
@@ -302,31 +405,28 @@ class OmemoPlugin(GajimPlugin):
plaintext = state.decrypt_msg(msg_dict) plaintext = state.decrypt_msg(msg_dict)
if not plaintext: if not plaintext:
msg.encrypted = 'drop'
return return
self.print_msg_to_log(msg.msg_) self.print_msg_to_log(msg.msg_)
msg.msgtxt = plaintext msg.msgtxt = plaintext
msg.encrypted = 'OMEMO'
contact_jid = msg.with_ return
if account in self.ui_list and \
contact_jid in self.ui_list[account]:
self.ui_list[account][contact_jid].activate_omemo()
return False
elif msg.msg_.getTag('body'): elif msg.msg_.getTag('body'):
account = msg.conn.name account = msg.conn.name
jid = msg.with_ from_jid = str(msg.msg_.getAttr('from'))
state = self.get_omemo_state(account) from_jid = gajim.get_jid_without_resource(from_jid)
omemo_enabled = state.encryption.is_active(jid)
if omemo_enabled: state = self.get_omemo_state(account)
encryption = gajim.config.get_per('contacts', from_jid, 'encryption')
if encryption == 'OMEMO':
msg.msgtxt = '**Unencrypted** ' + msg.msgtxt msg.msgtxt = '**Unencrypted** ' + msg.msgtxt
@log_calls('OmemoPlugin') def _message_received(self, msg):
def message_received(self, msg):
""" Handles an incoming message """ Handles an incoming message
Payload is decrypted and the plaintext is written into the Payload is decrypted and the plaintext is written into the
@@ -344,9 +444,6 @@ class OmemoPlugin(GajimPlugin):
if account in self.disabled_accounts: if account in self.disabled_accounts:
return return
if msg.stanza.getTag('openpgp', namespace=NS_PGP):
return
if msg.stanza.getTag('encrypted', namespace=NS_OMEMO): if msg.stanza.getTag('encrypted', namespace=NS_OMEMO):
log.debug(account + ' => OMEMO msg received') log.debug(account + ' => OMEMO msg received')
@@ -379,7 +476,8 @@ class OmemoPlugin(GajimPlugin):
log.error(account + log.error(account +
' => Cant decrypt GroupChat Message ' ' => Cant decrypt GroupChat Message '
'from ' + msg.resource) 'from ' + msg.resource)
return True msg.encrypted = 'drop'
return
self.groupchat[msg.jid][msg.resource] = from_jid self.groupchat[msg.jid][msg.resource] = from_jid
log.debug('GroupChat Message from: %s', from_jid) log.debug('GroupChat Message from: %s', from_jid)
@@ -392,25 +490,21 @@ class OmemoPlugin(GajimPlugin):
else: else:
log.error(account + ' => Cant decrypt own GroupChat ' log.error(account + ' => Cant decrypt own GroupChat '
'Message') 'Message')
msg.encrypted = 'drop'
else: else:
msg_dict['sender_jid'] = gajim. \ msg_dict['sender_jid'] = gajim. \
get_jid_without_resource(from_jid) get_jid_without_resource(from_jid)
plaintext = state.decrypt_msg(msg_dict) plaintext = state.decrypt_msg(msg_dict)
if not plaintext: if not plaintext:
return True msg.encrypted = 'drop'
return
msg.msgtxt = plaintext msg.msgtxt = plaintext
# Gajim bug: there must be a body or the message # Gajim bug: there must be a body or the message
# gets dropped from history # gets dropped from history
msg.stanza.setBody(plaintext) msg.stanza.setBody(plaintext)
msg.encrypted = 'OMEMO'
if msg.mtype != 'groupchat':
contact_jid = gajim.get_jid_without_resource(from_jid)
if account in self.ui_list and \
contact_jid in self.ui_list[account]:
self.ui_list[account][contact_jid].activate_omemo()
return False
elif msg.stanza.getTag('body'): elif msg.stanza.getTag('body'):
account = msg.conn.name account = msg.conn.name
@@ -418,19 +512,15 @@ class OmemoPlugin(GajimPlugin):
from_jid = str(msg.stanza.getFrom()) from_jid = str(msg.stanza.getFrom())
jid = gajim.get_jid_without_resource(from_jid) jid = gajim.get_jid_without_resource(from_jid)
state = self.get_omemo_state(account) state = self.get_omemo_state(account)
omemo_enabled = state.encryption.is_active(jid) encryption = gajim.config.get_per('contacts', jid, 'encryption')
if omemo_enabled: if encryption == 'OMEMO':
msg.msgtxt = '**Unencrypted** ' + msg.msgtxt msg.msgtxt = '**Unencrypted** ' + msg.msgtxt
# msg.stanza.setBody(msg.msgtxt) msg.stanza.setBody(msg.msgtxt)
try: ctrl = gajim.interface.msg_win_mgr.get_control(jid, account)
gui = self.ui_list[account].get(jid, None) if ctrl:
if gui and gui.encryption_active(): self.plain_warning(ctrl)
gui.plain_warning()
except KeyError:
log.debug('No Ui present for ' + jid +
', Ui Warning not shown')
def room_memberlist_received(self, event): def room_memberlist_received(self, event):
account = event.conn.name account = event.conn.name
@@ -440,6 +530,9 @@ class OmemoPlugin(GajimPlugin):
event.fjid, event.users_dict) event.fjid, event.users_dict)
room = event.fjid room = event.fjid
if room not in self.groupchat:
self.groupchat[room] = {}
def jid_known(jid): def jid_known(jid):
for nick in self.groupchat[room]: for nick in self.groupchat[room]:
if self.groupchat[room][nick] == jid: if self.groupchat[room][nick] == jid:
@@ -452,7 +545,6 @@ class OmemoPlugin(GajimPlugin):
self.groupchat[room][jid] = jid self.groupchat[room][jid] = jid
log.debug('JID Added: ' + jid) log.debug('JID Added: ' + jid)
@log_calls('OmemoPlugin')
def gc_presence_received(self, event): def gc_presence_received(self, event):
account = event.conn.name account = event.conn.name
if account in self.disabled_accounts: if account in self.disabled_accounts:
@@ -508,18 +600,19 @@ class OmemoPlugin(GajimPlugin):
gajim.connections[account].get_affiliation_list(room, 'admin') gajim.connections[account].get_affiliation_list(room, 'admin')
gajim.connections[account].get_affiliation_list(room, 'member') gajim.connections[account].get_affiliation_list(room, 'member')
self.ui_list[account][room].sensitive(True)
@log_calls('OmemoPlugin')
def gc_config_changed_received(self, event): def gc_config_changed_received(self, event):
account = event.conn.name account = event.conn.name
room = event.room_jid
if account in self.disabled_accounts: if account in self.disabled_accounts:
return return
if '172' in event.status_code:
if room not in self.groupchat:
self.groupchat[room] = self.temp_groupchat[room]
log.debug('CONFIG CHANGE') log.debug('CONFIG CHANGE')
log.debug(event.room_jid) log.debug(event.room_jid)
log.debug(event.status_code) log.debug(event.status_code)
def handle_outgoing_gc_stanza(self, event): def _gc_encrypt_message(self, conn, event, callback):
""" Manipulates the outgoing groupchat stanza """ Manipulates the outgoing groupchat stanza
The body is getting encrypted The body is getting encrypted
@@ -546,10 +639,7 @@ class OmemoPlugin(GajimPlugin):
state = self.get_omemo_state(account) state = self.get_omemo_state(account)
full_jid = str(event.msg_iq.getAttr('to')) full_jid = str(event.msg_iq.getAttr('to'))
to_jid = gajim.get_jid_without_resource(full_jid) to_jid = gajim.get_jid_without_resource(full_jid)
if to_jid not in self.groupchat:
return
if not state.encryption.is_active(to_jid):
return
# Delete previous Message out of Correction Message Stanza # Delete previous Message out of Correction Message Stanza
if event.msg_iq.getTag('replace', namespace=NS_CORRECT): if event.msg_iq.getTag('replace', namespace=NS_CORRECT):
event.msg_iq.delChild('encrypted', attrs={'xmlns': NS_OMEMO}) event.msg_iq.delChild('encrypted', attrs={'xmlns': NS_OMEMO})
@@ -562,9 +652,11 @@ class OmemoPlugin(GajimPlugin):
if not msg_dict: if not msg_dict:
return True return True
self.cleanup_stanza(event)
self.gc_message[msg_dict['payload']] = plaintext self.gc_message[msg_dict['payload']] = plaintext
encrypted_node = OmemoMessage(msg_dict) encrypted_node = OmemoMessage(msg_dict)
event.msg_iq.delChild('body')
event.msg_iq.addChild(node=encrypted_node) event.msg_iq.addChild(node=encrypted_node)
# XEP-0380: Explicit Message Encryption # XEP-0380: Explicit Message Encryption
@@ -588,40 +680,12 @@ class OmemoPlugin(GajimPlugin):
self.print_msg_to_log(event.correction_msg) self.print_msg_to_log(event.correction_msg)
else: else:
self.print_msg_to_log(event.msg_iq) self.print_msg_to_log(event.msg_iq)
callback(event)
except Exception as e: except Exception as e:
log.debug(e) log.debug(e)
return True
@log_calls('OmemoPlugin')
def handle_outgoing_event(self, event):
""" Handles a message outgoing event
In this event we have no stanza. XHTML is set to None
so that it doesnt make its way into the stanza
Parameters
----------
event : MessageOutgoingEvent
Returns
-------
Return if encryption is not activated
"""
if event.type_ == 'normal':
return False
account = event.account
if account in self.disabled_accounts:
return return
state = self.get_omemo_state(account)
if not state.encryption.is_active(event.jid): def _encrypt_message(self, conn, event, callback):
return False
event.xhtml = None
@log_calls('OmemoPlugin')
def handle_outgoing_stanza(self, event):
""" Manipulates the outgoing stanza """ Manipulates the outgoing stanza
The body is getting encrypted The body is getting encrypted
@@ -645,8 +709,6 @@ class OmemoPlugin(GajimPlugin):
state = self.get_omemo_state(account) state = self.get_omemo_state(account)
full_jid = str(event.msg_iq.getAttr('to')) full_jid = str(event.msg_iq.getAttr('to'))
to_jid = gajim.get_jid_without_resource(full_jid) to_jid = gajim.get_jid_without_resource(full_jid)
if not state.encryption.is_active(to_jid):
return
# Delete previous Message out of Correction Message Stanza # Delete previous Message out of Correction Message Stanza
if event.msg_iq.getTag('replace', namespace=NS_CORRECT): if event.msg_iq.getTag('replace', namespace=NS_CORRECT):
@@ -660,23 +722,7 @@ class OmemoPlugin(GajimPlugin):
return True return True
encrypted_node = OmemoMessage(msg_dict) encrypted_node = OmemoMessage(msg_dict)
self.cleanup_stanza(event)
# Check if non-OMEMO resource is online
contacts = gajim.contacts.get_contacts(account, to_jid)
non_omemo_resource_online = False
for contact in contacts:
if contact.show == 'offline':
continue
if not contact.supports(NS_NOTIFY):
log.debug(contact.get_full_jid() +
' => Contact doesnt support OMEMO, '
'adding Info Message to Body')
support_msg = 'You received a message encrypted with ' \
'OMEMO but your client doesnt support OMEMO.'
event.msg_iq.setBody(support_msg)
non_omemo_resource_online = True
if not non_omemo_resource_online:
event.msg_iq.delChild('body')
event.msg_iq.addChild(node=encrypted_node) event.msg_iq.addChild(node=encrypted_node)
@@ -691,11 +737,25 @@ class OmemoPlugin(GajimPlugin):
store = Node('store', attrs={'xmlns': NS_HINTS}) store = Node('store', attrs={'xmlns': NS_HINTS})
event.msg_iq.addChild(node=store) event.msg_iq.addChild(node=store)
self.print_msg_to_log(event.msg_iq) self.print_msg_to_log(event.msg_iq)
event.xhtml = None
callback(event)
except Exception as e: except Exception as e:
log.debug(e) log.debug(e)
return True
@log_calls('OmemoPlugin') @staticmethod
def cleanup_stanza(obj):
''' We make sure only allowed tags are in the stanza '''
stanza = nbxmpp.Message(
to=obj.msg_iq.getTo(),
typ=obj.msg_iq.getType())
stanza.setThread(obj.msg_iq.getThread())
for tag, ns in ALLOWED_TAGS:
node = obj.msg_iq.getTag(tag, namespace=ns)
if node:
stanza.addChild(node=node)
obj.msg_iq = stanza
def handle_device_list_update(self, event): def handle_device_list_update(self, event):
""" Check if the passed event is a device list update and store the new """ Check if the passed event is a device list update and store the new
device ids. device ids.
@@ -762,29 +822,10 @@ class OmemoPlugin(GajimPlugin):
self.query_for_bundles.remove(contact_jid) self.query_for_bundles.remove(contact_jid)
# Enable Encryption on receiving first Device List # Enable Encryption on receiving first Device List
if not state.encryption.exist(contact_jid): # TODO
if account in self.ui_list and \
contact_jid in self.ui_list[account]:
log.debug(account +
' => Switch encryption ON automatically ...')
self.ui_list[account][contact_jid].activate_omemo()
else:
log.debug(account +
' => Switch encryption ON automatically ...')
self.omemo_enable_for(contact_jid, account)
if account in self.ui_list and \
contact_jid not in self.ui_list[account]:
chat_control = gajim.interface.msg_win_mgr.get_control(
contact_jid, account)
if chat_control:
self.connect_ui(chat_control)
return True return True
@log_calls('OmemoPlugin')
def publish_own_devices_list(self, account, new=False): def publish_own_devices_list(self, account, new=False):
""" Get all currently known own active device ids and publish them """ Get all currently known own active device ids and publish them
@@ -813,58 +854,6 @@ class OmemoPlugin(GajimPlugin):
id_ = str(iq.getAttr('id')) id_ = str(iq.getAttr('id'))
IQ_CALLBACK[id_] = lambda event: log.debug(event) IQ_CALLBACK[id_] = lambda event: log.debug(event)
@log_calls('OmemoPlugin')
def connect_ui(self, chat_control):
""" Method called from Gajim when a Chat Window is opened
Parameters
----------
chat_control : ChatControl
Gajim ChatControl object
"""
account = chat_control.contact.account.name
if account in self.disabled_accounts:
return
contact_jid = chat_control.contact.jid
if account not in self.ui_list:
self.ui_list[account] = {}
state = self.get_omemo_state(account)
my_jid = gajim.get_jid_from_account(account)
omemo_enabled = state.encryption.is_active(contact_jid)
if omemo_enabled:
log.debug(account + " => Adding OMEMO ui for " + contact_jid)
self.ui_list[account][contact_jid] = Ui(self, chat_control,
omemo_enabled, state)
self.ui_list[account][contact_jid].new_fingerprints_available()
return
if contact_jid in state.device_ids or contact_jid == my_jid:
log.debug(account + " => Adding OMEMO ui for " + contact_jid)
self.ui_list[account][contact_jid] = Ui(self, chat_control,
omemo_enabled, state)
self.ui_list[account][contact_jid].new_fingerprints_available()
else:
log.warning(account + " => No devices for " + contact_jid)
if chat_control.type_id == message_control.TYPE_GC:
self.ui_list[account][contact_jid] = Ui(self, chat_control,
omemo_enabled, state)
self.ui_list[account][contact_jid].sensitive(False)
@log_calls('OmemoPlugin')
def disconnect_ui(self, chat_control):
""" Calls the removeUi method to remove all relatad UI objects.
Parameters
----------
chat_control : ChatControl
Gajim ChatControl object
"""
contact_jid = chat_control.contact.jid
account = chat_control.contact.account.name
if account in self.disabled_accounts:
return
self.ui_list[account][contact_jid].removeUi()
def are_keys_missing(self, account, contact_jid): def are_keys_missing(self, account, contact_jid):
""" Checks if devicekeys are missing and querys the """ Checks if devicekeys are missing and querys the
bundles bundles
@@ -932,7 +921,6 @@ class OmemoPlugin(GajimPlugin):
finally: finally:
del IQ_CALLBACK[id_] del IQ_CALLBACK[id_]
@log_calls('OmemoPlugin')
def fetch_device_bundle_information(self, account, jid, device_id): def fetch_device_bundle_information(self, account, jid, device_id):
""" Fetch bundle information for specified jid, key, and create axolotl """ Fetch bundle information for specified jid, key, and create axolotl
session on success. session on success.
@@ -956,7 +944,6 @@ class OmemoPlugin(GajimPlugin):
device_id) device_id)
gajim.connections[account].connection.send(iq) gajim.connections[account].connection.send(iq)
@log_calls('OmemoPlugin')
def session_from_prekey_bundle(self, account, stanza, def session_from_prekey_bundle(self, account, stanza,
recipient_id, device_id): recipient_id, device_id):
""" Starts a session from a PreKey bundle. """ Starts a session from a PreKey bundle.
@@ -995,12 +982,11 @@ class OmemoPlugin(GajimPlugin):
log.info(account + ' => session created for: ' + recipient_id) log.info(account + ' => session created for: ' + recipient_id)
# Trigger dialog to trust new Fingerprints if # Trigger dialog to trust new Fingerprints if
# the Chat Window is Open # the Chat Window is Open
if account in self.ui_list and \ ctrl = gajim.interface.msg_win_mgr.get_control(
recipient_id in self.ui_list[account]: recipient_id, account)
self.ui_list[account][recipient_id]. \ if ctrl:
new_fingerprints_available() self.new_fingerprints_available(ctrl)
@log_calls('OmemoPlugin')
def query_own_devicelist(self, account): def query_own_devicelist(self, account):
""" Query own devicelist from the server. """ Query own devicelist from the server.
@@ -1017,7 +1003,6 @@ class OmemoPlugin(GajimPlugin):
IQ_CALLBACK[id_] = lambda stanza: \ IQ_CALLBACK[id_] = lambda stanza: \
self.handle_devicelist_result(account, stanza) self.handle_devicelist_result(account, stanza)
@log_calls('OmemoPlugin')
def publish_bundle(self, account): def publish_bundle(self, account):
""" Publish our bundle information to the PEP node. """ Publish our bundle information to the PEP node.
@@ -1055,7 +1040,6 @@ class OmemoPlugin(GajimPlugin):
else: else:
log.error(account + ' => Publishing bundle was NOT successful') log.error(account + ' => Publishing bundle was NOT successful')
@log_calls('OmemoPlugin')
def handle_devicelist_result(self, account, stanza): def handle_devicelist_result(self, account, stanza):
""" If query was successful add own device to the list. """ If query was successful add own device to the list.
@@ -1094,7 +1078,6 @@ class OmemoPlugin(GajimPlugin):
log.error(account + ' => Devicelistquery was NOT successful') log.error(account + ' => Devicelistquery was NOT successful')
self.publish_own_devices_list(account, new=True) self.publish_own_devices_list(account, new=True)
@log_calls('OmemoPlugin')
def clear_device_list(self, account): def clear_device_list(self, account):
""" Clears the local devicelist of our own devices and publishes """ Clears the local devicelist of our own devices and publishes
a new one including only the current ID of this device a new one including only the current ID of this device
@@ -1126,7 +1109,18 @@ class OmemoPlugin(GajimPlugin):
log.debug(stanzastr) log.debug(stanzastr)
log.debug('-'*15) log.debug('-'*15)
@log_calls('OmemoPlugin') @staticmethod
def plain_warning(chat_control):
chat_control.print_conversation_line(
'Received plaintext message! ' +
'Your next message will still be encrypted!', 'status', '', None)
@staticmethod
def no_trusted_fingerprints_warning(chat_control):
msg = "To send an encrypted message, you have to " \
"first trust the fingerprint of your contact!"
chat_control.print_conversation_line(msg, 'status', '', None)
def omemo_enable_for(self, jid, account): def omemo_enable_for(self, jid, account):
""" Used by the UI to enable OMEMO for a specified contact. """ Used by the UI to enable OMEMO for a specified contact.
@@ -1144,7 +1138,6 @@ class OmemoPlugin(GajimPlugin):
state = self.get_omemo_state(account) state = self.get_omemo_state(account)
state.encryption.activate(jid) state.encryption.activate(jid)
@log_calls('OmemoPlugin')
def omemo_disable_for(self, jid, account): def omemo_disable_for(self, jid, account):
""" Used by the UI to disable OMEMO for a specified contact. """ Used by the UI to disable OMEMO for a specified contact.

View File

@@ -1,37 +1,32 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
#
# Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de> '''
# Copyright 2015 Daniel Gultsch <daniel@cgultsch.de> Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
# Copyright 2015 Daniel Gultsch <daniel@cgultsch.de>
# This file is part of Gajim-OMEMO plugin. Copyright 2016 Philipp Hörist <philipp@hoerist.com>
#
# The Gajim-OMEMO plugin is free software: you can redistribute it and/or modify This file is part of Gajim-OMEMO plugin.
# it under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any The Gajim-OMEMO plugin is free software: you can redistribute it and/or modify
# later version. it under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# Gajim-OMEMO is distributed in the hope that it will be useful, but WITHOUT ANY later version.
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details. 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
# You should have received a copy of the GNU General Public License along with A PARTICULAR PURPOSE. See the GNU General Public License for more details.
# the Gajim-OMEMO plugin. If not, see <http://www.gnu.org/licenses/>.
# You should have received a copy of the GNU General Public License along with
the Gajim-OMEMO plugin. If not, see <http://www.gnu.org/licenses/>.
'''
import binascii import binascii
import logging import logging
import os import os
import message_control from enum import IntEnum, unique
from gi.repository import GObject, Gtk, GdkPixbuf, Gdk from gi.repository import Gtk, GdkPixbuf, Gdk
# pylint: disable=import-error
import gtkgui_helpers
from common import gajim
from dialogs import YesNoDialog
from plugins.gui import GajimPluginConfigDialog
from axolotl.state.sessionrecord import SessionRecord from axolotl.state.sessionrecord import SessionRecord
from common import configpaths
log = logging.getLogger('gajim.plugin_system.omemo') log = logging.getLogger('gajim.plugin_system.omemo')
@@ -43,285 +38,17 @@ except Exception as e:
log.exception('Error:') log.exception('Error:')
log.error('python-qrcode or dependencies of it, are not available') log.error('python-qrcode or dependencies of it, are not available')
# pylint: enable=import-error from common import gajim
UNDECIDED = 2 from common import configpaths
TRUSTED = 1 from dialogs import YesNoDialog
UNTRUSTED = 0 from plugins.gui import GajimPluginConfigDialog
class OmemoButton(Gtk.Button): @unique
def __init__(self, plugin, chat_control, ui, enabled): class State(IntEnum):
super(OmemoButton, self).__init__(label=None, stock=None) UNTRUSTED = 0
self.chat_control = chat_control TRUSTED = 1
UNDECIDED = 2
self.set_property('relief', Gtk.ReliefStyle.NONE)
self.set_property('can-focus', False)
self.set_sensitive(True)
icon = Gtk.Image.new_from_file(
plugin.local_file_path('omemo16x16.png'))
self.set_image(icon)
self.set_tooltip_text('OMEMO Encryption')
self.connect('clicked', self.on_click)
self.menu = OmemoMenu(ui, enabled)
def on_click(self, widget):
"""
Popup omemo menu
"""
gtkgui_helpers.popup_emoticons_under_button(
self.menu, widget, self.chat_control.parent_win)
def set_omemo_state(self, state):
self.menu.set_omemo_state(state)
class OmemoMenu(Gtk.Menu):
def __init__(self, ui, enabled):
super(OmemoMenu, self).__init__()
self.ui = ui
self.item_omemo_state = Gtk.CheckMenuItem('Activate OMEMO')
self.item_omemo_state.set_active(enabled)
self.item_omemo_state.connect('activate', self.on_toggle_omemo)
self.append(self.item_omemo_state)
item = Gtk.ImageMenuItem('Fingerprints')
icon = Gtk.Image.new_from_stock(Gtk.STOCK_DIALOG_AUTHENTICATION,
Gtk.IconSize.MENU)
item.set_image(icon)
item.connect('activate', self.on_open_fingerprint_window)
self.append(item)
self.show_all()
def on_toggle_omemo(self, widget):
self.ui.set_omemo_state(widget.get_active())
def on_open_fingerprint_window(self, widget):
self.ui.show_fingerprint_window()
def set_omemo_state(self, state):
self.item_omemo_state.handler_block_by_func(self.on_toggle_omemo)
self.item_omemo_state.set_active(state)
self.item_omemo_state.handler_unblock_by_func(self.on_toggle_omemo)
class Ui(object):
def __init__(self, plugin, chat_control, enabled, state):
self.contact = chat_control.contact
self.chat_control = chat_control
self.plugin = plugin
self.state = state
self.account = self.contact.account.name
self.windowinstances = {}
self.groupchat = False
if chat_control.type_id == message_control.TYPE_GC:
self.groupchat = True
self.omemo_capable = False
self.room = self.chat_control.room_jid
self.display_omemo_state()
self.refresh_auth_lock_icon()
self.omemobutton = OmemoButton(plugin, chat_control, self, enabled)
self.actions_hbox = chat_control.xml.get_object('actions_hbox')
send_button = chat_control.xml.get_object('send_button')
send_button_pos = self.actions_hbox.child_get_property(send_button,
'position')
self.actions_hbox.add(self.omemobutton)
self.actions_hbox.reorder_child(self.omemobutton, send_button_pos - 1)
self.omemobutton.show_all()
# add a OMEMO entry to the context/advanced menu
self.chat_control.omemo_orig_prepare_context_menu = \
self.chat_control.prepare_context_menu
def omemo_prepare_context_menu(hide_buttonbar_items=False):
menu = self.chat_control. \
omemo_orig_prepare_context_menu(hide_buttonbar_items)
submenu = OmemoMenu(self, self.encryption_active())
item = Gtk.ImageMenuItem('OMEMO Encryption')
icon_path = plugin.local_file_path('omemo16x16.png')
item.set_image(Gtk.Image.new_from_file(icon_path))
item.set_submenu(submenu)
if self.groupchat:
item.set_sensitive(self.omemo_capable)
# at index 8 is the separator after the esession encryption entry
menu.insert(item, 8)
return menu
self.chat_control.prepare_context_menu = omemo_prepare_context_menu
# Hook into Send Button so we can check Stuff before sending
self.chat_control.orig_send_message = \
self.chat_control.send_message
def omemo_send_message(message, keyID='', chatstate=None, xhtml=None,
process_commands=True, attention=False):
self.new_fingerprints_available()
if self.encryption_active() and \
self.plugin.are_keys_missing(self.account,
self.contact.jid):
log.debug(self.account + ' => No Trusted Fingerprints for ' +
self.contact.jid)
self.no_trusted_fingerprints_warning()
else:
self.chat_control.orig_send_message(message, keyID, chatstate,
xhtml, process_commands,
attention)
log.debug(self.account + ' => Sending Message to ' +
self.contact.jid)
def omemo_send_gc_message(message, xhtml=None, process_commands=True):
self.new_fingerprints_available()
if self.encryption_active():
missing = True
own_jid = gajim.get_jid_from_account(self.account)
for nick in self.plugin.groupchat[self.room]:
real_jid = self.plugin.groupchat[self.room][nick]
if real_jid == own_jid:
continue
if not self.plugin.are_keys_missing(self.account,
real_jid):
missing = False
if missing:
log.debug(self.account +
' => No Trusted Fingerprints for ' +
self.room)
self.no_trusted_fingerprints_warning()
else:
self.chat_control.orig_send_message(message, xhtml,
process_commands)
log.debug(self.account + ' => Sending Message to ' +
self.room)
else:
self.chat_control.orig_send_message(message, xhtml,
process_commands)
log.debug(self.account + ' => Sending Message to ' +
self.room)
if self.groupchat:
self.chat_control.send_message = omemo_send_gc_message
else:
self.chat_control.send_message = omemo_send_message
def set_omemo_state(self, enabled):
"""
Enable or disable OMEMO for this window's contact and update the
window ui accordingly
"""
if enabled:
log.debug(self.contact.account.name + ' => Enable OMEMO for ' +
self.contact.jid)
self.plugin.omemo_enable_for(self.contact.jid,
self.contact.account.name)
self.refresh_auth_lock_icon()
else:
log.debug(self.contact.account.name + ' => Disable OMEMO for ' +
self.contact.jid)
self.plugin.omemo_disable_for(self.contact.jid,
self.contact.account.name)
self.refresh_auth_lock_icon()
self.omemobutton.set_omemo_state(enabled)
self.display_omemo_state()
def sensitive(self, value):
self.omemobutton.set_sensitive(value)
self.omemo_capable = value
if value:
self.chat_control.prepare_context_menu
def encryption_active(self):
return self.state.encryption.is_active(self.contact.jid)
def activate_omemo(self):
if not self.encryption_active():
self.set_omemo_state(True)
def new_fingerprints_available(self):
jid = self.contact.jid
if self.groupchat and self.room in self.plugin.groupchat:
for nick in self.plugin.groupchat[self.room]:
real_jid = self.plugin.groupchat[self.room][nick]
fingerprints = self.state.store. \
getNewFingerprints(real_jid)
if fingerprints:
self.show_fingerprint_window(fingerprints)
elif not self.groupchat:
fingerprints = self.state.store.getNewFingerprints(jid)
if fingerprints:
self.show_fingerprint_window(fingerprints)
def show_fingerprint_window(self, fingerprints=None):
if 'dialog' not in self.windowinstances:
if self.groupchat:
self.windowinstances['dialog'] = \
FingerprintWindow(self.plugin, self.contact,
self.chat_control.parent_win.window,
self.windowinstances, groupchat=True)
else:
self.windowinstances['dialog'] = \
FingerprintWindow(self.plugin, self.contact,
self.chat_control.parent_win.window,
self.windowinstances)
self.windowinstances['dialog'].show_all()
if fingerprints:
log.debug(self.account +
' => Showing Fingerprint Prompt for ' +
self.contact.jid)
self.state.store.setShownFingerprints(fingerprints)
else:
self.windowinstances['dialog'].update_context_list()
if fingerprints:
self.state.store.setShownFingerprints(fingerprints)
def plain_warning(self):
self.chat_control.print_conversation_line(
'Received plaintext message! ' +
'Your next message will still be encrypted!', 'status', '', None)
def display_omemo_state(self):
if self.encryption_active():
msg = u'OMEMO encryption enabled'
else:
msg = u'OMEMO encryption disabled'
self.chat_control.print_conversation_line(msg, 'status', '', None)
def no_trusted_fingerprints_warning(self):
msg = "To send an encrypted message, you have to " \
"first trust the fingerprint of your contact!"
self.chat_control.print_conversation_line(msg, 'status', '', None)
def refresh_auth_lock_icon(self):
if self.groupchat:
return
if self.encryption_active():
if self.state.getUndecidedFingerprints(self.contact.jid):
self.chat_control._show_lock_image(True, 'OMEMO', True, True,
False)
else:
self.chat_control._show_lock_image(True, 'OMEMO', True, True,
True)
else:
self.chat_control._show_lock_image(False, 'OMEMO', False, True,
False)
def removeUi(self):
self.actions_hbox.remove(self.omemobutton)
self.chat_control._show_lock_image(False, 'OMEMO', False, True,
False)
self.chat_control.prepare_context_menu = \
self.chat_control.omemo_orig_prepare_context_menu
self.chat_control.send_message = self.chat_control.orig_send_message
class OMEMOConfigDialog(GajimPluginConfigDialog): class OMEMOConfigDialog(GajimPluginConfigDialog):
@@ -480,7 +207,7 @@ class OMEMOConfigDialog(GajimPluginConfigDialog):
mod, paths = self.fpr_view.get_selection().get_selected_rows() mod, paths = self.fpr_view.get_selection().get_selected_rows()
def on_yes(checked, identity_key): def on_yes(checked, identity_key):
state.store.setTrust(identity_key, TRUSTED) state.store.setTrust(identity_key, State.TRUSTED)
try: try:
if self.plugin.ui_list[account]: if self.plugin.ui_list[account]:
self.plugin.ui_list[account][jid]. \ self.plugin.ui_list[account][jid]. \
@@ -490,7 +217,7 @@ class OMEMOConfigDialog(GajimPluginConfigDialog):
self.update_context_list() self.update_context_list()
def on_no(identity_key): def on_no(identity_key):
state.store.setTrust(identity_key, UNTRUSTED) state.store.setTrust(identity_key, State.UNTRUSTED)
try: try:
if jid in self.plugin.ui_list[account]: if jid in self.plugin.ui_list[account]:
self.plugin.ui_list[account][jid]. \ self.plugin.ui_list[account][jid]. \
@@ -700,17 +427,11 @@ class FingerprintWindow(Gtk.Dialog):
mod, paths = self.fpr_view.get_selection().get_selected_rows() mod, paths = self.fpr_view.get_selection().get_selected_rows()
def on_yes(checked, identity_key): def on_yes(checked, identity_key):
state.store.setTrust(identity_key, TRUSTED) state.store.setTrust(identity_key, State.TRUSTED)
if not self.groupchat:
self.plugin.ui_list[self.account][self.contact.jid]. \
refresh_auth_lock_icon()
self.update_context_list() self.update_context_list()
def on_no(identity_key): def on_no(identity_key):
state.store.setTrust(identity_key, UNTRUSTED) state.store.setTrust(identity_key, State.UNTRUSTED)
if not self.groupchat:
self.plugin.ui_list[self.account][self.contact.jid]. \
refresh_auth_lock_icon()
self.update_context_list() self.update_context_list()
for path in paths: for path in paths: