diff --git a/src/admin/user/commands.rs b/src/admin/user/commands.rs index 07dd459f..18da60b7 100644 --- a/src/admin/user/commands.rs +++ b/src/admin/user/commands.rs @@ -2,11 +2,11 @@ use std::{collections::BTreeMap, fmt::Write as _}; use futures::{FutureExt, StreamExt}; use ruma::{ - OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, UserId, + Int, OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, UserId, events::{ RoomAccountDataEventType, StateEventType, room::{ - power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent}, + power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent, UserPowerLevel}, redaction::RoomRedactionEventContent, }, tag::{TagEvent, TagEventContent, TagInfo}, @@ -22,7 +22,7 @@ use tuwunel_service::Services; use crate::{ admin_command, get_room_info, - utils::{parse_active_local_user_id, parse_local_user_id}, + utils::{parse_active_local_user_id, parse_local_user_id, parse_user_id}, }; const AUTO_GEN_PASSWORD_LENGTH: usize = 25; @@ -737,6 +737,85 @@ pub(super) async fn force_demote(&self, user_id: String, room_id: OwnedRoomOrAli .await } +#[admin_command] +pub(super) async fn force_promote( + &self, + target_id: String, + room_id: OwnedRoomOrAliasId, +) -> Result { + let target_id = parse_user_id(self.services, &target_id)?; + let room_id = self.services.alias.resolve(&room_id).await?; + + let state_lock = self.services.state.mutex.lock(&room_id).await; + + let room_power_levels = self + .services + .state_accessor + .get_power_levels(&room_id) + .await?; + + let privileged_member = self + .services + .state_cache + .room_members(&room_id) + .ready_filter(|member_id| { + self.services.globals.user_is_local(member_id) + && room_power_levels.user_can_change_user_power_level(member_id, &target_id) + }) + .map(ToOwned::to_owned) + .ready_fold_default(|selected_user, member_id| match selected_user { + | None => Some(member_id), + | Some(selected_user) => Some( + if room_power_levels.for_user(&selected_user) + > room_power_levels.for_user(&member_id) + { + selected_user + } else { + member_id + }, + ), + }) + .await; + + let Some(privileged_member) = privileged_member else { + return Err!("No privileged user exists in room, cannot promote."); + }; + + info!("Selected privileged member {privileged_member}"); + + let power_level: Int = match room_power_levels.for_user(&privileged_member) { + | UserPowerLevel::Infinite => Int::MAX, + | UserPowerLevel::Int(x) => x, + }; + + let mut power_levels_content: RoomPowerLevelsEventContent = room_power_levels.try_into()?; + + power_levels_content + .users + .insert(target_id.clone(), power_level); + + let event_id = self + .services + .timeline + .build_and_append_pdu( + PduBuilder::state(String::new(), &power_levels_content), + &privileged_member, + &room_id, + &state_lock, + ) + .await?; + + drop(state_lock); + + self.write_str(&format!( + "User {privileged_member} promoted {target_id} to {power_level} power level in \ + {room_id} - {event_id}" + )) + .await?; + + Ok(()) +} + #[admin_command] pub(super) async fn make_user_admin(&self, user_id: String) -> Result { let user_id = parse_local_user_id(self.services, &user_id)?; diff --git a/src/admin/user/mod.rs b/src/admin/user/mod.rs index c8459652..55520b80 100644 --- a/src/admin/user/mod.rs +++ b/src/admin/user/mod.rs @@ -88,6 +88,12 @@ pub(super) enum UserCommand { room_id: OwnedRoomOrAliasId, }, + /// - Force promote + ForcePromote { + user_id: String, + room_id: OwnedRoomOrAliasId, + }, + /// - Grant server-admin privileges to a user. MakeUserAdmin { user_id: String,