]> git.proxmox.com Git - proxmox-backup.git/blob - pbs-config/src/acl.rs
move acl to pbs_config workspaces, pbs_api_types cleanups
[proxmox-backup.git] / pbs-config / src / acl.rs
1 use std::collections::{BTreeMap, BTreeSet, HashMap};
2 use std::io::Write;
3 use std::path::{Path, PathBuf};
4 use std::str::FromStr;
5 use std::sync::{Arc, RwLock};
6
7 use anyhow::{bail, Error};
8
9 use lazy_static::lazy_static;
10
11 use proxmox::api::schema::{Schema, StringSchema, ApiStringFormat, ApiType};
12
13 use pbs_api_types::{Authid, Userid, Role, ROLE_NAME_NO_ACCESS};
14
15 use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard};
16
17 lazy_static! {
18 /// Map of pre-defined [Roles](Role) to their associated [privileges](PRIVILEGES) combination and
19 /// description.
20 pub static ref ROLE_NAMES: HashMap<&'static str, (u64, &'static str)> = {
21 let mut map = HashMap::new();
22
23 let list = match Role::API_SCHEMA {
24 Schema::String(StringSchema { format: Some(ApiStringFormat::Enum(list)), .. }) => list,
25 _ => unreachable!(),
26 };
27
28 for entry in list.iter() {
29 let privs: u64 = Role::from_str(entry.value).unwrap() as u64;
30 map.insert(entry.value, (privs, entry.description));
31 }
32
33 map
34 };
35 }
36
37 pub fn split_acl_path(path: &str) -> Vec<&str> {
38 let items = path.split('/');
39
40 let mut components = vec![];
41
42 for name in items {
43 if name.is_empty() {
44 continue;
45 }
46 components.push(name);
47 }
48
49 components
50 }
51
52 /// Check whether a given ACL `path` conforms to the expected schema.
53 ///
54 /// Currently this just checks for the number of components for various sub-trees.
55 pub fn check_acl_path(path: &str) -> Result<(), Error> {
56 let components = split_acl_path(path);
57
58 let components_len = components.len();
59
60 if components_len == 0 {
61 return Ok(());
62 }
63 match components[0] {
64 "access" => {
65 if components_len == 1 {
66 return Ok(());
67 }
68 match components[1] {
69 "acl" | "users" | "domains" => {
70 if components_len == 2 {
71 return Ok(());
72 }
73 }
74 // /access/openid/{endpoint}
75 "openid" => {
76 if components_len <= 3 {
77 return Ok(());
78 }
79 }
80 _ => {}
81 }
82 }
83 "datastore" => {
84 // /datastore/{store}
85 if components_len <= 2 {
86 return Ok(());
87 }
88 }
89 "remote" => {
90 // /remote/{remote}/{store}
91 if components_len <= 3 {
92 return Ok(());
93 }
94 }
95 "system" => {
96 if components_len == 1 {
97 return Ok(());
98 }
99 match components[1] {
100 "certificates" | "disks" | "log" | "status" | "tasks" | "time" => {
101 if components_len == 2 {
102 return Ok(());
103 }
104 }
105 "services" => {
106 // /system/services/{service}
107 if components_len <= 3 {
108 return Ok(());
109 }
110 }
111 "network" => {
112 if components_len == 2 {
113 return Ok(());
114 }
115 match components[2] {
116 "dns" => {
117 if components_len == 3 {
118 return Ok(());
119 }
120 }
121 "interfaces" => {
122 // /system/network/interfaces/{iface}
123 if components_len <= 4 {
124 return Ok(());
125 }
126 }
127 _ => {}
128 }
129 }
130 _ => {}
131 }
132 }
133 "tape" => {
134 if components_len == 1 {
135 return Ok(());
136 }
137 match components[1] {
138 "device" => {
139 // /tape/device/{name}
140 if components_len <= 3 {
141 return Ok(());
142 }
143 }
144 "pool" => {
145 // /tape/pool/{name}
146 if components_len <= 3 {
147 return Ok(());
148 }
149 }
150 "job" => {
151 // /tape/job/{id}
152 if components_len <= 3 {
153 return Ok(());
154 }
155 }
156 _ => {}
157 }
158 }
159 _ => {}
160 }
161
162 bail!("invalid acl path '{}'.", path);
163 }
164
165 /// Tree representing a parsed acl.cfg
166 #[derive(Default)]
167 pub struct AclTree {
168 /// Root node of the tree.
169 ///
170 /// The rest of the tree is available via [find_node()](AclTree::find_node()) or an
171 /// [AclTreeNode]'s [children](AclTreeNode::children) member.
172 pub root: AclTreeNode,
173 }
174
175 /// Node representing ACLs for a certain ACL path.
176 #[derive(Default)]
177 pub struct AclTreeNode {
178 /// [User](crate::config::user::User) or
179 /// [Token](crate::config::user::ApiToken) ACLs for this node.
180 pub users: HashMap<Authid, HashMap<String, bool>>,
181 /// `Group` ACLs for this node (not yet implemented)
182 pub groups: HashMap<String, HashMap<String, bool>>,
183 /// `AclTreeNodes` representing ACL paths directly below the current one.
184 pub children: BTreeMap<String, AclTreeNode>,
185 }
186
187 impl AclTreeNode {
188 /// Creates a new, empty AclTreeNode.
189 pub fn new() -> Self {
190 Self {
191 users: HashMap::new(),
192 groups: HashMap::new(),
193 children: BTreeMap::new(),
194 }
195 }
196
197 /// Returns applicable [Role] and their propagation status for a given
198 /// [Authid](crate::api2::types::Authid).
199 ///
200 /// If the `Authid` is a [User](crate::config::user::User) that has no specific `Roles` configured on this node,
201 /// applicable `Group` roles will be returned instead.
202 ///
203 /// If `leaf` is `false`, only those roles where the propagate flag in the ACL is set to `true`
204 /// are returned. Otherwise, all roles will be returned.
205 pub fn extract_roles(&self, auth_id: &Authid, leaf: bool) -> HashMap<String, bool> {
206 let user_roles = self.extract_user_roles(auth_id, leaf);
207 if !user_roles.is_empty() || auth_id.is_token() {
208 // user privs always override group privs
209 return user_roles;
210 };
211
212 self.extract_group_roles(auth_id.user(), leaf)
213 }
214
215 fn extract_user_roles(&self, auth_id: &Authid, leaf: bool) -> HashMap<String, bool> {
216 let mut map = HashMap::new();
217
218 let roles = match self.users.get(auth_id) {
219 Some(m) => m,
220 None => return map,
221 };
222
223 for (role, propagate) in roles {
224 if *propagate || leaf {
225 if role == ROLE_NAME_NO_ACCESS {
226 // return a map with a single role 'NoAccess'
227 let mut map = HashMap::new();
228 map.insert(role.to_string(), false);
229 return map;
230 }
231 map.insert(role.to_string(), *propagate);
232 }
233 }
234
235 map
236 }
237
238 fn extract_group_roles(&self, _user: &Userid, leaf: bool) -> HashMap<String, bool> {
239 let mut map = HashMap::new();
240
241 #[allow(clippy::for_kv_map)]
242 for (_group, roles) in &self.groups {
243 let is_member = false; // fixme: check if user is member of the group
244 if !is_member {
245 continue;
246 }
247
248 for (role, propagate) in roles {
249 if *propagate || leaf {
250 if role == ROLE_NAME_NO_ACCESS {
251 // return a map with a single role 'NoAccess'
252 let mut map = HashMap::new();
253 map.insert(role.to_string(), false);
254 return map;
255 }
256 map.insert(role.to_string(), *propagate);
257 }
258 }
259 }
260
261 map
262 }
263
264 fn delete_group_role(&mut self, group: &str, role: &str) {
265 let roles = match self.groups.get_mut(group) {
266 Some(r) => r,
267 None => return,
268 };
269 roles.remove(role);
270 }
271
272 fn delete_user_role(&mut self, auth_id: &Authid, role: &str) {
273 let roles = match self.users.get_mut(auth_id) {
274 Some(r) => r,
275 None => return,
276 };
277 roles.remove(role);
278 }
279
280 fn insert_group_role(&mut self, group: String, role: String, propagate: bool) {
281 let map = self.groups.entry(group).or_default();
282 if role == ROLE_NAME_NO_ACCESS {
283 map.clear();
284 map.insert(role, propagate);
285 } else {
286 map.remove(ROLE_NAME_NO_ACCESS);
287 map.insert(role, propagate);
288 }
289 }
290
291 fn insert_user_role(&mut self, auth_id: Authid, role: String, propagate: bool) {
292 let map = self.users.entry(auth_id).or_default();
293 if role == ROLE_NAME_NO_ACCESS {
294 map.clear();
295 map.insert(role, propagate);
296 } else {
297 map.remove(ROLE_NAME_NO_ACCESS);
298 map.insert(role, propagate);
299 }
300 }
301 }
302
303 impl AclTree {
304 /// Create a new, empty ACL tree with a single, empty root [node](AclTreeNode)
305 pub fn new() -> Self {
306 Self {
307 root: AclTreeNode::new(),
308 }
309 }
310
311 /// Iterates over the tree looking for a node matching `path`.
312 pub fn find_node(&mut self, path: &str) -> Option<&mut AclTreeNode> {
313 let path = split_acl_path(path);
314 self.get_node(&path)
315 }
316
317 fn get_node(&mut self, path: &[&str]) -> Option<&mut AclTreeNode> {
318 let mut node = &mut self.root;
319 for comp in path {
320 node = match node.children.get_mut(*comp) {
321 Some(n) => n,
322 None => return None,
323 };
324 }
325 Some(node)
326 }
327
328 fn get_or_insert_node(&mut self, path: &[&str]) -> &mut AclTreeNode {
329 let mut node = &mut self.root;
330 for comp in path {
331 node = node
332 .children
333 .entry(String::from(*comp))
334 .or_default();
335 }
336 node
337 }
338
339 /// Deletes the specified `role` from the `group`'s ACL on `path`.
340 ///
341 /// Never fails, even if the `path` has no ACLs configured, or the `group`/`role` combination
342 /// does not exist on `path`.
343 pub fn delete_group_role(&mut self, path: &str, group: &str, role: &str) {
344 let path = split_acl_path(path);
345 let node = match self.get_node(&path) {
346 Some(n) => n,
347 None => return,
348 };
349 node.delete_group_role(group, role);
350 }
351
352 /// Deletes the specified `role` from the `user`'s ACL on `path`.
353 ///
354 /// Never fails, even if the `path` has no ACLs configured, or the `user`/`role` combination
355 /// does not exist on `path`.
356 pub fn delete_user_role(&mut self, path: &str, auth_id: &Authid, role: &str) {
357 let path = split_acl_path(path);
358 let node = match self.get_node(&path) {
359 Some(n) => n,
360 None => return,
361 };
362 node.delete_user_role(auth_id, role);
363 }
364
365 /// Inserts the specified `role` into the `group` ACL on `path`.
366 ///
367 /// The [AclTreeNode] representing `path` will be created and inserted into the tree if
368 /// necessary.
369 pub fn insert_group_role(&mut self, path: &str, group: &str, role: &str, propagate: bool) {
370 let path = split_acl_path(path);
371 let node = self.get_or_insert_node(&path);
372 node.insert_group_role(group.to_string(), role.to_string(), propagate);
373 }
374
375 /// Inserts the specified `role` into the `user` ACL on `path`.
376 ///
377 /// The [AclTreeNode] representing `path` will be created and inserted into the tree if
378 /// necessary.
379 pub fn insert_user_role(&mut self, path: &str, auth_id: &Authid, role: &str, propagate: bool) {
380 let path = split_acl_path(path);
381 let node = self.get_or_insert_node(&path);
382 node.insert_user_role(auth_id.to_owned(), role.to_string(), propagate);
383 }
384
385 fn write_node_config(node: &AclTreeNode, path: &str, w: &mut dyn Write) -> Result<(), Error> {
386 let mut role_ug_map0 = HashMap::new();
387 let mut role_ug_map1 = HashMap::new();
388
389 for (auth_id, roles) in &node.users {
390 // no need to save, because root is always 'Administrator'
391 if !auth_id.is_token() && auth_id.user() == "root@pam" {
392 continue;
393 }
394 for (role, propagate) in roles {
395 let role = role.as_str();
396 let auth_id = auth_id.to_string();
397 if *propagate {
398 role_ug_map1
399 .entry(role)
400 .or_insert_with(BTreeSet::new)
401 .insert(auth_id);
402 } else {
403 role_ug_map0
404 .entry(role)
405 .or_insert_with(BTreeSet::new)
406 .insert(auth_id);
407 }
408 }
409 }
410
411 for (group, roles) in &node.groups {
412 for (role, propagate) in roles {
413 let group = format!("@{}", group);
414 if *propagate {
415 role_ug_map1
416 .entry(role)
417 .or_insert_with(BTreeSet::new)
418 .insert(group);
419 } else {
420 role_ug_map0
421 .entry(role)
422 .or_insert_with(BTreeSet::new)
423 .insert(group);
424 }
425 }
426 }
427
428 fn group_by_property_list(
429 item_property_map: &HashMap<&str, BTreeSet<String>>,
430 ) -> BTreeMap<String, BTreeSet<String>> {
431 let mut result_map = BTreeMap::new();
432 for (item, property_map) in item_property_map {
433 let item_list = property_map.iter().fold(String::new(), |mut acc, v| {
434 if !acc.is_empty() {
435 acc.push(',');
436 }
437 acc.push_str(v);
438 acc
439 });
440 result_map
441 .entry(item_list)
442 .or_insert_with(BTreeSet::new)
443 .insert(item.to_string());
444 }
445 result_map
446 }
447
448 let uglist_role_map0 = group_by_property_list(&role_ug_map0);
449 let uglist_role_map1 = group_by_property_list(&role_ug_map1);
450
451 fn role_list(roles: &BTreeSet<String>) -> String {
452 if roles.contains(ROLE_NAME_NO_ACCESS) {
453 return String::from(ROLE_NAME_NO_ACCESS);
454 }
455 roles.iter().fold(String::new(), |mut acc, v| {
456 if !acc.is_empty() {
457 acc.push(',');
458 }
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!(
467 w,
468 "acl:0:{}:{}:{}",
469 if path.is_empty() { "/" } else { path },
470 uglist,
471 role_list
472 )?;
473 }
474
475 for (uglist, roles) in &uglist_role_map1 {
476 let role_list = role_list(roles);
477 writeln!(
478 w,
479 "acl:1:{}:{}:{}",
480 if path.is_empty() { "/" } else { path },
481 uglist,
482 role_list
483 )?;
484 }
485
486 for (name, child) in node.children.iter() {
487 let child_path = format!("{}/{}", path, name);
488 Self::write_node_config(child, &child_path, w)?;
489 }
490
491 Ok(())
492 }
493
494 fn write_config(&self, w: &mut dyn Write) -> Result<(), Error> {
495 Self::write_node_config(&self.root, "", w)
496 }
497
498 fn parse_acl_line(&mut self, line: &str) -> Result<(), Error> {
499 let items: Vec<&str> = line.split(':').collect();
500
501 if items.len() != 5 {
502 bail!("wrong number of items.");
503 }
504
505 if items[0] != "acl" {
506 bail!("line does not start with 'acl'.");
507 }
508
509 let propagate = if items[1] == "0" {
510 false
511 } else if items[1] == "1" {
512 true
513 } else {
514 bail!("expected '0' or '1' for propagate flag.");
515 };
516
517 let path_str = items[2];
518 let path = split_acl_path(path_str);
519 let node = self.get_or_insert_node(&path);
520
521 let uglist: Vec<&str> = items[3].split(',').map(|v| v.trim()).collect();
522
523 let rolelist: Vec<&str> = items[4].split(',').map(|v| v.trim()).collect();
524
525 for user_or_group in &uglist {
526 for role in &rolelist {
527 if !ROLE_NAMES.contains_key(role) {
528 bail!("unknown role '{}'", role);
529 }
530 if let Some(group) = user_or_group.strip_prefix('@') {
531 node.insert_group_role(group.to_string(), role.to_string(), propagate);
532 } else {
533 node.insert_user_role(user_or_group.parse()?, role.to_string(), propagate);
534 }
535 }
536 }
537
538 Ok(())
539 }
540
541 fn load(filename: &Path) -> Result<(Self, [u8; 32]), Error> {
542 let mut tree = Self::new();
543
544 let raw = match std::fs::read_to_string(filename) {
545 Ok(v) => v,
546 Err(err) => {
547 if err.kind() == std::io::ErrorKind::NotFound {
548 String::new()
549 } else {
550 bail!("unable to read acl config {:?} - {}", filename, err);
551 }
552 }
553 };
554
555 let digest = openssl::sha::sha256(raw.as_bytes());
556
557 for (linenr, line) in raw.lines().enumerate() {
558 let line = line.trim();
559 if line.is_empty() {
560 continue;
561 }
562 if let Err(err) = tree.parse_acl_line(line) {
563 bail!(
564 "unable to parse acl config {:?}, line {} - {}",
565 filename,
566 linenr + 1,
567 err
568 );
569 }
570 }
571
572 Ok((tree, digest))
573 }
574
575 /// This is used for testing
576 pub fn from_raw(raw: &str) -> Result<Self, Error> {
577 let mut tree = Self::new();
578 for (linenr, line) in raw.lines().enumerate() {
579 let line = line.trim();
580 if line.is_empty() {
581 continue;
582 }
583 if let Err(err) = tree.parse_acl_line(line) {
584 bail!(
585 "unable to parse acl config data, line {} - {}",
586 linenr + 1,
587 err
588 );
589 }
590 }
591 Ok(tree)
592 }
593
594 /// Returns a map of role name and propagation status for a given `auth_id` and `path`.
595 ///
596 /// This will collect role mappings according to the following algorithm:
597 /// - iterate over all intermediate nodes along `path` and collect roles with `propagate` set
598 /// - get all (propagating and non-propagating) roles for last component of path
599 /// - more specific role maps replace less specific role maps
600 /// -- user/token is more specific than group at each level
601 /// -- roles lower in the tree are more specific than those higher up along the path
602 pub fn roles(&self, auth_id: &Authid, path: &[&str]) -> HashMap<String, bool> {
603 let mut node = &self.root;
604 let mut role_map = node.extract_roles(auth_id, path.is_empty());
605
606 for (pos, comp) in path.iter().enumerate() {
607 let last_comp = (pos + 1) == path.len();
608 node = match node.children.get(*comp) {
609 Some(n) => n,
610 None => return role_map, // path not found
611 };
612
613 let new_map = node.extract_roles(auth_id, last_comp);
614 if !new_map.is_empty() {
615 // overwrite previous maptings
616 role_map = new_map;
617 }
618 }
619
620 role_map
621 }
622 }
623
624 /// Filename where [AclTree] is stored.
625 pub const ACL_CFG_FILENAME: &str = "/etc/proxmox-backup/acl.cfg";
626 /// Path used to lock the [AclTree] when modifying.
627 pub const ACL_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.acl.lck";
628
629 /// Get exclusive lock
630 pub fn lock_config() -> Result<BackupLockGuard, Error> {
631 open_backup_lockfile(ACL_CFG_LOCKFILE, None, true)
632 }
633
634 /// Reads the [AclTree] from the [default path](ACL_CFG_FILENAME).
635 pub fn config() -> Result<(AclTree, [u8; 32]), Error> {
636 let path = PathBuf::from(ACL_CFG_FILENAME);
637 AclTree::load(&path)
638 }
639
640 /// Returns a cached [AclTree] or fresh copy read directly from the [default path](ACL_CFG_FILENAME)
641 ///
642 /// Since the AclTree is used for every API request's permission check, this caching mechanism
643 /// allows to skip reading and parsing the file again if it is unchanged.
644 pub fn cached_config() -> Result<Arc<AclTree>, Error> {
645 struct ConfigCache {
646 data: Option<Arc<AclTree>>,
647 last_mtime: i64,
648 last_mtime_nsec: i64,
649 }
650
651 lazy_static! {
652 static ref CACHED_CONFIG: RwLock<ConfigCache> = RwLock::new(ConfigCache {
653 data: None,
654 last_mtime: 0,
655 last_mtime_nsec: 0
656 });
657 }
658
659 let stat = match nix::sys::stat::stat(ACL_CFG_FILENAME) {
660 Ok(stat) => Some(stat),
661 Err(nix::Error::Sys(nix::errno::Errno::ENOENT)) => None,
662 Err(err) => bail!("unable to stat '{}' - {}", ACL_CFG_FILENAME, err),
663 };
664
665 {
666 // limit scope
667 let cache = CACHED_CONFIG.read().unwrap();
668 if let Some(ref config) = cache.data {
669 if let Some(stat) = stat {
670 if stat.st_mtime == cache.last_mtime && stat.st_mtime_nsec == cache.last_mtime_nsec
671 {
672 return Ok(config.clone());
673 }
674 } else if cache.last_mtime == 0 && cache.last_mtime_nsec == 0 {
675 return Ok(config.clone());
676 }
677 }
678 }
679
680 let (config, _digest) = config()?;
681 let config = Arc::new(config);
682
683 let mut cache = CACHED_CONFIG.write().unwrap();
684 if let Some(stat) = stat {
685 cache.last_mtime = stat.st_mtime;
686 cache.last_mtime_nsec = stat.st_mtime_nsec;
687 }
688 cache.data = Some(config.clone());
689
690 Ok(config)
691 }
692
693 /// Saves an [AclTree] to the [default path](ACL_CFG_FILENAME), ensuring proper ownership and
694 /// file permissions.
695 pub fn save_config(acl: &AclTree) -> Result<(), Error> {
696 let mut raw: Vec<u8> = Vec::new();
697
698 acl.write_config(&mut raw)?;
699
700 replace_backup_config(ACL_CFG_FILENAME, &raw)
701 }
702
703 #[cfg(test)]
704 mod test {
705 use super::AclTree;
706 use anyhow::Error;
707
708 use pbs_api_types::Authid;
709
710 fn check_roles(tree: &AclTree, auth_id: &Authid, path: &str, expected_roles: &str) {
711 let path_vec = super::split_acl_path(path);
712 let mut roles = tree
713 .roles(auth_id, &path_vec)
714 .iter()
715 .map(|(v, _)| v.clone())
716 .collect::<Vec<String>>();
717 roles.sort();
718 let roles = roles.join(",");
719
720 assert_eq!(
721 roles, expected_roles,
722 "\nat check_roles for '{}' on '{}'",
723 auth_id, path
724 );
725 }
726
727 #[test]
728 fn test_acl_line_compression() {
729 let tree = AclTree::from_raw(
730 "\
731 acl:0:/store/store2:user1@pbs:Admin\n\
732 acl:0:/store/store2:user2@pbs:Admin\n\
733 acl:0:/store/store2:user1@pbs:DatastoreBackup\n\
734 acl:0:/store/store2:user2@pbs:DatastoreBackup\n\
735 ",
736 )
737 .expect("failed to parse acl tree");
738
739 let mut raw: Vec<u8> = Vec::new();
740 tree.write_config(&mut raw)
741 .expect("failed to write acl tree");
742 let raw = std::str::from_utf8(&raw).expect("acl tree is not valid utf8");
743
744 assert_eq!(
745 raw,
746 "acl:0:/store/store2:user1@pbs,user2@pbs:Admin,DatastoreBackup\n"
747 );
748 }
749
750 #[test]
751 fn test_roles_1() -> Result<(), Error> {
752 let tree = AclTree::from_raw(
753 r###"
754 acl:1:/storage:user1@pbs:Admin
755 acl:1:/storage/store1:user1@pbs:DatastoreBackup
756 acl:1:/storage/store2:user2@pbs:DatastoreBackup
757 "###,
758 )?;
759 let user1: Authid = "user1@pbs".parse()?;
760 check_roles(&tree, &user1, "/", "");
761 check_roles(&tree, &user1, "/storage", "Admin");
762 check_roles(&tree, &user1, "/storage/store1", "DatastoreBackup");
763 check_roles(&tree, &user1, "/storage/store2", "Admin");
764
765 let user2: Authid = "user2@pbs".parse()?;
766 check_roles(&tree, &user2, "/", "");
767 check_roles(&tree, &user2, "/storage", "");
768 check_roles(&tree, &user2, "/storage/store1", "");
769 check_roles(&tree, &user2, "/storage/store2", "DatastoreBackup");
770
771 Ok(())
772 }
773
774 #[test]
775 fn test_role_no_access() -> Result<(), Error> {
776 let tree = AclTree::from_raw(
777 r###"
778 acl:1:/:user1@pbs:Admin
779 acl:1:/storage:user1@pbs:NoAccess
780 acl:1:/storage/store1:user1@pbs:DatastoreBackup
781 "###,
782 )?;
783 let user1: Authid = "user1@pbs".parse()?;
784 check_roles(&tree, &user1, "/", "Admin");
785 check_roles(&tree, &user1, "/storage", "NoAccess");
786 check_roles(&tree, &user1, "/storage/store1", "DatastoreBackup");
787 check_roles(&tree, &user1, "/storage/store2", "NoAccess");
788 check_roles(&tree, &user1, "/system", "Admin");
789
790 let tree = AclTree::from_raw(
791 r###"
792 acl:1:/:user1@pbs:Admin
793 acl:0:/storage:user1@pbs:NoAccess
794 acl:1:/storage/store1:user1@pbs:DatastoreBackup
795 "###,
796 )?;
797 check_roles(&tree, &user1, "/", "Admin");
798 check_roles(&tree, &user1, "/storage", "NoAccess");
799 check_roles(&tree, &user1, "/storage/store1", "DatastoreBackup");
800 check_roles(&tree, &user1, "/storage/store2", "Admin");
801 check_roles(&tree, &user1, "/system", "Admin");
802
803 Ok(())
804 }
805
806 #[test]
807 fn test_role_add_delete() -> Result<(), Error> {
808 let mut tree = AclTree::new();
809
810 let user1: Authid = "user1@pbs".parse()?;
811
812 tree.insert_user_role("/", &user1, "Admin", true);
813 tree.insert_user_role("/", &user1, "Audit", true);
814
815 check_roles(&tree, &user1, "/", "Admin,Audit");
816
817 tree.insert_user_role("/", &user1, "NoAccess", true);
818 check_roles(&tree, &user1, "/", "NoAccess");
819
820 let mut raw: Vec<u8> = Vec::new();
821 tree.write_config(&mut raw)?;
822 let raw = std::str::from_utf8(&raw)?;
823
824 assert_eq!(raw, "acl:1:/:user1@pbs:NoAccess\n");
825
826 Ok(())
827 }
828
829 #[test]
830 fn test_no_access_overwrite() -> Result<(), Error> {
831 let mut tree = AclTree::new();
832
833 let user1: Authid = "user1@pbs".parse()?;
834
835 tree.insert_user_role("/storage", &user1, "NoAccess", true);
836
837 check_roles(&tree, &user1, "/storage", "NoAccess");
838
839 tree.insert_user_role("/storage", &user1, "Admin", true);
840 tree.insert_user_role("/storage", &user1, "Audit", true);
841
842 check_roles(&tree, &user1, "/storage", "Admin,Audit");
843
844 tree.insert_user_role("/storage", &user1, "NoAccess", true);
845
846 check_roles(&tree, &user1, "/storage", "NoAccess");
847
848 Ok(())
849 }
850 }