feat(bindings): Session verification through FFI
This commit is contained in:
@@ -64,7 +64,7 @@ if [ "$IS_CI" = false ] ; then
|
||||
echo "Preparing matrix-rust-components-swift"
|
||||
|
||||
# Debug -> Copy generated files over to ../../../matrix-rust-components-swift
|
||||
echo "$(echo "import MatrixSDKFFIWrapper\n"; cat "${SWIFT_DIR}/sdk.swift")" > "${SWIFT_DIR}/sdk.swift"
|
||||
echo "$(printf "import MatrixSDKFFIWrapper\n\n"; cat "${SWIFT_DIR}/sdk.swift")" > "${SWIFT_DIR}/sdk.swift"
|
||||
|
||||
rsync -a --delete "${GENERATED_DIR}/MatrixSDKFFI.xcframework" "${SRC_ROOT}/../matrix-rust-components-swift/"
|
||||
rsync -a --delete "${GENERATED_DIR}/swift/" "${SRC_ROOT}/../matrix-rust-components-swift/Sources/MatrixRustSDK"
|
||||
|
||||
@@ -10,7 +10,7 @@ rust-version = "1.56"
|
||||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "staticlib"]
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
@@ -65,6 +65,9 @@ interface Client {
|
||||
|
||||
[Throws=ClientError]
|
||||
sequence<u8> get_media_content(MediaSource source);
|
||||
|
||||
[Throws=ClientError]
|
||||
SessionVerificationController get_session_verification_controller();
|
||||
};
|
||||
|
||||
callback interface RoomDelegate {
|
||||
@@ -148,3 +151,33 @@ interface EmoteMessage {
|
||||
interface MediaSource {
|
||||
string url();
|
||||
};
|
||||
|
||||
interface SessionVerificationEmoji {
|
||||
string symbol();
|
||||
string description();
|
||||
};
|
||||
|
||||
callback interface SessionVerificationControllerDelegate {
|
||||
void did_receive_verification_data(sequence<SessionVerificationEmoji> data);
|
||||
void did_fail();
|
||||
void did_cancel();
|
||||
void did_finish();
|
||||
};
|
||||
|
||||
interface SessionVerificationController {
|
||||
void set_delegate(SessionVerificationControllerDelegate? delegate);
|
||||
|
||||
boolean is_verified();
|
||||
|
||||
[Throws=ClientError]
|
||||
void request_verification();
|
||||
|
||||
[Throws=ClientError]
|
||||
void approve_verification();
|
||||
|
||||
[Throws=ClientError]
|
||||
void decline_verification();
|
||||
|
||||
[Throws=ClientError]
|
||||
void cancel_verification();
|
||||
};
|
||||
|
||||
@@ -15,7 +15,10 @@ use matrix_sdk::{
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use super::{room::Room, ClientState, RestoreToken, RUNTIME};
|
||||
use super::{
|
||||
room::Room, session_verification::SessionVerificationController, ClientState, RestoreToken,
|
||||
RUNTIME,
|
||||
};
|
||||
|
||||
impl std::ops::Deref for Client {
|
||||
type Target = MatrixClient;
|
||||
@@ -33,6 +36,8 @@ pub struct Client {
|
||||
client: MatrixClient,
|
||||
state: Arc<RwLock<ClientState>>,
|
||||
delegate: Arc<RwLock<Option<Box<dyn ClientDelegate>>>>,
|
||||
session_verification_controller:
|
||||
Arc<matrix_sdk::locks::RwLock<Option<SessionVerificationController>>>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
@@ -41,6 +46,7 @@ impl Client {
|
||||
client,
|
||||
state: Arc::new(RwLock::new(state)),
|
||||
delegate: Arc::new(RwLock::new(None)),
|
||||
session_verification_controller: Arc::new(matrix_sdk::locks::RwLock::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +75,7 @@ impl Client {
|
||||
let client = self.client.clone();
|
||||
let state = self.state.clone();
|
||||
let delegate = self.delegate.clone();
|
||||
let session_verification_controller = self.session_verification_controller.clone();
|
||||
RUNTIME.spawn(async move {
|
||||
let mut filter = FilterDefinition::default();
|
||||
let mut room_filter = RoomFilter::default();
|
||||
@@ -84,7 +91,7 @@ impl Client {
|
||||
let sync_settings = SyncSettings::new().filter(Filter::FilterId(&filter_id));
|
||||
|
||||
client
|
||||
.sync_with_callback(sync_settings, |_| async {
|
||||
.sync_with_callback(sync_settings, |sync_response| async {
|
||||
if !state.read().has_first_synced {
|
||||
state.write().has_first_synced = true
|
||||
}
|
||||
@@ -96,9 +103,18 @@ impl Client {
|
||||
state.write().is_syncing = true;
|
||||
}
|
||||
|
||||
if let Some(ref delegate) = *delegate.read() {
|
||||
if let Some(delegate) = &*delegate.read() {
|
||||
delegate.did_receive_sync_update()
|
||||
}
|
||||
|
||||
if let Some(session_verification_controller) =
|
||||
&*session_verification_controller.read().await
|
||||
{
|
||||
session_verification_controller
|
||||
.process_to_device_messages(sync_response.to_device)
|
||||
.await;
|
||||
}
|
||||
|
||||
LoopCtrl::Continue
|
||||
})
|
||||
.await;
|
||||
@@ -172,6 +188,33 @@ impl Client {
|
||||
.await?)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_session_verification_controller(
|
||||
&self,
|
||||
) -> anyhow::Result<Arc<SessionVerificationController>> {
|
||||
RUNTIME.block_on(async move {
|
||||
if let Some(session_verification_controller) =
|
||||
&*self.session_verification_controller.read().await
|
||||
{
|
||||
return Ok(Arc::new(session_verification_controller.clone()));
|
||||
}
|
||||
|
||||
let user_id = self.client.user_id().expect("Failed retrieving current user_id");
|
||||
let user_identity = self
|
||||
.client
|
||||
.encryption()
|
||||
.get_user_identity(user_id)
|
||||
.await?
|
||||
.expect("Failed retrieving user identity");
|
||||
|
||||
let session_verification_controller = SessionVerificationController::new(user_identity);
|
||||
|
||||
*self.session_verification_controller.write().await =
|
||||
Some(session_verification_controller.clone());
|
||||
|
||||
Ok(Arc::new(session_verification_controller))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_transaction_id() -> String {
|
||||
|
||||
@@ -7,6 +7,7 @@ pub mod client;
|
||||
pub mod client_builder;
|
||||
pub mod messages;
|
||||
pub mod room;
|
||||
pub mod session_verification;
|
||||
mod uniffi_api;
|
||||
|
||||
use client::Client;
|
||||
@@ -22,7 +23,7 @@ pub static RUNTIME: Lazy<Runtime> =
|
||||
|
||||
pub use matrix_sdk::ruma::{api::client::account::register, UserId};
|
||||
|
||||
pub use self::{backward_stream::*, client::*, messages::*, room::*};
|
||||
pub use self::{backward_stream::*, client::*, messages::*, room::*, session_verification::*};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ClientState {
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use matrix_sdk::{
|
||||
encryption::{
|
||||
identities::UserIdentity,
|
||||
verification::{SasVerification, VerificationRequest},
|
||||
},
|
||||
ruma::{
|
||||
api::client::sync::sync_events::v3::ToDevice,
|
||||
events::{key::verification::VerificationMethod, AnyToDeviceEvent},
|
||||
},
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use super::RUNTIME;
|
||||
|
||||
pub struct SessionVerificationEmoji {
|
||||
symbol: String,
|
||||
description: String,
|
||||
}
|
||||
|
||||
impl SessionVerificationEmoji {
|
||||
pub fn symbol(&self) -> String {
|
||||
self.symbol.clone()
|
||||
}
|
||||
|
||||
pub fn description(&self) -> String {
|
||||
self.description.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SessionVerificationControllerDelegate: Sync + Send {
|
||||
fn did_receive_verification_data(&self, data: Vec<Arc<SessionVerificationEmoji>>);
|
||||
fn did_fail(&self);
|
||||
fn did_cancel(&self);
|
||||
fn did_finish(&self);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SessionVerificationController {
|
||||
user_identity: UserIdentity,
|
||||
delegate: Arc<RwLock<Option<Box<dyn SessionVerificationControllerDelegate>>>>,
|
||||
verification_request: Arc<RwLock<Option<VerificationRequest>>>,
|
||||
sas_verification: Arc<RwLock<Option<SasVerification>>>,
|
||||
}
|
||||
|
||||
impl SessionVerificationController {
|
||||
pub fn new(user_identity: UserIdentity) -> Self {
|
||||
SessionVerificationController {
|
||||
user_identity,
|
||||
delegate: Arc::new(RwLock::new(None)),
|
||||
verification_request: Arc::new(RwLock::new(None)),
|
||||
sas_verification: Arc::new(RwLock::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_delegate(&self, delegate: Option<Box<dyn SessionVerificationControllerDelegate>>) {
|
||||
*self.delegate.write() = delegate;
|
||||
}
|
||||
|
||||
pub fn is_verified(&self) -> bool {
|
||||
self.user_identity.verified()
|
||||
}
|
||||
|
||||
pub fn request_verification(&self) -> anyhow::Result<()> {
|
||||
RUNTIME.block_on(async move {
|
||||
let methods = vec![VerificationMethod::SasV1];
|
||||
let verification_request =
|
||||
self.user_identity.request_verification_with_methods(methods).await?;
|
||||
*self.verification_request.write() = Some(verification_request);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn approve_verification(&self) -> anyhow::Result<()> {
|
||||
RUNTIME.block_on(async move {
|
||||
let sas_verification = self.sas_verification.read().clone();
|
||||
if let Some(sas_verification) = sas_verification {
|
||||
sas_verification.confirm().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn decline_verification(&self) -> anyhow::Result<()> {
|
||||
RUNTIME.block_on(async move {
|
||||
let sas_verification = self.sas_verification.read().clone();
|
||||
if let Some(sas_verification) = sas_verification {
|
||||
sas_verification.mismatch().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cancel_verification(&self) -> anyhow::Result<()> {
|
||||
RUNTIME.block_on(async move {
|
||||
let verification_request = self.verification_request.read().clone();
|
||||
if let Some(verification) = verification_request {
|
||||
verification.cancel().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn process_to_device_messages(&self, to_device: ToDevice) {
|
||||
let sas_verification = self.sas_verification.clone();
|
||||
|
||||
for event in to_device.events.into_iter().filter_map(|e| e.deserialize().ok()) {
|
||||
match event {
|
||||
AnyToDeviceEvent::KeyVerificationReady(event) => {
|
||||
if !self.is_transaction_id_valid(event.content.transaction_id.to_string()) {
|
||||
return;
|
||||
}
|
||||
self.start_sas_verification().await;
|
||||
}
|
||||
AnyToDeviceEvent::KeyVerificationCancel(event) => {
|
||||
if !self.is_transaction_id_valid(event.content.transaction_id.to_string()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(delegate) = &*self.delegate.read() {
|
||||
delegate.did_cancel()
|
||||
}
|
||||
}
|
||||
AnyToDeviceEvent::KeyVerificationKey(event) => {
|
||||
if !self.is_transaction_id_valid(event.content.transaction_id.to_string()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(sas_verification) = &*sas_verification.read() {
|
||||
if let Some(emojis) = sas_verification.emoji() {
|
||||
if let Some(delegate) = &*self.delegate.read() {
|
||||
let emojis = emojis
|
||||
.iter()
|
||||
.map(|e| {
|
||||
Arc::new(SessionVerificationEmoji {
|
||||
symbol: e.symbol.to_owned(),
|
||||
description: e.description.to_owned(),
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
delegate.did_receive_verification_data(emojis);
|
||||
}
|
||||
} else if let Some(delegate) = &*self.delegate.read() {
|
||||
delegate.did_fail()
|
||||
}
|
||||
} else if let Some(delegate) = &*self.delegate.read() {
|
||||
delegate.did_fail()
|
||||
}
|
||||
}
|
||||
AnyToDeviceEvent::KeyVerificationDone(event) => {
|
||||
if !self.is_transaction_id_valid(event.content.transaction_id.to_string()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(delegate) = &*self.delegate.read() {
|
||||
delegate.did_finish()
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_transaction_id_valid(&self, transaction_id: String) -> bool {
|
||||
if let Some(verification) = &*self.verification_request.read() {
|
||||
return verification.flow_id() == transaction_id;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
async fn start_sas_verification(&self) {
|
||||
let verification_request = self.verification_request.read().clone();
|
||||
if let Some(verification) = verification_request {
|
||||
match verification.start_sas().await {
|
||||
Ok(verification) => {
|
||||
*self.sas_verification.write() = verification;
|
||||
}
|
||||
Err(_) => {
|
||||
if let Some(delegate) = &*self.delegate.read() {
|
||||
delegate.did_fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user