[fractal] room: Retry to decrypt room messages



commit 002be9075ee78665ad98095f9b3fa092cd4e591d
Author: Julian Sparber <julian sparber net>
Date:   Fri May 13 12:24:08 2022 +0200

    room: Retry to decrypt room messages

 .../content/room_history/message_row/mod.rs        |  2 +-
 src/session/mod.rs                                 | 33 ++++++++-
 src/session/room/event.rs                          | 46 +++++++++++-
 src/session/room/mod.rs                            | 85 +++++++++++++++++++++-
 4 files changed, 161 insertions(+), 5 deletions(-)
---
diff --git a/src/session/content/room_history/message_row/mod.rs 
b/src/session/content/room_history/message_row/mod.rs
index 74f72bb35..864a97e7c 100644
--- a/src/session/content/room_history/message_row/mod.rs
+++ b/src/session/content/room_history/message_row/mod.rs
@@ -412,7 +412,7 @@ fn build_content(parent: &adw::Bin, event: &Event, compact: bool) {
                 parent.set_child(Some(&child));
                 child
             };
-            child.text(gettext("Fractal couldn’t decrypt this message."));
+            child.text(gettext("Fractal couldn’t decrypt this message, but will retry once the keys are 
available."));
         }
         Some(AnyMessageLikeEventContent::RoomRedaction(_)) => {
             let child = if let Some(Ok(child)) = parent.child().map(|w| w.downcast::<MessageText>())
diff --git a/src/session/mod.rs b/src/session/mod.rs
index 8de119523..0f748fe69 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -26,6 +26,7 @@ use log::{debug, error, warn};
 use matrix_sdk::{
     config::{RequestConfig, SyncSettings},
     deserialized_responses::SyncResponse,
+    room::Room as MatrixRoom,
     ruma::{
         api::{
             client::{
@@ -36,7 +37,10 @@ use matrix_sdk::{
             error::{FromHttpResponseError, ServerError},
         },
         assign,
-        events::{direct::DirectEventContent, GlobalAccountDataEvent},
+        events::{
+            direct::DirectEventContent, room::encryption::SyncRoomEncryptionEvent,
+            GlobalAccountDataEvent,
+        },
         RoomId,
     },
     store::{make_store_config, OpenStoreError},
@@ -476,6 +480,7 @@ impl Session {
 
                 self.room_list().load();
                 self.setup_direct_room_handler();
+                self.setup_room_encrypted_changes();
 
                 self.sync();
 
@@ -881,6 +886,32 @@ impl Session {
             })
         );
     }
+
+    fn setup_room_encrypted_changes(&self) {
+        let session_weak = glib::SendWeakRef::from(self.downgrade());
+        let client = self.client();
+        spawn_tokio!(async move {
+            client
+                .register_event_handler(
+                    move |_: SyncRoomEncryptionEvent, matrix_room: MatrixRoom| {
+                        let session_weak = session_weak.clone();
+                        async move {
+                            let ctx = glib::MainContext::default();
+                            ctx.spawn(async move {
+                                if let Some(session) = session_weak.upgrade() {
+                                    if let Some(room) =
+                                        session.room_list().get(matrix_room.room_id())
+                                    {
+                                        room.set_is_encrypted(true);
+                                    }
+                                }
+                            });
+                        }
+                    },
+                )
+                .await;
+        });
+    }
 }
 
 impl Default for Session {
diff --git a/src/session/room/event.rs b/src/session/room/event.rs
index 8f46970b5..79919570f 100644
--- a/src/session/room/event.rs
+++ b/src/session/room/event.rs
@@ -11,11 +11,13 @@ use matrix_sdk::{
     ruma::{
         events::{
             room::{
+                encrypted::RoomEncryptedEventContent,
                 message::{MessageType, Relation},
                 redaction::SyncRoomRedactionEvent,
             },
             AnyMessageLikeEventContent, AnySyncMessageLikeEvent, AnySyncRoomEvent,
-            AnySyncStateEvent, MessageLikeUnsigned, SyncMessageLikeEvent, SyncStateEvent,
+            AnySyncStateEvent, MessageLikeUnsigned, OriginalSyncMessageLikeEvent,
+            SyncMessageLikeEvent, SyncStateEvent,
         },
         MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId,
     },
@@ -27,7 +29,7 @@ use super::{
     Member, ReactionList, Room,
 };
 use crate::{
-    spawn_tokio,
+    spawn, spawn_tokio,
     utils::{filename_for_mime, media_type_uid},
 };
 
@@ -54,6 +56,7 @@ mod imp {
         pub replacing_events: RefCell<Vec<super::Event>>,
         pub reactions: ReactionList,
         pub source_changed_handler: RefCell<Option<SignalHandlerId>>,
+        pub keys_handle: RefCell<Option<SignalHandlerId>>,
         pub room: OnceCell<WeakRef<Room>>,
     }
 
@@ -221,6 +224,16 @@ impl Event {
         let priv_ = self.imp();
 
         if let Ok(deserialized) = event.event.deserialize() {
+            if let AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(
+                SyncMessageLikeEvent::Original(ref encrypted),
+            )) = deserialized
+            {
+                let encrypted = encrypted.to_owned();
+                spawn!(clone!(@weak self as obj => async move {
+                    obj.try_to_decrypt(encrypted).await;
+                }));
+            }
+
             priv_.event.replace(Some(deserialized));
         } else {
             warn!("Failed to deserialize event: {:?}", event);
@@ -230,6 +243,35 @@ impl Event {
 
         self.notify("event");
         self.notify("activatable");
+        self.notify("source");
+    }
+
+    async fn try_to_decrypt(&self, event: OriginalSyncMessageLikeEvent<RoomEncryptedEventContent>) {
+        let priv_ = self.imp();
+        let room = self.room().matrix_room();
+        let handle = spawn_tokio!(async move { room.decrypt_event(&event).await });
+
+        match handle.await.unwrap() {
+            Ok(decrypted) => {
+                if let Some(keys_handle) = priv_.keys_handle.take() {
+                    self.room().disconnect(keys_handle);
+                }
+                self.set_matrix_pure_event(decrypted.into());
+            }
+            Err(error) => {
+                warn!("Failed to decrypt event: {}", error);
+                if priv_.keys_handle.borrow().is_none() {
+                    let handle = self.room().connect_new_encryption_keys(
+                        clone!(@weak self as obj => move |_| {
+                            // Try to decrypt the event again
+                            obj.set_matrix_pure_event(obj.matrix_pure_event());
+                        }),
+                    );
+
+                    priv_.keys_handle.replace(Some(handle));
+                }
+            }
+        }
     }
 
     pub fn matrix_sender(&self) -> OwnedUserId {
diff --git a/src/session/room/mod.rs b/src/session/room/mod.rs
index 9db979fe7..b61758957 100644
--- a/src/session/room/mod.rs
+++ b/src/session/room/mod.rs
@@ -31,11 +31,12 @@ use matrix_sdk::{
                 redaction::{OriginalSyncRoomRedactionEvent, RoomRedactionEventContent},
                 topic::RoomTopicEventContent,
             },
+            room_key::ToDeviceRoomKeyEventContent,
             tag::{TagInfo, TagName},
             AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncMessageLikeEvent,
             AnySyncRoomEvent, AnySyncStateEvent, EventContent, MessageLikeEventType,
             MessageLikeUnsigned, OriginalSyncMessageLikeEvent, StateEventType,
-            SyncMessageLikeEvent, SyncStateEvent,
+            SyncMessageLikeEvent, SyncStateEvent, ToDeviceEvent,
         },
         receipt::ReceiptType,
         serde::Raw,
@@ -110,6 +111,8 @@ mod imp {
         pub successor: OnceCell<OwnedRoomId>,
         /// The most recent verification request event.
         pub verification: RefCell<Option<IdentityVerification>>,
+        /// Whether this room is encrypted
+        pub is_encrypted: Cell<bool>,
     }
 
     #[glib::object_subclass]
@@ -241,6 +244,13 @@ mod imp {
                         IdentityVerification::static_type(),
                         glib::ParamFlags::READWRITE,
                     ),
+                    glib::ParamSpecBoolean::new(
+                        "encrypted",
+                        "Encrypted",
+                        "Whether this room is encrypted",
+                        false,
+                        glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
+                    ),
                 ]
             });
 
@@ -276,6 +286,7 @@ mod imp {
                     obj.store_topic(topic);
                 }
                 "verification" => obj.set_verification(value.get().unwrap()),
+                "encrypted" => obj.set_is_encrypted(value.get().unwrap()),
                 _ => unimplemented!(),
             }
         }
