Add room upgrade.
This commit is contained in:
		
							parent
							
								
									aef0c8563c
								
							
						
					
					
						commit
						df55e8ed0b
					
				
					 5 changed files with 327 additions and 3 deletions
				
			
		|  | @ -3,15 +3,15 @@ use crate::{pdu::PduBuilder, ConduitResult, Database, Error, Ruma}; | |||
| use ruma::{ | ||||
|     api::client::{ | ||||
|         error::ErrorKind, | ||||
|         r0::room::{self, create_room, get_room_event}, | ||||
|         r0::room::{self, create_room, get_room_event, upgrade_room}, | ||||
|     }, | ||||
|     events::{ | ||||
|         room::{guest_access, history_visibility, join_rules, member, name, topic}, | ||||
|         EventType, | ||||
|     }, | ||||
|     RoomAliasId, RoomId, RoomVersionId, | ||||
|     Raw, RoomAliasId, RoomId, RoomVersionId, | ||||
| }; | ||||
| use std::{collections::BTreeMap, convert::TryFrom}; | ||||
| use std::{cmp::max, collections::BTreeMap, convert::TryFrom}; | ||||
| 
 | ||||
| #[cfg(feature = "conduit_bin")] | ||||
| use rocket::{get, post}; | ||||
|  | @ -344,3 +344,196 @@ pub fn get_room_event_route( | |||
|     } | ||||
|     .into()) | ||||
| } | ||||
| 
 | ||||
| #[cfg_attr(
 | ||||
|     feature = "conduit_bin", | ||||
|     post("/_matrix/client/r0/rooms/<_room_id>/upgrade", data = "<body>") | ||||
| )] | ||||
| pub fn upgrade_room_route( | ||||
|     db: State<'_, Database>, | ||||
|     body: Ruma<upgrade_room::Request>, | ||||
|     _room_id: String, | ||||
| ) -> ConduitResult<upgrade_room::Response> { | ||||
|     let sender_id = body.sender_id.as_ref().expect("user is authenticated"); | ||||
| 
 | ||||
|     // Validate the room version requested
 | ||||
|     let new_version = | ||||
|         RoomVersionId::try_from(body.new_version.clone()).expect("invalid room version id"); | ||||
| 
 | ||||
|     if !matches!( | ||||
|         new_version, | ||||
|         RoomVersionId::Version5 | RoomVersionId::Version6 | ||||
|     ) { | ||||
|         return Err(Error::BadRequest( | ||||
|             ErrorKind::UnsupportedRoomVersion, | ||||
|             "This server does not support that room version.", | ||||
|         )); | ||||
|     } | ||||
| 
 | ||||
|     // Create a replacement room
 | ||||
|     let replacement_room = RoomId::new(db.globals.server_name()); | ||||
| 
 | ||||
|     // 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 = db.rooms.append_pdu( | ||||
|         PduBuilder { | ||||
|             room_id: body.room_id.clone(), | ||||
|             sender: sender_id.clone(), | ||||
|             event_type: EventType::RoomTombstone, | ||||
|             content: serde_json::to_value(ruma::events::room::tombstone::TombstoneEventContent { | ||||
|                 body: "This room has been replaced".to_string(), | ||||
|                 replacement_room: replacement_room.clone(), | ||||
|             }) | ||||
|             .expect("event is valid, we just created it"), | ||||
|             unsigned: None, | ||||
|             state_key: Some("".to_owned()), | ||||
|             redacts: None, | ||||
|         }, | ||||
|         &db.globals, | ||||
|         &db.account_data, | ||||
|     )?; | ||||
| 
 | ||||
|     // Get the old room federations status
 | ||||
|     let federate = serde_json::from_value::<Raw<ruma::events::room::create::CreateEventContent>>( | ||||
|         db.rooms | ||||
|             .room_state_get(&body.room_id, &EventType::RoomCreate, "")? | ||||
|             .ok_or_else(|| Error::bad_database("Found room without m.room.create event."))? | ||||
|             .content, | ||||
|     ) | ||||
|     .expect("Raw::from_value always works") | ||||
|     .deserialize() | ||||
|     .map_err(|_| Error::bad_database("Invalid room event in database."))? | ||||
|     .federate; | ||||
| 
 | ||||
|     // 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, | ||||
|     )); | ||||
| 
 | ||||
|     // Send a m.room.create event containing a predecessor field and the applicable room_version
 | ||||
|     let mut create_event_content = | ||||
|         ruma::events::room::create::CreateEventContent::new(sender_id.clone()); | ||||
|     create_event_content.federate = federate; | ||||
|     create_event_content.room_version = new_version; | ||||
|     create_event_content.predecessor = predecessor; | ||||
| 
 | ||||
