comparison libervia/backend/plugins/plugin_xep_0384.py @ 4219:1b5cf2ee1d86

plugin XEP-0384, XEP-0391: download missing devices list: when a peer jid was not in our roster, devices list was not retrieved, resulting in failed en/decryption. This patch does check it and download missing devices list in necessary. There is no subscription managed yet, so the list won't be updated in case of new devices, this should be addressed at some point.
author Goffi <goffi@goffi.org>
date Tue, 05 Mar 2024 17:31:36 +0100
parents c0f3f29377f1
children e11b13418ba6
comparison
equal deleted inserted replaced
4218:c0f3f29377f1 4219:1b5cf2ee1d86
20 from datetime import datetime 20 from datetime import datetime
21 import enum 21 import enum
22 import logging 22 import logging
23 import time 23 import time
24 from typing import \ 24 from typing import \
25 Any, Dict, FrozenSet, List, Literal, NamedTuple, Optional, Set, Type, Union, cast 25 Any, Dict, FrozenSet, Iterable, List, Literal, NamedTuple, Optional, Set, Type, Union, cast
26 import uuid 26 import uuid
27 import xml.etree.ElementTree as ET 27 import xml.etree.ElementTree as ET
28 from xml.sax.saxutils import quoteattr 28 from xml.sax.saxutils import quoteattr
29 29
30 from typing_extensions import Final, Never, assert_never 30 from typing_extensions import Final, Never, assert_never
94 } 94 }
95 95
96 96
97 PARAM_CATEGORY = "Security" 97 PARAM_CATEGORY = "Security"
98 PARAM_NAME = "omemo_policy" 98 PARAM_NAME = "omemo_policy"
99
100 NamespaceType = Literal["urn:xmpp:omemo:2", "eu.siacs.conversations.axolotl"]
99 101
100 102
101 class LogHandler(logging.Handler): 103 class LogHandler(logging.Handler):
102 """ 104 """
103 Redirect python-omemo's log output to Libervia's log system. 105 Redirect python-omemo's log output to Libervia's log system.
2477 self.__xep_0334.add_hint_elements(stanza, [ "store" ]) 2479 self.__xep_0334.add_hint_elements(stanza, [ "store" ])
2478 2480
2479 # Let the flow continue. 2481 # Let the flow continue.
2480 return True 2482 return True
2481 2483
2484 async def download_missing_device_lists(
2485 self,
2486 client: SatXMPPClient,
2487 namespace: NamespaceType,
2488 recipients: Iterable[jid.JID],
2489 session_manager: omemo.SessionManager,
2490 ) -> None:
2491 """Retrieves missing device lists for recipients outside the profile's roster.
2492
2493 @param client: XMPP client.
2494 @param namespace: The namespace of the OMEMO version to use.
2495 @param recipients: Recipients to verify device list presence.
2496 @param session_manager: OMEMO session manager.
2497 """
2498 recipients = [j.userhostJID() for j in recipients]
2499 not_in_roster = [j for j in recipients if not client.roster.is_jid_in_roster(j)]
2500 for bare_jid in not_in_roster:
2501 device_information = await session_manager.get_device_information(
2502 bare_jid.userhost()
2503 )
2504 if (
2505 not device_information
2506 or not all(namespace in di.namespaces for di in device_information)
2507 ):
2508 if namespace == self.NS_TWOMEMO:
2509 algo, node = "OMEMO", TWOMEMO_DEVICE_LIST_NODE
2510 elif namespace == self.NS_OLDMEMO:
2511 algo, node = "OMEMO_legacy", OLDMEMO_DEVICE_LIST_NODE
2512 else:
2513 raise ValueError(f"Invalid namespace: {namespace!r}")
2514
2515 try:
2516 items, __ = await self._j.get_items(client, bare_jid, node, 1)
2517
2518 except Exception:
2519 log.exception(f"Can't find {algo} devices list for {bare_jid}.")
2520 else:
2521 await self._update_device_list(client, bare_jid, items)
2522 log.warning(f"{algo} devices list updated for {bare_jid}.")
2523
2482 async def encrypt( 2524 async def encrypt(
2483 self, 2525 self,
2484 client: SatXMPPClient, 2526 client: SatXMPPClient,
2485 namespace: Literal["urn:xmpp:omemo:2", "eu.siacs.conversations.axolotl"], 2527 namespace: NamespaceType,
2486 stanza: domish.Element, 2528 stanza: domish.Element,
2487 recipient_jids: Union[jid.JID, Set[jid.JID]], 2529 recipient_jids: Union[jid.JID, Set[jid.JID]],
2488 is_muc_message: bool, 2530 is_muc_message: bool,
2489 stanza_id: Optional[str] 2531 stanza_id: Optional[str]
2490 ) -> None: 2532 ) -> None:
2600 return 2642 return
2601 2643
2602 log.debug(f"Plaintext to encrypt: {plaintext}") 2644 log.debug(f"Plaintext to encrypt: {plaintext}")
2603 2645
2604 session_manager = await self.get_session_manager(client.profile) 2646 session_manager = await self.get_session_manager(client.profile)
2647 await self.download_missing_device_lists(client, namespace, recipient_jids, session_manager)
2605 2648
2606 try: 2649 try:
2607 messages, encryption_errors = await session_manager.encrypt( 2650 messages, encryption_errors = await session_manager.encrypt(
2608 frozenset(recipient_bare_jids), 2651 frozenset(recipient_bare_jids),
2609 { namespace: plaintext }, 2652 { namespace: plaintext },
2677 @param profile: The profile this event belongs to. 2720 @param profile: The profile this event belongs to.
2678 """ 2721 """
2679 2722
2680 sender = cast(jid.JID, items_event.sender) 2723 sender = cast(jid.JID, items_event.sender)
2681 items = cast(List[domish.Element], items_event.items) 2724 items = cast(List[domish.Element], items_event.items)
2725 client = self.host.get_client(profile)
2726 await self._update_device_list(client, sender, items)
2727
2728 async def _update_device_list(
2729 self,
2730 client: SatXMPPEntity,
2731 sender: jid.JID,
2732 items: list[domish.Element]
2733 ) -> None:
2682 2734
2683 if len(items) > 1: 2735 if len(items) > 1:
2684 log.warning("Ignoring device list update with more than one element.") 2736 log.warning("Ignoring device list update with more than one element.")
2685 return 2737 return
2686 2738
2717 f"Malformed device list update item:" 2769 f"Malformed device list update item:"
2718 f" {ET.tostring(item_elt, encoding='unicode')}" 2770 f" {ET.tostring(item_elt, encoding='unicode')}"
2719 ) 2771 )
2720 return 2772 return
2721 2773
2722 session_manager = await self.get_session_manager(profile) 2774 session_manager = await self.get_session_manager(client.profile)
2723 2775
2724 await session_manager.update_device_list( 2776 await session_manager.update_device_list(
2725 namespace, 2777 namespace,
2726 sender.userhost(), 2778 sender.userhost(),
2727 device_list 2779 device_list