sea_orm/rbac/engine/
mod.rs

1use super::entity::{
2    permission::{Model as Permission, PermissionId},
3    resource::{Model as Resource, ResourceId},
4    role::{Model as Role, RoleId},
5    role_hierarchy::Model as RoleHierarchy,
6    role_permission::Model as RolePermission,
7    user::UserId,
8    user_override::Model as UserOverride,
9    user_role::Model as UserRole,
10};
11use super::{Error, WILDCARD};
12
13mod loader;
14mod permission_request;
15mod resource_request;
16mod role_hierarchy_impl;
17mod snapshot;
18
19pub use permission_request::*;
20pub use resource_request::*;
21use role_hierarchy_impl::*;
22pub use snapshot::*;
23
24use std::collections::{HashMap, HashSet};
25
26pub struct RbacEngine {
27    resources: HashMap<ResourceRequest, Resource>,
28    permissions: HashMap<PermissionRequest, Permission>,
29    wildcard_resources: HashMap<ResourceId, Resource>,
30    wildcard_permissions: HashMap<PermissionId, Permission>,
31    roles: HashMap<RoleId, Role>,
32    user_roles: HashMap<UserId, RoleId>,
33    role_permissions: HashMap<RoleId, HashSet<(PermissionId, ResourceId)>>,
34    user_overrides: HashMap<UserId, Vec<UserOverride>>,
35    role_hierarchy: HashMap<RoleId, Vec<RoleId>>, // Role -> ChildRole
36}
37
38impl std::fmt::Debug for RbacEngine {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        write!(f, "RbacEngine")
41    }
42}
43
44#[derive(Debug, PartialEq, Eq)]
45pub struct RbacUserRolePermissions {
46    pub role: Role,
47    pub resource_permissions: RbacPermissionsByResources,
48}
49
50pub type RbacRolesAndRanks = Vec<(Role, u32)>;
51
52pub type RbacRoleHierarchyList = Vec<RoleHierarchy>;
53
54pub type RbacResourcesAndPermissions = (Vec<Resource>, Vec<Permission>);
55
56pub type RbacPermissionsByResources = Vec<(Resource, Vec<Permission>)>;
57
58impl RbacEngine {
59    pub fn from_snapshot(
60        RbacSnapshot {
61            resources: resources_rows,
62            permissions: permissions_rows,
63            roles: roles_rows,
64            user_roles: user_roles_rows,
65            role_permissions: role_permissions_rows,
66            user_overrides: user_overrides_rows,
67            role_hierarchy: role_hierarchy_rows,
68        }: RbacSnapshot,
69    ) -> Self {
70        let mut resources: HashMap<ResourceRequest, Resource> = Default::default();
71        let mut wildcard_resources = HashMap::new();
72        for resource in resources_rows {
73            if resource.schema.as_deref() == Some(WILDCARD) || resource.table == WILDCARD {
74                wildcard_resources.insert(resource.id, resource);
75            } else {
76                resources.insert(resource.clone().into(), resource);
77            }
78        }
79
80        let mut permissions: HashMap<PermissionRequest, Permission> = Default::default();
81        let mut wildcard_permissions = HashMap::new();
82        for permission in permissions_rows {
83            if permission.action == WILDCARD {
84                wildcard_permissions.insert(permission.id, permission);
85            } else {
86                permissions.insert(permission.clone().into(), permission);
87            }
88        }
89
90        let roles: HashMap<RoleId, Role> = roles_rows.into_iter().map(|r| (r.id, r)).collect();
91
92        let mut user_roles: HashMap<UserId, RoleId> = Default::default();
93        for user_role in user_roles_rows {
94            user_roles.insert(user_role.user_id, user_role.role_id);
95        }
96
97        let mut role_permissions: HashMap<RoleId, HashSet<(PermissionId, ResourceId)>> =
98            Default::default();
99        for rp in role_permissions_rows {
100            let set = role_permissions.entry(rp.role_id).or_default();
101            set.insert((rp.permission_id, rp.resource_id));
102        }
103
104        let mut user_overrides: HashMap<UserId, Vec<UserOverride>> = Default::default();
105        for user_override in user_overrides_rows {
106            user_overrides
107                .entry(user_override.user_id)
108                .or_default()
109                .push(user_override);
110        }
111
112        let mut role_hierarchy: HashMap<RoleId, Vec<RoleId>> = Default::default();
113        for rh in role_hierarchy_rows {
114            role_hierarchy
115                .entry(rh.super_role_id)
116                .or_default()
117                .push(rh.role_id);
118        }
119
120        RbacEngine {
121            resources,
122            permissions,
123            wildcard_resources,
124            wildcard_permissions,
125            roles,
126            user_roles,
127            role_permissions,
128            user_overrides,
129            role_hierarchy,
130        }
131    }
132
133    /// get user's role and walk the hierarchy, returning all assigned roles
134    fn get_user_role_ids(&self, user_id: &UserId) -> Result<HashSet<RoleId>, Error> {
135        if let Some(role) = self.user_roles.get(&user_id) {
136            let mut user_roles = HashSet::new();
137            for role in enumerate_role(*role, &self.role_hierarchy) {
138                if !self.roles.contains_key(&role) {
139                    return Err(Error::RoleNotFound(format!("{role:?}")));
140                }
141                user_roles.insert(role);
142            }
143            Ok(user_roles)
144        } else {
145            Err(Error::UserNotFound(format!("{user_id:?}")))
146        }
147    }
148
149    pub fn get_roles_and_ranks(&self) -> Result<RbacRolesAndRanks, Error> {
150        let mut all_roles = Vec::new();
151        for role_id in self.roles.keys() {
152            all_roles.push((
153                self.roles
154                    .get(role_id)
155                    .cloned()
156                    .ok_or_else(|| Error::RoleNotFound(format!("{role_id:?}")))?,
157                enumerate_role(*role_id, &self.role_hierarchy).len() as u32,
158            ));
159        }
160        // descending rank but ascending role
161        all_roles.sort_by_key(|r| (-(r.1 as i64), r.0.id));
162        Ok(all_roles)
163    }
164
165    pub fn get_user_role_permissions(
166        &self,
167        user_id: UserId,
168    ) -> Result<RbacUserRolePermissions, Error> {
169        let mut user_roles: Vec<RoleId> = self.get_user_role_ids(&user_id)?.into_iter().collect();
170        user_roles.sort();
171
172        let mut role_permissions: HashSet<(PermissionId, ResourceId)> = Default::default();
173
174        for role_id in user_roles {
175            if let Some(items) = self.role_permissions.get(&role_id) {
176                role_permissions.extend(items.into_iter());
177            }
178        }
179
180        if let Some(user_overrides) = self.user_overrides.get(&user_id) {
181            for over in user_overrides {
182                let role_permission = (over.permission_id, over.resource_id);
183                if role_permissions.contains(&role_permission) {
184                    if !over.grant {
185                        role_permissions.remove(&role_permission);
186                    }
187                } else if over.grant {
188                    role_permissions.insert(role_permission);
189                }
190            }
191        }
192
193        Ok(RbacUserRolePermissions {
194            role: self
195                .roles
196                .get(&self.user_roles.get(&user_id).expect("Checked above"))
197                .expect("Checked above")
198                .to_owned(),
199            resource_permissions: self.group_permissions_by_resources(
200                role_permissions.into_iter().map(|(p, r)| (r, p)),
201            )?,
202        })
203    }
204
205    pub fn list_resources_and_permissions(&self) -> RbacResourcesAndPermissions {
206        (
207            self.resources
208                .values()
209                .chain(self.wildcard_resources.values())
210                .cloned()
211                .collect(),
212            self.permissions
213                .values()
214                .chain(self.wildcard_permissions.values())
215                .cloned()
216                .collect(),
217        )
218    }
219
220    pub fn list_role_hierarchy_edges(&self, role_id: RoleId) -> Vec<RoleHierarchy> {
221        list_role_hierarchy_edges(role_id, &self.role_hierarchy)
222    }
223
224    fn group_permissions_by_resources(
225        &self,
226        items: impl Iterator<Item = (ResourceId, PermissionId)>,
227    ) -> Result<RbacPermissionsByResources, Error> {
228        let mut map: HashMap<ResourceId, (Resource, Vec<Permission>)> = Default::default();
229
230        for item in items {
231            let permission = if let Some(p) = self.wildcard_permissions.get(&item.1) {
232                p
233            } else {
234                self.permissions
235                    .values()
236                    .find(|p| p.id == item.1)
237                    .ok_or_else(|| Error::PermissionNotFound(format!("{:?}", item.1)))?
238            };
239
240            let resource = if let Some(r) = self.wildcard_resources.get(&item.0) {
241                r
242            } else {
243                self.resources
244                    .values()
245                    .find(|r| r.id == item.0)
246                    .ok_or_else(|| Error::ResourceNotFound(format!("{:?}", item.0)))?
247            };
248
249            map.entry(item.0)
250                .or_insert_with(|| (resource.to_owned(), Default::default()))
251                .1
252                .push(permission.to_owned());
253        }
254
255        let mut vec: Vec<_> = map.into_values().collect();
256        vec.sort_by_key(|r| r.0.id);
257        vec.iter_mut().for_each(|r| r.1.sort_by_key(|p| p.id));
258        Ok(vec)
259    }
260
261    pub fn list_role_permissions_by_resources(
262        &self,
263        role_id: RoleId,
264    ) -> Result<RbacPermissionsByResources, Error> {
265        self.group_permissions_by_resources(
266            self.role_permissions
267                .get(&role_id)
268                .ok_or_else(|| Error::RoleNotFound(format!("{role_id:?}")))?
269                .iter()
270                .map(|(p, r)| (*r, *p)),
271        )
272    }
273
274    pub fn user_can<P, R>(&self, user_id: UserId, permission: P, resource: R) -> Result<bool, Error>
275    where
276        P: Into<PermissionRequest>,
277        R: Into<ResourceRequest>,
278    {
279        let resource = resource.into();
280        let permission = permission.into();
281        let resource = self.resources.get(&resource);
282        let permission = self.permissions.get(&permission);
283
284        // get user roles and flatten hierarchy
285        let user_roles = self.get_user_role_ids(&user_id)?;
286
287        if let (Some(permission), Some(resource)) = (permission, resource) {
288            if let Some(user_overrides) = self.user_overrides.get(&user_id) {
289                for user_override in user_overrides {
290                    if user_override.permission_id == permission.id
291                        && user_override.resource_id == resource.id
292                    {
293                        return Ok(user_override.grant);
294                    }
295                }
296            }
297        }
298
299        for role_id in user_roles {
300            if let Some(role_permissions) = self.role_permissions.get(&role_id) {
301                if let (Some(permission), Some(resource)) = (permission, resource) {
302                    if role_permissions.contains(&(permission.id, resource.id)) {
303                        return Ok(true);
304                    }
305                }
306                for (permission_id, resource_id) in role_permissions {
307                    let is_wildcard_permission =
308                        self.is_wildcard_permission(*permission_id, permission);
309                    let is_wildcard_resource = self.is_wildcard_resource(*resource_id, resource);
310                    if let Some(resource) = &resource {
311                        if resource_id == &resource.id && is_wildcard_permission {
312                            return Ok(true);
313                        }
314                    }
315                    if let Some(permission) = &permission {
316                        if permission_id == &permission.id && is_wildcard_resource {
317                            return Ok(true);
318                        }
319                    }
320                    if is_wildcard_permission && is_wildcard_resource {
321                        return Ok(true);
322                    }
323                }
324            }
325        }
326
327        if resource.is_none() {
328            return Err(Error::ResourceNotFound(format!("{resource:?}")));
329        }
330
331        if permission.is_none() {
332            return Err(Error::PermissionNotFound(format!("{permission:?}")));
333        }
334
335        Ok(false)
336    }
337
338    fn is_wildcard_resource(&self, id: ResourceId, target: Option<&Resource>) -> bool {
339        if let Some(resource) = self.wildcard_resources.get(&id) {
340            if let Some(target) = target {
341                let schema_match = resource.schema.is_none()
342                    || resource.schema.as_ref().unwrap() == WILDCARD
343                    || resource.schema == target.schema;
344                let table_match = resource.table == WILDCARD || resource.table == target.table;
345                schema_match && table_match
346            } else {
347                (resource.schema.is_none() || resource.schema.as_ref().unwrap() == WILDCARD)
348                    && resource.table == WILDCARD
349            }
350        } else {
351            false
352        }
353    }
354
355    fn is_wildcard_permission(&self, id: PermissionId, _: Option<&Permission>) -> bool {
356        if let Some(permission) = self.wildcard_permissions.get(&id) {
357            return permission.action == WILDCARD;
358        }
359        false
360    }
361}
362
363#[cfg(test)]
364mod test {
365    use super::*;
366
367    #[allow(non_snake_case)]
368    fn Object(r: &str) -> Table<'_> {
369        Table(r)
370    }
371
372    fn resource(table: &str) -> Resource {
373        Resource {
374            id: ResourceId(0),
375            schema: None,
376            table: table.to_owned(),
377        }
378    }
379
380    fn permission(action: &str) -> Permission {
381        Permission {
382            id: PermissionId(0),
383            action: action.to_owned(),
384        }
385    }
386
387    fn role(role: &str) -> Role {
388        Role {
389            id: RoleId(0),
390            role: role.to_owned(),
391        }
392    }
393
394    fn seed_1() -> RbacSnapshot {
395        let mut snapshot = RbacSnapshot::default();
396        snapshot.set_resources(vec![
397            resource("book"),
398            resource("paper"),
399            resource("pen"),
400            resource("*"),
401        ]);
402        snapshot.set_permissions(vec![
403            permission("browse"),  // read
404            permission("buy"),     // create
405            permission("replace"), // update
406            permission("dispose"), // delete
407            permission("*"),       // anything
408        ]);
409        snapshot.set_roles(vec![
410            role("admin"),
411            role("manager"),
412            role("clerk"),
413            role("auditor"),
414        ]);
415        snapshot.set_user_role(UserId(1), "admin");
416        snapshot.set_user_role(UserId(2), "manager");
417        snapshot.set_user_role(UserId(3), "clerk");
418        snapshot.set_user_role(UserId(4), "auditor");
419        snapshot.set_user_role(UserId(5), "clerk");
420
421        snapshot.add_role_hierarchy("manager", "admin");
422        snapshot.add_role_hierarchy("clerk", "manager");
423        snapshot.add_role_hierarchy("auditor", "admin");
424
425        snapshot.add_role_permission("clerk", Action("browse"), Object("pen"));
426        snapshot.add_role_permission("clerk", Action("browse"), Object("paper"));
427        snapshot.add_role_permission("clerk", Action("dispose"), Object("paper"));
428
429        snapshot.add_role_permission("manager", Action("browse"), Object("book"));
430        snapshot.add_role_permission("manager", Action("buy"), Object("book"));
431        snapshot.add_role_permission("manager", Action("dispose"), Object("book"));
432        snapshot.add_role_permission("manager", Action("replace"), Object("paper"));
433
434        snapshot.add_role_permission("auditor", Action("browse"), Object("*"));
435
436        snapshot.add_user_override(UserId(5), Action("buy"), Object("pen"), true);
437        snapshot.add_user_override(UserId(5), Action("dispose"), Object("paper"), false);
438
439        snapshot.add_role_permission("admin", Action("*"), Object("*"));
440
441        snapshot
442    }
443
444    #[test]
445    #[rustfmt::skip]
446    fn test_rbac_engine_basic() {
447        let admin = UserId(1);
448        let manager = UserId(2);
449        let clerk = UserId(3);
450        let auditor = UserId(4);
451        let designer = UserId(5);
452
453        let engine = RbacEngine::from_snapshot(seed_1());
454
455        // anyone can use pen and paper
456        for item in ["pen", "paper"] {
457            assert!(engine.user_can(clerk, Action("browse"), Object(item)).unwrap());
458            assert!(engine.user_can(manager, Action("browse"), Object(item)).unwrap());
459            assert!(engine.user_can(admin, Action("browse"), Object(item)).unwrap());
460            // auditor can browse anything
461            assert!(engine.user_can(auditor, Action("browse"), Object(item)).unwrap());
462        }
463
464        // anyone can dispose paper except auditor and designer
465        for user in [clerk, manager, admin] {
466            assert!(engine.user_can(user, Action("dispose"), Object("paper")).unwrap());
467        }
468        for user in [designer, auditor] {
469            assert!(!engine.user_can(user, Action("dispose"), Object("paper")).unwrap());
470        }
471
472        // clerk cannot browse books
473        for user in [clerk, designer] {
474            assert!(!engine.user_can(user, Action("browse"), Object("book")).unwrap());
475        }
476
477        for user in [admin, manager] {
478            assert!(engine.user_can(user, Action("browse"), Object("book")).unwrap());
479            assert!(engine.user_can(user, Action("buy"), Object("book")).unwrap());
480            assert!(engine.user_can(user, Action("dispose"), Object("book")).unwrap());
481        }
482
483        // auditor cannot alter things
484        for action in ["buy", "replace", "dispose"] {
485            for item in ["book", "paper", "pen"] {
486                assert!(!engine.user_can(auditor, Action(action), Object(item)).unwrap());
487            }
488        }
489
490        // manager cannot replace books, but admin can
491        assert!(!engine.user_can(manager, Action("replace"), Object("book")).unwrap());
492        assert!(engine.user_can(admin, Action("replace"), Object("book")).unwrap());
493
494        // manager can replace paper
495        assert!(!engine.user_can(clerk, Action("replace"), Object("paper")).unwrap());
496        assert!(!engine.user_can(designer, Action("replace"), Object("paper")).unwrap());
497        assert!(engine.user_can(manager, Action("replace"), Object("paper")).unwrap());
498        assert!(engine.user_can(admin, Action("replace"), Object("paper")).unwrap());
499
500        // only admin can buy paper
501        for user in [clerk, manager, designer] {
502            assert!(!engine.user_can(user, Action("buy"), Object("paper")).unwrap());
503        }
504        assert!(engine.user_can(admin, Action("buy"), Object("paper")).unwrap());
505
506        // designer has an exception can buy pen
507        for user in [designer, admin] {
508            assert!(engine.user_can(user, Action("buy"), Object("pen")).unwrap());
509        }
510        for user in [clerk, manager] {
511            assert!(!engine.user_can(user, Action("buy"), Object("pen")).unwrap());
512        }
513
514        // only admin can replace / dispose pen
515        for action in ["replace", "dispose"] {
516            assert!(engine.user_can(admin, Action(action), Object("pen")).unwrap());
517        }
518
519        // unknown action / object; admin has wildcard
520        assert!(engine.user_can(admin, Action("?"), Object("?")).is_ok());
521        assert!(engine.user_can(manager, Action("?"), Object("?")).is_err());
522        assert!(engine.user_can(clerk, Action("?"), Object("?")).is_err());
523
524        assert_eq!(engine.get_user_role_permissions(clerk).unwrap(), RbacUserRolePermissions {
525            role: Role {
526                id: RoleId(3),
527                role: "clerk".to_owned(),
528            },
529            resource_permissions: vec![
530                (
531                    Resource { id: ResourceId(2), schema: None, table: "paper".to_owned() },
532                    vec![
533                        Permission { id: PermissionId(1), action: "browse".to_owned() },
534                        Permission { id: PermissionId(4), action: "dispose".to_owned() },
535                    ]
536                ),
537                (
538                    Resource { id: ResourceId(3), schema: None, table: "pen".to_owned() },
539                    vec![Permission { id: PermissionId(1), action: "browse".to_owned() }]
540                ),
541            ],
542        });
543
544        assert_eq!(engine.get_user_role_permissions(designer).unwrap(), RbacUserRolePermissions {
545            role: Role {
546                id: RoleId(3),
547                role: "clerk".to_owned(),
548            },
549            resource_permissions: vec![
550                (
551                    Resource { id: ResourceId(2), schema: None, table: "paper".to_owned() },
552                    vec![Permission { id: PermissionId(1), action: "browse".to_owned() }]
553                ),
554                (
555                    Resource { id: ResourceId(3), schema: None, table: "pen".to_owned() },
556                    vec![
557                        Permission { id: PermissionId(1), action: "browse".to_owned() },
558                        Permission { id: PermissionId(2), action: "buy".to_owned() },
559                    ]
560                ),
561            ],
562        });
563
564        assert_eq!(engine.get_roles_and_ranks().unwrap(), vec![
565            (Role { id: RoleId(1), role: "admin".to_owned()   }, 4), // <- manager | auditor
566            (Role { id: RoleId(2), role: "manager".to_owned() }, 2), // <- clerk
567            (Role { id: RoleId(3), role: "clerk".to_owned()   }, 1), //
568            (Role { id: RoleId(4), role: "auditor".to_owned() }, 1), //
569        ]);
570
571        assert_eq!(engine.list_role_hierarchy_edges(RoleId(1)), vec![
572            RoleHierarchy { super_role_id: RoleId(1), role_id: RoleId(2) },
573            RoleHierarchy { super_role_id: RoleId(1), role_id: RoleId(4) },
574            RoleHierarchy { super_role_id: RoleId(2), role_id: RoleId(3) },
575        ]);
576
577        assert_eq!(engine.list_role_hierarchy_edges(RoleId(2)), vec![
578            RoleHierarchy { super_role_id: RoleId(2), role_id: RoleId(3) },
579        ]);
580
581        assert_eq!(engine.list_role_hierarchy_edges(RoleId(3)), vec![]);
582
583        assert_eq!(engine.list_role_permissions_by_resources(RoleId(2)).unwrap(), vec![
584            (Resource { id: ResourceId(1), schema: None, table: "book".into() }, vec![
585                Permission { id: PermissionId(1), action: "browse".into() },
586                Permission { id: PermissionId(2), action: "buy".into() },
587                Permission { id: PermissionId(4), action: "dispose".into() },
588            ]),
589            (Resource { id: ResourceId(2), schema: None, table: "paper".into() }, vec![
590                Permission { id: PermissionId(3), action: "replace".into() },
591            ]),
592        ]);
593
594        assert_eq!(engine.list_role_permissions_by_resources(RoleId(4)).unwrap(), vec![
595            (Resource { id: ResourceId(4), schema: None, table: "*".into() }, vec![
596                Permission { id: PermissionId(1), action: "browse".into() },
597            ]),
598        ]);
599    }
600
601    #[rustfmt::skip]
602    fn seed_2() -> RbacSnapshot {
603        fn resource(schema: &str, table: &str) -> Resource {
604            Resource {
605                id: ResourceId(0),
606                schema: Some(schema.to_owned()),
607                table: table.to_owned(),
608            }
609        }
610
611        let mut snapshot = RbacSnapshot::default();
612        snapshot.set_resources(vec![
613            resource("departmentA", "book"),
614            resource("departmentB", "book"),
615            resource("departmentB", "CD"),
616            resource("*", "book"),
617            resource("departmentB", "*"),
618            resource("*", "*"),
619        ]);
620        snapshot.set_permissions(vec![
621            permission("browse"),
622        ]);
623        snapshot.set_roles(vec![
624            role("silver"),
625            role("gold"),
626            role("platinum"),
627            role("reader"),
628            role("admin"),
629        ]);
630        snapshot.set_user_role(UserId(1), "silver");
631        snapshot.set_user_role(UserId(2), "gold");
632        snapshot.set_user_role(UserId(3), "platinum");
633        snapshot.set_user_role(UserId(4), "reader");
634        snapshot.set_user_role(UserId(5), "admin");
635
636        snapshot.add_role_permission("silver", Action("browse"), SchemaTable("departmentA", "book"));
637        snapshot.add_role_permission("gold", Action("browse"), SchemaTable("departmentB", "book"));
638        snapshot.add_role_permission("platinum", Action("browse"), SchemaTable("departmentA", "book"));
639        snapshot.add_role_permission("platinum", Action("browse"), SchemaTable("departmentB", "*"));
640
641        snapshot.add_role_permission("reader", Action("browse"), SchemaTable("*", "book"));
642
643        snapshot.add_role_permission("admin", Action("browse"), SchemaTable("*", "*"));
644
645        snapshot
646    }
647
648    #[test]
649    #[rustfmt::skip]
650    fn test_rbac_engine_wildcard() {
651        let silver = UserId(1);
652        let gold = UserId(2);
653        let platinum = UserId(3);
654        let reader = UserId(4);
655        let admin = UserId(5);
656
657        let engine = RbacEngine::from_snapshot(seed_2());
658
659        assert!(engine.user_can(silver, Action("browse"), SchemaTable("departmentA", "book")).unwrap());
660        assert!(!engine.user_can(silver, Action("browse"), SchemaTable("departmentB", "book")).unwrap());
661        assert!(!engine.user_can(silver, Action("browse"), SchemaTable("departmentB", "CD")).unwrap());
662
663        assert!(!engine.user_can(gold, Action("browse"), SchemaTable("departmentA", "book")).unwrap());
664        assert!(engine.user_can(gold, Action("browse"), SchemaTable("departmentB", "book")).unwrap());
665        assert!(!engine.user_can(gold, Action("browse"), SchemaTable("departmentB", "CD")).unwrap());
666
667        assert!(engine.user_can(platinum, Action("browse"), SchemaTable("departmentA", "book")).unwrap());
668        assert!(engine.user_can(platinum, Action("browse"), SchemaTable("departmentB", "book")).unwrap());
669        assert!(engine.user_can(platinum, Action("browse"), SchemaTable("departmentB", "CD")).unwrap());
670
671        assert!(engine.user_can(reader, Action("browse"), SchemaTable("departmentA", "book")).unwrap());
672        assert!(engine.user_can(reader, Action("browse"), SchemaTable("departmentB", "book")).unwrap());
673        assert!(!engine.user_can(reader, Action("browse"), SchemaTable("departmentB", "CD")).unwrap());
674
675        assert!(engine.user_can(admin, Action("browse"), SchemaTable("departmentA", "book")).unwrap());
676        assert!(engine.user_can(admin, Action("browse"), SchemaTable("departmentB", "book")).unwrap());
677        assert!(engine.user_can(admin, Action("browse"), SchemaTable("departmentB", "CD")).unwrap());
678    }
679
680    #[rustfmt::skip]
681    fn seed_3() -> RbacSnapshot {
682        let mut snapshot = RbacSnapshot::default();
683        snapshot.set_resources(vec![
684            resource("book"),
685            resource("CD"),
686            resource("magazine"),
687        ]);
688        snapshot.set_permissions(vec![
689            permission("browse"),
690        ]);
691        snapshot.set_roles(vec![
692            role("A"),
693            role("B"),
694            role("C"),
695            role("A+B"),
696            role("A+C"),
697            role("A+B+C"),
698            role("(A+B)+C"),
699        ]);
700        snapshot.set_user_role(UserId(1), "A");
701        snapshot.set_user_role(UserId(2), "B");
702        snapshot.set_user_role(UserId(3), "C");
703        snapshot.set_user_role(UserId(4), "A+B");
704        snapshot.set_user_role(UserId(5), "A+C");
705        snapshot.set_user_role(UserId(6), "A+B+C");
706        snapshot.set_user_role(UserId(7), "(A+B)+C");
707
708        snapshot.add_role_permission("A", Action("browse"), Object("book"));
709        snapshot.add_role_permission("B", Action("browse"), Object("CD"));
710        snapshot.add_role_permission("C", Action("browse"), Object("magazine"));
711
712        snapshot.add_role_hierarchy("A", "A+B");
713        snapshot.add_role_hierarchy("B", "A+B");
714
715        snapshot.add_role_hierarchy("A", "A+C");
716        snapshot.add_role_hierarchy("C", "A+C");
717
718        snapshot.add_role_hierarchy("A", "A+B+C");
719        snapshot.add_role_hierarchy("B", "A+B+C");
720        snapshot.add_role_hierarchy("C", "A+B+C");
721
722        snapshot.add_role_hierarchy("A+B", "(A+B)+C");
723        snapshot.add_role_hierarchy("C", "(A+B)+C");
724
725        snapshot
726    }
727
728    #[test]
729    #[rustfmt::skip]
730    #[allow(non_snake_case)]
731    fn test_rbac_engine_hierarchy() {
732        let A = UserId(1);
733        let B = UserId(2);
734        let C = UserId(3);
735        let A_B = UserId(4);
736        let A_C = UserId(5);
737        let A_B_C = UserId(6);
738        let A_B_C_ = UserId(7);
739
740        let engine = RbacEngine::from_snapshot(seed_3());
741
742        assert!(engine.user_can(A, Action("browse"), Object("book")).unwrap());
743        assert!(!engine.user_can(A, Action("browse"), Object("CD")).unwrap());
744        assert!(!engine.user_can(A, Action("browse"), Object("magazine")).unwrap());
745
746        assert!(!engine.user_can(B, Action("browse"), Object("book")).unwrap());
747        assert!(engine.user_can(B, Action("browse"), Object("CD")).unwrap());
748        assert!(!engine.user_can(B, Action("browse"), Object("magazine")).unwrap());
749
750        assert!(!engine.user_can(C, Action("browse"), Object("book")).unwrap());
751        assert!(!engine.user_can(C, Action("browse"), Object("CD")).unwrap());
752        assert!(engine.user_can(C, Action("browse"), Object("magazine")).unwrap());
753
754        assert!(engine.user_can(A_B, Action("browse"), Object("book")).unwrap());
755        assert!(engine.user_can(A_B, Action("browse"), Object("CD")).unwrap());
756        assert!(!engine.user_can(A_B, Action("browse"), Object("magazine")).unwrap());
757
758        assert!(engine.user_can(A_C, Action("browse"), Object("book")).unwrap());
759        assert!(!engine.user_can(A_C, Action("browse"), Object("CD")).unwrap());
760        assert!(engine.user_can(A_C, Action("browse"), Object("magazine")).unwrap());
761
762        assert!(engine.user_can(A_B_C, Action("browse"), Object("book")).unwrap());
763        assert!(engine.user_can(A_B_C, Action("browse"), Object("CD")).unwrap());
764        assert!(engine.user_can(A_B_C, Action("browse"), Object("magazine")).unwrap());
765
766        assert!(engine.user_can(A_B_C_, Action("browse"), Object("book")).unwrap());
767        assert!(engine.user_can(A_B_C_, Action("browse"), Object("CD")).unwrap());
768        assert!(engine.user_can(A_B_C_, Action("browse"), Object("magazine")).unwrap());
769
770        assert_eq!(engine.get_roles_and_ranks().unwrap(), vec![
771            (Role { id: RoleId(7), role: "(A+B)+C".into() }, 5),
772            (Role { id: RoleId(6), role: "A+B+C".into() }, 4),
773            (Role { id: RoleId(4), role: "A+B".into() }, 3),
774            (Role { id: RoleId(5), role: "A+C".into() }, 3),
775            (Role { id: RoleId(1), role: "A".into() }, 1),
776            (Role { id: RoleId(2), role: "B".into() }, 1),
777            (Role { id: RoleId(3), role: "C".into() }, 1),
778        ]);
779
780        assert_eq!(engine.list_role_hierarchy_edges(RoleId(1)), vec![]);
781        assert_eq!(engine.list_role_hierarchy_edges(RoleId(2)), vec![]);
782        assert_eq!(engine.list_role_hierarchy_edges(RoleId(3)), vec![]);
783
784        assert_eq!(engine.list_role_hierarchy_edges(RoleId(4)), vec![
785            RoleHierarchy { super_role_id: RoleId(4), role_id: RoleId(1) },
786            RoleHierarchy { super_role_id: RoleId(4), role_id: RoleId(2) },
787        ]);
788
789        assert_eq!(engine.list_role_hierarchy_edges(RoleId(5)), vec![
790            RoleHierarchy { super_role_id: RoleId(5), role_id: RoleId(1) },
791            RoleHierarchy { super_role_id: RoleId(5), role_id: RoleId(3) },
792        ]);
793
794        assert_eq!(engine.list_role_hierarchy_edges(RoleId(6)), vec![
795            RoleHierarchy { super_role_id: RoleId(6), role_id: RoleId(1) },
796            RoleHierarchy { super_role_id: RoleId(6), role_id: RoleId(2) },
797            RoleHierarchy { super_role_id: RoleId(6), role_id: RoleId(3) },
798        ]);
799
800        assert_eq!(engine.list_role_hierarchy_edges(RoleId(7)), vec![
801            RoleHierarchy { super_role_id: RoleId(7), role_id: RoleId(4) },
802            RoleHierarchy { super_role_id: RoleId(7), role_id: RoleId(3) },
803            RoleHierarchy { super_role_id: RoleId(4), role_id: RoleId(1) },
804            RoleHierarchy { super_role_id: RoleId(4), role_id: RoleId(2) },
805        ]);
806    }
807
808    #[test]
809    fn test_unrestricted() {
810        let engine = RbacEngine::from_snapshot(RbacSnapshot::danger_unrestricted());
811        assert!(
812            engine
813                .user_can(UserId(0), Action("browse"), Object("book"))
814                .unwrap()
815        );
816        assert_eq!(
817            engine.get_user_role_permissions(UserId(0)).unwrap(),
818            RbacUserRolePermissions {
819                role: Role {
820                    id: RoleId(1),
821                    role: "unrestricted".to_owned(),
822                },
823                resource_permissions: vec![(
824                    Resource {
825                        id: ResourceId(1),
826                        schema: None,
827                        table: "*".to_owned(),
828                    },
829                    vec![Permission {
830                        id: PermissionId(1),
831                        action: "*".to_owned(),
832                    }]
833                ),],
834            }
835        );
836    }
837}