use std::{cmp::max, collections::BTreeMap}; use ruma::{ api::client::{ error::ErrorKind, room::{self, aliases, create_room, get_room_event, upgrade_room}, }, events::{ room::{ canonical_alias::RoomCanonicalAliasEventContent, create::RoomCreateEventContent, guest_access::{GuestAccess, RoomGuestAccessEventContent}, history_visibility::{ HistoryVisibility, RoomHistoryVisibilityEventContent, }, join_rules::{JoinRule, RoomJoinRulesEventContent}, member::{MembershipState, RoomMemberEventContent}, name::RoomNameEventContent, power_levels::RoomPowerLevelsEventContent, tombstone::RoomTombstoneEventContent, topic::RoomTopicEventContent, }, StateEventType, TimelineEventType, }, int, serde::JsonObject, CanonicalJsonObject, OwnedRoomAliasId, RoomAliasId, RoomId, }; use serde_json::{json, value::to_raw_value}; use tracing::{info, warn}; use crate::{ api::client_server::invite_helper, service::pdu::PduBuilder, services, utils::room_version::RoomVersion, Ar, Error, Ra, Result, }; /// # `POST /_matrix/client/r0/createRoom` /// /// Creates a new room. /// /// - Room ID is randomly generated /// - Create alias if `room_alias_name` is set /// - Send create event /// - Join sender user /// - Send power levels event /// - Send canonical room alias /// - Send join rules /// - Send history visibility /// - Send guest access /// - Send events listed in initial state /// - Send events implied by `name` and `topic` /// - Send invite events #[allow(clippy::too_many_lines)] pub(crate) async fn create_room_route( body: Ar, ) -> Result> { use create_room::v3::RoomPreset; let sender_user = body.sender_user.as_deref().expect("user is authenticated"); let room_id = RoomId::new(services().globals.server_name()); services().rooms.short.get_or_create_shortroomid(&room_id)?; let room_token = services().globals.roomid_mutex_state.lock_key(room_id.clone()).await; if !services().globals.allow_room_creation() && body.appservice_info.is_none() && !services().users.is_admin(sender_user)? { return Err(Error::BadRequest( ErrorKind::forbidden(), "Room creation has been disabled.", )); } let alias: Option = body.room_alias_name.as_ref().map_or(Ok(None), |localpart| { // TODO: Check for invalid characters and maximum length let alias = RoomAliasId::parse(format!( "#{}:{}", localpart, services().globals.server_name() )) .map_err(|_| { Error::BadRequest(ErrorKind::InvalidParam, "Invalid alias.") })?; if services().rooms.alias.resolve_local_alias(&alias)?.is_some() { Err(Error::BadRequest( ErrorKind::RoomInUse, "Room alias already exists.", )) } else { Ok(Some(alias)) } })?; if let Some(alias) = &alias { if let Some(info) = &body.appservice_info { if !info.aliases.is_match(alias.as_str()) { return Err(Error::BadRequest( ErrorKind::Exclusive, "Room alias is not in namespace.", )); } } else if services().appservice.is_exclusive_alias(alias).await { return Err(Error::BadRequest( ErrorKind::Exclusive, "Room alias reserved by appservice.", )); } } let room_version_id = match body.room_version.clone() { Some(room_version) => { if services() .globals .supported_room_versions() .contains(&room_version) { room_version } else { return Err(Error::BadRequest( ErrorKind::UnsupportedRoomVersion, "This server does not support that room version.", )); } } None => services().globals.default_room_version(), }; let room_version = RoomVersion::try_from(&room_version_id)?; let content = if let Some(content) = &body.creation_content { let mut content = content .deserialize_as::() .expect("Invalid creation content"); if room_version.create_event_creator_prop { content.insert( "creator".into(), json!(&sender_user).try_into().map_err(|_| { Error::BadRequest( ErrorKind::BadJson, "Invalid creation content", ) })?, ); } content.insert( "room_version".into(), json!(room_version_id.as_str()).try_into().map_err(|_| { Error::BadRequest( ErrorKind::BadJson, "Invalid creation content", ) })?, ); content } else { let content = if room_version.create_event_creator_prop { RoomCreateEventContent::new_v1(sender_user.to_owned()) } else { RoomCreateEventContent::new_v11() }; let mut content = serde_json::from_str::( to_raw_value(&content) .map_err(|_| { Error::BadRequest( ErrorKind::BadJson, "Invalid creation content", ) })? .get(), ) .unwrap(); content.insert( "room_version".into(), json!(room_version_id.as_str()).try_into().map_err(|_| { Error::BadRequest( ErrorKind::BadJson, "Invalid creation content", ) })?, ); content }; // Validate creation content let de_result = serde_json::from_str::( to_raw_value(&content).expect("Invalid creation content").get(), ); if de_result.is_err() { return Err(Error::BadRequest( ErrorKind::BadJson, "Invalid creation content", )); } // 1. The room create event services() .rooms .timeline .build_and_append_pdu( PduBuilder { event_type: TimelineEventType::RoomCreate, content: to_raw_value(&content) .expect("event is valid, we just created it"), unsigned: None, state_key: Some(String::new()), redacts: None, }, sender_user, &room_token, ) .await?; // 2. Let the room creator join 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)?, avatar_url: services().users.avatar_url(sender_user)?, is_direct: Some(body.is_direct), third_party_invite: None, blurhash: services().users.blurhash(sender_user)?, 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, }, sender_user, &room_token, ) .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::Private => RoomPreset::PrivateChat, room::Visibility::Public => RoomPreset::PublicChat, _ => unimplemented!("unknown room visibility"), }); let mut users = BTreeMap::new(); users.insert(sender_user.to_owned(), int!(100)); if preset == RoomPreset::TrustedPrivateChat { for invite_ in &body.invite { users.insert(invite_.clone(), int!(100)); } } let mut power_levels_content = serde_json::to_value(RoomPowerLevelsEventContent { users, ..Default::default() }) .expect("event is valid, we just created it"); if let Some(power_level_content_override) = &body.power_level_content_override { let json: JsonObject = serde_json::from_str(power_level_content_override.json().get()) .map_err(|_| { Error::BadRequest( ErrorKind::BadJson, "Invalid power_level_content_override.", ) })?; for (key, value) in json { power_levels_content[key] = value; } } services() .rooms .timeline .build_and_append_pdu( PduBuilder { event_type: TimelineEventType::RoomPowerLevels, content: to_raw_value(&power_levels_content) .expect("to_raw_value always works on serde_json::Value"), unsigned: None, state_key: Some(String::new()), redacts: None, }, sender_user, &room_token, ) .await?; // 4. Canonical room alias if let Some(room_alias_id) = &alias { services() .rooms .timeline .build_and_append_pdu( PduBuilder { event_type: TimelineEventType::RoomCanonicalAlias, content: to_raw_value(&RoomCanonicalAliasEventContent { alias: Some(room_alias_id.to_owned()), alt_aliases: vec![], }) .expect("We checked that alias earlier, it must be fine"), unsigned: None, state_key: Some(String::new()), redacts: None, }, sender_user, &room_token, ) .await?; } // 5. Events set by preset // 5.1 Join Rules services() .rooms .timeline .build_and_append_pdu( PduBuilder { event_type: TimelineEventType::RoomJoinRules, content: to_raw_value(&RoomJoinRulesEventContent::new( match preset { RoomPreset::PublicChat => JoinRule::Public, // according to spec "invite" is the default _ => JoinRule::Invite, }, )) .expect("event is valid, we just created it"), unsigned: None, state_key: Some(String::new()), redacts: None, }, sender_user, &room_token, ) .await?; // 5.2 History Visibility services() .rooms .timeline .build_and_append_pdu( PduBuilder { event_type: TimelineEventType::RoomHistoryVisibility, content: to_raw_value(&RoomHistoryVisibilityEventContent::new( HistoryVisibility::Shared, )) .expect("event is valid, we just created it"), unsigned: None, state_key: Some(String::new()), redacts: None, }, sender_user, &room_token, ) .await?; // 5.3 Guest Access services() .rooms .timeline .build_and_append_pdu( PduBuilder { event_type: TimelineEventType::RoomGuestAccess, content: to_raw_value(&RoomGuestAccessEventContent::new( match preset { RoomPreset::PublicChat => GuestAccess::Forbidden, _ => GuestAccess::CanJoin, }, )) .expect("event is valid, we just created it"), unsigned: None, state_key: Some(String::new()), redacts: None, }, sender_user, &room_token, ) .await?; // 6. Events listed in initial_state for event in &body.initial_state { let mut pdu_builder = event.deserialize_as::().map_err(|error| { warn!(%error, "Invalid initial state event"); Error::BadRequest( ErrorKind::InvalidParam, "Invalid initial state event.", ) })?; // Implicit state key defaults to "" pdu_builder.state_key.get_or_insert_with(String::new); // Silently skip encryption events if they are not allowed if pdu_builder.event_type == TimelineEventType::RoomEncryption && !services().globals.allow_encryption() { continue; } services() .rooms .timeline .build_and_append_pdu(pdu_builder, sender_user, &room_token) .await?; } // 7. Events implied by name and topic if let Some(name) = &body.name { services() .rooms .timeline .build_and_append_pdu( PduBuilder { event_type: TimelineEventType::RoomName, content: to_raw_value(&RoomNameEventContent::new( name.clone(), )) .expect("event is valid, we just created it"), unsigned: None, state_key: Some(String::new()), redacts: None, }, sender_user, &room_token, ) .await?; } if let Some(topic) = &body.topic { services() .rooms .timeline .build_and_append_pdu( PduBuilder { event_type: TimelineEventType::RoomTopic, content: to_raw_value(&RoomTopicEventContent { topic: topic.clone(), }) .expect("event is valid, we just created it"), unsigned: None, state_key: Some(String::new()), redacts: None, }, sender_user, &room_token, ) .await?; } // 8. Events implied by invite (and TODO: invite_3pid) drop(room_token); for user_id in &body.invite { if let Err(error) = invite_helper(sender_user, user_id, &room_id, None, body.is_direct) .await { warn!(%error, "Invite helper failed"); }; } // Homeserver specific stuff if let Some(alias) = alias { services().rooms.alias.set_alias(&alias, &room_id, sender_user)?; } if body.visibility == room::Visibility::Public { services().rooms.directory.set_public(&room_id)?; } info!(user_id = %sender_user, room_id = %room_id, "User created a room"); Ok(Ra(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( body: Ar, ) -> Result> { let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let event = services().rooms.timeline.get_pdu(&body.event_id)?.ok_or_else( || { warn!(event_id = %body.event_id, "Event not found"); Error::BadRequest(ErrorKind::NotFound, "Event not found.") }, )?; if !services().rooms.state_accessor.user_can_see_event( sender_user, &event.room_id, &body.event_id, )? { return Err(Error::BadRequest( ErrorKind::forbidden(), "You don't have permission to view this event.", )); } let mut event = (*event).clone(); event.add_age()?; Ok(Ra(get_room_event::v3::Response { event: event.to_room_event(), })) } /// # `GET /_matrix/client/r0/rooms/{roomId}/aliases` /// /// Lists all aliases of the room. /// /// - Only users joined to the room are allowed to call this TODO: Allow any /// user to call it if `history_visibility` is world readable pub(crate) async fn get_room_aliases_route( body: Ar, ) -> Result> { let sender_user = body.sender_user.as_ref().expect("user is authenticated"); if !services().rooms.state_cache.is_joined(sender_user, &body.room_id)? { return Err(Error::BadRequest( ErrorKind::forbidden(), "You don't have permission to view this room.", )); } Ok(Ra(aliases::v3::Response { aliases: services() .rooms .alias .local_aliases_for_room(&body.room_id) .filter_map(Result::ok) .collect(), })) } /// # `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 #[allow(clippy::too_many_lines)] pub(crate) async fn upgrade_room_route( body: Ar, ) -> Result> { 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.", )); } let new_version = RoomVersion::try_from(&body.new_version)?; // Create a replacement room let replacement_room = RoomId::new(services().globals.server_name()); services().rooms.short.get_or_create_shortroomid(&replacement_room)?; let original_room_token = services() .globals .roomid_mutex_state .lock_key(body.room_id.clone()) .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 { event_type: TimelineEventType::RoomTombstone, content: to_raw_value(&RoomTombstoneEventContent { body: "This room has been replaced".to_owned(), replacement_room: replacement_room.clone(), }) .expect("event is valid, we just created it"), unsigned: None, state_key: Some(String::new()), redacts: None, }, sender_user, &original_room_token, ) .await?; // Change lock to replacement room let replacement_room_token = services() .globals .roomid_mutex_state .lock_key(replacement_room.clone()) .await; // Get the old room creation event let mut create_event_content = serde_json::from_str::( services() .rooms .state_accessor .room_state_get(&body.room_id, &StateEventType::RoomCreate, "")? .ok_or_else(|| { Error::bad_database("Found room without m.room.create event.") })? .content .get(), ) .map_err(|_| Error::bad_database("Invalid room event in database."))?; // 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 if new_version.create_event_creator_prop { create_event_content.insert( "creator".into(), json!(&sender_user).try_into().map_err(|_| { Error::BadRequest( ErrorKind::BadJson, "Error forming creation event", ) })?, ); } else { 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 let de_result = serde_json::from_str::( to_raw_value(&create_event_content) .expect("Error forming creation event") .get(), ); if de_result.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, }, sender_user, &replacement_room_token, ) .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)?, avatar_url: services().users.avatar_url(sender_user)?, is_direct: None, third_party_invite: None, blurhash: services().users.blurhash(sender_user)?, 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, }, sender_user, &replacement_room_token, ) .await?; // Recommended transferable state events list from the specs let transferable_state_events = vec![ StateEventType::RoomServerAcl, StateEventType::RoomEncryption, StateEventType::RoomName, StateEventType::RoomAvatar, StateEventType::RoomTopic, StateEventType::RoomGuestAccess, StateEventType::RoomHistoryVisibility, StateEventType::RoomJoinRules, StateEventType::RoomPowerLevels, ]; // 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, "")? { Some(v) => v.content.clone(), // Skipping missing events. None => continue, }; services() .rooms .timeline .build_and_append_pdu( PduBuilder { event_type: event_type.to_string().into(), content: event_content, unsigned: None, state_key: Some(String::new()), redacts: None, }, sender_user, &replacement_room_token, ) .await?; } // Moves any local aliases to the new room let mut old_canonical_alias: RoomCanonicalAliasEventContent = services() .rooms .state_accessor .room_state_get(&body.room_id, &StateEventType::RoomCanonicalAlias, "")? .and_then(|event| { serde_json::from_str(event.content.get()) .inspect_err(|_| { Error::bad_database("invalid m.room.canonical_alias event"); }) .ok() }) .unwrap_or_default(); let mut new_canonical_alias = RoomCanonicalAliasEventContent::default(); for alias in services() .rooms .alias .local_aliases_for_room(&body.room_id) .filter_map(Result::ok) { services().rooms.alias.set_alias( &alias, &replacement_room, sender_user, )?; if old_canonical_alias.alias.as_ref() == Some(&alias) { new_canonical_alias.alias = Some(alias.clone()); old_canonical_alias.alias = None; } let mut is_alt_alias = false; old_canonical_alias.alt_aliases.retain(|a| { let equal = a == &alias; is_alt_alias |= equal; !equal }); if is_alt_alias { new_canonical_alias.alt_aliases.push(alias); } } // Send updated events to both rooms if we moved any canonical aliases let new_canonical_alias_empty = new_canonical_alias.alias.is_none() && new_canonical_alias.alt_aliases.is_empty(); if !new_canonical_alias_empty { if let Err(error) = services() .rooms .timeline .build_and_append_pdu( PduBuilder { event_type: TimelineEventType::RoomCanonicalAlias, content: to_raw_value(&old_canonical_alias) .expect("event is valid"), unsigned: None, state_key: Some(String::new()), redacts: None, }, sender_user, &original_room_token, ) .await { // The sender may not have had permission to send // m.room.canonical_alias in the old room. Don't treat // this as fatal. warn!( %error, "Failed to remove local canonical aliases from old room" ); } services() .rooms .timeline .build_and_append_pdu( PduBuilder { event_type: TimelineEventType::RoomCanonicalAlias, content: to_raw_value(&new_canonical_alias) .expect("event is valid"), unsigned: None, state_key: Some(String::new()), redacts: None, }, sender_user, &replacement_room_token, ) .await?; } // Get the old room power levels let mut power_levels_event_content: RoomPowerLevelsEventContent = serde_json::from_str( services() .rooms .state_accessor .room_state_get( &body.room_id, &StateEventType::RoomPowerLevels, "", )? .ok_or_else(|| { Error::bad_database( "Found room without m.room.create event.", ) })? .content .get(), ) .map_err(|_| Error::bad_database("Invalid room event in database."))?; // 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 + int!(1)); power_levels_event_content.events_default = new_level; power_levels_event_content.invite = new_level; // Modify the power levels in the old room to prevent sending of events and // inviting new users let _ = services() .rooms .timeline .build_and_append_pdu( PduBuilder { event_type: TimelineEventType::RoomPowerLevels, content: to_raw_value(&power_levels_event_content) .expect("event is valid, we just created it"), unsigned: None, state_key: Some(String::new()), redacts: None, }, sender_user, &original_room_token, ) .await?; // Return the replacement room id Ok(Ra(upgrade_room::v3::Response { replacement_room, })) }