|     db.rooms.append_pdu( | ||||
|         PduBuilder { | ||||
|             room_id: replacement_room.clone(), | ||||
|             sender: sender_id.clone(), | ||||
|             event_type: EventType::RoomCreate, | ||||
|             content: serde_json::to_value(create_event_content) | ||||
|                 .expect("event is valid, we just created it"), | ||||
|             unsigned: None, | ||||
|             state_key: Some("".to_owned()), | ||||
|             redacts: None, | ||||
|         }, | ||||
|         &db.globals, | ||||
|         &db.account_data, | ||||
|     )?; | ||||
| 
 | ||||
|     // Join the new room
 | ||||
|     db.rooms.append_pdu( | ||||
|         PduBuilder { | ||||
|             room_id: replacement_room.clone(), | ||||
|             sender: sender_id.clone(), | ||||
|             event_type: EventType::RoomMember, | ||||
|             content: serde_json::to_value(member::MemberEventContent { | ||||
|                 membership: member::MembershipState::Join, | ||||
|                 displayname: db.users.displayname(&sender_id)?, | ||||
|                 avatar_url: db.users.avatar_url(&sender_id)?, | ||||
|                 is_direct: None, | ||||
|                 third_party_invite: None, | ||||
|             }) | ||||
|             .expect("event is valid, we just created it"), | ||||
|             unsigned: None, | ||||
|             state_key: Some(sender_id.to_string()), | ||||
|             redacts: None, | ||||
|         }, | ||||
|         &db.globals, | ||||
|         &db.account_data, | ||||
|     )?; | ||||
| 
 | ||||
|     // Recommended transferable state events list from the specs
 | ||||
|     let transferable_state_events = vec![ | ||||
|         EventType::RoomServerAcl, | ||||
|         EventType::RoomEncryption, | ||||
|         EventType::RoomName, | ||||
|         EventType::RoomAvatar, | ||||
|         EventType::RoomTopic, | ||||
|         EventType::RoomGuestAccess, | ||||
|         EventType::RoomHistoryVisibility, | ||||
|         EventType::RoomJoinRules, | ||||
|         EventType::RoomPowerLevels, | ||||
|     ]; | ||||
| 
 | ||||
|     // Replicate transferable state events to the new room
 | ||||
|     for event_type in transferable_state_events { | ||||
|         let event_content = match db.rooms.room_state_get(&body.room_id, &event_type, "")? { | ||||
|             Some(v) => v.content.clone(), | ||||
|             None => continue, // Skipping missing events.
 | ||||
|         }; | ||||
| 
 | ||||
|         db.rooms.append_pdu( | ||||
|             PduBuilder { | ||||
|                 room_id: replacement_room.clone(), | ||||
|                 sender: sender_id.clone(), | ||||
|                 event_type, | ||||
|                 content: event_content, | ||||
|                 unsigned: None, | ||||
|                 state_key: Some("".to_owned()), | ||||
|                 redacts: None, | ||||
|             }, | ||||
|             &db.globals, | ||||
|             &db.account_data, | ||||
|         )?; | ||||
|     } | ||||
| 
 | ||||
|     // Moves any local aliases to the new room
 | ||||
|     for alias in db.rooms.room_aliases(&body.room_id).filter_map(|r| r.ok()) { | ||||
|         db.rooms | ||||
|             .set_alias(&alias, Some(&replacement_room), &db.globals)?; | ||||
|     } | ||||
| 
 | ||||
|     // Get the old room power levels
 | ||||
|     let mut power_levels_event_content = | ||||
|         serde_json::from_value::<Raw<ruma::events::room::power_levels::PowerLevelsEventContent>>( | ||||
|             db.rooms | ||||
|                 .room_state_get(&body.room_id, &EventType::RoomPowerLevels, "")? | ||||
|                 .ok_or_else(|| Error::bad_database("Found room without m.room.create event."))? | ||||
|                 .content, | ||||
|         ) | ||||
|         .expect("database contains invalid PDU") | ||||
|         .deserialize() | ||||
|         .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( | ||||
|         50.into(), | ||||
|         power_levels_event_content.users_default + 1.into(), | ||||
|     ); | ||||
|     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
 | ||||
|     db.rooms | ||||
|         .append_pdu( | ||||
|             PduBuilder { | ||||
|                 room_id: body.room_id.clone(), | ||||
|                 sender: sender_id.clone(), | ||||
|                 event_type: EventType::RoomPowerLevels, | ||||
|                 content: serde_json::to_value(power_levels_event_content) | ||||
|                     .expect("event is valid, we just created it"), | ||||
|                 unsigned: None, | ||||
|                 state_key: Some("".to_owned()), | ||||
|                 redacts: None, | ||||
|             }, | ||||
|             &db.globals, | ||||
|             &db.account_data, | ||||
|         ) | ||||
|         .ok(); | ||||
| 
 | ||||
