1use gbp_core::MemberId;
12use std::collections::HashMap;
13
14#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
18pub struct Permissions(pub u32);
19
20impl Permissions {
21 pub const SEND_TEXT: u32 = 1 << 0;
23 pub const SEND_AUDIO: u32 = 1 << 1;
25 pub const SEND_SIGNAL: u32 = 1 << 2;
27 pub const MUTE_OTHERS: u32 = 1 << 3;
29 pub const ASSIGN_ROLES: u32 = 1 << 4;
31 pub const INVITE: u32 = 1 << 5;
33 pub const REMOVE_MEMBERS: u32 = 1 << 6;
35 pub const CLOSE_GROUP: u32 = 1 << 7;
37
38 pub const fn has(self, mask: u32) -> bool {
40 self.0 & mask == mask
41 }
42
43 pub const fn with(self, mask: u32) -> Self {
45 Self(self.0 | mask)
46 }
47
48 pub const fn without(self, mask: u32) -> Self {
50 Self(self.0 & !mask)
51 }
52}
53
54impl core::ops::BitOr<u32> for Permissions {
55 type Output = Self;
56 fn bitor(self, rhs: u32) -> Self::Output {
57 Self(self.0 | rhs)
58 }
59}
60
61#[derive(Clone, Debug)]
63pub struct RoleSpec {
64 pub id: u32,
66 pub name: String,
68 pub permissions: Permissions,
70}
71
72#[derive(Debug, thiserror::Error)]
74pub enum RoleError {
75 #[error("unknown role: {0}")]
77 UnknownRole(u32),
78 #[error("member {member} lacks permission 0x{permission:08X}")]
80 Unauthorised {
81 member: MemberId,
83 permission: u32,
85 },
86}
87
88#[derive(Default)]
91pub struct RoleRegistry {
92 roles: HashMap<u32, RoleSpec>,
93 assignments: HashMap<MemberId, u32>,
94}
95
96impl RoleRegistry {
97 pub fn new() -> Self {
99 Self::default()
100 }
101
102 pub fn define(&mut self, spec: RoleSpec) {
104 self.roles.insert(spec.id, spec);
105 }
106
107 pub fn define_role(&mut self, id: u32, name: impl Into<String>, permissions: Permissions) {
109 self.define(RoleSpec {
110 id,
111 name: name.into(),
112 permissions,
113 });
114 }
115
116 pub fn role(&self, id: u32) -> Option<&RoleSpec> {
118 self.roles.get(&id)
119 }
120
121 pub fn roles(&self) -> impl Iterator<Item = &RoleSpec> {
123 self.roles.values()
124 }
125
126 pub fn assign(&mut self, member: MemberId, role_id: u32) -> Result<(), RoleError> {
128 if !self.roles.contains_key(&role_id) {
129 return Err(RoleError::UnknownRole(role_id));
130 }
131 self.assignments.insert(member, role_id);
132 Ok(())
133 }
134
135 pub fn role_of(&self, member: MemberId) -> Option<&RoleSpec> {
137 let id = self.assignments.get(&member)?;
138 self.roles.get(id)
139 }
140
141 pub fn permissions_of(&self, member: MemberId) -> Permissions {
144 self.role_of(member)
145 .map(|r| r.permissions)
146 .unwrap_or_default()
147 }
148
149 pub fn require(&self, member: MemberId, mask: u32) -> Result<(), RoleError> {
152 if self.permissions_of(member).has(mask) {
153 Ok(())
154 } else {
155 Err(RoleError::Unauthorised {
156 member,
157 permission: mask,
158 })
159 }
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn permissions_and_require() {
169 let mut r = RoleRegistry::new();
170 r.define_role(1, "viewer", Permissions::default());
171 r.define_role(
172 10,
173 "speaker",
174 Permissions::default() | Permissions::SEND_TEXT | Permissions::SEND_AUDIO,
175 );
176 r.define_role(
177 100,
178 "admin",
179 Permissions::default()
180 | Permissions::SEND_TEXT
181 | Permissions::SEND_AUDIO
182 | Permissions::MUTE_OTHERS
183 | Permissions::ASSIGN_ROLES,
184 );
185
186 r.assign(2, 10).unwrap();
187 r.assign(3, 100).unwrap();
188
189 assert!(r.require(2, Permissions::SEND_TEXT).is_ok());
190 assert!(r.require(2, Permissions::MUTE_OTHERS).is_err());
191 assert!(r.require(3, Permissions::MUTE_OTHERS).is_ok());
192 }
193
194 #[test]
195 fn unknown_role_rejected() {
196 let mut r = RoleRegistry::new();
197 let err = r.assign(1, 42).unwrap_err();
198 assert!(matches!(err, RoleError::UnknownRole(42)));
199 }
200}