Merge branch 'e2ee-muc' into 'master'

E2ee muc

See merge request poezio/poezio!53
This commit is contained in:
Maxime Buquet 2020-01-01 22:52:28 +01:00
commit e6d2347d93
3 changed files with 89 additions and 11 deletions

View file

@ -26,6 +26,7 @@ disabled.
from typing import List, Dict, Type, Optional, Union
from collections import defaultdict
from slixmpp import JID
from poezio import tabs
from poezio.events import EventHandler
@ -38,6 +39,7 @@ class Tabs:
'_current_index',
'_current_tab',
'_tabs',
'_tab_jids',
'_tab_types',
'_tab_names',
'_previous_tab',
@ -56,6 +58,7 @@ class Tabs:
self._previous_tab = None # type: Optional[tabs.Tab]
self._tabs = [] # type: List[tabs.Tab]
self._tab_jids = dict() # type: Dict[JID, tabs.Tab]
self._tab_types = defaultdict(
list) # type: Dict[Type[tabs.Tab], List[tabs.Tab]]
self._tab_names = dict() # type: Dict[str, tabs.Tab]
@ -111,6 +114,10 @@ class Tabs:
"""Return the tab list"""
return self._tabs
def by_jid(self, jid: JID) -> Optional[tabs.Tab]:
"""Get a tab with a specific jid"""
return self._tab_jids.get(jid)
def by_name(self, name: str) -> Optional[tabs.Tab]:
"""Get a tab with a specific name"""
return self._tab_names.get(name)
@ -142,11 +149,14 @@ class Tabs:
return None
def _rebuild(self):
self._tab_jids = dict()
self._tab_types = defaultdict(list)
self._tab_names = dict()
for tab in self._tabs:
for cls in _get_tab_types(tab):
self._tab_types[cls].append(tab)
if hasattr(tab, 'jid'):
self._tab_jids[tab.jid] = tab
self._tab_names[tab.name] = tab
self._update_numbers()
@ -206,6 +216,8 @@ class Tabs:
self._tabs.append(tab)
for cls in _get_tab_types(tab):
self._tab_types[cls].append(tab)
if hasattr(tab, 'jid'):
self._tab_jids[tab.jid] = tab
self._tab_names[tab.name] = tab
def delete(self, tab: tabs.Tab, gap=False):
@ -222,6 +234,8 @@ class Tabs:
for cls in _get_tab_types(tab):
self._tab_types[cls].remove(tab)
if hasattr(tab, 'jid'):
del self._tab_jids[tab.jid]
del self._tab_names[tab.name]
if gap:

View file

@ -91,6 +91,7 @@ class SafetyMetaclass(type):
async def async_helper(*args, **kwargs):
passthrough = kwargs.pop('passthrough', False)
try:
log.debug('FOO: %r, %r', args, kwargs)
return await f(*args, **kwargs)
except:
if passthrough:

View file

