Merge branch 'deactivate-user-command' into 'next'

Deactivate user command

See merge request famedly/conduit!337
This commit is contained in:
Timo Kösters 2022-06-19 19:38:07 +00:00
commit 9ee199b0c3
3 changed files with 156 additions and 60 deletions

View file

@ -4,7 +4,7 @@ use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH};
use crate::{
database::{admin::make_user_admin, DatabaseGuard},
pdu::PduBuilder,
utils, Error, Result, Ruma,
utils, Database, Error, Result, Ruma,
};
use ruma::{
api::client::{
@ -398,55 +398,8 @@ pub async fn deactivate_route(
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
}
// Leave all joined rooms and reject all invitations
// TODO: work over federation invites
let all_rooms = db
.rooms
.rooms_joined(sender_user)
.chain(
db.rooms
.rooms_invited(sender_user)
.map(|t| t.map(|(r, _)| r)),
)
.collect::<Vec<_>>();
for room_id in all_rooms {
let room_id = room_id?;
let event = RoomMemberEventContent {
membership: MembershipState::Leave,
displayname: None,
avatar_url: None,
is_direct: None,
third_party_invite: None,
blurhash: None,
reason: None,
join_authorized_via_users_server: None,
};
let mutex_state = Arc::clone(
db.globals
.roomid_mutex_state
.write()
.unwrap()
.entry(room_id.clone())
.or_default(),
);
let state_lock = mutex_state.lock().await;
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: RoomEventType::RoomMember,
content: to_raw_value(&event).expect("event is valid, we just created it"),
unsigned: None,
state_key: Some(sender_user.to_string()),
redacts: None,
},
sender_user,
&room_id,
&db,
&state_lock,
)?;
}
// Make the user leave all rooms before deactivation
db.rooms.leave_all_rooms(&sender_user, &db).await?;
// Remove devices and mark account as deactivated
db.users.deactivate_account(sender_user)?;

View file

@ -101,6 +101,12 @@ impl Admin {
tokio::select! {
Some(event) = receiver.recv() => {
let guard = db.read().await;
let message_content = match event {
AdminRoomEvent::SendMessage(content) => content,
AdminRoomEvent::ProcessMessage(room_message) => process_admin_message(&*guard, room_message).await
};
let mutex_state = Arc::clone(
guard.globals
.roomid_mutex_state
@ -109,18 +115,10 @@ impl Admin {
.entry(conduit_room.clone())
.or_default(),
);
let state_lock = mutex_state.lock().await;
match event {
AdminRoomEvent::SendMessage(content) => {
send_message(content, guard, &state_lock);
}
AdminRoomEvent::ProcessMessage(room_message) => {
let reply_message = process_admin_message(&*guard, room_message).await;
send_message(reply_message, guard, &state_lock);
}
}
send_message(message_content, guard, &state_lock);
drop(state_lock);
}
@ -240,6 +238,39 @@ enum AdminCommand {
/// List all rooms we are currently handling an incoming pdu from
IncomingFederation,
/// Deactivate a user
///
/// User will not be removed from all rooms by default.
/// Use --leave-rooms to force the user to leave all rooms
DeactivateUser {
#[clap(short, long)]
leave_rooms: bool,
user_id: Box<UserId>,
},
#[clap(verbatim_doc_comment)]
/// Deactivate a list of users
///
/// Recommended to use in conjunction with list-local-users.
///
/// Users will not be removed from joined rooms by default.
/// Can be overridden with --leave-rooms flag.
/// Removing a mass amount of users from a room may cause a significant amount of leave events.
/// The time to leave rooms may depend significantly on joined rooms and servers.
///
/// [commandbody]
/// # ```
/// # User list here
/// # ```
DeactivateAll {
#[clap(short, long)]
/// Remove users from their joined rooms
leave_rooms: bool,
#[clap(short, long)]
/// Also deactivate admin accounts
force: bool,
},
/// Get the auth_chain of a PDU
GetAuthChain {
/// An event ID (the $ character followed by the base64 reference hash)
@ -603,6 +634,97 @@ async fn process_admin_command(
db.rooms.disabledroomids.remove(room_id.as_bytes())?;
RoomMessageEventContent::text_plain("Room enabled.")
}
AdminCommand::DeactivateUser {
leave_rooms,
user_id,
} => {
let user_id = Arc::<UserId>::from(user_id);
if db.users.exists(&user_id)? {
RoomMessageEventContent::text_plain(format!(
"Making {} leave all rooms before deactivation...",
user_id
));
db.users.deactivate_account(&user_id)?;
if leave_rooms {
db.rooms.leave_all_rooms(&user_id, &db).await?;
}
RoomMessageEventContent::text_plain(format!(
"User {} has been deactivated",
user_id
))
} else {
RoomMessageEventContent::text_plain(format!(
"User {} doesn't exist on this server",
user_id
))
}
}
AdminCommand::DeactivateAll { leave_rooms, force } => {
if body.len() > 2 && body[0].trim() == "```" && body.last().unwrap().trim() == "```" {
let usernames = body.clone().drain(1..body.len() - 1).collect::<Vec<_>>();
let mut user_ids: Vec<&UserId> = Vec::new();
for &username in &usernames {
match <&UserId>::try_from(username) {
Ok(user_id) => user_ids.push(user_id),
Err(_) => {
return Ok(RoomMessageEventContent::text_plain(format!(
"{} is not a valid username",
username
)))
}
}
}
let mut deactivation_count = 0;
let mut admins = Vec::new();
if !force {
user_ids.retain(|&user_id| {
match db.users.is_admin(user_id, &db.rooms, &db.globals) {
Ok(is_admin) => match is_admin {
true => {
admins.push(user_id.localpart());
false
}
false => true,
},
Err(_) => false,
}
})
}
for &user_id in &user_ids {
match db.users.deactivate_account(user_id) {
Ok(_) => deactivation_count += 1,
Err(_) => {}
}
}
if leave_rooms {
for &user_id in &user_ids {
let _ = db.rooms.leave_all_rooms(user_id, &db).await;
}
}
if admins.is_empty() {
RoomMessageEventContent::text_plain(format!(
"Deactivated {} accounts.",
deactivation_count
))
} else {
RoomMessageEventContent::text_plain(format!("Deactivated {} accounts.\nSkipped admin accounts: {:?}. Use --force to deactivate admin accounts", deactivation_count, admins.join(", ")))
}
} else {
RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.",
)
}
}
};
Ok(reply_message_content)

View file

@ -2569,6 +2569,27 @@ impl Rooms {
}
}
// Make a user leave all their joined rooms
#[tracing::instrument(skip(self, db))]
pub async fn leave_all_rooms(&self, user_id: &UserId, db: &Database) -> Result<()> {
let all_rooms = db
.rooms
.rooms_joined(user_id)
.chain(db.rooms.rooms_invited(user_id).map(|t| t.map(|(r, _)| r)))
.collect::<Vec<_>>();
for room_id in all_rooms {
let room_id = match room_id {
Ok(room_id) => room_id,
Err(_) => continue,
};
let _ = self.leave_room(user_id, &room_id, db).await;
}
Ok(())
}
#[tracing::instrument(skip(self, db))]
pub async fn leave_room(
&self,