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::{ | use ruma::{ | ||||||
|     api::client::{ |     api::client::{ | ||||||
|         error::ErrorKind, |         error::ErrorKind, | ||||||
|         r0::room::{self, create_room, get_room_event}, |         r0::room::{self, create_room, get_room_event, upgrade_room}, | ||||||
|     }, |     }, | ||||||
|     events::{ |     events::{ | ||||||
|         room::{guest_access, history_visibility, join_rules, member, name, topic}, |         room::{guest_access, history_visibility, join_rules, member, name, topic}, | ||||||
|         EventType, |         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")] | #[cfg(feature = "conduit_bin")] | ||||||
| use rocket::{get, post}; | use rocket::{get, post}; | ||||||
|  | @ -344,3 +344,196 @@ pub fn get_room_event_route( | ||||||
|     } |     } | ||||||
|     .into()) |     .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")?, |                 userroomid_joined: db.open_tree("userroomid_joined")?, | ||||||
|                 roomuserid_joined: db.open_tree("roomuserid_joined")?, |                 roomuserid_joined: db.open_tree("roomuserid_joined")?, | ||||||
|  |                 roomuseroncejoinedids: db.open_tree("roomuseroncejoinedids")?, | ||||||
|                 userroomid_invited: db.open_tree("userroomid_invited")?, |                 userroomid_invited: db.open_tree("userroomid_invited")?, | ||||||
|                 roomuserid_invited: db.open_tree("roomuserid_invited")?, |                 roomuserid_invited: db.open_tree("roomuserid_invited")?, | ||||||
|                 userroomid_left: db.open_tree("userroomid_left")?, |                 userroomid_left: db.open_tree("userroomid_left")?, | ||||||
|  |  | ||||||
|  | @ -38,6 +38,7 @@ pub struct Rooms { | ||||||
| 
 | 
 | ||||||
|     pub(super) userroomid_joined: sled::Tree, |     pub(super) userroomid_joined: sled::Tree, | ||||||
|     pub(super) roomuserid_joined: sled::Tree, |     pub(super) roomuserid_joined: sled::Tree, | ||||||
|  |     pub(super) roomuseroncejoinedids: sled::Tree, | ||||||
|     pub(super) userroomid_invited: sled::Tree, |     pub(super) userroomid_invited: sled::Tree, | ||||||
|     pub(super) roomuserid_invited: sled::Tree, |     pub(super) roomuserid_invited: sled::Tree, | ||||||
|     pub(super) userroomid_left: sled::Tree, |     pub(super) userroomid_left: sled::Tree, | ||||||
|  | @ -782,6 +783,104 @@ impl Rooms { | ||||||
| 
 | 
 | ||||||
|         match &membership { |         match &membership { | ||||||
|             member::MembershipState::Join => { |             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.userroomid_joined.insert(&userroom_id, &[])?; | ||||||
|                 self.roomuserid_joined.insert(&roomuser_id, &[])?; |                 self.roomuserid_joined.insert(&roomuser_id, &[])?; | ||||||
|                 self.userroomid_invited.remove(&userroom_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.
 |     /// Returns an iterator over all invited members of a room.
 | ||||||
|     pub fn room_members_invited(&self, room_id: &RoomId) -> impl Iterator<Item = Result<UserId>> { |     pub fn room_members_invited(&self, room_id: &RoomId) -> impl Iterator<Item = Result<UserId>> { | ||||||
|         self.roomuserid_invited |         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> { |     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(); |         let mut userroom_id = user_id.to_string().as_bytes().to_vec(); | ||||||
|         userroom_id.push(0xff); |         userroom_id.push(0xff); | ||||||
|  |  | ||||||
|  | @ -118,6 +118,7 @@ fn setup_rocket() -> rocket::Rocket { | ||||||
|                 client_server::get_key_changes_route, |                 client_server::get_key_changes_route, | ||||||
|                 client_server::get_pushers_route, |                 client_server::get_pushers_route, | ||||||
|                 client_server::set_pushers_route, |                 client_server::set_pushers_route, | ||||||
|  |                 client_server::upgrade_room_route, | ||||||
|                 server_server::well_known_server, |                 server_server::well_known_server, | ||||||
|                 server_server::get_server_version, |                 server_server::get_server_version, | ||||||
|                 server_server::get_server_keys, |                 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/leave can leave a room | ||||||
| POST /rooms/:room_id/state/m.room.name sets name | POST /rooms/:room_id/state/m.room.name sets name | ||||||
| POST /rooms/:room_id/state/m.room.topic sets topic | 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 | POSTed media can be thumbnailed | ||||||
| PUT /device/{deviceId} gives a 404 for unknown devices | PUT /device/{deviceId} gives a 404 for unknown devices | ||||||
| PUT /device/{deviceId} updates device fields | PUT /device/{deviceId} updates device fields | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue