de-global services for admin
Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
parent
9b20c6918f
commit
992c0a1e58
14 changed files with 221 additions and 227 deletions
|
@ -8,7 +8,7 @@ use api::client::validate_and_add_event_id;
|
|||
use conduit::{
|
||||
debug, info, log,
|
||||
log::{capture, Capture},
|
||||
warn, Error, Result,
|
||||
warn, Error, PduEvent, Result,
|
||||
};
|
||||
use ruma::{
|
||||
api::{client::error::ErrorKind, federation::event::get_room_state},
|
||||
|
|
|
@ -14,7 +14,7 @@ use ruma::{
|
|||
extern crate conduit_service as service;
|
||||
|
||||
use conduit::{utils::string::common_prefix, Result};
|
||||
pub(crate) use service::admin::{Command, Service};
|
||||
pub(crate) use service::admin::Command;
|
||||
use service::admin::{CommandOutput, CommandResult, HandlerResult};
|
||||
|
||||
use crate::{
|
||||
|
|
|
@ -20,10 +20,7 @@ extern crate conduit_service as service;
|
|||
pub(crate) use conduit::{mod_ctor, mod_dtor, Result};
|
||||
pub(crate) use service::{services, user_is_local};
|
||||
|
||||
pub(crate) use crate::{
|
||||
handler::Service,
|
||||
utils::{escape_html, get_room_info},
|
||||
};
|
||||
pub(crate) use crate::utils::{escape_html, get_room_info};
|
||||
|
||||
mod_ctor! {}
|
||||
mod_dtor! {}
|
||||
|
|
|
@ -2,7 +2,7 @@ use api::client::leave_room;
|
|||
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomAliasId, RoomId, RoomOrAliasId};
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
use super::{super::Service, RoomModerationCommand};
|
||||
use super::RoomModerationCommand;
|
||||
use crate::{get_room_info, services, user_is_local, Result};
|
||||
|
||||
pub(super) async fn process(command: RoomModerationCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
|
@ -31,7 +31,7 @@ async fn ban_room(
|
|||
|
||||
let admin_room_alias = &services().globals.admin_alias;
|
||||
|
||||
if let Some(admin_room_id) = Service::get_admin_room()? {
|
||||
if let Some(admin_room_id) = services().admin.get_admin_room()? {
|
||||
if room.to_string().eq(&admin_room_id) || room.to_string().eq(admin_room_alias) {
|
||||
return Ok(RoomMessageEventContent::text_plain("Not allowed to ban the admin room."));
|
||||
}
|
||||
|
@ -198,7 +198,7 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
|
|||
for &room in &rooms_s {
|
||||
match <&RoomOrAliasId>::try_from(room) {
|
||||
Ok(room_alias_or_id) => {
|
||||
if let Some(admin_room_id) = Service::get_admin_room()? {
|
||||
if let Some(admin_room_id) = services().admin.get_admin_room()? {
|
||||
if room.to_owned().eq(&admin_room_id) || room.to_owned().eq(admin_room_alias) {
|
||||
info!("User specified admin room in bulk ban list, ignoring");
|
||||
continue;
|
||||
|
|
|
@ -363,7 +363,10 @@ pub(super) async fn make_user_admin(_body: Vec<&str>, user_id: String) -> Result
|
|||
.unwrap_or_else(|| user_id.to_string());
|
||||
|
||||
assert!(service::user_is_local(&user_id), "Parsed user_id must be a local user");
|
||||
service::admin::make_user_admin(&user_id, displayname).await?;
|
||||
services()
|
||||
.admin
|
||||
.make_user_admin(&user_id, displayname)
|
||||
.await?;
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"{user_id} has been granted admin privileges.",
|
||||
|
|
|
@ -347,9 +347,12 @@ pub(crate) async fn register_route(
|
|||
// If this is the first real user, grant them admin privileges except for guest
|
||||
// users Note: the server user, @conduit:servername, is generated first
|
||||
if !is_guest {
|
||||
if let Some(admin_room) = service::admin::Service::get_admin_room()? {
|
||||
if let Some(admin_room) = services.admin.get_admin_room()? {
|
||||
if services.rooms.state_cache.room_joined_count(&admin_room)? == Some(1) {
|
||||
service::admin::make_user_admin(&user_id, displayname).await?;
|
||||
services
|
||||
.admin
|
||||
.make_user_admin(&user_id, displayname)
|
||||
.await?;
|
||||
|
||||
warn!("Granting {user_id} admin privileges as the first user");
|
||||
}
|
||||
|
|
|
@ -210,7 +210,7 @@ async fn allowed_to_send_state_event(
|
|||
},
|
||||
// admin room is a sensitive room, it should not ever be made public
|
||||
StateEventType::RoomJoinRules => {
|
||||
if let Some(admin_room_id) = service::admin::Service::get_admin_room()? {
|
||||
if let Some(admin_room_id) = services.admin.get_admin_room()? {
|
||||
if admin_room_id == room_id {
|
||||
if let Ok(join_rule) = serde_json::from_str::<RoomJoinRulesEventContent>(json.json().get()) {
|
||||
if join_rule.join_rule == JoinRule::Public {
|
||||
|
@ -225,7 +225,7 @@ async fn allowed_to_send_state_event(
|
|||
},
|
||||
// admin room is a sensitive room, it should not ever be made world readable
|
||||
StateEventType::RoomHistoryVisibility => {
|
||||
if let Some(admin_room_id) = service::admin::Service::get_admin_room()? {
|
||||
if let Some(admin_room_id) = services.admin.get_admin_room()? {
|
||||
if admin_room_id == room_id {
|
||||
if let Ok(visibility_content) =
|
||||
serde_json::from_str::<RoomHistoryVisibilityEventContent>(json.json().get())
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::{
|
|||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use conduit::{debug, defer, error, log};
|
||||
use conduit::{debug, defer, error, log, Server};
|
||||
use futures_util::future::{AbortHandle, Abortable};
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
use rustyline_async::{Readline, ReadlineError, ReadlineEvent};
|
||||
|
@ -14,6 +14,7 @@ use tokio::task::JoinHandle;
|
|||
use crate::services;
|
||||
|
||||
pub struct Console {
|
||||
server: Arc<Server>,
|
||||
worker_join: Mutex<Option<JoinHandle<()>>>,
|
||||
input_abort: Mutex<Option<AbortHandle>>,
|
||||
command_abort: Mutex<Option<AbortHandle>>,
|
||||
|
@ -25,9 +26,9 @@ const PROMPT: &str = "uwu> ";
|
|||
const HISTORY_LIMIT: usize = 48;
|
||||
|
||||
impl Console {
|
||||
#[must_use]
|
||||
pub fn new() -> Arc<Self> {
|
||||
pub(super) fn new(args: &crate::Args<'_>) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
server: args.server.clone(),
|
||||
worker_join: None.into(),
|
||||
input_abort: None.into(),
|
||||
command_abort: None.into(),
|
||||
|
@ -37,7 +38,7 @@ impl Console {
|
|||
}
|
||||
|
||||
pub(super) async fn handle_signal(self: &Arc<Self>, sig: &'static str) {
|
||||
if !services().server.running() {
|
||||
if !self.server.running() {
|
||||
self.interrupt();
|
||||
} else if sig == "SIGINT" {
|
||||
self.interrupt_command();
|
||||
|
@ -49,7 +50,7 @@ impl Console {
|
|||
let mut worker_join = self.worker_join.lock().expect("locked");
|
||||
if worker_join.is_none() {
|
||||
let self_ = Arc::clone(self);
|
||||
_ = worker_join.insert(services().server.runtime().spawn(self_.worker()));
|
||||
_ = worker_join.insert(self.server.runtime().spawn(self_.worker()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,16 +90,13 @@ impl Console {
|
|||
#[tracing::instrument(skip_all, name = "console")]
|
||||
async fn worker(self: Arc<Self>) {
|
||||
debug!("session starting");
|
||||
while services().server.running() {
|
||||
while self.server.running() {
|
||||
match self.readline().await {
|
||||
Ok(event) => match event {
|
||||
ReadlineEvent::Line(string) => self.clone().handle(string).await,
|
||||
ReadlineEvent::Interrupted => continue,
|
||||
ReadlineEvent::Eof => break,
|
||||
ReadlineEvent::Quit => services()
|
||||
.server
|
||||
.shutdown()
|
||||
.unwrap_or_else(error::default_log),
|
||||
ReadlineEvent::Quit => self.server.shutdown().unwrap_or_else(error::default_log),
|
||||
},
|
||||
Err(error) => match error {
|
||||
ReadlineError::Closed => break,
|
||||
|
@ -115,7 +113,7 @@ impl Console {
|
|||
}
|
||||
|
||||
async fn readline(self: &Arc<Self>) -> Result<ReadlineEvent, ReadlineError> {
|
||||
let _suppression = log::Suppress::new(&services().server);
|
||||
let _suppression = log::Suppress::new(&self.server);
|
||||
|
||||
let (mut readline, _writer) = Readline::new(PROMPT.to_owned())?;
|
||||
readline.set_tab_completer(Self::tab_complete);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use conduit::{Error, Result};
|
||||
use conduit::{pdu::PduBuilder, warn, Error, Result};
|
||||
use ruma::{
|
||||
api::client::error::ErrorKind,
|
||||
events::{
|
||||
|
@ -21,26 +21,25 @@ use ruma::{
|
|||
RoomId, RoomVersionId,
|
||||
};
|
||||
use serde_json::value::to_raw_value;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::{pdu::PduBuilder, services};
|
||||
use crate::Services;
|
||||
|
||||
/// Create the admin room.
|
||||
///
|
||||
/// Users in this room are considered admins by conduit, and the room can be
|
||||
/// used to issue admin commands by talking to the server user inside it.
|
||||
pub async fn create_admin_room() -> Result<()> {
|
||||
let room_id = RoomId::new(services().globals.server_name());
|
||||
pub async fn create_admin_room(services: &Services) -> Result<()> {
|
||||
let room_id = RoomId::new(services.globals.server_name());
|
||||
|
||||
let _short_id = services().rooms.short.get_or_create_shortroomid(&room_id)?;
|
||||
let _short_id = services.rooms.short.get_or_create_shortroomid(&room_id)?;
|
||||
|
||||
let state_lock = services().rooms.state.mutex.lock(&room_id).await;
|
||||
let state_lock = services.rooms.state.mutex.lock(&room_id).await;
|
||||
|
||||
// Create a user for the server
|
||||
let server_user = &services().globals.server_user;
|
||||
services().users.create(server_user, None)?;
|
||||
let server_user = &services.globals.server_user;
|
||||
services.users.create(server_user, None)?;
|
||||
|
||||
let room_version = services().globals.default_room_version();
|
||||
let room_version = services.globals.default_room_version();
|
||||
|
||||
let mut content = {
|
||||
use RoomVersionId::*;
|
||||
|
@ -62,7 +61,7 @@ pub async fn create_admin_room() -> Result<()> {
|
|||
content.room_version = room_version;
|
||||
|
||||
// 1. The room create event
|
||||
services()
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
|
@ -80,7 +79,7 @@ pub async fn create_admin_room() -> Result<()> {
|
|||
.await?;
|
||||
|
||||
// 2. Make conduit bot join
|
||||
services()
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
|
@ -111,7 +110,7 @@ pub async fn create_admin_room() -> Result<()> {
|
|||
let mut users = BTreeMap::new();
|
||||
users.insert(server_user.clone(), 100.into());
|
||||
|
||||
services()
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
|
@ -133,7 +132,7 @@ pub async fn create_admin_room() -> Result<()> {
|
|||
.await?;
|
||||
|
||||
// 4.1 Join Rules
|
||||
services()
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
|
@ -152,7 +151,7 @@ pub async fn create_admin_room() -> Result<()> {
|
|||
.await?;
|
||||
|
||||
// 4.2 History Visibility
|
||||
services()
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
|
@ -171,7 +170,7 @@ pub async fn create_admin_room() -> Result<()> {
|
|||
.await?;
|
||||
|
||||
// 4.3 Guest Access
|
||||
services()
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
|
@ -190,8 +189,8 @@ pub async fn create_admin_room() -> Result<()> {
|
|||
.await?;
|
||||
|
||||
// 5. Events implied by name and topic
|
||||
let room_name = format!("{} Admin Room", services().globals.server_name());
|
||||
services()
|
||||
let room_name = format!("{} Admin Room", services.globals.server_name());
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
|
@ -209,14 +208,14 @@ pub async fn create_admin_room() -> Result<()> {
|
|||
)
|
||||
.await?;
|
||||
|
||||
services()
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomTopic,
|
||||
content: to_raw_value(&RoomTopicEventContent {
|
||||
topic: format!("Manage {}", services().globals.server_name()),
|
||||
topic: format!("Manage {}", services.globals.server_name()),
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
|
@ -230,9 +229,9 @@ pub async fn create_admin_room() -> Result<()> {
|
|||
.await?;
|
||||
|
||||
// 6. Room alias
|
||||
let alias = &services().globals.admin_alias;
|
||||
let alias = &services.globals.admin_alias;
|
||||
|
||||
services()
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
|
@ -253,13 +252,13 @@ pub async fn create_admin_room() -> Result<()> {
|
|||
)
|
||||
.await?;
|
||||
|
||||
services()
|
||||
services
|
||||
.rooms
|
||||
.alias
|
||||
.set_alias(alias, &room_id, server_user)?;
|
||||
|
||||
// 7. (ad-hoc) Disable room previews for everyone by default
|
||||
services()
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
|
|
|
@ -14,23 +14,24 @@ use ruma::{
|
|||
};
|
||||
use serde_json::value::to_raw_value;
|
||||
|
||||
use super::Service;
|
||||
use crate::{pdu::PduBuilder, services};
|
||||
use crate::pdu::PduBuilder;
|
||||
|
||||
/// Invite the user to the conduit admin room.
|
||||
///
|
||||
/// In conduit, this is equivalent to granting admin privileges.
|
||||
pub async fn make_user_admin(user_id: &UserId, displayname: String) -> Result<()> {
|
||||
if let Some(room_id) = Service::get_admin_room()? {
|
||||
let state_lock = services().rooms.state.mutex.lock(&room_id).await;
|
||||
impl super::Service {
|
||||
/// Invite the user to the conduit admin room.
|
||||
///
|
||||
/// In conduit, this is equivalent to granting admin privileges.
|
||||
pub async fn make_user_admin(&self, user_id: &UserId, displayname: String) -> Result<()> {
|
||||
let Some(room_id) = self.get_admin_room()? else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let state_lock = self.state.mutex.lock(&room_id).await;
|
||||
|
||||
// Use the server user to grant the new admin's power level
|
||||
let server_user = &services().globals.server_user;
|
||||
let server_user = &self.globals.server_user;
|
||||
|
||||
// Invite and join the real user
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
self.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
|
@ -54,9 +55,7 @@ pub async fn make_user_admin(user_id: &UserId, displayname: String) -> Result<()
|
|||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
self.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
|
@ -86,9 +85,7 @@ pub async fn make_user_admin(user_id: &UserId, displayname: String) -> Result<()
|
|||
users.insert(server_user.clone(), 100.into());
|
||||
users.insert(user_id.to_owned(), 100.into());
|
||||
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
self.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomPowerLevels,
|
||||
|
@ -108,12 +105,12 @@ pub async fn make_user_admin(user_id: &UserId, displayname: String) -> Result<()
|
|||
.await?;
|
||||
|
||||
// Send welcome message
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
self.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMessage,
|
||||
content: to_raw_value(&RoomMessageEventContent::text_html(
|
||||
format!("## Thank you for trying out conduwuit!\n\nconduwuit is a fork of upstream Conduit which is in Beta. This means you can join and participate in most Matrix rooms, but not all features are supported and you might run into bugs from time to time.\n\nHelpful links:\n> Git and Documentation: https://github.com/girlbossceo/conduwuit\n> Report issues: https://github.com/girlbossceo/conduwuit/issues\n\nFor a list of available commands, send the following message in this room: `@conduit:{}: --help`\n\nHere are some rooms you can join (by typing the command):\n\nconduwuit room (Ask questions and get notified on updates):\n`/join #conduwuit:puppygock.gay`", services().globals.server_name()),
|
||||
format!("<h2>Thank you for trying out conduwuit!</h2>\n<p>conduwuit is a fork of upstream Conduit which is in Beta. This means you can join and participate in most Matrix rooms, but not all features are supported and you might run into bugs from time to time.</p>\n<p>Helpful links:</p>\n<blockquote>\n<p>Git and Documentation: https://github.com/girlbossceo/conduwuit<br>Report issues: https://github.com/girlbossceo/conduwuit/issues</p>\n</blockquote>\n<p>For a list of available commands, send the following message in this room: <code>@conduit:{}: --help</code></p>\n<p>Here are some rooms you can join (by typing the command):</p>\n<p>conduwuit room (Ask questions and get notified on updates):<br><code>/join #conduwuit:puppygock.gay</code></p>\n", services().globals.server_name()),
|
||||
format!("## Thank you for trying out conduwuit!\n\nconduwuit is a fork of upstream Conduit which is in Beta. This means you can join and participate in most Matrix rooms, but not all features are supported and you might run into bugs from time to time.\n\nHelpful links:\n> Git and Documentation: https://github.com/girlbossceo/conduwuit\n> Report issues: https://github.com/girlbossceo/conduwuit/issues\n\nFor a list of available commands, send the following message in this room: `@conduit:{}: --help`\n\nHere are some rooms you can join (by typing the command):\n\nconduwuit room (Ask questions and get notified on updates):\n`/join #conduwuit:puppygock.gay`", self.globals.server_name()),
|
||||
format!("<h2>Thank you for trying out conduwuit!</h2>\n<p>conduwuit is a fork of upstream Conduit which is in Beta. This means you can join and participate in most Matrix rooms, but not all features are supported and you might run into bugs from time to time.</p>\n<p>Helpful links:</p>\n<blockquote>\n<p>Git and Documentation: https://github.com/girlbossceo/conduwuit<br>Report issues: https://github.com/girlbossceo/conduwuit/issues</p>\n</blockquote>\n<p>For a list of available commands, send the following message in this room: <code>@conduit:{}: --help</code></p>\n<p>Here are some rooms you can join (by typing the command):</p>\n<p>conduwuit room (Ask questions and get notified on updates):<br><code>/join #conduwuit:puppygock.gay</code></p>\n", self.globals.server_name()),
|
||||
))
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
|
@ -124,7 +121,7 @@ pub async fn make_user_admin(user_id: &UserId, displayname: String) -> Result<()
|
|||
&room_id,
|
||||
&state_lock,
|
||||
).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,8 @@ use std::{
|
|||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use conduit::{debug, error, error::default_log, Error, Result};
|
||||
use conduit::{debug, error, error::default_log, pdu::PduBuilder, Error, PduEvent, Result, Server};
|
||||
pub use create::create_admin_room;
|
||||
pub use grant::make_user_admin;
|
||||
use loole::{Receiver, Sender};
|
||||
use ruma::{
|
||||
events::{
|
||||
|
@ -23,9 +22,15 @@ use ruma::{
|
|||
use serde_json::value::to_raw_value;
|
||||
use tokio::sync::{Mutex, RwLock};
|
||||
|
||||
use crate::{pdu::PduBuilder, rooms::state::RoomMutexGuard, services, user_is_local, PduEvent};
|
||||
use crate::{globals, rooms, rooms::state::RoomMutexGuard, user_is_local};
|
||||
|
||||
pub struct Service {
|
||||
server: Arc<Server>,
|
||||
globals: Arc<globals::Service>,
|
||||
alias: Arc<rooms::alias::Service>,
|
||||
timeline: Arc<rooms::timeline::Service>,
|
||||
state: Arc<rooms::state::Service>,
|
||||
state_cache: Arc<rooms::state_cache::Service>,
|
||||
sender: Sender<Command>,
|
||||
receiver: Mutex<Receiver<Command>>,
|
||||
pub handle: RwLock<Option<Handler>>,
|
||||
|
@ -50,21 +55,27 @@ const COMMAND_QUEUE_LIMIT: usize = 512;
|
|||
|
||||
#[async_trait]
|
||||
impl crate::Service for Service {
|
||||
fn build(_args: crate::Args<'_>) -> Result<Arc<Self>> {
|
||||
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
|
||||
let (sender, receiver) = loole::bounded(COMMAND_QUEUE_LIMIT);
|
||||
Ok(Arc::new(Self {
|
||||
server: args.server.clone(),
|
||||
globals: args.require_service::<globals::Service>("globals"),
|
||||
alias: args.require_service::<rooms::alias::Service>("rooms::alias"),
|
||||
timeline: args.require_service::<rooms::timeline::Service>("rooms::timeline"),
|
||||
state: args.require_service::<rooms::state::Service>("rooms::state"),
|
||||
state_cache: args.require_service::<rooms::state_cache::Service>("rooms::state_cache"),
|
||||
sender,
|
||||
receiver: Mutex::new(receiver),
|
||||
handle: RwLock::new(None),
|
||||
complete: StdRwLock::new(None),
|
||||
#[cfg(feature = "console")]
|
||||
console: console::Console::new(),
|
||||
console: console::Console::new(&args),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn worker(self: Arc<Self>) -> Result<()> {
|
||||
let receiver = self.receiver.lock().await;
|
||||
let mut signals = services().server.signal.subscribe();
|
||||
let mut signals = self.server.signal.subscribe();
|
||||
loop {
|
||||
tokio::select! {
|
||||
command = receiver.recv_async() => match command {
|
||||
|
@ -104,9 +115,10 @@ impl Service {
|
|||
}
|
||||
|
||||
pub async fn send_message(&self, message_content: RoomMessageEventContent) {
|
||||
if let Ok(Some(room_id)) = Self::get_admin_room() {
|
||||
let user_id = &services().globals.server_user;
|
||||
respond_to_room(message_content, &room_id, user_id).await;
|
||||
if let Ok(Some(room_id)) = self.get_admin_room() {
|
||||
let user_id = &self.globals.server_user;
|
||||
self.respond_to_room(message_content, &room_id, user_id)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,7 +159,7 @@ impl Service {
|
|||
|
||||
async fn handle_command(&self, command: Command) {
|
||||
match self.process_command(command).await {
|
||||
Ok(Some(output)) => handle_response(output).await,
|
||||
Ok(Some(output)) => self.handle_response(output).await,
|
||||
Ok(None) => debug!("Command successful with no response"),
|
||||
Err(e) => error!("Command processing error: {e}"),
|
||||
}
|
||||
|
@ -163,8 +175,8 @@ impl Service {
|
|||
|
||||
/// Checks whether a given user is an admin of this server
|
||||
pub async fn user_is_admin(&self, user_id: &UserId) -> Result<bool> {
|
||||
if let Ok(Some(admin_room)) = Self::get_admin_room() {
|
||||
services().rooms.state_cache.is_joined(user_id, &admin_room)
|
||||
if let Ok(Some(admin_room)) = self.get_admin_room() {
|
||||
self.state_cache.is_joined(user_id, &admin_room)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
|
@ -174,16 +186,11 @@ impl Service {
|
|||
///
|
||||
/// Errors are propagated from the database, and will have None if there is
|
||||
/// no admin room
|
||||
pub fn get_admin_room() -> Result<Option<OwnedRoomId>> {
|
||||
if let Some(room_id) = services()
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_local_alias(&services().globals.admin_alias)?
|
||||
{
|
||||
if services()
|
||||
.rooms
|
||||
pub fn get_admin_room(&self) -> Result<Option<OwnedRoomId>> {
|
||||
if let Some(room_id) = self.alias.resolve_local_alias(&self.globals.admin_alias)? {
|
||||
if self
|
||||
.state_cache
|
||||
.is_joined(&services().globals.server_user, &room_id)?
|
||||
.is_joined(&self.globals.server_user, &room_id)?
|
||||
{
|
||||
return Ok(Some(room_id));
|
||||
}
|
||||
|
@ -191,142 +198,133 @@ impl Service {
|
|||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_response(content: RoomMessageEventContent) {
|
||||
let Some(Relation::Reply {
|
||||
in_reply_to,
|
||||
}) = content.relates_to.as_ref()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
async fn handle_response(&self, content: RoomMessageEventContent) {
|
||||
let Some(Relation::Reply {
|
||||
in_reply_to,
|
||||
}) = content.relates_to.as_ref()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(Some(pdu)) = services().rooms.timeline.get_pdu(&in_reply_to.event_id) else {
|
||||
return;
|
||||
};
|
||||
let Ok(Some(pdu)) = self.timeline.get_pdu(&in_reply_to.event_id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let response_sender = if is_admin_room(&pdu.room_id) {
|
||||
&services().globals.server_user
|
||||
} else {
|
||||
&pdu.sender
|
||||
};
|
||||
let response_sender = if self.is_admin_room(&pdu.room_id) {
|
||||
&self.globals.server_user
|
||||
} else {
|
||||
&pdu.sender
|
||||
};
|
||||
|
||||
respond_to_room(content, &pdu.room_id, response_sender).await;
|
||||
}
|
||||
self.respond_to_room(content, &pdu.room_id, response_sender)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn respond_to_room(content: RoomMessageEventContent, room_id: &RoomId, user_id: &UserId) {
|
||||
assert!(
|
||||
services()
|
||||
.admin
|
||||
.user_is_admin(user_id)
|
||||
async fn respond_to_room(&self, content: RoomMessageEventContent, room_id: &RoomId, user_id: &UserId) {
|
||||
assert!(
|
||||
self.user_is_admin(user_id)
|
||||
.await
|
||||
.expect("checked user is admin"),
|
||||
"sender is not admin"
|
||||
);
|
||||
|
||||
let state_lock = self.state.mutex.lock(room_id).await;
|
||||
let response_pdu = PduBuilder {
|
||||
event_type: TimelineEventType::RoomMessage,
|
||||
content: to_raw_value(&content).expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: None,
|
||||
redacts: None,
|
||||
};
|
||||
|
||||
if let Err(e) = self
|
||||
.timeline
|
||||
.build_and_append_pdu(response_pdu, user_id, room_id, &state_lock)
|
||||
.await
|
||||
.expect("checked user is admin"),
|
||||
"sender is not admin"
|
||||
);
|
||||
{
|
||||
self.handle_response_error(e, room_id, user_id, &state_lock)
|
||||
.await
|
||||
.unwrap_or_else(default_log);
|
||||
}
|
||||
}
|
||||
|
||||
let state_lock = services().rooms.state.mutex.lock(room_id).await;
|
||||
let response_pdu = PduBuilder {
|
||||
event_type: TimelineEventType::RoomMessage,
|
||||
content: to_raw_value(&content).expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: None,
|
||||
redacts: None,
|
||||
};
|
||||
async fn handle_response_error(
|
||||
&self, e: Error, room_id: &RoomId, user_id: &UserId, state_lock: &RoomMutexGuard,
|
||||
) -> Result<()> {
|
||||
error!("Failed to build and append admin room response PDU: \"{e}\"");
|
||||
let error_room_message = RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to build and append admin room PDU: \"{e}\"\n\nThe original admin command may have finished \
|
||||
successfully, but we could not return the output."
|
||||
));
|
||||
|
||||
if let Err(e) = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(response_pdu, user_id, room_id, &state_lock)
|
||||
.await
|
||||
{
|
||||
handle_response_error(e, room_id, user_id, &state_lock)
|
||||
.await
|
||||
.unwrap_or_else(default_log);
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_response_error(
|
||||
e: Error, room_id: &RoomId, user_id: &UserId, state_lock: &RoomMutexGuard,
|
||||
) -> Result<()> {
|
||||
error!("Failed to build and append admin room response PDU: \"{e}\"");
|
||||
let error_room_message = RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to build and append admin room PDU: \"{e}\"\n\nThe original admin command may have finished \
|
||||
successfully, but we could not return the output."
|
||||
));
|
||||
|
||||
let response_pdu = PduBuilder {
|
||||
event_type: TimelineEventType::RoomMessage,
|
||||
content: to_raw_value(&error_room_message).expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: None,
|
||||
redacts: None,
|
||||
};
|
||||
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(response_pdu, user_id, room_id, state_lock)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn is_admin_command(pdu: &PduEvent, body: &str) -> bool {
|
||||
// Server-side command-escape with public echo
|
||||
let is_escape = body.starts_with('\\');
|
||||
let is_public_escape = is_escape && body.trim_start_matches('\\').starts_with("!admin");
|
||||
|
||||
// Admin command with public echo (in admin room)
|
||||
let server_user = &services().globals.server_user;
|
||||
let is_public_prefix = body.starts_with("!admin") || body.starts_with(server_user.as_str());
|
||||
|
||||
// Expected backward branch
|
||||
if !is_public_escape && !is_public_prefix {
|
||||
return false;
|
||||
}
|
||||
|
||||
// only allow public escaped commands by local admins
|
||||
if is_public_escape && !user_is_local(&pdu.sender) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if server-side command-escape is disabled by configuration
|
||||
if is_public_escape && !services().globals.config.admin_escape_commands {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent unescaped !admin from being used outside of the admin room
|
||||
if is_public_prefix && !is_admin_room(&pdu.room_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only senders who are admin can proceed
|
||||
if !services()
|
||||
.admin
|
||||
.user_is_admin(&pdu.sender)
|
||||
.await
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// This will evaluate to false if the emergency password is set up so that
|
||||
// the administrator can execute commands as conduit
|
||||
let emergency_password_set = services().globals.emergency_password().is_some();
|
||||
let from_server = pdu.sender == *server_user && !emergency_password_set;
|
||||
if from_server && is_admin_room(&pdu.room_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Authentic admin command
|
||||
true
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_admin_room(room_id: &RoomId) -> bool {
|
||||
if let Ok(Some(admin_room_id)) = Service::get_admin_room() {
|
||||
admin_room_id == room_id
|
||||
} else {
|
||||
false
|
||||
let response_pdu = PduBuilder {
|
||||
event_type: TimelineEventType::RoomMessage,
|
||||
content: to_raw_value(&error_room_message).expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: None,
|
||||
redacts: None,
|
||||
};
|
||||
|
||||
self.timeline
|
||||
.build_and_append_pdu(response_pdu, user_id, room_id, state_lock)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn is_admin_command(&self, pdu: &PduEvent, body: &str) -> bool {
|
||||
// Server-side command-escape with public echo
|
||||
let is_escape = body.starts_with('\\');
|
||||
let is_public_escape = is_escape && body.trim_start_matches('\\').starts_with("!admin");
|
||||
|
||||
// Admin command with public echo (in admin room)
|
||||
let server_user = &self.globals.server_user;
|
||||
let is_public_prefix = body.starts_with("!admin") || body.starts_with(server_user.as_str());
|
||||
|
||||
// Expected backward branch
|
||||
if !is_public_escape && !is_public_prefix {
|
||||
return false;
|
||||
}
|
||||
|
||||
// only allow public escaped commands by local admins
|
||||
if is_public_escape && !user_is_local(&pdu.sender) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if server-side command-escape is disabled by configuration
|
||||
if is_public_escape && !self.globals.config.admin_escape_commands {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent unescaped !admin from being used outside of the admin room
|
||||
if is_public_prefix && !self.is_admin_room(&pdu.room_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only senders who are admin can proceed
|
||||
if !self.user_is_admin(&pdu.sender).await.unwrap_or(false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This will evaluate to false if the emergency password is set up so that
|
||||
// the administrator can execute commands as conduit
|
||||
let emergency_password_set = self.globals.emergency_password().is_some();
|
||||
let from_server = pdu.sender == *server_user && !emergency_password_set;
|
||||
if from_server && self.is_admin_room(&pdu.room_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Authentic admin command
|
||||
true
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_admin_room(&self, room_id: &RoomId) -> bool {
|
||||
if let Ok(Some(admin_room_id)) = self.get_admin_room() {
|
||||
admin_room_id == room_id
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ async fn fresh(db: &Arc<Database>, config: &Config) -> Result<()> {
|
|||
db["global"].insert(b"retroactively_fix_bad_data_from_roomuserid_joined", &[])?;
|
||||
|
||||
// Create the admin room and server user on first run
|
||||
crate::admin::create_admin_room().await?;
|
||||
crate::admin::create_admin_room(services).await?;
|
||||
|
||||
warn!(
|
||||
"Created new {} database with version {DATABASE_VERSION}",
|
||||
|
|
|
@ -38,7 +38,6 @@ use serde_json::value::{to_raw_value, RawValue as RawJsonValue};
|
|||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::{
|
||||
admin,
|
||||
appservice::NamespaceRegex,
|
||||
pdu::{EventHash, PduBuilder},
|
||||
rooms::{event_handler::parse_incoming_pdu, state_compressor::CompressedStateEvent},
|
||||
|
@ -485,7 +484,7 @@ impl Service {
|
|||
.search
|
||||
.index_pdu(shortroomid, &pdu_id, &body)?;
|
||||
|
||||
if admin::is_admin_command(pdu, &body).await {
|
||||
if services().admin.is_admin_command(pdu, &body).await {
|
||||
services()
|
||||
.admin
|
||||
.command(body, Some((*pdu.event_id).into()))
|
||||
|
@ -784,7 +783,7 @@ impl Service {
|
|||
state_lock: &RoomMutexGuard, // Take mutex guard to make sure users get the room state mutex
|
||||
) -> Result<Arc<EventId>> {
|
||||
let (pdu, pdu_json) = self.create_hash_and_sign_event(pdu_builder, sender, room_id, state_lock)?;
|
||||
if let Some(admin_room) = admin::Service::get_admin_room()? {
|
||||
if let Some(admin_room) = services().admin.get_admin_room()? {
|
||||
if admin_room == room_id {
|
||||
match pdu.event_type() {
|
||||
TimelineEventType::RoomEncryption => {
|
||||
|
|
|
@ -247,7 +247,7 @@ impl Service {
|
|||
|
||||
/// Check if a user is an admin
|
||||
pub fn is_admin(&self, user_id: &UserId) -> Result<bool> {
|
||||
if let Some(admin_room_id) = crate::admin::Service::get_admin_room()? {
|
||||
if let Some(admin_room_id) = services().admin.get_admin_room()? {
|
||||
services()
|
||||
.rooms
|
||||
.state_cache
|
||||
|
|
Loading…
Add table
Reference in a new issue