refactor: Fix errors in api/client/room/

This commit is contained in:
Ginger
2026-04-10 12:52:42 -04:00
parent 97a01a1500
commit a2f6141f4b
6 changed files with 181 additions and 135 deletions
+1 -1
View File
@@ -259,7 +259,7 @@ pub(crate) async fn invite_helper(
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(recipient_user.to_string(), &content),
PartialPdu::state(recipient_user.to_string(), &content),
sender_user,
Some(room_id),
&state_lock,
+52 -21
View File
@@ -9,8 +9,10 @@ use conduwuit::{
use conduwuit_service::{Services, appservice::RegistrationInfo};
use futures::FutureExt;
use ruma::{
CanonicalJsonObject, Int, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId, RoomVersionId,
CanonicalJsonObject, Int, MilliSecondsSinceUnixEpoch, OwnedRoomAliasId, OwnedRoomId,
OwnedUserId, RoomAliasId, RoomId, RoomVersionId, UserId,
api::client::room::{self, create_room},
assign,
events::{
TimelineEventType,
room::{
@@ -26,7 +28,7 @@ use ruma::{
},
},
int,
room_version_rules::RoomIdFormatVersion,
room_version_rules::{AuthorizationRules, RoomIdFormatVersion},
serde::{JsonObject, Raw},
};
use ruminuwuity::invite_permission_config::FilterLevel;
@@ -214,18 +216,33 @@ pub(crate) async fn create_room_route(
.short
.get_or_create_shortroomid(&room_id)
.await;
services.rooms.state.mutex.lock(&room_id).await
services.rooms.state.mutex.lock(room_id.as_str()).await
},
| None => {
let temp_room_id = RoomId::new(services.globals.server_name());
let temp_room_id = RoomId::new_v1(services.globals.server_name());
trace!("Locking temporary room state mutex for {temp_room_id}");
services.rooms.state.mutex.lock(&temp_room_id).await
services.rooms.state.mutex.lock(temp_room_id.as_str()).await
},
};
// 1. The room create event
debug!("Creating room create event for {sender_user} in room {room_id:?}");
let tmp_id = room_id.as_deref();
// Allow requesters to override the `origin_server_ts` to customize room ids
// from v12 onwards
let custom_origin_server_ts = body
.json_body
.as_ref()
.unwrap()
.as_object()
.unwrap()
.get("origin_server_ts")
.and_then(|value| value.as_integer())
.map(|value| value.into())
.and_then(|value: i64| value.try_into().ok())
.map(MilliSecondsSinceUnixEpoch);
let create_event_id = services
.rooms
.timeline
@@ -234,7 +251,7 @@ pub(crate) async fn create_room_route(
event_type: TimelineEventType::RoomCreate,
content: to_raw_value(&create_content)?,
state_key: Some(StateKey::new()),
timestamp: body.origin_server_ts,
timestamp: custom_origin_server_ts,
..Default::default()
},
sender_user,
@@ -295,7 +312,10 @@ pub(crate) async fn create_room_route(
let mut creators: Vec<OwnedUserId> = vec![sender_user.to_owned()];
// Do we care about additional_creators?
if room_version_rules.explicitly_privilege_room_creators {
if room_version_rules
.authorization
.explicitly_privilege_room_creators
{
// Have they been specified?
if let Some(additional_creators) = create_content.get("additional_creators") {
// Are they a real array?
@@ -305,9 +325,9 @@ pub(crate) async fn create_room_route(
// Are they a string?
if let Some(creator) = creator.as_str() {
// Do they parse into a real user ID?
if let Ok(creator) = OwnedUserId::parse(creator) {
if let Ok(creator) = UserId::parse(creator) {
// Add them to the power levels and creators
creators.push(creator.clone());
creators.push(creator);
}
}
}
@@ -320,10 +340,13 @@ pub(crate) async fn create_room_route(
}
let power_levels_content = default_power_levels_content(
body.power_level_content_override.as_ref(),
body.power_level_content_override
.as_ref()
.map(|power_levels| power_levels.cast_ref()),
&body.visibility,
power_levels_to_grant,
creators,
&room_version_rules.authorization,
)?;
services
@@ -349,10 +372,13 @@ pub(crate) async fn create_room_route(
.rooms
.timeline
.build_and_append_pdu(
PartialPdu::state(String::new(), &RoomCanonicalAliasEventContent {
alias: Some(room_alias_id.to_owned()),
alt_aliases: vec![],
}),
PartialPdu::state(
String::new(),
&assign!(RoomCanonicalAliasEventContent::new(), {
alias: Some(room_alias_id.to_owned()),
alt_aliases: vec![],
}),
),
sender_user,
Some(&room_id),
&state_lock,
@@ -420,9 +446,11 @@ pub(crate) async fn create_room_route(
// 6. Events listed in initial_state
for event in &body.initial_state {
let mut partial_pdu = event.deserialize_as::<PartialPdu>().map_err(|e| {
err!(Request(InvalidParam(warn!("Invalid initial state event: {e:?}"))))
})?;
let mut partial_pdu = event
.deserialize_as_unchecked::<PartialPdu>()
.map_err(|e| {
err!(Request(InvalidParam(warn!("Invalid initial state event: {e:?}"))))
})?;
debug_info!("Room creation initial state event: {event:?}");
@@ -473,7 +501,7 @@ pub(crate) async fn create_room_route(
.rooms
.timeline
.build_and_append_pdu(
PartialPdu::state(String::new(), &RoomTopicEventContent { topic: topic.clone() }),
PartialPdu::state(String::new(), &RoomTopicEventContent::new(topic.clone())),
sender_user,
Some(&room_id),
&state_lock,
@@ -528,10 +556,13 @@ fn default_power_levels_content(
visibility: &room::Visibility,
users: BTreeMap<OwnedUserId, Int>,
creators: Vec<OwnedUserId>,
authorization_rules: &AuthorizationRules,
) -> Result<serde_json::Value> {
let mut power_levels_content =
serde_json::to_value(RoomPowerLevelsEventContent { users, ..Default::default() })
.expect("event is valid, we just created it");
serde_json::to_value(assign!(RoomPowerLevelsEventContent::new(authorization_rules), {
users
}))
.unwrap();
// secure proper defaults of sensitive/dangerous permissions that moderators
// (power level 50) should not have easy access to
@@ -621,7 +652,7 @@ async fn room_alias_check(
}
let server_name = services.globals.server_name();
let full_room_alias = OwnedRoomAliasId::parse(format!("#{room_alias_name}:{server_name}"))
let full_room_alias = RoomAliasId::parse(format!("#{room_alias_name}:{server_name}"))
.map_err(|e| {
err!(Request(InvalidParam(debug_error!(
?e,
+1 -1
View File
@@ -44,5 +44,5 @@ pub(crate) async fn get_room_event_route(
event.set_unsigned(body.sender_user.as_deref());
Ok(get_room_event::v3::Response { event: event.into_format() })
Ok(get_room_event::v3::Response::new(event.into_format()))
}
+20 -19
View File
@@ -4,7 +4,10 @@ use conduwuit::{
utils::{BoolExt, stream::TryTools},
};
use futures::{FutureExt, TryStreamExt, future::try_join4};
use ruma::api::client::room::initial_sync::v3::{PaginationChunk, Request, Response};
use ruma::{
api::client::peeking::get_current_state::v3::{PaginationChunk, Request, Response},
assign,
};
use crate::Ruma;
@@ -69,29 +72,27 @@ pub(crate) async fn room_initial_sync_route(
.boxed()
.await?;
let messages = PaginationChunk {
start: events.last().map(at!(0)).as_ref().map(ToString::to_string),
let end = events
.first()
.map(at!(0))
.as_ref()
.map(ToString::to_string)
.unwrap_or_default();
let start = events.last().map(at!(0)).as_ref().map(ToString::to_string);
end: events
.first()
.map(at!(0))
.as_ref()
.map(ToString::to_string)
.unwrap_or_default(),
let chunk = events
.into_iter()
.map(at!(1))
.map(Event::into_format)
.collect();
chunk: events
.into_iter()
.map(at!(1))
.map(Event::into_format)
.collect(),
};
let messages = assign!(PaginationChunk::new(chunk, end), { start });
Ok(Response {
room_id: room_id.to_owned(),
account_data: None,
Ok(assign!(Response::new(room_id.to_owned()), {
account_data: vec![],
state: state.into(),
messages: messages.chunk.is_empty().or_some(messages),
visibility: visibility.into(),
membership,
})
}))
}
+105 -90
View File
@@ -2,16 +2,18 @@ use std::cmp::max;
use axum::extract::State;
use conduwuit::{
Err, Error, Event, Result, RoomVersion, debug, err, info,
Err, Error, Event, Result, debug, err, info,
matrix::{StateKey, pdu::PartialPdu},
};
use futures::{FutureExt, StreamExt};
use ruma::{
CanonicalJsonObject, RoomId, RoomVersionId,
api::client::{error::ErrorKind, room::upgrade_room},
api::{client::room::upgrade_room, error::ErrorKind},
assign,
events::{
StateEventType, TimelineEventType,
room::{
create::PreviousRoom,
member::{MembershipState, RoomMemberEventContent},
power_levels::RoomPowerLevelsEventContent,
tombstone::RoomTombstoneEventContent,
@@ -19,6 +21,7 @@ use ruma::{
space::child::{RedactedSpaceChildEventContent, SpaceChildEventContent},
},
int,
room_version_rules::RoomIdFormatVersion,
};
use serde_json::{json, value::to_raw_value};
@@ -76,7 +79,7 @@ pub(crate) async fn upgrade_room_route(
// First, check if the user has permission to upgrade the room (send tombstone
// event)
let old_room_state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
let old_room_state_lock = services.rooms.state.mutex.lock(body.room_id.as_str()).await;
// Check tombstone permission by attempting to create (but not send) the event
// Note that this does internally call the policy server with a fake room ID,
@@ -85,10 +88,13 @@ pub(crate) async fn upgrade_room_route(
.rooms
.timeline
.create_hash_and_sign_event(
PartialPdu::state(StateKey::new(), &RoomTombstoneEventContent {
body: "This room has been replaced".to_owned(),
replacement_room: RoomId::new(services.globals.server_name()),
}),
PartialPdu::state(
StateKey::new(),
&RoomTombstoneEventContent::new(
String::new(),
RoomId::new_v1(services.globals.server_name()),
),
),
sender_user,
Some(&body.room_id),
&old_room_state_lock,
@@ -102,16 +108,19 @@ pub(crate) async fn upgrade_room_route(
drop(old_room_state_lock);
// Create a replacement room
let room_features = RoomVersion::new(&body.new_version)?;
let replacement_room_owned = if !room_features.room_ids_as_hashes {
Some(RoomId::new(services.globals.server_name()))
let room_version_rules = body
.new_version
.rules()
.expect("new room version should have defined rules");
let replacement_room_owned = if room_version_rules.room_id_format == RoomIdFormatVersion::V2 {
Some(RoomId::new_v1(services.globals.server_name()))
} else {
None
};
let replacement_room: Option<&RoomId> = replacement_room_owned.as_ref().map(AsRef::as_ref);
let replacement_room_tmp = match replacement_room {
| Some(v) => v,
| None => &RoomId::new(services.globals.server_name()),
| None => &RoomId::new_v1(services.globals.server_name()),
};
let _short_id = services
@@ -121,18 +130,21 @@ pub(crate) async fn upgrade_room_route(
.await;
// For pre-v12 rooms, send tombstone before creating replacement room
let tombstone_event_id = if !room_features.room_ids_as_hashes {
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
let tombstone_event_id = if room_version_rules.room_id_format != RoomIdFormatVersion::V2 {
let state_lock = services.rooms.state.mutex.lock(body.room_id.as_str()).await;
// Send a m.room.tombstone event to the old room to indicate that it is not
// intended to be used any further
let tombstone_event_id = services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
body: "This room has been replaced".to_owned(),
replacement_room: replacement_room.unwrap().to_owned(),
}),
PartialPdu::state(
StateKey::new(),
&RoomTombstoneEventContent::new(
"This room has been replaced".to_owned(),
replacement_room.unwrap().to_owned(),
),
),
sender_user,
Some(&body.room_id),
&state_lock,
@@ -145,7 +157,12 @@ pub(crate) async fn upgrade_room_route(
} else {
None
};
let state_lock = services.rooms.state.mutex.lock(replacement_room_tmp).await;
let state_lock = services
.rooms
.state
.mutex
.lock(replacement_room_tmp.as_str())
.await;
// Get the old room creation event
let mut create_event_content: CanonicalJsonObject = services
@@ -156,10 +173,13 @@ pub(crate) async fn upgrade_room_route(
.map_err(|_| err!(Database("Found room without m.room.create event.")))?;
// Use the m.room.tombstone event as the predecessor
let predecessor = Some(ruma::events::room::create::PreviousRoom::new(
body.room_id.clone(),
tombstone_event_id,
));
let predecessor = {
#[allow(deprecated, reason = "Clients still use event_id even though it's deprecated")]
Some(assign!(PreviousRoom::new(body.room_id.clone()), {
event_id: tombstone_event_id,
}))
};
// Send a m.room.create event containing a predecessor field and the applicable
// room_version
@@ -211,7 +231,7 @@ pub(crate) async fn upgrade_room_route(
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
PartialPdu {
event_type: TimelineEventType::RoomCreate,
content: to_raw_value(&create_event_content)
.expect("event is valid, we just created it"),
@@ -227,39 +247,35 @@ pub(crate) async fn upgrade_room_route(
.boxed()
.await?;
let create_id = create_event_id.as_str().replace('$', "!");
let (replacement_room, state_lock) = if room_features.room_ids_as_hashes {
let parsed_room_id = RoomId::parse(&create_id)?;
(Some(parsed_room_id), services.rooms.state.mutex.lock(parsed_room_id).await)
} else {
(replacement_room, state_lock)
};
let (replacement_room, state_lock) =
if room_version_rules.room_id_format == RoomIdFormatVersion::V2 {
let parsed_room_id = RoomId::parse(&create_id)?;
let lock = services
.rooms
.state
.mutex
.lock(parsed_room_id.as_str())
.await;
(Some(parsed_room_id), lock)
} else {
(replacement_room.map(ToOwned::to_owned), state_lock)
};
// Join the new room
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: TimelineEventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Join,
PartialPdu::state(
sender_user.as_str(),
&assign!(RoomMemberEventContent::new(MembershipState::Join), {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
is_direct: None,
third_party_invite: None,
blurhash: services.users.blurhash(sender_user).await.ok(),
reason: None,
join_authorized_via_users_server: None,
redact_events: None,
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some(sender_user.as_str().into()),
redacts: None,
timestamp: None,
},
}),
),
sender_user,
replacement_room,
replacement_room.as_deref(),
&state_lock,
)
.boxed()
@@ -306,14 +322,14 @@ pub(crate) async fn upgrade_room_route(
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
PartialPdu {
event_type: event_type.to_string().into(),
content: event_content,
state_key: Some(StateKey::from(state_key)),
..Default::default()
},
sender_user,
replacement_room,
replacement_room.as_deref(),
&state_lock,
)
.boxed()
@@ -332,27 +348,27 @@ pub(crate) async fn upgrade_room_route(
services
.rooms
.alias
.remove_alias(alias, sender_user)
.remove_alias(&alias, sender_user)
.await?;
services
.rooms
.alias
.set_alias(alias, replacement_room.unwrap(), sender_user)?;
services.rooms.alias.set_alias(
&alias,
replacement_room.as_ref().unwrap(),
sender_user,
)?;
}
// Get the old room power levels
let power_levels_event_content: RoomPowerLevelsEventContent = services
let mut power_levels = services
.rooms
.state_accessor
.room_state_get_content(&body.room_id, &StateEventType::RoomPowerLevels, "")
.await
.map_err(|_| err!(Database("Found room without m.room.power_levels event.")))?;
.get_room_power_levels(&body.room_id)
.await;
// Setting events_default and invite to the greater of 50 and users_default + 1
let new_level = max(
int!(50),
power_levels_event_content
power_levels
.users_default
.checked_add(int!(1))
.ok_or_else(|| {
@@ -360,17 +376,19 @@ pub(crate) async fn upgrade_room_route(
})?,
);
power_levels.events_default = new_level;
power_levels.invite = new_level;
// Modify the power levels in the old room to prevent sending of events and
// inviting new users
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(StateKey::new(), &RoomPowerLevelsEventContent {
events_default: new_level,
invite: new_level,
..power_levels_event_content
}),
PartialPdu::state(
StateKey::new(),
&RoomPowerLevelsEventContent::try_from(power_levels).unwrap(),
),
sender_user,
Some(&body.room_id),
&state_lock,
@@ -381,18 +399,21 @@ pub(crate) async fn upgrade_room_route(
drop(state_lock);
// For v12 rooms, send tombstone AFTER creating replacement room
if room_features.room_ids_as_hashes {
let old_room_state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
if room_version_rules.room_id_format == RoomIdFormatVersion::V2 {
let old_room_state_lock = services.rooms.state.mutex.lock(body.room_id.as_str()).await;
// For v12 rooms, no event reference in predecessor due to cyclic dependency -
// could best effort one maybe?
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
body: "This room has been replaced".to_owned(),
replacement_room: replacement_room.unwrap().to_owned(),
}),
PartialPdu::state(
StateKey::new(),
&RoomTombstoneEventContent::new(
"This room has been replaced".to_owned(),
replacement_room.as_ref().unwrap().to_owned(),
),
),
sender_user,
Some(&body.room_id),
&old_room_state_lock,
@@ -415,7 +436,7 @@ pub(crate) async fn upgrade_room_route(
.rooms
.state_accessor
.room_state_get_content::<SpaceChildEventContent>(
space_id,
&space_id,
&StateEventType::SpaceChild,
body.room_id.as_str(),
)
@@ -427,24 +448,24 @@ pub(crate) async fn upgrade_room_route(
debug!(
"Updating space {space_id} child event for room {} to {}",
&body.room_id,
replacement_room.unwrap()
replacement_room.as_ref().unwrap()
);
// First, drop the space's child event
let state_lock = services.rooms.state.mutex.lock(space_id).await;
let state_lock = services.rooms.state.mutex.lock(space_id.as_str()).await;
debug!("Removing space child event for room {} in space {space_id}", &body.room_id);
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
PartialPdu {
event_type: StateEventType::SpaceChild.into(),
content: to_raw_value(&RedactedSpaceChildEventContent {})
content: to_raw_value(&RedactedSpaceChildEventContent::new())
.expect("event is valid, we just created it"),
state_key: Some(body.room_id.clone().as_str().into()),
..Default::default()
},
sender_user,
Some(space_id),
Some(&space_id),
&state_lock,
)
.boxed()
@@ -453,25 +474,21 @@ pub(crate) async fn upgrade_room_route(
// Now, add a new child event for the replacement room
debug!(
"Adding space child event for room {} in space {space_id}",
replacement_room.unwrap()
replacement_room.as_ref().unwrap()
);
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: StateEventType::SpaceChild.into(),
content: to_raw_value(&SpaceChildEventContent {
via: vec![sender_user.server_name().to_owned()],
PartialPdu::state(
replacement_room.as_ref().unwrap().as_str(),
&assign!(SpaceChildEventContent::new(vec![sender_user.server_name().to_owned()]), {
order: child.order,
suggested: child.suggested,
})
.expect("event is valid, we just created it"),
state_key: Some(replacement_room.unwrap().as_str().into()),
..Default::default()
},
}),
),
sender_user,
Some(space_id),
Some(&space_id),
&state_lock,
)
.boxed()
@@ -480,13 +497,11 @@ pub(crate) async fn upgrade_room_route(
debug!(
"Finished updating space {space_id} child event for room {} to {}",
&body.room_id,
replacement_room.unwrap()
replacement_room.as_ref().unwrap()
);
drop(state_lock);
}
// Return the replacement room id
Ok(upgrade_room::v3::Response {
replacement_room: replacement_room.unwrap().to_owned(),
})
Ok(upgrade_room::v3::Response::new(replacement_room.as_ref().unwrap().to_owned()))
}
+2 -3
View File
@@ -4,7 +4,6 @@ use ruma::{
events::{
StateEventType, TimelineEventType,
room::{
create::RoomCreateEventContent,
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
member::{MembershipState, RoomMemberEventContent},
},
@@ -64,7 +63,7 @@ pub async fn user_can_redact(
return Ok(is_own_event);
}
return Ok(false);
Ok(false)
}
/// Whether a user is allowed to see an event, based on
@@ -139,7 +138,7 @@ pub async fn user_can_invite(
self.services
.timeline
.create_hash_and_sign_event(
PduBuilder::state(
PartialPdu::state(
target_user.as_str(),
&RoomMemberEventContent::new(MembershipState::Invite),
),