Skip to content
Snippets Groups Projects
room.rs 23.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • Jonathan de Jong's avatar
    Jonathan de Jong committed
    use crate::{
    
    Timo Kösters's avatar
    Timo Kösters committed
        api::client_server::invite_helper, service::pdu::PduBuilder, services, Error, Result, Ruma,
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
    };
    
    use ruma::{
        api::client::{
            error::ErrorKind,
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
            room::{self, aliases, create_room, get_room_event, upgrade_room},
    
        },
        events::{
    
    Jonas Platte's avatar
    Jonas Platte committed
            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,
            },
    
    Timo Kösters's avatar
    Timo Kösters committed
            RoomEventType, StateEventType,
    
        serde::{CanonicalJsonObject, JsonObject},
    
    use serde_json::{json, value::to_raw_value};
    
    use std::{cmp::max, collections::BTreeMap, sync::Arc};
    
    use tracing::{info, warn};
    
    /// # `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
    
    pub async fn create_room_route(
    
    Timo Kösters's avatar
    Timo Kösters committed
        body: Ruma<create_room::v3::IncomingRequest>,
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
    ) -> Result<create_room::v3::Response> {
        use create_room::v3::RoomPreset;
    
    
        let sender_user = body.sender_user.as_ref().expect("user is authenticated");
    
        let room_id = RoomId::new(services().globals.server_name());
    
    Timo Kösters's avatar
    Timo Kösters committed
        services().rooms.short.get_or_create_shortroomid(&room_id)?;
    
    Timo Kösters's avatar
    Timo Kösters committed
        let mutex_state = Arc::clone(
    
    Timo Kösters's avatar
    Timo Kösters committed
            services()
                .globals
    
    Timo Kösters's avatar
    Timo Kösters committed
                .roomid_mutex_state
    
    Timo Kösters's avatar
    Timo Kösters committed
                .write()
                .unwrap()
                .entry(room_id.clone())
                .or_default(),
        );
    
    Timo Kösters's avatar
    Timo Kösters committed
        let state_lock = mutex_state.lock().await;
    
    Timo Kösters's avatar
    Timo Kösters committed
    
    
        if !services().globals.allow_room_creation()
    
            && !services().users.is_admin(sender_user)?
    
        {
            return Err(Error::BadRequest(
                ErrorKind::Forbidden,
                "Room creation has been disabled.",
            ));
        }
    
    
    Jonas Platte's avatar
    Jonas Platte committed
        let alias: Option<Box<RoomAliasId>> =
    
            body.room_alias_name
                .as_ref()
                .map_or(Ok(None), |localpart| {
                    // TODO: Check for invalid characters and maximum length
    
    Timo Kösters's avatar
    Timo Kösters committed
                    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))
                    }
                })?;
    
        let room_version = match body.room_version.clone() {
    
            Some(room_version) => {
    
    Timo Kösters's avatar
    Timo Kösters committed
                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 content = match &body.creation_content {
            Some(content) => {
                let mut content = content
                    .deserialize_as::<CanonicalJsonObject>()
                    .expect("Invalid creation content");
                content.insert(
                    "creator".into(),
    
                    json!(&sender_user).try_into().map_err(|_| {
                        Error::BadRequest(ErrorKind::BadJson, "Invalid creation content")
                    })?,
    
                    json!(room_version.as_str()).try_into().map_err(|_| {
                        Error::BadRequest(ErrorKind::BadJson, "Invalid creation content")
                    })?,
    
                let mut content = serde_json::from_str::<CanonicalJsonObject>(
                    to_raw_value(&RoomCreateEventContent::new(sender_user.clone()))
                        .map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid creation content"))?
                        .get(),
    
                )
                .unwrap();
                content.insert(
                    "room_version".into(),
    
                    json!(room_version.as_str()).try_into().map_err(|_| {
                        Error::BadRequest(ErrorKind::BadJson, "Invalid creation content")
                    })?,
    
        let de_result = serde_json::from_str::<CanonicalJsonObject>(
            to_raw_value(&content)
                .expect("Invalid creation content")
                .get(),
        );
    
    
    Jonas Platte's avatar
    Jonas Platte committed
        if de_result.is_err() {
    
            return Err(Error::BadRequest(
                ErrorKind::BadJson,
                "Invalid creation content",
            ));
        }
    
        // 1. The room create event
    
    Timo Kösters's avatar
    Timo Kösters committed
        services().rooms.timeline.build_and_append_pdu(
    
    Timo Kösters's avatar
    Timo Kösters committed
            PduBuilder {
    
    Timo Kösters's avatar
    Timo Kösters committed
                event_type: RoomEventType::RoomCreate,
    
    Jonas Platte's avatar
    Jonas Platte committed
                content: to_raw_value(&content).expect("event is valid, we just created it"),
    
    Timo Kösters's avatar
    Timo Kösters committed
                unsigned: None,
                state_key: Some("".to_owned()),
                redacts: None,
            },
    
    Timo Kösters's avatar
    Timo Kösters committed
            &room_id,
    
    Timo Kösters's avatar
    Timo Kösters committed
            &state_lock,
    
    Timo Kösters's avatar
    Timo Kösters committed
        )?;
    
    
        // 2. Let the room creator join
    
    Timo Kösters's avatar
    Timo Kösters committed
        services().rooms.timeline.build_and_append_pdu(
    
    Timo Kösters's avatar
    Timo Kösters committed
            PduBuilder {
    
    Timo Kösters's avatar
    Timo Kösters committed
                event_type: RoomEventType::RoomMember,
    
    Jonas Platte's avatar
    Jonas Platte committed
                content: to_raw_value(&RoomMemberEventContent {
                    membership: MembershipState::Join,
    
                    displayname: services().users.displayname(sender_user)?,
                    avatar_url: services().users.avatar_url(sender_user)?,
    
    Timo Kösters's avatar
    Timo Kösters committed
                    is_direct: Some(body.is_direct),
                    third_party_invite: None,
    
                    blurhash: services().users.blurhash(sender_user)?,
    
                    join_authorized_via_users_server: None,
    
    Timo Kösters's avatar
    Timo Kösters committed
                })
                .expect("event is valid, we just created it"),
                unsigned: None,
    
                state_key: Some(sender_user.to_string()),
    
    Timo Kösters's avatar
    Timo Kösters committed
                redacts: None,
            },
    
    Timo Kösters's avatar
    Timo Kösters committed
            &room_id,
    
    Timo Kösters's avatar
    Timo Kösters committed
            &state_lock,
    
    Timo Kösters's avatar
    Timo Kösters committed
        )?;
    
    
        // 3. Power levels
    
    
        // Figure out preset. We need it for preset specific events
        let preset = body
            .preset
            .clone()
            .unwrap_or_else(|| match &body.visibility {
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
                room::Visibility::Private => RoomPreset::PrivateChat,
                room::Visibility::Public => RoomPreset::PublicChat,
                _ => RoomPreset::PrivateChat, // Room visibility should not be custom
    
        let mut users = BTreeMap::new();
    
        users.insert(sender_user.clone(), int!(100));
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
        if preset == RoomPreset::TrustedPrivateChat {
    
            for invite_ in &body.invite {
    
                users.insert(invite_.clone(), int!(100));
    
    Jonas Platte's avatar
    Jonas Platte committed
        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 {
    
    Jonas Platte's avatar
    Jonas Platte committed
            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;
            }
        }
    
    
    Timo Kösters's avatar
    Timo Kösters committed
        services().rooms.timeline.build_and_append_pdu(
    
    Timo Kösters's avatar
    Timo Kösters committed
            PduBuilder {
    
    Timo Kösters's avatar
    Timo Kösters committed
                event_type: RoomEventType::RoomPowerLevels,
    
    Jonas Platte's avatar
    Jonas Platte committed
                content: to_raw_value(&power_levels_content)
                    .expect("to_raw_value always works on serde_json::Value"),
    
    Timo Kösters's avatar
    Timo Kösters committed
                unsigned: None,
                state_key: Some("".to_owned()),
                redacts: None,
            },
    
    Timo Kösters's avatar
    Timo Kösters committed
            &room_id,
    
    Timo Kösters's avatar
    Timo Kösters committed
            &state_lock,
    
    Timo Kösters's avatar
    Timo Kösters committed
        )?;
    
        // 4. Canonical room alias
        if let Some(room_alias_id) = &alias {
    
    Timo Kösters's avatar
    Timo Kösters committed
            services().rooms.timeline.build_and_append_pdu(
    
    Timo Kösters's avatar
    Timo Kösters committed
                    event_type: RoomEventType::RoomCanonicalAlias,
    
    Jonas Platte's avatar
    Jonas Platte committed
                    content: to_raw_value(&RoomCanonicalAliasEventContent {
    
    Jonas Platte's avatar
    Jonas Platte committed
                        alias: Some(room_alias_id.to_owned()),
    
    Jonas Platte's avatar
    Jonas Platte committed
                        alt_aliases: vec![],
                    })
    
                    .expect("We checked that alias earlier, it must be fine"),
                    unsigned: None,
                    state_key: Some("".to_owned()),
                    redacts: None,
                },
    
        }
    
        // 5. Events set by preset
    
    Timo Kösters's avatar
    Timo Kösters committed
        services().rooms.timeline.build_and_append_pdu(
    
    Timo Kösters's avatar
    Timo Kösters committed
            PduBuilder {
    
    Timo Kösters's avatar
    Timo Kösters committed
                event_type: RoomEventType::RoomJoinRules,
    
    Jonas Platte's avatar
    Jonas Platte committed
                content: to_raw_value(&RoomJoinRulesEventContent::new(match preset {
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
                    RoomPreset::PublicChat => JoinRule::Public,
    
    Timo Kösters's avatar
    Timo Kösters committed
                    // according to spec "invite" is the default
    
    Jonas Platte's avatar
    Jonas Platte committed
                    _ => JoinRule::Invite,
                }))
                .expect("event is valid, we just created it"),
    
    Timo Kösters's avatar
    Timo Kösters committed
                unsigned: None,
                state_key: Some("".to_owned()),
                redacts: None,
            },
    
    Timo Kösters's avatar
    Timo Kösters committed
            &room_id,
    
    Timo Kösters's avatar
    Timo Kösters committed
            &state_lock,
    
    Timo Kösters's avatar
    Timo Kösters committed
        )?;
    
    
        // 5.2 History Visibility
    
    Timo Kösters's avatar
    Timo Kösters committed
        services().rooms.timeline.build_and_append_pdu(
    
    Timo Kösters's avatar
    Timo Kösters committed
            PduBuilder {
    
    Timo Kösters's avatar
    Timo Kösters committed
                event_type: RoomEventType::RoomHistoryVisibility,
    
    Jonas Platte's avatar
    Jonas Platte committed
                content: to_raw_value(&RoomHistoryVisibilityEventContent::new(
                    HistoryVisibility::Shared,
    
    Timo Kösters's avatar
    Timo Kösters committed
                ))
                .expect("event is valid, we just created it"),
                unsigned: None,
                state_key: Some("".to_owned()),
                redacts: None,
            },
    
    Timo Kösters's avatar
    Timo Kösters committed
            &room_id,
    
    Timo Kösters's avatar
    Timo Kösters committed
            &state_lock,
    
    Timo Kösters's avatar
    Timo Kösters committed
        )?;
    
    
    Timo Kösters's avatar
    Timo Kösters committed
        services().rooms.timeline.build_and_append_pdu(
    
    Timo Kösters's avatar
    Timo Kösters committed
            PduBuilder {
    
    Timo Kösters's avatar
    Timo Kösters committed
                event_type: RoomEventType::RoomGuestAccess,
    
    Jonas Platte's avatar
    Jonas Platte committed
                content: to_raw_value(&RoomGuestAccessEventContent::new(match preset {
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
                    RoomPreset::PublicChat => GuestAccess::Forbidden,
    
    Jonas Platte's avatar
    Jonas Platte committed
                    _ => GuestAccess::CanJoin,
                }))
                .expect("event is valid, we just created it"),
    
    Timo Kösters's avatar
    Timo Kösters committed
                unsigned: None,
                state_key: Some("".to_owned()),
                redacts: None,
            },
    
    Timo Kösters's avatar
    Timo Kösters committed
            &room_id,
    
    Timo Kösters's avatar
    Timo Kösters committed
            &state_lock,
    
    Timo Kösters's avatar
    Timo Kösters committed
        )?;
    
    
        // 6. Events listed in initial_state
    
    Timo Kösters's avatar
    Timo Kösters committed
        for event in &body.initial_state {
    
            let mut pdu_builder = event.deserialize_as::<PduBuilder>().map_err(|e| {
    
                warn!("Invalid initial state event: {:?}", e);
    
                Error::BadRequest(ErrorKind::InvalidParam, "Invalid initial state event.")
    
            })?;
    
            // Implicit state key defaults to ""
            pdu_builder.state_key.get_or_insert_with(|| "".to_owned());
    
    Timo Kösters's avatar
    Timo Kösters committed
    
            // Silently skip encryption events if they are not allowed
    
    Timo Kösters's avatar
    Timo Kösters committed
            if pdu_builder.event_type == RoomEventType::RoomEncryption
                && !services().globals.allow_encryption()
    
    Timo Kösters's avatar
    Timo Kösters committed
            {
    
    Timo Kösters's avatar
    Timo Kösters committed
                continue;
            }
    
    
    Timo Kösters's avatar
    Timo Kösters committed
            services().rooms.timeline.build_and_append_pdu(
                pdu_builder,
                sender_user,
                &room_id,
                &state_lock,
            )?;
    
    Timo Kösters's avatar
    Timo Kösters committed
        }
    
        // 7. Events implied by name and topic
    
    Timo Kösters's avatar
    Timo Kösters committed
        if let Some(name) = &body.name {
    
    Timo Kösters's avatar
    Timo Kösters committed
            services().rooms.timeline.build_and_append_pdu(
    
                PduBuilder {
    
    Timo Kösters's avatar
    Timo Kösters committed
                    event_type: RoomEventType::RoomName,
    
    Jonas Platte's avatar
    Jonas Platte committed
                    content: to_raw_value(&RoomNameEventContent::new(Some(name.clone())))
    
    Timo Kösters's avatar
    Timo Kösters committed
                        .expect("event is valid, we just created it"),
    
                    unsigned: None,
                    state_key: Some("".to_owned()),
                    redacts: None,
                },
    
                &room_id,
    
    Timo Kösters's avatar
    Timo Kösters committed
                &state_lock,
    
    Timo Kösters's avatar
    Timo Kösters committed
            )?;
        }
    
    Timo Kösters's avatar
    Timo Kösters committed
        if let Some(topic) = &body.topic {
    
    Timo Kösters's avatar
    Timo Kösters committed
            services().rooms.timeline.build_and_append_pdu(
    
                PduBuilder {
    
    Timo Kösters's avatar
    Timo Kösters committed
                    event_type: RoomEventType::RoomTopic,
    
    Jonas Platte's avatar
    Jonas Platte committed
                    content: to_raw_value(&RoomTopicEventContent {
    
    Timo Kösters's avatar
    Timo Kösters committed
                        topic: topic.clone(),
                    })
                    .expect("event is valid, we just created it"),
    
                    unsigned: None,
                    state_key: Some("".to_owned()),
                    redacts: None,
                },
    
                &room_id,
    
    Timo Kösters's avatar
    Timo Kösters committed
                &state_lock,
    
    Timo Kösters's avatar
    Timo Kösters committed
            )?;
    
        // 8. Events implied by invite (and TODO: invite_3pid)
    
    Timo Kösters's avatar
    Timo Kösters committed
        drop(state_lock);
    
        for user_id in &body.invite {
    
            let _ = invite_helper(sender_user, user_id, &room_id, body.is_direct).await;
    
        }
    
        // Homeserver specific stuff
        if let Some(alias) = alias {
    
    Timo Kösters's avatar
    Timo Kösters committed
            services().rooms.alias.set_alias(&alias, &room_id)?;
    
    Timo Kösters's avatar
    Timo Kösters committed
        if body.visibility == room::Visibility::Public {
    
    Timo Kösters's avatar
    Timo Kösters committed
            services().rooms.directory.set_public(&room_id)?;
    
        info!("{} created a room", sender_user);
    
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
        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 async fn get_room_event_route(
    
    Timo Kösters's avatar
    Timo Kösters committed
        body: Ruma<get_room_event::v3::IncomingRequest>,
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
    ) -> Result<get_room_event::v3::Response> {
    
        let sender_user = body.sender_user.as_ref().expect("user is authenticated");
    
    Timo Kösters's avatar
    Timo Kösters committed
        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.",
            ));
        }
    
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
        Ok(get_room_event::v3::Response {
    
            event: services()
    
    Timo Kösters's avatar
    Timo Kösters committed
                .timeline
    
                .get_pdu(&body.event_id)?
                .ok_or(Error::BadRequest(ErrorKind::NotFound, "Event not found."))?
                .to_room_event(),
    
    Faelar's avatar
    Faelar committed
    
    
    /// # `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 async fn get_room_aliases_route(
    
    Timo Kösters's avatar
    Timo Kösters committed
        body: Ruma<aliases::v3::IncomingRequest>,
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
    ) -> Result<aliases::v3::Response> {
    
        let sender_user = body.sender_user.as_ref().expect("user is authenticated");
    
    
    Timo Kösters's avatar
    Timo Kösters committed
        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.",
            ));
        }
    
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
        Ok(aliases::v3::Response {
    
            aliases: services()
    
                .rooms
    
    Timo Kösters's avatar
    Timo Kösters committed
                .alias
                .local_aliases_for_room(&body.room_id)
    
                .filter_map(|a| a.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
    
    pub async fn upgrade_room_route(
    
    Timo Kösters's avatar
    Timo Kösters committed
        body: Ruma<upgrade_room::v3::IncomingRequest>,
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
    ) -> Result<upgrade_room::v3::Response> {
    
        let sender_user = body.sender_user.as_ref().expect("user is authenticated");
    
    Faelar's avatar
    Faelar committed
    
    
    Timo Kösters's avatar
    Timo Kösters committed
        if !services()
            .globals
            .supported_room_versions()
            .contains(&body.new_version)
        {
    
    Faelar's avatar
    Faelar committed
            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());
    
    Timo Kösters's avatar
    Timo Kösters committed
        services()
            .rooms
            .short
            .get_or_create_shortroomid(&replacement_room)?;
    
    Faelar's avatar
    Faelar committed
    
    
    Timo Kösters's avatar
    Timo Kösters committed
        let mutex_state = Arc::clone(
    
    Timo Kösters's avatar
    Timo Kösters committed
            services()
                .globals
    
    Timo Kösters's avatar
    Timo Kösters committed
                .roomid_mutex_state
    
    Timo Kösters's avatar
    Timo Kösters committed
                .write()
                .unwrap()
                .entry(body.room_id.clone())
                .or_default(),
        );
    
    Timo Kösters's avatar
    Timo Kösters committed
        let state_lock = mutex_state.lock().await;
    
    Faelar's avatar
    Faelar committed
        // 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
    
    Timo Kösters's avatar
    Timo Kösters committed
        let tombstone_event_id = services().rooms.timeline.build_and_append_pdu(
    
    Timo Kösters's avatar
    Timo Kösters committed
            PduBuilder {
    
    Timo Kösters's avatar
    Timo Kösters committed
                event_type: RoomEventType::RoomTombstone,
    
    Jonas Platte's avatar
    Jonas Platte committed
                content: to_raw_value(&RoomTombstoneEventContent {
    
                    body: "This room has been replaced".to_owned(),
    
    Timo Kösters's avatar
    Timo Kösters committed
                    replacement_room: replacement_room.clone(),
                })
                .expect("event is valid, we just created it"),
                unsigned: None,
                state_key: Some("".to_owned()),
                redacts: None,
            },
    
            sender_user,
    
    Timo Kösters's avatar
    Timo Kösters committed
            &body.room_id,
    
    Timo Kösters's avatar
    Timo Kösters committed
            &state_lock,
    
    Timo Kösters's avatar
    Timo Kösters committed
        )?;
    
    Faelar's avatar
    Faelar committed
    
    
    Timo Kösters's avatar
    Timo Kösters committed
        // Change lock to replacement room
        drop(state_lock);
        let mutex_state = Arc::clone(
    
    Timo Kösters's avatar
    Timo Kösters committed
            services()
                .globals
    
    Timo Kösters's avatar
    Timo Kösters committed
                .roomid_mutex_state
                .write()
                .unwrap()
                .entry(replacement_room.clone())
                .or_default(),
        );
        let state_lock = mutex_state.lock().await;
    
    
        // Get the old room creation event
        let mut create_event_content = serde_json::from_str::<CanonicalJsonObject>(
    
    Timo Kösters's avatar
    Timo Kösters committed
            services()
                .rooms
    
    Timo Kösters's avatar
    Timo Kösters committed
                .state_accessor
    
    Timo Kösters's avatar
    Timo Kösters committed
                .room_state_get(&body.room_id, &StateEventType::RoomCreate, "")?
    
    Faelar's avatar
    Faelar committed
                .ok_or_else(|| Error::bad_database("Found room without m.room.create event."))?
    
    Jonas Platte's avatar
    Jonas Platte committed
                .get(),
    
    Faelar's avatar
    Faelar committed
        )
    
        .map_err(|_| Error::bad_database("Invalid room event in database."))?;
    
    Faelar's avatar
    Faelar committed
    
        // 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(),
    
    Faelar's avatar
    Faelar committed
        ));
    
        // Send a m.room.create event containing a predecessor field and the applicable room_version
    
        create_event_content.insert(
            "creator".into(),
    
            json!(&sender_user)
                .try_into()
                .map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?,
    
        );
        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::<CanonicalJsonObject>(
            to_raw_value(&create_event_content)
                .expect("Error forming creation event")
                .get(),
        );
    
    
    Jonas Platte's avatar
    Jonas Platte committed
        if de_result.is_err() {
    
            return Err(Error::BadRequest(
                ErrorKind::BadJson,
                "Error forming creation event",
            ));
        }
    
    Faelar's avatar
    Faelar committed
    
    
    Timo Kösters's avatar
    Timo Kösters committed
        services().rooms.timeline.build_and_append_pdu(
    
    Timo Kösters's avatar
    Timo Kösters committed
            PduBuilder {
    
    Timo Kösters's avatar
    Timo Kösters committed
                event_type: RoomEventType::RoomCreate,
    
    Jonas Platte's avatar
    Jonas Platte committed
                content: to_raw_value(&create_event_content)
    
    Timo Kösters's avatar
    Timo Kösters committed
                    .expect("event is valid, we just created it"),
                unsigned: None,
                state_key: Some("".to_owned()),
                redacts: None,
            },
    
            sender_user,
    
    Timo Kösters's avatar
    Timo Kösters committed
            &replacement_room,
    
    Timo Kösters's avatar
    Timo Kösters committed
            &state_lock,
    
    Timo Kösters's avatar
    Timo Kösters committed
        )?;
    
    Faelar's avatar
    Faelar committed
    
        // Join the new room
    
    Timo Kösters's avatar
    Timo Kösters committed
        services().rooms.timeline.build_and_append_pdu(
    
    Timo Kösters's avatar
    Timo Kösters committed
            PduBuilder {
    
    Timo Kösters's avatar
    Timo Kösters committed
                event_type: RoomEventType::RoomMember,
    
    Jonas Platte's avatar
    Jonas Platte committed
                content: to_raw_value(&RoomMemberEventContent {
                    membership: MembershipState::Join,
    
                    displayname: services().users.displayname(sender_user)?,
                    avatar_url: services().users.avatar_url(sender_user)?,
    
    Timo Kösters's avatar
    Timo Kösters committed
                    is_direct: None,
                    third_party_invite: None,
    
                    blurhash: services().users.blurhash(sender_user)?,
    
                    join_authorized_via_users_server: None,
    
    Timo Kösters's avatar
    Timo Kösters committed
                })
                .expect("event is valid, we just created it"),
                unsigned: None,
    
                state_key: Some(sender_user.to_string()),
    
    Timo Kösters's avatar
    Timo Kösters committed
                redacts: None,
            },
    
            sender_user,
    
    Timo Kösters's avatar
    Timo Kösters committed
            &replacement_room,
    
    Timo Kösters's avatar
    Timo Kösters committed
            &state_lock,
    
    Timo Kösters's avatar
    Timo Kösters committed
        )?;
    
    Faelar's avatar
    Faelar committed
    
        // Recommended transferable state events list from the specs
        let transferable_state_events = vec![
    
    Timo Kösters's avatar
    Timo Kösters committed
            StateEventType::RoomServerAcl,
            StateEventType::RoomEncryption,
            StateEventType::RoomName,
            StateEventType::RoomAvatar,
            StateEventType::RoomTopic,
            StateEventType::RoomGuestAccess,
            StateEventType::RoomHistoryVisibility,
            StateEventType::RoomJoinRules,
            StateEventType::RoomPowerLevels,
    
    Faelar's avatar
    Faelar committed
        ];
    
        // Replicate transferable state events to the new room
        for event_type in transferable_state_events {
    
    Timo Kösters's avatar
    Timo Kösters committed
            let event_content =
                match services()
                    .rooms
                    .state_accessor
                    .room_state_get(&body.room_id, &event_type, "")?
                {
                    Some(v) => v.content.clone(),
                    None => continue, // Skipping missing events.
                };
    
    Faelar's avatar
    Faelar committed
    
    
    Timo Kösters's avatar
    Timo Kösters committed
            services().rooms.timeline.build_and_append_pdu(
    
    Timo Kösters's avatar
    Timo Kösters committed
                PduBuilder {
    
    Timo Kösters's avatar
    Timo Kösters committed
                    event_type: event_type.to_string().into(),
    
    Timo Kösters's avatar
    Timo Kösters committed
                    content: event_content,
                    unsigned: None,
                    state_key: Some("".to_owned()),
                    redacts: None,
                },
    
                sender_user,
    
    Timo Kösters's avatar
    Timo Kösters committed
                &replacement_room,
    
    Timo Kösters's avatar
    Timo Kösters committed
                &state_lock,
    
    Timo Kösters's avatar
    Timo Kösters committed
            )?;
    
    Faelar's avatar
    Faelar committed
        }
    
        // Moves any local aliases to the new room
    
    Timo Kösters's avatar
    Timo Kösters committed
        for alias in services()
            .rooms
            .alias
            .local_aliases_for_room(&body.room_id)
            .filter_map(|r| r.ok())
        {
            services()
                .rooms
                .alias
                .set_alias(&alias, &replacement_room)?;
    
    Faelar's avatar
    Faelar committed
        }
    
        // Get the old room power levels
    
    Jonas Platte's avatar
    Jonas Platte committed
        let mut power_levels_event_content: RoomPowerLevelsEventContent = serde_json::from_str(
    
    Timo Kösters's avatar
    Timo Kösters committed
            services()
                .rooms
    
    Timo Kösters's avatar
    Timo Kösters committed
                .state_accessor
    
    Timo Kösters's avatar
    Timo Kösters committed
                .room_state_get(&body.room_id, &StateEventType::RoomPowerLevels, "")?
    
    Jonas Platte's avatar
    Jonas Platte committed
                .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."))?;
    
    Faelar's avatar
    Faelar committed
    
        // 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));
    
    Faelar's avatar
    Faelar committed
        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
    
    Timo Kösters's avatar
    Timo Kösters committed
        let _ = services().rooms.timeline.build_and_append_pdu(
    
    Timo Kösters's avatar
    Timo Kösters committed
            PduBuilder {
    
    Timo Kösters's avatar
    Timo Kösters committed
                event_type: RoomEventType::RoomPowerLevels,
    
    Jonas Platte's avatar
    Jonas Platte committed
                content: to_raw_value(&power_levels_event_content)
    
    Timo Kösters's avatar
    Timo Kösters committed
                    .expect("event is valid, we just created it"),
                unsigned: None,
                state_key: Some("".to_owned()),
                redacts: None,
            },
    
            sender_user,
    
    Timo Kösters's avatar
    Timo Kösters committed
            &body.room_id,
    
    Timo Kösters's avatar
    Timo Kösters committed
            &state_lock,
    
    Timo Kösters's avatar
    Timo Kösters committed
        )?;
    
    Faelar's avatar
    Faelar committed
    
    
    Timo Kösters's avatar
    Timo Kösters committed
        drop(state_lock);
    
    Faelar's avatar
    Faelar committed
        // Return the replacement room id
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
        Ok(upgrade_room::v3::Response { replacement_room })
    
    Faelar's avatar
    Faelar committed
    }