split api/client/room
Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
parent
7450c654ae
commit
f36757027e
5 changed files with 388 additions and 352 deletions
40
src/api/client/room/aliases.rs
Normal file
40
src/api/client/room/aliases.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
use axum::extract::State;
|
||||||
|
use conduit::{Error, Result};
|
||||||
|
use futures::StreamExt;
|
||||||
|
use ruma::api::client::{error::ErrorKind, room::aliases};
|
||||||
|
|
||||||
|
use crate::Ruma;
|
||||||
|
|
||||||
|
/// # `GET /_matrix/client/r0/rooms/{roomId}/aliases`
|
||||||
|
///
|
||||||
|
/// Lists all aliases of the room.
|
||||||
|
///
|
||||||
|
/// - Only users joined to the room are allowed to call this, or if
|
||||||
|
/// `history_visibility` is world readable in the room
|
||||||
|
pub(crate) async fn get_room_aliases_route(
|
||||||
|
State(services): State<crate::State>, body: Ruma<aliases::v3::Request>,
|
||||||
|
) -> Result<aliases::v3::Response> {
|
||||||
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
|
if !services
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.user_can_see_state_events(sender_user, &body.room_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
ErrorKind::forbidden(),
|
||||||
|
"You don't have permission to view this room.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(aliases::v3::Response {
|
||||||
|
aliases: services
|
||||||
|
.rooms
|
||||||
|
.alias
|
||||||
|
.local_aliases_for_room(&body.room_id)
|
||||||
|
.map(ToOwned::to_owned)
|
||||||
|
.collect()
|
||||||
|
.await,
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
use std::{cmp::max, collections::BTreeMap};
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use conduit::{debug_info, debug_warn, err, Err};
|
use conduit::{debug_info, debug_warn, error, info, pdu::PduBuilder, warn, Err, Error, Result};
|
||||||
use futures::{FutureExt, StreamExt, TryFutureExt};
|
use futures::FutureExt;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::client::{
|
api::client::{
|
||||||
error::ErrorKind,
|
error::ErrorKind,
|
||||||
room::{self, aliases, create_room, get_room_event, upgrade_room},
|
room::{self, create_room},
|
||||||
},
|
},
|
||||||
events::{
|
events::{
|
||||||
room::{
|
room::{
|
||||||
|
@ -18,36 +18,18 @@ use ruma::{
|
||||||
member::{MembershipState, RoomMemberEventContent},
|
member::{MembershipState, RoomMemberEventContent},
|
||||||
name::RoomNameEventContent,
|
name::RoomNameEventContent,
|
||||||
power_levels::RoomPowerLevelsEventContent,
|
power_levels::RoomPowerLevelsEventContent,
|
||||||
tombstone::RoomTombstoneEventContent,
|
|
||||||
topic::RoomTopicEventContent,
|
topic::RoomTopicEventContent,
|
||||||
},
|
},
|
||||||
StateEventType, TimelineEventType,
|
TimelineEventType,
|
||||||
},
|
},
|
||||||
int,
|
int,
|
||||||
serde::{JsonObject, Raw},
|
serde::{JsonObject, Raw},
|
||||||
CanonicalJsonObject, Int, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, RoomVersionId,
|
CanonicalJsonObject, Int, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, RoomVersionId,
|
||||||
};
|
};
|
||||||
use serde_json::{json, value::to_raw_value};
|
use serde_json::{json, value::to_raw_value};
|
||||||
use tracing::{error, info, warn};
|
use service::{appservice::RegistrationInfo, Services};
|
||||||
|
|
||||||
use super::invite_helper;
|
use crate::{client::invite_helper, Ruma};
|
||||||
use crate::{
|
|
||||||
service::{appservice::RegistrationInfo, pdu::PduBuilder, Services},
|
|
||||||
Error, Result, Ruma,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Recommended transferable state events list from the spec
|
|
||||||
const TRANSFERABLE_STATE_EVENTS: &[StateEventType; 9] = &[
|
|
||||||
StateEventType::RoomServerAcl,
|
|
||||||
StateEventType::RoomEncryption,
|
|
||||||
StateEventType::RoomName,
|
|
||||||
StateEventType::RoomAvatar,
|
|
||||||
StateEventType::RoomTopic,
|
|
||||||
StateEventType::RoomGuestAccess,
|
|
||||||
StateEventType::RoomHistoryVisibility,
|
|
||||||
StateEventType::RoomJoinRules,
|
|
||||||
StateEventType::RoomPowerLevels,
|
|
||||||
];
|
|
||||||
|
|
||||||
/// # `POST /_matrix/client/v3/createRoom`
|
/// # `POST /_matrix/client/v3/createRoom`
|
||||||
///
|
///
|
||||||
|
@ -479,333 +461,6 @@ pub(crate) async fn create_room_route(
|
||||||
Ok(create_room::v3::Response::new(room_id))
|
Ok(create_room::v3::Response::new(room_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # `GET /_matrix/client/r0/rooms/{roomId}/event/{eventId}`
|
|
||||||
///
|
|
||||||
/// Gets a single event.
|
|
||||||
///
|
|
||||||
/// - You have to currently be joined to the room (TODO: Respect history
|
|
||||||
/// visibility)
|
|
||||||
pub(crate) async fn get_room_event_route(
|
|
||||||
State(services): State<crate::State>, ref body: Ruma<get_room_event::v3::Request>,
|
|
||||||
) -> Result<get_room_event::v3::Response> {
|
|
||||||
Ok(get_room_event::v3::Response {
|
|
||||||
event: services
|
|
||||||
.rooms
|
|
||||||
.timeline
|
|
||||||
.get_pdu_owned(&body.event_id)
|
|
||||||
.map_err(|_| err!(Request(NotFound("Event {} not found.", &body.event_id))))
|
|
||||||
.and_then(|event| async move {
|
|
||||||
services
|
|
||||||
.rooms
|
|
||||||
.state_accessor
|
|
||||||
.user_can_see_event(body.sender_user(), &event.room_id, &body.event_id)
|
|
||||||
.await
|
|
||||||
.then_some(event)
|
|
||||||
.ok_or_else(|| err!(Request(Forbidden("You don't have permission to view this event."))))
|
|
||||||
})
|
|
||||||
.map_ok(|mut event| {
|
|
||||||
event.add_age().ok();
|
|
||||||
event.to_room_event()
|
|
||||||
})
|
|
||||||
.await?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # `GET /_matrix/client/r0/rooms/{roomId}/aliases`
|
|
||||||
///
|
|
||||||
/// Lists all aliases of the room.
|
|
||||||
///
|
|
||||||
/// - Only users joined to the room are allowed to call this, or if
|
|
||||||
/// `history_visibility` is world readable in the room
|
|
||||||
pub(crate) async fn get_room_aliases_route(
|
|
||||||
State(services): State<crate::State>, body: Ruma<aliases::v3::Request>,
|
|
||||||
) -> Result<aliases::v3::Response> {
|
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
|
||||||
|
|
||||||
if !services
|
|
||||||
.rooms
|
|
||||||
.state_accessor
|
|
||||||
.user_can_see_state_events(sender_user, &body.room_id)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
return Err(Error::BadRequest(
|
|
||||||
ErrorKind::forbidden(),
|
|
||||||
"You don't have permission to view this room.",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(aliases::v3::Response {
|
|
||||||
aliases: services
|
|
||||||
.rooms
|
|
||||||
.alias
|
|
||||||
.local_aliases_for_room(&body.room_id)
|
|
||||||
.map(ToOwned::to_owned)
|
|
||||||
.collect()
|
|
||||||
.await,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # `POST /_matrix/client/r0/rooms/{roomId}/upgrade`
|
|
||||||
///
|
|
||||||
/// Upgrades the room.
|
|
||||||
///
|
|
||||||
/// - Creates a replacement room
|
|
||||||
/// - Sends a tombstone event into the current room
|
|
||||||
/// - Sender user joins the room
|
|
||||||
/// - Transfers some state events
|
|
||||||
/// - Moves local aliases
|
|
||||||
/// - Modifies old room power levels to prevent users from speaking
|
|
||||||
pub(crate) async fn upgrade_room_route(
|
|
||||||
State(services): State<crate::State>, body: Ruma<upgrade_room::v3::Request>,
|
|
||||||
) -> Result<upgrade_room::v3::Response> {
|
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
|
||||||
|
|
||||||
if !services
|
|
||||||
.globals
|
|
||||||
.supported_room_versions()
|
|
||||||
.contains(&body.new_version)
|
|
||||||
{
|
|
||||||
return Err(Error::BadRequest(
|
|
||||||
ErrorKind::UnsupportedRoomVersion,
|
|
||||||
"This server does not support that room version.",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a replacement room
|
|
||||||
let replacement_room = RoomId::new(services.globals.server_name());
|
|
||||||
|
|
||||||
let _short_id = services
|
|
||||||
.rooms
|
|
||||||
.short
|
|
||||||
.get_or_create_shortroomid(&replacement_room)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
|
|
||||||
|
|
||||||
// Send a m.room.tombstone event to the old room to indicate that it is not
|
|
||||||
// intended to be used any further Fail if the sender does not have the required
|
|
||||||
// permissions
|
|
||||||
let tombstone_event_id = services
|
|
||||||
.rooms
|
|
||||||
.timeline
|
|
||||||
.build_and_append_pdu(
|
|
||||||
PduBuilder::state(
|
|
||||||
String::new(),
|
|
||||||
&RoomTombstoneEventContent {
|
|
||||||
body: "This room has been replaced".to_owned(),
|
|
||||||
replacement_room: replacement_room.clone(),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
sender_user,
|
|
||||||
&body.room_id,
|
|
||||||
&state_lock,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Change lock to replacement room
|
|
||||||
drop(state_lock);
|
|
||||||
let state_lock = services.rooms.state.mutex.lock(&replacement_room).await;
|
|
||||||
|
|
||||||
// Get the old room creation event
|
|
||||||
let mut create_event_content: CanonicalJsonObject = services
|
|
||||||
.rooms
|
|
||||||
.state_accessor
|
|
||||||
.room_state_get_content(&body.room_id, &StateEventType::RoomCreate, "")
|
|
||||||
.await
|
|
||||||
.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).to_owned(),
|
|
||||||
));
|
|
||||||
|
|
||||||
// Send a m.room.create event containing a predecessor field and the applicable
|
|
||||||
// room_version
|
|
||||||
{
|
|
||||||
use RoomVersionId::*;
|
|
||||||
match body.new_version {
|
|
||||||
V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
|
|
||||||
create_event_content.insert(
|
|
||||||
"creator".into(),
|
|
||||||
json!(&sender_user).try_into().map_err(|e| {
|
|
||||||
info!("Error forming creation event: {e}");
|
|
||||||
Error::BadRequest(ErrorKind::BadJson, "Error forming creation event")
|
|
||||||
})?,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
// "creator" key no longer exists in V11+ rooms
|
|
||||||
create_event_content.remove("creator");
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
create_event_content.insert(
|
|
||||||
"room_version".into(),
|
|
||||||
json!(&body.new_version)
|
|
||||||
.try_into()
|
|
||||||
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?,
|
|
||||||
);
|
|
||||||
create_event_content.insert(
|
|
||||||
"predecessor".into(),
|
|
||||||
json!(predecessor)
|
|
||||||
.try_into()
|
|
||||||
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Validate creation event content
|
|
||||||
if serde_json::from_str::<CanonicalJsonObject>(
|
|
||||||
to_raw_value(&create_event_content)
|
|
||||||
.expect("Error forming creation event")
|
|
||||||
.get(),
|
|
||||||
)
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
return Err(Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"));
|
|
||||||
}
|
|
||||||
|
|
||||||
services
|
|
||||||
.rooms
|
|
||||||
.timeline
|
|
||||||
.build_and_append_pdu(
|
|
||||||
PduBuilder {
|
|
||||||
event_type: TimelineEventType::RoomCreate,
|
|
||||||
content: to_raw_value(&create_event_content).expect("event is valid, we just created it"),
|
|
||||||
unsigned: None,
|
|
||||||
state_key: Some(String::new()),
|
|
||||||
redacts: None,
|
|
||||||
timestamp: None,
|
|
||||||
},
|
|
||||||
sender_user,
|
|
||||||
&replacement_room,
|
|
||||||
&state_lock,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Join the new room
|
|
||||||
services
|
|
||||||
.rooms
|
|
||||||
.timeline
|
|
||||||
.build_and_append_pdu(
|
|
||||||
PduBuilder {
|
|
||||||
event_type: TimelineEventType::RoomMember,
|
|
||||||
content: to_raw_value(&RoomMemberEventContent {
|
|
||||||
membership: 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,
|
|
||||||
})
|
|
||||||
.expect("event is valid, we just created it"),
|
|
||||||
unsigned: None,
|
|
||||||
state_key: Some(sender_user.to_string()),
|
|
||||||
redacts: None,
|
|
||||||
timestamp: None,
|
|
||||||
},
|
|
||||||
sender_user,
|
|
||||||
&replacement_room,
|
|
||||||
&state_lock,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Replicate transferable state events to the new room
|
|
||||||
for event_type in TRANSFERABLE_STATE_EVENTS {
|
|
||||||
let event_content = match services
|
|
||||||
.rooms
|
|
||||||
.state_accessor
|
|
||||||
.room_state_get(&body.room_id, event_type, "")
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(v) => v.content.clone(),
|
|
||||||
Err(_) => continue, // Skipping missing events.
|
|
||||||
};
|
|
||||||
|
|
||||||
services
|
|
||||||
.rooms
|
|
||||||
.timeline
|
|
||||||
.build_and_append_pdu(
|
|
||||||
PduBuilder {
|
|
||||||
event_type: event_type.to_string().into(),
|
|
||||||
content: event_content,
|
|
||||||
state_key: Some(String::new()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
sender_user,
|
|
||||||
&replacement_room,
|
|
||||||
&state_lock,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Moves any local aliases to the new room
|
|
||||||
let mut local_aliases = services
|
|
||||||
.rooms
|
|
||||||
.alias
|
|
||||||
.local_aliases_for_room(&body.room_id)
|
|
||||||
.boxed();
|
|
||||||
|
|
||||||
while let Some(alias) = local_aliases.next().await {
|
|
||||||
services
|
|
||||||
.rooms
|
|
||||||
.alias
|
|
||||||
.remove_alias(alias, sender_user)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
services
|
|
||||||
.rooms
|
|
||||||
.alias
|
|
||||||
.set_alias(alias, &replacement_room, sender_user)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the old room power levels
|
|
||||||
let power_levels_event_content: RoomPowerLevelsEventContent = 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.")))?;
|
|
||||||
|
|
||||||
// Setting events_default and invite to the greater of 50 and users_default + 1
|
|
||||||
let new_level = max(
|
|
||||||
int!(50),
|
|
||||||
power_levels_event_content
|
|
||||||
.users_default
|
|
||||||
.checked_add(int!(1))
|
|
||||||
.ok_or_else(|| err!(Request(BadJson("users_default power levels event content is not valid"))))?,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 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(
|
|
||||||
String::new(),
|
|
||||||
&RoomPowerLevelsEventContent {
|
|
||||||
events_default: new_level,
|
|
||||||
invite: new_level,
|
|
||||||
..power_levels_event_content
|
|
||||||
},
|
|
||||||
),
|
|
||||||
sender_user,
|
|
||||||
&body.room_id,
|
|
||||||
&state_lock,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
drop(state_lock);
|
|
||||||
|
|
||||||
// Return the replacement room id
|
|
||||||
Ok(upgrade_room::v3::Response {
|
|
||||||
replacement_room,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// creates the power_levels_content for the PDU builder
|
/// creates the power_levels_content for the PDU builder
|
||||||
fn default_power_levels_content(
|
fn default_power_levels_content(
|
||||||
power_level_content_override: Option<&Raw<RoomPowerLevelsEventContent>>, visibility: &room::Visibility,
|
power_level_content_override: Option<&Raw<RoomPowerLevelsEventContent>>, visibility: &room::Visibility,
|
38
src/api/client/room/event.rs
Normal file
38
src/api/client/room/event.rs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
use axum::extract::State;
|
||||||
|
use conduit::{err, Result};
|
||||||
|
use futures::TryFutureExt;
|
||||||
|
use ruma::api::client::room::get_room_event;
|
||||||
|
|
||||||
|
use crate::Ruma;
|
||||||
|
|
||||||
|
/// # `GET /_matrix/client/r0/rooms/{roomId}/event/{eventId}`
|
||||||
|
///
|
||||||
|
/// Gets a single event.
|
||||||
|
///
|
||||||
|
/// - You have to currently be joined to the room (TODO: Respect history
|
||||||
|
/// visibility)
|
||||||
|
pub(crate) async fn get_room_event_route(
|
||||||
|
State(services): State<crate::State>, ref body: Ruma<get_room_event::v3::Request>,
|
||||||
|
) -> Result<get_room_event::v3::Response> {
|
||||||
|
Ok(get_room_event::v3::Response {
|
||||||
|
event: services
|
||||||
|
.rooms
|
||||||
|
.timeline
|
||||||
|
.get_pdu_owned(&body.event_id)
|
||||||
|
.map_err(|_| err!(Request(NotFound("Event {} not found.", &body.event_id))))
|
||||||
|
.and_then(|event| async move {
|
||||||
|
services
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.user_can_see_event(body.sender_user(), &event.room_id, &body.event_id)
|
||||||
|
.await
|
||||||
|
.then_some(event)
|
||||||
|
.ok_or_else(|| err!(Request(Forbidden("You don't have permission to view this event."))))
|
||||||
|
})
|
||||||
|
.map_ok(|mut event| {
|
||||||
|
event.add_age().ok();
|
||||||
|
event.to_room_event()
|
||||||
|
})
|
||||||
|
.await?,
|
||||||
|
})
|
||||||
|
}
|
9
src/api/client/room/mod.rs
Normal file
9
src/api/client/room/mod.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
mod aliases;
|
||||||
|
mod create;
|
||||||
|
mod event;
|
||||||
|
mod upgrade;
|
||||||
|
|
||||||
|
pub(crate) use self::{
|
||||||
|
aliases::get_room_aliases_route, create::create_room_route, event::get_room_event_route,
|
||||||
|
upgrade::upgrade_room_route,
|
||||||
|
};
|
294
src/api/client/room/upgrade.rs
Normal file
294
src/api/client/room/upgrade.rs
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
use std::cmp::max;
|
||||||
|
|
||||||
|
use axum::extract::State;
|
||||||
|
use conduit::{err, info, pdu::PduBuilder, Error, Result};
|
||||||
|
use futures::StreamExt;
|
||||||
|
use ruma::{
|
||||||
|
api::client::{error::ErrorKind, room::upgrade_room},
|
||||||
|
events::{
|
||||||
|
room::{
|
||||||
|
member::{MembershipState, RoomMemberEventContent},
|
||||||
|
power_levels::RoomPowerLevelsEventContent,
|
||||||
|
tombstone::RoomTombstoneEventContent,
|
||||||
|
},
|
||||||
|
StateEventType, TimelineEventType,
|
||||||
|
},
|
||||||
|
int, CanonicalJsonObject, RoomId, RoomVersionId,
|
||||||
|
};
|
||||||
|
use serde_json::{json, value::to_raw_value};
|
||||||
|
|
||||||
|
use crate::Ruma;
|
||||||
|
|
||||||
|
/// Recommended transferable state events list from the spec
|
||||||
|
const TRANSFERABLE_STATE_EVENTS: &[StateEventType; 9] = &[
|
||||||
|
StateEventType::RoomServerAcl,
|
||||||
|
StateEventType::RoomEncryption,
|
||||||
|
StateEventType::RoomName,
|
||||||
|
StateEventType::RoomAvatar,
|
||||||
|
StateEventType::RoomTopic,
|
||||||
|
StateEventType::RoomGuestAccess,
|
||||||
|
StateEventType::RoomHistoryVisibility,
|
||||||
|
StateEventType::RoomJoinRules,
|
||||||
|
StateEventType::RoomPowerLevels,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// # `POST /_matrix/client/r0/rooms/{roomId}/upgrade`
|
||||||
|
///
|
||||||
|
/// Upgrades the room.
|
||||||
|
///
|
||||||
|
/// - Creates a replacement room
|
||||||
|
/// - Sends a tombstone event into the current room
|
||||||
|
/// - Sender user joins the room
|
||||||
|
/// - Transfers some state events
|
||||||
|
/// - Moves local aliases
|
||||||
|
/// - Modifies old room power levels to prevent users from speaking
|
||||||
|
pub(crate) async fn upgrade_room_route(
|
||||||
|
State(services): State<crate::State>, body: Ruma<upgrade_room::v3::Request>,
|
||||||
|
) -> Result<upgrade_room::v3::Response> {
|
||||||
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
|
if !services
|
||||||
|
.globals
|
||||||
|
.supported_room_versions()
|
||||||
|
.contains(&body.new_version)
|
||||||
|
{
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
ErrorKind::UnsupportedRoomVersion,
|
||||||
|
"This server does not support that room version.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a replacement room
|
||||||
|
let replacement_room = RoomId::new(services.globals.server_name());
|
||||||
|
|
||||||
|
let _short_id = services
|
||||||
|
.rooms
|
||||||
|
.short
|
||||||
|
.get_or_create_shortroomid(&replacement_room)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
|
||||||
|
|
||||||
|
// Send a m.room.tombstone event to the old room to indicate that it is not
|
||||||
|
// intended to be used any further Fail if the sender does not have the required
|
||||||
|
// permissions
|
||||||
|
let tombstone_event_id = services
|
||||||
|
.rooms
|
||||||
|
.timeline
|
||||||
|
.build_and_append_pdu(
|
||||||
|
PduBuilder::state(
|
||||||
|
String::new(),
|
||||||
|
&RoomTombstoneEventContent {
|
||||||
|
body: "This room has been replaced".to_owned(),
|
||||||
|
replacement_room: replacement_room.clone(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
sender_user,
|
||||||
|
&body.room_id,
|
||||||
|
&state_lock,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Change lock to replacement room
|
||||||
|
drop(state_lock);
|
||||||
|
let state_lock = services.rooms.state.mutex.lock(&replacement_room).await;
|
||||||
|
|
||||||
|
// Get the old room creation event
|
||||||
|
let mut create_event_content: CanonicalJsonObject = services
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.room_state_get_content(&body.room_id, &StateEventType::RoomCreate, "")
|
||||||
|
.await
|
||||||
|
.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).to_owned(),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Send a m.room.create event containing a predecessor field and the applicable
|
||||||
|
// room_version
|
||||||
|
{
|
||||||
|
use RoomVersionId::*;
|
||||||
|
match body.new_version {
|
||||||
|
V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
|
||||||
|
create_event_content.insert(
|
||||||
|
"creator".into(),
|
||||||
|
json!(&sender_user).try_into().map_err(|e| {
|
||||||
|
info!("Error forming creation event: {e}");
|
||||||
|
Error::BadRequest(ErrorKind::BadJson, "Error forming creation event")
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
// "creator" key no longer exists in V11+ rooms
|
||||||
|
create_event_content.remove("creator");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
create_event_content.insert(
|
||||||
|
"room_version".into(),
|
||||||
|
json!(&body.new_version)
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?,
|
||||||
|
);
|
||||||
|
create_event_content.insert(
|
||||||
|
"predecessor".into(),
|
||||||
|
json!(predecessor)
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Validate creation event content
|
||||||
|
if serde_json::from_str::<CanonicalJsonObject>(
|
||||||
|
to_raw_value(&create_event_content)
|
||||||
|
.expect("Error forming creation event")
|
||||||
|
.get(),
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return Err(Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"));
|
||||||
|
}
|
||||||
|
|
||||||
|
services
|
||||||
|
.rooms
|
||||||
|
.timeline
|
||||||
|
.build_and_append_pdu(
|
||||||
|
PduBuilder {
|
||||||
|
event_type: TimelineEventType::RoomCreate,
|
||||||
|
content: to_raw_value(&create_event_content).expect("event is valid, we just created it"),
|
||||||
|
unsigned: None,
|
||||||
|
state_key: Some(String::new()),
|
||||||
|
redacts: None,
|
||||||
|
timestamp: None,
|
||||||
|
},
|
||||||
|
sender_user,
|
||||||
|
&replacement_room,
|
||||||
|
&state_lock,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Join the new room
|
||||||
|
services
|
||||||
|
.rooms
|
||||||
|
.timeline
|
||||||
|
.build_and_append_pdu(
|
||||||
|
PduBuilder {
|
||||||
|
event_type: TimelineEventType::RoomMember,
|
||||||
|
content: to_raw_value(&RoomMemberEventContent {
|
||||||
|
membership: 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,
|
||||||
|
})
|
||||||
|
.expect("event is valid, we just created it"),
|
||||||
|
unsigned: None,
|
||||||
|
state_key: Some(sender_user.to_string()),
|
||||||
|
redacts: None,
|
||||||
|
timestamp: None,
|
||||||
|
},
|
||||||
|
sender_user,
|
||||||
|
&replacement_room,
|
||||||
|
&state_lock,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Replicate transferable state events to the new room
|
||||||
|
for event_type in TRANSFERABLE_STATE_EVENTS {
|
||||||
|
let event_content = match services
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.room_state_get(&body.room_id, event_type, "")
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(v) => v.content.clone(),
|
||||||
|
Err(_) => continue, // Skipping missing events.
|
||||||
|
};
|
||||||
|
|
||||||
|
services
|
||||||
|
.rooms
|
||||||
|
.timeline
|
||||||
|
.build_and_append_pdu(
|
||||||
|
PduBuilder {
|
||||||
|
event_type: event_type.to_string().into(),
|
||||||
|
content: event_content,
|
||||||
|
state_key: Some(String::new()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
sender_user,
|
||||||
|
&replacement_room,
|
||||||
|
&state_lock,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Moves any local aliases to the new room
|
||||||
|
let mut local_aliases = services
|
||||||
|
.rooms
|
||||||
|
.alias
|
||||||
|
.local_aliases_for_room(&body.room_id)
|
||||||
|
.boxed();
|
||||||
|
|
||||||
|
while let Some(alias) = local_aliases.next().await {
|
||||||
|
services
|
||||||
|
.rooms
|
||||||
|
.alias
|
||||||
|
.remove_alias(alias, sender_user)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
services
|
||||||
|
.rooms
|
||||||
|
.alias
|
||||||
|
.set_alias(alias, &replacement_room, sender_user)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the old room power levels
|
||||||
|
let power_levels_event_content: RoomPowerLevelsEventContent = 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.")))?;
|
||||||
|
|
||||||
|
// Setting events_default and invite to the greater of 50 and users_default + 1
|
||||||
|
let new_level = max(
|
||||||
|
int!(50),
|
||||||
|
power_levels_event_content
|
||||||
|
.users_default
|
||||||
|
.checked_add(int!(1))
|
||||||
|
.ok_or_else(|| err!(Request(BadJson("users_default power levels event content is not valid"))))?,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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(
|
||||||
|
String::new(),
|
||||||
|
&RoomPowerLevelsEventContent {
|
||||||
|
events_default: new_level,
|
||||||
|
invite: new_level,
|
||||||
|
..power_levels_event_content
|
||||||
|
},
|
||||||
|
),
|
||||||
|
sender_user,
|
||||||
|
&body.room_id,
|
||||||
|
&state_lock,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
drop(state_lock);
|
||||||
|
|
||||||
|
// Return the replacement room id
|
||||||
|
Ok(upgrade_room::v3::Response {
|
||||||
|
replacement_room,
|
||||||
|
})
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue