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