1use bitflags::bitflags;
12use serde::{Deserialize, Serialize};
13
14use crate::dbms::table::TableFingerprint;
15
16bitflags! {
17 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
23 pub struct TablePerms: u8 {
24 const READ = 0b0001;
26 const INSERT = 0b0010;
28 const UPDATE = 0b0100;
30 const DELETE = 0b1000;
32 }
33}
34
35#[cfg(feature = "candid")]
36impl candid::CandidType for TablePerms {
37 fn _ty() -> candid::types::Type {
38 candid::types::TypeInner::Nat8.into()
39 }
40
41 fn idl_serialize<S>(&self, serializer: S) -> Result<(), S::Error>
42 where
43 S: candid::types::Serializer,
44 {
45 serializer.serialize_nat8(self.bits())
46 }
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
52#[cfg_attr(feature = "candid", derive(candid::CandidType))]
53pub enum RequiredPerm {
54 Table(TablePerms),
56 Admin,
58 ManageAcl,
60 Migrate,
62}
63
64#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
70#[cfg_attr(feature = "candid", derive(candid::CandidType))]
71pub struct IdentityPerms {
72 pub admin: bool,
74 pub manage_acl: bool,
76 pub migrate: bool,
78 pub all_tables: TablePerms,
80 pub per_table: Vec<(TableFingerprint, TablePerms)>,
82}
83
84impl IdentityPerms {
85 pub fn fully_permissive() -> Self {
87 Self {
88 admin: true,
89 manage_acl: true,
90 migrate: true,
91 all_tables: TablePerms::all(),
92 per_table: Vec::new(),
93 }
94 }
95
96 pub fn grants_table(&self, table: TableFingerprint, required: TablePerms) -> bool {
98 if self.admin {
99 return true;
100 }
101 let union = self.all_tables | self.lookup(table);
102 union.contains(required)
103 }
104
105 fn lookup(&self, table: TableFingerprint) -> TablePerms {
107 self.per_table
108 .iter()
109 .find(|(t, _)| *t == table)
110 .map(|(_, p)| *p)
111 .unwrap_or_default()
112 }
113
114 pub fn apply_grant(&mut self, grant: PermGrant) {
116 match grant {
117 PermGrant::Admin => self.admin = true,
118 PermGrant::ManageAcl => self.manage_acl = true,
119 PermGrant::Migrate => self.migrate = true,
120 PermGrant::AllTables(p) => self.all_tables |= p,
121 PermGrant::Table(t, p) => match self.per_table.iter_mut().find(|(tt, _)| *tt == t) {
122 Some((_, existing)) => *existing |= p,
123 None => self.per_table.push((t, p)),
124 },
125 }
126 }
127
128 pub fn apply_revoke(&mut self, revoke: PermRevoke) {
130 match revoke {
131 PermRevoke::Admin => self.admin = false,
132 PermRevoke::ManageAcl => self.manage_acl = false,
133 PermRevoke::Migrate => self.migrate = false,
134 PermRevoke::AllTables(p) => self.all_tables.remove(p),
135 PermRevoke::Table(t, p) => {
136 if let Some(pos) = self.per_table.iter().position(|(tt, _)| *tt == t) {
137 self.per_table[pos].1.remove(p);
138 if self.per_table[pos].1.is_empty() {
139 self.per_table.swap_remove(pos);
140 }
141 }
142 }
143 }
144 }
145
146 pub fn is_empty(&self) -> bool {
148 !self.admin
149 && !self.manage_acl
150 && !self.migrate
151 && self.all_tables.is_empty()
152 && self.per_table.is_empty()
153 }
154}
155
156#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
158#[cfg_attr(feature = "candid", derive(candid::CandidType))]
159pub enum PermGrant {
160 Admin,
162 ManageAcl,
164 Migrate,
166 AllTables(TablePerms),
168 Table(TableFingerprint, TablePerms),
170}
171
172#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
174#[cfg_attr(feature = "candid", derive(candid::CandidType))]
175pub enum PermRevoke {
176 Admin,
178 ManageAcl,
180 Migrate,
182 AllTables(TablePerms),
185 Table(TableFingerprint, TablePerms),
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192 use crate::dbms::table::fingerprint_for_name;
193
194 fn fp(name: &str) -> TableFingerprint {
195 fingerprint_for_name(name)
196 }
197
198 #[test]
199 fn test_admin_bypasses_table_check() {
200 let mut p = IdentityPerms::default();
201 p.admin = true;
202 assert!(p.grants_table(fp("users"), TablePerms::DELETE));
203 }
204
205 #[test]
206 fn test_all_tables_grant_unions_with_per_table() {
207 let mut p = IdentityPerms::default();
208 p.all_tables = TablePerms::READ;
209 p.apply_grant(PermGrant::Table(
210 fp("users"),
211 TablePerms::INSERT | TablePerms::UPDATE,
212 ));
213 assert!(p.grants_table(fp("users"), TablePerms::READ));
214 assert!(p.grants_table(fp("users"), TablePerms::INSERT));
215 assert!(!p.grants_table(fp("users"), TablePerms::DELETE));
216 assert!(p.grants_table(fp("posts"), TablePerms::READ));
217 assert!(!p.grants_table(fp("posts"), TablePerms::INSERT));
218 }
219
220 #[test]
221 fn test_apply_grant_is_idempotent() {
222 let mut p = IdentityPerms::default();
223 p.apply_grant(PermGrant::Admin);
224 p.apply_grant(PermGrant::Admin);
225 assert!(p.admin);
226 p.apply_grant(PermGrant::Table(fp("users"), TablePerms::READ));
227 p.apply_grant(PermGrant::Table(fp("users"), TablePerms::READ));
228 assert_eq!(p.per_table.len(), 1);
229 assert_eq!(p.per_table[0], (fp("users"), TablePerms::READ));
230 }
231
232 #[test]
233 fn test_revoke_partial_table_bits() {
234 let mut p = IdentityPerms::default();
235 p.apply_grant(PermGrant::Table(
236 fp("users"),
237 TablePerms::READ | TablePerms::INSERT | TablePerms::DELETE,
238 ));
239 p.apply_revoke(PermRevoke::Table(
240 fp("users"),
241 TablePerms::INSERT | TablePerms::DELETE,
242 ));
243 assert_eq!(p.per_table[0].1, TablePerms::READ);
244 }
245
246 #[test]
247 fn test_revoke_all_table_bits_removes_entry() {
248 let mut p = IdentityPerms::default();
249 p.apply_grant(PermGrant::Table(fp("users"), TablePerms::READ));
250 p.apply_revoke(PermRevoke::Table(fp("users"), TablePerms::READ));
251 assert!(p.per_table.is_empty());
252 }
253
254 #[test]
255 fn test_admin_does_not_imply_manage_acl_or_migrate() {
256 let mut p = IdentityPerms::default();
257 p.admin = true;
258 assert!(!p.manage_acl);
259 assert!(!p.migrate);
260 }
261
262 #[test]
263 fn test_fully_permissive_grants_everything() {
264 let p = IdentityPerms::fully_permissive();
265 assert!(p.admin && p.manage_acl && p.migrate);
266 assert!(p.grants_table(fp("anything"), TablePerms::all()));
267 }
268
269 #[test]
270 fn test_is_empty_after_revoking_all() {
271 let mut p = IdentityPerms::default();
272 p.apply_grant(PermGrant::Admin);
273 assert!(!p.is_empty());
274 p.apply_revoke(PermRevoke::Admin);
275 assert!(p.is_empty());
276 }
277}