Merge branch 'e2ee-muc' into 'master'
E2ee muc See merge request poezio/poezio!53
This commit is contained in:
commit
e6d2347d93
3 changed files with 89 additions and 11 deletions
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue