diff options
-rw-r--r-- | poezio/core/tabs.py | 14 | ||||
-rw-r--r-- | poezio/plugin.py | 1 | ||||
-rw-r--r-- | poezio/plugin_e2ee.py | 85 |
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 |