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

feat: MSC4311, Ensuring the create event is available on invites and knocks

This commit is contained in:
Matthias Ahouansou 2025-08-01 17:05:53 +02:00
parent d71d94a0c8
commit 532b17ade8
No known key found for this signature in database
5 changed files with 153 additions and 17 deletions

View file

@ -5,18 +5,24 @@ use cmp::Ordering;
use rand::prelude::*;
use ring::digest;
use ruma::{
api::{client::sync::sync_events::StrippedState, federation::membership::RawStrippedState},
api::{
client::{error::ErrorKind, sync::sync_events::StrippedState},
federation::membership::RawStrippedState,
},
canonical_json::try_from_json_map,
events::{AnyStateEvent, StateEventType},
room_version_rules::RoomVersionRules,
serde::Raw,
CanonicalJsonError, CanonicalJsonObject, RoomId,
CanonicalJsonError, CanonicalJsonObject, CanonicalJsonValue, RoomId,
};
use serde_json::value::to_raw_value;
use std::{
cmp, fmt,
str::FromStr,
time::{SystemTime, UNIX_EPOCH},
};
use crate::Result;
use crate::{service::pdu::gen_event_id_canonical_json, services, Result};
pub fn millis_since_unix_epoch() -> u64 {
SystemTime::now()
@ -196,12 +202,119 @@ impl fmt::Display for HtmlEscape<'_> {
/// Converts `RawStrippedState` (federation format) into `Raw<StrippedState>` (client format)
pub fn convert_stripped_state(
stripped_state: Vec<RawStrippedState>,
_room_id: &RoomId,
room_id: &RoomId,
) -> Result<Vec<Raw<StrippedState>>> {
stripped_state
.into_iter()
.map(|stripped_state| match stripped_state {
RawStrippedState::Stripped(state) => Ok(state.cast()),
RawStrippedState::Pdu(state) => {
let rules = services()
.rooms
.state
.get_room_version(room_id)?
.rules()
.expect("Supported room version must have rules.");
let (event_id, mut event) = gen_event_id_canonical_json(&state, &rules)?;
event.retain(|k, _| {
matches!(
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))
.expect("To raw json should not fail since only change was adding signature");
Ok(Raw::<AnyStateEvent>::from_json(raw_value).cast())
}
})
.collect()
}
pub fn check_stripped_state(
stripped_state: &Vec<RawStrippedState>,
room_id: &RoomId,
rules: &RoomVersionRules,
) -> Result<()> {
// Nothing needs to be done for legacy room ids
if room_id.server_name().is_some() && !rules.authorization.room_create_event_id_as_room_id {
return Ok(());
}
let mut seen_create_event = false;
#[cfg(feature = "enforce_msc4311")]
let mut seen_valid_create_event = false;
for state in stripped_state {
match state {
RawStrippedState::Pdu(pdu) => {
let Ok((event_id, value)) = gen_event_id_canonical_json(pdu, rules) else {
continue;
};
let Some(event_type) = value.get("type").and_then(|t| t.as_str()) else {
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 {
continue;
};
if event.event_type() != StateEventType::RoomCreate {
continue;
}
if seen_create_event {
return Err(error::Error::BadRequest(
ErrorKind::InvalidParam,
"Stripped state has multiple create events",
));
}
seen_create_event = true;
}
}
}
#[cfg(feature = "enforce_msc4311")]
if !seen_valid_create_event {
return Err(error::Error::BadRequest(
ErrorKind::InvalidParam,
"Stripped state contained no valid create PDUs",
));
}
Ok(())
}