use super::cache::PermissionCache;
use super::types::PermissionTreeDef;
pub fn resolve_permission(
cache: &PermissionCache,
def: &PermissionTreeDef,
tenant_id: u64,
user_id: &str,
user_roles: &[String],
resource_id: &str,
) -> String {
let no_access = def
.levels
.first()
.cloned()
.unwrap_or_else(|| "none".to_owned());
let mut current = resource_id.to_owned();
let max_depth = 256;
for _ in 0..max_depth {
if let Some((level, _inherited)) = cache.get_grant(tenant_id, ¤t, user_id) {
return level.to_owned();
}
for role in user_roles {
if let Some((level, _inherited)) = cache.get_grant(tenant_id, ¤t, role) {
return level.to_owned();
}
}
match cache.get_parent(tenant_id, ¤t) {
Some(parent) => current = parent.to_owned(),
None => break, }
}
no_access
}
pub fn accessible_resources(
cache: &PermissionCache,
def: &PermissionTreeDef,
tenant_id: u64,
user_id: &str,
user_roles: &[String],
required_level: &str,
) -> Vec<String> {
let required_ordinal = match def.level_ordinal(required_level) {
Some(ord) => ord,
None => return Vec::new(), };
let all_ids = cache.all_resource_ids(tenant_id);
let mut accessible = Vec::new();
for rid in all_ids {
let effective = resolve_permission(cache, def, tenant_id, user_id, user_roles, rid);
if let Some(effective_ordinal) = def.level_ordinal(&effective)
&& effective_ordinal >= required_ordinal
{
accessible.push(rid.to_owned());
}
}
accessible
}
pub fn check_permission(
cache: &PermissionCache,
def: &PermissionTreeDef,
tenant_id: u64,
user_id: &str,
user_roles: &[String],
resource_id: &str,
required_level: &str,
) -> bool {
let effective = resolve_permission(cache, def, tenant_id, user_id, user_roles, resource_id);
def.level_meets_requirement(&effective, required_level)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::control::security::permission_tree::types::PermissionGrant;
fn setup() -> (PermissionCache, PermissionTreeDef) {
let mut cache = PermissionCache::new();
let def = PermissionTreeDef {
resource_column: "id".into(),
graph_index: "resource_tree".into(),
permission_table: "permissions".into(),
levels: vec![
"none".into(),
"viewer".into(),
"commenter".into(),
"editor".into(),
"owner".into(),
],
read_level: "viewer".into(),
write_level: "editor".into(),
delete_level: "owner".into(),
};
cache.put_edge(1, "folder-design", "workspace-acme");
cache.put_edge(1, "doc-mockup", "folder-design");
cache.put_grant(
1,
&PermissionGrant {
resource_id: "folder-design".into(),
grantee: "user-42".into(),
level: "editor".into(),
inherited: false,
},
);
cache.put_grant(
1,
&PermissionGrant {
resource_id: "workspace-acme".into(),
grantee: "user-99".into(),
level: "viewer".into(),
inherited: false,
},
);
(cache, def)
}
#[test]
fn resolve_inherits_from_parent() {
let (cache, def) = setup();
let level = resolve_permission(&cache, &def, 1, "user-42", &[], "doc-mockup");
assert_eq!(level, "editor");
}
#[test]
fn resolve_direct_grant() {
let (cache, def) = setup();
let level = resolve_permission(&cache, &def, 1, "user-42", &[], "folder-design");
assert_eq!(level, "editor");
}
#[test]
fn resolve_no_access() {
let (cache, def) = setup();
let level = resolve_permission(&cache, &def, 1, "user-unknown", &[], "doc-mockup");
assert_eq!(level, "none");
}
#[test]
fn resolve_via_role() {
let (mut cache, def) = setup();
cache.put_grant(
1,
&PermissionGrant {
resource_id: "folder-design".into(),
grantee: "design-team".into(),
level: "editor".into(),
inherited: false,
},
);
let level = resolve_permission(
&cache,
&def,
1,
"user-77",
&["design-team".into()],
"doc-mockup",
);
assert_eq!(level, "editor");
}
#[test]
fn resolve_workspace_level_grant() {
let (cache, def) = setup();
let level = resolve_permission(&cache, &def, 1, "user-99", &[], "doc-mockup");
assert_eq!(level, "viewer");
}
#[test]
fn accessible_resources_filters_correctly() {
let (cache, def) = setup();
let accessible = accessible_resources(&cache, &def, 1, "user-42", &[], "viewer");
assert!(accessible.contains(&"folder-design".to_owned()));
assert!(accessible.contains(&"doc-mockup".to_owned()));
assert!(!accessible.contains(&"workspace-acme".to_owned()));
}
#[test]
fn check_permission_threshold() {
let (cache, def) = setup();
assert!(check_permission(
&cache,
&def,
1,
"user-42",
&[],
"doc-mockup",
"viewer"
));
assert!(check_permission(
&cache,
&def,
1,
"user-42",
&[],
"doc-mockup",
"editor"
));
assert!(!check_permission(
&cache,
&def,
1,
"user-42",
&[],
"doc-mockup",
"owner"
));
}
#[test]
fn override_at_lower_level() {
let (mut cache, def) = setup();
cache.put_grant(
1,
&PermissionGrant {
resource_id: "doc-mockup".into(),
grantee: "user-99".into(),
level: "editor".into(),
inherited: false,
},
);
let level = resolve_permission(&cache, &def, 1, "user-99", &[], "doc-mockup");
assert_eq!(level, "editor");
}
}