use std::io::Write;
-use std::collections::{HashMap, HashSet};
+use std::collections::{HashMap, HashSet, BTreeMap, BTreeSet};
use std::path::{PathBuf, Path};
+use std::sync::{Arc, RwLock};
-use failure::*;
+use anyhow::{bail, Error};
use lazy_static::lazy_static;
// define Privilege bitfield
-pub const PRIV_SYS_AUDIT: u64 = 1 << 0;
-pub const PRIV_SYS_MODIFY: u64 = 1 << 1;
-pub const PRIV_SYS_POWER_MANAGEMENT: u64 = 1 << 2;
+pub const PRIV_SYS_AUDIT: u64 = 1 << 0;
+pub const PRIV_SYS_MODIFY: u64 = 1 << 1;
+pub const PRIV_SYS_POWER_MANAGEMENT: u64 = 1 << 2;
-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 PRIV_DATASTORE_AUDIT: u64 = 1 << 3;
+pub const PRIV_DATASTORE_MODIFY: u64 = 1 << 4;
+pub const PRIV_DATASTORE_CREATE_BACKUP: u64 = 1 << 5;
+pub const PRIV_DATASTORE_READ: u64 = 1 << 6;
+pub const PRIV_DATASTORE_PRUNE: u64 = 1 << 7;
+
+pub const PRIV_PERMISSIONS_MODIFY: u64 = 1 << 8;
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;
+PRIV_DATASTORE_AUDIT;
+
+pub const ROLE_DATASTORE_ADMIN: u64 =
+PRIV_DATASTORE_AUDIT |
+PRIV_DATASTORE_MODIFY |
+PRIV_DATASTORE_CREATE_BACKUP |
+PRIV_DATASTORE_READ |
+PRIV_DATASTORE_PRUNE;
+
+pub const ROLE_DATASTORE_USER: u64 =
+PRIV_DATASTORE_CREATE_BACKUP;
-pub const ROLE_STORE_ADMIN: u64 =
-PRIV_STORE_AUDIT |
-PRIV_STORE_ALLOCATE |
-PRIV_STORE_ALLOCATE_SPACE;
+pub const ROLE_DATASTORE_AUDIT: u64 =
+PRIV_DATASTORE_AUDIT;
-pub const ROLE_STORE_USER: u64 =
-PRIV_STORE_AUDIT |
-PRIV_STORE_ALLOCATE_SPACE;
+pub const ROLE_NAME_NO_ACCESS: &str ="NoAccess";
lazy_static! {
- static ref ROLE_NAMES: HashMap<&'static str, u64> = {
+ pub static ref ROLE_NAMES: HashMap<&'static str, (u64, &'static str)> = {
let mut map = HashMap::new();
- 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);
+ map.insert("Admin", (
+ ROLE_ADMIN,
+ "Administrator",
+ ));
+ map.insert("Audit", (
+ ROLE_AUDIT,
+ "Auditor",
+ ));
+ map.insert(ROLE_NAME_NO_ACCESS, (
+ ROLE_NO_ACCESS,
+ "Disable access",
+ ));
+
+ map.insert("Datastore.Admin", (
+ ROLE_DATASTORE_ADMIN,
+ "Datastore Administrator",
+ ));
+ map.insert("Datastore.User", (
+ ROLE_DATASTORE_USER,
+ "Datastore User",
+ ));
+ map.insert("Datastore.Audit", (
+ ROLE_DATASTORE_AUDIT,
+ "Datastore Auditor",
+ ));
map
};
}
-fn split_acl_path(path: &str) -> Vec<&str> {
+pub fn split_acl_path(path: &str) -> Vec<&str> {
let items = path.split('/');
}
pub struct AclTree {
- root: AclTreeNode,
+ pub root: AclTreeNode,
}
-struct AclTreeNode {
- users: HashMap<String, HashMap<String, bool>>,
- groups: HashMap<String, HashMap<String, bool>>,
- children: HashMap<String, AclTreeNode>,
+pub struct AclTreeNode {
+ pub users: HashMap<String, HashMap<String, bool>>,
+ pub groups: HashMap<String, HashMap<String, bool>>,
+ pub children: BTreeMap<String, AclTreeNode>,
}
impl AclTreeNode {
Self {
users: HashMap::new(),
groups: HashMap::new(),
- children: HashMap::new(),
+ children: BTreeMap::new(),
}
}
for (role, propagate) in roles {
if *propagate || all {
- if role == "NoAccess" {
+ if role == ROLE_NAME_NO_ACCESS {
// return a set with a single role 'NoAccess'
let mut set = HashSet::new();
set.insert(role.to_string());
for (role, propagate) in roles {
if *propagate || all {
- if role == "NoAccess" {
+ if role == ROLE_NAME_NO_ACCESS {
// return a set with a single role 'NoAccess'
let mut set = HashSet::new();
set.insert(role.to_string());
set
}
+ pub fn delete_group_role(&mut self, group: &str, role: &str) {
+ let roles = match self.groups.get_mut(group) {
+ Some(r) => r,
+ None => return,
+ };
+ roles.remove(role);
+ }
+
+ pub fn delete_user_role(&mut self, userid: &str, role: &str) {
+ let roles = match self.users.get_mut(userid) {
+ Some(r) => r,
+ None => return,
+ };
+ roles.remove(role);
+ }
+
pub fn insert_group_role(&mut self, group: String, role: String, propagate: bool) {
- self.groups
- .entry(group).or_insert_with(|| HashMap::new())
- .insert(role, propagate);
+ let map = self.groups.entry(group).or_insert_with(|| HashMap::new());
+ if role == ROLE_NAME_NO_ACCESS {
+ map.clear();
+ map.insert(role, propagate);
+ } else {
+ map.remove(ROLE_NAME_NO_ACCESS);
+ map.insert(role, propagate);
+ }
}
pub fn insert_user_role(&mut self, user: String, role: String, propagate: bool) {
- self.users
- .entry(user).or_insert_with(|| HashMap::new())
- .insert(role, propagate);
+ let map = self.users.entry(user).or_insert_with(|| HashMap::new());
+ if role == ROLE_NAME_NO_ACCESS {
+ map.clear();
+ map.insert(role, propagate);
+ } else {
+ map.remove(ROLE_NAME_NO_ACCESS);
+ map.insert(role, propagate);
+ }
}
}
Self { root: AclTreeNode::new() }
}
+ fn get_node(&mut self, path: &[&str]) -> Option<&mut AclTreeNode> {
+ let mut node = &mut self.root;
+ for comp in path {
+ node = match node.children.get_mut(*comp) {
+ Some(n) => n,
+ None => return None,
+ };
+ }
+ Some(node)
+ }
+
fn get_or_insert_node(&mut self, path: &[&str]) -> &mut AclTreeNode {
let mut node = &mut self.root;
for comp in path {
node
}
+ pub fn delete_group_role(&mut self, path: &str, group: &str, role: &str) {
+ let path = split_acl_path(path);
+ let node = match self.get_node(&path) {
+ Some(n) => n,
+ None => return,
+ };
+ node.delete_group_role(group, role);
+ }
+
+ pub fn delete_user_role(&mut self, path: &str, userid: &str, 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);
+ }
+
pub fn insert_group_role(&mut self, path: &str, group: &str, role: &str, propagate: bool) {
let path = split_acl_path(path);
let node = self.get_or_insert_node(&path);
let role = role.as_str();
let user = user.to_string();
if *propagate {
- role_ug_map1.entry(role).or_insert_with(|| HashSet::new())
+ role_ug_map1.entry(role).or_insert_with(|| BTreeSet::new())
.insert(user);
} else {
- role_ug_map0.entry(role).or_insert_with(|| HashSet::new())
+ role_ug_map0.entry(role).or_insert_with(|| BTreeSet::new())
.insert(user);
}
}
for (role, propagate) in roles {
let group = format!("@{}", group);
if *propagate {
- role_ug_map1.entry(role).or_insert_with(|| HashSet::new())
+ role_ug_map1.entry(role).or_insert_with(|| BTreeSet::new())
.insert(group);
} else {
- role_ug_map0.entry(role).or_insert_with(|| HashSet::new())
+ role_ug_map0.entry(role).or_insert_with(|| BTreeSet::new())
.insert(group);
}
}
}
fn group_by_property_list(
- item_property_map: &HashMap<&str, HashSet<String>>,
- ) -> HashMap<String, HashSet<String>> {
- let mut result_map = HashMap::new();
+ item_property_map: &HashMap<&str, BTreeSet<String>>,
+ ) -> BTreeMap<String, BTreeSet<String>> {
+ let mut result_map = BTreeMap::new();
for (item, property_map) in item_property_map {
- let mut item_list = property_map.iter().map(|v| v.as_str())
- .collect::<Vec<&str>>();
- item_list.sort();
- let item_list = item_list.join(",");
- result_map.entry(item_list).or_insert_with(|| HashSet::new())
+ let item_list = property_map.iter().fold(String::new(), |mut acc, v| {
+ if !acc.is_empty() { acc.push(','); }
+ acc.push_str(v);
+ acc
+ });
+ result_map.entry(item_list).or_insert_with(|| BTreeSet::new())
.insert(item.to_string());
}
result_map
}
- let mut uglist_role_map0 = group_by_property_list(&role_ug_map0)
- .into_iter()
- .collect::<Vec<(String, HashSet<String>)>>();
- uglist_role_map0.sort_unstable_by(|a,b| a.0.cmp(&b.0));
+ let uglist_role_map0 = group_by_property_list(&role_ug_map0);
+ let uglist_role_map1 = group_by_property_list(&role_ug_map1);
- let mut uglist_role_map1 = group_by_property_list(&role_ug_map1)
- .into_iter()
- .collect::<Vec<(String, HashSet<String>)>>();
- uglist_role_map1.sort_unstable_by(|a,b| a.0.cmp(&b.0));
-
-
- for (uglist, roles) in uglist_role_map0 {
- let mut role_list = roles.iter().map(|v| v.as_str())
- .collect::<Vec<&str>>();
- role_list.sort();
- writeln!(w, "acl:0:{}:{}:{}", path, uglist, role_list.join(","))?;
+ fn role_list(roles: &BTreeSet<String>) -> String {
+ if roles.contains(ROLE_NAME_NO_ACCESS) { return String::from(ROLE_NAME_NO_ACCESS); }
+ roles.iter().fold(String::new(), |mut acc, v| {
+ if !acc.is_empty() { acc.push(','); }
+ acc.push_str(v);
+ acc
+ })
}
- for (uglist, roles) in uglist_role_map1 {
- let mut role_list = roles.iter().map(|v| v.as_str())
- .collect::<Vec<&str>>();
- role_list.sort();
- writeln!(w, "acl:1:{}:{}:{}", path, uglist, role_list.join(","))?;
+ for (uglist, roles) in &uglist_role_map0 {
+ let role_list = role_list(roles);
+ writeln!(w, "acl:0:{}:{}:{}", if path.is_empty() { "/" } else { path }, uglist, role_list)?;
}
- let mut child_names = node.children.keys().map(|v| v.as_str()).collect::<Vec<&str>>();
- child_names.sort();
+ for (uglist, roles) in &uglist_role_map1 {
+ let role_list = role_list(roles);
+ writeln!(w, "acl:1:{}:{}:{}", if path.is_empty() { "/" } else { path }, uglist, role_list)?;
+ }
- for name in child_names {
- let child = node.children.get(name).unwrap();
+ for (name, child) in node.children.iter() {
let child_path = format!("{}/{}", path, name);
Self::write_node_config(child, &child_path, w)?;
}
AclTree::load(&path)
}
-pub fn store_config(acl: &AclTree, filename: &Path) -> Result<(), Error> {
+pub fn cached_config() -> Result<Arc<AclTree>, Error> {
+
+ struct ConfigCache {
+ data: Option<Arc<AclTree>>,
+ last_mtime: i64,
+ last_mtime_nsec: i64,
+ }
+
+ lazy_static! {
+ static ref CACHED_CONFIG: RwLock<ConfigCache> = RwLock::new(
+ ConfigCache { data: None, last_mtime: 0, last_mtime_nsec: 0 });
+ }
+
+ let stat = nix::sys::stat::stat(ACL_CFG_FILENAME)?;
+
+ { // limit scope
+ let cache = CACHED_CONFIG.read().unwrap();
+ if stat.st_mtime == cache.last_mtime && stat.st_mtime_nsec == cache.last_mtime_nsec {
+ if let Some(ref config) = cache.data {
+ return Ok(config.clone());
+ }
+ }
+ }
+
+ let (config, _digest) = config()?;
+ let config = Arc::new(config);
+
+ let mut cache = CACHED_CONFIG.write().unwrap();
+ cache.last_mtime = stat.st_mtime;
+ cache.last_mtime_nsec = stat.st_mtime_nsec;
+ cache.data = Some(config.clone());
+
+ Ok(config)
+}
+
+pub fn save_config(acl: &AclTree) -> Result<(), Error> {
let mut raw: Vec<u8> = Vec::new();
acl.write_config(&mut raw)?;
.owner(nix::unistd::ROOT)
.group(backup_user.gid);
- replace_file(filename, &raw, options)?;
+ replace_file(ACL_CFG_FILENAME, &raw, options)?;
Ok(())
}
-
#[cfg(test)]
mod test {
- use failure::*;
+ use anyhow::{Error};
use super::AclTree;
fn check_roles(
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
+acl:0:/store/store2:user1:Datastore.User
+acl:0:/store/store2:user2:Datastore.User
"###)?;
let mut raw: Vec<u8> = 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");
+ assert_eq!(raw, "acl:0:/store/store2:user1,user2:Admin,Datastore.User\n");
Ok(())
}
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
+acl:1:/storage/store1:user1@pbs:Datastore.User
+acl:1:/storage/store2:user2@pbs:Datastore.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/store1", "Datastore.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");
+ check_roles(&tree, "user2@pbs", "/storage/store2", "Datastore.User");
Ok(())
}
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
+acl:1:/storage/store1:user1@pbs:Datastore.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/store1", "Datastore.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
+acl:1:/storage/store1:user1@pbs:Datastore.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/store1", "Datastore.User");
check_roles(&tree, "user1@pbs", "/storage/store2", "Admin");
check_roles(&tree, "user1@pbs", "/system", "Admin");
Ok(())
}
+
+ #[test]
+ fn test_role_add_delete() -> Result<(), Error> {
+
+ let mut tree = AclTree::new();
+
+ tree.insert_user_role("/", "user1@pbs", "Admin", true);
+ tree.insert_user_role("/", "user1@pbs", "Audit", true);
+
+ check_roles(&tree, "user1@pbs", "/", "Admin,Audit");
+
+ tree.insert_user_role("/", "user1@pbs", "NoAccess", true);
+ check_roles(&tree, "user1@pbs", "/", "NoAccess");
+
+ let mut raw: Vec<u8> = Vec::new();
+ tree.write_config(&mut raw)?;
+ let raw = std::str::from_utf8(&raw)?;
+
+ assert_eq!(raw, "acl:1:/:user1@pbs:NoAccess\n");
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_no_access_overwrite() -> Result<(), Error> {
+
+ let mut tree = AclTree::new();
+
+ tree.insert_user_role("/storage", "user1@pbs", "NoAccess", true);
+
+ check_roles(&tree, "user1@pbs", "/storage", "NoAccess");
+
+ tree.insert_user_role("/storage", "user1@pbs", "Admin", true);
+ tree.insert_user_role("/storage", "user1@pbs", "Audit", true);
+
+ check_roles(&tree, "user1@pbs", "/storage", "Admin,Audit");
+
+ tree.insert_user_role("/storage", "user1@pbs", "NoAccess", true);
+
+ check_roles(&tree, "user1@pbs", "/storage", "NoAccess");
+
+ Ok(())
+ }
+
}