fractal/session/model/verification/
verification_list.rs1use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
2use matrix_sdk::{
3 encryption::verification::VerificationRequest, Client as MatrixClient, Room as MatrixRoom,
4};
5use ruma::{
6 events::{
7 key::verification::request::ToDeviceKeyVerificationRequestEvent,
8 room::message::{MessageType, OriginalSyncRoomMessageEvent},
9 },
10 RoomId,
11};
12use tracing::{debug, error};
13
14use super::{load_supported_verification_methods, VerificationKey, VerificationState};
15use crate::{
16 session::model::{IdentityVerification, Member, Membership, Session, User},
17 spawn, spawn_tokio,
18};
19
20mod imp {
21 use std::{cell::RefCell, sync::LazyLock};
22
23 use glib::subclass::Signal;
24 use indexmap::IndexMap;
25
26 use super::*;
27
28 #[derive(Debug, Default, glib::Properties)]
29 #[properties(wrapper_type = super::VerificationList)]
30 pub struct VerificationList {
31 pub(super) list: RefCell<IndexMap<VerificationKey, IdentityVerification>>,
33 #[property(get, construct_only)]
35 session: glib::WeakRef<Session>,
36 }
37
38 #[glib::object_subclass]
39 impl ObjectSubclass for VerificationList {
40 const NAME: &'static str = "VerificationList";
41 type Type = super::VerificationList;
42 type Interfaces = (gio::ListModel,);
43 }
44
45 #[glib::derived_properties]
46 impl ObjectImpl for VerificationList {
47 fn signals() -> &'static [Signal] {
48 static SIGNALS: LazyLock<Vec<Signal>> =
49 LazyLock::new(|| vec![Signal::builder("secret-received").build()]);
50 SIGNALS.as_ref()
51 }
52 }
53
54 impl ListModelImpl for VerificationList {
55 fn item_type(&self) -> glib::Type {
56 IdentityVerification::static_type()
57 }
58
59 fn n_items(&self) -> u32 {
60 self.list.borrow().len() as u32
61 }
62
63 fn item(&self, position: u32) -> Option<glib::Object> {
64 self.list
65 .borrow()
66 .get_index(position as usize)
67 .map(|(_, item)| item.clone().upcast())
68 }
69 }
70
71 impl VerificationList {
72 pub(super) async fn add_to_device_request(&self, request: VerificationRequest) {
74 if request.is_done() || request.is_cancelled() || request.is_passive() {
75 return;
77 }
78
79 let Some(session) = self.session.upgrade() else {
80 return;
81 };
82
83 let verification = IdentityVerification::new(request, &session.user(), None).await;
84 self.add(verification.clone());
85
86 if verification.state() == VerificationState::Requested {
87 session
88 .notifications()
89 .show_to_device_identity_verification(&verification)
90 .await;
91 }
92 }
93
94 pub(super) async fn add_in_room_request(
96 &self,
97 request: VerificationRequest,
98 room_id: &RoomId,
99 ) {
100 if request.is_done() || request.is_cancelled() || request.is_passive() {
101 return;
103 }
104
105 let Some(session) = self.session.upgrade() else {
106 return;
107 };
108 let Some(room) = session.room_list().get(room_id) else {
109 error!(
110 "Room for verification request `({}, {})` not found",
111 request.other_user_id(),
112 request.flow_id()
113 );
114 return;
115 };
116
117 if matches!(
118 room.own_member().membership(),
119 Membership::Leave | Membership::Ban
120 ) {
121 return;
123 }
124
125 let other_user_id = request.other_user_id().to_owned();
126 let member = room.members().map_or_else(
127 || Member::new(&room, other_user_id.clone()),
128 |l| l.get_or_create(other_user_id.clone()),
129 );
130
131 let matrix_room = room.matrix_room().clone();
133 let handle =
134 spawn_tokio!(async move { matrix_room.get_member_no_sync(&other_user_id).await });
135 match handle.await.expect("task was not aborted") {
136 Ok(Some(matrix_member)) => member.update_from_room_member(&matrix_member),
137 Ok(None) => {
138 error!(
139 "Room member for verification request `({}, {})` not found",
140 request.other_user_id(),
141 request.flow_id()
142 );
143 return;
144 }
145 Err(error) => {
146 error!(
147 "Could not get room member for verification request `({}, {})`: {error}",
148 request.other_user_id(),
149 request.flow_id()
150 );
151 return;
152 }
153 }
154
155 let verification =
156 IdentityVerification::new(request, member.upcast_ref(), Some(&room)).await;
157
158 room.set_verification(Some(&verification));
159
160 self.add(verification.clone());
161
162 if verification.state() == VerificationState::Requested {
163 session
164 .notifications()
165 .show_in_room_identity_verification(&verification)
166 .await;
167 }
168 }
169
170 pub(super) fn add(&self, verification: IdentityVerification) {
172 let key = verification.key();
173
174 if self.list.borrow().contains_key(&key) {
176 return;
177 }
178
179 let obj = self.obj();
180 verification.connect_remove_from_list(clone!(
181 #[weak]
182 obj,
183 move |verification| {
184 obj.remove(&verification.key());
185 }
186 ));
187
188 let (pos, _) = self.list.borrow_mut().insert_full(key, verification);
189
190 obj.items_changed(pos as u32, 0, 1);
191 }
192 }
193}
194
195glib::wrapper! {
196 pub struct VerificationList(ObjectSubclass<imp::VerificationList>)
198 @implements gio::ListModel;
199}
200
201impl VerificationList {
202 pub fn new(session: &Session) -> Self {
204 glib::Object::builder().property("session", session).build()
205 }
206
207 pub(crate) fn init(&self) {
209 let Some(session) = self.session() else {
210 return;
211 };
212
213 let client = session.client();
214 let obj_weak = glib::SendWeakRef::from(self.downgrade());
215
216 let obj_weak_clone = obj_weak.clone();
217 client.add_event_handler(
218 move |ev: ToDeviceKeyVerificationRequestEvent, client: MatrixClient| {
219 let obj_weak = obj_weak_clone.clone();
220 async move {
221 let Some(request) = client
222 .encryption()
223 .get_verification_request(&ev.sender, &ev.content.transaction_id)
224 .await
225 else {
226 debug!(
228 "To-device verification request `({}, {})` not found in the SDK",
229 ev.sender, ev.content.transaction_id
230 );
231 return;
232 };
233
234 if !request.is_self_verification() {
235 debug!(
237 "To-device verification request `({}, {})` for other users is not supported",
238 ev.sender, ev.content.transaction_id
239 );
240 return;
241 }
242
243 let ctx = glib::MainContext::default();
244 ctx.spawn(async move {
245 spawn!(async move {
246 if let Some(obj) = obj_weak.upgrade() {
247 obj.imp().add_to_device_request(request).await;
248 }
249 });
250 });
251 }
252 },
253 );
254
255 client.add_event_handler(
256 move |ev: OriginalSyncRoomMessageEvent, room: MatrixRoom, client: MatrixClient| {
257 let obj_weak = obj_weak.clone();
258 async move {
259 let MessageType::VerificationRequest(_) = &ev.content.msgtype else {
260 return;
261 };
262 let Some(request) = client
263 .encryption()
264 .get_verification_request(&ev.sender, &ev.event_id)
265 .await
266 else {
267 debug!(
269 "To-device verification request `({}, {})` not found in the SDK",
270 ev.sender, ev.event_id
271 );
272 return;
273 };
274 let room_id = room.room_id().to_owned();
275
276 let ctx = glib::MainContext::default();
277 ctx.spawn(async move {
278 spawn!(async move {
279 if let Some(obj) = obj_weak.upgrade() {
280 obj.imp().add_in_room_request(request, &room_id).await;
281 }
282 });
283 });
284 }
285 },
286 );
287 }
288
289 pub(crate) fn remove(&self, key: &VerificationKey) {
291 let Some((pos, ..)) = self.imp().list.borrow_mut().shift_remove_full(key) else {
292 return;
293 };
294
295 self.items_changed(pos as u32, 1, 0);
296
297 if let Some(session) = self.session() {
298 session.notifications().withdraw_identity_verification(key);
299 }
300 }
301
302 pub(crate) fn get(&self, key: &VerificationKey) -> Option<IdentityVerification> {
304 self.imp().list.borrow().get(key).cloned()
305 }
306
307 pub(crate) fn ongoing_session_verification(&self) -> Option<IdentityVerification> {
309 let list = self.imp().list.borrow();
310 list.values()
311 .find(|v| v.is_self_verification() && !v.is_finished())
312 .cloned()
313 }
314
315 pub(crate) fn ongoing_room_verification(
317 &self,
318 room_id: &RoomId,
319 ) -> Option<IdentityVerification> {
320 let list = self.imp().list.borrow();
321 list.values()
322 .find(|v| v.room().is_some_and(|room| room.room_id() == room_id) && !v.is_finished())
323 .cloned()
324 }
325
326 pub(crate) async fn create(&self, user: Option<User>) -> Result<IdentityVerification, ()> {
331 let Some(session) = self.session() else {
332 error!("Could not create identity verification: failed to upgrade session");
333 return Err(());
334 };
335
336 let user = user.unwrap_or_else(|| session.user());
337
338 let supported_methods = load_supported_verification_methods().await;
339
340 let Some(identity) = user.ensure_crypto_identity().await else {
341 error!("Could not create identity verification: cryptographic identity not found");
342 return Err(());
343 };
344
345 let handle = spawn_tokio!(async move {
346 identity
347 .request_verification_with_methods(supported_methods)
348 .await
349 });
350
351 match handle.await.expect("task was not aborted") {
352 Ok(request) => {
353 let room = if let Some(room_id) = request.room_id() {
354 let Some(room) = session.room_list().get(room_id) else {
355 error!(
356 "Room for verification request `({}, {})` not found",
357 request.other_user_id(),
358 request.flow_id()
359 );
360 return Err(());
361 };
362 Some(room)
363 } else {
364 None
365 };
366
367 let verification = IdentityVerification::new(request, &user, room.as_ref()).await;
368 self.imp().add(verification.clone());
369
370 Ok(verification)
371 }
372 Err(error) => {
373 error!("Could not create identity verification: {error}");
374 Err(())
375 }
376 }
377 }
378}