Skip to content

Commit

Permalink
feat: consider role hierarchy
Browse files Browse the repository at this point in the history
  • Loading branch information
GenericNerd committed May 22, 2024
1 parent d926b9e commit 3efd75c
Show file tree
Hide file tree
Showing 13 changed files with 175 additions and 13 deletions.
48 changes: 37 additions & 11 deletions src/commands/config/moderation.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use serenity::{
all::{
ActionRowComponent, ButtonStyle, CommandInteraction, ComponentInteractionDataKind,
InputTextStyle,
InputTextStyle, Permissions,
},
builder::{
CreateActionRow, CreateButton, CreateEmbed, CreateInputText, CreateInteractionResponse,
Expand Down Expand Up @@ -606,19 +606,45 @@ impl ConfigStage for ModerationMuteRole {
if let ComponentInteractionDataKind::RoleSelect { values } =
interaction.data.kind
{
let role = values
.first()
.ok_or_else(|| {
ResponseError::Execution(
"No role selected",
Some("Please select a role.".to_string()),
)
})?
.get() as i64;
let role = values.first().ok_or_else(|| {
ResponseError::Execution(
"No role selected",
Some("Please select a role.".to_string()),
)
})?;

let role_position = match ctx.guild.roles.get(role) {
Some(role) => {
if role.permissions.contains(Permissions::ADMINISTRATOR) {
u16::max_value() - 1
} else {
role.position
}
}
None => {
return Err(ConfigError {
error: ResponseError::Execution(
"Invalid role",
Some("Please select a valid role.".to_string()),
),
stages_to_skip: None,
})
}
};

if role_position >= ctx.highest_role {
return Err(ConfigError {
error: ResponseError::Execution(
"Cannot set this role",
Some("The role you have selected is higher than yours, please move it and try again.".to_string()),
),
stages_to_skip: None,
});
}

sqlx::query!(
"UPDATE moderation_configuration SET mute_role = $1 WHERE guild_id = $2",
role,
role.get() as i64,
ctx.guild.id.get() as i64
)
.execute(&handler.main_database)
Expand Down
23 changes: 23 additions & 0 deletions src/commands/moderation/ban.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::{
command::{Command, CommandContext, CommandContextReply},
config::LoggingConfig,
handler::Handler,
highest_role::get_highest_role,
permissions::Permission,
response::{Response, ResponseError, ResponseResult},
},
Expand Down Expand Up @@ -245,6 +246,28 @@ impl Command for BanCommand {
None => Duration::permanent(),
};

let target_user_highest_role = get_highest_role(ctx, &user).await;
if ctx.highest_role <= target_user_highest_role {
return Err(ResponseError::Execution(
"You cannot ban this user!",
Some(
"You cannot ban a user with a role equal to or higher than yours.".to_string(),
),
));
}

let bot = ctx.ctx.cache.current_user().to_owned();
let bot_highest_role = get_highest_role(ctx, &bot).await;
if bot_highest_role <= target_user_highest_role {
return Err(ResponseError::Execution(
"Reaper cannot ban this user!",
Some(
"Reaper cannot ban a user with a role equal to or higher than itself."
.to_string(),
),
));
}

let action = handler
.ban_user(
ctx,
Expand Down
23 changes: 23 additions & 0 deletions src/commands/moderation/kick.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::{
command::{Command, CommandContext, CommandContextReply},
config::LoggingConfig,
handler::Handler,
highest_role::get_highest_role,
permissions::Permission,
response::{Response, ResponseError, ResponseResult},
},
Expand Down Expand Up @@ -207,6 +208,28 @@ impl Command for KickCommand {
));
};

let target_user_highest_role = get_highest_role(ctx, &user).await;
if ctx.highest_role <= target_user_highest_role {
return Err(ResponseError::Execution(
"You cannot kick this user!",
Some(
"You cannot kick a user with a role equal to or higher than yours.".to_string(),
),
));
}

let bot = ctx.ctx.cache.current_user().to_owned();
let bot_highest_role = get_highest_role(ctx, &bot).await;
if bot_highest_role <= target_user_highest_role {
return Err(ResponseError::Execution(
"Reaper cannot kick this user!",
Some(
"Reaper cannot kick a user with a role equal to or higher than itself."
.to_string(),
),
));
}

let action = handler
.kick_user(
ctx,
Expand Down
11 changes: 11 additions & 0 deletions src/commands/moderation/mute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use crate::{
command::{Command, CommandContext, CommandContextReply},
config::LoggingConfig,
handler::Handler,
highest_role::get_highest_role,
permissions::Permission,
response::{Response, ResponseError, ResponseResult},
},
Expand Down Expand Up @@ -270,6 +271,16 @@ impl Command for MuteCommand {
));
};

let target_user_highest_role = get_highest_role(ctx, &user).await;
if ctx.highest_role <= target_user_highest_role {
return Err(ResponseError::Execution(
"You cannot mute this user!",
Some(
"You cannot mute a user with a role equal to or higher than yours.".to_string(),
),
));
}

let action = handler
.mute_user(
ctx,
Expand Down
12 changes: 12 additions & 0 deletions src/commands/moderation/strike.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::{
command::{Command, CommandContext, CommandContextReply},
config::LoggingConfig,
handler::Handler,
highest_role::get_highest_role,
permissions::Permission,
response::{Response, ResponseError, ResponseResult},
},
Expand Down Expand Up @@ -350,6 +351,17 @@ impl Command for StrikeCommand {
.as_deref()
.map(Duration::new);

let target_user_highest_role = get_highest_role(ctx, &user).await;
if ctx.highest_role <= target_user_highest_role {
return Err(ResponseError::Execution(
"You cannot strike this user!",
Some(
"You cannot strike a user with a role equal to or higher than yours."
.to_string(),
),
));
}

let action = Box::pin(handler.strike_user(
ctx,
ctx.guild.id.get() as i64,
Expand Down
17 changes: 16 additions & 1 deletion src/commands/permissions/role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::{
};

use serenity::{
all::{ButtonStyle, CommandInteraction, ComponentInteractionDataKind},
all::{ButtonStyle, CommandInteraction, ComponentInteractionDataKind, Permissions},
builder::{
CreateActionRow, CreateButton, CreateEmbed, CreateEmbedFooter, CreateInteractionResponse,
CreateSelectMenu, CreateSelectMenuKind, CreateSelectMenuOption,
Expand Down Expand Up @@ -65,6 +65,21 @@ pub async fn role(
return Err(ResponseError::Execution("No member found!", Some("The role option either was not provided, or this command was not ran in a guild. Both of these should not occur, if they do, please contact a developer.".to_string())));
};

let role_position = if role.permissions.contains(Permissions::ADMINISTRATOR) {
u16::max_value() - 1
} else {
role.position
};
if role_position >= ctx.highest_role {
return Err(ResponseError::Execution(
"You cannot edit the permissions of this role!",
Some(
"You cannot edit a role that is equal to or higher than your highest role"
.to_string(),
),
));
}

let existing_permissions = get_role(
handler,
cmd.guild_id.unwrap().get() as i64,
Expand Down
12 changes: 12 additions & 0 deletions src/commands/permissions/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use crate::{
models::{
command::{CommandContext, CommandContextReply, InteractionContext},
handler::Handler,
highest_role::get_highest_role,
permissions::Permission,
response::{Response, ResponseError, ResponseResult},
},
Expand Down Expand Up @@ -66,6 +67,17 @@ pub async fn user(
return Err(ResponseError::Execution("No member found!", Some("The user option either was not provided, or this command was not ran in a guild. Both of these should not occur, if they do, please contact a developer.".to_string())));
};

let target_user_highest_role = get_highest_role(ctx, &user).await;
if ctx.highest_role <= target_user_highest_role {
return Err(ResponseError::Execution(
"You cannot change the permissions of this user!",
Some(
"You cannot change the permissions of a user with a role equal to or higher than yours."
.to_string(),
),
));
}

let has_admin = if let Ok(permissions) = ctx
.guild
.member(&ctx.ctx, user.id)
Expand Down
1 change: 1 addition & 0 deletions src/events/automod_trigger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ impl Handler {
ctx,
has_responsed: Arc::new(AtomicBool::new(false)),
user_permissions: vec![],
highest_role: u16::max_value(),
guild,
};

Expand Down
10 changes: 9 additions & 1 deletion src/events/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ impl Handler {

debug!("Took {:?} to get guild ID and guild", start.elapsed());

let user_permissions = if guild.owner_id == command.user.id {
let mut highest_role = 0;
let user_permissions: Vec<Permission> = if guild.owner_id == command.user.id {
highest_role = u16::max_value();
Permission::iter().collect::<Vec<_>>()
} else {
let mut user_permissions: Vec<Permission> = vec![];
Expand All @@ -78,7 +80,12 @@ impl Handler {
}
for role in command.member.clone().unwrap().roles {
if let Some(role) = guild.roles.get(&role) {
if role.position > highest_role {
highest_role = role.position;
}

if role.permissions.contains(Permissions::ADMINISTRATOR) {
highest_role = u16::max_value() - 1;
user_permissions = Permission::iter().collect::<Vec<_>>();
break;
}
Expand All @@ -99,6 +106,7 @@ impl Handler {
ctx,
has_responsed: Arc::new(AtomicBool::new(true)),
user_permissions,
highest_role,
guild,
};

Expand Down
1 change: 1 addition & 0 deletions src/events/ready.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ impl Handler {
ctx: ctx.clone(),
has_responsed: Arc::new(AtomicBool::new(false)),
user_permissions: vec![],
highest_role: u16::max_value(),
guild,
};

Expand Down
1 change: 1 addition & 0 deletions src/models/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub struct CommandContext {
pub ctx: IncomingContext,
pub has_responsed: Arc<AtomicBool>,
pub user_permissions: Vec<Permission>,
pub highest_role: u16,
pub guild: PartialGuild,
}

Expand Down
28 changes: 28 additions & 0 deletions src/models/highest_role.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use serenity::all::{Permissions, User};

use super::command::CommandContext;

pub async fn get_highest_role(ctx: &CommandContext, user: &User) -> u16 {
if ctx.guild.owner_id == user.id {
return u16::max_value();
}

let Ok(member) = ctx.guild.member(&ctx.ctx.http, user.id).await else {
return 0;
};

let mut highest_role = 0;
for role in member.roles {
if let Some(role) = ctx.guild.roles.get(&role) {
if role.permissions.contains(Permissions::ADMINISTRATOR) {
return u16::max_value() - 1;
}

if role.position > highest_role {
highest_role = role.position;
}
}
}

highest_role
}
1 change: 1 addition & 0 deletions src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod command;
pub mod config;
pub mod giveaway;
pub mod handler;
pub mod highest_role;
pub mod message;
pub mod permissions;
pub mod response;

0 comments on commit 3efd75c

Please sign in to comment.