use gbp_core::MemberId;
use std::collections::HashMap;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct Permissions(pub u32);
impl Permissions {
pub const SEND_TEXT: u32 = 1 << 0;
pub const SEND_AUDIO: u32 = 1 << 1;
pub const SEND_SIGNAL: u32 = 1 << 2;
pub const MUTE_OTHERS: u32 = 1 << 3;
pub const ASSIGN_ROLES: u32 = 1 << 4;
pub const INVITE: u32 = 1 << 5;
pub const REMOVE_MEMBERS: u32 = 1 << 6;
pub const CLOSE_GROUP: u32 = 1 << 7;
pub const fn has(self, mask: u32) -> bool {
self.0 & mask == mask
}
pub const fn with(self, mask: u32) -> Self {
Self(self.0 | mask)
}
pub const fn without(self, mask: u32) -> Self {
Self(self.0 & !mask)
}
}
impl core::ops::BitOr<u32> for Permissions {
type Output = Self;
fn bitor(self, rhs: u32) -> Self::Output {
Self(self.0 | rhs)
}
}
#[derive(Clone, Debug)]
pub struct RoleSpec {
pub id: u32,
pub name: String,
pub permissions: Permissions,
}
#[derive(Debug, thiserror::Error)]
pub enum RoleError {
#[error("unknown role: {0}")]
UnknownRole(u32),
#[error("member {member} lacks permission 0x{permission:08X}")]
Unauthorised {
member: MemberId,
permission: u32,
},
}
#[derive(Default)]
pub struct RoleRegistry {
roles: HashMap<u32, RoleSpec>,
assignments: HashMap<MemberId, u32>,
}
impl RoleRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn define(&mut self, spec: RoleSpec) {
self.roles.insert(spec.id, spec);
}
pub fn define_role(&mut self, id: u32, name: impl Into<String>, permissions: Permissions) {
self.define(RoleSpec {
id,
name: name.into(),
permissions,
});
}
pub fn role(&self, id: u32) -> Option<&RoleSpec> {
self.roles.get(&id)
}
pub fn roles(&self) -> impl Iterator<Item = &RoleSpec> {
self.roles.values()
}
pub fn assign(&mut self, member: MemberId, role_id: u32) -> Result<(), RoleError> {
if !self.roles.contains_key(&role_id) {
return Err(RoleError::UnknownRole(role_id));
}
self.assignments.insert(member, role_id);
Ok(())
}
pub fn role_of(&self, member: MemberId) -> Option<&RoleSpec> {
let id = self.assignments.get(&member)?;
self.roles.get(id)
}
pub fn permissions_of(&self, member: MemberId) -> Permissions {
self.role_of(member)
.map(|r| r.permissions)
.unwrap_or_default()
}
pub fn require(&self, member: MemberId, mask: u32) -> Result<(), RoleError> {
if self.permissions_of(member).has(mask) {
Ok(())
} else {
Err(RoleError::Unauthorised {
member,
permission: mask,
})
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn permissions_and_require() {
let mut r = RoleRegistry::new();
r.define_role(1, "viewer", Permissions::default());
r.define_role(
10,
"speaker",
Permissions::default() | Permissions::SEND_TEXT | Permissions::SEND_AUDIO,
);
r.define_role(
100,
"admin",
Permissions::default()
| Permissions::SEND_TEXT
| Permissions::SEND_AUDIO
| Permissions::MUTE_OTHERS
| Permissions::ASSIGN_ROLES,
);
r.assign(2, 10).unwrap();
r.assign(3, 100).unwrap();
assert!(r.require(2, Permissions::SEND_TEXT).is_ok());
assert!(r.require(2, Permissions::MUTE_OTHERS).is_err());
assert!(r.require(3, Permissions::MUTE_OTHERS).is_ok());
}
#[test]
fn unknown_role_rejected() {
let mut r = RoleRegistry::new();
let err = r.assign(1, 42).unwrap_err();
assert!(matches!(err, RoleError::UnknownRole(42)));
}
}