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