1
0
Fork 0
mirror of https://gitlab.com/famedly/conduit.git synced 2025-09-15 18:57:03 +00:00

Merge branch 'release-0.10.9' into 'master'

fix: don't lookup create event when converting stripped state

See merge request famedly/conduit!779
This commit is contained in:
Matthias Ahouansou 2025-09-12 17:13:42 +00:00
commit 918ca3aa6d
12 changed files with 278 additions and 202 deletions

24
Cargo.lock generated
View file

@ -492,7 +492,7 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]] [[package]]
name = "conduit" name = "conduit"
version = "0.10.8" version = "0.10.9"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"axum", "axum",
@ -2519,7 +2519,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma" name = "ruma"
version = "0.12.6" version = "0.12.6"
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7" source = "git+https://github.com/ruma/ruma.git#d879f7df16ba9928a73649f8149dabeee939691e"
dependencies = [ dependencies = [
"assign", "assign",
"js_int", "js_int",
@ -2538,7 +2538,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-appservice-api" name = "ruma-appservice-api"
version = "0.12.2" version = "0.12.2"
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7" source = "git+https://github.com/ruma/ruma.git#d879f7df16ba9928a73649f8149dabeee939691e"
dependencies = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@ -2550,7 +2550,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-client-api" name = "ruma-client-api"
version = "0.20.4" version = "0.20.4"
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7" source = "git+https://github.com/ruma/ruma.git#d879f7df16ba9928a73649f8149dabeee939691e"
dependencies = [ dependencies = [
"as_variant", "as_variant",
"assign", "assign",
@ -2573,7 +2573,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-common" name = "ruma-common"
version = "0.15.4" version = "0.15.4"
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7" source = "git+https://github.com/ruma/ruma.git#d879f7df16ba9928a73649f8149dabeee939691e"
dependencies = [ dependencies = [
"as_variant", "as_variant",
"base64 0.22.1", "base64 0.22.1",
@ -2605,7 +2605,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-events" name = "ruma-events"
version = "0.30.5" version = "0.30.5"
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7" source = "git+https://github.com/ruma/ruma.git#d879f7df16ba9928a73649f8149dabeee939691e"
dependencies = [ dependencies = [
"as_variant", "as_variant",
"indexmap 2.9.0", "indexmap 2.9.0",
@ -2629,7 +2629,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-federation-api" name = "ruma-federation-api"
version = "0.11.2" version = "0.11.2"
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7" source = "git+https://github.com/ruma/ruma.git#d879f7df16ba9928a73649f8149dabeee939691e"
dependencies = [ dependencies = [
"bytes", "bytes",
"headers", "headers",
@ -2651,7 +2651,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-identifiers-validation" name = "ruma-identifiers-validation"
version = "0.10.1" version = "0.10.1"
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7" source = "git+https://github.com/ruma/ruma.git#d879f7df16ba9928a73649f8149dabeee939691e"
dependencies = [ dependencies = [
"js_int", "js_int",
"thiserror 2.0.12", "thiserror 2.0.12",
@ -2660,7 +2660,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-macros" name = "ruma-macros"
version = "0.15.2" version = "0.15.2"
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7" source = "git+https://github.com/ruma/ruma.git#d879f7df16ba9928a73649f8149dabeee939691e"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"proc-macro-crate", "proc-macro-crate",
@ -2675,7 +2675,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-push-gateway-api" name = "ruma-push-gateway-api"
version = "0.11.0" version = "0.11.0"
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7" source = "git+https://github.com/ruma/ruma.git#d879f7df16ba9928a73649f8149dabeee939691e"
dependencies = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@ -2687,7 +2687,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-signatures" name = "ruma-signatures"
version = "0.17.1" version = "0.17.1"
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7" source = "git+https://github.com/ruma/ruma.git#d879f7df16ba9928a73649f8149dabeee939691e"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"ed25519-dalek", "ed25519-dalek",
@ -2703,7 +2703,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-state-res" name = "ruma-state-res"
version = "0.13.0" version = "0.13.0"
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7" source = "git+https://github.com/ruma/ruma.git#d879f7df16ba9928a73649f8149dabeee939691e"
dependencies = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",

View file

@ -16,7 +16,7 @@ license = "Apache-2.0"
name = "conduit" name = "conduit"
readme = "README.md" readme = "README.md"
repository = "https://gitlab.com/famedly/conduit" repository = "https://gitlab.com/famedly/conduit"
version = "0.10.8" version = "0.10.9"
# See also `rust-toolchain.toml` # See also `rust-toolchain.toml`
rust-version = "1.85.0" rust-version = "1.85.0"

View file

@ -188,9 +188,6 @@ pub async fn knock_room_route(
} }
_ => return Err(Error::BadServerResponse("Room version is not supported")), _ => return Err(Error::BadServerResponse("Room version is not supported")),
}; };
let rules = room_version_id
.rules()
.expect("Supported room version has rules");
let (event_id, knock_event, _) = services().rooms.helpers.populate_membership_template( let (event_id, knock_event, _) = services().rooms.helpers.populate_membership_template(
&knock_template.event, &knock_template.event,
@ -215,8 +212,6 @@ pub async fn knock_room_route(
) )
.await?; .await?;
utils::check_stripped_state(&send_kock_response.knock_room_state, &room_id, &rules)?;
info!("send_knock finished"); info!("send_knock finished");
let mut stripped_state = send_kock_response.knock_room_state; let mut stripped_state = send_kock_response.knock_room_state;
@ -231,7 +226,7 @@ pub async fn knock_room_route(
.to_stripped_state_event() .to_stripped_state_event()
.into(), .into(),
); );
let stripped_state = utils::convert_stripped_state(stripped_state, &room_id)?; let stripped_state = utils::convert_stripped_state(stripped_state)?;
services().rooms.state_cache.update_membership( services().rooms.state_cache.update_membership(
&room_id, &room_id,

View file

@ -23,13 +23,15 @@ use ruma::{
}, },
int, int,
serde::JsonObject, serde::JsonObject,
CanonicalJsonObject, CanonicalJsonValue, OwnedRoomAliasId, OwnedUserId, RoomAliasId, CanonicalJsonObject, CanonicalJsonValue, Int, OwnedRoomAliasId, OwnedUserId, RoomAliasId,
RoomVersionId,
}; };
use serde::Deserialize; use serde::Deserialize;
use serde_json::{json, value::to_raw_value}; use serde_json::{json, value::to_raw_value};
use std::{ use std::{
cmp::max, cmp::max,
collections::{BTreeMap, HashSet}, collections::{BTreeMap, HashSet},
str::FromStr,
sync::Arc, sync::Arc,
}; };
use tracing::{error, info, warn}; use tracing::{error, info, warn};
@ -600,16 +602,29 @@ pub async fn upgrade_room_route(
.authorization; .authorization;
// Get the old room creation event // Get the old room creation event
let mut create_event_content = serde_json::from_str::<CanonicalJsonObject>( let create_event = services()
services() .rooms
.rooms .state_accessor
.state_accessor .room_state_get(&body.room_id, &StateEventType::RoomCreate, "")?
.room_state_get(&body.room_id, &StateEventType::RoomCreate, "")? .ok_or_else(|| Error::bad_database("Found room without m.room.create event."))?;
.ok_or_else(|| Error::bad_database("Found room without m.room.create event."))?
.content let mut create_event_content =
.get(), serde_json::from_str::<CanonicalJsonObject>(create_event.content.get())
) .map_err(|_| Error::bad_database("Invalid room event in database."))?;
.map_err(|_| Error::bad_database("Invalid room event in database."))?;
let old_rules = if let Some(CanonicalJsonValue::String(old_version)) =
create_event_content.get("room_version")
{
RoomVersionId::from_str(old_version)
.map_err(|_| Error::BadDatabase("Create event must be a valid room version ID"))?
.rules()
.expect("Supported room version must have rules")
.authorization
} else {
return Err(Error::BadDatabase(
"Room create event does not have content",
));
};
// Use the m.room.tombstone event as the predecessor // Use the m.room.tombstone event as the predecessor
let predecessor = Some(ruma::events::room::create::PreviousRoom::new( let predecessor = Some(ruma::events::room::create::PreviousRoom::new(
@ -772,8 +787,7 @@ pub async fn upgrade_room_route(
None => continue, // Skipping missing events. None => continue, // Skipping missing events.
}; };
if event_type == StateEventType::RoomPowerLevels && rules.explicitly_privilege_room_creators if event_type == StateEventType::RoomPowerLevels {
{
let mut pl_event_content: CanonicalJsonObject = let mut pl_event_content: CanonicalJsonObject =
serde_json::from_str(event_content.get()).map_err(|e| { serde_json::from_str(event_content.get()).map_err(|e| {
error!( error!(
@ -783,13 +797,41 @@ pub async fn upgrade_room_route(
Error::BadDatabase("Invalid m.room.power_levels event content in room") Error::BadDatabase("Invalid m.room.power_levels event content in room")
})?; })?;
if let Some(CanonicalJsonValue::Object(users)) = pl_event_content.get_mut("users") { let mut users_was_empty = false;
users.remove(sender_user.as_str());
if rules.additional_room_creators { if let CanonicalJsonValue::Object(users) = pl_event_content
for user in &body.additional_creators { .entry("users".to_owned())
users.remove(user.as_str()); .or_insert_with(|| {
users_was_empty = true;
CanonicalJsonValue::Object(BTreeMap::new())
})
{
if rules.explicitly_privilege_room_creators {
users.remove(sender_user.as_str());
if rules.additional_room_creators {
for user in &body.additional_creators {
users.remove(user.as_str());
}
} }
} else if old_rules.explicitly_privilege_room_creators {
users.insert(create_event.sender.to_string(), Int::MAX.into());
if old_rules.additional_room_creators {
if let Some(CanonicalJsonValue::Array(creators)) =
create_event_content.get("additional_creators")
{
for creator in creators {
if let CanonicalJsonValue::String(creator) = creator {
users.insert(creator.to_owned(), Int::MAX.into());
}
}
}
}
}
if users.is_empty() && users_was_empty {
pl_event_content.remove("users");
} }
} }

View file

@ -2105,7 +2105,7 @@ pub async fn create_invite_route(
.rules() .rules()
.expect("Supported room version has rules"); .expect("Supported room version has rules");
utils::check_stripped_state(&invite_room_state, &room_id, &rules)?; utils::check_stripped_state(&invite_room_state, &room_id, &rules).await?;
let mut signed_event = utils::to_canonical_object(&event) let mut signed_event = utils::to_canonical_object(&event)
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invite event is invalid."))?; .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invite event is invalid."))?;
@ -2169,7 +2169,7 @@ pub async fn create_invite_route(
})?; })?;
invite_state.push(pdu.to_stripped_state_event().into()); invite_state.push(pdu.to_stripped_state_event().into());
let invite_state = utils::convert_stripped_state(invite_state, &room_id)?; let invite_state = utils::convert_stripped_state(invite_state)?;
// If we are active in the room, the remote server will notify us about the join via /send // If we are active in the room, the remote server will notify us about the join via /send
if !services() if !services()

View file

@ -1,7 +1,8 @@
use std::{collections::HashSet, sync::Arc}; use std::{collections::HashSet, sync::Arc};
use ruma::{ use ruma::{
api::client::sync::sync_events::StrippedState, events::AnySyncStateEvent, serde::Raw, events::{AnyStrippedStateEvent, AnySyncStateEvent},
serde::Raw,
OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
}; };
@ -38,7 +39,7 @@ impl service::rooms::state_cache::Data for KeyValueDatabase {
&self, &self,
user_id: &UserId, user_id: &UserId,
room_id: &RoomId, room_id: &RoomId,
last_state: Option<Vec<Raw<StrippedState>>>, last_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
) -> Result<()> { ) -> Result<()> {
let (roomuser_id, userroom_id) = get_room_and_user_byte_ids(room_id, user_id); let (roomuser_id, userroom_id) = get_room_and_user_byte_ids(room_id, user_id);
@ -65,7 +66,7 @@ impl service::rooms::state_cache::Data for KeyValueDatabase {
&self, &self,
user_id: &UserId, user_id: &UserId,
room_id: &RoomId, room_id: &RoomId,
last_state: Option<Vec<Raw<StrippedState>>>, last_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
) -> Result<()> { ) -> Result<()> {
let (roomuser_id, userroom_id) = get_room_and_user_byte_ids(room_id, user_id); let (roomuser_id, userroom_id) = get_room_and_user_byte_ids(room_id, user_id);
@ -482,7 +483,7 @@ impl service::rooms::state_cache::Data for KeyValueDatabase {
fn rooms_invited<'a>( fn rooms_invited<'a>(
&'a self, &'a self,
user_id: &UserId, user_id: &UserId,
) -> Box<dyn Iterator<Item = Result<(OwnedRoomId, Vec<Raw<StrippedState>>)>> + 'a> { ) -> Box<dyn Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnyStrippedStateEvent>>)>> + 'a> {
scan_userroom_id_memberstate_tree(user_id, &self.userroomid_invitestate) scan_userroom_id_memberstate_tree(user_id, &self.userroomid_invitestate)
} }
@ -492,7 +493,7 @@ impl service::rooms::state_cache::Data for KeyValueDatabase {
fn rooms_knocked<'a>( fn rooms_knocked<'a>(
&'a self, &'a self,
user_id: &UserId, user_id: &UserId,
) -> Box<dyn Iterator<Item = Result<(OwnedRoomId, Vec<Raw<StrippedState>>)>> + 'a> { ) -> Box<dyn Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnyStrippedStateEvent>>)>> + 'a> {
scan_userroom_id_memberstate_tree(user_id, &self.userroomid_knockstate) scan_userroom_id_memberstate_tree(user_id, &self.userroomid_knockstate)
} }
@ -501,7 +502,7 @@ impl service::rooms::state_cache::Data for KeyValueDatabase {
&self, &self,
user_id: &UserId, user_id: &UserId,
room_id: &RoomId, room_id: &RoomId,
) -> Result<Option<Vec<Raw<StrippedState>>>> { ) -> Result<Option<Vec<Raw<AnyStrippedStateEvent>>>> {
let mut key = user_id.as_bytes().to_vec(); let mut key = user_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(room_id.as_bytes()); key.extend_from_slice(room_id.as_bytes());
@ -522,7 +523,7 @@ impl service::rooms::state_cache::Data for KeyValueDatabase {
&self, &self,
user_id: &UserId, user_id: &UserId,
room_id: &RoomId, room_id: &RoomId,
) -> Result<Option<Vec<Raw<StrippedState>>>> { ) -> Result<Option<Vec<Raw<AnyStrippedStateEvent>>>> {
let mut key = user_id.as_bytes().to_vec(); let mut key = user_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(room_id.as_bytes()); key.extend_from_slice(room_id.as_bytes());
@ -543,7 +544,7 @@ impl service::rooms::state_cache::Data for KeyValueDatabase {
&self, &self,
user_id: &UserId, user_id: &UserId,
room_id: &RoomId, room_id: &RoomId,
) -> Result<Option<Vec<Raw<StrippedState>>>> { ) -> Result<Option<Vec<Raw<AnyStrippedStateEvent>>>> {
let mut key = user_id.as_bytes().to_vec(); let mut key = user_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(room_id.as_bytes()); key.extend_from_slice(room_id.as_bytes());

View file

@ -32,6 +32,7 @@ use ruma::{
}, },
int, int,
room_version_rules::{AuthorizationRules, RoomVersionRules, StateResolutionV2Rules}, room_version_rules::{AuthorizationRules, RoomVersionRules, StateResolutionV2Rules},
serde::Base64,
state_res::{self, StateMap}, state_res::{self, StateMap},
uint, CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch, uint, CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch,
OwnedServerName, OwnedServerSigningKeyId, RoomId, ServerName, OwnedServerName, OwnedServerSigningKeyId, RoomId, ServerName,
@ -338,43 +339,14 @@ impl Service {
} }
// TODO: For RoomVersion6 we must check that Raw<..> is canonical do we anywhere?: https://matrix.org/docs/spec/rooms/v6#canonical-json // TODO: For RoomVersion6 we must check that Raw<..> is canonical do we anywhere?: https://matrix.org/docs/spec/rooms/v6#canonical-json
// We go through all the signatures we see on the value and fetch the corresponding signing // We go through all the signatures we see on the value and fetch the corresponding signing
// keys // keys
self.fetch_required_signing_keys(&value, pub_key_map) self.fetch_required_signing_keys(&value, pub_key_map)
.await?; .await?;
let origin_server_ts = value.get("origin_server_ts").ok_or_else(|| { let filtered_keys = self
error!("Invalid PDU, no origin_server_ts field"); .filter_required_signing_keys(&value, pub_key_map, &room_version_rules)
Error::BadRequest( .await?;
ErrorKind::MissingParam,
"Invalid PDU, no origin_server_ts field",
)
})?;
let origin_server_ts: MilliSecondsSinceUnixEpoch = {
let ts = origin_server_ts.as_integer().ok_or_else(|| {
Error::BadRequest(
ErrorKind::InvalidParam,
"origin_server_ts must be an integer",
)
})?;
MilliSecondsSinceUnixEpoch(i64::from(ts).try_into().map_err(|_| {
Error::BadRequest(ErrorKind::InvalidParam, "Time must be after the unix epoch")
})?)
};
let guard = pub_key_map.read().await;
let pkey_map = (*guard).clone();
// Removing all the expired keys, unless the room version allows stale keys
let filtered_keys = services().globals.filter_keys_server_map(
pkey_map,
origin_server_ts,
&room_version_rules,
);
let mut val = let mut val =
match ruma::signatures::verify_event(&filtered_keys, &value, &room_version_rules) { match ruma::signatures::verify_event(&filtered_keys, &value, &room_version_rules) {
@ -416,8 +388,6 @@ impl Service {
Ok(ruma::signatures::Verified::All) => value, Ok(ruma::signatures::Verified::All) => value,
}; };
drop(guard);
// Now that we have checked the signature and hashes we can add the eventID and convert // Now that we have checked the signature and hashes we can add the eventID and convert
// to our PduEvent type // to our PduEvent type
val.insert( val.insert(
@ -1451,6 +1421,47 @@ impl Service {
Ok((sorted, eventid_info)) Ok((sorted, eventid_info))
} }
/// Filters down the given signing keys, only keeping those which could be valid for this event.
#[tracing::instrument(skip_all)]
pub async fn filter_required_signing_keys(
&self,
event: &BTreeMap<String, CanonicalJsonValue>,
pub_key_map: &RwLock<BTreeMap<String, SigningKeys>>,
room_version_rules: &RoomVersionRules,
) -> Result<BTreeMap<String, BTreeMap<String, Base64>>> {
let origin_server_ts = event.get("origin_server_ts").ok_or_else(|| {
error!("Invalid PDU, no origin_server_ts field");
Error::BadRequest(
ErrorKind::MissingParam,
"Invalid PDU, no origin_server_ts field",
)
})?;
let origin_server_ts: MilliSecondsSinceUnixEpoch = {
let ts = origin_server_ts.as_integer().ok_or_else(|| {
Error::BadRequest(
ErrorKind::InvalidParam,
"origin_server_ts must be an integer",
)
})?;
MilliSecondsSinceUnixEpoch(i64::from(ts).try_into().map_err(|_| {
Error::BadRequest(ErrorKind::InvalidParam, "Time must be after the unix epoch")
})?)
};
let guard = pub_key_map.write().await;
let pkey_map = (*guard).clone();
// Removing all the expired keys, unless the room version allows stale keys
Ok(services().globals.filter_keys_server_map(
pkey_map,
origin_server_ts,
room_version_rules,
))
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub(crate) async fn fetch_required_signing_keys( pub(crate) async fn fetch_required_signing_keys(
&self, &self,

View file

@ -6,13 +6,11 @@ use std::{
pub use data::Data; pub use data::Data;
use ruma::{ use ruma::{
api::{ api::{client::error::ErrorKind, federation::membership::RawStrippedState},
client::{error::ErrorKind, sync::sync_events::StrippedState},
federation::membership::RawStrippedState,
},
events::{ events::{
room::{create::RoomCreateEventContent, member::MembershipState}, room::{create::RoomCreateEventContent, member::MembershipState},
StateEventType, TimelineEventType, RECOMMENDED_STRIPPED_STATE_EVENT_TYPES, AnyStrippedStateEvent, StateEventType, TimelineEventType,
RECOMMENDED_STRIPPED_STATE_EVENT_TYPES,
}, },
room_version_rules::AuthorizationRules, room_version_rules::AuthorizationRules,
serde::Raw, serde::Raw,
@ -273,31 +271,28 @@ impl Service {
services() services()
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(room_id, state_event_type, "") .room_state_get_id(room_id, state_event_type, "")
.transpose() .transpose()
}) })
.map(|e| { .map(|e| {
if e.as_ref() e.and_then(|e| {
.is_ok_and(|e| e.kind == TimelineEventType::RoomCreate) services()
{ .rooms
e.and_then(|e| { .timeline
services() .get_pdu_json(&e)
.rooms .transpose()
.timeline .expect("Event must be present for it to make up the current state")
.get_pdu_json(&e.event_id) .map(PduEvent::convert_to_outgoing_federation_event)
.transpose() .map(RawStrippedState::Pdu)
.expect("Event must be present for it to make up the current state") })
.map(PduEvent::convert_to_outgoing_federation_event)
.map(RawStrippedState::Pdu)
})
} else {
e.map(|e| RawStrippedState::Stripped(e.to_stripped_state_event()))
}
}) })
.collect::<Result<Vec<_>>>() .collect::<Result<Vec<_>>>()
} }
pub fn stripped_state_client(&self, room_id: &RoomId) -> Result<Vec<Raw<StrippedState>>> { pub fn stripped_state_client(
&self,
room_id: &RoomId,
) -> Result<Vec<Raw<AnyStrippedStateEvent>>> {
RECOMMENDED_STRIPPED_STATE_EVENT_TYPES RECOMMENDED_STRIPPED_STATE_EVENT_TYPES
.iter() .iter()
.filter_map(|state_event_type| { .filter_map(|state_event_type| {
@ -307,7 +302,7 @@ impl Service {
.room_state_get(room_id, state_event_type, "") .room_state_get(room_id, state_event_type, "")
.transpose() .transpose()
}) })
.map(|e| e.map(|e| e.to_stripped_state_event().cast())) .map(|e| e.map(|e| e.to_stripped_state_event()))
.collect::<Result<Vec<_>>>() .collect::<Result<Vec<_>>>()
} }

View file

@ -2,7 +2,8 @@ use std::{collections::HashSet, sync::Arc};
use crate::{service::appservice::RegistrationInfo, Result}; use crate::{service::appservice::RegistrationInfo, Result};
use ruma::{ use ruma::{
api::client::sync::sync_events::StrippedState, events::AnySyncStateEvent, serde::Raw, events::{AnyStrippedStateEvent, AnySyncStateEvent},
serde::Raw,
OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
}; };
@ -13,13 +14,13 @@ pub trait Data: Send + Sync {
&self, &self,
user_id: &UserId, user_id: &UserId,
room_id: &RoomId, room_id: &RoomId,
last_state: Option<Vec<Raw<StrippedState>>>, last_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
) -> Result<()>; ) -> Result<()>;
fn mark_as_knocked( fn mark_as_knocked(
&self, &self,
user_id: &UserId, user_id: &UserId,
room_id: &RoomId, room_id: &RoomId,
last_state: Option<Vec<Raw<StrippedState>>>, last_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
) -> Result<()>; ) -> Result<()>;
fn mark_as_left(&self, user_id: &UserId, room_id: &RoomId) -> Result<()>; fn mark_as_left(&self, user_id: &UserId, room_id: &RoomId) -> Result<()>;
@ -85,32 +86,32 @@ pub trait Data: Send + Sync {
fn rooms_invited<'a>( fn rooms_invited<'a>(
&'a self, &'a self,
user_id: &UserId, user_id: &UserId,
) -> Box<dyn Iterator<Item = Result<(OwnedRoomId, Vec<Raw<StrippedState>>)>> + 'a>; ) -> Box<dyn Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnyStrippedStateEvent>>)>> + 'a>;
/// Returns an iterator over all rooms a user has knocked on. /// Returns an iterator over all rooms a user has knocked on.
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
fn rooms_knocked<'a>( fn rooms_knocked<'a>(
&'a self, &'a self,
user_id: &UserId, user_id: &UserId,
) -> Box<dyn Iterator<Item = Result<(OwnedRoomId, Vec<Raw<StrippedState>>)>> + 'a>; ) -> Box<dyn Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnyStrippedStateEvent>>)>> + 'a>;
fn invite_state( fn invite_state(
&self, &self,
user_id: &UserId, user_id: &UserId,
room_id: &RoomId, room_id: &RoomId,
) -> Result<Option<Vec<Raw<StrippedState>>>>; ) -> Result<Option<Vec<Raw<AnyStrippedStateEvent>>>>;
fn knock_state( fn knock_state(
&self, &self,
user_id: &UserId, user_id: &UserId,
room_id: &RoomId, room_id: &RoomId,
) -> Result<Option<Vec<Raw<StrippedState>>>>; ) -> Result<Option<Vec<Raw<AnyStrippedStateEvent>>>>;
fn left_state( fn left_state(
&self, &self,
user_id: &UserId, user_id: &UserId,
room_id: &RoomId, room_id: &RoomId,
) -> Result<Option<Vec<Raw<StrippedState>>>>; ) -> Result<Option<Vec<Raw<AnyStrippedStateEvent>>>>;
/// Returns an iterator over all rooms a user left. /// Returns an iterator over all rooms a user left.
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]

View file

@ -4,12 +4,12 @@ use std::{collections::HashSet, sync::Arc};
pub use data::Data; pub use data::Data;
use ruma::{ use ruma::{
api::client::sync::sync_events::StrippedState,
events::{ events::{
direct::DirectEvent, direct::DirectEvent,
ignored_user_list::IgnoredUserListEvent, ignored_user_list::IgnoredUserListEvent,
room::{create::RoomCreateEventContent, member::MembershipState}, room::{create::RoomCreateEventContent, member::MembershipState},
AnySyncStateEvent, GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType, AnyStrippedStateEvent, AnySyncStateEvent, GlobalAccountDataEventType,
RoomAccountDataEventType, StateEventType,
}, },
serde::Raw, serde::Raw,
OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId, OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
@ -31,7 +31,7 @@ impl Service {
user_id: &UserId, user_id: &UserId,
membership: MembershipState, membership: MembershipState,
sender: &UserId, sender: &UserId,
last_state: Option<Vec<Raw<StrippedState>>>, last_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
update_joined_count: bool, update_joined_count: bool,
) -> Result<()> { ) -> Result<()> {
// Keep track what remote users exist by adding them as "deactivated" users // Keep track what remote users exist by adding them as "deactivated" users
@ -317,7 +317,7 @@ impl Service {
pub fn rooms_invited<'a>( pub fn rooms_invited<'a>(
&'a self, &'a self,
user_id: &UserId, user_id: &UserId,
) -> impl Iterator<Item = Result<(OwnedRoomId, Vec<Raw<StrippedState>>)>> + 'a { ) -> impl Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnyStrippedStateEvent>>)>> + 'a {
self.db.rooms_invited(user_id) self.db.rooms_invited(user_id)
} }
@ -326,7 +326,7 @@ impl Service {
pub fn rooms_knocked<'a>( pub fn rooms_knocked<'a>(
&'a self, &'a self,
user_id: &UserId, user_id: &UserId,
) -> impl Iterator<Item = Result<(OwnedRoomId, Vec<Raw<StrippedState>>)>> + 'a { ) -> impl Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnyStrippedStateEvent>>)>> + 'a {
self.db.rooms_knocked(user_id) self.db.rooms_knocked(user_id)
} }
@ -335,7 +335,7 @@ impl Service {
&self, &self,
user_id: &UserId, user_id: &UserId,
room_id: &RoomId, room_id: &RoomId,
) -> Result<Option<Vec<Raw<StrippedState>>>> { ) -> Result<Option<Vec<Raw<AnyStrippedStateEvent>>>> {
self.db.invite_state(user_id, room_id) self.db.invite_state(user_id, room_id)
} }
@ -344,7 +344,7 @@ impl Service {
&self, &self,
user_id: &UserId, user_id: &UserId,
room_id: &RoomId, room_id: &RoomId,
) -> Result<Option<Vec<Raw<StrippedState>>>> { ) -> Result<Option<Vec<Raw<AnyStrippedStateEvent>>>> {
self.db.knock_state(user_id, room_id) self.db.knock_state(user_id, room_id)
} }
@ -353,7 +353,7 @@ impl Service {
&self, &self,
user_id: &UserId, user_id: &UserId,
room_id: &RoomId, room_id: &RoomId,
) -> Result<Option<Vec<Raw<StrippedState>>>> { ) -> Result<Option<Vec<Raw<AnyStrippedStateEvent>>>> {
self.db.left_state(user_id, room_id) self.db.left_state(user_id, room_id)
} }

View file

@ -455,7 +455,7 @@ impl Service {
.state .state
.stripped_state_client(&pdu.room_id())?; .stripped_state_client(&pdu.room_id())?;
// So that clients can get info about who invitied them (not relevant for knocking), the reason, when, etc. // So that clients can get info about who invitied them (not relevant for knocking), the reason, when, etc.
state.push(pdu.to_stripped_state_event().cast()); state.push(pdu.to_stripped_state_event());
Some(state) Some(state)
} }
_ => None, _ => None,

View file

@ -5,24 +5,26 @@ use cmp::Ordering;
use rand::prelude::*; use rand::prelude::*;
use ring::digest; use ring::digest;
use ruma::{ use ruma::{
api::{ api::{client::error::ErrorKind, federation::membership::RawStrippedState},
client::{error::ErrorKind, sync::sync_events::StrippedState},
federation::membership::RawStrippedState,
},
canonical_json::try_from_json_map, canonical_json::try_from_json_map,
events::{AnyStateEvent, StateEventType}, events::AnyStrippedStateEvent,
room_version_rules::RoomVersionRules, room_version_rules::RoomVersionRules,
serde::Raw, serde::Raw,
signatures::Verified,
CanonicalJsonError, CanonicalJsonObject, CanonicalJsonValue, RoomId, CanonicalJsonError, CanonicalJsonObject, CanonicalJsonValue, RoomId,
}; };
use serde_json::value::to_raw_value; use serde_json::value::to_raw_value;
use std::{ use std::{
cmp, fmt, cmp,
collections::BTreeMap,
fmt,
str::FromStr, str::FromStr,
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
}; };
use tokio::sync::RwLock;
use tracing::warn;
use crate::{service::pdu::gen_event_id_canonical_json, services, Result}; use crate::{service::pdu::gen_event_id_canonical_json, services, Error, Result};
pub fn millis_since_unix_epoch() -> u64 { pub fn millis_since_unix_epoch() -> u64 {
SystemTime::now() SystemTime::now()
@ -199,51 +201,39 @@ impl fmt::Display for HtmlEscape<'_> {
} }
} }
/// Converts `RawStrippedState` (federation format) into `Raw<StrippedState>` (client format) /// Converts `RawStrippedState` (federation format) into `Raw<AnyStrippedState>` (client format)
pub fn convert_stripped_state( pub fn convert_stripped_state(
stripped_state: Vec<RawStrippedState>, stripped_state: Vec<RawStrippedState>,
room_id: &RoomId, ) -> Result<Vec<Raw<AnyStrippedStateEvent>>> {
) -> Result<Vec<Raw<StrippedState>>> {
stripped_state stripped_state
.into_iter() .into_iter()
.map(|stripped_state| match stripped_state { .map(|stripped_state| match stripped_state {
RawStrippedState::Stripped(state) => Ok(state.cast()), RawStrippedState::Stripped(state) => Ok(state),
RawStrippedState::Pdu(state) => { RawStrippedState::Pdu(state) => {
let rules = services() let mut event: CanonicalJsonObject =
.rooms serde_json::from_str(state.get()).map_err(|e| {
.state warn!("Error parsing incoming event {:?}: {:?}", state, e);
.get_room_version(room_id)? Error::BadServerResponse("Invalid PDU in server response")
.rules() })?;
.expect("Supported room version must have rules.");
let (event_id, mut event) = gen_event_id_canonical_json(&state, &rules)?;
event.retain(|k, _| { event.retain(|k, _| {
matches!( matches!(k.as_str(), "content" | "sender" | "state_key" | "type")
k.as_str(),
"content"
| "event_id"
| "origin_server_ts"
| "room_id"
| "sender"
| "state_key"
| "type"
| "unsigned"
)
}); });
event.insert("event_id".to_owned(), event_id.as_str().into());
let raw_value = to_raw_value(&CanonicalJsonValue::Object(event)) let raw_value = to_raw_value(&CanonicalJsonValue::Object(event))
.expect("To raw json should not fail since only change was adding signature"); .expect("To raw json should not fail since only change was adding signature");
Ok(Raw::<AnyStateEvent>::from_json(raw_value).cast()) Ok(Raw::<AnyStrippedStateEvent>::from_json(raw_value))
} }
}) })
.collect() .collect()
} }
pub fn check_stripped_state( /// Performs checks on incoming stripped state, as per [MSC4311]
stripped_state: &Vec<RawStrippedState>, ///
/// [MSC4311]: https://github.com/matrix-org/matrix-spec-proposals/pull/4311
pub async fn check_stripped_state(
stripped_state: &[RawStrippedState],
room_id: &RoomId, room_id: &RoomId,
rules: &RoomVersionRules, rules: &RoomVersionRules,
) -> Result<()> { ) -> Result<()> {
@ -252,65 +242,106 @@ pub fn check_stripped_state(
return Ok(()); return Ok(());
} }
#[cfg(feature = "enforce_msc4311")]
let mut seen_create_event = false; let mut seen_create_event = false;
#[cfg(feature = "enforce_msc4311")] #[cfg(feature = "enforce_msc4311")]
let mut seen_valid_create_event = false; if !stripped_state.iter().all(|state| match state {
RawStrippedState::Pdu(_) => true,
RawStrippedState::Stripped(_) => false,
}) {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Non-pdu found in stripped state",
));
}
for state in stripped_state { let stripped_state = stripped_state
match state { .iter()
RawStrippedState::Pdu(pdu) => { .filter_map(|event| {
let Ok((event_id, value)) = gen_event_id_canonical_json(pdu, rules) else { if let RawStrippedState::Pdu(pdu) = event {
continue; Some(pdu)
}; } else {
let Some(event_type) = value.get("type").and_then(|t| t.as_str()) else { None
continue;
};
if event_type != "m.room.create" {
continue;
}
if seen_create_event {
return Err(error::Error::BadRequest(
ErrorKind::InvalidParam,
"Stripped state has multiple create events",
));
}
if event_id.localpart() != room_id.strip_sigil() {
return Err(error::Error::BadRequest(
ErrorKind::InvalidParam,
"Room ID generated from create event does not match that from the request",
));
}
seen_create_event = true;
#[cfg(feature = "enforce_msc4311")]
{
seen_valid_create_event = true;
}
} }
RawStrippedState::Stripped(event) => { })
let Ok(event) = event.deserialize() else { .map(|pdu| gen_event_id_canonical_json(pdu, rules))
continue; .collect::<Result<Vec<_>>>()?;
};
if event.event_type() != StateEventType::RoomCreate { let pub_key_map = RwLock::new(BTreeMap::new());
continue;
}
if seen_create_event { for (_, pdu) in &stripped_state {
return Err(error::Error::BadRequest( services()
ErrorKind::InvalidParam, .rooms
"Stripped state has multiple create events", .event_handler
)); .fetch_required_signing_keys(pdu, &pub_key_map)
} .await?;
}
for (event_id, pdu) in stripped_state {
let filtered_keys = services()
.rooms
.event_handler
.filter_required_signing_keys(&pdu, &pub_key_map, rules)
.await?;
if !ruma::signatures::verify_event(&filtered_keys, &pdu, rules)
.is_ok_and(|verified| verified == Verified::All)
{
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Signature check on stripped state failed",
));
}
let Some(event_type) = pdu.get("type").and_then(|t| t.as_str()) else {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Event with no type returned",
));
};
if !(event_type == "m.room.create" && rules.authorization.room_create_event_id_as_room_id) {
let pdu_room_id = pdu
.get("room_id")
.ok_or_else(|| Error::BadRequest(ErrorKind::InvalidParam, "Event missing room ID"))
.map(|v| v.as_str())?
.ok_or_else(|| {
Error::BadRequest(ErrorKind::InvalidParam, "Event has non-string room id")
})
.map(RoomId::parse)?
.map_err(|_| {
Error::BadRequest(ErrorKind::InvalidParam, "Event has invalid room ID")
})?;
if pdu_room_id != room_id {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Stripped state room ID does not match the one of the request",
));
}
}
if event_type == "m.room.create" {
#[allow(clippy::collapsible_if)]
if event_id.localpart() != room_id.strip_sigil()
&& rules.authorization.room_create_event_id_as_room_id
{
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Room ID generated from create event does not match that from the request",
));
}
#[cfg(feature = "enforce_msc4311")]
{
seen_create_event = true; seen_create_event = true;
} }
} }
} }
#[cfg(feature = "enforce_msc4311")] #[cfg(feature = "enforce_msc4311")]
if !seen_valid_create_event { if !seen_create_event {
return Err(error::Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Stripped state contained no valid create PDUs", "Stripped state contained no valid create PDUs",
)); ));