Skip to content

Commit

Permalink
chore: Strongly typed ShieldStates.
Browse files Browse the repository at this point in the history
  • Loading branch information
pixlwave committed Jul 31, 2024
1 parent de502f4 commit be2667b
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 69 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 26 additions & 6 deletions bindings/matrix-sdk-crypto-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -731,18 +733,36 @@ pub struct ShieldState {

impl From<RustShieldState> 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)]
Expand Down
27 changes: 4 additions & 23 deletions bindings/matrix-sdk-ffi/src/timeline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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<RustShieldState> 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<String> {
shield_state.message().map(ToOwned::to_owned)
}

#[derive(uniffi::Object)]
Expand Down
2 changes: 1 addition & 1 deletion crates/matrix-sdk-base/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down
2 changes: 2 additions & 0 deletions crates/matrix-sdk-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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"] }
Expand Down
80 changes: 62 additions & 18 deletions crates/matrix-sdk-common/src/deserialized_responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
}
}
}
}
Expand All @@ -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 }
}
},
},
Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions crates/matrix-sdk-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,6 @@ macro_rules! boxed_into_future {
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
#[cfg(not(target_arch = "wasm32"))]
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;

#[cfg(feature = "uniffi")]
uniffi::setup_scaffolding!();
56 changes: 39 additions & 17 deletions crates/matrix-sdk-crypto/src/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -3389,15 +3387,23 @@ 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();
assert_eq!(
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)
Expand All @@ -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();
Expand All @@ -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;
Expand All @@ -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();
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions crates/matrix-sdk-ui/src/timeline/event_item/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 }),
}
}

Expand Down
Loading

0 comments on commit be2667b

Please sign in to comment.