|     // Return the replacement room id
 | ||||
|     Ok(upgrade_room::Response { replacement_room }.into()) | ||||
| } | ||||
|  |  | |||
|  | @ -112,6 +112,7 @@ impl Database { | |||
| 
 | ||||
|                 userroomid_joined: db.open_tree("userroomid_joined")?, | ||||
|                 roomuserid_joined: db.open_tree("roomuserid_joined")?, | ||||
|                 roomuseroncejoinedids: db.open_tree("roomuseroncejoinedids")?, | ||||
|                 userroomid_invited: db.open_tree("userroomid_invited")?, | ||||
|                 roomuserid_invited: db.open_tree("roomuserid_invited")?, | ||||
|                 userroomid_left: db.open_tree("userroomid_left")?, | ||||
|  |  | |||
|  | @ -38,6 +38,7 @@ pub struct Rooms { | |||
| 
 | ||||
|     pub(super) userroomid_joined: sled::Tree, | ||||
|     pub(super) roomuserid_joined: sled::Tree, | ||||
|     pub(super) roomuseroncejoinedids: sled::Tree, | ||||
|     pub(super) userroomid_invited: sled::Tree, | ||||
|     pub(super) roomuserid_invited: sled::Tree, | ||||
|     pub(super) userroomid_left: sled::Tree, | ||||
|  | @ -782,6 +783,104 @@ impl Rooms { | |||
| 
 | ||||
|         match &membership { | ||||
|             member::MembershipState::Join => { | ||||
|                 // Check if the user never joined this room
 | ||||
|                 if !self.once_joined(&user_id, &room_id)? { | ||||
|                     // Add the user ID to the join list then
 | ||||
|                     self.roomuseroncejoinedids.insert(&userroom_id, &[])?; | ||||
| 
 | ||||
|                     // Check if the room has a predecessor
 | ||||
|                     if let Some(predecessor) = serde_json::from_value::< | ||||
|                         Raw<ruma::events::room::create::CreateEventContent>, | ||||
|                     >( | ||||
|                         self.room_state_get(&room_id, &EventType::RoomCreate, "")? | ||||
|                             .ok_or_else(|| { | ||||
|                                 Error::bad_database("Found room without m.room.create event.") | ||||
|                             })? | ||||
|                             .content, | ||||
|                     ) | ||||
|                     .expect("Raw::from_value always works") | ||||
|                     .deserialize() | ||||
|                     .map_err(|_| Error::bad_database("Invalid room event in database."))? | ||||
|                     .predecessor | ||||
|                     { | ||||
|                         // Copy user settings from predecessor to the current room:
 | ||||
| 
 | ||||
|                         // - Push rules
 | ||||
|                         //
 | ||||
|                         // TODO: finish this once push rules are implemented.
 | ||||
|                         //
 | ||||
|                         // let mut push_rules_event_content = account_data
 | ||||
|                         //     .get::<ruma::events::push_rules::PushRulesEvent>(
 | ||||
|                         //         None,
 | ||||
|                         //         user_id,
 | ||||
|                         //         EventType::PushRules,
 | ||||
|                         //     )?;
 | ||||
|                         //
 | ||||
|                         // NOTE: find where `predecessor.room_id` match
 | ||||
|                         //       and update to `room_id`.
 | ||||
|                         //
 | ||||
|                         // account_data
 | ||||
|                         //     .update(
 | ||||
|                         //         None,
 | ||||
|                         //         user_id,
 | ||||
|                         //         EventType::PushRules,
 | ||||
|                         //         &push_rules_event_content,
 | ||||
|                         //         globals,
 | ||||
|                         //     )
 | ||||
|                         //     .ok();
 | ||||
| 
 | ||||
|                         // - Tags
 | ||||
|                         if let Some(basic_event) = account_data.get::<ruma::events::tag::TagEvent>( | ||||
|                             Some(&predecessor.room_id), | ||||
|                             user_id, | ||||
|                             EventType::Tag, | ||||
|                         )? { | ||||
|                             let tag_event_content = basic_event.content; | ||||
| 
 | ||||
|                             account_data | ||||
|                                 .update( | ||||
|                                     Some(room_id), | ||||
|                                     user_id, | ||||
|                                     EventType::Tag, | ||||
|                                     &tag_event_content, | ||||
|                                     globals, | ||||
|                                 ) | ||||
|                                 .ok(); | ||||
|                         }; | ||||
| 
 | ||||
|                         // - Direct chat
 | ||||
|                         if let Some(basic_event) = account_data | ||||
|                             .get::<ruma::events::direct::DirectEvent>( | ||||
|                                 None, | ||||
|                                 user_id, | ||||
|                                 EventType::Direct, | ||||
|                             )? | ||||
|                         { | ||||
|                             let mut direct_event_content = basic_event.content; | ||||
|                             let mut room_ids_updated = false; | ||||
| 
 | ||||
|                             for room_ids in direct_event_content.0.values_mut() { | ||||
|                                 if room_ids.iter().any(|r| r == &predecessor.room_id) { | ||||
|                                     room_ids.push(room_id.clone()); | ||||
|                                     room_ids_updated = true; | ||||
|                                 } | ||||
|                             } | ||||
| 
 | ||||
|                             if room_ids_updated { | ||||
|                                 account_data | ||||
|                                     .update( | ||||
|                                         None, | ||||
|                                         user_id, | ||||
|                                         EventType::Direct, | ||||
|                                         &direct_event_content, | ||||
|                                         globals, | ||||
|                                     ) | ||||
|                                     .ok(); | ||||
|                             } | ||||
|                         }; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 self.userroomid_joined.insert(&userroom_id, &[])?; | ||||
|                 self.roomuserid_joined.insert(&roomuser_id, &[])?; | ||||
|                 self.userroomid_invited.remove(&userroom_id)?; | ||||
|  | @ -1042,6 +1141,27 @@ impl Rooms { | |||
|             }) | ||||
|     } | ||||
| 
 | ||||
|     /// Returns an iterator over all User IDs who ever joined a room.
 | ||||
|     pub fn room_useroncejoined(&self, room_id: &RoomId) -> impl Iterator<Item = Result<UserId>> { | ||||
|         self.roomuseroncejoinedids | ||||
|             .scan_prefix(room_id.to_string()) | ||||
|             .keys() | ||||
|             .map(|key| { | ||||
|                 Ok(UserId::try_from( | ||||
|                     utils::string_from_bytes( | ||||
|                         &key? | ||||
|                             .rsplit(|&b| b == 0xff) | ||||
|                             .next() | ||||
|                             .expect("rsplit always returns an element"), | ||||
|                     ) | ||||
|                     .map_err(|_| { | ||||
|                         Error::bad_database("User ID in room_useroncejoined is invalid unicode.") | ||||
|                     })?, | ||||
|                 ) | ||||
|                 .map_err(|_| Error::bad_database("User ID in room_useroncejoined is invalid."))?) | ||||
|             }) | ||||
|     } | ||||
| 
 | ||||
|     /// Returns an iterator over all invited members of a room.
 | ||||
|     pub fn room_members_invited(&self, room_id: &RoomId) -> impl Iterator<Item = Result<UserId>> { | ||||
|         self.roomuserid_invited | ||||
|  | @ -1126,6 +1246,14 @@ impl Rooms { | |||
|             }) | ||||
|     } | ||||
| 
 | ||||
|     pub fn once_joined(&self, user_id: &UserId, room_id: &RoomId) -> Result<bool> { | ||||
|         let mut userroom_id = user_id.to_string().as_bytes().to_vec(); | ||||
|         userroom_id.push(0xff); | ||||
|         userroom_id.extend_from_slice(room_id.to_string().as_bytes()); | ||||
| 
 | ||||
|         Ok(self.roomuseroncejoinedids.get(userroom_id)?.is_some()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn is_joined(&self, user_id: &UserId, room_id: &RoomId) -> Result<bool> { | ||||
|         let mut userroom_id = user_id.to_string().as_bytes().to_vec(); | ||||
|         userroom_id.push(0xff); | ||||
|  |  | |||
|  | @ -118,6 +118,7 @@ fn setup_rocket() -> rocket::Rocket { | |||
|                 client_server::get_key_changes_route, | ||||
|                 client_server::get_pushers_route, | ||||
|                 client_server::set_pushers_route, | ||||
|                 client_server::upgrade_room_route, | ||||
|                 server_server::well_known_server, | ||||
|                 server_server::get_server_version, | ||||
|                 server_server::get_server_keys, | ||||
|  |  | |||
|  | @ -89,6 +89,7 @@ POST /rooms/:room_id/join can join a room | |||
| POST /rooms/:room_id/leave can leave a room | ||||
| POST /rooms/:room_id/state/m.room.name sets name | ||||
| POST /rooms/:room_id/state/m.room.topic sets topic | ||||
| POST /rooms/:room_id/upgrade can upgrade a room version | ||||
| POSTed media can be thumbnailed | ||||
| PUT /device/{deviceId} gives a 404 for unknown devices | ||||
| PUT /device/{deviceId} updates device fields | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue