1use std::{
2 collections::{HashMap, HashSet},
3 fmt::{Debug, Display},
4 hash::Hash,
5};
6
7use candid::CandidType;
8use serde::{Deserialize, Serialize};
9
10use crate::{common::option::display_option_by, identity::UserId};
11
12#[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
16pub enum PermissionUpdatedArg<Permission: Eq + Hash> {
17 UpdateUserPermission(UserId, Option<HashSet<Permission>>),
19 UpdateRolePermission(String, Option<HashSet<Permission>>),
21 UpdateUserRole(UserId, Option<HashSet<String>>),
23}
24
25#[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
27pub enum PermissionUpdatedError<Permission> {
28 InvalidPermission(Permission),
30 InvalidRole(String),
32}
33impl<Permission: Debug> Display for PermissionUpdatedError<Permission> {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 match self {
36 PermissionUpdatedError::InvalidPermission(permission) => {
37 write!(f, "InvalidPermission({permission:?})")
38 }
39 PermissionUpdatedError::InvalidRole(role) => write!(f, "InvalidRole({role})"),
40 }
41 }
42}
43impl<Permission: Debug> std::error::Error for PermissionUpdatedError<Permission> {}
44
45pub trait Permissable<Permission: Eq + Hash> {
47 fn permission_users(&self) -> HashSet<&UserId>;
50 fn permission_roles(&self) -> HashSet<&String>;
52
53 fn permission_assigned(&self, user_id: &UserId) -> Option<&HashSet<Permission>>;
55 fn permission_role_assigned(&self, role: &str) -> Option<&HashSet<Permission>>;
57 fn permission_user_roles(&self, user_id: &UserId) -> Option<&HashSet<String>>;
59
60 fn permission_has(&self, user_id: &UserId, permission: &Permission) -> bool;
66 fn permission_owned(&self, user_id: &UserId) -> HashMap<&Permission, bool>;
68
69 fn permission_reset(&mut self, permissions: HashSet<Permission>);
73 fn permission_update(
75 &mut self,
76 args: Vec<PermissionUpdatedArg<Permission>>,
77 ) -> Result<(), PermissionUpdatedError<Permission>>;
78}
79
80impl Display for PermissionUpdatedArg<String> {
81 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 match self {
83 Self::UpdateUserPermission(user_id, permissions) => f.write_str(&format!(
84 "update user: {} permissions: {}",
85 user_id.to_text(),
86 display_option_by(permissions, |permissions| format!(
87 "[{}]",
88 permissions.iter().map(|p| p.to_string()).collect::<Vec<_>>().join(",")
89 ))
90 )),
91 Self::UpdateRolePermission(role, permissions) => f.write_str(&format!(
92 "update role: {} permissions: {}",
93 role,
94 display_option_by(permissions, |permissions| format!(
95 "[{}]",
96 permissions.iter().map(|p| p.to_string()).collect::<Vec<_>>().join(",")
97 ))
98 )),
99 Self::UpdateUserRole(user_id, roles) => f.write_str(&format!(
100 "update user: {} roles: {}",
101 user_id.to_text(),
102 display_option_by(roles, |roles| format!(
103 "[{}]",
104 roles.iter().cloned().collect::<Vec<_>>().join(",")
105 ))
106 )),
107 }
108 }
109}
110
111pub mod basic {
115 use std::{
116 collections::{HashMap, HashSet},
117 fmt::Display,
118 };
119
120 use candid::CandidType;
121 use serde::{Deserialize, Serialize};
122
123 use crate::{
124 functions::types::{Permissable, PermissionUpdatedArg, PermissionUpdatedError},
125 identity::UserId,
126 };
127
128 #[derive(CandidType, Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
130 pub enum Permission {
131 Permitted(String),
133 Forbidden(String),
135 }
136
137 impl Display for Permission {
138 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139 match self {
140 Permission::Permitted(name) => write!(f, "Permitted({name})"),
141 Permission::Forbidden(name) => write!(f, "Forbidden({name})"),
142 }
143 }
144 }
145
146 impl Permission {
147 pub fn by_permit(name: &str) -> Self {
149 Permission::Permitted(name.to_string())
150 }
151 pub fn by_forbid(name: &str) -> Self {
153 Permission::Forbidden(name.to_string())
154 }
155 pub fn is_permit(&self) -> bool {
157 matches!(self, Self::Permitted(_))
158 }
159 pub fn is_forbid(&self) -> bool {
161 matches!(self, Self::Forbidden(_))
162 }
163 pub fn name(&self) -> &str {
165 match self {
166 Permission::Permitted(name) => name,
167 Permission::Forbidden(name) => name,
168 }
169 }
170 }
171
172 #[derive(CandidType, Serialize, Deserialize, Debug, Clone, Default)]
174 pub struct Permissions {
175 pub permissions: HashSet<Permission>,
177 pub user_permissions: HashMap<UserId, HashSet<Permission>>,
179 pub role_permissions: HashMap<String, HashSet<Permission>>,
181 pub user_roles: HashMap<UserId, HashSet<String>>,
183 }
184
185 impl Permissions {
186 fn assure_permission_exist(
188 &self,
189 permissions: &Option<HashSet<Permission>>,
190 ) -> Result<(), PermissionUpdatedError<Permission>> {
191 if let Some(permissions) = permissions {
192 for permission in permissions {
193 if !self.permissions.contains(permission) {
194 return Err(PermissionUpdatedError::InvalidPermission(permission.clone()));
195 }
196 }
197 }
198
199 Ok(())
200 }
201 fn assure_role_exist(&self, roles: &Option<HashSet<String>>) -> Result<(), PermissionUpdatedError<Permission>> {
203 if let Some(roles) = roles {
204 for role in roles {
205 if !self.role_permissions.contains_key(role) {
206 return Err(PermissionUpdatedError::InvalidRole(role.clone()));
207 }
208 }
209 }
210 Ok(())
211 }
212 }
213
214 impl Permissable<Permission> for Permissions {
215 fn permission_users(&self) -> HashSet<&UserId> {
217 let mut users: HashSet<&UserId> = self.user_roles.keys().collect();
218 users.extend(self.user_permissions.keys());
219 users
220 }
221 fn permission_roles(&self) -> HashSet<&String> {
222 self.role_permissions.keys().collect()
223 }
224
225 fn permission_assigned(&self, user_id: &UserId) -> Option<&HashSet<Permission>> {
226 self.user_permissions.get(user_id)
227 }
228 fn permission_role_assigned(&self, role: &str) -> Option<&HashSet<Permission>> {
229 self.role_permissions.get(role)
230 }
231 fn permission_user_roles(&self, user_id: &UserId) -> Option<&HashSet<String>> {
232 self.user_roles.get(user_id)
233 }
234
235 fn permission_has(&self, user_id: &UserId, permission: &Permission) -> bool {
236 if let Some(permissions) = self.user_permissions.get(user_id)
238 && permissions.contains(permission)
239 {
240 return match permission {
241 Permission::Permitted(_) => true,
242 Permission::Forbidden(_) => false,
243 };
244 }
245 if let Some(roles) = self.user_roles.get(user_id) {
247 for role in roles {
248 if let Some(permissions) = self.role_permissions.get(role)
249 && permissions.contains(permission)
250 {
251 return match permission {
252 Permission::Permitted(_) => true,
253 Permission::Forbidden(_) => false,
254 };
255 }
256 }
257 }
258 match permission {
260 Permission::Permitted(_) => false,
261 Permission::Forbidden(_) => true,
262 }
263 }
264 fn permission_owned(&self, user_id: &UserId) -> HashMap<&Permission, bool> {
265 self.permissions
266 .iter()
267 .map(|permission| (permission, self.permission_has(user_id, permission)))
268 .collect()
269 }
270
271 fn permission_reset(&mut self, permissions: HashSet<Permission>) {
273 self.permissions = permissions;
274 self.role_permissions.iter_mut().for_each(|(_, permissions)| {
276 let mut removed = Vec::new();
277 for permission in permissions.iter() {
278 if !self.permissions.contains(permission) {
279 removed.push(permission.clone());
280 }
281 }
282 for permission in removed {
283 permissions.remove(&permission);
284 }
285 });
286 self.user_permissions.iter_mut().for_each(|(_, permissions)| {
287 let mut removed = Vec::new();
288 for permission in permissions.iter() {
289 if !self.permissions.contains(permission) {
290 removed.push(permission.clone());
291 }
292 }
293 for permission in removed {
294 permissions.remove(&permission);
295 }
296 });
297 }
298 fn permission_update(
299 &mut self,
300 args: Vec<PermissionUpdatedArg<Permission>>,
301 ) -> Result<(), PermissionUpdatedError<Permission>> {
302 let mut updated = self.clone();
305 for arg in args.iter() {
306 match arg {
307 PermissionUpdatedArg::UpdateUserPermission(user_id, permissions) => {
308 updated.assure_permission_exist(permissions)?;
310
311 let exist = updated.user_permissions.get(user_id);
312 if let Some(permissions) = &permissions {
313 if let Some(exist) = exist
314 && exist == permissions
315 {
316 continue;
317 }
318 } else if exist.is_none() {
319 continue;
320 }
321 if let Some(permissions) = permissions {
322 updated.user_permissions.insert(*user_id, permissions.clone());
323 } else {
324 updated.user_permissions.remove(user_id);
325 }
326 }
327 PermissionUpdatedArg::UpdateRolePermission(role, permissions) => {
328 updated.assure_permission_exist(permissions)?;
330
331 let exist = updated.role_permissions.get(role);
332 if let Some(permissions) = permissions {
333 if let Some(exist) = exist
334 && exist == permissions
335 {
336 continue;
337 }
338 } else if exist.is_none() {
339 continue;
340 }
341 if let Some(permissions) = permissions {
342 updated.role_permissions.insert(role.clone(), permissions.clone());
343 } else {
344 updated.role_permissions.remove(role);
345 let valid_roles: HashSet<String> = updated.role_permissions.keys().cloned().collect();
347 updated.user_roles.iter_mut().for_each(|(_, roles)| {
348 let mut removed = Vec::new();
349 for role in roles.iter() {
350 if !valid_roles.contains(role) {
351 removed.push(role.clone());
352 }
353 }
354 for role in removed {
355 roles.remove(&role);
356 }
357 });
358 }
359 }
360 PermissionUpdatedArg::UpdateUserRole(user_id, roles) => {
361 updated.assure_role_exist(roles)?;
363
364 let exist = updated.user_roles.get(user_id);
365 if let Some(roles) = &roles {
366 if let Some(exist) = exist
367 && exist == roles
368 {
369 continue;
370 }
371 } else if exist.is_none() {
372 continue;
373 }
374 if let Some(roles) = roles {
375 updated.user_roles.insert(*user_id, roles.clone());
376 } else {
377 updated.user_roles.remove(user_id);
378 }
379 }
380 }
381 }
382 *self = updated;
383 Ok(())
384 }
385 }
386
387 impl PermissionUpdatedArg<String> {
388 pub fn parse_permission<E, F: Fn(&str) -> Result<Permission, E>>(
394 self,
395 f: F,
396 ) -> Result<PermissionUpdatedArg<Permission>, E> {
397 Ok(match self {
398 PermissionUpdatedArg::UpdateUserPermission(user_id, permissions) => {
399 PermissionUpdatedArg::UpdateUserPermission(
400 user_id,
401 permissions
402 .map(|ps| ps.into_iter().map(|p| f(&p)).collect::<Result<HashSet<_>, _>>())
403 .transpose()?,
404 )
405 }
406 PermissionUpdatedArg::UpdateRolePermission(role, permissions) => {
407 PermissionUpdatedArg::UpdateRolePermission(
408 role,
409 permissions
410 .map(|ps| ps.into_iter().map(|p| f(&p)).collect::<Result<HashSet<_>, _>>())
411 .transpose()?,
412 )
413 }
414 PermissionUpdatedArg::UpdateUserRole(user_id, roles) => {
415 PermissionUpdatedArg::UpdateUserRole(user_id, roles)
416 }
417 })
418 }
419 }
420
421 pub fn parse_all_permissions<'a, F, E>(actions: &[&'a str], parse: F) -> Result<Vec<Permission>, E>
425 where
426 F: Fn(&'a str) -> Result<Permission, E>,
427 {
428 let mut permissions = Vec::with_capacity(actions.len());
429 for name in actions {
430 permissions.push(parse(name)?);
431 }
432 Ok(permissions)
433 }
434
435 pub fn permitted_permissions(permissions: &HashSet<Permission>) -> HashSet<Permission> {
437 permissions.iter().filter(|p| p.is_permit()).cloned().collect()
438 }
439
440 pub fn supers_updated(
442 supers: &[UserId],
443 permissions: &HashSet<Permission>,
444 ) -> Vec<PermissionUpdatedArg<Permission>> {
445 let permitted: HashSet<Permission> = permitted_permissions(permissions);
446 supers
447 .iter()
448 .map(|su| PermissionUpdatedArg::UpdateUserPermission(*su, Some(permitted.clone())))
449 .collect()
450 }
451
452 #[cfg(test)]
453 mod tests {
454 use std::collections::HashSet;
455
456 use candid::Principal;
457
458 use super::{Permission, Permissions};
459 use crate::functions::permission::{Permissable, PermissionUpdatedArg, PermissionUpdatedError};
460
461 fn permissions() -> Permissions {
462 Permissions {
463 permissions: HashSet::from([Permission::by_permit("read")]),
464 ..Default::default()
465 }
466 }
467
468 #[test]
469 fn batch_update_is_atomic_when_a_later_item_is_invalid() {
470 let user = Principal::anonymous();
471 let read = Permission::by_permit("read");
472 let invalid = Permission::by_permit("missing");
473 let mut permissions = permissions();
474
475 let result = permissions.permission_update(vec![
476 PermissionUpdatedArg::UpdateUserPermission(user, Some(HashSet::from([read]))),
477 PermissionUpdatedArg::UpdateUserPermission(user, Some(HashSet::from([invalid]))),
478 ]);
479
480 assert!(matches!(result, Err(PermissionUpdatedError::InvalidPermission(_))));
481 assert!(permissions.user_permissions.is_empty());
482 }
483
484 #[test]
485 fn batch_can_create_and_assign_a_role_atomically() {
486 let user = Principal::anonymous();
487 let read = Permission::by_permit("read");
488 let mut permissions = permissions();
489
490 permissions
491 .permission_update(vec![
492 PermissionUpdatedArg::UpdateRolePermission(
493 "reader".to_string(),
494 Some(HashSet::from([read.clone()])),
495 ),
496 PermissionUpdatedArg::UpdateUserRole(user, Some(HashSet::from(["reader".to_string()]))),
497 ])
498 .unwrap();
499
500 assert!(permissions.permission_has(&user, &read));
501 }
502 }
503}