summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--poezio/core/tabs.py14
-rw-r--r--poezio/plugin.py1
-rw-r--r--poezio/plugin_e2ee.py85
3 files changed, 89 insertions, 11 deletions
diff --git a/poezio/core/tabs.py b/poezio/core/tabs.py
index 3d4db8b0..abea7313 100644
--- a/poezio/core/tabs.py
+++ b/poezio/core/tabs.py
@@ -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:
diff --git a/poezio/plugin.py b/poezio/plugin.py
index 61e0ea87..94cb330f 100644
--- a/poezio/plugin.py
+++ b/poezio/plugin.py
@@ -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:
diff --git a/poezio/plugin_e2ee.py b/poezio/plugin_e2ee.py
index d4b26d46..daf00818 100644
--- a/poezio/plugin_e2ee.py
+++ b/poezio/plugin_e2ee.py
@@ -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