plugin XEP-0384: code cleanup and failure tracking
This commit is contained in:
parent
0f9bfc38ac
commit
5d9f2d2da9
1 changed files with 95 additions and 60 deletions
|
|
@ -16,6 +16,7 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from asyncio import Future
|
||||
import base64
|
||||
from datetime import datetime
|
||||
import enum
|
||||
|
|
@ -64,7 +65,7 @@ from libervia.backend.plugins.plugin_xep_0420 import (
|
|||
SCEProfile,
|
||||
)
|
||||
from libervia.backend.tools import xml_tools
|
||||
from twisted.internet import defer
|
||||
from twisted.internet.defer import Deferred, ensureDeferred
|
||||
from twisted.words.protocols.jabber import error, jid
|
||||
from twisted.words.xish import domish
|
||||
|
||||
|
|
@ -155,10 +156,10 @@ class TrustLevel(enum.Enum):
|
|||
The trust levels required for ATM and BTBV.
|
||||
"""
|
||||
|
||||
TRUSTED: str = "TRUSTED"
|
||||
BLINDLY_TRUSTED: str = "BLINDLY_TRUSTED"
|
||||
UNDECIDED: str = "UNDECIDED"
|
||||
DISTRUSTED: str = "DISTRUSTED"
|
||||
TRUSTED = "TRUSTED"
|
||||
BLINDLY_TRUSTED = "BLINDLY_TRUSTED"
|
||||
UNDECIDED = "UNDECIDED"
|
||||
DISTRUSTED = "DISTRUSTED"
|
||||
|
||||
|
||||
TWOMEMO_DEVICE_LIST_NODE = "urn:xmpp:omemo:2:devices"
|
||||
|
|
@ -224,7 +225,8 @@ class LegacyStorageImpl(oldmemo.migrations.LegacyStorage):
|
|||
def __init__(self, profile: str, own_bare_jid: str) -> None:
|
||||
"""
|
||||
@param profile: The profile this OMEMO data belongs to.
|
||||
@param own_bare_jid: The own bare JID, to return by the :meth:`load_own_data` call.
|
||||
@param own_bare_jid: The own bare JID, to return by the :meth:`load_own_data`
|
||||
call.
|
||||
"""
|
||||
|
||||
self.__storage = persistent.LazyPersistentBinaryDict("XEP-0384", profile)
|
||||
|
|
@ -1361,10 +1363,11 @@ def make_session_manager(
|
|||
"This is OMEMO trusting system. You'll see below the devices of your "
|
||||
"contacts, and a checkbox to trust them or not. A trusted device "
|
||||
"can read your messages in plain text, so be sure to only validate "
|
||||
"devices that you are sure are belonging to your contact. It's better "
|
||||
"to do this when you are next to your contact and their device, so "
|
||||
'you can check the "fingerprint" (the number next to the device) '
|
||||
"yourself. Do *not* validate a device if the fingerprint is wrong!"
|
||||
"devices that you are sure are belonging to your contact. It's "
|
||||
"better to do this when you are next to your contact and their "
|
||||
'device, so you can check the "fingerprint" (the number next to the '
|
||||
"device) yourself. Do *not* validate a device if the fingerprint is "
|
||||
"wrong!"
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -1626,11 +1629,14 @@ class OMEMO:
|
|||
# really necessary.
|
||||
self.__muc_plaintext_cache: Dict[MUCPlaintextCacheKey, bytes] = {}
|
||||
|
||||
# A set containing all profiles for which OMEMO init failed.
|
||||
self.__init_failed: Set[str] = set()
|
||||
|
||||
# Mapping from profile name to corresponding session manager
|
||||
self.__session_managers: Dict[str, omemo.SessionManager] = {}
|
||||
|
||||
# Calls waiting for a specific session manager to be built
|
||||
self.__session_manager_waiters: Dict[str, List[defer.Deferred]] = {}
|
||||
self.__session_manager_waiters: Dict[str, Future[omemo.SessionManager]] = {}
|
||||
|
||||
# These triggers are used by oldmemo, which doesn't do SCE and only applies to
|
||||
# messages. Temporarily, until a more fitting trigger for SCE-based encryption is
|
||||
|
|
@ -1653,14 +1659,14 @@ class OMEMO:
|
|||
xep_0163.add_pep_event(
|
||||
"TWOMEMO_DEVICES",
|
||||
TWOMEMO_DEVICE_LIST_NODE,
|
||||
lambda items_event, profile: defer.ensureDeferred(
|
||||
lambda items_event, profile: ensureDeferred(
|
||||
self.__on_device_list_update(items_event, profile)
|
||||
),
|
||||
)
|
||||
xep_0163.add_pep_event(
|
||||
"OLDMEMO_DEVICES",
|
||||
OLDMEMO_DEVICE_LIST_NODE,
|
||||
lambda items_event, profile: defer.ensureDeferred(
|
||||
lambda items_event, profile: ensureDeferred(
|
||||
self.__on_device_list_update(items_event, profile)
|
||||
),
|
||||
)
|
||||
|
|
@ -1679,7 +1685,7 @@ class OMEMO:
|
|||
@param client: The client.
|
||||
"""
|
||||
|
||||
defer.ensureDeferred(self.get_session_manager(cast(str, client.profile)))
|
||||
ensureDeferred(self.get_session_manager(client.profile))
|
||||
|
||||
async def cmd_omemo_reset(
|
||||
self, client: SatXMPPClient, mess_data: MessageDataLegacy
|
||||
|
|
@ -1697,6 +1703,11 @@ class OMEMO:
|
|||
the message is not supposed to be sent.
|
||||
"""
|
||||
|
||||
if client.profile in self.__init_failed:
|
||||
raise exceptions.InternalError(
|
||||
f"Can't reset OMEMO for {client.profile}, OMEMO init failed."
|
||||
)
|
||||
|
||||
twomemo_requested = client.encryption.is_encryption_requested(
|
||||
mess_data, twomemo.twomemo.NAMESPACE
|
||||
)
|
||||
|
|
@ -1737,6 +1748,11 @@ class OMEMO:
|
|||
devices belonging to the entity.
|
||||
"""
|
||||
|
||||
if client.profile in self.__init_failed:
|
||||
raise exceptions.InternalError(
|
||||
f"Can't get trust UI for {client.profile}, OMEMO init failed."
|
||||
)
|
||||
|
||||
if entity.resource:
|
||||
raise ValueError("A bare JID is expected.")
|
||||
|
||||
|
|
@ -1844,8 +1860,8 @@ class OMEMO:
|
|||
" to do this when you are next to your contact and their device, so"
|
||||
' you can check the "fingerprint" (the number next to the device)'
|
||||
" yourself. Do *not* validate a device if the fingerprint is wrong!"
|
||||
" Note that manually validating a fingerprint disables any form of automatic"
|
||||
" trust."
|
||||
" Note that manually validating a fingerprint disables any form of"
|
||||
" automatic trust."
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -1948,46 +1964,50 @@ class OMEMO:
|
|||
none was prepared before.
|
||||
"""
|
||||
|
||||
try:
|
||||
# Try to return the session manager
|
||||
if profile in self.__init_failed:
|
||||
raise exceptions.InternalError(
|
||||
f"Can't get SessionManager, OMEMO init failed for {profile}."
|
||||
)
|
||||
|
||||
# Try to return the session manager
|
||||
if profile in self.__session_managers:
|
||||
return self.__session_managers[profile]
|
||||
except KeyError:
|
||||
# If a session manager for that profile doesn't exist yet, check whether it is
|
||||
# currently being built. A session manager being built is signified by the
|
||||
# profile key existing on __session_manager_waiters.
|
||||
if profile in self.__session_manager_waiters:
|
||||
# If the session manager is being built, add ourselves to the waiting
|
||||
# queue
|
||||
deferred = defer.Deferred()
|
||||
self.__session_manager_waiters[profile].append(deferred)
|
||||
return cast(omemo.SessionManager, await deferred)
|
||||
|
||||
# If the session manager is not being built, do so here.
|
||||
self.__session_manager_waiters[profile] = []
|
||||
# If a session manager for that profile doesn't exist yet, check whether it is
|
||||
# currently being built. A session manager being built is signified by the
|
||||
# profile key existing on __session_manager_waiters.
|
||||
if profile in self.__session_manager_waiters:
|
||||
# If the session manager is being built, add ourselves to the waiting queue
|
||||
return await Deferred.fromFuture(self.__session_manager_waiters[profile])
|
||||
|
||||
# Build and store the session manager
|
||||
try:
|
||||
session_manager = await prepare_for_profile(
|
||||
self.host, profile, initial_own_label="Libervia"
|
||||
)
|
||||
except Exception as e:
|
||||
# In case of an error during initalization, notify the waiters accordingly
|
||||
# and delete them
|
||||
for waiter in self.__session_manager_waiters[profile]:
|
||||
waiter.errback(e)
|
||||
del self.__session_manager_waiters[profile]
|
||||
# If the session manager is not being built, do so here.
|
||||
waiter = self.__session_manager_waiters[profile] = Future()
|
||||
|
||||
# Re-raise the exception
|
||||
raise
|
||||
# Build and store the session manager
|
||||
try:
|
||||
session_manager = await prepare_for_profile(
|
||||
self.host, profile, initial_own_label="Libervia"
|
||||
)
|
||||
except Exception as e:
|
||||
# Keep track of the failure to avoid re-attempting initialization
|
||||
self.__init_failed.add(profile)
|
||||
log.error(f"error while preparing profile for {profile}: {e}", exc_info=e)
|
||||
|
||||
self.__session_managers[profile] = session_manager
|
||||
|
||||
# Notify the waiters and delete them
|
||||
for waiter in self.__session_manager_waiters[profile]:
|
||||
waiter.callback(session_manager)
|
||||
# In case of an error during initalization, notify the waiters accordingly
|
||||
# and delete them
|
||||
waiter.set_exception(e)
|
||||
del self.__session_manager_waiters[profile]
|
||||
|
||||
return session_manager
|
||||
# Re-raise the exception
|
||||
raise
|
||||
|
||||
self.__session_managers[profile] = session_manager
|
||||
|
||||
# Notify the waiters and delete them
|
||||
waiter.set_result(session_manager)
|
||||
del self.__session_manager_waiters[profile]
|
||||
|
||||
return session_manager
|
||||
|
||||
async def __message_received_trigger_atm(
|
||||
self,
|
||||
|
|
@ -2142,7 +2162,7 @@ class OMEMO:
|
|||
self,
|
||||
client: SatXMPPClient,
|
||||
message_elt: domish.Element,
|
||||
post_treat: defer.Deferred,
|
||||
post_treat: Deferred,
|
||||
) -> bool:
|
||||
"""
|
||||
@param client: The client which received the message.
|
||||
|
|
@ -2238,10 +2258,17 @@ class OMEMO:
|
|||
next(message_elt.elements(oldmemo.oldmemo.NAMESPACE, "encrypted"), None),
|
||||
)
|
||||
|
||||
if twomemo_encrypted_elt is None and oldmemo_encrypted_elt is None:
|
||||
# None of our business, let the flow continue
|
||||
return True
|
||||
|
||||
if client.profile in self.__init_failed:
|
||||
log.error(f"Can't decrypt message, OMEMO init failed for {client.profile}")
|
||||
return True
|
||||
|
||||
try:
|
||||
session_manager = await self.get_session_manager(cast(str, client.profile))
|
||||
session_manager = await self.get_session_manager(client.profile)
|
||||
except Exception as e:
|
||||
log.error(f"error while preparing profile for {client.profile}: {e}")
|
||||
# we don't want to block the workflow
|
||||
return True
|
||||
|
||||
|
|
@ -2453,6 +2480,11 @@ class OMEMO:
|
|||
# Encryption is not requested for this recipient
|
||||
return True
|
||||
|
||||
if client.profile in self.__init_failed:
|
||||
raise exceptions.InternalError(
|
||||
f"Can't encrypt stanza, OMEMO init failed for {client.profile}."
|
||||
)
|
||||
|
||||
encryption_ns = encryption["plugin"].namespace
|
||||
# All pre-checks done, we can start encrypting!
|
||||
if encryption_ns in (twomemo.twomemo.NAMESPACE, oldmemo.oldmemo.NAMESPACE):
|
||||
|
|
@ -2540,6 +2572,11 @@ class OMEMO:
|
|||
hint to the stanza if applicable! This can be done before or after this call,
|
||||
the order doesn't matter.
|
||||
"""
|
||||
if client.profile in self.__init_failed:
|
||||
raise exceptions.InternalError(
|
||||
f"Can't encrypt stanza, OMEMO init failed for {client.profile}."
|
||||
)
|
||||
|
||||
if isinstance(recipient_jids, jid.JID):
|
||||
recipient_jids = {recipient_jids}
|
||||
if not recipient_jids:
|
||||
|
|
@ -2706,13 +2743,6 @@ class OMEMO:
|
|||
@param items_event: The event.
|
||||
@param profile: The profile this event belongs to.
|
||||
"""
|
||||
# Skip if session manager is already being built to prevent reentrancy loop
|
||||
# (device list refresh during init triggers this handler again)
|
||||
if profile in self.__session_manager_waiters:
|
||||
log.debug(
|
||||
f"Ignoring device list update for {profile} (session manager being built)"
|
||||
)
|
||||
return
|
||||
|
||||
sender = cast(jid.JID, items_event.sender)
|
||||
items = cast(List[domish.Element], items_event.items)
|
||||
|
|
@ -2722,6 +2752,11 @@ class OMEMO:
|
|||
async def _update_device_list(
|
||||
self, client: SatXMPPEntity, sender: jid.JID, items: list[domish.Element]
|
||||
) -> None:
|
||||
if client.profile in self.__init_failed:
|
||||
# It should be fine to silently ignore this here in case OMEMO failed
|
||||
# to initialize, as the data will be freshly fetched during the next
|
||||
# library init attempt (after restart).
|
||||
return
|
||||
|
||||
if len(items) > 1:
|
||||
log.warning("Ignoring device list update with more than one element.")
|
||||
|
|
|
|||
Loading…
Reference in a new issue