de-global services for admin

Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
Jason Volk 2024-07-20 23:38:20 +00:00
parent 9b20c6918f
commit 992c0a1e58
14 changed files with 221 additions and 227 deletions

View file

@ -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},

View file

@ -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::{

View file

@ -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! {}

View file

@ -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;

View file

@ -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.",

View file

@ -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");
}

View file

@ -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())

View file

@ -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);

View file

@ -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(

View file

@ -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(())
}
}

View file

@ -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
}
}
}

View file

@ -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}",

View file

@ -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 => {

View file

@ -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