core (memory/migration):

Update XEP-0384 and `fe3a02cb4bec_convert_legacypickle_columns_to_json.py` migration to
properly handle (de)serialisation of `TrustMessageCacheEntry`.
This commit is contained in:
Goffi 2024-03-05 17:31:12 +01:00
commit 3694bb3991
2 changed files with 89 additions and 26 deletions

View file

@ -10,6 +10,7 @@ import sqlalchemy as sa
import pickle
import json
from libervia.backend.plugins.plugin_xep_0373 import PublicKeyMetadata
from libervia.backend.plugins.plugin_xep_0384 import TrustMessageCacheEntry
# revision identifiers, used by Alembic.
revision = "fe3a02cb4bec"
@ -46,6 +47,21 @@ def convert_pickle_to_json(value, table, primary_keys):
# `from_dict` methods.
deserialized = [pkm.to_dict() for pkm in deserialized]
elif (
table == "private_ind_bin"
and primary_keys[0] == "XEP-0384/TM"
and primary_keys[1] == "cache"
):
# Same issue and solution as for XEP-0373
try:
deserialized = [tm.to_dict() for tm in deserialized]
except Exception as e:
print(
"Warning: Failed to convert Trust Management cache with value "
f" {deserialized!r}, using empty array instead: {e}"
)
deserialized=[]
ret = json.dumps(deserialized, ensure_ascii=False, default=str)
if table == 'history' and ret == "{}":
# For history, we can remove empty data, but for other tables it may be
@ -104,6 +120,14 @@ def convert_json_to_pickle(value, table, primary_keys):
# Convert list of dicts back to set of PublicKeyMetadata objects
if isinstance(deserialized, list):
deserialized = {PublicKeyMetadata.from_dict(d) for d in deserialized}
elif (
table == "private_ind_bin"
and primary_keys[0] == "XEP-0384/TM"
and primary_keys[1] == "cache"
):
# Convert list of dicts back to set of TrustMessageCacheEntry objects
if isinstance(deserialized, list):
deserialized = {TrustMessageCacheEntry.from_dict(d) for d in deserialized}
return pickle.dumps(deserialized, 0)
except Exception as e:
print(

View file

@ -434,6 +434,22 @@ class TrustUpdate(NamedTuple):
target_key: bytes
target_trust: bool
def to_dict(self) -> dict[str, Any]:
"""Convert the instance to a serialised dictionary"""
data = {
"target_jid": self.target_jid.full(),
"target_key": self.target_key.hex(),
"target_trust": self.target_trust,
}
return data
@staticmethod
def from_dict(data: dict[str, Any]) -> "TrustUpdate":
"""Load a serialized dictionary"""
data["target_jid"] = jid.JID(data["target_jid"])
data["target_key"] = bytes.fromhex(data["target_key"])
return TrustUpdate(**data)
class TrustMessageCacheEntry(NamedTuple):
# pylint: disable=invalid-name
@ -446,6 +462,25 @@ class TrustMessageCacheEntry(NamedTuple):
timestamp: datetime
trust_update: TrustUpdate
def to_dict(self) -> dict[str, Any]:
"""Convert the instance to a serialised dictionary"""
data = {
"sender_jid": self.sender_jid.full(),
"sender_key": self.sender_key.hex(),
"timestamp": self.timestamp.isoformat(),
"trust_update": self.trust_update.to_dict()
}
return data
@staticmethod
def from_dict(data: dict[str, Any]) -> "TrustMessageCacheEntry":
"""Load a serialized dictionary"""
data["sender_jid"] = jid.JID(data["sender_jid"])
data["sender_key"] = bytes.fromhex(data["sender_key"])
data["timestamp"] = datetime.fromisoformat(data["timestamp"])
data["trust_update"] = TrustUpdate.from_dict(data["trust_update"])
return TrustMessageCacheEntry(**data)
class PartialTrustMessage(NamedTuple):
# pylint: disable=invalid-name
@ -478,10 +513,10 @@ async def manage_trust_message_cache(
)
# Load cache entries
cache_entries = cast(
Set[TrustMessageCacheEntry],
await trust_message_cache.get("cache", set())
)
cache_entries = {
TrustMessageCacheEntry.from_dict(d)
for d in await trust_message_cache.get("cache", [])
}
# Expire cache entries that were overwritten by the applied trust updates
cache_entries_by_target = {
@ -534,7 +569,10 @@ async def manage_trust_message_cache(
cache_entries.remove(cache_entry)
# Store the updated cache entries
await trust_message_cache.force("cache", cache_entries)
await trust_message_cache.force(
"cache",
[tm.to_dict() for tm in cache_entries]
)
# TODO: Notify the user ("feedback") about automatically updated trust?
@ -1540,22 +1578,23 @@ class OMEMO:
custom_policies={}
)
def __init__(self, sat: LiberviaBackend) -> None:
def __init__(self, host: LiberviaBackend) -> None:
"""
@param sat: The SAT instance.
"""
self.__sat = sat
self.host = host
# Add configuration option to choose between manual trust and BTBV as the trust
# model
sat.memory.update_params(DEFAULT_TRUST_MODEL_PARAM)
host.memory.update_params(DEFAULT_TRUST_MODEL_PARAM)
# Plugins
self.__xep_0045 = cast(Optional[XEP_0045], sat.plugins.get("XEP-0045"))
self.__xep_0334 = cast(XEP_0334, sat.plugins["XEP-0334"])
self.__xep_0359 = cast(Optional[XEP_0359], sat.plugins.get("XEP-0359"))
self.__xep_0420 = cast(XEP_0420, sat.plugins["XEP-0420"])
self._j = cast(XEP_0060, host.plugins["XEP-0060"])
self.__xep_0045 = cast(Optional[XEP_0045], host.plugins.get("XEP-0045"))
self.__xep_0334 = cast(XEP_0334, host.plugins["XEP-0334"])
self.__xep_0359 = cast(Optional[XEP_0359], host.plugins.get("XEP-0359"))
self.__xep_0420 = cast(XEP_0420, host.plugins["XEP-0420"])
# In contrast to one to one messages, MUC messages are reflected to the sender.
# Thus, the sender does not add messages to their local message log when sending
@ -1579,23 +1618,23 @@ class OMEMO:
# 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
# added, the message_received trigger is also used for twomemo.
sat.trigger.add(
host.trigger.add(
"message_received",
self._message_received_trigger,
priority=100050
)
sat.trigger.add("send", self.__send_trigger, priority=0)
host.trigger.add("send", self.__send_trigger, priority=0)
# TODO: Add new triggers here for freshly received and about-to-be-sent stanzas,
# including IQs.
# Give twomemo a (slightly) higher priority than oldmemo
sat.register_encryption_plugin(self, "OMEMO", twomemo.twomemo.NAMESPACE, 101)
sat.register_encryption_plugin(
host.register_encryption_plugin(self, "OMEMO", twomemo.twomemo.NAMESPACE, 101)
host.register_encryption_plugin(
self, "OMEMO_legacy", oldmemo.oldmemo.NAMESPACE, 100
)
xep_0163 = cast(XEP_0163, sat.plugins["XEP-0163"])
xep_0163 = cast(XEP_0163, host.plugins["XEP-0163"])
xep_0163.add_pep_event(
"TWOMEMO_DEVICES",
TWOMEMO_DEVICE_LIST_NODE,
@ -1612,7 +1651,7 @@ class OMEMO:
)
try:
self.__text_commands = cast(TextCommands, sat.plugins[C.TEXT_CMDS])
self.__text_commands = cast(TextCommands, host.plugins[C.TEXT_CMDS])
except KeyError:
log.info(_("Text commands not available"))
else:
@ -1759,7 +1798,7 @@ class OMEMO:
))
# Check whether ATM is enabled and handle everything in case it is
trust_system = cast(str, self.__sat.memory.param_get_a(
trust_system = cast(str, self.host.memory.param_get_a(
PARAM_NAME,
PARAM_CATEGORY,
profile_key=profile
@ -1781,7 +1820,7 @@ class OMEMO:
return {}
submit_id = self.__sat.register_callback(callback, with_data=True, one_shot=True)
submit_id = self.host.register_callback(callback, with_data=True, one_shot=True)
result = xml_tools.XMLUI(
panel_type=C.XMLUI_FORM,
@ -1921,7 +1960,7 @@ class OMEMO:
# Build and store the session manager
try:
session_manager = await prepare_for_profile(
self.__sat,
self.host,
profile,
initial_own_label="Libervia"
)
@ -2021,10 +2060,10 @@ class OMEMO:
))
# Load existing cache entries
existing_cache_entries = cast(
Set[TrustMessageCacheEntry],
await trust_message_cache.get("cache", set())
)
existing_cache_entries = {
TrustMessageCacheEntry.from_dict(d)
for d in await trust_message_cache.get("cache", [])
}
# Discard cache entries by timestamp comparison
existing_by_target = {
@ -2083,7 +2122,7 @@ class OMEMO:
# Store the remaining existing and new cache entries
await trust_message_cache.force(
"cache",
existing_cache_entries | new_cache_entries
[tm.to_dict() for tm in existing_cache_entries | new_cache_entries]
)
# If the trust of at least one device was modified, run the ATM cache update logic