diff --git a/src/config/acl.rs b/src/config/acl.rs index 9120c967..16f2a17d 100644 --- a/src/config/acl.rs +++ b/src/config/acl.rs @@ -18,6 +18,9 @@ pub const PRIV_STORE_AUDIT: u64 = 1 << 3; pub const PRIV_STORE_ALLOCATE: u64 = 1 << 4; pub const PRIV_STORE_ALLOCATE_SPACE: u64 = 1 << 5; +pub const ROLE_ADMIN: u64 = std::u64::MAX; +pub const ROLE_NO_ACCESS: u64 = 0; + pub const ROLE_AUDIT: u64 = PRIV_SYS_AUDIT | PRIV_STORE_AUDIT; @@ -35,9 +38,9 @@ lazy_static! { static ref ROLE_NAMES: HashMap<&'static str, u64> = { let mut map = HashMap::new(); - map.insert("Admin", std::u64::MAX); + map.insert("Admin", ROLE_ADMIN); map.insert("Audit", ROLE_AUDIT); - + map.insert("NoAccess", ROLE_NO_ACCESS); map.insert("Store.Admin", ROLE_STORE_ADMIN); map.insert("Store.User", ROLE_STORE_USER); @@ -80,6 +83,64 @@ impl AclTreeNode { } } + pub fn extract_roles(&self, user: &str, all: bool) -> HashSet { + let user_roles = self.extract_user_roles(user, all); + if !user_roles.is_empty() { + // user privs always override group privs + return user_roles + }; + + self.extract_group_roles(user, all) + } + + pub fn extract_user_roles(&self, user: &str, all: bool) -> HashSet { + + let mut set = HashSet::new(); + + let roles = match self.users.get(user) { + Some(m) => m, + None => return set, + }; + + for (role, propagate) in roles { + if *propagate || all { + if role == "NoAccess" { + // return a set with a single role 'NoAccess' + let mut set = HashSet::new(); + set.insert(role.to_string()); + return set; + } + set.insert(role.to_string()); + } + } + + set + } + + pub fn extract_group_roles(&self, _user: &str, all: bool) -> HashSet { + + let mut set = HashSet::new(); + + for (_group, roles) in &self.groups { + let is_member = false; // fixme: check if user is member of the group + if !is_member { continue; } + + for (role, propagate) in roles { + if *propagate || all { + if role == "NoAccess" { + // return a set with a single role 'NoAccess' + let mut set = HashSet::new(); + set.insert(role.to_string()); + return set; + } + set.insert(role.to_string()); + } + } + } + + set + } + pub fn insert_group_role(&mut self, group: String, role: String, propagate: bool) { self.groups .entry(group).or_insert_with(|| HashMap::new()) @@ -275,13 +336,49 @@ impl AclTree { let digest = openssl::sha::sha256(raw.as_bytes()); for (linenr, line) in raw.lines().enumerate() { + let line = line.trim(); + if line.is_empty() { continue; } if let Err(err) = tree.parse_acl_line(line) { - bail!("unable to parse acl config {:?}, line {} - {}", filename, linenr, err); + bail!("unable to parse acl config {:?}, line {} - {}", + filename, linenr+1, err); } } Ok((tree, digest)) } + + pub fn from_raw(raw: &str) -> Result { + let mut tree = Self::new(); + for (linenr, line) in raw.lines().enumerate() { + let line = line.trim(); + if line.is_empty() { continue; } + if let Err(err) = tree.parse_acl_line(line) { + bail!("unable to parse acl config data, line {} - {}", linenr+1, err); + } + } + Ok(tree) + } + + pub fn roles(&self, userid: &str, path: &[&str]) -> HashSet { + + let mut node = &self.root; + let mut role_set = node.extract_roles(userid, path.is_empty()); + + for (pos, comp) in path.iter().enumerate() { + let last_comp = (pos + 1) == path.len(); + node = match node.children.get(*comp) { + Some(n) => n, + None => return role_set, // path not found + }; + let new_set = node.extract_roles(userid, last_comp); + if !new_set.is_empty() { + // overwrite previous settings + role_set = new_set; + } + } + + role_set + } } pub const ACL_CFG_FILENAME: &str = "/etc/proxmox-backup/acl.cfg"; @@ -310,3 +407,95 @@ pub fn store_config(acl: &AclTree, filename: &Path) -> Result<(), Error> { Ok(()) } + + +#[cfg(test)] +mod test { + + use failure::*; + use super::AclTree; + + fn check_roles( + tree: &AclTree, + user: &str, + path: &str, + expected_roles: &str, + ) { + + let path_vec = super::split_acl_path(path); + let mut roles = tree.roles(user, &path_vec) + .iter().map(|v| v.clone()).collect::>(); + roles.sort(); + let roles = roles.join(","); + + assert_eq!(roles, expected_roles, "\nat check_roles for '{}' on '{}'", user, path); + } + + #[test] + fn test_acl_line_compression() -> Result<(), Error> { + + let tree = AclTree::from_raw(r###" +acl:0:/store/store2:user1:Admin +acl:0:/store/store2:user2:Admin +acl:0:/store/store2:user1:Store.User +acl:0:/store/store2:user2:Store.User +"###)?; + + let mut raw: Vec = Vec::new(); + tree.write_config(&mut raw)?; + let raw = std::str::from_utf8(&raw)?; + + assert_eq!(raw, "acl:0:/store/store2:user1,user2:Admin,Store.User\n"); + + Ok(()) + } + + #[test] + fn test_roles_1() -> Result<(), Error> { + + let tree = AclTree::from_raw(r###" +acl:1:/storage:user1@pbs:Admin +acl:1:/storage/store1:user1@pbs:Store.User +acl:1:/storage/store2:user2@pbs:Store.User +"###)?; + check_roles(&tree, "user1@pbs", "/", ""); + check_roles(&tree, "user1@pbs", "/storage", "Admin"); + check_roles(&tree, "user1@pbs", "/storage/store1", "Store.User"); + check_roles(&tree, "user1@pbs", "/storage/store2", "Admin"); + + check_roles(&tree, "user2@pbs", "/", ""); + check_roles(&tree, "user2@pbs", "/storage", ""); + check_roles(&tree, "user2@pbs", "/storage/store1", ""); + check_roles(&tree, "user2@pbs", "/storage/store2", "Store.User"); + + Ok(()) + } + + #[test] + fn test_role_no_access() -> Result<(), Error> { + + let tree = AclTree::from_raw(r###" +acl:1:/:user1@pbs:Admin +acl:1:/storage:user1@pbs:NoAccess +acl:1:/storage/store1:user1@pbs:Store.User +"###)?; + check_roles(&tree, "user1@pbs", "/", "Admin"); + check_roles(&tree, "user1@pbs", "/storage", "NoAccess"); + check_roles(&tree, "user1@pbs", "/storage/store1", "Store.User"); + check_roles(&tree, "user1@pbs", "/storage/store2", "NoAccess"); + check_roles(&tree, "user1@pbs", "/system", "Admin"); + + let tree = AclTree::from_raw(r###" +acl:1:/:user1@pbs:Admin +acl:0:/storage:user1@pbs:NoAccess +acl:1:/storage/store1:user1@pbs:Store.User +"###)?; + check_roles(&tree, "user1@pbs", "/", "Admin"); + check_roles(&tree, "user1@pbs", "/storage", "NoAccess"); + check_roles(&tree, "user1@pbs", "/storage/store1", "Store.User"); + check_roles(&tree, "user1@pbs", "/storage/store2", "Admin"); + check_roles(&tree, "user1@pbs", "/system", "Admin"); + + Ok(()) + } +}