@@ -310,6 +321,7 @@ mod imp {
                     |id| id.as_ref().to_value(),
                 ),
                 "verification" => obj.verification().to_value(),
+                "encrypted" => obj.is_encrypted().to_value(),
                 _ => unimplemented!(),
             }
         }
@@ -319,6 +331,7 @@ mod imp {
                 vec![
                     Signal::builder("order-changed", &[], <()>::static_type().into()).build(),
                     Signal::builder("room-forgotten", &[], <()>::static_type().into()).build(),
+                    Signal::builder("new-encryption-keys", &[], <()>::static_type().into()).build(),
                 ]
             });
             SIGNALS.as_ref()
@@ -338,6 +351,7 @@ mod imp {
                 .unwrap();
 
             obj.load_power_levels();
+            obj.setup_is_encrypted();
 
             obj.bind_property("display-name", obj.avatar(), "display-name")
                 .flags(glib::BindingFlags::SYNC_CREATE)
@@ -1637,6 +1651,75 @@ impl Room {
 
         self.set_latest_unread(latest_unread);
     }
+
+    pub fn is_encrypted(&self) -> bool {
+        self.imp().is_encrypted.get()
+    }
+
+    pub fn set_is_encrypted(&self, is_encrypted: bool) {
+        let was_encrypted = self.is_encrypted();
+        if was_encrypted == is_encrypted {
+            return;
+        }
+
+        if was_encrypted && !is_encrypted {
+            error!("Encryption for a room can't be disabled");
+            return;
+        }
+
+        if self.matrix_room().is_encrypted() != is_encrypted {
+            // TODO: enable encryption if it isn't enabled yet
+        }
+
+        self.setup_is_encrypted();
+    }
+
+    fn setup_is_encrypted(&self) {
+        if !self.matrix_room().is_encrypted() {
+            return;
+        }
+        self.setup_new_encryption_keys_handler();
+        self.imp().is_encrypted.set(true);
+        self.notify("encrypted");
+    }
+
+    fn setup_new_encryption_keys_handler(&self) {
+        spawn!(
+            glib::PRIORITY_DEFAULT_IDLE,
+            clone!(@weak self as obj => async move {
+                let obj_weak = glib::SendWeakRef::from(obj.downgrade());
+                    obj.session().client().register_event_handler(
+                        move |event: ToDeviceEvent<ToDeviceRoomKeyEventContent>| {
+                            let obj_weak = obj_weak.clone();
+                            async move {
+                                let ctx = glib::MainContext::default();
+                                ctx.spawn(async move {
+                                        if let Some(room) = obj_weak.upgrade() {
+                                            if room.room_id() == event.content.room_id {
+                                                room.emit_by_name::<()>("new-encryption-keys", &[]);
+                                            }
+                                        }
+                                });
+                            }
+                        },
+                    )
+                    .await;
+            })
+        );
+    }
+
+    pub fn connect_new_encryption_keys<F: Fn(&Self) + 'static>(
+        &self,
+        f: F,
+    ) -> glib::SignalHandlerId {
+        self.connect_local("new-encryption-keys", true, move |values| {
+            let obj = values[0].get::<Self>().unwrap();
+
+            f(&obj);
+
+            None
+        })
+    }
 }
 
 /// Whether the given event can count as an unread message.


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]