fractal/session/model/verification/
verification_list.rsuse gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
use matrix_sdk::{
encryption::verification::VerificationRequest, Client as MatrixClient, Room as MatrixRoom,
};
use ruma::{
events::{
key::verification::request::ToDeviceKeyVerificationRequestEvent,
room::message::{MessageType, OriginalSyncRoomMessageEvent},
},
RoomId,
};
use tracing::{debug, error};
use super::{load_supported_verification_methods, VerificationKey, VerificationState};
use crate::{
session::model::{IdentityVerification, Member, Membership, Session, User},
spawn, spawn_tokio,
};
mod imp {
use std::cell::RefCell;
use glib::subclass::Signal;
use indexmap::IndexMap;
use once_cell::sync::Lazy;
use super::*;
#[derive(Debug, Default, glib::Properties)]
#[properties(wrapper_type = super::VerificationList)]
pub struct VerificationList {
pub list: RefCell<IndexMap<VerificationKey, IdentityVerification>>,
#[property(get, construct_only)]
pub session: glib::WeakRef<Session>,
}
#[glib::object_subclass]
impl ObjectSubclass for VerificationList {
const NAME: &'static str = "VerificationList";
type Type = super::VerificationList;
type Interfaces = (gio::ListModel,);
}
#[glib::derived_properties]
impl ObjectImpl for VerificationList {
fn signals() -> &'static [Signal] {
static SIGNALS: Lazy<Vec<Signal>> =
Lazy::new(|| vec![Signal::builder("secret-received").build()]);
SIGNALS.as_ref()
}
}
impl ListModelImpl for VerificationList {
fn item_type(&self) -> glib::Type {
IdentityVerification::static_type()
}
fn n_items(&self) -> u32 {
self.list.borrow().len() as u32
}
fn item(&self, position: u32) -> Option<glib::Object> {
self.list
.borrow()
.get_index(position as usize)
.map(|(_, item)| item.clone().upcast())
}
}
}
glib::wrapper! {
pub struct VerificationList(ObjectSubclass<imp::VerificationList>)
@implements gio::ListModel;
}
impl VerificationList {
pub fn new(session: &Session) -> Self {
glib::Object::builder().property("session", session).build()
}
pub fn init(&self) {
let Some(session) = self.session() else {
return;
};
let client = session.client();
let obj_weak = glib::SendWeakRef::from(self.downgrade());
let obj_weak_clone = obj_weak.clone();
client.add_event_handler(
move |ev: ToDeviceKeyVerificationRequestEvent, client: MatrixClient| {
let obj_weak = obj_weak_clone.clone();
async move {
let Some(request) = client
.encryption()
.get_verification_request(&ev.sender, &ev.content.transaction_id)
.await
else {
debug!(
"To-device verification request `({}, {})` not found in the SDK",
ev.sender, ev.content.transaction_id
);
return;
};
if !request.is_self_verification() {
debug!(
"To-device verification request `({}, {})` for other users is not supported",
ev.sender, ev.content.transaction_id
);
return;
}
let ctx = glib::MainContext::default();
ctx.spawn(async move {
spawn!(async move {
if let Some(obj) = obj_weak.upgrade() {
obj.add_to_device_request(request).await;
}
});
});
}
},
);
client.add_event_handler(
move |ev: OriginalSyncRoomMessageEvent, room: MatrixRoom, client: MatrixClient| {
let obj_weak = obj_weak.clone();
async move {
let MessageType::VerificationRequest(_) = &ev.content.msgtype else {
return;
};
let Some(request) = client
.encryption()
.get_verification_request(&ev.sender, &ev.event_id)
.await
else {
debug!(
"To-device verification request `({}, {})` not found in the SDK",
ev.sender, ev.event_id
);
return;
};
let room_id = room.room_id().to_owned();
let ctx = glib::MainContext::default();
ctx.spawn(async move {
spawn!(async move {
if let Some(obj) = obj_weak.upgrade() {
obj.add_in_room_request(request, &room_id).await;
}
});
});
}
},
);
}
async fn add_to_device_request(&self, request: VerificationRequest) {
if request.is_done() || request.is_cancelled() || request.is_passive() {
return;
}
let Some(session) = self.session() else {
return;
};
let verification = IdentityVerification::new(request, &session.user(), None).await;
self.add(verification.clone());
if verification.state() == VerificationState::Requested {
session
.notifications()
.show_to_device_identity_verification(&verification)
.await;
}
}
async fn add_in_room_request(&self, request: VerificationRequest, room_id: &RoomId) {
if request.is_done() || request.is_cancelled() || request.is_passive() {
return;
}
let Some(session) = self.session() else {
return;
};
let Some(room) = session.room_list().get(room_id) else {
error!(
"Room for verification request `({}, {})` not found",
request.other_user_id(),
request.flow_id()
);
return;
};
if matches!(
room.own_member().membership(),
Membership::Leave | Membership::Ban
) {
return;
}
let other_user_id = request.other_user_id().to_owned();
let member = room
.members()
.map(|l| l.get_or_create(other_user_id.clone()))
.unwrap_or_else(|| Member::new(&room, other_user_id.clone()));
let matrix_room = room.matrix_room().clone();
let handle =
spawn_tokio!(async move { matrix_room.get_member_no_sync(&other_user_id).await });
match handle.await.unwrap() {
Ok(Some(matrix_member)) => member.update_from_room_member(&matrix_member),
Ok(None) => {
error!(
"Room member for verification request `({}, {})` not found",
request.other_user_id(),
request.flow_id()
);
return;
}
Err(error) => {
error!(
"Could not get room member for verification request `({}, {})`: {error}",
request.other_user_id(),
request.flow_id()
);
return;
}
}
let verification =
IdentityVerification::new(request, member.upcast_ref(), Some(&room)).await;
room.set_verification(Some(&verification));
self.add(verification.clone());
if verification.state() == VerificationState::Requested {
session
.notifications()
.show_in_room_identity_verification(&verification);
}
}
fn add(&self, verification: IdentityVerification) {
let imp = self.imp();
let key = verification.key();
if imp.list.borrow().contains_key(&key) {
return;
}
verification.connect_remove_from_list(clone!(
#[weak(rename_to = obj)]
self,
move |verification| {
obj.remove(&verification.key());
}
));
let (pos, _) = imp.list.borrow_mut().insert_full(key, verification);
self.items_changed(pos as u32, 0, 1)
}
pub fn remove(&self, key: &VerificationKey) {
let Some((pos, ..)) = self.imp().list.borrow_mut().shift_remove_full(key) else {
return;
};
self.items_changed(pos as u32, 1, 0);
if let Some(session) = self.session() {
session.notifications().withdraw_identity_verification(key);
}
}
pub fn get(&self, key: &VerificationKey) -> Option<IdentityVerification> {
self.imp().list.borrow().get(key).cloned()
}
pub fn ongoing_session_verification(&self) -> Option<IdentityVerification> {
let list = self.imp().list.borrow();
list.values()
.find(|v| v.is_self_verification() && !v.is_finished())
.cloned()
}
pub fn ongoing_room_verification(&self, room_id: &RoomId) -> Option<IdentityVerification> {
let list = self.imp().list.borrow();
list.values()
.find(|v| v.room().is_some_and(|room| room.room_id() == room_id) && !v.is_finished())
.cloned()
}
pub async fn create(&self, user: Option<User>) -> Result<IdentityVerification, ()> {
let Some(session) = self.session() else {
error!("Could not create identity verification: failed to upgrade session");
return Err(());
};
let user = user.unwrap_or_else(|| session.user());
let supported_methods = load_supported_verification_methods().await;
let Some(identity) = user.ensure_crypto_identity().await else {
error!("Could not create identity verification: cryptographic identity not found");
return Err(());
};
let handle = spawn_tokio!(async move {
identity
.request_verification_with_methods(supported_methods)
.await
});
match handle.await.unwrap() {
Ok(request) => {
let room = if let Some(room_id) = request.room_id() {
let Some(room) = session.room_list().get(room_id) else {
error!(
"Room for verification request `({}, {})` not found",
request.other_user_id(),
request.flow_id()
);
return Err(());
};
Some(room)
} else {
None
};
let verification = IdentityVerification::new(request, &user, room.as_ref()).await;
self.add(verification.clone());
Ok(verification)
}
Err(error) => {
error!("Could not create identity verification: {error}");
Err(())
}
}
}
}