feat(ffi): add support for starting and responding to user verification requests

This commit is contained in:
Stefan Ceriu
2025-01-29 16:58:35 +02:00
committed by Stefan Ceriu
parent 2291a61379
commit f9ff4fff50
3 changed files with 124 additions and 61 deletions

View File

@@ -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<Option<SessionVerificationController>>,
> = 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());

View File

@@ -252,13 +252,13 @@ impl Room {
}
pub async fn member(&self, user_id: String) -> Result<RoomMember, 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")?;
Ok(member.try_into().context("Unknown state membership")?)
}
pub async fn member_avatar_url(&self, user_id: String) -> Result<Option<String>, 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<Option<String>, 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)

View File

@@ -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<String>,
flow_id: String,
device_id: String,
display_name: Option<String>,
device_display_name: Option<String>,
/// First time this device was seen in milliseconds since epoch.
first_seen_timestamp: Timestamp,
}
@@ -66,6 +68,7 @@ pub type Delegate = Arc<RwLock<Option<Box<dyn SessionVerificationControllerDeleg
pub struct SessionVerificationController {
encryption: Encryption,
user_identity: UserIdentity,
account: Account,
delegate: Delegate,
verification_request: Arc<RwLock<Option<VerificationRequest>>>,
sas_verification: Arc<RwLock<Option<SasVerification>>>,
@@ -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<str>,
) {
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(