Skip to content

Commit d4aeab4

Browse files
committed
feat(commands): restrict maintenance mode to admins only
1 parent fb78a09 commit d4aeab4

7 files changed

Lines changed: 127 additions & 4 deletions

File tree

rustmail/src/commands/status/common.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use strum::EnumString;
22

3-
#[derive(EnumString)]
3+
#[derive(EnumString, PartialEq)]
44
#[strum(serialize_all = "snake_case")]
55
pub enum BotStatus {
66
Online,

rustmail/src/commands/status/slash_command/status.rs

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,66 @@
11
use crate::commands::status::BotStatus;
22
use crate::commands::{BoxFuture, RegistrableCommand};
33
use crate::config::Config;
4-
use crate::errors::ModmailResult;
4+
use crate::errors::{CommandError, ModmailError, ModmailResult};
55
use crate::handlers::InteractionHandler;
66
use crate::i18n::get_translated_message;
77
use crate::utils::{MessageBuilder, defer_response};
88
use serenity::FutureExt;
99
use serenity::all::{
1010
ActivityData, CommandDataOptionValue, CommandInteraction, CommandOptionType, Context,
11-
CreateCommand, CreateCommandOption, ResolvedOption,
11+
CreateCommand, CreateCommandOption, Permissions, ResolvedOption,
1212
};
1313
use std::str::FromStr;
1414
use std::sync::Arc;
1515
use std::sync::atomic::Ordering;
1616

17+
async fn is_interaction_user_admin_or_super_admin(
18+
ctx: &Context,
19+
command: &CommandInteraction,
20+
config: &Config,
21+
) -> bool {
22+
let user_id = command.user.id.get();
23+
24+
if config.bot.panel_super_admin_users.contains(&user_id) {
25+
return true;
26+
}
27+
28+
let guild_id = match command.guild_id {
29+
Some(id) => id,
30+
None => return false,
31+
};
32+
33+
let member = match guild_id.member(&ctx.http, command.user.id).await {
34+
Ok(m) => m,
35+
Err(_) => return false,
36+
};
37+
38+
if !config.bot.panel_super_admin_roles.is_empty() {
39+
for role_id in &member.roles {
40+
if config.bot.panel_super_admin_roles.contains(&role_id.get()) {
41+
return true;
42+
}
43+
}
44+
}
45+
46+
let guild = match guild_id.to_partial_guild(&ctx.http).await {
47+
Ok(g) => g,
48+
Err(_) => return false,
49+
};
50+
51+
if guild.owner_id == command.user.id {
52+
return true;
53+
}
54+
55+
member.roles.iter().any(|role_id| {
56+
guild
57+
.roles
58+
.get(role_id)
59+
.map(|role| role.permissions.contains(Permissions::ADMINISTRATOR))
60+
.unwrap_or(false)
61+
})
62+
}
63+
1764
pub struct StatusCommand;
1865

1966
impl RegistrableCommand for StatusCommand {
@@ -136,6 +183,16 @@ impl RegistrableCommand for StatusCommand {
136183
Err(_) => return Ok(()),
137184
};
138185

186+
if bot_status == BotStatus::Maintenance {
187+
let is_allowed =
188+
is_interaction_user_admin_or_super_admin(&ctx, &command, &config).await;
189+
if !is_allowed {
190+
return Err(ModmailError::Command(
191+
CommandError::MaintenanceModeNotAllowed,
192+
));
193+
}
194+
}
195+
139196
let message_key = match bot_status {
140197
BotStatus::Online => {
141198
handler.maintenance_mode.store(false, Ordering::Relaxed);

rustmail/src/commands/status/text_command/status.rs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,54 @@ use crate::errors::{CommandError, ModmailError, ModmailResult};
44
use crate::handlers::GuildMessagesHandler;
55
use crate::i18n::get_translated_message;
66
use crate::utils::{MessageBuilder, extract_reply_content};
7-
use serenity::all::{ActivityData, Context, Message};
7+
use serenity::all::{ActivityData, Context, Message, Permissions};
88
use std::str::FromStr;
99
use std::sync::Arc;
1010
use std::sync::atomic::Ordering;
1111

12+
async fn is_user_admin_or_super_admin(ctx: &Context, msg: &Message, config: &Config) -> bool {
13+
let user_id = msg.author.id.get();
14+
15+
if config.bot.panel_super_admin_users.contains(&user_id) {
16+
return true;
17+
}
18+
19+
let guild_id = match msg.guild_id {
20+
Some(id) => id,
21+
None => return false,
22+
};
23+
24+
let member = match guild_id.member(&ctx.http, msg.author.id).await {
25+
Ok(m) => m,
26+
Err(_) => return false,
27+
};
28+
29+
if !config.bot.panel_super_admin_roles.is_empty() {
30+
for role_id in &member.roles {
31+
if config.bot.panel_super_admin_roles.contains(&role_id.get()) {
32+
return true;
33+
}
34+
}
35+
}
36+
37+
let guild = match guild_id.to_partial_guild(&ctx.http).await {
38+
Ok(g) => g,
39+
Err(_) => return false,
40+
};
41+
42+
if guild.owner_id == msg.author.id {
43+
return true;
44+
}
45+
46+
member.roles.iter().any(|role_id| {
47+
guild
48+
.roles
49+
.get(role_id)
50+
.map(|role| role.permissions.contains(Permissions::ADMINISTRATOR))
51+
.unwrap_or(false)
52+
})
53+
}
54+
1255
pub async fn status_command(
1356
ctx: Context,
1457
msg: Message,
@@ -25,6 +68,14 @@ pub async fn status_command(
2568
Err(_) => return Err(ModmailError::Command(CommandError::InvalidStatusValue)),
2669
};
2770

71+
if bot_status == BotStatus::Maintenance
72+
&& !is_user_admin_or_super_admin(&ctx, &msg, config).await
73+
{
74+
return Err(ModmailError::Command(
75+
CommandError::MaintenanceModeNotAllowed,
76+
));
77+
}
78+
2879
let message_key = match bot_status {
2980
BotStatus::Online => {
3081
handler.maintenance_mode.store(false, Ordering::Relaxed);

rustmail/src/errors/dictionary.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,9 @@ impl DictionaryManager {
251251
}
252252
CommandError::StatusIsMissing => ("status.status_is_missing".to_string(), None),
253253
CommandError::InvalidStatusValue => ("status.invalid_status".to_string(), None),
254+
CommandError::MaintenanceModeNotAllowed => {
255+
("status.maintenance_not_allowed".to_string(), None)
256+
}
254257
_ => ("command.invalid_format".to_string(), None),
255258
},
256259
ModmailError::Thread(thread_err) => match thread_err {

rustmail/src/errors/types.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ pub enum CommandError {
7070
SnippetContentTooLong,
7171
StatusIsMissing,
7272
InvalidStatusValue,
73+
MaintenanceModeNotAllowed,
7374
}
7475

7576
#[derive(Debug, Clone)]
@@ -186,6 +187,9 @@ impl fmt::Display for CommandError {
186187
}
187188
CommandError::StatusIsMissing => write!(f, "Status is missing"),
188189
CommandError::InvalidStatusValue => write!(f, "Invalid status value"),
190+
CommandError::MaintenanceModeNotAllowed => {
191+
write!(f, "Only admins can enable maintenance mode")
192+
}
189193
}
190194
}
191195
}

rustmail/src/i18n/language/en.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1806,6 +1806,10 @@ pub fn load_english_messages(dict: &mut ErrorDictionary) {
18061806
"status.maintenance_mode_active_user".to_string(),
18071807
DictionaryMessage::new("🔧 The support system is currently undergoing maintenance. Your message could not be processed. Please try again later."),
18081808
);
1809+
dict.messages.insert(
1810+
"status.maintenance_not_allowed".to_string(),
1811+
DictionaryMessage::new("Only administrators can enable maintenance mode."),
1812+
);
18091813
dict.messages.insert(
18101814
"status.maintenance_activity".to_string(),
18111815
DictionaryMessage::new("🔧 Maintenance in progress"),

rustmail/src/i18n/language/fr.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1821,6 +1821,10 @@ pub fn load_french_messages(dict: &mut ErrorDictionary) {
18211821
"status.maintenance_mode_active_user".to_string(),
18221822
DictionaryMessage::new("🔧 Le système de support est actuellement en maintenance. Votre message n'a pas pu être traité. Veuillez réessayer plus tard."),
18231823
);
1824+
dict.messages.insert(
1825+
"status.maintenance_not_allowed".to_string(),
1826+
DictionaryMessage::new("Seuls les administrateurs peuvent activer le mode maintenance."),
1827+
);
18241828
dict.messages.insert(
18251829
"status.maintenance_activity".to_string(),
18261830
DictionaryMessage::new("🔧 Maintenance en cours"),

0 commit comments

Comments
 (0)