matrix-rust-sdk/crates/matrix-sdk-common/src/deserialized_responses.rs

275 lines
11 KiB
Rust

use std::collections::BTreeMap;
use ruma::{
events::{AnySyncTimelineEvent, AnyTimelineEvent},
push::Action,
serde::Raw,
DeviceKeyAlgorithm, OwnedDeviceId, OwnedEventId, OwnedUserId,
};
use serde::{Deserialize, Serialize};
const AUTHENTICITY_NOT_GUARANTEED: &str =
"The authenticity of this encrypted message can't be guaranteed on this device.";
const UNVERIFIED_IDENTITY: &str = "Encrypted by an unverified user.";
const UNSIGNED_DEVICE: &str = "Encrypted by a device not verified by its owner.";
const UNKNOWN_DEVICE: &str = "Encrypted by an unknown or deleted device.";
/// Represents the state of verification for a decrypted message sent by a
/// device.
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub enum VerificationState {
/// This message is guaranteed to be authentic as it is coming from a device
/// belonging to a user that we have verified.
///
/// This is the only state where authenticity can be guaranteed.
Verified,
/// The message could not be linked to a verified device.
///
/// For more detailed information on why the message is considered
/// unverified, refer to the VerificationLevel sub-enum.
Unverified(VerificationLevel),
}
impl VerificationState {
/// Convert the `VerificationState` into a `ShieldState` which can be
/// directly used to decorate messages in the recommended way.
///
/// This method decorates messages using a strict ruleset, for a more lax
/// variant of this method take a look at
/// [`VerificationState::to_shield_state_lax()`].
pub fn to_shield_state_strict(&self) -> ShieldState {
match self {
VerificationState::Verified => ShieldState::None,
VerificationState::Unverified(level) => {
let message = match level {
VerificationLevel::UnverifiedIdentity | VerificationLevel::UnsignedDevice => {
UNVERIFIED_IDENTITY
}
VerificationLevel::None(link) => match link {
DeviceLinkProblem::MissingDevice => UNKNOWN_DEVICE,
DeviceLinkProblem::InsecureSource => AUTHENTICITY_NOT_GUARANTEED,
},
};
ShieldState::Red { message }
}
}
}
/// Convert the `VerificationState` into a `ShieldState` which can be used
/// to decorate messages in the recommended way.
///
/// This implements a legacy, lax decoration mode.
///
/// For a more strict variant of this method take a look at
/// [`VerificationState::to_shield_state_strict()`].
pub fn to_shield_state_lax(&self) -> ShieldState {
match self {
VerificationState::Verified => ShieldState::None,
VerificationState::Unverified(level) => match level {
VerificationLevel::UnverifiedIdentity => {
// If you didn't show interest in verifying that user we don't
// nag you with an error message.
// TODO: We should detect identity rotation of a previously trusted identity and
// then warn see https://github.com/matrix-org/matrix-rust-sdk/issues/1129
ShieldState::None
}
VerificationLevel::UnsignedDevice => {
// This is a high warning. The sender hasn't verified his own device.
ShieldState::Red { 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::Red { 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::Grey { message: AUTHENTICITY_NOT_GUARANTEED }
}
},
},
}
}
}
/// The sub-enum containing detailed information on why a message is considered
/// to be unverified.
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub enum VerificationLevel {
/// The message was sent by a user identity we have not verified.
UnverifiedIdentity,
/// The message was sent by a device not linked to (signed by) any user
/// identity.
UnsignedDevice,
/// We weren't able to link the message back to any device. This might be
/// because the message claims to have been sent by a device which we have
/// not been able to obtain (for example, because the device was since
/// deleted) or because the key to decrypt the message was obtained from
/// an insecure source.
None(DeviceLinkProblem),
}
/// The sub-enum containing detailed information on why we were not able to link
/// a message back to a device.
#[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
/// lag).
MissingDevice,
/// The key was obtained from an insecure source: imported from a file,
/// obtained from a legacy (asymmetric) backup, unsafe key forward, etc.
InsecureSource,
}
/// Recommended decorations for decrypted messages, representing the message's
/// authenticity properties.
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
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.
None,
}
/// The algorithm specific information of a decrypted event.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum AlgorithmInfo {
/// The info if the event was encrypted using m.megolm.v1.aes-sha2
MegolmV1AesSha2 {
/// The curve25519 key of the device that created the megolm decryption
/// key originally.
curve25519_key: String,
/// The signing keys that have created the megolm key that was used to
/// decrypt this session. This map will usually contain a single ed25519
/// key.
sender_claimed_keys: BTreeMap<DeviceKeyAlgorithm, String>,
},
}
/// Struct containing information on how an event was decrypted.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EncryptionInfo {
/// The user ID of the event sender, note this is untrusted data unless the
/// `verification_state` is `Verified` as well.
pub sender: OwnedUserId,
/// The device ID of the device that sent us the event, note this is
/// untrusted data unless `verification_state` is `Verified` as well.
pub sender_device: Option<OwnedDeviceId>,
/// Information about the algorithm that was used to encrypt the event.
pub algorithm_info: AlgorithmInfo,
/// The verification state of the device that sent us the event, note this
/// is the state of the device at the time of decryption. It may change in
/// the future if a device gets verified or deleted.
///
/// Callers that persist this should mark the state as dirty when a device
/// change is received down the sync.
pub verification_state: VerificationState,
}
/// A customized version of a room event coming from a sync that holds optional
/// encryption info.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct SyncTimelineEvent {
/// The actual event.
pub event: Raw<AnySyncTimelineEvent>,
/// The encryption info about the event. Will be `None` if the event was not
/// encrypted.
pub encryption_info: Option<EncryptionInfo>,
/// The push actions associated with this event.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub push_actions: Vec<Action>,
}
impl SyncTimelineEvent {
/// Get the event id of this `SyncTimelineEvent` if the event has any valid
/// id.
pub fn event_id(&self) -> Option<OwnedEventId> {
self.event.get_field::<OwnedEventId>("event_id").ok().flatten()
}
}
impl From<Raw<AnySyncTimelineEvent>> for SyncTimelineEvent {
fn from(inner: Raw<AnySyncTimelineEvent>) -> Self {
Self { encryption_info: None, event: inner, push_actions: Vec::default() }
}
}
impl From<TimelineEvent> for SyncTimelineEvent {
fn from(o: TimelineEvent) -> Self {
// This conversion is unproblematic since a `SyncTimelineEvent` is just a
// `TimelineEvent` without the `room_id`. By converting the raw value in
// this way, we simply cause the `room_id` field in the json to be
// ignored by a subsequent deserialization.
Self {
event: o.event.cast(),
encryption_info: o.encryption_info,
push_actions: o.push_actions,
}
}
}
#[derive(Clone, Debug)]
pub struct TimelineEvent {
/// The actual event.
pub event: Raw<AnyTimelineEvent>,
/// The encryption info about the event. Will be `None` if the event was not
/// encrypted.
pub encryption_info: Option<EncryptionInfo>,
/// The push actions associated with this event.
pub push_actions: Vec<Action>,
}
impl TimelineEvent {
/// Create a new `TimelineEvent` from the given raw event.
///
/// This is a convenience constructor for when you don't need to set
/// `encryption_info` or `push_action`, for example inside a test.
pub fn new(event: Raw<AnyTimelineEvent>) -> Self {
Self { event, encryption_info: None, push_actions: vec![] }
}
}
#[cfg(test)]
mod tests {
use ruma::{
events::{room::message::RoomMessageEventContent, AnySyncTimelineEvent},
serde::Raw,
};
use serde_json::json;
use super::{SyncTimelineEvent, TimelineEvent};
#[test]
fn room_event_to_sync_room_event() {
let event = json!({
"content": RoomMessageEventContent::text_plain("foobar"),
"type": "m.room.message",
"event_id": "$xxxxx:example.org",
"room_id": "!someroom:example.com",
"origin_server_ts": 2189,
"sender": "@carl:example.com",
});
let room_event = TimelineEvent::new(Raw::new(&event).unwrap().cast());
let converted_room_event: SyncTimelineEvent = room_event.into();
let converted_event: AnySyncTimelineEvent =
converted_room_event.event.deserialize().unwrap();
assert_eq!(converted_event.event_id(), "$xxxxx:example.org");
assert_eq!(converted_event.sender(), "@carl:example.com");
}
}