From f9ff4fff50e53aff44ff3b635bd6a60480ed2f5f Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Wed, 29 Jan 2025 16:58:35 +0200 Subject: [PATCH] feat(ffi): add support for starting and responding to user verification requests --- bindings/matrix-sdk-ffi/src/client.rs | 41 ++++-- bindings/matrix-sdk-ffi/src/room.rs | 6 +- .../src/session_verification.rs | 138 +++++++++++------- 3 files changed, 124 insertions(+), 61 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs index d2431c25e..ee7ed80c2 100644 --- a/bindings/matrix-sdk-ffi/src/client.rs +++ b/bindings/matrix-sdk-ffi/src/client.rs @@ -32,8 +32,11 @@ use matrix_sdk::{ user_directory::search_users, }, events::{ - room::{avatar::RoomAvatarEventContent, encryption::RoomEncryptionEventContent}, - AnyInitialStateEvent, AnyToDeviceEvent, InitialStateEvent, + room::{ + avatar::RoomAvatarEventContent, encryption::RoomEncryptionEventContent, + message::MessageType, + }, + AnyInitialStateEvent, InitialStateEvent, }, serde::Raw, EventEncryptionAlgorithm, RoomId, TransactionId, UInt, UserId, @@ -53,10 +56,12 @@ use ruma::{ }, events::{ ignored_user_list::IgnoredUserListEventContent, + key::verification::request::ToDeviceKeyVerificationRequestEvent, room::{ join_rules::{ AllowRule as RumaAllowRule, JoinRule as RumaJoinRule, RoomJoinRulesEventContent, }, + message::OriginalSyncRoomMessageEvent, power_levels::RoomPowerLevelsEventContent, }, GlobalAccountDataEventType, @@ -204,12 +209,27 @@ impl Client { tokio::sync::RwLock>, > = Default::default(); let controller = session_verification_controller.clone(); + sdk_client.add_event_handler( + move |event: ToDeviceKeyVerificationRequestEvent| async move { + if let Some(session_verification_controller) = &*controller.clone().read().await { + session_verification_controller + .process_incoming_verification_request( + &event.sender, + event.content.transaction_id, + ) + .await; + } + }, + ); - sdk_client.add_event_handler(move |ev: AnyToDeviceEvent| async move { - if let Some(session_verification_controller) = &*controller.clone().read().await { - session_verification_controller.process_to_device_message(ev).await; - } else { - debug!("received to-device message, but verification controller isn't ready"); + let controller = session_verification_controller.clone(); + sdk_client.add_event_handler(move |event: OriginalSyncRoomMessageEvent| async move { + if let MessageType::VerificationRequest(_) = &event.content.msgtype { + if let Some(session_verification_controller) = &*controller.clone().read().await { + session_verification_controller + .process_incoming_verification_request(&event.sender, event.event_id) + .await; + } } }); @@ -777,8 +797,11 @@ impl Client { .await? .context("Failed retrieving user identity")?; - let session_verification_controller = - SessionVerificationController::new(self.inner.encryption(), user_identity); + let session_verification_controller = SessionVerificationController::new( + self.inner.encryption(), + user_identity, + self.inner.account(), + ); *self.session_verification_controller.write().await = Some(session_verification_controller.clone()); diff --git a/bindings/matrix-sdk-ffi/src/room.rs b/bindings/matrix-sdk-ffi/src/room.rs index a734ce9fe..59198535f 100644 --- a/bindings/matrix-sdk-ffi/src/room.rs +++ b/bindings/matrix-sdk-ffi/src/room.rs @@ -252,13 +252,13 @@ impl Room { } pub async fn member(&self, user_id: String) -> Result { - let user_id = UserId::parse(&*user_id).context("Invalid user id.")?; + let user_id = UserId::parse(&*user_id)?; let member = self.inner.get_member(&user_id).await?.context("User not found")?; Ok(member.try_into().context("Unknown state membership")?) } pub async fn member_avatar_url(&self, user_id: String) -> Result, ClientError> { - let user_id = UserId::parse(&*user_id).context("Invalid user id.")?; + let user_id = UserId::parse(&*user_id)?; let member = self.inner.get_member(&user_id).await?.context("User not found")?; let avatar_url_string = member.avatar_url().map(|m| m.to_string()); Ok(avatar_url_string) @@ -268,7 +268,7 @@ impl Room { &self, user_id: String, ) -> Result, ClientError> { - let user_id = UserId::parse(&*user_id).context("Invalid user id.")?; + let user_id = UserId::parse(&*user_id)?; let member = self.inner.get_member(&user_id).await?.context("User not found")?; let avatar_url_string = member.display_name().map(|m| m.to_owned()); Ok(avatar_url_string) diff --git a/bindings/matrix-sdk-ffi/src/session_verification.rs b/bindings/matrix-sdk-ffi/src/session_verification.rs index 6843c3e23..7bbaec3b0 100644 --- a/bindings/matrix-sdk-ffi/src/session_verification.rs +++ b/bindings/matrix-sdk-ffi/src/session_verification.rs @@ -7,10 +7,11 @@ use matrix_sdk::{ verification::{SasState, SasVerification, VerificationRequest, VerificationRequestState}, Encryption, }, - ruma::events::{key::verification::VerificationMethod, AnyToDeviceEvent}, + ruma::events::key::verification::VerificationMethod, + Account, }; use ruma::UserId; -use tracing::{error, info}; +use tracing::error; use super::RUNTIME; use crate::{error::ClientError, utils::Timestamp}; @@ -42,9 +43,10 @@ pub enum SessionVerificationData { #[derive(Debug, uniffi::Record)] pub struct SessionVerificationRequestDetails { sender_id: String, + sender_display_name: Option, flow_id: String, device_id: String, - display_name: Option, + device_display_name: Option, /// First time this device was seen in milliseconds since epoch. first_seen_timestamp: Timestamp, } @@ -66,6 +68,7 @@ pub type Delegate = Arc>>, sas_verification: Arc>>, @@ -94,15 +97,7 @@ impl SessionVerificationController { .await .ok_or(ClientError::new("Unknown session verification request"))?; - *self.verification_request.write().unwrap() = Some(verification_request.clone()); - - RUNTIME.spawn(Self::listen_to_verification_request_changes( - verification_request, - self.sas_verification.clone(), - self.delegate.clone(), - )); - - Ok(()) + self.set_ongoing_verification_request(verification_request) } /// Accept the previously acknowledged verification request @@ -118,7 +113,7 @@ impl SessionVerificationController { } /// Request verification for the current device - pub async fn request_verification(&self) -> Result<(), ClientError> { + pub async fn request_device_verification(&self) -> Result<(), ClientError> { let methods = vec![VerificationMethod::SasV1]; let verification_request = self .user_identity @@ -126,15 +121,31 @@ impl SessionVerificationController { .await .map_err(anyhow::Error::from)?; - *self.verification_request.write().unwrap() = Some(verification_request.clone()); + self.set_ongoing_verification_request(verification_request) + } - RUNTIME.spawn(Self::listen_to_verification_request_changes( - verification_request, - self.sas_verification.clone(), - self.delegate.clone(), - )); + /// Request verification for the given user + pub async fn request_user_verification(&self, user_id: String) -> Result<(), ClientError> { + let user_id = UserId::parse(user_id)?; - Ok(()) + let user_identity = self + .encryption + .get_user_identity(&user_id) + .await? + .ok_or(ClientError::new("Unknown user identity"))?; + + if user_identity.is_verified() { + return Err(ClientError::new("User is already verified")); + } + + let methods = vec![VerificationMethod::SasV1]; + + let verification_request = user_identity + .request_verification_with_methods(methods) + .await + .map_err(anyhow::Error::from)?; + + self.set_ongoing_verification_request(verification_request) } /// Transition the current verification request into a SAS verification @@ -202,50 +213,79 @@ impl SessionVerificationController { } impl SessionVerificationController { - pub(crate) fn new(encryption: Encryption, user_identity: UserIdentity) -> Self { + pub(crate) fn new( + encryption: Encryption, + user_identity: UserIdentity, + account: Account, + ) -> Self { SessionVerificationController { encryption, user_identity, + account, delegate: Arc::new(RwLock::new(None)), verification_request: Arc::new(RwLock::new(None)), sas_verification: Arc::new(RwLock::new(None)), } } - pub(crate) async fn process_to_device_message(&self, event: AnyToDeviceEvent) { - if let AnyToDeviceEvent::KeyVerificationRequest(event) = event { - info!("Received verification request: {:}", event.sender); + /// Ask the controller to process an incoming request based on the sender + /// and flow identifier. It will fetch the request, verify that it's in the + /// correct state and then and notify the delegate. + pub(crate) async fn process_incoming_verification_request( + &self, + sender: &UserId, + flow_id: impl AsRef, + ) { + let Some(request) = self.encryption.get_verification_request(sender, flow_id).await else { + error!("Failed retrieving verification request"); + return; + }; - let Some(request) = self - .encryption - .get_verification_request(&event.sender, &event.content.transaction_id) - .await - else { - error!("Failed retrieving verification request"); - return; - }; + let VerificationRequestState::Requested { other_device_data, .. } = request.state() else { + error!("Received verification request event but the request is in the wrong state."); + return; + }; - if !request.is_self_verification() { - info!("Received non-self verification request. Ignoring."); - return; - } + let Ok(user_profile) = self.account.fetch_user_profile_of(sender).await else { + error!("Failed fetching user profile for verification request"); + return; + }; - let VerificationRequestState::Requested { other_device_data, .. } = request.state() - else { - error!("Received key verification event but the request is in the wrong state."); - return; - }; + if let Some(delegate) = &*self.delegate.read().unwrap() { + delegate.did_receive_verification_request(SessionVerificationRequestDetails { + sender_id: request.other_user_id().into(), + sender_display_name: user_profile.displayname, + flow_id: request.flow_id().into(), + device_id: other_device_data.device_id().into(), + device_display_name: other_device_data.display_name().map(str::to_string), + first_seen_timestamp: other_device_data.first_time_seen_ts().into(), + }); + } + } - if let Some(delegate) = &*self.delegate.read().unwrap() { - delegate.did_receive_verification_request(SessionVerificationRequestDetails { - sender_id: request.other_user_id().into(), - flow_id: request.flow_id().into(), - device_id: other_device_data.device_id().into(), - display_name: other_device_data.display_name().map(str::to_string), - first_seen_timestamp: other_device_data.first_time_seen_ts().into(), - }); + fn set_ongoing_verification_request( + &self, + verification_request: VerificationRequest, + ) -> Result<(), ClientError> { + if let Some(ongoing_verification_request) = + self.verification_request.read().unwrap().clone() + { + if !ongoing_verification_request.is_done() + && !ongoing_verification_request.is_cancelled() + { + return Err(ClientError::new("There is another verification flow ongoing.")); } } + + *self.verification_request.write().unwrap() = Some(verification_request.clone()); + + RUNTIME.spawn(Self::listen_to_verification_request_changes( + verification_request, + self.sas_verification.clone(), + self.delegate.clone(), + )); + + Ok(()) } async fn listen_to_verification_request_changes(