use crate::bits::Permissions;
use crate::cache::CachedGuild;
use crate::types::Channel;
pub fn compute_permissions(guild: &CachedGuild, channel: &Channel, user_id: &str) -> u64 {
const ALL: u64 = Permissions::ALL;
if channel.guild_id.as_deref() != Some(guild.id.as_str()) {
return 0;
}
let Some(member) = guild.members.get(user_id) else {
return 0;
};
if guild.owner_id == user_id || member.is_admin {
return ALL;
}
let mut base = guild.roles.get(&guild.id).map_or(0, |r| r.permissions);
for r in &member.roles {
base |= guild.roles.get(r).map_or(0, |r| r.permissions);
}
if base & Permissions::ADMINISTRATOR != 0 {
return ALL;
}
let overwrites = channel.permission_overwrites.as_deref().unwrap_or(&[]);
let mut p = base;
if let Some(everyone) = overwrites.iter().find(|o| o.kind == 0 && o.id == guild.id) {
p = (p & !everyone.deny) | everyone.allow;
}
let (mut allow, mut deny) = (0u64, 0u64);
for o in overwrites {
if o.kind == 0 && o.id != guild.id && member.roles.contains(&o.id) {
allow |= o.allow;
deny |= o.deny;
}
}
p = (p & !deny) | allow;
if let Some(own) = overwrites.iter().find(|o| o.kind == 1 && o.id == user_id) {
p = (p & !own.deny) | own.allow;
}
if p & Permissions::VIEW_CHANNEL == 0 {
return 0;
}
if p & Permissions::SEND_MESSAGES == 0 {
p &= !Permissions::ATTACH_FILES;
}
if p & Permissions::CONNECT == 0 {
p &= !(Permissions::SPEAK | Permissions::STREAM);
}
p
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{Member, Overwrite, Role, User};
use Permissions as P;
const G: &str = "100";
const MOD: &str = "200";
const MUTED: &str = "201";
fn guild(everyone: u64) -> CachedGuild {
let mut g = CachedGuild {
id: G.into(),
owner_id: "1".into(),
..Default::default()
};
for (id, perms) in [
(G, everyone),
(MOD, P::KICK_MEMBERS | P::MANAGE_MESSAGES),
(MUTED, 0),
] {
g.roles.insert(
id.into(),
Role {
id: id.into(),
guild_id: G.into(),
permissions: perms,
..Default::default()
},
);
}
for (uid, roles, admin) in [
("1", vec![], false),
("2", vec![], false),
("3", vec![MOD], false),
("4", vec![MUTED], false),
("5", vec![], true),
] {
g.members.insert(
uid.into(),
Member {
guild_id: G.into(),
user: User {
id: uid.into(),
..Default::default()
},
is_admin: admin,
roles: roles.into_iter().map(String::from).collect(),
..Default::default()
},
);
}
g
}
fn channel(overwrites: Vec<Overwrite>) -> Channel {
Channel {
id: "300".into(),
guild_id: Some(G.into()),
permission_overwrites: Some(overwrites),
..Default::default()
}
}
fn ow(id: &str, kind: u8, allow: u64, deny: u64) -> Overwrite {
Overwrite {
id: id.into(),
kind,
allow,
deny,
}
}
const BASIC: u64 = P::VIEW_CHANNEL
| P::SEND_MESSAGES
| P::ATTACH_FILES
| P::CONNECT
| P::SPEAK
| P::READ_MESSAGE_HISTORY;
#[test]
fn owner_and_admins_get_everything() {
let g = guild(BASIC);
let c = channel(vec![ow(G, 0, 0, P::VIEW_CHANNEL)]);
assert_eq!(compute_permissions(&g, &c, "1"), P::ALL);
assert_eq!(compute_permissions(&g, &c, "5"), P::ALL);
}
#[test]
fn administrator_role_bit_gives_everything() {
let mut g = guild(BASIC);
g.roles.get_mut(MOD).unwrap().permissions |= P::ADMINISTRATOR;
assert_eq!(compute_permissions(&g, &channel(vec![]), "3"), P::ALL);
}
#[test]
fn roles_add_to_everyone() {
let g = guild(BASIC);
let c = channel(vec![]);
assert_eq!(compute_permissions(&g, &c, "2"), BASIC);
assert_eq!(
compute_permissions(&g, &c, "3"),
BASIC | P::KICK_MEMBERS | P::MANAGE_MESSAGES
);
}
#[test]
fn non_members_and_foreign_channels_get_nothing() {
let g = guild(BASIC);
assert_eq!(compute_permissions(&g, &channel(vec![]), "999"), 0);
let mut other = channel(vec![]);
other.guild_id = Some("555".into());
assert_eq!(compute_permissions(&g, &other, "2"), 0);
}
#[test]
fn overwrites_apply_everyone_then_roles_then_member() {
let g = guild(BASIC);
let c = channel(vec![
ow(G, 0, 0, P::SEND_MESSAGES),
ow(MUTED, 0, 0, P::SPEAK),
ow(MOD, 0, P::SEND_MESSAGES, 0),
ow("2", 1, P::SEND_MESSAGES, 0),
]);
assert_eq!(
compute_permissions(&g, &c, "4"),
BASIC & !(P::SEND_MESSAGES | P::ATTACH_FILES | P::SPEAK)
);
assert_eq!(
compute_permissions(&g, &c, "3"),
BASIC | P::KICK_MEMBERS | P::MANAGE_MESSAGES
);
assert_eq!(compute_permissions(&g, &c, "2"), BASIC);
}
#[test]
fn role_allow_wins_over_role_deny() {
let mut g = guild(BASIC);
g.members.get_mut("4").unwrap().roles.push(MOD.into());
let c = channel(vec![
ow(MUTED, 0, 0, P::SEND_MESSAGES),
ow(MOD, 0, P::SEND_MESSAGES, 0),
]);
assert!(compute_permissions(&g, &c, "4") & P::SEND_MESSAGES != 0);
}
#[test]
fn member_overwrite_beats_roles() {
let g = guild(BASIC);
let c = channel(vec![
ow(MOD, 0, P::MANAGE_MESSAGES, 0),
ow("3", 1, 0, P::MANAGE_MESSAGES | P::SEND_MESSAGES),
]);
let p = compute_permissions(&g, &c, "3");
assert_eq!(p & P::MANAGE_MESSAGES, 0);
assert_eq!(p & P::ATTACH_FILES, 0);
}
#[test]
fn no_view_means_nothing() {
let g = guild(BASIC);
let c = channel(vec![ow(G, 0, 0, P::VIEW_CHANNEL)]);
assert_eq!(compute_permissions(&g, &c, "2"), 0);
let c = channel(vec![
ow(G, 0, 0, P::VIEW_CHANNEL),
ow("2", 1, P::VIEW_CHANNEL, 0),
]);
assert_eq!(compute_permissions(&g, &c, "2"), BASIC);
}
#[test]
fn no_connect_drops_speak_and_stream() {
let g = guild(BASIC | P::STREAM);
let c = channel(vec![ow(G, 0, 0, P::CONNECT)]);
let p = compute_permissions(&g, &c, "2");
assert_eq!(p & (P::CONNECT | P::SPEAK | P::STREAM), 0);
assert!(p & P::VIEW_CHANNEL != 0);
}
}