@ -336,7 +336,9 @@ class E2EEPlugin(BasePlugin):
dump_tuple(get_theme().COLOR_CHAR_NACK),
exc,
)
tab.nack_message(msg, stanza['id'], stanza['from'])
# XXX: check before commit. Do we not nack in MUCs?
if not isinstance(tab, MucTab):
tab.nack_message(msg, stanza['id'], stanza['from'])
# TODO: display exceptions to the user properly
log.error('Exception in encrypt:', exc_info=True)
return None
@ -353,6 +355,7 @@ class E2EEPlugin(BasePlugin):
if not has_eme and self.encrypted_tags is not None:
for (namespace, tag) in self.encrypted_tags:
if message.xml.find('{%s}%s' % (namespace, tag)) is not None:
# TODO: count all encrypted tags.
has_encrypted_tag = True
break
@ -361,19 +364,73 @@ class E2EEPlugin(BasePlugin):
log.debug('Received %s message: %r', self.encryption_name, message['body'])
self.decrypt(message, tab)
# Get the original JID of the sender. The JID might be None if it
# comes from a semi-anonymous MUC for example. Some plugins might be
# fine with this so let them handle it.
jid = message['from']
muctab = tab
if isinstance(muctab, PrivateTab):
muctab = tab.parent_muc
jid = None
if isinstance(muctab, MucTab):
nick = message['from'].resource
for user in muctab.users:
if user.nick == nick:
jid = user.jid or None
break
self.decrypt(message, jid, tab)
log.debug('Decrypted %s message: %r', self.encryption_name, message['body'])
return None
async def _encrypt(self, stanza: StanzaBase) -> Optional[StanzaBase]:
if not isinstance(stanza, Message) or stanza['type'] not in ('chat', 'groupchat'):
if not isinstance(stanza, Message) or stanza['type'] not in ('normal', 'chat', 'groupchat'):
raise NothingToEncrypt()
message = stanza
jid = stanza['to']
tab = self.core.tabs.by_name_and_class(jid, ChatTab)
if not self._encryption_enabled(jid):
# Find who to encrypt to. If in a groupchat this can be multiple JIDs.
# It is possible that we are not able to find a jid (e.g., semi-anon
# MUCs). Let the plugin decide what to do with this information.
jids = [message['to']] # type: Optional[List[JID]]
tab = self.core.tabs.by_jid(message['to'])
if tab is None: # When does that ever happen?
log.debug('Attempting to encrypt a message to \'%s\' '
'that is not attached to a Tab. ?! Aborting '
'encryption.', message['to'])
return None
parent = None
if isinstance(tab, PrivateTab):
parent = tab.parent_muc
nick = tab.jid.resource
jids = None
for user in parent.users:
if user.nick == nick:
jids = user.jid or None
break
if isinstance(tab, MucTab):
jids = []
for user in tab.users:
# If the JID of a user is None, assume all others are None and
# we are in a (at least) semi-anon room. TODO: Really check if
# the room is semi-anon. Currently a moderator of a semi-anon
# room will possibly encrypt to everybody, leaking their
# public key/identity, and they wouldn't be able to decrypt it
# anyway if they don't know the moderator's JID.
# TODO: Change MUC to give easier access to this information.
if user.jid is None:
jids = None
break
# If we encrypt to all of these JIDs is up to the plugin, we
# just tell it who is in the room.
jids.append(user.jid)
if not self._encryption_enabled(tab.jid):
raise NothingToEncrypt()
log.debug('Sending %s message: %r', self.encryption_name, message)
@ -392,11 +449,13 @@ class E2EEPlugin(BasePlugin):
return None
# Call the enabled encrypt method
func = self._enabled_tabs[jid]
func = self._enabled_tabs[tab.jid]
if iscoroutinefunction(func):
await func(message, tab, passthrough=True)
# pylint: disable=unexpected-keyword-arg
await func(message, jids, tab, passthrough=True)
else:
func(message, tab, passthrough=True)
# pylint: disable=unexpected-keyword-arg
func(message, jids, tab, passthrough=True)
if has_body:
# Only add EME tag if the message has a body.
@ -437,13 +496,16 @@ class E2EEPlugin(BasePlugin):
option_name = '%s:%s' % (self.encryption_short_name, fingerprint)
return config.get(option=option_name, section=jid)
async def decrypt(self, _message: Message, tab: ChatTabs):
async def decrypt(self, message: Message, jid: Optional[JID], tab: ChatTab):
"""Decryption method
This is a method the plugin must implement. It is expected that this
method will edit the received message and return nothing.
:param message: Message to be decrypted.
:param jid: Real Jid of the sender if available. We might be
talking through a semi-anonymous MUC where real JIDs are
not available.
:param tab: Tab the message is coming from.
:returns: None
@ -451,13 +513,14 @@ class E2EEPlugin(BasePlugin):
raise NotImplementedError
async def encrypt(self, _message: Message, tab: ChatTabs):
async def encrypt(self, message: Message, jids: Optional[List[JID]], tab: ChatTabs):
"""Encryption method
This is a method the plugin must implement. It is expected that this
method will edit the received message and return nothing.
:param message: Message to be encrypted.
:param jids: Real Jids of all possible recipients.
:param tab: Tab the message is going to.
:returns: None