2 use std
::collections
::{HashMap, BTreeMap, BTreeSet}
;
3 use std
::path
::{PathBuf, Path}
;
4 use std
::sync
::{Arc, RwLock}
;
7 use anyhow
::{bail, Error}
;
9 use lazy_static
::lazy_static
;
11 use ::serde
::{Deserialize, Serialize}
;
12 use serde
::de
::{value, IntoDeserializer}
;
14 use proxmox
::tools
::{fs::replace_file, fs::CreateOptions}
;
15 use proxmox
::constnamedbitmap
;
16 use proxmox
::api
::{api, schema::*}
;
18 use crate::api2
::types
::{Authid,Userid}
;
20 // define Privilege bitfield
23 /// Contains a list of Privileges
25 PRIV_SYS_AUDIT("Sys.Audit");
26 PRIV_SYS_MODIFY("Sys.Modify");
27 PRIV_SYS_POWER_MANAGEMENT("Sys.PowerManagement");
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 PRIV_DATASTORE_VERIFY("Datastore.Verify");
35 /// Datastore.Backup also requires backup ownership
36 PRIV_DATASTORE_BACKUP("Datastore.Backup");
37 /// Datastore.Prune also requires backup ownership
38 PRIV_DATASTORE_PRUNE("Datastore.Prune");
40 PRIV_PERMISSIONS_MODIFY("Permissions.Modify");
42 PRIV_REMOTE_AUDIT("Remote.Audit");
43 PRIV_REMOTE_MODIFY("Remote.Modify");
44 PRIV_REMOTE_READ("Remote.Read");
45 PRIV_REMOTE_PRUNE("Remote.Prune");
47 PRIV_SYS_CONSOLE("Sys.Console");
52 /// Admin always has all privileges. It can do everything except a few actions
53 /// which are limited to the 'root@pam` superuser
54 pub const ROLE_ADMIN
: u64 = std
::u64::MAX
;
56 /// NoAccess can be used to remove privileges from specific paths
57 pub const ROLE_NO_ACCESS
: u64 = 0;
59 pub const ROLE_AUDIT
: u64 =
63 /// Datastore.Admin can do anything on the datastore.
64 pub const ROLE_DATASTORE_ADMIN
: u64 =
65 PRIV_DATASTORE_AUDIT
|
66 PRIV_DATASTORE_MODIFY
|
68 PRIV_DATASTORE_VERIFY
|
69 PRIV_DATASTORE_BACKUP
|
72 /// Datastore.Reader can read/verify datastore content and do restore
73 pub const ROLE_DATASTORE_READER
: u64 =
74 PRIV_DATASTORE_AUDIT
|
75 PRIV_DATASTORE_VERIFY
|
78 /// Datastore.Backup can do backup and restore, but no prune.
79 pub const ROLE_DATASTORE_BACKUP
: u64 =
80 PRIV_DATASTORE_BACKUP
;
82 /// Datastore.PowerUser can do backup, restore, and prune.
83 pub const ROLE_DATASTORE_POWERUSER
: u64 =
84 PRIV_DATASTORE_PRUNE
|
85 PRIV_DATASTORE_BACKUP
;
87 /// Datastore.Audit can audit the datastore.
88 pub const ROLE_DATASTORE_AUDIT
: u64 =
91 /// Remote.Audit can audit the remote
92 pub const ROLE_REMOTE_AUDIT
: u64 =
95 /// Remote.Admin can do anything on the remote.
96 pub const ROLE_REMOTE_ADMIN
: u64 =
102 /// Remote.SyncOperator can do read and prune on the remote.
103 pub const ROLE_REMOTE_SYNC_OPERATOR
: u64 =
108 pub const ROLE_NAME_NO_ACCESS
: &str ="NoAccess";
112 #[derive(Serialize, Deserialize)]
120 NoAccess
= ROLE_NO_ACCESS
,
121 /// Datastore Administrator
122 DatastoreAdmin
= ROLE_DATASTORE_ADMIN
,
123 /// Datastore Reader (inspect datastore content and do restores)
124 DatastoreReader
= ROLE_DATASTORE_READER
,
125 /// Datastore Backup (backup and restore owned backups)
126 DatastoreBackup
= ROLE_DATASTORE_BACKUP
,
127 /// Datastore PowerUser (backup, restore and prune owned backup)
128 DatastorePowerUser
= ROLE_DATASTORE_POWERUSER
,
129 /// Datastore Auditor
130 DatastoreAudit
= ROLE_DATASTORE_AUDIT
,
132 RemoteAudit
= ROLE_REMOTE_AUDIT
,
133 /// Remote Administrator
134 RemoteAdmin
= ROLE_REMOTE_ADMIN
,
135 /// Syncronisation Opertator
136 RemoteSyncOperator
= ROLE_REMOTE_SYNC_OPERATOR
,
139 impl FromStr
for Role
{
140 type Err
= value
::Error
;
142 fn from_str(s
: &str) -> Result
<Self, Self::Err
> {
143 Self::deserialize(s
.into_deserializer())
148 pub static ref ROLE_NAMES
: HashMap
<&'
static str, (u64, &'
static str)> = {
149 let mut map
= HashMap
::new();
151 let list
= match Role
::API_SCHEMA
{
152 Schema
::String(StringSchema { format: Some(ApiStringFormat::Enum(list)), .. }
) => list
,
156 for entry
in list
.iter() {
157 let privs
: u64 = Role
::from_str(entry
.value
).unwrap() as u64;
158 map
.insert(entry
.value
, (privs
, entry
.description
));
165 pub fn split_acl_path(path
: &str) -> Vec
<&str> {
167 let items
= path
.split('
/'
);
169 let mut components
= vec
![];
172 if name
.is_empty() { continue; }
173 components
.push(name
);
179 pub fn check_acl_path(path
: &str) -> Result
<(), Error
> {
181 let components
= split_acl_path(path
);
183 let components_len
= components
.len();
185 if components_len
== 0 { return Ok(()); }
186 match components
[0] {
188 if components_len
== 1 { return Ok(()); }
189 match components
[1] {
191 if components_len
== 2 { return Ok(()); }
196 "datastore" => { // /datastore/{store}
197 if components_len
<= 2 { return Ok(()); }
199 "remote" => { // /remote/{remote}
/{store}
200 if components_len
<= 3 { return Ok(()); }
203 if components_len
== 1 { return Ok(()); }
204 match components
[1] {
205 "disks" | "log" | "status" | "tasks" | "time" => {
206 if components_len
== 2 { return Ok(()); }
208 "services" => { // /system/services/{service}
209 if components_len
<= 3 { return Ok(()); }
212 if components_len
== 2 { return Ok(()); }
213 match components
[2] {
215 if components_len
== 3 { return Ok(()); }
217 "interfaces" => { // /system/network/interfaces/{iface}
218 if components_len
<= 4 { return Ok(()); }
229 bail
!("invalid acl path '{}'.", path
);
233 pub root
: AclTreeNode
,
236 pub struct AclTreeNode
{
237 pub users
: HashMap
<Authid
, HashMap
<String
, bool
>>,
238 pub groups
: HashMap
<String
, HashMap
<String
, bool
>>,
239 pub children
: BTreeMap
<String
, AclTreeNode
>,
244 pub fn new() -> Self {
246 users
: HashMap
::new(),
247 groups
: HashMap
::new(),
248 children
: BTreeMap
::new(),
252 pub fn extract_roles(&self, auth_id
: &Authid
, all
: bool
) -> HashMap
<String
, bool
> {
253 let user_roles
= self.extract_user_roles(auth_id
, all
);
254 if !user_roles
.is_empty() || auth_id
.is_token() {
255 // user privs always override group privs
259 self.extract_group_roles(auth_id
.user(), all
)
262 pub fn extract_user_roles(&self, auth_id
: &Authid
, all
: bool
) -> HashMap
<String
, bool
> {
264 let mut map
= HashMap
::new();
266 let roles
= match self.users
.get(auth_id
) {
271 for (role
, propagate
) in roles
{
272 if *propagate
|| all
{
273 if role
== ROLE_NAME_NO_ACCESS
{
274 // return a map with a single role 'NoAccess'
275 let mut map
= HashMap
::new();
276 map
.insert(role
.to_string(), false);
279 map
.insert(role
.to_string(), *propagate
);
286 pub fn extract_group_roles(&self, _user
: &Userid
, all
: bool
) -> HashMap
<String
, bool
> {
288 let mut map
= HashMap
::new();
290 for (_group
, roles
) in &self.groups
{
291 let is_member
= false; // fixme: check if user is member of the group
292 if !is_member { continue; }
294 for (role
, propagate
) in roles
{
295 if *propagate
|| all
{
296 if role
== ROLE_NAME_NO_ACCESS
{
297 // return a map with a single role 'NoAccess'
298 let mut map
= HashMap
::new();
299 map
.insert(role
.to_string(), false);
302 map
.insert(role
.to_string(), *propagate
);
310 pub fn delete_group_role(&mut self, group
: &str, role
: &str) {
311 let roles
= match self.groups
.get_mut(group
) {
318 pub fn delete_user_role(&mut self, auth_id
: &Authid
, role
: &str) {
319 let roles
= match self.users
.get_mut(auth_id
) {
326 pub fn insert_group_role(&mut self, group
: String
, role
: String
, propagate
: bool
) {
327 let map
= self.groups
.entry(group
).or_insert_with(|| HashMap
::new());
328 if role
== ROLE_NAME_NO_ACCESS
{
330 map
.insert(role
, propagate
);
332 map
.remove(ROLE_NAME_NO_ACCESS
);
333 map
.insert(role
, propagate
);
337 pub fn insert_user_role(&mut self, auth_id
: Authid
, role
: String
, propagate
: bool
) {
338 let map
= self.users
.entry(auth_id
).or_insert_with(|| HashMap
::new());
339 if role
== ROLE_NAME_NO_ACCESS
{
341 map
.insert(role
, propagate
);
343 map
.remove(ROLE_NAME_NO_ACCESS
);
344 map
.insert(role
, propagate
);
351 pub fn new() -> Self {
353 root
: AclTreeNode
::new(),
357 pub fn find_node(&mut self, path
: &str) -> Option
<&mut AclTreeNode
> {
358 let path
= split_acl_path(path
);
359 return self.get_node(&path
);
362 fn get_node(&mut self, path
: &[&str]) -> Option
<&mut AclTreeNode
> {
363 let mut node
= &mut self.root
;
365 node
= match node
.children
.get_mut(*comp
) {
373 fn get_or_insert_node(&mut self, path
: &[&str]) -> &mut AclTreeNode
{
374 let mut node
= &mut self.root
;
376 node
= node
.children
.entry(String
::from(*comp
))
377 .or_insert_with(|| AclTreeNode
::new());
382 pub fn delete_group_role(&mut self, path
: &str, group
: &str, role
: &str) {
383 let path
= split_acl_path(path
);
384 let node
= match self.get_node(&path
) {
388 node
.delete_group_role(group
, role
);
391 pub fn delete_user_role(&mut self, path
: &str, auth_id
: &Authid
, role
: &str) {
392 let path
= split_acl_path(path
);
393 let node
= match self.get_node(&path
) {
397 node
.delete_user_role(auth_id
, role
);
400 pub fn insert_group_role(&mut self, path
: &str, group
: &str, role
: &str, propagate
: bool
) {
401 let path
= split_acl_path(path
);
402 let node
= self.get_or_insert_node(&path
);
403 node
.insert_group_role(group
.to_string(), role
.to_string(), propagate
);
406 pub fn insert_user_role(&mut self, path
: &str, auth_id
: &Authid
, role
: &str, propagate
: bool
) {
407 let path
= split_acl_path(path
);
408 let node
= self.get_or_insert_node(&path
);
409 node
.insert_user_role(auth_id
.to_owned(), role
.to_string(), propagate
);
412 fn write_node_config(
416 ) -> Result
<(), Error
> {
418 let mut role_ug_map0
= HashMap
::new();
419 let mut role_ug_map1
= HashMap
::new();
421 for (auth_id
, roles
) in &node
.users
{
422 // no need to save, because root is always 'Administrator'
423 if !auth_id
.is_token() && auth_id
.user() == "root@pam" { continue; }
424 for (role
, propagate
) in roles
{
425 let role
= role
.as_str();
426 let auth_id
= auth_id
.to_string();
428 role_ug_map1
.entry(role
).or_insert_with(|| BTreeSet
::new())
431 role_ug_map0
.entry(role
).or_insert_with(|| BTreeSet
::new())
437 for (group
, roles
) in &node
.groups
{
438 for (role
, propagate
) in roles
{
439 let group
= format
!("@{}", group
);
441 role_ug_map1
.entry(role
).or_insert_with(|| BTreeSet
::new())
444 role_ug_map0
.entry(role
).or_insert_with(|| BTreeSet
::new())
450 fn group_by_property_list(
451 item_property_map
: &HashMap
<&str, BTreeSet
<String
>>,
452 ) -> BTreeMap
<String
, BTreeSet
<String
>> {
453 let mut result_map
= BTreeMap
::new();
454 for (item
, property_map
) in item_property_map
{
455 let item_list
= property_map
.iter().fold(String
::new(), |mut acc
, v
| {
456 if !acc
.is_empty() { acc.push(','); }
460 result_map
.entry(item_list
).or_insert_with(|| BTreeSet
::new())
461 .insert(item
.to_string());
466 let uglist_role_map0
= group_by_property_list(&role_ug_map0
);
467 let uglist_role_map1
= group_by_property_list(&role_ug_map1
);
469 fn role_list(roles
: &BTreeSet
<String
>) -> String
{
470 if roles
.contains(ROLE_NAME_NO_ACCESS
) { return String::from(ROLE_NAME_NO_ACCESS); }
471 roles
.iter().fold(String
::new(), |mut acc
, v
| {
472 if !acc
.is_empty() { acc.push(','); }
478 for (uglist
, roles
) in &uglist_role_map0
{
479 let role_list
= role_list(roles
);
480 writeln
!(w
, "acl:0:{}:{}:{}", if path
.is_empty() { "/" }
else { path }
, uglist
, role_list
)?
;
483 for (uglist
, roles
) in &uglist_role_map1
{
484 let role_list
= role_list(roles
);
485 writeln
!(w
, "acl:1:{}:{}:{}", if path
.is_empty() { "/" }
else { path }
, uglist
, role_list
)?
;
488 for (name
, child
) in node
.children
.iter() {
489 let child_path
= format
!("{}/{}", path
, name
);
490 Self::write_node_config(child
, &child_path
, w
)?
;
496 pub fn write_config(&self, w
: &mut dyn Write
) -> Result
<(), Error
> {
497 Self::write_node_config(&self.root
, "", w
)
500 fn parse_acl_line(&mut self, line
: &str) -> Result
<(), Error
> {
502 let items
: Vec
<&str> = line
.split('
:'
).collect();
504 if items
.len() != 5 {
505 bail
!("wrong number of items.");
508 if items
[0] != "acl" {
509 bail
!("line does not start with 'acl'.");
512 let propagate
= if items
[1] == "0" {
514 } else if items
[1] == "1" {
517 bail
!("expected '0' or '1' for propagate flag.");
520 let path_str
= items
[2];
521 let path
= split_acl_path(path_str
);
522 let node
= self.get_or_insert_node(&path
);
524 let uglist
: Vec
<&str> = items
[3].split('
,'
).map(|v
| v
.trim()).collect();
526 let rolelist
: Vec
<&str> = items
[4].split('
,'
).map(|v
| v
.trim()).collect();
528 for user_or_group
in &uglist
{
529 for role
in &rolelist
{
530 if !ROLE_NAMES
.contains_key(role
) {
531 bail
!("unknown role '{}'", role
);
533 if user_or_group
.starts_with('@'
) {
534 let group
= &user_or_group
[1..];
535 node
.insert_group_role(group
.to_string(), role
.to_string(), propagate
);
537 node
.insert_user_role(user_or_group
.parse()?
, role
.to_string(), propagate
);
545 pub fn load(filename
: &Path
) -> Result
<(Self, [u8;32]), Error
> {
546 let mut tree
= Self::new();
548 let raw
= match std
::fs
::read_to_string(filename
) {
551 if err
.kind() == std
::io
::ErrorKind
::NotFound
{
554 bail
!("unable to read acl config {:?} - {}", filename
, err
);
559 let digest
= openssl
::sha
::sha256(raw
.as_bytes());
561 for (linenr
, line
) in raw
.lines().enumerate() {
562 let line
= line
.trim();
563 if line
.is_empty() { continue; }
564 if let Err(err
) = tree
.parse_acl_line(line
) {
565 bail
!("unable to parse acl config {:?}, line {} - {}",
566 filename
, linenr
+1, err
);
573 pub fn from_raw(raw
: &str) -> Result
<Self, Error
> {
574 let mut tree
= Self::new();
575 for (linenr
, line
) in raw
.lines().enumerate() {
576 let line
= line
.trim();
577 if line
.is_empty() { continue; }
578 if let Err(err
) = tree
.parse_acl_line(line
) {
579 bail
!("unable to parse acl config data, line {} - {}", linenr
+1, err
);
585 pub fn roles(&self, auth_id
: &Authid
, path
: &[&str]) -> HashMap
<String
, bool
> {
587 let mut node
= &self.root
;
588 let mut role_map
= node
.extract_roles(auth_id
, path
.is_empty());
590 for (pos
, comp
) in path
.iter().enumerate() {
591 let last_comp
= (pos
+ 1) == path
.len();
592 node
= match node
.children
.get(*comp
) {
594 None
=> return role_map
, // path not found
597 let new_map
= node
.extract_roles(auth_id
, last_comp
);
598 if !new_map
.is_empty() {
599 // overwrite previous maptings
608 pub const ACL_CFG_FILENAME
: &str = "/etc/proxmox-backup/acl.cfg";
609 pub const ACL_CFG_LOCKFILE
: &str = "/etc/proxmox-backup/.acl.lck";
611 pub fn config() -> Result
<(AclTree
, [u8; 32]), Error
> {
612 let path
= PathBuf
::from(ACL_CFG_FILENAME
);
616 pub fn cached_config() -> Result
<Arc
<AclTree
>, Error
> {
619 data
: Option
<Arc
<AclTree
>>,
621 last_mtime_nsec
: i64,
625 static ref CACHED_CONFIG
: RwLock
<ConfigCache
> = RwLock
::new(
626 ConfigCache { data: None, last_mtime: 0, last_mtime_nsec: 0 }
);
629 let stat
= match nix
::sys
::stat
::stat(ACL_CFG_FILENAME
) {
630 Ok(stat
) => Some(stat
),
631 Err(nix
::Error
::Sys(nix
::errno
::Errno
::ENOENT
)) => None
,
632 Err(err
) => bail
!("unable to stat '{}' - {}", ACL_CFG_FILENAME
, err
),
636 let cache
= CACHED_CONFIG
.read().unwrap();
637 if let Some(ref config
) = cache
.data
{
638 if let Some(stat
) = stat
{
639 if stat
.st_mtime
== cache
.last_mtime
&& stat
.st_mtime_nsec
== cache
.last_mtime_nsec
{
640 return Ok(config
.clone());
642 } else if cache
.last_mtime
== 0 && cache
.last_mtime_nsec
== 0 {
643 return Ok(config
.clone());
648 let (config
, _digest
) = config()?
;
649 let config
= Arc
::new(config
);
651 let mut cache
= CACHED_CONFIG
.write().unwrap();
652 if let Some(stat
) = stat
{
653 cache
.last_mtime
= stat
.st_mtime
;
654 cache
.last_mtime_nsec
= stat
.st_mtime_nsec
;
656 cache
.data
= Some(config
.clone());
661 pub fn save_config(acl
: &AclTree
) -> Result
<(), Error
> {
662 let mut raw
: Vec
<u8> = Vec
::new();
664 acl
.write_config(&mut raw
)?
;
666 let backup_user
= crate::backup
::backup_user()?
;
667 let mode
= nix
::sys
::stat
::Mode
::from_bits_truncate(0o0640);
668 // set the correct owner/group/permissions while saving file
669 // owner(rw) = root, group(r)= backup
670 let options
= CreateOptions
::new()
672 .owner(nix
::unistd
::ROOT
)
673 .group(backup_user
.gid
);
675 replace_file(ACL_CFG_FILENAME
, &raw
, options
)?
;
685 use crate::api2
::types
::Authid
;
691 expected_roles
: &str,
694 let path_vec
= super::split_acl_path(path
);
695 let mut roles
= tree
.roles(auth_id
, &path_vec
)
696 .iter().map(|(v
, _
)| v
.clone()).collect
::<Vec
<String
>>();
698 let roles
= roles
.join(",");
700 assert_eq
!(roles
, expected_roles
, "\nat check_roles for '{}' on '{}'", auth_id
, path
);
704 fn test_acl_line_compression() {
706 let tree
= AclTree
::from_raw(
708 acl:0:/store/store2:user1@pbs:Admin\n\
709 acl:0:/store/store2:user2@pbs:Admin\n\
710 acl:0:/store/store2:user1@pbs:DatastoreBackup\n\
711 acl:0:/store/store2:user2@pbs:DatastoreBackup\n\
714 .expect("failed to parse acl tree");
716 let mut raw
: Vec
<u8> = Vec
::new();
717 tree
.write_config(&mut raw
).expect("failed to write acl tree");
718 let raw
= std
::str::from_utf8(&raw
).expect("acl tree is not valid utf8");
720 assert_eq
!(raw
, "acl:0:/store/store2:user1@pbs,user2@pbs:Admin,DatastoreBackup\n");
724 fn test_roles_1() -> Result
<(), Error
> {
726 let tree
= AclTree
::from_raw(r
###"
727 acl:1:/storage:user1@pbs:Admin
728 acl:1:/storage/store1:user1@pbs:DatastoreBackup
729 acl:1:/storage/store2:user2@pbs:DatastoreBackup
731 let user1
: Authid
= "user1@pbs".parse()?
;
732 check_roles(&tree
, &user1
, "/", "");
733 check_roles(&tree
, &user1
, "/storage", "Admin");
734 check_roles(&tree
, &user1
, "/storage/store1", "DatastoreBackup");
735 check_roles(&tree
, &user1
, "/storage/store2", "Admin");
737 let user2
: Authid
= "user2@pbs".parse()?
;
738 check_roles(&tree
, &user2
, "/", "");
739 check_roles(&tree
, &user2
, "/storage", "");
740 check_roles(&tree
, &user2
, "/storage/store1", "");
741 check_roles(&tree
, &user2
, "/storage/store2", "DatastoreBackup");
747 fn test_role_no_access() -> Result
<(), Error
> {
749 let tree
= AclTree
::from_raw(r
###"
750 acl:1:/:user1@pbs:Admin
751 acl:1:/storage:user1@pbs:NoAccess
752 acl:1:/storage/store1:user1@pbs:DatastoreBackup
754 let user1
: Authid
= "user1@pbs".parse()?
;
755 check_roles(&tree
, &user1
, "/", "Admin");
756 check_roles(&tree
, &user1
, "/storage", "NoAccess");
757 check_roles(&tree
, &user1
, "/storage/store1", "DatastoreBackup");
758 check_roles(&tree
, &user1
, "/storage/store2", "NoAccess");
759 check_roles(&tree
, &user1
, "/system", "Admin");
761 let tree
= AclTree
::from_raw(r
###"
762 acl:1:/:user1@pbs:Admin
763 acl:0:/storage:user1@pbs:NoAccess
764 acl:1:/storage/store1:user1@pbs:DatastoreBackup
766 check_roles(&tree
, &user1
, "/", "Admin");
767 check_roles(&tree
, &user1
, "/storage", "NoAccess");
768 check_roles(&tree
, &user1
, "/storage/store1", "DatastoreBackup");
769 check_roles(&tree
, &user1
, "/storage/store2", "Admin");
770 check_roles(&tree
, &user1
, "/system", "Admin");
776 fn test_role_add_delete() -> Result
<(), Error
> {
778 let mut tree
= AclTree
::new();
780 let user1
: Authid
= "user1@pbs".parse()?
;
782 tree
.insert_user_role("/", &user1
, "Admin", true);
783 tree
.insert_user_role("/", &user1
, "Audit", true);
785 check_roles(&tree
, &user1
, "/", "Admin,Audit");
787 tree
.insert_user_role("/", &user1
, "NoAccess", true);
788 check_roles(&tree
, &user1
, "/", "NoAccess");
790 let mut raw
: Vec
<u8> = Vec
::new();
791 tree
.write_config(&mut raw
)?
;
792 let raw
= std
::str::from_utf8(&raw
)?
;
794 assert_eq
!(raw
, "acl:1:/:user1@pbs:NoAccess\n");
800 fn test_no_access_overwrite() -> Result
<(), Error
> {
802 let mut tree
= AclTree
::new();
804 let user1
: Authid
= "user1@pbs".parse()?
;
806 tree
.insert_user_role("/storage", &user1
, "NoAccess", true);
808 check_roles(&tree
, &user1
, "/storage", "NoAccess");
810 tree
.insert_user_role("/storage", &user1
, "Admin", true);
811 tree
.insert_user_role("/storage", &user1
, "Audit", true);
813 check_roles(&tree
, &user1
, "/storage", "Admin,Audit");
815 tree
.insert_user_role("/storage", &user1
, "NoAccess", true);
817 check_roles(&tree
, &user1
, "/storage", "NoAccess");