mod bitops;
mod preset;
use self::preset::{
PERMISSIONS_MESSAGING, PERMISSIONS_ROOT_ONLY, PERMISSIONS_STAGE_OMIT, PERMISSIONS_TEXT_OMIT,
PERMISSIONS_VOICE_OMIT,
};
use twilight_model::{
channel::{
ChannelType,
permission_overwrite::{PermissionOverwrite, PermissionOverwriteType},
},
guild::Permissions,
id::{
Id,
marker::{GuildMarker, RoleMarker, UserMarker},
},
};
#[derive(Clone, Debug, Eq, PartialEq)]
#[must_use = "calculators aren't useful if you don't calculate permissions"]
pub struct PermissionCalculator<'a> {
everyone_role: Permissions,
guild_id: Id<GuildMarker>,
member_roles: &'a [(Id<RoleMarker>, Permissions)],
owner_id: Option<Id<UserMarker>>,
user_id: Id<UserMarker>,
}
impl<'a> PermissionCalculator<'a> {
#[must_use = "calculators should be used to calculate permissions"]
pub const fn new(
guild_id: Id<GuildMarker>,
user_id: Id<UserMarker>,
everyone_role: Permissions,
member_roles: &'a [(Id<RoleMarker>, Permissions)],
) -> Self {
Self {
everyone_role,
guild_id,
owner_id: None,
member_roles,
user_id,
}
}
#[must_use = "calculators should be used to calculate permissions"]
pub const fn owner_id(mut self, owner_id: Id<UserMarker>) -> Self {
self.owner_id = Some(owner_id);
self
}
#[must_use = "calculating permissions is only useful if they're used"]
pub const fn root(&self) -> Permissions {
if matches!(self.owner_id, Some(id) if id.get() == self.user_id.get()) {
return Permissions::all();
}
if self.everyone_role.contains(Permissions::ADMINISTRATOR) {
return Permissions::all();
}
let mut permissions = self.everyone_role;
let member_role_count = self.member_roles.len();
let mut idx = 0;
while idx < member_role_count {
let (_, role_permissions) = self.member_roles[idx];
if role_permissions.contains(Permissions::ADMINISTRATOR) {
return Permissions::all();
}
permissions = bitops::insert(permissions, role_permissions);
idx += 1;
}
permissions
}
#[must_use = "calculating permissions is only useful if they're used"]
pub const fn in_channel(
self,
channel_type: ChannelType,
channel_overwrites: &[PermissionOverwrite],
) -> Permissions {
let mut permissions = self.root();
if permissions.contains(Permissions::ADMINISTRATOR) {
return Permissions::all();
}
permissions = bitops::remove(permissions, PERMISSIONS_ROOT_ONLY);
permissions = process_permission_overwrites(
permissions,
channel_overwrites,
self.member_roles,
self.guild_id,
self.user_id,
);
if permissions.is_empty() {
return permissions;
}
permissions = bitops::remove(permissions, PERMISSIONS_ROOT_ONLY);
if matches!(channel_type, ChannelType::GuildStageVoice) {
permissions = bitops::remove(permissions, PERMISSIONS_STAGE_OMIT);
} else if matches!(channel_type, ChannelType::GuildText) {
permissions = bitops::remove(permissions, PERMISSIONS_TEXT_OMIT);
} else if matches!(channel_type, ChannelType::GuildVoice) {
permissions = bitops::remove(permissions, PERMISSIONS_VOICE_OMIT);
}
permissions
}
}
const fn has_role(roles: &[(Id<RoleMarker>, Permissions)], role_id: Id<RoleMarker>) -> bool {
let len = roles.len();
let mut idx = 0;
while idx < len {
let (iter_role_id, _) = roles[idx];
if iter_role_id.get() == role_id.get() {
return true;
}
idx += 1;
}
false
}
const fn process_permission_overwrites(
mut permissions: Permissions,
channel_overwrites: &[PermissionOverwrite],
member_roles: &[(Id<RoleMarker>, Permissions)],
configured_guild_id: Id<GuildMarker>,
configured_user_id: Id<UserMarker>,
) -> Permissions {
let mut member_allow = Permissions::empty();
let mut member_deny = Permissions::empty();
let mut roles_allow = Permissions::empty();
let mut roles_deny = Permissions::empty();
let channel_overwrite_len = channel_overwrites.len();
let mut idx = 0;
while idx < channel_overwrite_len {
let overwrite = &channel_overwrites[idx];
match overwrite.kind {
PermissionOverwriteType::Role => {
if overwrite.id.get() == configured_guild_id.get() {
permissions = bitops::remove(permissions, overwrite.deny);
permissions = bitops::insert(permissions, overwrite.allow);
idx += 1;
continue;
}
if !has_role(member_roles, overwrite.id.cast()) {
idx += 1;
continue;
}
roles_allow = bitops::insert(roles_allow, overwrite.allow);
roles_deny = bitops::insert(roles_deny, overwrite.deny);
}
PermissionOverwriteType::Member => {
if overwrite.id.get() == configured_user_id.get() {
member_allow = bitops::insert(member_allow, overwrite.allow);
member_deny = bitops::insert(member_deny, overwrite.deny);
}
}
PermissionOverwriteType::Unknown(_) => (),
_ => unimplemented!(),
}
idx += 1;
}
let user_view_allowed = member_allow.contains(Permissions::VIEW_CHANNEL);
let user_view_denied = member_deny.contains(Permissions::VIEW_CHANNEL) && !user_view_allowed;
let role_view_denied = roles_deny.contains(Permissions::VIEW_CHANNEL)
&& !roles_allow.contains(Permissions::VIEW_CHANNEL)
&& !user_view_allowed;
if user_view_denied || role_view_denied {
return Permissions::empty();
}
let user_send_allowed = member_allow.contains(Permissions::SEND_MESSAGES);
let user_send_denied = member_deny.contains(Permissions::SEND_MESSAGES) && !user_send_allowed;
let role_send_denied = roles_deny.contains(Permissions::SEND_MESSAGES)
&& !roles_allow.contains(Permissions::SEND_MESSAGES)
&& !user_send_allowed;
if user_send_denied || role_send_denied {
member_allow = bitops::remove(member_allow, PERMISSIONS_MESSAGING);
roles_allow = bitops::remove(roles_allow, PERMISSIONS_MESSAGING);
permissions = bitops::remove(permissions, PERMISSIONS_MESSAGING);
}
permissions = bitops::remove(permissions, roles_deny);
permissions = bitops::insert(permissions, roles_allow);
permissions = bitops::remove(permissions, member_deny);
permissions = bitops::insert(permissions, member_allow);
permissions
}
#[cfg(test)]
mod tests {
use super::{PermissionCalculator, preset::PERMISSIONS_ROOT_ONLY};
use static_assertions::assert_impl_all;
use std::fmt::Debug;
use twilight_model::{
channel::{
ChannelType,
permission_overwrite::{PermissionOverwrite, PermissionOverwriteType},
},
guild::Permissions,
id::Id,
};
assert_impl_all!(PermissionCalculator<'_>: Clone, Debug, Eq, PartialEq, Send, Sync);
#[test]
fn owner_is_admin() {
let guild_id = Id::new(1);
let user_id = Id::new(2);
let everyone_role = Permissions::SEND_MESSAGES;
let roles = &[];
let calculator =
PermissionCalculator::new(guild_id, user_id, everyone_role, roles).owner_id(user_id);
assert_eq!(Permissions::all(), calculator.root());
}
#[test]
fn view_channel_deny_implicit() {
let guild_id = Id::new(1);
let user_id = Id::new(2);
let everyone_role = Permissions::MENTION_EVERYONE | Permissions::SEND_MESSAGES;
let roles = &[(Id::new(3), Permissions::empty())];
{
let overwrites = &[PermissionOverwrite {
allow: Permissions::SEND_TTS_MESSAGES,
deny: Permissions::VIEW_CHANNEL,
id: Id::new(3),
kind: PermissionOverwriteType::Role,
}];
let calculated = PermissionCalculator::new(guild_id, user_id, everyone_role, roles)
.in_channel(ChannelType::GuildText, overwrites);
assert_eq!(calculated, Permissions::empty());
}
{
let overwrites = &[PermissionOverwrite {
allow: Permissions::SEND_TTS_MESSAGES,
deny: Permissions::VIEW_CHANNEL,
id: Id::new(2),
kind: PermissionOverwriteType::Member,
}];
let calculated = PermissionCalculator::new(guild_id, user_id, everyone_role, roles)
.in_channel(ChannelType::GuildText, overwrites);
assert_eq!(calculated, Permissions::empty());
}
{
let overwrites = &[
PermissionOverwrite {
allow: Permissions::VIEW_CHANNEL,
deny: Permissions::empty(),
id: Id::new(2),
kind: PermissionOverwriteType::Member,
},
PermissionOverwrite {
allow: Permissions::empty(),
deny: Permissions::VIEW_CHANNEL,
id: Id::new(3),
kind: PermissionOverwriteType::Role,
},
];
let calculated = PermissionCalculator::new(guild_id, user_id, everyone_role, roles)
.in_channel(ChannelType::GuildText, overwrites);
assert_eq!(
calculated,
Permissions::VIEW_CHANNEL
| Permissions::SEND_MESSAGES
| Permissions::MENTION_EVERYONE
);
}
}
#[test]
fn remove_text_and_stage_perms_when_voice() {
let guild_id = Id::new(1);
let user_id = Id::new(2);
let everyone_role = Permissions::CONNECT;
let roles = &[(Id::new(3), Permissions::SEND_MESSAGES)];
let calculated = PermissionCalculator::new(guild_id, user_id, everyone_role, roles)
.in_channel(ChannelType::GuildVoice, &[]);
assert_eq!(calculated, Permissions::CONNECT);
}
#[test]
fn remove_audio_perms_when_text() {
let guild_id = Id::new(1);
let user_id = Id::new(2);
let everyone_role = Permissions::CONNECT;
let roles = &[(Id::new(3), Permissions::SEND_MESSAGES)];
let calculated = PermissionCalculator::new(guild_id, user_id, everyone_role, roles)
.in_channel(ChannelType::GuildText, &[]);
assert_eq!(calculated, Permissions::SEND_MESSAGES);
}
#[test]
fn deny_send_messages_removes_related() {
let guild_id = Id::new(1);
let user_id = Id::new(2);
let everyone_role =
Permissions::MANAGE_MESSAGES | Permissions::EMBED_LINKS | Permissions::MENTION_EVERYONE;
let roles = &[(Id::new(3), Permissions::empty())];
let overwrites = &[PermissionOverwrite {
allow: Permissions::ATTACH_FILES,
deny: Permissions::SEND_MESSAGES,
id: Id::new(3),
kind: PermissionOverwriteType::Role,
}];
let calculated = PermissionCalculator::new(guild_id, user_id, everyone_role, roles)
.in_channel(ChannelType::GuildText, overwrites);
assert_eq!(calculated, Permissions::MANAGE_MESSAGES);
}
#[test]
fn admin() {
let member_roles = &[(Id::new(3), Permissions::ADMINISTRATOR)];
let calc =
PermissionCalculator::new(Id::new(1), Id::new(2), Permissions::empty(), member_roles);
assert!(calc.root().is_all());
assert!(calc.in_channel(ChannelType::GuildText, &[]).is_all());
}
#[test]
fn guild_level_removed_in_channel() {
const CHANNEL_TYPES: &[ChannelType] = &[
ChannelType::GuildCategory,
ChannelType::GuildAnnouncement,
ChannelType::GuildStageVoice,
ChannelType::GuildText,
ChannelType::GuildVoice,
];
let mut everyone = PERMISSIONS_ROOT_ONLY;
everyone.remove(Permissions::ADMINISTRATOR);
for kind in CHANNEL_TYPES {
let calc = PermissionCalculator::new(Id::new(1), Id::new(2), everyone, &[]);
let calculated = calc.in_channel(*kind, &[]);
assert!(!calculated.intersects(PERMISSIONS_ROOT_ONLY));
}
}
}