diff --git a/Cargo.toml b/Cargo.toml index 71dd3f53..8623837b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,10 +23,10 @@ thiserror = "1.0.20" tracing-subscriber = "0.2.11" [dependencies.ruma] -git = "https://github.com/DevinR528/ruma" -branch = "unstable-join" +git = "https://github.com/ruma/ruma" +# branch = "dev/unstable-join" # path = "../__forks__/ruma/ruma" -# rev = "64b9c646d15a359d62ab464a95176ff94adb2554" +rev = "c15382ca41262058302959eac4029ab4a1ea5889" features = ["client-api", "federation-api", "appservice-api", "unstable-pre-spec", "unstable-synapse-quirks"] #[dependencies.ruma] @@ -40,6 +40,7 @@ features = ["client-api", "federation-api", "appservice-api", "unstable-pre-spec # features = ["client-api", "federation-api", "appservice-api"] [features] +default = ["unstable-pre-spec"] unstable-pre-spec = ["ruma/unstable-pre-spec"] [dev-dependencies] diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..2461f065 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +merge_imports = true \ No newline at end of file diff --git a/src/event_auth.rs b/src/event_auth.rs index a2a27005..056ab90b 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -804,7 +804,7 @@ pub fn verify_third_party_invite( // If there is no m.room.third_party_invite event in the current room state // with state_key matching token, reject if let Some(current_tpid) = current_third_party_invite { - if current_tpid.state_key() != tp_id.signed.token.to_string() { + if current_tpid.state_key() != tp_id.signed.token { return false; } diff --git a/src/state_event.rs b/src/state_event.rs index 92cdd367..afa1ba3c 100644 --- a/src/state_event.rs +++ b/src/state_event.rs @@ -1,16 +1,124 @@ -use std::collections::BTreeMap; +use std::{collections::BTreeMap, time::SystemTime}; use js_int::UInt; use ruma::{ events::{ + from_raw_json_value, pdu::{EventHash, Pdu, PduStub}, room::member::{MemberEventContent, MembershipState}, - EventType, + EventDeHelper, EventType, }, + serde::CanonicalJsonValue, + signatures::reference_hash, EventId, RoomId, RoomVersionId, ServerName, UserId, }; -use serde::{Deserialize, Serialize}; -use std::time::SystemTime; +use serde::{de, ser, Deserialize, Serialize}; +use serde_json::value::RawValue as RawJsonValue; + +#[derive(Clone, Debug, Deserialize, Serialize)] +struct EventIdHelper { + event_id: EventId, +} + +#[derive(Clone, Debug)] +pub enum StateEvent { + Full(EventId, Pdu), + Stub(PduStub), +} + +impl Serialize for StateEvent { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + use ser::Error; + use std::convert::TryInto; + + match self { + Self::Full(_id, ev) => { + // TODO: do we want to add the eventId when we + // serialize + let val: CanonicalJsonValue = serde_json::to_value(ev) + .map_err(S::Error::custom)? + .try_into() + .map_err(S::Error::custom)?; + // let val = val + // .as_object_mut() + // .ok_or_else(|| S::Error::custom("PDU is not an object"))?; + // val.insert("event_id".into(), serde_json::json!(_id)); + + match val { + CanonicalJsonValue::Object(obj) => obj.serialize(serializer), + _ => panic!("Pdu not an object"), + } + } + Self::Stub(_) => panic!("Found PduStub"), + } + } +} + +impl<'de> de::Deserialize<'de> for StateEvent { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + let json = Box::::deserialize(deserializer)?; + let EventDeHelper { + room_id, unsigned, .. + } = from_raw_json_value(&json)?; + + // TODO: do we even want to try for the existing ID + + // Determine whether the event is a full or stub + // based on the fields present. + Ok(if room_id.is_some() { + match unsigned { + Some(unsigned) if unsigned.redacted_because.is_some() => { + panic!("TODO deal with redacted events") + } + _ => StateEvent::Full( + event_id(&json)?, + Pdu::RoomV3Pdu(from_raw_json_value(&json)?), + ), + } + } else { + match unsigned { + Some(unsigned) if unsigned.redacted_because.is_some() => { + panic!("TODO deal with redacted events") + } + _ => StateEvent::Stub(from_raw_json_value(&json)?), + } + }) + } +} + +// #[cfg(not(test))] +// fn event_id(json: &RawJsonValue) -> Result { +// use std::convert::TryFrom; +// EventId::try_from(format!( +// "${}", +// reference_hash(&from_raw_json_value(&json)?, &RoomVersionId::Version6) +// .map_err(de::Error::custom)?, +// )) +// .map_err(de::Error::custom) +// } + +// #[cfg(test)] +fn event_id(json: &RawJsonValue) -> Result { + use std::convert::TryFrom; + Ok(match from_raw_json_value::(&json) { + Ok(id) => id.event_id, + Err(_) => { + // panic!("NOT DURING TESTS"); + EventId::try_from(format!( + "${}", + reference_hash(&from_raw_json_value(&json)?, &RoomVersionId::Version6) + .map_err(de::Error::custom)?, + )) + .map_err(de::Error::custom)? + } + }) +} pub struct Requester<'a> { pub prev_event_ids: Vec, @@ -20,14 +128,6 @@ pub struct Requester<'a> { pub sender: &'a UserId, } -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(untagged)] -pub enum StateEvent { - Full(Pdu), - #[serde(skip_deserializing)] - Sync(PduStub), -} - impl StateEvent { pub fn to_requester(&self) -> Requester<'_> { Requester { @@ -41,7 +141,7 @@ impl StateEvent { pub fn is_power_event(&self) -> bool { match self { - Self::Full(any_event) => match any_event { + Self::Full(_, any_event) => match any_event { Pdu::RoomV1Pdu(event) => match event.kind { EventType::RoomPowerLevels | EventType::RoomJoinRules @@ -66,7 +166,7 @@ impl StateEvent { }, Pdu::RoomV3Pdu(event) => event.state_key == Some("".into()), }, - Self::Sync(any_event) => match any_event { + Self::Stub(any_event) => match any_event { PduStub::RoomV1PduStub(event) => match event.kind { EventType::RoomPowerLevels | EventType::RoomJoinRules @@ -96,11 +196,11 @@ impl StateEvent { &self, ) -> Result { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => serde_json::from_value(ev.content.clone()), Pdu::RoomV3Pdu(ev) => serde_json::from_value(ev.content.clone()), }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => serde_json::from_value(ev.content.clone()), PduStub::RoomV3PduStub(ev) => serde_json::from_value(ev.content.clone()), }, @@ -108,49 +208,31 @@ impl StateEvent { } pub fn origin_server_ts(&self) -> &SystemTime { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => &ev.origin_server_ts, Pdu::RoomV3Pdu(ev) => &ev.origin_server_ts, }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => &ev.origin_server_ts, PduStub::RoomV3PduStub(ev) => &ev.origin_server_ts, }, } } pub fn event_id(&self) -> EventId { - use std::convert::TryFrom; - match self { - Self::Full(ev) => match ev { - Pdu::RoomV1Pdu(ev) => ev.event_id.clone(), - Pdu::RoomV3Pdu(ev) => { - let mut value = serde_json::from_slice::>( - &serde_json::to_vec(ev).expect("all ruma pdus are json values"), - ) - .unwrap(); - - value.remove("event_id"); - - EventId::try_from(&*format!( - "${}", - ruma::signatures::reference_hash(&value, &RoomVersionId::Version6) - .expect("ruma can calculate reference hashes") - )) - .expect("ruma's reference hashes are valid event ids") - } - }, - Self::Sync(_ev) => panic!("Stubs don't have an event id"), + // TODO; make this a &EventId + Self::Full(id, _) => id.clone(), + Self::Stub(_) => panic!("Stubs don't have an event id"), } } pub fn sender(&self) -> &UserId { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => &ev.sender, Pdu::RoomV3Pdu(ev) => &ev.sender, }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => &ev.sender, PduStub::RoomV3PduStub(ev) => &ev.sender, }, @@ -159,11 +241,11 @@ impl StateEvent { pub fn redacts(&self) -> Option<&EventId> { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => ev.redacts.as_ref(), Pdu::RoomV3Pdu(ev) => ev.redacts.as_ref(), }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => ev.redacts.as_ref(), PduStub::RoomV3PduStub(ev) => ev.redacts.as_ref(), }, @@ -172,20 +254,20 @@ impl StateEvent { pub fn room_id(&self) -> Option<&RoomId> { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => Some(&ev.room_id), Pdu::RoomV3Pdu(ev) => Some(&ev.room_id), }, - Self::Sync(_) => None, + Self::Stub(_) => None, } } pub fn kind(&self) -> EventType { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => ev.kind.clone(), Pdu::RoomV3Pdu(ev) => ev.kind.clone(), }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => ev.kind.clone(), PduStub::RoomV3PduStub(ev) => ev.kind.clone(), }, @@ -193,11 +275,11 @@ impl StateEvent { } pub fn state_key(&self) -> String { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => ev.state_key.clone(), Pdu::RoomV3Pdu(ev) => ev.state_key.clone(), }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => ev.state_key.clone(), PduStub::RoomV3PduStub(ev) => ev.state_key.clone(), }, @@ -208,11 +290,11 @@ impl StateEvent { #[cfg(not(feature = "unstable-pre-spec"))] pub fn origin(&self) -> String { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => ev.origin.clone(), Pdu::RoomV3Pdu(ev) => ev.origin.clone(), }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => ev.origin.clone(), PduStub::RoomV3PduStub(ev) => ev.origin.clone(), }, @@ -221,11 +303,11 @@ impl StateEvent { pub fn prev_event_ids(&self) -> Vec { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => ev.prev_events.iter().map(|(id, _)| id).cloned().collect(), Pdu::RoomV3Pdu(ev) => ev.prev_events.clone(), }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => { ev.prev_events.iter().map(|(id, _)| id).cloned().collect() } @@ -236,11 +318,11 @@ impl StateEvent { pub fn auth_events(&self) -> Vec { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => ev.auth_events.iter().map(|(id, _)| id).cloned().collect(), Pdu::RoomV3Pdu(ev) => ev.auth_events.to_vec(), }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => { ev.auth_events.iter().map(|(id, _)| id).cloned().collect() } @@ -251,11 +333,11 @@ impl StateEvent { pub fn content(&self) -> &serde_json::Value { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => &ev.content, Pdu::RoomV3Pdu(ev) => &ev.content, }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => &ev.content, PduStub::RoomV3PduStub(ev) => &ev.content, }, @@ -263,14 +345,12 @@ impl StateEvent { } pub fn unsigned(&self) -> &BTreeMap { - // CONFIRM: The only way this would fail is if we got bad json, it should fail in ruma - // before it fails here. match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => &ev.unsigned, Pdu::RoomV3Pdu(ev) => &ev.unsigned, }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => &ev.unsigned, PduStub::RoomV3PduStub(ev) => &ev.unsigned, }, @@ -279,11 +359,11 @@ impl StateEvent { pub fn signatures(&self) -> BTreeMap, BTreeMap> { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(_) => maplit::btreemap! {}, Pdu::RoomV3Pdu(ev) => ev.signatures.clone(), }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => ev.signatures.clone(), PduStub::RoomV3PduStub(ev) => ev.signatures.clone(), }, @@ -292,11 +372,11 @@ impl StateEvent { pub fn hashes(&self) -> &EventHash { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => &ev.hashes, Pdu::RoomV3Pdu(ev) => &ev.hashes, }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => &ev.hashes, PduStub::RoomV3PduStub(ev) => &ev.hashes, }, @@ -305,11 +385,11 @@ impl StateEvent { pub fn depth(&self) -> &UInt { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => &ev.depth, Pdu::RoomV3Pdu(ev) => &ev.depth, }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => &ev.depth, PduStub::RoomV3PduStub(ev) => &ev.depth, }, @@ -318,7 +398,7 @@ impl StateEvent { pub fn is_type_and_key(&self, ev_type: EventType, state_key: &str) -> bool { match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(ev) => { ev.kind == ev_type && ev.state_key.as_deref() == Some(state_key) } @@ -326,7 +406,7 @@ impl StateEvent { ev.kind == ev_type && ev.state_key.as_deref() == Some(state_key) } }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(ev) => { ev.kind == ev_type && ev.state_key.as_deref() == Some(state_key) } @@ -339,16 +419,16 @@ impl StateEvent { /// Returns the room version this event is formatted for. /// - /// Currently either version 1 or 3 is returned, 3 represents + /// Currently either version 1 or 6 is returned, 6 represents /// version 3 and above. pub fn room_version(&self) -> RoomVersionId { // TODO: We have to know the actual room version this is not sufficient match self { - Self::Full(ev) => match ev { + Self::Full(_, ev) => match ev { Pdu::RoomV1Pdu(_) => RoomVersionId::Version1, Pdu::RoomV3Pdu(_) => RoomVersionId::Version6, }, - Self::Sync(ev) => match ev { + Self::Stub(ev) => match ev { PduStub::RoomV1PduStub(_) => RoomVersionId::Version1, PduStub::RoomV3PduStub(_) => RoomVersionId::Version6, }, @@ -356,22 +436,43 @@ impl StateEvent { } } -// fn process_incoming_pdu( -// pdu: &ruma::Raw, -// version: &ruma::RoomVersionId, -// ) -> (EventId, serde_json::Value) { -// let mut value = serde_json::to_value(pdu.json().get()).expect("all ruma pdus are json values"); -// let event_id = EventId::try_from(&*format!( -// "${}", -// ruma::signatures::reference_hash(&value, version) -// .expect("ruma can calculate reference hashes") -// )) -// .expect("ruma's reference hashes are valid event ids"); +#[cfg(test)] +mod test { + use super::*; -// value -// .as_object_mut() -// .expect("ruma pdus are json objects") -// .insert("event_id".to_owned(), event_id.to_string().into()); + #[test] + fn deserialize_pdu() { + let non_canonical_json = r#"{"auth_events": ["$FEKmyWTamMqoL3zkEC3mVPg3qkcXcUShxxaq5BltsCE", "$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc", "$3ImCSXY6bbWbZ5S2N6BMplHHlP7RkxWZCM9fMbdM2NY", "$8Lfs0rVCE9bHQrUztEF9kbsrT4zASnPEtpImZN4L2n8"], "content": {"membership": "join"}, "depth": 135, "hashes": {"sha256": "Q7OehFJaB32W3NINZKesQZH7+ba7xZVFuyCtuWQ2emk"}, "origin": "pc.koesters.xyz:59003", "origin_server_ts": 1599901756522, "prev_events": ["$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"], "prev_state": [], "room_id": "!eGNyCFvnKcpsnIZiEV:koesters.xyz", "sender": "@timo:pc.koesters.xyz:59003", "state_key": "@timo:pc.koesters.xyz:59003", "type": "m.room.member", "signatures": {"koesters.xyz": {"ed25519:a_wwQy": "bb8T5haywaEXKNxUUjeNBfjYi/Qe32R/dGliduIs3Ct913WGzXYLjWh7xHqapie7viHPzkDw/KYJacpAYKvMBA"}, "pc.koesters.xyz:59003": {"ed25519:key1": "/B3tpaMZKoLNITrup4fbFhbIMWixxEKM49nS4MiKOFfyJjDGuC5nWsurw0m2eYzrffhkF5qQQ8+RlFvkqwqkBw"}}, "unsigned": {"age": 30, "replaces_state": "$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc", "prev_content": {"membership": "join"}, "prev_sender": "@timo:pc.koesters.xyz:59003"}}"#; -// (event_id, value) -// } + let pdu = serde_json::from_str::(non_canonical_json).unwrap(); + + assert_eq!( + match &pdu { + StateEvent::Full(id, _) => id, + _ => panic!("Stub found"), + }, + &ruma::event_id!("$Sfx_o8eLfo4idpTO8_IGrKSPKoRMC1CmQugVw9tu_MU") + ); + } + + #[test] + fn serialize_pdu() { + let non_canonical_json = r#"{"auth_events": ["$FEKmyWTamMqoL3zkEC3mVPg3qkcXcUShxxaq5BltsCE", "$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc", "$3ImCSXY6bbWbZ5S2N6BMplHHlP7RkxWZCM9fMbdM2NY", "$8Lfs0rVCE9bHQrUztEF9kbsrT4zASnPEtpImZN4L2n8"], "content": {"membership": "join"}, "depth": 135, "hashes": {"sha256": "Q7OehFJaB32W3NINZKesQZH7+ba7xZVFuyCtuWQ2emk"}, "origin": "pc.koesters.xyz:59003", "origin_server_ts": 1599901756522, "prev_events": ["$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"], "prev_state": [], "room_id": "!eGNyCFvnKcpsnIZiEV:koesters.xyz", "sender": "@timo:pc.koesters.xyz:59003", "state_key": "@timo:pc.koesters.xyz:59003", "type": "m.room.member", "signatures": {"koesters.xyz": {"ed25519:a_wwQy": "bb8T5haywaEXKNxUUjeNBfjYi/Qe32R/dGliduIs3Ct913WGzXYLjWh7xHqapie7viHPzkDw/KYJacpAYKvMBA"}, "pc.koesters.xyz:59003": {"ed25519:key1": "/B3tpaMZKoLNITrup4fbFhbIMWixxEKM49nS4MiKOFfyJjDGuC5nWsurw0m2eYzrffhkF5qQQ8+RlFvkqwqkBw"}}, "unsigned": {"age": 30, "replaces_state": "$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc", "prev_content": {"membership": "join"}, "prev_sender": "@timo:pc.koesters.xyz:59003"}}"#; + + let pdu = serde_json::from_str::(non_canonical_json).unwrap(); + + assert_eq!( + match &pdu { + StateEvent::Full(id, _) => id, + _ => panic!("Stub found"), + }, + &ruma::event_id!("$Sfx_o8eLfo4idpTO8_IGrKSPKoRMC1CmQugVw9tu_MU") + ); + + // TODO: the `origin` field is left out, though it seems it should be part of the eventId hashing + assert_eq!( + ruma::serde::to_canonical_json_string(&pdu).unwrap(), + r#"{"auth_events":["$FEKmyWTamMqoL3zkEC3mVPg3qkcXcUShxxaq5BltsCE","$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc","$3ImCSXY6bbWbZ5S2N6BMplHHlP7RkxWZCM9fMbdM2NY","$8Lfs0rVCE9bHQrUztEF9kbsrT4zASnPEtpImZN4L2n8"],"content":{"membership":"join"},"depth":135,"hashes":{"sha256":"Q7OehFJaB32W3NINZKesQZH7+ba7xZVFuyCtuWQ2emk"},"origin_server_ts":1599901756522,"prev_events":["$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"],"room_id":"!eGNyCFvnKcpsnIZiEV:koesters.xyz","sender":"@timo:pc.koesters.xyz:59003","signatures":{"koesters.xyz":{"ed25519:a_wwQy":"bb8T5haywaEXKNxUUjeNBfjYi/Qe32R/dGliduIs3Ct913WGzXYLjWh7xHqapie7viHPzkDw/KYJacpAYKvMBA"},"pc.koesters.xyz:59003":{"ed25519:key1":"/B3tpaMZKoLNITrup4fbFhbIMWixxEKM49nS4MiKOFfyJjDGuC5nWsurw0m2eYzrffhkF5qQQ8+RlFvkqwqkBw"}},"state_key":"@timo:pc.koesters.xyz:59003","type":"m.room.member","unsigned":{"age":30,"prev_content":{"membership":"join"},"prev_sender":"@timo:pc.koesters.xyz:59003","replaces_state":"$Oc8MYrZ3-eM4yBbhlj8YkYYluF9KHFDKU5uDpO-Ewcc"}}"#, + ) + } +}