Update the OTR plugin

make it more restrictive and give some hints
This commit is contained in:
mathieui 2018-08-12 12:59:40 +02:00
parent 7d3403d288
commit 31fc9d00f5
No known key found for this signature in database
GPG key ID: C59F84CEEFD616E3
2 changed files with 69 additions and 59 deletions

View file

@ -44,11 +44,26 @@ Install the python module:
You can also use pip in a virtualenv (built-in as pyvenv_ with python since 3.3)
with the requirements.txt at the root of the poezio directory.
Important details
-----------------
The OTR session is considered for a full JID (e.g. toto@example/**client1**),
but the trust is set with a bare JID (e.g. toto@example). This is important
in the case of Private Chats (in a chatroom), since you cannot always get the
real JID of your contact (or check if the same nick is used by different people).
.. note::
This also means that you cannot have an OTR session in the "common"
conversation tab, which is not locked to a specific JID. After activating
the plugin, you need to open a session with a full JID to be able to use
OTR.
Usage
-----
Command added to Conversation Tabs and Private Tabs:
Command added to Static Conversation Tabs (opened with ``/message foo@bar/baz`` or
by expanding a contact in the roster) and Private Tabs:
.. glossary::
@ -161,14 +176,6 @@ Configuration
The :term:`require_encryption`, :term:`decode_xhtml`, :term:`decode_entities`
and :term:`log` configuration parameters are tab-specific.
Important details
-----------------
The OTR session is considered for a full JID (e.g. toto@example/**client1**),
but the trust is set with a bare JID (e.g. toto@example). This is important
in the case of Private Chats (in a chatroom), since you cannot always get the
real JID of your contact (or check if the same nick is used by different people).
.. _Off The Record messaging: http://wiki.xmpp.org/web/OTR
.. _pyvenv: https://docs.python.org/3/using/scripts.html#pyvenv-creating-virtual-environments
@ -188,12 +195,13 @@ from potr.context import NotEncryptedError, UnencryptedMessage, ErrorReceived, N
STATE_ENCRYPTED, STATE_PLAINTEXT, STATE_FINISHED, Context, Account, crypt
from poezio import common
from poezio import xhtml
from poezio import xdg
from poezio import xhtml
from poezio.common import safeJID
from poezio.config import config
from poezio.plugin import BasePlugin
from poezio.tabs import ConversationTab, DynamicConversationTab, PrivateTab
from poezio.roster import roster
from poezio.tabs import StaticConversationTab, PrivateTab
from poezio.theming import get_theme, dump_tuple
from poezio.decorators import command_args_parser
from poezio.core.structs import Completion
@ -234,6 +242,15 @@ MESSAGE_NOT_SENT = _('%(info)sYour message to %(jid_c)s%(jid)s%(info)s was'
'encrypted session.\nWait until it is established or '
'change your configuration.')
INCOMPATIBLE_TAB = _('%(info)sYour message to %(jid_c)s%(jid)s%(info)s was'
' not sent because your configuration requires an '
'encrypted session and the current tab is a bare-jid '
'one, with which you cannot open or use an OTR session.'
' You need to open a fulljid tab with /message if you '
'want to use OTR.%(help)s')
TAB_HELP_RESOURCE = _('\nChoose the relevant one among the following:%s')
OTR_REQUEST = _('%(info)sOTR request to %(jid_c)s%(jid)s%(info)s sent.')
OTR_OWN_FPR = _('%(info)sYour OTR key fingerprint is '
@ -304,6 +321,7 @@ TRUST_REMOVED = _('%(info)sYou removed %(jid_c)s%(bare_jid)s%(info)s with '
KEY_DROPPED = _('%(info)sPrivate key dropped.')
def hl(tab):
"""
Make a tab beep and change its status.
@ -363,10 +381,7 @@ class PoezioContext(Context):
tab = self.core.tabs.by_name(self.peer)
if not tab:
tab = self.core.tabs.by_name(safeJID(self.peer).bare,
DynamicConversationTab)
if tab and not tab.locked_resource == safeJID(self.peer).resource:
tab = None
tab = None
if self.state == STATE_ENCRYPTED:
if newstate == STATE_ENCRYPTED and tab:
log.debug('OTR conversation with %s refreshed', self.peer)
@ -489,7 +504,7 @@ class Plugin(BasePlugin):
self.api.add_event_handler('conversation_say_after', self.on_conversation_say)
self.api.add_event_handler('private_say_after', self.on_conversation_say)
ConversationTab.add_information_element('otr', self.display_encryption_status)
StaticConversationTab.add_information_element('otr', self.display_encryption_status)
PrivateTab.add_information_element('otr', self.display_encryption_status)
self.core.xmpp.plugin['xep_0030'].add_feature('urn:xmpp:otr:0')
@ -514,14 +529,14 @@ class Plugin(BasePlugin):
'ask: Start a verification, with a question or not\n'
'answer: Finish a verification\n')
self.api.add_tab_command(ConversationTab, 'otrsmp', self.command_smp,
self.api.add_tab_command(StaticConversationTab, 'otrsmp', self.command_smp,
help=smp_desc, usage=smp_usage, short=smp_short,
completion=self.completion_smp)
self.api.add_tab_command(PrivateTab, 'otrsmp', self.command_smp,
help=smp_desc, usage=smp_usage, short=smp_short,
completion=self.completion_smp)
self.api.add_tab_command(ConversationTab, 'otr', self.command_otr,
self.api.add_tab_command(StaticConversationTab, 'otr', self.command_otr,
help=desc, usage=usage, short=shortdesc,
completion=self.completion_otr)
self.api.add_tab_command(PrivateTab, 'otr', self.command_otr,
@ -534,7 +549,7 @@ class Plugin(BasePlugin):
self.core.xmpp.plugin['xep_0030'].del_feature(feature='urn:xmpp:otr:0')
ConversationTab.remove_information_element('otr')
StaticConversationTab.remove_information_element('otr')
PrivateTab.remove_information_element('otr')
def get_context(self, jid):
@ -734,10 +749,6 @@ class Plugin(BasePlugin):
def find_encrypted_context_with_matching(self, bare_jid):
"""
Find an OTR session from a bare JID.
Useful when a dynamic tab unlocks, which would lead to sending
unencrypted messages until it locks again, if we didnt fallback
with this.
"""
for ctx in self.contexts:
if safeJID(ctx).bare == bare_jid and self.contexts[ctx].state == STATE_ENCRYPTED:
@ -748,13 +759,8 @@ class Plugin(BasePlugin):
"""
On message sent
"""
if isinstance(tab, DynamicConversationTab) and tab.locked_resource:
jid = safeJID(tab.name)
jid.resource = tab.locked_resource
name = jid.full
else:
name = tab.name
jid = safeJID(tab.name)
name = tab.name
jid = safeJID(tab.name)
format_dict = {
'jid_c': '\x19%s}' % dump_tuple(get_theme().COLOR_MUC_JID),
@ -762,17 +768,13 @@ class Plugin(BasePlugin):
'jid': name,
}
ctx = None
ctx = self.find_encrypted_context_with_matching(jid)
default_ctx = self.get_context(name)
if isinstance(tab, DynamicConversationTab) and not tab.locked_resource:
log.debug('Unlocked tab %s found, falling back to the first encrypted chat we find.', name)
ctx = self.find_encrypted_context_with_matching(jid.bare)
if ctx is None:
ctx = default_ctx
if ctx and ctx.state == STATE_ENCRYPTED:
if is_relevant(tab) and ctx and ctx.state == STATE_ENCRYPTED:
ctx.sendMessage(0, msg['body'].encode('utf-8'))
if not tab.send_chat_state('active'):
tab.send_chat_state('inactive', always_send=True)
@ -787,12 +789,28 @@ class Plugin(BasePlugin):
del msg['body']
del msg['replace']
del msg['html']
elif ctx and ctx.getPolicy('REQUIRE_ENCRYPTION'):
tab.add_message(MESSAGE_NOT_SENT % format_dict, typ=0)
elif is_relevant(tab) and ctx and ctx.getPolicy('REQUIRE_ENCRYPTION'):
warning_msg = MESSAGE_NOT_SENT % format_dict
tab.add_message(warning_msg, typ=0)
del msg['body']
del msg['replace']
del msg['html']
self.otr_start(tab, name, format_dict)
elif not is_relevant(tab) and ctx and (
ctx.state == STATE_ENCRYPTED or ctx.getPolicy('REQUIRE_ENCRYPTION')):
contact = roster[tab.name]
res = []
if contact:
res = [resource.jid for resource in contact.resources]
help_msg = ''
if res:
help_msg = TAB_HELP_RESOURCE % ''.join(('\n - /message %s' % jid) for jid in res)
format_dict['help'] = help_msg
warning_msg = INCOMPATIBLE_TAB % format_dict
tab.add_message(warning_msg, typ=0)
del msg['body']
del msg['replace']
del msg['html']
def display_encryption_status(self, jid):
"""
@ -818,10 +836,6 @@ class Plugin(BasePlugin):
action = args.pop(0)
tab = self.api.current_tab()
name = tab.name
if isinstance(tab, DynamicConversationTab) and tab.locked_resource:
name = safeJID(tab.name)
name.resource = tab.locked_resource
name = name.full
format_dict = {
'jid_c': '\x19%s}' % dump_tuple(get_theme().COLOR_MUC_JID),
'info': '\x19%s}' % dump_tuple(get_theme().COLOR_INFORMATION_TEXT),
@ -833,11 +847,6 @@ class Plugin(BasePlugin):
if action == 'end': # close the session
context = self.get_context(name)
context.disconnect()
if isinstance(tab, DynamicConversationTab):
ctx = self.find_encrypted_context_with_matching(safeJID(name).bare)
while ctx is not None:
ctx.disconnect()
ctx = self.find_encrypted_context_with_matching(safeJID(name).bare)
elif action == 'start' or action == 'refresh':
self.otr_start(tab, name, format_dict)
elif action == 'ourfpr':
@ -891,13 +900,7 @@ class Plugin(BasePlugin):
secs = self.config.get('timeout', 3)
def notify_otr_timeout():
tab_name = tab.name
otr = self.get_context(tab_name)
if isinstance(tab, DynamicConversationTab):
if tab.locked_resource:
tab_name = safeJID(tab.name)
tab_name.resource = tab.locked_resource
tab_name = tab_name.full
otr = self.get_context(tab_name)
otr = self.find_encrypted_context_with_matching(tab_name)
if otr.state != STATE_ENCRYPTED:
format_dict['secs'] = secs
text = OTR_NOT_ENABLED % format_dict
@ -938,11 +941,6 @@ class Plugin(BasePlugin):
tab = self.api.current_tab()
name = tab.name
if isinstance(tab, DynamicConversationTab) and tab.locked_resource:
name = safeJID(tab.name)
name.resource = tab.locked_resource
name = name.full
format_dict = {
'jid_c': '\x19%s}' % dump_tuple(get_theme().COLOR_MUC_JID),
'info': '\x19%s}' % dump_tuple(get_theme().COLOR_INFORMATION_TEXT),
@ -983,3 +981,7 @@ def get_tlv(tlvs, cls):
for tlv in tlvs:
if isinstance(tlv, cls):
return tlv
def is_relevant(tab):
"""Check if a tab should be concerned with OTR"""
return isinstance(tab, (StaticConversationTab, PrivateTab))

View file

@ -409,6 +409,8 @@ class DynamicConversationTab(ConversationTab):
bad idea so it has been removed.
Only one DynamicConversationTab can be opened for a given jid.
"""
plugin_commands = {}
plugin_keys = {}
def __init__(self, core, jid, resource=None):
self.locked_resource = None
@ -418,6 +420,8 @@ class DynamicConversationTab(ConversationTab):
self.register_command(
'unlock', self.unlock_command, shortdesc='Deprecated, do nothing.')
self.resize()
self.update_commands()
self.update_keys()
def get_info_header(self):
return self.info_header
@ -475,12 +479,16 @@ class StaticConversationTab(ConversationTab):
A conversation tab associated with one Full JID. It cannot be locked to
an different resource or unlocked.
"""
plugin_commands = {}
plugin_keys = {}
def __init__(self, core, jid):
assert (safeJID(jid).resource)
ConversationTab.__init__(self, core, jid)
self.info_header = windows.ConversationInfoWin()
self.resize()
self.update_commands()
self.update_keys()
def get_info_header(self):
return self.info_header