diff libervia/backend/plugins/plugin_xep_0373.py @ 4212:5f2d496c633f

core: get rid of `pickle`: Use of `pickle` to serialise data was a technical legacy that was causing trouble to store in database, to update (if a class was serialised, a change could break update), and to security (pickle can lead to code execution). This patch remove all use of Pickle in favour in JSON, notably: - for caching data, a Pydantic model is now used instead - for SQLAlchemy model, the LegacyPickle is replaced by JSON serialisation - in XEP-0373 a class `PublicKeyMetadata` was serialised. New method `from_dict` and `to_dict` method have been implemented to do serialisation. - new methods to (de)serialise data can now be specified with Identity data types. It is notably used to (de)serialise `path` of avatars. A migration script has been created to convert data (for upgrade or downgrade), with special care for XEP-0373 case. Depending of size of database, this migration script can be long to run. rel 443
author Goffi <goffi@goffi.org>
date Fri, 23 Feb 2024 13:31:04 +0100
parents 7c5654c54fed
children b53b6dc1f929
line wrap: on
line diff
--- a/libervia/backend/plugins/plugin_xep_0373.py	Fri Feb 16 18:46:06 2024 +0100
+++ b/libervia/backend/plugins/plugin_xep_0373.py	Fri Feb 23 13:31:04 2024 +0100
@@ -20,12 +20,13 @@
 import base64
 from datetime import datetime, timezone
 import enum
+import json
 import secrets
 import string
 from typing import Any, Dict, Iterable, List, Literal, Optional, Set, Tuple, cast
 from xml.sax.saxutils import quoteattr
 
-from typing_extensions import Final, NamedTuple, Never, assert_never
+from typing import Final, NamedTuple, Never, assert_never
 from wokkel import muc, pubsub
 from wokkel.disco import DiscoFeature, DiscoInfo
 import xmlschema
@@ -812,10 +813,21 @@
     """
     Metadata about a published public key.
     """
-
     fingerprint: str
     timestamp: datetime
 
+    def to_dict(self) -> dict:
+        # Convert the instance to a dictionary and handle datetime serialization
+        data = self._asdict()
+        data['timestamp'] = self.timestamp.isoformat()
+        return data
+
+    @staticmethod
+    def from_dict(data: dict) -> 'PublicKeyMetadata':
+        # Load a serialised dictionary
+        data['timestamp'] = datetime.fromisoformat(data['timestamp'])
+        return PublicKeyMetadata(**data)
+
 
 @enum.unique
 class TrustLevel(enum.Enum):
@@ -1102,10 +1114,10 @@
 
         storage_key = STR_KEY_PUBLIC_KEYS_METADATA.format(sender.userhost())
 
-        local_public_keys_metadata = cast(
-            Set[PublicKeyMetadata],
-            await self.__storage[profile].get(storage_key, set())
-        )
+        local_public_keys_metadata = {
+            PublicKeyMetadata.from_dict(pkm)
+            for pkm in await self.__storage[profile].get(storage_key, [])
+        }
 
         unchanged_keys = new_public_keys_metadata & local_public_keys_metadata
         changed_or_new_keys = new_public_keys_metadata - unchanged_keys
@@ -1149,7 +1161,10 @@
 
                 await self.publish_public_keys_list(client, new_public_keys_metadata)
 
-        await self.__storage[profile].force(storage_key, new_public_keys_metadata)
+        await self.__storage[profile].force(
+            storage_key,
+            [pkm.to_dict() for pkm in new_public_keys_metadata]
+        )
 
     def list_public_keys(self, client: SatXMPPClient, jid: jid.JID) -> Set[GPGPublicKey]:
         """List GPG public keys available for a JID.
@@ -1191,10 +1206,10 @@
 
         storage_key = STR_KEY_PUBLIC_KEYS_METADATA.format(client.jid.userhost())
 
-        public_keys_list = cast(
-            Set[PublicKeyMetadata],
-            await self.__storage[client.profile].get(storage_key, set())
-        )
+        public_keys_list = {
+            PublicKeyMetadata.from_dict(pkm)
+            for pkm in await self.__storage[client.profile].get(storage_key, [])
+        }
 
         public_keys_list.add(PublicKeyMetadata(
             fingerprint=secret_key.public_key.fingerprint,
@@ -1508,10 +1523,10 @@
 
         storage_key = STR_KEY_PUBLIC_KEYS_METADATA.format(entity_jid.userhost())
 
-        public_keys_metadata = cast(
-            Set[PublicKeyMetadata],
-            await self.__storage[client.profile].get(storage_key, set())
-        )
+        public_keys_metadata = {
+            PublicKeyMetadata.from_dict(pkm)
+            for pkm in await self.__storage[client.profile].get(storage_key, [])
+        }
         if not public_keys_metadata:
             public_keys_metadata = await self.download_public_keys_list(
                 client, entity_jid
@@ -1522,7 +1537,8 @@
                 )
             else:
                 await self.__storage[client.profile].aset(
-                    storage_key, public_keys_metadata
+                    storage_key,
+                    [pkm.to_dict() for pkm in public_keys_metadata]
                 )