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