]> git.proxmox.com Git - proxmox-backup.git/blob - src/config/acl.rs
acl: use modified constnamedbitmap macro
[proxmox-backup.git] / src / config / acl.rs
1 use std::io::Write;
2 use std::collections::{HashMap, HashSet, BTreeMap, BTreeSet};
3 use std::path::{PathBuf, Path};
4 use std::sync::{Arc, RwLock};
5 use std::str::FromStr;
6
7 use anyhow::{bail, Error};
8
9 use lazy_static::lazy_static;
10
11 use ::serde::{Deserialize, Serialize};
12 use serde::de::{value, IntoDeserializer};
13
14 use proxmox::tools::{fs::replace_file, fs::CreateOptions};
15 use proxmox::constnamedbitmap;
16 use proxmox::api::{api, schema::*};
17
18 use crate::api2::types::Userid;
19
20 // define Privilege bitfield
21
22 constnamedbitmap! {
23 /// Contains a list of Privileges
24 PRIVILEGES: u64 => {
25 PRIV_SYS_AUDIT("Sys.Audit");
26 PRIV_SYS_MODIFY("Sys.Modify");
27 PRIV_SYS_POWER_MANAGEMENT("Sys.PowerManagement");
28
29 PRIV_DATASTORE_AUDIT("Datastore.Audit");
30 PRIV_DATASTORE_MODIFY("Datastore.Modify");
31 PRIV_DATASTORE_READ("Datastore.Read");
32
33 /// Datastore.Backup also requires backup ownership
34 PRIV_DATASTORE_BACKUP("Datastore.Backup");
35 /// Datastore.Prune also requires backup ownership
36 PRIV_DATASTORE_PRUNE("Datastore.Prune");
37
38 PRIV_PERMISSIONS_MODIFY("Permissions.Modify");
39
40 PRIV_REMOTE_AUDIT("Remote.Audit");
41 PRIV_REMOTE_MODIFY("Remote.Modify");
42 PRIV_REMOTE_READ("Remote.Read");
43 PRIV_REMOTE_PRUNE("Remote.Prune");
44
45 PRIV_SYS_CONSOLE("Sys.Console");
46 }
47 }
48
49
50 /// Admin always has all privileges. It can do everything except a few actions
51 /// which are limited to the 'root@pam` superuser
52 pub const ROLE_ADMIN: u64 = std::u64::MAX;
53
54 /// NoAccess can be used to remove privileges from specific paths
55 pub const ROLE_NO_ACCESS: u64 = 0;
56
57 pub const ROLE_AUDIT: u64 =
58 PRIV_SYS_AUDIT |
59 PRIV_DATASTORE_AUDIT;
60
61 /// Datastore.Admin can do anything on the datastore.
62 pub const ROLE_DATASTORE_ADMIN: u64 =
63 PRIV_DATASTORE_AUDIT |
64 PRIV_DATASTORE_MODIFY |
65 PRIV_DATASTORE_READ |
66 PRIV_DATASTORE_BACKUP |
67 PRIV_DATASTORE_PRUNE;
68
69 /// Datastore.Reader can read datastore content an do restore
70 pub const ROLE_DATASTORE_READER: u64 =
71 PRIV_DATASTORE_AUDIT |
72 PRIV_DATASTORE_READ;
73
74 /// Datastore.Backup can do backup and restore, but no prune.
75 pub const ROLE_DATASTORE_BACKUP: u64 =
76 PRIV_DATASTORE_BACKUP;
77
78 /// Datastore.PowerUser can do backup, restore, and prune.
79 pub const ROLE_DATASTORE_POWERUSER: u64 =
80 PRIV_DATASTORE_PRUNE |
81 PRIV_DATASTORE_BACKUP;
82
83 /// Datastore.Audit can audit the datastore.
84 pub const ROLE_DATASTORE_AUDIT: u64 =
85 PRIV_DATASTORE_AUDIT;
86
87 /// Remote.Audit can audit the remote
88 pub const ROLE_REMOTE_AUDIT: u64 =
89 PRIV_REMOTE_AUDIT;
90
91 /// Remote.Admin can do anything on the remote.
92 pub const ROLE_REMOTE_ADMIN: u64 =
93 PRIV_REMOTE_AUDIT |
94 PRIV_REMOTE_MODIFY |
95 PRIV_REMOTE_READ |
96 PRIV_REMOTE_PRUNE;
97
98 /// Remote.SyncOperator can do read and prune on the remote.
99 pub const ROLE_REMOTE_SYNC_OPERATOR: u64 =
100 PRIV_REMOTE_AUDIT |
101 PRIV_REMOTE_READ |
102 PRIV_REMOTE_PRUNE;
103
104 pub const ROLE_NAME_NO_ACCESS: &str ="NoAccess";
105
106 #[api()]
107 #[repr(u64)]
108 #[derive(Serialize, Deserialize)]
109 /// Role
110 pub enum Role {
111 /// Administrator
112 Admin = ROLE_ADMIN,
113 /// Auditor
114 Audit = ROLE_AUDIT,
115 /// Disable Access
116 NoAccess = ROLE_NO_ACCESS,
117 /// Datastore Administrator
118 DatastoreAdmin = ROLE_DATASTORE_ADMIN,
119 /// Datastore Reader (inspect datastore content and do restores)
120 DatastoreReader = ROLE_DATASTORE_READER,
121 /// Datastore Backup (backup and restore owned backups)
122 DatastoreBackup = ROLE_DATASTORE_BACKUP,
123 /// Datastore PowerUser (backup, restore and prune owned backup)
124 DatastorePowerUser = ROLE_DATASTORE_POWERUSER,
125 /// Datastore Auditor
126 DatastoreAudit = ROLE_DATASTORE_AUDIT,
127 /// Remote Auditor
128 RemoteAudit = ROLE_REMOTE_AUDIT,
129 /// Remote Administrator
130 RemoteAdmin = ROLE_REMOTE_ADMIN,
131 /// Syncronisation Opertator
132 RemoteSyncOperator = ROLE_REMOTE_SYNC_OPERATOR,
133 }
134
135 impl FromStr for Role {
136 type Err = value::Error;
137
138 fn from_str(s: &str) -> Result<Self, Self::Err> {
139 Self::deserialize(s.into_deserializer())
140 }
141 }
142
143 lazy_static! {
144 pub static ref ROLE_NAMES: HashMap<&'static str, (u64, &'static str)> = {
145 let mut map = HashMap::new();
146
147 let list = match Role::API_SCHEMA {
148 Schema::String(StringSchema { format: Some(ApiStringFormat::Enum(list)), .. }) => list,
149 _ => unreachable!(),
150 };
151
152 for entry in list.iter() {
153 let privs: u64 = Role::from_str(entry.value).unwrap() as u64;
154 map.insert(entry.value, (privs, entry.description));
155 }
156
157 map
158 };
159 }
160
161 pub fn split_acl_path(path: &str) -> Vec<&str> {
162
163 let items = path.split('/');
164
165 let mut components = vec![];
166
167 for name in items {
168 if name.is_empty() { continue; }
169 components.push(name);
170 }
171
172 components
173 }
174
175 pub fn check_acl_path(path: &str) -> Result<(), Error> {
176
177 let components = split_acl_path(path);
178
179 let components_len = components.len();
180
181 if components_len == 0 { return Ok(()); }
182 match components[0] {
183 "access" => {
184 if components_len == 1 { return Ok(()); }
185 match components[1] {
186 "acl" | "users" => {
187 if components_len == 2 { return Ok(()); }
188 }
189 _ => {},
190 }
191 }
192 "datastore" => { // /datastore/{store}
193 if components_len <= 2 { return Ok(()); }
194 }
195 "remote" => { // /remote/{remote}/{store}
196 if components_len <= 3 { return Ok(()); }
197 }
198 "system" => {
199 if components_len == 1 { return Ok(()); }
200 match components[1] {
201 "disks" | "log" | "status" | "tasks" | "time" => {
202 if components_len == 2 { return Ok(()); }
203 }
204 "services" => { // /system/services/{service}
205 if components_len <= 3 { return Ok(()); }
206 }
207 "network" => {
208 if components_len == 2 { return Ok(()); }
209 match components[2] {
210 "dns" => {
211 if components_len == 3 { return Ok(()); }
212 }
213 "interfaces" => { // /system/network/interfaces/{iface}
214 if components_len <= 4 { return Ok(()); }
215 }
216 _ => {}
217 }
218 }
219 _ => {}
220 }
221 }
222 _ => {}
223 }
224
225 bail!("invalid acl path '{}'.", path);
226 }
227
228 pub struct AclTree {
229 pub root: AclTreeNode,
230 }
231
232 pub struct AclTreeNode {
233 pub users: HashMap<Userid, HashMap<String, bool>>,
234 pub groups: HashMap<String, HashMap<String, bool>>,
235 pub children: BTreeMap<String, AclTreeNode>,
236 }
237
238 impl AclTreeNode {
239
240 pub fn new() -> Self {
241 Self {
242 users: HashMap::new(),
243 groups: HashMap::new(),
244 children: BTreeMap::new(),
245 }
246 }
247
248 pub fn extract_roles(&self, user: &Userid, all: bool) -> HashSet<String> {
249 let user_roles = self.extract_user_roles(user, all);
250 if !user_roles.is_empty() {
251 // user privs always override group privs
252 return user_roles
253 };
254
255 self.extract_group_roles(user, all)
256 }
257
258 pub fn extract_user_roles(&self, user: &Userid, all: bool) -> HashSet<String> {
259
260 let mut set = HashSet::new();
261
262 let roles = match self.users.get(user) {
263 Some(m) => m,
264 None => return set,
265 };
266
267 for (role, propagate) in roles {
268 if *propagate || all {
269 if role == ROLE_NAME_NO_ACCESS {
270 // return a set with a single role 'NoAccess'
271 let mut set = HashSet::new();
272 set.insert(role.to_string());
273 return set;
274 }
275 set.insert(role.to_string());
276 }
277 }
278
279 set
280 }
281
282 pub fn extract_group_roles(&self, _user: &Userid, all: bool) -> HashSet<String> {
283
284 let mut set = HashSet::new();
285
286 for (_group, roles) in &self.groups {
287 let is_member = false; // fixme: check if user is member of the group
288 if !is_member { continue; }
289
290 for (role, propagate) in roles {
291 if *propagate || all {
292 if role == ROLE_NAME_NO_ACCESS {
293 // return a set with a single role 'NoAccess'
294 let mut set = HashSet::new();
295 set.insert(role.to_string());
296 return set;
297 }
298 set.insert(role.to_string());
299 }
300 }
301 }
302
303 set
304 }
305
306 pub fn delete_group_role(&mut self, group: &str, role: &str) {
307 let roles = match self.groups.get_mut(group) {
308 Some(r) => r,
309 None => return,
310 };
311 roles.remove(role);
312 }
313
314 pub fn delete_user_role(&mut self, userid: &Userid, role: &str) {
315 let roles = match self.users.get_mut(userid) {
316 Some(r) => r,
317 None => return,
318 };
319 roles.remove(role);
320 }
321
322 pub fn insert_group_role(&mut self, group: String, role: String, propagate: bool) {
323 let map = self.groups.entry(group).or_insert_with(|| HashMap::new());
324 if role == ROLE_NAME_NO_ACCESS {
325 map.clear();
326 map.insert(role, propagate);
327 } else {
328 map.remove(ROLE_NAME_NO_ACCESS);
329 map.insert(role, propagate);
330 }
331 }
332
333 pub fn insert_user_role(&mut self, user: Userid, role: String, propagate: bool) {
334 let map = self.users.entry(user).or_insert_with(|| HashMap::new());
335 if role == ROLE_NAME_NO_ACCESS {
336 map.clear();
337 map.insert(role, propagate);
338 } else {
339 map.remove(ROLE_NAME_NO_ACCESS);
340 map.insert(role, propagate);
341 }
342 }
343 }
344
345 impl AclTree {
346
347 pub fn new() -> Self {
348 Self { root: AclTreeNode::new() }
349 }
350
351 pub fn find_node(&mut self, path: &str) -> Option<&mut AclTreeNode> {
352 let path = split_acl_path(path);
353 return self.get_node(&path);
354 }
355
356 fn get_node(&mut self, path: &[&str]) -> Option<&mut AclTreeNode> {
357 let mut node = &mut self.root;
358 for comp in path {
359 node = match node.children.get_mut(*comp) {
360 Some(n) => n,
361 None => return None,
362 };
363 }
364 Some(node)
365 }
366
367 fn get_or_insert_node(&mut self, path: &[&str]) -> &mut AclTreeNode {
368 let mut node = &mut self.root;
369 for comp in path {
370 node = node.children.entry(String::from(*comp))
371 .or_insert_with(|| AclTreeNode::new());
372 }
373 node
374 }
375
376 pub fn delete_group_role(&mut self, path: &str, group: &str, role: &str) {
377 let path = split_acl_path(path);
378 let node = match self.get_node(&path) {
379 Some(n) => n,
380 None => return,
381 };
382 node.delete_group_role(group, role);
383 }
384
385 pub fn delete_user_role(&mut self, path: &str, userid: &Userid, role: &str) {
386 let path = split_acl_path(path);
387 let node = match self.get_node(&path) {
388 Some(n) => n,
389 None => return,
390 };
391 node.delete_user_role(userid, role);
392 }
393
394 pub fn insert_group_role(&mut self, path: &str, group: &str, role: &str, propagate: bool) {
395 let path = split_acl_path(path);
396 let node = self.get_or_insert_node(&path);
397 node.insert_group_role(group.to_string(), role.to_string(), propagate);
398 }
399
400 pub fn insert_user_role(&mut self, path: &str, user: &Userid, role: &str, propagate: bool) {
401 let path = split_acl_path(path);
402 let node = self.get_or_insert_node(&path);
403 node.insert_user_role(user.to_owned(), role.to_string(), propagate);
404 }
405
406 fn write_node_config(
407 node: &AclTreeNode,
408 path: &str,
409 w: &mut dyn Write,
410 ) -> Result<(), Error> {
411
412 let mut role_ug_map0 = HashMap::new();
413 let mut role_ug_map1 = HashMap::new();
414
415 for (user, roles) in &node.users {
416 // no need to save, because root is always 'Administrator'
417 if user == "root@pam" { continue; }
418 for (role, propagate) in roles {
419 let role = role.as_str();
420 let user = user.to_string();
421 if *propagate {
422 role_ug_map1.entry(role).or_insert_with(|| BTreeSet::new())
423 .insert(user);
424 } else {
425 role_ug_map0.entry(role).or_insert_with(|| BTreeSet::new())
426 .insert(user);
427 }
428 }
429 }
430
431 for (group, roles) in &node.groups {
432 for (role, propagate) in roles {
433 let group = format!("@{}", group);
434 if *propagate {
435 role_ug_map1.entry(role).or_insert_with(|| BTreeSet::new())
436 .insert(group);
437 } else {
438 role_ug_map0.entry(role).or_insert_with(|| BTreeSet::new())
439 .insert(group);
440 }
441 }
442 }
443
444 fn group_by_property_list(
445 item_property_map: &HashMap<&str, BTreeSet<String>>,
446 ) -> BTreeMap<String, BTreeSet<String>> {
447 let mut result_map = BTreeMap::new();
448 for (item, property_map) in item_property_map {
449 let item_list = property_map.iter().fold(String::new(), |mut acc, v| {
450 if !acc.is_empty() { acc.push(','); }
451 acc.push_str(v);
452 acc
453 });
454 result_map.entry(item_list).or_insert_with(|| BTreeSet::new())
455 .insert(item.to_string());
456 }
457 result_map
458 }
459
460 let uglist_role_map0 = group_by_property_list(&role_ug_map0);
461 let uglist_role_map1 = group_by_property_list(&role_ug_map1);
462
463 fn role_list(roles: &BTreeSet<String>) -> String {
464 if roles.contains(ROLE_NAME_NO_ACCESS) { return String::from(ROLE_NAME_NO_ACCESS); }
465 roles.iter().fold(String::new(), |mut acc, v| {
466 if !acc.is_empty() { acc.push(','); }
467 acc.push_str(v);
468 acc
469 })
470 }
471
472 for (uglist, roles) in &uglist_role_map0 {
473 let role_list = role_list(roles);
474 writeln!(w, "acl:0:{}:{}:{}", if path.is_empty() { "/" } else { path }, uglist, role_list)?;
475 }
476
477 for (uglist, roles) in &uglist_role_map1 {
478 let role_list = role_list(roles);
479 writeln!(w, "acl:1:{}:{}:{}", if path.is_empty() { "/" } else { path }, uglist, role_list)?;
480 }
481
482 for (name, child) in node.children.iter() {
483 let child_path = format!("{}/{}", path, name);
484 Self::write_node_config(child, &child_path, w)?;
485 }
486
487 Ok(())
488 }
489
490 pub fn write_config(&self, w: &mut dyn Write) -> Result<(), Error> {
491 Self::write_node_config(&self.root, "", w)
492 }
493
494 fn parse_acl_line(&mut self, line: &str) -> Result<(), Error> {
495
496 let items: Vec<&str> = line.split(':').collect();
497
498 if items.len() != 5 {
499 bail!("wrong number of items.");
500 }
501
502 if items[0] != "acl" {
503 bail!("line does not start with 'acl'.");
504 }
505
506 let propagate = if items[1] == "0" {
507 false
508 } else if items[1] == "1" {
509 true
510 } else {
511 bail!("expected '0' or '1' for propagate flag.");
512 };
513
514 let path = split_acl_path(items[2]);
515 let node = self.get_or_insert_node(&path);
516
517 let uglist: Vec<&str> = items[3].split(',').map(|v| v.trim()).collect();
518
519 let rolelist: Vec<&str> = items[4].split(',').map(|v| v.trim()).collect();
520
521 for user_or_group in &uglist {
522 for role in &rolelist {
523 if !ROLE_NAMES.contains_key(role) {
524 bail!("unknown role '{}'", role);
525 }
526 if user_or_group.starts_with('@') {
527 let group = &user_or_group[1..];
528 node.insert_group_role(group.to_string(), role.to_string(), propagate);
529 } else {
530 node.insert_user_role(user_or_group.parse()?, role.to_string(), propagate);
531 }
532 }
533 }
534
535 Ok(())
536 }
537
538 pub fn load(filename: &Path) -> Result<(Self, [u8;32]), Error> {
539 let mut tree = Self::new();
540
541 let raw = match std::fs::read_to_string(filename) {
542 Ok(v) => v,
543 Err(err) => {
544 if err.kind() == std::io::ErrorKind::NotFound {
545 String::new()
546 } else {
547 bail!("unable to read acl config {:?} - {}", filename, err);
548 }
549 }
550 };
551
552 let digest = openssl::sha::sha256(raw.as_bytes());
553
554 for (linenr, line) in raw.lines().enumerate() {
555 let line = line.trim();
556 if line.is_empty() { continue; }
557 if let Err(err) = tree.parse_acl_line(line) {
558 bail!("unable to parse acl config {:?}, line {} - {}",
559 filename, linenr+1, err);
560 }
561 }
562
563 Ok((tree, digest))
564 }
565
566 pub fn from_raw(raw: &str) -> Result<Self, Error> {
567 let mut tree = Self::new();
568 for (linenr, line) in raw.lines().enumerate() {
569 let line = line.trim();
570 if line.is_empty() { continue; }
571 if let Err(err) = tree.parse_acl_line(line) {
572 bail!("unable to parse acl config data, line {} - {}", linenr+1, err);
573 }
574 }
575 Ok(tree)
576 }
577
578 pub fn roles(&self, userid: &Userid, path: &[&str]) -> HashSet<String> {
579
580 let mut node = &self.root;
581 let mut role_set = node.extract_roles(userid, path.is_empty());
582
583 for (pos, comp) in path.iter().enumerate() {
584 let last_comp = (pos + 1) == path.len();
585 node = match node.children.get(*comp) {
586 Some(n) => n,
587 None => return role_set, // path not found
588 };
589 let new_set = node.extract_roles(userid, last_comp);
590 if !new_set.is_empty() {
591 // overwrite previous settings
592 role_set = new_set;
593 }
594 }
595
596 role_set
597 }
598 }
599
600 pub const ACL_CFG_FILENAME: &str = "/etc/proxmox-backup/acl.cfg";
601 pub const ACL_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.acl.lck";
602
603 pub fn config() -> Result<(AclTree, [u8; 32]), Error> {
604 let path = PathBuf::from(ACL_CFG_FILENAME);
605 AclTree::load(&path)
606 }
607
608 pub fn cached_config() -> Result<Arc<AclTree>, Error> {
609
610 struct ConfigCache {
611 data: Option<Arc<AclTree>>,
612 last_mtime: i64,
613 last_mtime_nsec: i64,
614 }
615
616 lazy_static! {
617 static ref CACHED_CONFIG: RwLock<ConfigCache> = RwLock::new(
618 ConfigCache { data: None, last_mtime: 0, last_mtime_nsec: 0 });
619 }
620
621 let stat = match nix::sys::stat::stat(ACL_CFG_FILENAME) {
622 Ok(stat) => Some(stat),
623 Err(nix::Error::Sys(nix::errno::Errno::ENOENT)) => None,
624 Err(err) => bail!("unable to stat '{}' - {}", ACL_CFG_FILENAME, err),
625 };
626
627 { // limit scope
628 let cache = CACHED_CONFIG.read().unwrap();
629 if let Some(ref config) = cache.data {
630 if let Some(stat) = stat {
631 if stat.st_mtime == cache.last_mtime && stat.st_mtime_nsec == cache.last_mtime_nsec {
632 return Ok(config.clone());
633 }
634 } else if cache.last_mtime == 0 && cache.last_mtime_nsec == 0 {
635 return Ok(config.clone());
636 }
637 }
638 }
639
640 let (config, _digest) = config()?;
641 let config = Arc::new(config);
642
643 let mut cache = CACHED_CONFIG.write().unwrap();
644 if let Some(stat) = stat {
645 cache.last_mtime = stat.st_mtime;
646 cache.last_mtime_nsec = stat.st_mtime_nsec;
647 }
648 cache.data = Some(config.clone());
649
650 Ok(config)
651 }
652
653 pub fn save_config(acl: &AclTree) -> Result<(), Error> {
654 let mut raw: Vec<u8> = Vec::new();
655
656 acl.write_config(&mut raw)?;
657
658 let backup_user = crate::backup::backup_user()?;
659 let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640);
660 // set the correct owner/group/permissions while saving file
661 // owner(rw) = root, group(r)= backup
662 let options = CreateOptions::new()
663 .perm(mode)
664 .owner(nix::unistd::ROOT)
665 .group(backup_user.gid);
666
667 replace_file(ACL_CFG_FILENAME, &raw, options)?;
668
669 Ok(())
670 }
671
672 #[cfg(test)]
673 mod test {
674 use anyhow::{Error};
675 use super::AclTree;
676
677 use crate::api2::types::Userid;
678
679 fn check_roles(
680 tree: &AclTree,
681 user: &Userid,
682 path: &str,
683 expected_roles: &str,
684 ) {
685
686 let path_vec = super::split_acl_path(path);
687 let mut roles = tree.roles(user, &path_vec)
688 .iter().map(|v| v.clone()).collect::<Vec<String>>();
689 roles.sort();
690 let roles = roles.join(",");
691
692 assert_eq!(roles, expected_roles, "\nat check_roles for '{}' on '{}'", user, path);
693 }
694
695 #[test]
696 fn test_acl_line_compression() {
697
698 let tree = AclTree::from_raw(
699 "\
700 acl:0:/store/store2:user1@pbs:Admin\n\
701 acl:0:/store/store2:user2@pbs:Admin\n\
702 acl:0:/store/store2:user1@pbs:DatastoreBackup\n\
703 acl:0:/store/store2:user2@pbs:DatastoreBackup\n\
704 ",
705 )
706 .expect("failed to parse acl tree");
707
708 let mut raw: Vec<u8> = Vec::new();
709 tree.write_config(&mut raw).expect("failed to write acl tree");
710 let raw = std::str::from_utf8(&raw).expect("acl tree is not valid utf8");
711
712 assert_eq!(raw, "acl:0:/store/store2:user1@pbs,user2@pbs:Admin,DatastoreBackup\n");
713 }
714
715 #[test]
716 fn test_roles_1() -> Result<(), Error> {
717
718 let tree = AclTree::from_raw(r###"
719 acl:1:/storage:user1@pbs:Admin
720 acl:1:/storage/store1:user1@pbs:DatastoreBackup
721 acl:1:/storage/store2:user2@pbs:DatastoreBackup
722 "###)?;
723 let user1: Userid = "user1@pbs".parse()?;
724 check_roles(&tree, &user1, "/", "");
725 check_roles(&tree, &user1, "/storage", "Admin");
726 check_roles(&tree, &user1, "/storage/store1", "DatastoreBackup");
727 check_roles(&tree, &user1, "/storage/store2", "Admin");
728
729 let user2: Userid = "user2@pbs".parse()?;
730 check_roles(&tree, &user2, "/", "");
731 check_roles(&tree, &user2, "/storage", "");
732 check_roles(&tree, &user2, "/storage/store1", "");
733 check_roles(&tree, &user2, "/storage/store2", "DatastoreBackup");
734
735 Ok(())
736 }
737
738 #[test]
739 fn test_role_no_access() -> Result<(), Error> {
740
741 let tree = AclTree::from_raw(r###"
742 acl:1:/:user1@pbs:Admin
743 acl:1:/storage:user1@pbs:NoAccess
744 acl:1:/storage/store1:user1@pbs:DatastoreBackup
745 "###)?;
746 let user1: Userid = "user1@pbs".parse()?;
747 check_roles(&tree, &user1, "/", "Admin");
748 check_roles(&tree, &user1, "/storage", "NoAccess");
749 check_roles(&tree, &user1, "/storage/store1", "DatastoreBackup");
750 check_roles(&tree, &user1, "/storage/store2", "NoAccess");
751 check_roles(&tree, &user1, "/system", "Admin");
752
753 let tree = AclTree::from_raw(r###"
754 acl:1:/:user1@pbs:Admin
755 acl:0:/storage:user1@pbs:NoAccess
756 acl:1:/storage/store1:user1@pbs:DatastoreBackup
757 "###)?;
758 check_roles(&tree, &user1, "/", "Admin");
759 check_roles(&tree, &user1, "/storage", "NoAccess");
760 check_roles(&tree, &user1, "/storage/store1", "DatastoreBackup");
761 check_roles(&tree, &user1, "/storage/store2", "Admin");
762 check_roles(&tree, &user1, "/system", "Admin");
763
764 Ok(())
765 }
766
767 #[test]
768 fn test_role_add_delete() -> Result<(), Error> {
769
770 let mut tree = AclTree::new();
771
772 let user1: Userid = "user1@pbs".parse()?;
773
774 tree.insert_user_role("/", &user1, "Admin", true);
775 tree.insert_user_role("/", &user1, "Audit", true);
776
777 check_roles(&tree, &user1, "/", "Admin,Audit");
778
779 tree.insert_user_role("/", &user1, "NoAccess", true);
780 check_roles(&tree, &user1, "/", "NoAccess");
781
782 let mut raw: Vec<u8> = Vec::new();
783 tree.write_config(&mut raw)?;
784 let raw = std::str::from_utf8(&raw)?;
785
786 assert_eq!(raw, "acl:1:/:user1@pbs:NoAccess\n");
787
788 Ok(())
789 }
790
791 #[test]
792 fn test_no_access_overwrite() -> Result<(), Error> {
793
794 let mut tree = AclTree::new();
795
796 let user1: Userid = "user1@pbs".parse()?;
797
798 tree.insert_user_role("/storage", &user1, "NoAccess", true);
799
800 check_roles(&tree, &user1, "/storage", "NoAccess");
801
802 tree.insert_user_role("/storage", &user1, "Admin", true);
803 tree.insert_user_role("/storage", &user1, "Audit", true);
804
805 check_roles(&tree, &user1, "/storage", "Admin,Audit");
806
807 tree.insert_user_role("/storage", &user1, "NoAccess", true);
808
809 check_roles(&tree, &user1, "/storage", "NoAccess");
810
811 Ok(())
812 }
813 }