use std::io::Write;
-use std::collections::{HashMap, HashSet, BTreeMap, BTreeSet};
+use std::collections::{HashMap, BTreeMap, BTreeSet};
use std::path::{PathBuf, Path};
use std::sync::{Arc, RwLock};
use std::str::FromStr;
use serde::de::{value, IntoDeserializer};
use proxmox::tools::{fs::replace_file, fs::CreateOptions};
-use proxmox::constnamemap;
+use proxmox::constnamedbitmap;
use proxmox::api::{api, schema::*};
-use crate::api2::types::Userid;
+use crate::api2::types::{Authid,Userid};
// define Privilege bitfield
-constnamemap! {
+constnamedbitmap! {
/// Contains a list of Privileges
PRIVILEGES: u64 => {
- PRIV_SYS_AUDIT("Sys.Audit") = 1 << 0;
- PRIV_SYS_MODIFY("Sys.Modify") = 1 << 1;
- PRIV_SYS_POWER_MANAGEMENT("Sys.PowerManagement") = 1 << 2;
+ PRIV_SYS_AUDIT("Sys.Audit");
+ PRIV_SYS_MODIFY("Sys.Modify");
+ PRIV_SYS_POWER_MANAGEMENT("Sys.PowerManagement");
- PRIV_DATASTORE_AUDIT("Datastore.Audit") = 1 << 3;
- PRIV_DATASTORE_MODIFY("Datastore.Modify") = 1 << 4;
- PRIV_DATASTORE_READ("Datastore.Read") = 1 << 5;
+ PRIV_DATASTORE_AUDIT("Datastore.Audit");
+ PRIV_DATASTORE_ALLOCATE("Datastore.Allocate");
+ PRIV_DATASTORE_MODIFY("Datastore.Modify");
+ PRIV_DATASTORE_READ("Datastore.Read");
+ PRIV_DATASTORE_VERIFY("Datastore.Verify");
/// Datastore.Backup also requires backup ownership
- PRIV_DATASTORE_BACKUP("Datastore.Backup") = 1 << 6;
+ PRIV_DATASTORE_BACKUP("Datastore.Backup");
/// Datastore.Prune also requires backup ownership
- PRIV_DATASTORE_PRUNE("Datastore.Prune") = 1 << 7;
+ PRIV_DATASTORE_PRUNE("Datastore.Prune");
- PRIV_PERMISSIONS_MODIFY("Permissions.Modify") = 1 << 8;
+ PRIV_PERMISSIONS_MODIFY("Permissions.Modify");
- PRIV_REMOTE_AUDIT("Remote.Audit") = 1 << 9;
- PRIV_REMOTE_MODIFY("Remote.Modify") = 1 << 10;
- PRIV_REMOTE_READ("Remote.Read") = 1 << 11;
- PRIV_REMOTE_PRUNE("Remote.Prune") = 1 << 12;
+ PRIV_REMOTE_AUDIT("Remote.Audit");
+ PRIV_REMOTE_MODIFY("Remote.Modify");
+ PRIV_REMOTE_READ("Remote.Read");
+ PRIV_REMOTE_PRUNE("Remote.Prune");
- PRIV_SYS_CONSOLE("Sys.Console") = 1 << 13;
+ PRIV_SYS_CONSOLE("Sys.Console");
}
}
+/// Admin always has all privileges. It can do everything except a few actions
+/// which are limited to the 'root@pam` superuser
pub const ROLE_ADMIN: u64 = std::u64::MAX;
+
+/// NoAccess can be used to remove privileges from specific paths
pub const ROLE_NO_ACCESS: u64 = 0;
pub const ROLE_AUDIT: u64 =
PRIV_DATASTORE_AUDIT |
PRIV_DATASTORE_MODIFY |
PRIV_DATASTORE_READ |
+PRIV_DATASTORE_VERIFY |
PRIV_DATASTORE_BACKUP |
PRIV_DATASTORE_PRUNE;
-/// Datastore.Reader can read datastore content an do restore
+/// Datastore.Reader can read/verify datastore content and do restore
pub const ROLE_DATASTORE_READER: u64 =
PRIV_DATASTORE_AUDIT |
+PRIV_DATASTORE_VERIFY |
PRIV_DATASTORE_READ;
/// Datastore.Backup can do backup and restore, but no prune.
}
pub struct AclTreeNode {
- pub users: HashMap<Userid, HashMap<String, bool>>,
+ pub users: HashMap<Authid, HashMap<String, bool>>,
pub groups: HashMap<String, HashMap<String, bool>>,
pub children: BTreeMap<String, AclTreeNode>,
}
}
}
- pub fn extract_roles(&self, user: &Userid, all: bool) -> HashSet<String> {
- let user_roles = self.extract_user_roles(user, all);
- if !user_roles.is_empty() {
+ pub fn extract_roles(&self, auth_id: &Authid, all: bool) -> HashMap<String, bool> {
+ let user_roles = self.extract_user_roles(auth_id, all);
+ if !user_roles.is_empty() || auth_id.is_token() {
// user privs always override group privs
return user_roles
};
- self.extract_group_roles(user, all)
+ self.extract_group_roles(auth_id.user(), all)
}
- pub fn extract_user_roles(&self, user: &Userid, all: bool) -> HashSet<String> {
+ pub fn extract_user_roles(&self, auth_id: &Authid, all: bool) -> HashMap<String, bool> {
- let mut set = HashSet::new();
+ let mut map = HashMap::new();
- let roles = match self.users.get(user) {
+ let roles = match self.users.get(auth_id) {
Some(m) => m,
- None => return set,
+ None => return map,
};
for (role, propagate) in roles {
if *propagate || all {
if role == ROLE_NAME_NO_ACCESS {
- // return a set with a single role 'NoAccess'
- let mut set = HashSet::new();
- set.insert(role.to_string());
- return set;
+ // return a map with a single role 'NoAccess'
+ let mut map = HashMap::new();
+ map.insert(role.to_string(), false);
+ return map;
}
- set.insert(role.to_string());
+ map.insert(role.to_string(), *propagate);
}
}
- set
+ map
}
- pub fn extract_group_roles(&self, _user: &Userid, all: bool) -> HashSet<String> {
+ pub fn extract_group_roles(&self, _user: &Userid, all: bool) -> HashMap<String, bool> {
- let mut set = HashSet::new();
+ let mut map = HashMap::new();
for (_group, roles) in &self.groups {
let is_member = false; // fixme: check if user is member of the group
for (role, propagate) in roles {
if *propagate || all {
if role == ROLE_NAME_NO_ACCESS {
- // return a set with a single role 'NoAccess'
- let mut set = HashSet::new();
- set.insert(role.to_string());
- return set;
+ // return a map with a single role 'NoAccess'
+ let mut map = HashMap::new();
+ map.insert(role.to_string(), false);
+ return map;
}
- set.insert(role.to_string());
+ map.insert(role.to_string(), *propagate);
}
}
}
- set
+ map
}
pub fn delete_group_role(&mut self, group: &str, role: &str) {
roles.remove(role);
}
- pub fn delete_user_role(&mut self, userid: &Userid, role: &str) {
- let roles = match self.users.get_mut(userid) {
+ pub fn delete_user_role(&mut self, auth_id: &Authid, role: &str) {
+ let roles = match self.users.get_mut(auth_id) {
Some(r) => r,
None => return,
};
}
}
- pub fn insert_user_role(&mut self, user: Userid, role: String, propagate: bool) {
- let map = self.users.entry(user).or_insert_with(|| HashMap::new());
+ pub fn insert_user_role(&mut self, auth_id: Authid, role: String, propagate: bool) {
+ let map = self.users.entry(auth_id).or_insert_with(|| HashMap::new());
if role == ROLE_NAME_NO_ACCESS {
map.clear();
map.insert(role, propagate);
impl AclTree {
pub fn new() -> Self {
- Self { root: AclTreeNode::new() }
+ Self {
+ root: AclTreeNode::new(),
+ }
}
pub fn find_node(&mut self, path: &str) -> Option<&mut AclTreeNode> {
node.delete_group_role(group, role);
}
- pub fn delete_user_role(&mut self, path: &str, userid: &Userid, role: &str) {
+ pub fn delete_user_role(&mut self, path: &str, auth_id: &Authid, role: &str) {
let path = split_acl_path(path);
let node = match self.get_node(&path) {
Some(n) => n,
None => return,
};
- node.delete_user_role(userid, role);
+ node.delete_user_role(auth_id, role);
}
pub fn insert_group_role(&mut self, path: &str, group: &str, role: &str, propagate: bool) {
node.insert_group_role(group.to_string(), role.to_string(), propagate);
}
- pub fn insert_user_role(&mut self, path: &str, user: &Userid, role: &str, propagate: bool) {
+ pub fn insert_user_role(&mut self, path: &str, auth_id: &Authid, role: &str, propagate: bool) {
let path = split_acl_path(path);
let node = self.get_or_insert_node(&path);
- node.insert_user_role(user.to_owned(), role.to_string(), propagate);
+ node.insert_user_role(auth_id.to_owned(), role.to_string(), propagate);
}
fn write_node_config(
let mut role_ug_map0 = HashMap::new();
let mut role_ug_map1 = HashMap::new();
- for (user, roles) in &node.users {
+ for (auth_id, roles) in &node.users {
// no need to save, because root is always 'Administrator'
- if user == "root@pam" { continue; }
+ if !auth_id.is_token() && auth_id.user() == "root@pam" { continue; }
for (role, propagate) in roles {
let role = role.as_str();
- let user = user.to_string();
+ let auth_id = auth_id.to_string();
if *propagate {
role_ug_map1.entry(role).or_insert_with(|| BTreeSet::new())
- .insert(user);
+ .insert(auth_id);
} else {
role_ug_map0.entry(role).or_insert_with(|| BTreeSet::new())
- .insert(user);
+ .insert(auth_id);
}
}
}
bail!("expected '0' or '1' for propagate flag.");
};
- let path = split_acl_path(items[2]);
+ let path_str = items[2];
+ let path = split_acl_path(path_str);
let node = self.get_or_insert_node(&path);
let uglist: Vec<&str> = items[3].split(',').map(|v| v.trim()).collect();
Ok(tree)
}
- pub fn roles(&self, userid: &Userid, path: &[&str]) -> HashSet<String> {
+ pub fn roles(&self, auth_id: &Authid, path: &[&str]) -> HashMap<String, bool> {
let mut node = &self.root;
- let mut role_set = node.extract_roles(userid, path.is_empty());
+ let mut role_map = node.extract_roles(auth_id, 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
+ None => return role_map, // path not found
};
- let new_set = node.extract_roles(userid, last_comp);
- if !new_set.is_empty() {
- // overwrite previous settings
- role_set = new_set;
+
+ let new_map = node.extract_roles(auth_id, last_comp);
+ if !new_map.is_empty() {
+ // overwrite previous maptings
+ role_map = new_map;
}
}
- role_set
+ role_map
}
}
use anyhow::{Error};
use super::AclTree;
- use crate::api2::types::Userid;
+ use crate::api2::types::Authid;
fn check_roles(
tree: &AclTree,
- user: &Userid,
+ auth_id: &Authid,
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::<Vec<String>>();
+ let mut roles = tree.roles(auth_id, &path_vec)
+ .iter().map(|(v, _)| v.clone()).collect::<Vec<String>>();
roles.sort();
let roles = roles.join(",");
- assert_eq!(roles, expected_roles, "\nat check_roles for '{}' on '{}'", user, path);
+ assert_eq!(roles, expected_roles, "\nat check_roles for '{}' on '{}'", auth_id, path);
}
#[test]
acl:1:/storage/store1:user1@pbs:DatastoreBackup
acl:1:/storage/store2:user2@pbs:DatastoreBackup
"###)?;
- let user1: Userid = "user1@pbs".parse()?;
+ let user1: Authid = "user1@pbs".parse()?;
check_roles(&tree, &user1, "/", "");
check_roles(&tree, &user1, "/storage", "Admin");
check_roles(&tree, &user1, "/storage/store1", "DatastoreBackup");
check_roles(&tree, &user1, "/storage/store2", "Admin");
- let user2: Userid = "user2@pbs".parse()?;
+ let user2: Authid = "user2@pbs".parse()?;
check_roles(&tree, &user2, "/", "");
check_roles(&tree, &user2, "/storage", "");
check_roles(&tree, &user2, "/storage/store1", "");
acl:1:/storage:user1@pbs:NoAccess
acl:1:/storage/store1:user1@pbs:DatastoreBackup
"###)?;
- let user1: Userid = "user1@pbs".parse()?;
+ let user1: Authid = "user1@pbs".parse()?;
check_roles(&tree, &user1, "/", "Admin");
check_roles(&tree, &user1, "/storage", "NoAccess");
check_roles(&tree, &user1, "/storage/store1", "DatastoreBackup");
let mut tree = AclTree::new();
- let user1: Userid = "user1@pbs".parse()?;
+ let user1: Authid = "user1@pbs".parse()?;
tree.insert_user_role("/", &user1, "Admin", true);
tree.insert_user_role("/", &user1, "Audit", true);
let mut tree = AclTree::new();
- let user1: Userid = "user1@pbs".parse()?;
+ let user1: Authid = "user1@pbs".parse()?;
tree.insert_user_role("/storage", &user1, "NoAccess", true);