diff --git a/src/database/admin.rs b/src/database/admin.rs index bf38bd8c..81e98393 100644 --- a/src/database/admin.rs +++ b/src/database/admin.rs @@ -14,6 +14,7 @@ pub enum AdminCommand { RegisterAppservice(serde_yaml::Value), UnregisterAppservice(String), ListAppservices, + ListLocalUsers, ShowMemoryUsage, SendMessage(RoomMessageEventContent), } @@ -95,6 +96,18 @@ impl Admin { let state_lock = mutex_state.lock().await; match event { + AdminCommand::ListLocalUsers => { + match guard.users.list_local_users() { + Ok(users) => { + let mut msg: String = format!("Found {} local user account(s):\n", users.len()); + msg += &users.join("\n"); + send_message(RoomMessageEventContent::text_plain(&msg), guard, &state_lock); + } + Err(e) => { + send_message(RoomMessageEventContent::text_plain(e.to_string()), guard, &state_lock); + } + } + } AdminCommand::RegisterAppservice(yaml) => { guard.appservice.register_appservice(yaml).unwrap(); // TODO handle error } diff --git a/src/database/rooms.rs b/src/database/rooms.rs index c9a3c202..c0cb1ce9 100644 --- a/src/database/rooms.rs +++ b/src/database/rooms.rs @@ -1548,6 +1548,9 @@ impl Rooms { "list_appservices" => { db.admin.send(AdminCommand::ListAppservices); } + "list_local_users" => { + db.admin.send(AdminCommand::ListLocalUsers); + } "get_auth_chain" => { if args.len() == 1 { if let Ok(event_id) = EventId::parse_arc(args[0]) { diff --git a/src/database/users.rs b/src/database/users.rs index 69a277c6..13f9b151 100644 --- a/src/database/users.rs +++ b/src/database/users.rs @@ -131,6 +131,42 @@ impl Users { }) } + /// Returns a list of local users as list of usernames. + /// + /// A user account is considered `local` if the length of it's password is greater then zero. + #[tracing::instrument(skip(self))] + pub fn list_local_users(&self) -> Result> { + let users: Vec = self + .userid_password + .iter() + .filter_map(|(username, pw)| self.get_username_with_valid_password(&username, &pw)) + .collect(); + Ok(users) + } + + /// Will only return with Some(username) if the password was not empty and the + /// username could be successfully parsed. + /// If utils::string_from_bytes(...) returns an error that username will be skipped + /// and the error will be logged. + #[tracing::instrument(skip(self))] + fn get_username_with_valid_password(&self, username: &[u8], password: &[u8]) -> Option { + // A valid password is not empty + if password.is_empty() { + None + } else { + match utils::string_from_bytes(username) { + Ok(u) => Some(u), + Err(e) => { + warn!( + "Failed to parse username while calling get_local_users(): {}", + e.to_string() + ); + None + } + } + } + } + /// Returns the password hash for the given user. #[tracing::instrument(skip(self, user_id))] pub fn password_hash(&self, user_id: &UserId) -> Result> {