diff --git a/bindings/matrix-sdk-crypto-ffi/Cargo.toml b/bindings/matrix-sdk-crypto-ffi/Cargo.toml index 7d6322692f2..50dcaf137fe 100644 --- a/bindings/matrix-sdk-crypto-ffi/Cargo.toml +++ b/bindings/matrix-sdk-crypto-ffi/Cargo.toml @@ -25,7 +25,7 @@ anyhow = { workspace = true } futures-util = { workspace = true } hmac = "0.12.1" http = { workspace = true } -matrix-sdk-common = { workspace = true } +matrix-sdk-common = { workspace = true, features = ["uniffi"] } pbkdf2 = "0.12.2" rand = { workspace = true } ruma = { workspace = true } @@ -35,7 +35,7 @@ sha2 = { workspace = true } thiserror = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter"] } # keep in sync with uniffi dependency in matrix-sdk-ffi, and uniffi_bindgen in ffi CI job -uniffi = { workspace = true , features = ["cli"]} +uniffi = { workspace = true, features = ["cli"] } vodozemac = { workspace = true } zeroize = { workspace = true, features = ["zeroize_derive"] } diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index 20fed27d6d4..8c7c6b0871d 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -33,9 +33,7 @@ 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, ShieldStateColor as RustShieldStateColor, -}; +use matrix_sdk_common::deserialized_responses::{ShieldState as RustShieldState, ShieldStateCode}; use matrix_sdk_crypto::{ olm::{IdentityKeys, InboundGroupSession, SenderData, Session}, store::{Changes, CryptoStore, PendingChanges, RoomSettings as RustRoomSettings}, @@ -728,41 +726,24 @@ pub enum ShieldColor { #[allow(missing_docs)] pub struct ShieldState { color: ShieldColor, + code: ShieldStateCode, message: Option, } impl From for ShieldState { fn from(value: RustShieldState) -> Self { - 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) } + match value { + RustShieldState::Red { code, message } => { + Self { color: ShieldColor::Red, code, message: Some(message.to_owned()) } } - RustShieldState::SentInClear { color } => { - Self { color: color.into(), message: value.message().map(ToOwned::to_owned) } + RustShieldState::Grey { code, message } => { + Self { color: ShieldColor::Grey, code, message: Some(message.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 17f70c18874..b9d7a3bfec9 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, + deserialized_responses::{ShieldState as RustShieldState, ShieldStateCode}, Error, }; use matrix_sdk_ui::timeline::{ @@ -923,9 +923,32 @@ impl From<&matrix_sdk_ui::timeline::EventSendState> for EventSendState { } } -#[uniffi::export] -pub fn message_for_shield_state(shield_state: ShieldState) -> Option { - shield_state.message().map(ToOwned::to_owned) +/// 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 { code: ShieldStateCode, message: String }, + /// A grey shield with a tooltip containing the associated message should be + /// presented. + Grey { code: ShieldStateCode, message: String }, + /// No shield should be presented. + None, +} + +impl From for ShieldState { + fn from(value: RustShieldState) -> Self { + match value { + RustShieldState::Red { code, message } => { + Self::Red { code, message: message.to_owned() } + } + RustShieldState::Grey { code, message } => { + Self::Grey { code, message: message.to_owned() } + } + RustShieldState::None => Self::None, + } + } } #[derive(uniffi::Object)] diff --git a/crates/matrix-sdk-common/src/deserialized_responses.rs b/crates/matrix-sdk-common/src/deserialized_responses.rs index 10782458065..db620ada371 100644 --- a/crates/matrix-sdk-common/src/deserialized_responses.rs +++ b/crates/matrix-sdk-common/src/deserialized_responses.rs @@ -87,22 +87,24 @@ impl VerificationState { pub fn to_shield_state_strict(&self) -> ShieldState { match self { VerificationState::Verified => ShieldState::None, - VerificationState::Unverified(level) => { - let red = ShieldStateColor::Red; - match level { - VerificationLevel::UnverifiedIdentity | VerificationLevel::UnsignedDevice => { - ShieldState::UnverifiedIdentity { color: red } + VerificationState::Unverified(level) => match level { + VerificationLevel::UnverifiedIdentity | VerificationLevel::UnsignedDevice => { + ShieldState::Red { + code: ShieldStateCode::UnverifiedIdentity, + message: UNVERIFIED_IDENTITY, } - VerificationLevel::None(link) => match link { - DeviceLinkProblem::MissingDevice => { - ShieldState::UnknownDevice { color: red } - } - DeviceLinkProblem::InsecureSource => { - ShieldState::AuthenticityNotGuaranteed { color: red } - } - }, } - } + VerificationLevel::None(link) => match link { + DeviceLinkProblem::MissingDevice => ShieldState::Red { + code: ShieldStateCode::UnknownDevice, + message: UNKNOWN_DEVICE, + }, + DeviceLinkProblem::InsecureSource => ShieldState::Red { + code: ShieldStateCode::AuthenticityNotGuaranteed, + message: AUTHENTICITY_NOT_GUARANTEED, + }, + }, + }, } } @@ -126,19 +128,28 @@ impl VerificationState { } VerificationLevel::UnsignedDevice => { // This is a high warning. The sender hasn't verified his own device. - ShieldState::UnsignedDevice { color: ShieldStateColor::Red } + ShieldState::Red { + code: ShieldStateCode::UnsignedDevice, + message: UNSIGNED_DEVICE, + } } 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::UnknownDevice { color: ShieldStateColor::Red } + ShieldState::Red { + code: ShieldStateCode::UnknownDevice, + message: UNKNOWN_DEVICE, + } } 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::AuthenticityNotGuaranteed { color: ShieldStateColor::Grey } + ShieldState::Grey { + code: ShieldStateCode::AuthenticityNotGuaranteed, + message: AUTHENTICITY_NOT_GUARANTEED, + } } }, }, @@ -170,7 +181,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 downloaded it or the server is erroneously omitting it (federation + /// yet downoaled it or the server is erroneously omitting it (federation /// lag). MissingDevice, /// The key was obtained from an insecure source: imported from a file, @@ -181,56 +192,41 @@ 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 { - /// 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. + /// A red shield with a tooltip containing the associated message should be + /// presented. + Red { + /// A machine-readable representation. + code: ShieldStateCode, + /// A human readable description. + message: &'static str, + }, + /// A grey shield with a tooltip containing the associated message should be + /// presented. + Grey { + /// A machine-readable representation. + code: ShieldStateCode, + /// A human readable description. + message: &'static str, + }, + /// No shield should be presented. 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. +/// A machine-readable representation of the authenticity for a `ShieldState`. #[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, +pub enum ShieldStateCode { + /// Not enough information available to check the authenticity. + AuthenticityNotGuaranteed, + /// The sending device isn't yet known by the Client. + UnknownDevice, + /// The sending device hasn't been verified by the sender. + UnsignedDevice, + /// The sender hasn't been verified by the Client's user. + UnverifiedIdentity, + /// An unencrypted event in an encrypted room. + SentInClear, } /// The algorithm specific information of a decrypted event. diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index e46d864a57b..0bfc44573ca 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -2388,8 +2388,8 @@ pub(crate) mod tests { use futures_util::{pin_mut, FutureExt, StreamExt}; use itertools::Itertools; use matrix_sdk_common::deserialized_responses::{ - DeviceLinkProblem, EncryptionInfo, ShieldState, ShieldStateColor, UnableToDecryptInfo, - UnsignedDecryptionResult, UnsignedEventLocation, VerificationLevel, VerificationState, + DeviceLinkProblem, ShieldState, UnableToDecryptInfo, UnsignedDecryptionResult, + UnsignedEventLocation, VerificationLevel, VerificationState, }; use matrix_sdk_test::{async_test, message_like_event_content, test_json}; use ruma::{ @@ -3346,13 +3346,15 @@ pub(crate) mod tests { #[async_test] async fn test_decryption_verification_state() { - 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(); + 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(); - assert_eq!(info_lax, lax); - assert_eq!(info_strict, strict); - }; + assert_matches!(lax, ShieldState::$lax { .. }); + assert_matches!(strict, ShieldState::$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"); @@ -3409,11 +3411,7 @@ pub(crate) mod tests { encryption_info.verification_state ); - assert_shield( - encryption_info, - ShieldState::UnverifiedIdentity { color: ShieldStateColor::Red }, - ShieldState::UnsignedDevice { color: ShieldStateColor::Red }, - ); + assert_shield!(encryption_info, Red, 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(); @@ -3421,11 +3419,7 @@ pub(crate) mod tests { VerificationState::Unverified(VerificationLevel::UnsignedDevice), encryption_info.verification_state ); - assert_shield( - encryption_info, - ShieldState::UnverifiedIdentity { color: ShieldStateColor::Red }, - ShieldState::UnsignedDevice { color: ShieldStateColor::Red }, - ); + assert_shield!(encryption_info, Red, Red); // Local trust state has no effect bob.get_device(alice.user_id(), alice_device_id(), None) @@ -3440,11 +3434,7 @@ pub(crate) mod tests { VerificationState::Unverified(VerificationLevel::UnsignedDevice), encryption_info.verification_state ); - assert_shield( - encryption_info, - ShieldState::UnverifiedIdentity { color: ShieldStateColor::Red }, - ShieldState::UnsignedDevice { color: ShieldStateColor::Red }, - ); + assert_shield!(encryption_info, Red, 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(); @@ -3459,11 +3449,7 @@ pub(crate) mod tests { VerificationState::Unverified(VerificationLevel::UnsignedDevice), encryption_info.verification_state ); - assert_shield( - encryption_info, - ShieldState::UnverifiedIdentity { color: ShieldStateColor::Red }, - ShieldState::UnsignedDevice { color: ShieldStateColor::Red }, - ); + assert_shield!(encryption_info, Red, Red); // Let alice sign her device sign_alice_device_for_machine_test_helper(&alice, &bob).await; @@ -3475,11 +3461,7 @@ pub(crate) mod tests { encryption_info.verification_state ); - assert_shield( - encryption_info, - ShieldState::UnverifiedIdentity { color: ShieldStateColor::Red }, - ShieldState::None, - ); + assert_shield!(encryption_info, Red, None); // Given alice is verified mark_alice_identity_as_verified_test_helper(&alice, &bob).await; @@ -3494,7 +3476,7 @@ pub(crate) mod tests { let encryption_info = bob.get_room_event_encryption_info(&event, room_id).await.unwrap(); // Then it should say Verified assert_eq!(VerificationState::Verified, encryption_info.verification_state); - assert_shield(encryption_info, ShieldState::None, ShieldState::None); + assert_shield!(encryption_info, None, None); // And the updated SenderData should have been saved into the store. let session = load_session(&bob, room_id, &event).await.unwrap().unwrap(); @@ -3515,11 +3497,7 @@ pub(crate) mod tests { encryption_info.verification_state ); - assert_shield( - encryption_info, - ShieldState::AuthenticityNotGuaranteed { color: ShieldStateColor::Red }, - ShieldState::AuthenticityNotGuaranteed { color: ShieldStateColor::Grey }, - ); + assert_shield!(encryption_info, Red, Grey); } async fn load_session( 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 e80ccf7ab9f..cc3bbc8bf61 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::{ShieldStateColor, SyncTimelineEvent}, + deserialized_responses::{ShieldStateCode, SyncTimelineEvent, SENT_IN_CLEAR}, latest_event::LatestEvent, }; use once_cell::sync::Lazy; @@ -382,7 +382,10 @@ impl EventTimelineItem { Some(info.verification_state.to_shield_state_lax()) } } - None => Some(ShieldState::SentInClear { color: ShieldStateColor::Grey }), + None => Some(ShieldState::Grey { + code: ShieldStateCode::SentInClear, + message: SENT_IN_CLEAR, + }), } } diff --git a/crates/matrix-sdk-ui/src/timeline/tests/shields.rs b/crates/matrix-sdk-ui/src/timeline/tests/shields.rs index f22a32c4b35..3fccc220778 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, ShieldStateColor}; +use matrix_sdk_base::deserialized_responses::{ShieldState, ShieldStateCode}; use matrix_sdk_test::{async_test, ALICE}; use ruma::events::room::message::RoomMessageEventContent; use stream_assert::assert_next_matches; @@ -37,5 +37,8 @@ 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::SentInClear { color: ShieldStateColor::Grey })); + assert_eq!( + shield, + Some(ShieldState::Grey { code: ShieldStateCode::SentInClear, message: "Sent in clear." }) + ); }