State-reset and security mitigations.
Upgrade Ruma to present. The following are intentionally benign for activation in a later commit: - Hydra backports not default. - Room version 12 not default. - Room version 12 not listed as stable. Do not enable them manually or you can brick your database. Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use axum::extract::State;
|
||||
use futures::FutureExt;
|
||||
use futures::{FutureExt, future::OptionFuture};
|
||||
use ruma::{
|
||||
CanonicalJsonObject, Int, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId, RoomVersionId,
|
||||
api::client::room::{self, create_room},
|
||||
api::client::room::{
|
||||
self, create_room,
|
||||
create_room::v3::{CreationContent, RoomPreset},
|
||||
},
|
||||
events::{
|
||||
TimelineEventType,
|
||||
room::{
|
||||
@@ -16,19 +19,21 @@ use ruma::{
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
name::RoomNameEventContent,
|
||||
power_levels::RoomPowerLevelsEventContent,
|
||||
topic::RoomTopicEventContent,
|
||||
topic::{RoomTopicEventContent, TopicContentBlock},
|
||||
},
|
||||
},
|
||||
int,
|
||||
room_version_rules::{RoomIdFormatVersion, RoomVersionRules},
|
||||
serde::{JsonObject, Raw},
|
||||
};
|
||||
use serde_json::{json, value::to_raw_value};
|
||||
use tuwunel_core::{
|
||||
Err, Result, debug_info, debug_warn, err, info,
|
||||
matrix::{StateKey, pdu::PduBuilder},
|
||||
matrix::{StateKey, pdu::PduBuilder, room_version},
|
||||
utils::BoolExt,
|
||||
warn,
|
||||
};
|
||||
use tuwunel_service::{Services, appservice::RegistrationInfo};
|
||||
use tuwunel_service::{Services, appservice::RegistrationInfo, rooms::state::RoomMutexGuard};
|
||||
|
||||
use crate::{Ruma, client::invite_helper};
|
||||
|
||||
@@ -53,164 +58,61 @@ pub(crate) async fn create_room_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<create_room::v3::Request>,
|
||||
) -> Result<create_room::v3::Response> {
|
||||
use create_room::v3::RoomPreset;
|
||||
can_create_room_check(&services, &body).await?;
|
||||
can_publish_directory_check(&services, &body).await?;
|
||||
|
||||
let sender_user = body.sender_user();
|
||||
// Figure out preset. We need it for preset specific events
|
||||
let preset = body
|
||||
.preset
|
||||
.clone()
|
||||
.unwrap_or(match &body.visibility {
|
||||
| room::Visibility::Public => RoomPreset::PublicChat,
|
||||
| _ => RoomPreset::PrivateChat, // Room visibility should not be custom
|
||||
});
|
||||
|
||||
if !services.globals.allow_room_creation()
|
||||
&& body.appservice_info.is_none()
|
||||
&& !services.users.is_admin(sender_user).await
|
||||
{
|
||||
return Err!(Request(Forbidden("Room creation has been disabled.",)));
|
||||
}
|
||||
let alias: OptionFuture<_> = body
|
||||
.room_alias_name
|
||||
.as_ref()
|
||||
.map(|alias| room_alias_check(&services, alias, body.appservice_info.as_ref()))
|
||||
.into();
|
||||
|
||||
let room_id: OwnedRoomId = match &body.room_id {
|
||||
| Some(custom_room_id) => custom_room_id_check(&services, custom_room_id)?,
|
||||
| _ => RoomId::new(&services.server.name),
|
||||
};
|
||||
|
||||
// check if room ID doesn't already exist instead of erroring on auth check
|
||||
if services
|
||||
.rooms
|
||||
.short
|
||||
.get_shortroomid(&room_id)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
return Err!(Request(RoomInUse("Room with that custom room ID already exists",)));
|
||||
}
|
||||
|
||||
if body.visibility == room::Visibility::Public
|
||||
&& services
|
||||
.server
|
||||
.config
|
||||
.lockdown_public_room_directory
|
||||
&& !services.users.is_admin(sender_user).await
|
||||
&& body.appservice_info.is_none()
|
||||
{
|
||||
warn!(
|
||||
"Non-admin user {sender_user} tried to publish {room_id} to the room directory \
|
||||
while \"lockdown_public_room_directory\" is enabled"
|
||||
);
|
||||
|
||||
if services.server.config.admin_room_notices {
|
||||
// Determine room version
|
||||
let (room_version, version_rules) = body
|
||||
.room_version
|
||||
.as_ref()
|
||||
.map_or(Ok(&services.server.config.default_room_version), |version| {
|
||||
services
|
||||
.admin
|
||||
.notice(&format!(
|
||||
"Non-admin user {sender_user} tried to publish {room_id} to the room \
|
||||
directory while \"lockdown_public_room_directory\" is enabled"
|
||||
))
|
||||
.await;
|
||||
}
|
||||
|
||||
return Err!(Request(Forbidden("Publishing rooms to the room directory is not allowed")));
|
||||
}
|
||||
|
||||
let _short_id = services
|
||||
.rooms
|
||||
.short
|
||||
.get_or_create_shortroomid(&room_id)
|
||||
.await;
|
||||
|
||||
let state_lock = services.rooms.state.mutex.lock(&room_id).await;
|
||||
|
||||
let alias: Option<OwnedRoomAliasId> = match body.room_alias_name.as_ref() {
|
||||
| Some(alias) =>
|
||||
Some(room_alias_check(&services, alias, body.appservice_info.as_ref()).await?),
|
||||
| _ => None,
|
||||
};
|
||||
|
||||
let room_version = match body.room_version.clone() {
|
||||
| Some(room_version) =>
|
||||
if services
|
||||
.server
|
||||
.supported_room_version(&room_version)
|
||||
{
|
||||
room_version
|
||||
} else {
|
||||
return Err!(Request(UnsupportedRoomVersion(
|
||||
"This server does not support that room version."
|
||||
)));
|
||||
},
|
||||
| None => services
|
||||
.server
|
||||
.config
|
||||
.default_room_version
|
||||
.clone(),
|
||||
};
|
||||
.supported_room_version(version)
|
||||
.then_ok_or_else(version, || {
|
||||
err!(Request(UnsupportedRoomVersion(
|
||||
"This server does not support room version {version:?}"
|
||||
)))
|
||||
})
|
||||
})
|
||||
.and_then(|version| Ok((version, room_version::rules(version)?)))?;
|
||||
|
||||
let create_content = match &body.creation_content {
|
||||
| Some(content) => {
|
||||
use RoomVersionId::*;
|
||||
|
||||
let mut content = content
|
||||
.deserialize_as::<CanonicalJsonObject>()
|
||||
.map_err(|e| {
|
||||
err!(Request(BadJson(error!(
|
||||
"Failed to deserialise content as canonical JSON: {e}"
|
||||
))))
|
||||
})?;
|
||||
|
||||
match room_version {
|
||||
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
|
||||
content.insert(
|
||||
"creator".into(),
|
||||
json!(&sender_user).try_into().map_err(|e| {
|
||||
err!(Request(BadJson(debug_error!("Invalid creation content: {e}"))))
|
||||
})?,
|
||||
);
|
||||
},
|
||||
| _ => {
|
||||
// V11+ removed the "creator" key
|
||||
},
|
||||
}
|
||||
content.insert(
|
||||
"room_version".into(),
|
||||
json!(room_version.as_str())
|
||||
.try_into()
|
||||
.map_err(|e| err!(Request(BadJson("Invalid creation content: {e}"))))?,
|
||||
);
|
||||
content
|
||||
},
|
||||
| None => {
|
||||
use RoomVersionId::*;
|
||||
|
||||
let content = match room_version {
|
||||
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 =>
|
||||
RoomCreateEventContent::new_v1(sender_user.to_owned()),
|
||||
| _ => RoomCreateEventContent::new_v11(),
|
||||
};
|
||||
let mut content =
|
||||
serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())
|
||||
.unwrap();
|
||||
content.insert("room_version".into(), json!(room_version.as_str()).try_into()?);
|
||||
content
|
||||
},
|
||||
};
|
||||
// Error on existing alias before committing to creation.
|
||||
let alias = alias.await.transpose()?;
|
||||
|
||||
// Increment and hold the counter; the room will sync atomically to clients
|
||||
// which is preferable.
|
||||
let next_count = services.globals.next_count();
|
||||
|
||||
// 1. The room create event
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomCreate,
|
||||
content: to_raw_value(&create_content)?,
|
||||
state_key: Some(StateKey::new()),
|
||||
..Default::default()
|
||||
},
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await?;
|
||||
// 1. Create the create event.
|
||||
let (room_id, state_lock) = match version_rules.room_id_format {
|
||||
| RoomIdFormatVersion::V1 =>
|
||||
create_create_event_legacy(&services, &body, room_version, &version_rules).await?,
|
||||
| RoomIdFormatVersion::V2 =>
|
||||
create_create_event(&services, &body, &preset, room_version, &version_rules)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
err!(Request(InvalidParam("Error while creating m.room.create event: {e}")))
|
||||
})?,
|
||||
};
|
||||
|
||||
// 2. Let the room creator join
|
||||
let sender_user = body.sender_user();
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
@@ -230,17 +132,14 @@ pub(crate) async fn create_room_route(
|
||||
.await?;
|
||||
|
||||
// 3. Power levels
|
||||
|
||||
// Figure out preset. We need it for preset specific events
|
||||
let preset = body
|
||||
.preset
|
||||
.clone()
|
||||
.unwrap_or(match &body.visibility {
|
||||
| room::Visibility::Public => RoomPreset::PublicChat,
|
||||
| _ => RoomPreset::PrivateChat, // Room visibility should not be custom
|
||||
});
|
||||
|
||||
let mut users = BTreeMap::from_iter([(sender_user.to_owned(), int!(100))]);
|
||||
let mut users = if !version_rules
|
||||
.authorization
|
||||
.explicitly_privilege_room_creators
|
||||
{
|
||||
BTreeMap::from_iter([(sender_user.to_owned(), int!(100))])
|
||||
} else {
|
||||
BTreeMap::new()
|
||||
};
|
||||
|
||||
if preset == RoomPreset::TrustedPrivateChat {
|
||||
for invite in &body.invite {
|
||||
@@ -260,11 +159,17 @@ pub(crate) async fn create_room_route(
|
||||
continue;
|
||||
}
|
||||
|
||||
users.insert(invite.clone(), int!(100));
|
||||
if !version_rules
|
||||
.authorization
|
||||
.additional_room_creators
|
||||
{
|
||||
users.insert(invite.clone(), int!(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let power_levels_content = default_power_levels_content(
|
||||
&version_rules,
|
||||
body.power_level_content_override.as_ref(),
|
||||
&body.visibility,
|
||||
users,
|
||||
@@ -365,7 +270,7 @@ pub(crate) async fn create_room_route(
|
||||
// 6. Events listed in initial_state
|
||||
for event in &body.initial_state {
|
||||
let mut pdu_builder = event
|
||||
.deserialize_as::<PduBuilder>()
|
||||
.deserialize_as_unchecked::<PduBuilder>()
|
||||
.map_err(|e| {
|
||||
err!(Request(InvalidParam(warn!("Invalid initial state event: {e:?}"))))
|
||||
})?;
|
||||
@@ -421,7 +326,10 @@ pub(crate) async fn create_room_route(
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(String::new(), &RoomTopicEventContent { topic: topic.clone() }),
|
||||
PduBuilder::state(String::new(), &RoomTopicEventContent {
|
||||
topic: topic.clone(),
|
||||
topic_block: TopicContentBlock::default(),
|
||||
}),
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
@@ -488,25 +396,226 @@ pub(crate) async fn create_room_route(
|
||||
Ok(create_room::v3::Response::new(room_id))
|
||||
}
|
||||
|
||||
async fn create_create_event(
|
||||
services: &Services,
|
||||
body: &Ruma<create_room::v3::Request>,
|
||||
preset: &RoomPreset,
|
||||
room_version: &RoomVersionId,
|
||||
version_rules: &RoomVersionRules,
|
||||
) -> Result<(OwnedRoomId, RoomMutexGuard)> {
|
||||
let _sender_user = body.sender_user();
|
||||
|
||||
let mut create_content = match &body.creation_content {
|
||||
| Some(content) => {
|
||||
let mut content = content
|
||||
.deserialize_as_unchecked::<CanonicalJsonObject>()
|
||||
.map_err(|e| {
|
||||
err!(Request(BadJson(error!(
|
||||
"Failed to deserialise content as canonical JSON: {e}"
|
||||
))))
|
||||
})?;
|
||||
|
||||
content.insert(
|
||||
"room_version".into(),
|
||||
json!(room_version.as_str())
|
||||
.try_into()
|
||||
.map_err(|e| err!(Request(BadJson("Invalid creation content: {e}"))))?,
|
||||
);
|
||||
|
||||
content
|
||||
},
|
||||
| None => {
|
||||
let content = RoomCreateEventContent::new_v11();
|
||||
|
||||
let mut content =
|
||||
serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())?;
|
||||
|
||||
content.insert("room_version".into(), json!(room_version.as_str()).try_into()?);
|
||||
content
|
||||
},
|
||||
};
|
||||
|
||||
if version_rules
|
||||
.authorization
|
||||
.additional_room_creators
|
||||
{
|
||||
let mut additional_creators = body
|
||||
.creation_content
|
||||
.as_ref()
|
||||
.and_then(|c| {
|
||||
c.deserialize_as_unchecked::<CreationContent>()
|
||||
.ok()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.additional_creators;
|
||||
|
||||
if *preset == RoomPreset::TrustedPrivateChat {
|
||||
additional_creators.extend(body.invite.clone());
|
||||
}
|
||||
|
||||
additional_creators.sort();
|
||||
additional_creators.dedup();
|
||||
if !additional_creators.is_empty() {
|
||||
create_content
|
||||
.insert("additional_creators".into(), json!(additional_creators).try_into()?);
|
||||
}
|
||||
}
|
||||
|
||||
// 1. The room create event, using a placeholder room_id
|
||||
let room_id = ruma::room_id!("!thiswillbereplaced").to_owned();
|
||||
let state_lock = services.rooms.state.mutex.lock(&room_id).await;
|
||||
let create_event_id = services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomCreate,
|
||||
content: to_raw_value(&create_content)?,
|
||||
state_key: Some(StateKey::new()),
|
||||
..Default::default()
|
||||
},
|
||||
body.sender_user(),
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
// The real room_id is now the event_id.
|
||||
let room_id = OwnedRoomId::from_parts('!', create_event_id.localpart(), None)?;
|
||||
let state_lock = services.rooms.state.mutex.lock(&room_id).await;
|
||||
|
||||
Ok((room_id, state_lock))
|
||||
}
|
||||
|
||||
async fn create_create_event_legacy(
|
||||
services: &Services,
|
||||
body: &Ruma<create_room::v3::Request>,
|
||||
room_version: &RoomVersionId,
|
||||
_version_rules: &RoomVersionRules,
|
||||
) -> Result<(OwnedRoomId, RoomMutexGuard)> {
|
||||
let room_id: OwnedRoomId = match &body.room_id {
|
||||
| None => RoomId::new_v1(&services.server.name),
|
||||
| Some(custom_id) => custom_room_id_check(services, custom_id).await?,
|
||||
};
|
||||
|
||||
let state_lock = services.rooms.state.mutex.lock(&room_id).await;
|
||||
|
||||
let _short_id = services
|
||||
.rooms
|
||||
.short
|
||||
.get_or_create_shortroomid(&room_id)
|
||||
.await;
|
||||
|
||||
let create_content = match &body.creation_content {
|
||||
| Some(content) => {
|
||||
use RoomVersionId::*;
|
||||
|
||||
let mut content = content
|
||||
.deserialize_as_unchecked::<CanonicalJsonObject>()
|
||||
.map_err(|e| {
|
||||
err!(Request(BadJson(error!(
|
||||
"Failed to deserialise content as canonical JSON: {e}"
|
||||
))))
|
||||
})?;
|
||||
|
||||
match room_version {
|
||||
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
|
||||
content.insert(
|
||||
"creator".into(),
|
||||
json!(body.sender_user())
|
||||
.try_into()
|
||||
.map_err(|e| {
|
||||
err!(Request(BadJson(debug_error!(
|
||||
"Invalid creation content: {e}"
|
||||
))))
|
||||
})?,
|
||||
);
|
||||
},
|
||||
| _ => {
|
||||
// V11+ removed the "creator" key
|
||||
},
|
||||
}
|
||||
|
||||
content.insert(
|
||||
"room_version".into(),
|
||||
json!(room_version.as_str())
|
||||
.try_into()
|
||||
.map_err(|e| err!(Request(BadJson("Invalid creation content: {e}"))))?,
|
||||
);
|
||||
|
||||
content
|
||||
},
|
||||
| None => {
|
||||
use RoomVersionId::*;
|
||||
|
||||
let content = match room_version {
|
||||
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 =>
|
||||
RoomCreateEventContent::new_v1(body.sender_user().to_owned()),
|
||||
| _ => RoomCreateEventContent::new_v11(),
|
||||
};
|
||||
|
||||
let mut content =
|
||||
serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())?;
|
||||
|
||||
content.insert("room_version".into(), json!(room_version.as_str()).try_into()?);
|
||||
content
|
||||
},
|
||||
};
|
||||
|
||||
// 1. The room create event
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomCreate,
|
||||
content: to_raw_value(&create_content)?,
|
||||
state_key: Some(StateKey::new()),
|
||||
..Default::default()
|
||||
},
|
||||
body.sender_user(),
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
Ok((room_id, state_lock))
|
||||
}
|
||||
|
||||
/// creates the power_levels_content for the PDU builder
|
||||
fn default_power_levels_content(
|
||||
version_rules: &RoomVersionRules,
|
||||
power_level_content_override: Option<&Raw<RoomPowerLevelsEventContent>>,
|
||||
visibility: &room::Visibility,
|
||||
users: BTreeMap<OwnedUserId, Int>,
|
||||
) -> Result<serde_json::Value> {
|
||||
use serde_json::to_value;
|
||||
|
||||
let mut power_levels_content =
|
||||
to_value(RoomPowerLevelsEventContent { users, ..Default::default() })?;
|
||||
let mut power_levels_content = RoomPowerLevelsEventContent::new(&version_rules.authorization);
|
||||
power_levels_content.users = users;
|
||||
|
||||
let mut power_levels_content = to_value(power_levels_content)?;
|
||||
|
||||
// secure proper defaults of sensitive/dangerous permissions that moderators
|
||||
// (power level 50) should not have easy access to
|
||||
power_levels_content["events"]["m.room.power_levels"] = to_value(100)?;
|
||||
power_levels_content["events"]["m.room.server_acl"] = to_value(100)?;
|
||||
power_levels_content["events"]["m.room.tombstone"] = to_value(100)?;
|
||||
power_levels_content["events"]["m.room.encryption"] = to_value(100)?;
|
||||
power_levels_content["events"]["m.room.history_visibility"] = to_value(100)?;
|
||||
|
||||
if version_rules
|
||||
.authorization
|
||||
.explicitly_privilege_room_creators
|
||||
{
|
||||
power_levels_content["events"]["m.room.tombstone"] = to_value(150)?;
|
||||
} else {
|
||||
power_levels_content["events"]["m.room.tombstone"] = to_value(100)?;
|
||||
}
|
||||
|
||||
// always allow users to respond (not post new) to polls. this is primarily
|
||||
// useful in read-only announcement rooms that post a public poll.
|
||||
power_levels_content["events"]["org.matrix.msc3381.poll.response"] = to_value(0)?;
|
||||
@@ -599,7 +708,7 @@ async fn room_alias_check(
|
||||
}
|
||||
|
||||
/// if a room is being created with a custom room ID, run our checks against it
|
||||
fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<OwnedRoomId> {
|
||||
async fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<OwnedRoomId> {
|
||||
// apply forbidden room alias checks to custom room IDs too
|
||||
if services
|
||||
.globals
|
||||
@@ -623,8 +732,65 @@ fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<Own
|
||||
let server_name = services.globals.server_name();
|
||||
let full_room_id = format!("!{custom_room_id}:{server_name}");
|
||||
|
||||
OwnedRoomId::parse(full_room_id)
|
||||
.map_err(Into::into)
|
||||
let room_id = OwnedRoomId::parse(full_room_id)
|
||||
.inspect(|full_room_id| debug_info!(?full_room_id, "Full custom room ID"))
|
||||
.inspect_err(|e| warn!(?e, ?custom_room_id, "Failed to create room with custom room ID",))
|
||||
.inspect_err(|e| {
|
||||
warn!(?e, ?custom_room_id, "Failed to create room with custom room ID");
|
||||
})?;
|
||||
|
||||
// check if room ID doesn't already exist instead of erroring on auth check
|
||||
if services
|
||||
.rooms
|
||||
.short
|
||||
.get_shortroomid(&room_id)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
return Err!(Request(RoomInUse("Room with that custom room ID already exists",)));
|
||||
}
|
||||
|
||||
Ok(room_id)
|
||||
}
|
||||
|
||||
async fn can_publish_directory_check(
|
||||
services: &Services,
|
||||
body: &Ruma<create_room::v3::Request>,
|
||||
) -> Result {
|
||||
if !services
|
||||
.server
|
||||
.config
|
||||
.lockdown_public_room_directory
|
||||
|| body.appservice_info.is_some()
|
||||
|| body.visibility != room::Visibility::Public
|
||||
|| services.users.is_admin(body.sender_user()).await
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let msg = format!(
|
||||
"Non-admin user {} tried to publish new to the directory while \
|
||||
lockdown_public_room_directory is enabled",
|
||||
body.sender_user(),
|
||||
);
|
||||
|
||||
warn!("{msg}");
|
||||
if services.server.config.admin_room_notices {
|
||||
services.admin.notice(&msg).await;
|
||||
}
|
||||
|
||||
Err!(Request(Forbidden("Publishing rooms to the room directory is not allowed")))
|
||||
}
|
||||
|
||||
async fn can_create_room_check(
|
||||
services: &Services,
|
||||
body: &Ruma<create_room::v3::Request>,
|
||||
) -> Result {
|
||||
if !services.globals.allow_room_creation()
|
||||
&& body.appservice_info.is_none()
|
||||
&& !services.users.is_admin(body.sender_user()).await
|
||||
{
|
||||
return Err!(Request(Forbidden("Room creation has been disabled.",)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use ruma::{
|
||||
federation::space::{SpaceHierarchyParentSummary, get_hierarchy},
|
||||
},
|
||||
events::room::member::MembershipState,
|
||||
space::SpaceRoomJoinRule::{self, *},
|
||||
room::{JoinRuleSummary, RoomSummary},
|
||||
};
|
||||
use tuwunel_core::{
|
||||
Err, Result, debug_warn, trace,
|
||||
@@ -34,8 +34,8 @@ use crate::{Ruma, RumaResponse};
|
||||
pub(crate) async fn get_room_summary_legacy(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_summary::msc3266::Request>,
|
||||
) -> Result<RumaResponse<get_summary::msc3266::Response>> {
|
||||
body: Ruma<get_summary::v1::Request>,
|
||||
) -> Result<RumaResponse<get_summary::v1::Response>> {
|
||||
get_room_summary(State(services), InsecureClientIp(client), body)
|
||||
.boxed()
|
||||
.await
|
||||
@@ -51,8 +51,8 @@ pub(crate) async fn get_room_summary_legacy(
|
||||
pub(crate) async fn get_room_summary(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_summary::msc3266::Request>,
|
||||
) -> Result<get_summary::msc3266::Response> {
|
||||
body: Ruma<get_summary::v1::Request>,
|
||||
) -> Result<get_summary::v1::Response> {
|
||||
let (room_id, servers) = services
|
||||
.rooms
|
||||
.alias
|
||||
@@ -73,7 +73,7 @@ async fn room_summary_response(
|
||||
room_id: &RoomId,
|
||||
servers: &[OwnedServerName],
|
||||
sender_user: Option<&UserId>,
|
||||
) -> Result<get_summary::msc3266::Response> {
|
||||
) -> Result<get_summary::v1::Response> {
|
||||
if services
|
||||
.rooms
|
||||
.state_cache
|
||||
@@ -85,23 +85,12 @@ async fn room_summary_response(
|
||||
.await;
|
||||
}
|
||||
|
||||
let room =
|
||||
remote_room_summary_hierarchy_response(services, room_id, servers, sender_user).await?;
|
||||
let summary = remote_room_summary_hierarchy_response(services, room_id, servers, sender_user)
|
||||
.await?
|
||||
.summary;
|
||||
|
||||
Ok(get_summary::msc3266::Response {
|
||||
room_id: room_id.to_owned(),
|
||||
canonical_alias: room.canonical_alias,
|
||||
avatar_url: room.avatar_url,
|
||||
guest_can_join: room.guest_can_join,
|
||||
name: room.name,
|
||||
num_joined_members: room.num_joined_members,
|
||||
topic: room.topic,
|
||||
world_readable: room.world_readable,
|
||||
join_rule: room.join_rule,
|
||||
room_type: room.room_type,
|
||||
room_version: room.room_version,
|
||||
encryption: room.encryption,
|
||||
allowed_room_ids: room.allowed_room_ids,
|
||||
Ok(get_summary::v1::Response {
|
||||
summary,
|
||||
membership: sender_user
|
||||
.is_some()
|
||||
.then_some(MembershipState::Leave),
|
||||
@@ -112,7 +101,7 @@ async fn local_room_summary_response(
|
||||
services: &Services,
|
||||
room_id: &RoomId,
|
||||
sender_user: Option<&UserId>,
|
||||
) -> Result<get_summary::msc3266::Response> {
|
||||
) -> Result<get_summary::v1::Response> {
|
||||
trace!(?sender_user, "Sending local room summary response for {room_id:?}");
|
||||
let join_rule = services
|
||||
.rooms
|
||||
@@ -139,7 +128,7 @@ async fn local_room_summary_response(
|
||||
&join_rule.clone().into(),
|
||||
guest_can_join,
|
||||
world_readable,
|
||||
join_rule.allowed_rooms(),
|
||||
join_rule.allowed_room_ids(),
|
||||
sender_user,
|
||||
)
|
||||
.await?;
|
||||
@@ -224,24 +213,22 @@ async fn local_room_summary_response(
|
||||
membership,
|
||||
);
|
||||
|
||||
Ok(get_summary::msc3266::Response {
|
||||
room_id: room_id.to_owned(),
|
||||
canonical_alias,
|
||||
avatar_url,
|
||||
guest_can_join,
|
||||
name,
|
||||
num_joined_members: num_joined_members.try_into().unwrap_or_default(),
|
||||
topic,
|
||||
world_readable,
|
||||
room_type,
|
||||
room_version,
|
||||
encryption,
|
||||
Ok(get_summary::v1::Response {
|
||||
summary: RoomSummary {
|
||||
room_id: room_id.to_owned(),
|
||||
canonical_alias,
|
||||
avatar_url,
|
||||
guest_can_join,
|
||||
name,
|
||||
num_joined_members: num_joined_members.try_into().unwrap_or_default(),
|
||||
topic,
|
||||
world_readable,
|
||||
room_type,
|
||||
room_version,
|
||||
encryption,
|
||||
join_rule: join_rule.into(),
|
||||
},
|
||||
membership,
|
||||
allowed_room_ids: join_rule
|
||||
.allowed_rooms()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
join_rule: join_rule.into(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -277,10 +264,11 @@ async fn remote_room_summary_hierarchy_response(
|
||||
while let Some(Ok(response)) = requests.next().await {
|
||||
trace!("{response:?}");
|
||||
let room = response.room.clone();
|
||||
if room.room_id != room_id {
|
||||
let summary = &room.summary;
|
||||
if summary.room_id != room_id {
|
||||
debug_warn!(
|
||||
"Room ID {} returned does not belong to the requested room ID {}",
|
||||
room.room_id,
|
||||
summary.room_id,
|
||||
room_id
|
||||
);
|
||||
continue;
|
||||
@@ -289,10 +277,10 @@ async fn remote_room_summary_hierarchy_response(
|
||||
return user_can_see_summary(
|
||||
services,
|
||||
room_id,
|
||||
&room.join_rule,
|
||||
room.guest_can_join,
|
||||
room.world_readable,
|
||||
room.allowed_room_ids.iter().map(AsRef::as_ref),
|
||||
&summary.join_rule,
|
||||
summary.guest_can_join,
|
||||
summary.world_readable,
|
||||
summary.join_rule.allowed_room_ids(),
|
||||
sender_user,
|
||||
)
|
||||
.await
|
||||
@@ -308,7 +296,7 @@ async fn remote_room_summary_hierarchy_response(
|
||||
async fn user_can_see_summary<'a, I>(
|
||||
services: &Services,
|
||||
room_id: &RoomId,
|
||||
join_rule: &SpaceRoomJoinRule,
|
||||
join_rule: &JoinRuleSummary,
|
||||
guest_can_join: bool,
|
||||
world_readable: bool,
|
||||
allowed_room_ids: I,
|
||||
@@ -317,17 +305,23 @@ async fn user_can_see_summary<'a, I>(
|
||||
where
|
||||
I: Iterator<Item = &'a RoomId> + Send,
|
||||
{
|
||||
let is_public_room = matches!(join_rule, Public | Knock | KnockRestricted);
|
||||
let is_public_room = matches!(
|
||||
join_rule,
|
||||
JoinRuleSummary::Public | JoinRuleSummary::Knock | JoinRuleSummary::KnockRestricted(_)
|
||||
);
|
||||
|
||||
match sender_user {
|
||||
| Some(sender_user) => {
|
||||
let user_can_see_state_events = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_state_events(sender_user, room_id);
|
||||
|
||||
let is_guest = services
|
||||
.users
|
||||
.is_deactivated(sender_user)
|
||||
.unwrap_or(false);
|
||||
|
||||
let user_in_allowed_restricted_room = allowed_room_ids.stream().any(|room| {
|
||||
services
|
||||
.rooms
|
||||
|
||||
@@ -4,7 +4,7 @@ use axum::extract::State;
|
||||
use futures::StreamExt;
|
||||
use ruma::{
|
||||
CanonicalJsonObject, RoomId, RoomVersionId,
|
||||
api::client::{error::ErrorKind, room::upgrade_room},
|
||||
api::client::room::upgrade_room,
|
||||
events::{
|
||||
StateEventType, TimelineEventType,
|
||||
room::{
|
||||
@@ -14,11 +14,12 @@ use ruma::{
|
||||
},
|
||||
},
|
||||
int,
|
||||
room_version_rules::RoomIdFormatVersion,
|
||||
};
|
||||
use serde_json::{json, value::to_raw_value};
|
||||
use tuwunel_core::{
|
||||
Err, Error, Result, err,
|
||||
matrix::{Event, StateKey, pdu::PduBuilder},
|
||||
Err, Result, err,
|
||||
matrix::{Event, StateKey, pdu::PduBuilder, room_version},
|
||||
};
|
||||
|
||||
use crate::Ruma;
|
||||
@@ -61,14 +62,23 @@ pub(crate) async fn upgrade_room_route(
|
||||
.server
|
||||
.supported_room_version(&body.new_version)
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::UnsupportedRoomVersion,
|
||||
return Err!(Request(UnsupportedRoomVersion(
|
||||
"This server does not support that room version.",
|
||||
));
|
||||
)));
|
||||
}
|
||||
|
||||
if matches!(body.new_version, RoomVersionId::V12) {
|
||||
return Err!(Request(UnsupportedRoomVersion(
|
||||
"Upgrading to version 12 is still under development.",
|
||||
)));
|
||||
}
|
||||
|
||||
let room_version_rules = room_version::rules(&body.new_version)?;
|
||||
let room_id_format = &room_version_rules.room_id_format;
|
||||
assert!(*room_id_format == RoomIdFormatVersion::V1, "TODO");
|
||||
|
||||
// Create a replacement room
|
||||
let replacement_room = RoomId::new(services.globals.server_name());
|
||||
let replacement_room = RoomId::new_v1(services.globals.server_name());
|
||||
|
||||
let _short_id = services
|
||||
.rooms
|
||||
|
||||
Reference in New Issue
Block a user