From be2667b2e330ab2fd8016b0fcf6c2b57c48887a8 Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 31 Jul 2024 18:50:57 +0100 Subject: [PATCH] chore: Strongly typed ShieldStates. --- Cargo.lock | 1 + bindings/matrix-sdk-crypto-ffi/src/lib.rs | 32 ++++++-- bindings/matrix-sdk-ffi/src/timeline/mod.rs | 27 +------ crates/matrix-sdk-base/Cargo.toml | 2 +- crates/matrix-sdk-common/Cargo.toml | 2 + .../src/deserialized_responses.rs | 80 ++++++++++++++----- crates/matrix-sdk-common/src/lib.rs | 3 + crates/matrix-sdk-crypto/src/machine.rs | 56 +++++++++---- .../src/timeline/event_item/mod.rs | 4 +- .../src/timeline/tests/shields.rs | 4 +- 10 files changed, 142 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bf8e73b9718..77d11222188 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3299,6 +3299,7 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", + "uniffi", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index 2cac969e8bc..20fed27d6d4 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -33,7 +33,9 @@ pub use error::{ use js_int::UInt; pub use logger::{set_logger, Logger}; pub use machine::{KeyRequestPair, OlmMachine, SignatureVerification}; -use matrix_sdk_common::deserialized_responses::ShieldState as RustShieldState; +use matrix_sdk_common::deserialized_responses::{ + ShieldState as RustShieldState, ShieldStateColor as RustShieldStateColor, +}; use matrix_sdk_crypto::{ olm::{IdentityKeys, InboundGroupSession, SenderData, Session}, store::{Changes, CryptoStore, PendingChanges, RoomSettings as RustRoomSettings}, @@ -731,18 +733,36 @@ pub struct ShieldState { impl From for ShieldState { fn from(value: RustShieldState) -> Self { - match value { - RustShieldState::Red { message } => { - Self { color: ShieldColor::Red, message: Some(message.to_owned()) } + match &value { + RustShieldState::AuthenticityNotGuaranteed { color } => { + Self { color: color.into(), message: value.message().map(ToOwned::to_owned) } + } + RustShieldState::UnknownDevice { color } => { + Self { color: color.into(), message: value.message().map(ToOwned::to_owned) } + } + RustShieldState::UnsignedDevice { color } => { + Self { color: color.into(), message: value.message().map(ToOwned::to_owned) } + } + RustShieldState::UnverifiedIdentity { color } => { + Self { color: color.into(), message: value.message().map(ToOwned::to_owned) } } - RustShieldState::Grey { message } => { - Self { color: ShieldColor::Grey, message: Some(message.to_owned()) } + RustShieldState::SentInClear { color } => { + Self { color: color.into(), message: value.message().map(ToOwned::to_owned) } } RustShieldState::None => Self { color: ShieldColor::None, message: None }, } } } +impl From<&RustShieldStateColor> for ShieldColor { + fn from(value: &RustShieldStateColor) -> Self { + match value { + RustShieldStateColor::Red => ShieldColor::Red, + RustShieldStateColor::Grey => ShieldColor::Grey, + } + } +} + /// Struct representing the state of our private cross signing keys, it shows /// which private cross signing keys we have locally stored. #[derive(Debug, Clone, uniffi::Record)] diff --git a/bindings/matrix-sdk-ffi/src/timeline/mod.rs b/bindings/matrix-sdk-ffi/src/timeline/mod.rs index 92eb1b62521..17f70c18874 100644 --- a/bindings/matrix-sdk-ffi/src/timeline/mod.rs +++ b/bindings/matrix-sdk-ffi/src/timeline/mod.rs @@ -24,7 +24,7 @@ use matrix_sdk::{ AttachmentConfig, AttachmentInfo, BaseAudioInfo, BaseFileInfo, BaseImageInfo, BaseThumbnailInfo, BaseVideoInfo, Thumbnail, }, - deserialized_responses::ShieldState as RustShieldState, + deserialized_responses::ShieldState, Error, }; use matrix_sdk_ui::timeline::{ @@ -923,28 +923,9 @@ impl From<&matrix_sdk_ui::timeline::EventSendState> for EventSendState { } } -/// Recommended decorations for decrypted messages, representing the message's -/// authenticity properties. -#[derive(uniffi::Enum)] -pub enum ShieldState { - /// A red shield with a tooltip containing the associated message should be - /// presented. - Red { message: String }, - /// A grey shield with a tooltip containing the associated message should be - /// presented. - Grey { message: String }, - /// No shield should be presented. - None, -} - -impl From for ShieldState { - fn from(value: RustShieldState) -> Self { - match value { - RustShieldState::Red { message } => Self::Red { message: message.to_owned() }, - RustShieldState::Grey { message } => Self::Grey { message: message.to_owned() }, - RustShieldState::None => Self::None, - } - } +#[uniffi::export] +pub fn message_for_shield_state(shield_state: ShieldState) -> Option { + shield_state.message().map(ToOwned::to_owned) } #[derive(uniffi::Object)] diff --git a/crates/matrix-sdk-base/Cargo.toml b/crates/matrix-sdk-base/Cargo.toml index d177fa9e81f..23bc4e02bd3 100644 --- a/crates/matrix-sdk-base/Cargo.toml +++ b/crates/matrix-sdk-base/Cargo.toml @@ -25,7 +25,7 @@ experimental-sliding-sync = [ "ruma/unstable-msc3575", "ruma/unstable-simplified-msc3575", ] -uniffi = ["dep:uniffi", "matrix-sdk-crypto?/uniffi"] +uniffi = ["dep:uniffi", "matrix-sdk-crypto?/uniffi", "matrix-sdk-common/uniffi"] # "message-ids" feature doesn't do anything and is deprecated. message-ids = [] diff --git a/crates/matrix-sdk-common/Cargo.toml b/crates/matrix-sdk-common/Cargo.toml index e8994ab33f5..86f84132a26 100644 --- a/crates/matrix-sdk-common/Cargo.toml +++ b/crates/matrix-sdk-common/Cargo.toml @@ -17,6 +17,7 @@ targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] [features] js = ["instant/wasm-bindgen", "wasm-bindgen-futures"] +uniffi = ["dep:uniffi"] [dependencies] async-trait = { workspace = true } @@ -28,6 +29,7 @@ serde_json = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } tokio = { workspace = true, features = ["rt", "time"] } +uniffi = { workspace = true, optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] futures-util = { workspace = true, features = ["channel"] } diff --git a/crates/matrix-sdk-common/src/deserialized_responses.rs b/crates/matrix-sdk-common/src/deserialized_responses.rs index b1c424bcd28..10782458065 100644 --- a/crates/matrix-sdk-common/src/deserialized_responses.rs +++ b/crates/matrix-sdk-common/src/deserialized_responses.rs @@ -88,17 +88,20 @@ impl VerificationState { match self { VerificationState::Verified => ShieldState::None, VerificationState::Unverified(level) => { - let message = match level { + let red = ShieldStateColor::Red; + match level { VerificationLevel::UnverifiedIdentity | VerificationLevel::UnsignedDevice => { - UNVERIFIED_IDENTITY + ShieldState::UnverifiedIdentity { color: red } } VerificationLevel::None(link) => match link { - DeviceLinkProblem::MissingDevice => UNKNOWN_DEVICE, - DeviceLinkProblem::InsecureSource => AUTHENTICITY_NOT_GUARANTEED, + DeviceLinkProblem::MissingDevice => { + ShieldState::UnknownDevice { color: red } + } + DeviceLinkProblem::InsecureSource => { + ShieldState::AuthenticityNotGuaranteed { color: red } + } }, - }; - - ShieldState::Red { message } + } } } } @@ -123,19 +126,19 @@ impl VerificationState { } VerificationLevel::UnsignedDevice => { // This is a high warning. The sender hasn't verified his own device. - ShieldState::Red { message: UNSIGNED_DEVICE } + ShieldState::UnsignedDevice { color: ShieldStateColor::Red } } VerificationLevel::None(link) => match link { DeviceLinkProblem::MissingDevice => { // Have to warn as it could have been a temporary injected device. // Notice that the device might just not be known at this time, so callers // should retry when there is a device change for that user. - ShieldState::Red { message: UNKNOWN_DEVICE } + ShieldState::UnknownDevice { color: ShieldStateColor::Red } } DeviceLinkProblem::InsecureSource => { // In legacy mode, we tone down this warning as it is quite common and // mostly noise (due to legacy backup and lack of trusted forwards). - ShieldState::Grey { message: AUTHENTICITY_NOT_GUARANTEED } + ShieldState::AuthenticityNotGuaranteed { color: ShieldStateColor::Grey } } }, }, @@ -167,7 +170,7 @@ pub enum VerificationLevel { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] pub enum DeviceLinkProblem { /// The device is missing, either because it was deleted, or you haven't - /// yet downoaled it or the server is erroneously omitting it (federation + /// yet downloaded it or the server is erroneously omitting it (federation /// lag). MissingDevice, /// The key was obtained from an insecure source: imported from a file, @@ -178,17 +181,58 @@ pub enum DeviceLinkProblem { /// Recommended decorations for decrypted messages, representing the message's /// authenticity properties. #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] pub enum ShieldState { - /// A red shield with a tooltip containing the associated message should be - /// presented. - Red { message: &'static str }, - /// A grey shield with a tooltip containing the associated message should be - /// presented. - Grey { message: &'static str }, - /// No shield should be presented. + /// Not enough information available to check the authenticity. + AuthenticityNotGuaranteed { color: ShieldStateColor }, + /// The sending device isn't yet known by the Client. + UnknownDevice { color: ShieldStateColor }, + /// The sending device hasn't been verified by the sender. + UnsignedDevice { color: ShieldStateColor }, + /// The sender hasn't been verified by the Client's user. + UnverifiedIdentity { color: ShieldStateColor }, + /// An unencrypted event in an encrypted room. + SentInClear { color: ShieldStateColor }, + /// The event is trusted as authentic. None, } +impl ShieldState { + /// The message (in English) that should be presented. + pub fn message(&self) -> Option<&str> { + match self { + ShieldState::AuthenticityNotGuaranteed { .. } => Some(AUTHENTICITY_NOT_GUARANTEED), + ShieldState::UnknownDevice { .. } => Some(UNKNOWN_DEVICE), + ShieldState::UnsignedDevice { .. } => Some(UNSIGNED_DEVICE), + ShieldState::UnverifiedIdentity { .. } => Some(UNVERIFIED_IDENTITY), + ShieldState::SentInClear { .. } => Some(SENT_IN_CLEAR), + ShieldState::None => None, + } + } + + /// A helper method to get the color that should be used. + pub fn color(&self) -> Option<&ShieldStateColor> { + match self { + ShieldState::AuthenticityNotGuaranteed { color } => Some(color), + ShieldState::UnknownDevice { color } => Some(color), + ShieldState::UnsignedDevice { color } => Some(color), + ShieldState::UnverifiedIdentity { color } => Some(color), + ShieldState::SentInClear { color } => Some(color), + ShieldState::None => None, + } + } +} + +/// The color of the shield that should be presented to the user. +#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] +pub enum ShieldStateColor { + /// A red shield should be presented. + Red, + /// A grey shield should be presented. + Grey, +} + /// The algorithm specific information of a decrypted event. #[derive(Clone, Debug, Deserialize, Serialize)] pub enum AlgorithmInfo { diff --git a/crates/matrix-sdk-common/src/lib.rs b/crates/matrix-sdk-common/src/lib.rs index a0febbf3d34..2e81ab8cc6c 100644 --- a/crates/matrix-sdk-common/src/lib.rs +++ b/crates/matrix-sdk-common/src/lib.rs @@ -97,3 +97,6 @@ macro_rules! boxed_into_future { pub type BoxFuture<'a, T> = Pin + 'a>>; #[cfg(not(target_arch = "wasm32"))] pub type BoxFuture<'a, T> = Pin + Send + 'a>>; + +#[cfg(feature = "uniffi")] +uniffi::setup_scaffolding!(); diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index f082884d909..49fb37e28f0 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -2369,8 +2369,8 @@ pub(crate) mod tests { use futures_util::{pin_mut, FutureExt, StreamExt}; use itertools::Itertools; use matrix_sdk_common::deserialized_responses::{ - DeviceLinkProblem, ShieldState, UnableToDecryptInfo, UnsignedDecryptionResult, - UnsignedEventLocation, VerificationLevel, VerificationState, + DeviceLinkProblem, EncryptionInfo, ShieldState, ShieldStateColor, UnableToDecryptInfo, + UnsignedDecryptionResult, UnsignedEventLocation, VerificationLevel, VerificationState, }; use matrix_sdk_test::{async_test, message_like_event_content, test_json}; use ruma::{ @@ -3324,15 +3324,13 @@ pub(crate) mod tests { #[async_test] async fn test_decryption_verification_state() { - macro_rules! assert_shield { - ($foo: ident, $strict: ident, $lax: ident) => { - let lax = $foo.verification_state.to_shield_state_lax(); - let strict = $foo.verification_state.to_shield_state_strict(); + let assert_shield = |info: EncryptionInfo, strict: ShieldState, lax: ShieldState| { + let info_lax = info.verification_state.to_shield_state_lax(); + let info_strict = info.verification_state.to_shield_state_strict(); - assert_matches!(lax, ShieldState::$lax { .. }); - assert_matches!(strict, ShieldState::$strict { .. }); - }; - } + assert_eq!(info_lax, lax); + assert_eq!(info_strict, strict); + }; let (alice, bob) = get_machine_pair_with_setup_sessions_test_helper(alice_id(), user_id(), false).await; let room_id = room_id!("!test:example.org"); @@ -3389,7 +3387,11 @@ pub(crate) mod tests { encryption_info.verification_state ); - assert_shield!(encryption_info, Red, Red); + assert_shield( + encryption_info, + ShieldState::UnverifiedIdentity { color: ShieldStateColor::Red }, + ShieldState::UnsignedDevice { color: ShieldStateColor::Red }, + ); // get_room_event_encryption_info should return the same information let encryption_info = bob.get_room_event_encryption_info(&event, room_id).await.unwrap(); @@ -3397,7 +3399,11 @@ pub(crate) mod tests { VerificationState::Unverified(VerificationLevel::UnsignedDevice), encryption_info.verification_state ); - assert_shield!(encryption_info, Red, Red); + assert_shield( + encryption_info, + ShieldState::UnverifiedIdentity { color: ShieldStateColor::Red }, + ShieldState::UnsignedDevice { color: ShieldStateColor::Red }, + ); // Local trust state has no effect bob.get_device(alice.user_id(), alice_device_id(), None) @@ -3412,7 +3418,11 @@ pub(crate) mod tests { VerificationState::Unverified(VerificationLevel::UnsignedDevice), encryption_info.verification_state ); - assert_shield!(encryption_info, Red, Red); + assert_shield( + encryption_info, + ShieldState::UnverifiedIdentity { color: ShieldStateColor::Red }, + ShieldState::UnsignedDevice { color: ShieldStateColor::Red }, + ); setup_cross_signing_for_machine_test_helper(&alice, &bob).await; let bob_id_from_alice = alice.get_identity(bob.user_id(), None).await.unwrap(); @@ -3427,7 +3437,11 @@ pub(crate) mod tests { VerificationState::Unverified(VerificationLevel::UnsignedDevice), encryption_info.verification_state ); - assert_shield!(encryption_info, Red, Red); + assert_shield( + encryption_info, + ShieldState::UnverifiedIdentity { color: ShieldStateColor::Red }, + ShieldState::UnsignedDevice { color: ShieldStateColor::Red }, + ); // Let alice sign her device sign_alice_device_for_machine_test_helper(&alice, &bob).await; @@ -3439,12 +3453,16 @@ pub(crate) mod tests { encryption_info.verification_state ); - assert_shield!(encryption_info, Red, None); + assert_shield( + encryption_info, + ShieldState::UnverifiedIdentity { color: ShieldStateColor::Red }, + ShieldState::None, + ); mark_alice_identity_as_verified_test_helper(&alice, &bob).await; let encryption_info = bob.get_room_event_encryption_info(&event, room_id).await.unwrap(); assert_eq!(VerificationState::Verified, encryption_info.verification_state); - assert_shield!(encryption_info, None, None); + assert_shield(encryption_info, ShieldState::None, ShieldState::None); // Simulate an imported session, to change verification state let imported = InboundGroupSession::from_export(&export).unwrap(); @@ -3461,7 +3479,11 @@ pub(crate) mod tests { encryption_info.verification_state ); - assert_shield!(encryption_info, Red, Grey); + assert_shield( + encryption_info, + ShieldState::AuthenticityNotGuaranteed { color: ShieldStateColor::Red }, + ShieldState::AuthenticityNotGuaranteed { color: ShieldStateColor::Grey }, + ); } /// Test what happens when we feed an unencrypted event into the decryption diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs index d23148928cd..e80ccf7ab9f 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs @@ -22,7 +22,7 @@ use matrix_sdk::{ Client, Error, }; use matrix_sdk_base::{ - deserialized_responses::{SyncTimelineEvent, SENT_IN_CLEAR}, + deserialized_responses::{ShieldStateColor, SyncTimelineEvent}, latest_event::LatestEvent, }; use once_cell::sync::Lazy; @@ -382,7 +382,7 @@ impl EventTimelineItem { Some(info.verification_state.to_shield_state_lax()) } } - None => Some(ShieldState::Grey { message: SENT_IN_CLEAR }), + None => Some(ShieldState::SentInClear { color: ShieldStateColor::Grey }), } } diff --git a/crates/matrix-sdk-ui/src/timeline/tests/shields.rs b/crates/matrix-sdk-ui/src/timeline/tests/shields.rs index e865729e001..f22a32c4b35 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/shields.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/shields.rs @@ -1,5 +1,5 @@ use eyeball_im::VectorDiff; -use matrix_sdk_base::deserialized_responses::ShieldState; +use matrix_sdk_base::deserialized_responses::{ShieldState, ShieldStateColor}; use matrix_sdk_test::{async_test, ALICE}; use ruma::events::room::message::RoomMessageEventContent; use stream_assert::assert_next_matches; @@ -37,5 +37,5 @@ async fn test_sent_in_clear_shield() { let item = assert_next_matches!(stream, VectorDiff::PushBack { value } => value); let shield = item.as_event().unwrap().get_shield(false); - assert_eq!(shield, Some(ShieldState::Grey { message: "Sent in clear." })); + assert_eq!(shield, Some(ShieldState::SentInClear { color: ShieldStateColor::Grey })); }