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