1 use std
::collections
::{BTreeMap, BTreeSet, HashMap}
;
3 use std
::path
::{Path, PathBuf}
;
5 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
::api
::{api, schema::*}
;
15 use proxmox
::constnamedbitmap
;
16 use proxmox
::tools
::{fs::replace_file, fs::CreateOptions}
;
18 use crate::api2
::types
::{Authid, Userid}
;
20 // define Privilege bitfield
23 /// Contains a list of privilege name to privilege value mappings.
25 /// The names are used when displaying/persisting privileges anywhere, the values are used to
26 /// allow easy matching of privileges as bitflags.
28 /// Sys.Audit allows knowing about the system and its status
29 PRIV_SYS_AUDIT("Sys.Audit");
30 /// Sys.Modify allows modifying system-level configuration
31 PRIV_SYS_MODIFY("Sys.Modify");
32 /// Sys.Modify allows to poweroff/reboot/.. the system
33 PRIV_SYS_POWER_MANAGEMENT("Sys.PowerManagement");
35 /// Datastore.Audit allows knowing about a datastore,
36 /// including reading the configuration entry and listing its contents
37 PRIV_DATASTORE_AUDIT("Datastore.Audit");
38 /// Datastore.Allocate allows creating or deleting datastores
39 PRIV_DATASTORE_ALLOCATE("Datastore.Allocate");
40 /// Datastore.Modify allows modifying a datastore and its contents
41 PRIV_DATASTORE_MODIFY("Datastore.Modify");
42 /// Datastore.Read allows reading arbitrary backup contents
43 PRIV_DATASTORE_READ("Datastore.Read");
44 /// Allows verifying a datastore
45 PRIV_DATASTORE_VERIFY("Datastore.Verify");
47 /// Datastore.Backup allows Datastore.Read|Verify and creating new snapshots,
48 /// but also requires backup ownership
49 PRIV_DATASTORE_BACKUP("Datastore.Backup");
50 /// Datastore.Prune allows deleting snapshots,
51 /// but also requires backup ownership
52 PRIV_DATASTORE_PRUNE("Datastore.Prune");
54 /// Permissions.Modify allows modifying ACLs
55 PRIV_PERMISSIONS_MODIFY("Permissions.Modify");
57 /// Remote.Audit allows reading remote.cfg and sync.cfg entries
58 PRIV_REMOTE_AUDIT("Remote.Audit");
59 /// Remote.Modify allows modifying remote.cfg
60 PRIV_REMOTE_MODIFY("Remote.Modify");
61 /// Remote.Read allows reading data from a configured `Remote`
62 PRIV_REMOTE_READ("Remote.Read");
64 /// Sys.Console allows access to the system's console
65 PRIV_SYS_CONSOLE("Sys.Console");
69 /// Admin always has all privileges. It can do everything except a few actions
70 /// which are limited to the 'root@pam` superuser
71 pub const ROLE_ADMIN
: u64 = std
::u64::MAX
;
73 /// NoAccess can be used to remove privileges from specific (sub-)paths
74 pub const ROLE_NO_ACCESS
: u64 = 0;
77 /// Audit can view configuration and status information, but not modify it.
78 pub const ROLE_AUDIT
: u64 = 0
80 | PRIV_DATASTORE_AUDIT
;
83 /// Datastore.Admin can do anything on the datastore.
84 pub const ROLE_DATASTORE_ADMIN
: u64 = 0
85 | PRIV_DATASTORE_AUDIT
86 | PRIV_DATASTORE_MODIFY
88 | PRIV_DATASTORE_VERIFY
89 | PRIV_DATASTORE_BACKUP
90 | PRIV_DATASTORE_PRUNE
;
93 /// Datastore.Reader can read/verify datastore content and do restore
94 pub const ROLE_DATASTORE_READER
: u64 = 0
95 | PRIV_DATASTORE_AUDIT
96 | PRIV_DATASTORE_VERIFY
97 | PRIV_DATASTORE_READ
;
100 /// Datastore.Backup can do backup and restore, but no prune.
101 pub const ROLE_DATASTORE_BACKUP
: u64 = 0
102 | PRIV_DATASTORE_BACKUP
;
105 /// Datastore.PowerUser can do backup, restore, and prune.
106 pub const ROLE_DATASTORE_POWERUSER
: u64 = 0
107 | PRIV_DATASTORE_PRUNE
108 | PRIV_DATASTORE_BACKUP
;
111 /// Datastore.Audit can audit the datastore.
112 pub const ROLE_DATASTORE_AUDIT
: u64 = 0
113 | PRIV_DATASTORE_AUDIT
;
116 /// Remote.Audit can audit the remote
117 pub const ROLE_REMOTE_AUDIT
: u64 = 0
121 /// Remote.Admin can do anything on the remote.
122 pub const ROLE_REMOTE_ADMIN
: u64 = 0
128 /// Remote.SyncOperator can do read and prune on the remote.
129 pub const ROLE_REMOTE_SYNC_OPERATOR
: u64 = 0
133 /// NoAccess can be used to remove privileges from specific (sub-)paths
134 pub const ROLE_NAME_NO_ACCESS
: &str = "NoAccess";
138 #[derive(Serialize, Deserialize)]
139 /// Enum representing roles via their [PRIVILEGES] combination.
141 /// Since privileges are implemented as bitflags, each unique combination of privileges maps to a
142 /// single, unique `u64` value that is used in this enum definition.
149 NoAccess
= ROLE_NO_ACCESS
,
150 /// Datastore Administrator
151 DatastoreAdmin
= ROLE_DATASTORE_ADMIN
,
152 /// Datastore Reader (inspect datastore content and do restores)
153 DatastoreReader
= ROLE_DATASTORE_READER
,
154 /// Datastore Backup (backup and restore owned backups)
155 DatastoreBackup
= ROLE_DATASTORE_BACKUP
,
156 /// Datastore PowerUser (backup, restore and prune owned backup)
157 DatastorePowerUser
= ROLE_DATASTORE_POWERUSER
,
158 /// Datastore Auditor
159 DatastoreAudit
= ROLE_DATASTORE_AUDIT
,
161 RemoteAudit
= ROLE_REMOTE_AUDIT
,
162 /// Remote Administrator
163 RemoteAdmin
= ROLE_REMOTE_ADMIN
,
164 /// Syncronisation Opertator
165 RemoteSyncOperator
= ROLE_REMOTE_SYNC_OPERATOR
,
168 impl FromStr
for Role
{
169 type Err
= value
::Error
;
171 fn from_str(s
: &str) -> Result
<Self, Self::Err
> {
172 Self::deserialize(s
.into_deserializer())
177 /// Map of pre-defined [Roles](Role) to their associated [privileges](PRIVILEGES) combination and
179 pub static ref ROLE_NAMES
: HashMap
<&'
static str, (u64, &'
static str)> = {
180 let mut map
= HashMap
::new();
182 let list
= match Role
::API_SCHEMA
{
183 Schema
::String(StringSchema { format: Some(ApiStringFormat::Enum(list)), .. }
) => list
,
187 for entry
in list
.iter() {
188 let privs
: u64 = Role
::from_str(entry
.value
).unwrap() as u64;
189 map
.insert(entry
.value
, (privs
, entry
.description
));
196 pub(crate) fn split_acl_path(path
: &str) -> Vec
<&str> {
197 let items
= path
.split('
/'
);
199 let mut components
= vec
![];
205 components
.push(name
);
211 /// Check whether a given ACL `path` conforms to the expected schema.
213 /// Currently this just checks for the number of components for various sub-trees.
214 pub fn check_acl_path(path
: &str) -> Result
<(), Error
> {
215 let components
= split_acl_path(path
);
217 let components_len
= components
.len();
219 if components_len
== 0 {
222 match components
[0] {
224 if components_len
== 1 {
227 match components
[1] {
229 if components_len
== 2 {
237 // /datastore/{store}
238 if components_len
<= 2 {
243 // /remote/{remote}/{store}
244 if components_len
<= 3 {
249 if components_len
== 1 {
252 match components
[1] {
253 "disks" | "log" | "status" | "tasks" | "time" => {
254 if components_len
== 2 {
259 // /system/services/{service}
260 if components_len
<= 3 {
265 if components_len
== 2 {
268 match components
[2] {
270 if components_len
== 3 {
275 // /system/network/interfaces/{iface}
276 if components_len
<= 4 {
289 bail
!("invalid acl path '{}'.", path
);
292 /// Tree representing a parsed acl.cfg
294 /// Root node of the tree.
296 /// The rest of the tree is available via [find_node()](AclTree::find_node()) or an
297 /// [AclTreeNode]'s [children](AclTreeNode::children) member.
298 pub root
: AclTreeNode
,
301 /// Node representing ACLs for a certain ACL path.
302 pub struct AclTreeNode
{
303 /// [User](crate::config::user::User) or
304 /// [Token](crate::config::user::ApiToken) ACLs for this node.
305 pub users
: HashMap
<Authid
, HashMap
<String
, bool
>>,
306 /// `Group` ACLs for this node (not yet implemented)
307 pub groups
: HashMap
<String
, HashMap
<String
, bool
>>,
308 /// `AclTreeNodes` representing ACL paths directly below the current one.
309 pub children
: BTreeMap
<String
, AclTreeNode
>,
313 /// Creates a new, empty AclTreeNode.
314 pub fn new() -> Self {
316 users
: HashMap
::new(),
317 groups
: HashMap
::new(),
318 children
: BTreeMap
::new(),
322 /// Returns applicable [Role] and their propagation status for a given
323 /// [Authid](crate::api2::types::Authid).
325 /// If the `Authid` is a [User](crate::config::user::User) that has no specific `Roles` configured on this node,
326 /// applicable `Group` roles will be returned instead.
328 /// If `leaf` is `false`, only those roles where the propagate flag in the ACL is set to `true`
329 /// are returned. Otherwise, all roles will be returned.
330 pub fn extract_roles(&self, auth_id
: &Authid
, leaf
: bool
) -> HashMap
<String
, bool
> {
331 let user_roles
= self.extract_user_roles(auth_id
, leaf
);
332 if !user_roles
.is_empty() || auth_id
.is_token() {
333 // user privs always override group privs
337 self.extract_group_roles(auth_id
.user(), leaf
)
340 fn extract_user_roles(&self, auth_id
: &Authid
, leaf
: bool
) -> HashMap
<String
, bool
> {
341 let mut map
= HashMap
::new();
343 let roles
= match self.users
.get(auth_id
) {
348 for (role
, propagate
) in roles
{
349 if *propagate
|| leaf
{
350 if role
== ROLE_NAME_NO_ACCESS
{
351 // return a map with a single role 'NoAccess'
352 let mut map
= HashMap
::new();
353 map
.insert(role
.to_string(), false);
356 map
.insert(role
.to_string(), *propagate
);
363 fn extract_group_roles(&self, _user
: &Userid
, leaf
: bool
) -> HashMap
<String
, bool
> {
364 let mut map
= HashMap
::new();
366 for (_group
, roles
) in &self.groups
{
367 let is_member
= false; // fixme: check if user is member of the group
372 for (role
, propagate
) in roles
{
373 if *propagate
|| leaf
{
374 if role
== ROLE_NAME_NO_ACCESS
{
375 // return a map with a single role 'NoAccess'
376 let mut map
= HashMap
::new();
377 map
.insert(role
.to_string(), false);
380 map
.insert(role
.to_string(), *propagate
);
388 fn delete_group_role(&mut self, group
: &str, role
: &str) {
389 let roles
= match self.groups
.get_mut(group
) {
396 fn delete_user_role(&mut self, auth_id
: &Authid
, role
: &str) {
397 let roles
= match self.users
.get_mut(auth_id
) {
404 fn insert_group_role(&mut self, group
: String
, role
: String
, propagate
: bool
) {
405 let map
= self.groups
.entry(group
).or_insert_with(HashMap
::new
);
406 if role
== ROLE_NAME_NO_ACCESS
{
408 map
.insert(role
, propagate
);
410 map
.remove(ROLE_NAME_NO_ACCESS
);
411 map
.insert(role
, propagate
);
415 fn insert_user_role(&mut self, auth_id
: Authid
, role
: String
, propagate
: bool
) {
416 let map
= self.users
.entry(auth_id
).or_insert_with(HashMap
::new
);
417 if role
== ROLE_NAME_NO_ACCESS
{
419 map
.insert(role
, propagate
);
421 map
.remove(ROLE_NAME_NO_ACCESS
);
422 map
.insert(role
, propagate
);
428 /// Create a new, empty ACL tree with a single, empty root [node](AclTreeNode)
429 pub fn new() -> Self {
431 root
: AclTreeNode
::new(),
435 /// Iterates over the tree looking for a node matching `path`.
436 pub fn find_node(&mut self, path
: &str) -> Option
<&mut AclTreeNode
> {
437 let path
= split_acl_path(path
);
438 return self.get_node(&path
);
441 fn get_node(&mut self, path
: &[&str]) -> Option
<&mut AclTreeNode
> {
442 let mut node
= &mut self.root
;
444 node
= match node
.children
.get_mut(*comp
) {
452 fn get_or_insert_node(&mut self, path
: &[&str]) -> &mut AclTreeNode
{
453 let mut node
= &mut self.root
;
457 .entry(String
::from(*comp
))
458 .or_insert_with(AclTreeNode
::new
);
463 /// Deletes the specified `role` from the `group`'s ACL on `path`.
465 /// Never fails, even if the `path` has no ACLs configured, or the `group`/`role` combination
466 /// does not exist on `path`.
467 pub fn delete_group_role(&mut self, path
: &str, group
: &str, role
: &str) {
468 let path
= split_acl_path(path
);
469 let node
= match self.get_node(&path
) {
473 node
.delete_group_role(group
, role
);
476 /// Deletes the specified `role` from the `user`'s ACL on `path`.
478 /// Never fails, even if the `path` has no ACLs configured, or the `user`/`role` combination
479 /// does not exist on `path`.
480 pub fn delete_user_role(&mut self, path
: &str, auth_id
: &Authid
, role
: &str) {
481 let path
= split_acl_path(path
);
482 let node
= match self.get_node(&path
) {
486 node
.delete_user_role(auth_id
, role
);
489 /// Inserts the specified `role` into the `group` ACL on `path`.
491 /// The [AclTreeNode] representing `path` will be created and inserted into the tree if
493 pub fn insert_group_role(&mut self, path
: &str, group
: &str, role
: &str, propagate
: bool
) {
494 let path
= split_acl_path(path
);
495 let node
= self.get_or_insert_node(&path
);
496 node
.insert_group_role(group
.to_string(), role
.to_string(), propagate
);
499 /// Inserts the specified `role` into the `user` ACL on `path`.
501 /// The [AclTreeNode] representing `path` will be created and inserted into the tree if
503 pub fn insert_user_role(&mut self, path
: &str, auth_id
: &Authid
, role
: &str, propagate
: bool
) {
504 let path
= split_acl_path(path
);
505 let node
= self.get_or_insert_node(&path
);
506 node
.insert_user_role(auth_id
.to_owned(), role
.to_string(), propagate
);
509 fn write_node_config(node
: &AclTreeNode
, path
: &str, w
: &mut dyn Write
) -> Result
<(), Error
> {
510 let mut role_ug_map0
= HashMap
::new();
511 let mut role_ug_map1
= HashMap
::new();
513 for (auth_id
, roles
) in &node
.users
{
514 // no need to save, because root is always 'Administrator'
515 if !auth_id
.is_token() && auth_id
.user() == "root@pam" {
518 for (role
, propagate
) in roles
{
519 let role
= role
.as_str();
520 let auth_id
= auth_id
.to_string();
524 .or_insert_with(BTreeSet
::new
)
529 .or_insert_with(BTreeSet
::new
)
535 for (group
, roles
) in &node
.groups
{
536 for (role
, propagate
) in roles
{
537 let group
= format
!("@{}", group
);
541 .or_insert_with(BTreeSet
::new
)
546 .or_insert_with(BTreeSet
::new
)
552 fn group_by_property_list(
553 item_property_map
: &HashMap
<&str, BTreeSet
<String
>>,
554 ) -> BTreeMap
<String
, BTreeSet
<String
>> {
555 let mut result_map
= BTreeMap
::new();
556 for (item
, property_map
) in item_property_map
{
557 let item_list
= property_map
.iter().fold(String
::new(), |mut acc
, v
| {
566 .or_insert_with(BTreeSet
::new
)
567 .insert(item
.to_string());
572 let uglist_role_map0
= group_by_property_list(&role_ug_map0
);
573 let uglist_role_map1
= group_by_property_list(&role_ug_map1
);
575 fn role_list(roles
: &BTreeSet
<String
>) -> String
{
576 if roles
.contains(ROLE_NAME_NO_ACCESS
) {
577 return String
::from(ROLE_NAME_NO_ACCESS
);
579 roles
.iter().fold(String
::new(), |mut acc
, v
| {
588 for (uglist
, roles
) in &uglist_role_map0
{
589 let role_list
= role_list(roles
);
593 if path
.is_empty() { "/" }
else { path }
,
599 for (uglist
, roles
) in &uglist_role_map1
{
600 let role_list
= role_list(roles
);
604 if path
.is_empty() { "/" }
else { path }
,
610 for (name
, child
) in node
.children
.iter() {
611 let child_path
= format
!("{}/{}", path
, name
);
612 Self::write_node_config(child
, &child_path
, w
)?
;
618 fn write_config(&self, w
: &mut dyn Write
) -> Result
<(), Error
> {
619 Self::write_node_config(&self.root
, "", w
)
622 fn parse_acl_line(&mut self, line
: &str) -> Result
<(), Error
> {
623 let items
: Vec
<&str> = line
.split('
:'
).collect();
625 if items
.len() != 5 {
626 bail
!("wrong number of items.");
629 if items
[0] != "acl" {
630 bail
!("line does not start with 'acl'.");
633 let propagate
= if items
[1] == "0" {
635 } else if items
[1] == "1" {
638 bail
!("expected '0' or '1' for propagate flag.");
641 let path_str
= items
[2];
642 let path
= split_acl_path(path_str
);
643 let node
= self.get_or_insert_node(&path
);
645 let uglist
: Vec
<&str> = items
[3].split('
,'
).map(|v
| v
.trim()).collect();
647 let rolelist
: Vec
<&str> = items
[4].split('
,'
).map(|v
| v
.trim()).collect();
649 for user_or_group
in &uglist
{
650 for role
in &rolelist
{
651 if !ROLE_NAMES
.contains_key(role
) {
652 bail
!("unknown role '{}'", role
);
654 if user_or_group
.starts_with('@'
) {
655 let group
= &user_or_group
[1..];
656 node
.insert_group_role(group
.to_string(), role
.to_string(), propagate
);
658 node
.insert_user_role(user_or_group
.parse()?
, role
.to_string(), propagate
);
666 fn load(filename
: &Path
) -> Result
<(Self, [u8; 32]), Error
> {
667 let mut tree
= Self::new();
669 let raw
= match std
::fs
::read_to_string(filename
) {
672 if err
.kind() == std
::io
::ErrorKind
::NotFound
{
675 bail
!("unable to read acl config {:?} - {}", filename
, err
);
680 let digest
= openssl
::sha
::sha256(raw
.as_bytes());
682 for (linenr
, line
) in raw
.lines().enumerate() {
683 let line
= line
.trim();
687 if let Err(err
) = tree
.parse_acl_line(line
) {
689 "unable to parse acl config {:?}, line {} - {}",
701 pub(crate) fn from_raw(raw
: &str) -> Result
<Self, Error
> {
702 let mut tree
= Self::new();
703 for (linenr
, line
) in raw
.lines().enumerate() {
704 let line
= line
.trim();
708 if let Err(err
) = tree
.parse_acl_line(line
) {
710 "unable to parse acl config data, line {} - {}",
719 /// Returns a map of role name and propagation status for a given `auth_id` and `path`.
721 /// This will collect role mappings according to the following algorithm:
722 /// - iterate over all intermediate nodes along `path` and collect roles with `propagate` set
723 /// - get all (propagating and non-propagating) roles for last component of path
724 /// - more specific role maps replace less specific role maps
725 /// -- user/token is more specific than group at each level
726 /// -- roles lower in the tree are more specific than those higher up along the path
727 pub fn roles(&self, auth_id
: &Authid
, path
: &[&str]) -> HashMap
<String
, bool
> {
728 let mut node
= &self.root
;
729 let mut role_map
= node
.extract_roles(auth_id
, path
.is_empty());
731 for (pos
, comp
) in path
.iter().enumerate() {
732 let last_comp
= (pos
+ 1) == path
.len();
733 node
= match node
.children
.get(*comp
) {
735 None
=> return role_map
, // path not found
738 let new_map
= node
.extract_roles(auth_id
, last_comp
);
739 if !new_map
.is_empty() {
740 // overwrite previous maptings
749 /// Filename where [AclTree] is stored.
750 pub const ACL_CFG_FILENAME
: &str = "/etc/proxmox-backup/acl.cfg";
751 /// Path used to lock the [AclTree] when modifying.
752 pub const ACL_CFG_LOCKFILE
: &str = "/etc/proxmox-backup/.acl.lck";
754 /// Reads the [AclTree] from the [default path](ACL_CFG_FILENAME).
755 pub fn config() -> Result
<(AclTree
, [u8; 32]), Error
> {
756 let path
= PathBuf
::from(ACL_CFG_FILENAME
);
760 /// Returns a cached [AclTree] or fresh copy read directly from the [default path](ACL_CFG_FILENAME)
762 /// Since the AclTree is used for every API request's permission check, this caching mechanism
763 /// allows to skip reading and parsing the file again if it is unchanged.
764 pub fn cached_config() -> Result
<Arc
<AclTree
>, Error
> {
766 data
: Option
<Arc
<AclTree
>>,
768 last_mtime_nsec
: i64,
772 static ref CACHED_CONFIG
: RwLock
<ConfigCache
> = RwLock
::new(ConfigCache
{
779 let stat
= match nix
::sys
::stat
::stat(ACL_CFG_FILENAME
) {
780 Ok(stat
) => Some(stat
),
781 Err(nix
::Error
::Sys(nix
::errno
::Errno
::ENOENT
)) => None
,
782 Err(err
) => bail
!("unable to stat '{}' - {}", ACL_CFG_FILENAME
, err
),
787 let cache
= CACHED_CONFIG
.read().unwrap();
788 if let Some(ref config
) = cache
.data
{
789 if let Some(stat
) = stat
{
790 if stat
.st_mtime
== cache
.last_mtime
&& stat
.st_mtime_nsec
== cache
.last_mtime_nsec
792 return Ok(config
.clone());
794 } else if cache
.last_mtime
== 0 && cache
.last_mtime_nsec
== 0 {
795 return Ok(config
.clone());
800 let (config
, _digest
) = config()?
;
801 let config
= Arc
::new(config
);
803 let mut cache
= CACHED_CONFIG
.write().unwrap();
804 if let Some(stat
) = stat
{
805 cache
.last_mtime
= stat
.st_mtime
;
806 cache
.last_mtime_nsec
= stat
.st_mtime_nsec
;
808 cache
.data
= Some(config
.clone());
813 /// Saves an [AclTree] to the [default path](ACL_CFG_FILENAME), ensuring proper ownership and
814 /// file permissions.
815 pub fn save_config(acl
: &AclTree
) -> Result
<(), Error
> {
816 let mut raw
: Vec
<u8> = Vec
::new();
818 acl
.write_config(&mut raw
)?
;
820 let backup_user
= crate::backup
::backup_user()?
;
821 let mode
= nix
::sys
::stat
::Mode
::from_bits_truncate(0o0640);
822 // set the correct owner/group/permissions while saving file
823 // owner(rw) = root, group(r)= backup
824 let options
= CreateOptions
::new()
826 .owner(nix
::unistd
::ROOT
)
827 .group(backup_user
.gid
);
829 replace_file(ACL_CFG_FILENAME
, &raw
, options
)?
;
839 use crate::api2
::types
::Authid
;
841 fn check_roles(tree
: &AclTree
, auth_id
: &Authid
, path
: &str, expected_roles
: &str) {
842 let path_vec
= super::split_acl_path(path
);
844 .roles(auth_id
, &path_vec
)
846 .map(|(v
, _
)| v
.clone())
847 .collect
::<Vec
<String
>>();
849 let roles
= roles
.join(",");
852 roles
, expected_roles
,
853 "\nat check_roles for '{}' on '{}'",
859 fn test_acl_line_compression() {
860 let tree
= AclTree
::from_raw(
862 acl:0:/store/store2:user1@pbs:Admin\n\
863 acl:0:/store/store2:user2@pbs:Admin\n\
864 acl:0:/store/store2:user1@pbs:DatastoreBackup\n\
865 acl:0:/store/store2:user2@pbs:DatastoreBackup\n\
868 .expect("failed to parse acl tree");
870 let mut raw
: Vec
<u8> = Vec
::new();
871 tree
.write_config(&mut raw
)
872 .expect("failed to write acl tree");
873 let raw
= std
::str::from_utf8(&raw
).expect("acl tree is not valid utf8");
877 "acl:0:/store/store2:user1@pbs,user2@pbs:Admin,DatastoreBackup\n"
882 fn test_roles_1() -> Result
<(), Error
> {
883 let tree
= AclTree
::from_raw(
885 acl:1:/storage:user1@pbs:Admin
886 acl:1:/storage/store1:user1@pbs:DatastoreBackup
887 acl:1:/storage/store2:user2@pbs:DatastoreBackup
890 let user1
: Authid
= "user1@pbs".parse()?
;
891 check_roles(&tree
, &user1
, "/", "");
892 check_roles(&tree
, &user1
, "/storage", "Admin");
893 check_roles(&tree
, &user1
, "/storage/store1", "DatastoreBackup");
894 check_roles(&tree
, &user1
, "/storage/store2", "Admin");
896 let user2
: Authid
= "user2@pbs".parse()?
;
897 check_roles(&tree
, &user2
, "/", "");
898 check_roles(&tree
, &user2
, "/storage", "");
899 check_roles(&tree
, &user2
, "/storage/store1", "");
900 check_roles(&tree
, &user2
, "/storage/store2", "DatastoreBackup");
906 fn test_role_no_access() -> Result
<(), Error
> {
907 let tree
= AclTree
::from_raw(
909 acl:1:/:user1@pbs:Admin
910 acl:1:/storage:user1@pbs:NoAccess
911 acl:1:/storage/store1:user1@pbs:DatastoreBackup
914 let user1
: Authid
= "user1@pbs".parse()?
;
915 check_roles(&tree
, &user1
, "/", "Admin");
916 check_roles(&tree
, &user1
, "/storage", "NoAccess");
917 check_roles(&tree
, &user1
, "/storage/store1", "DatastoreBackup");
918 check_roles(&tree
, &user1
, "/storage/store2", "NoAccess");
919 check_roles(&tree
, &user1
, "/system", "Admin");
921 let tree
= AclTree
::from_raw(
923 acl:1:/:user1@pbs:Admin
924 acl:0:/storage:user1@pbs:NoAccess
925 acl:1:/storage/store1:user1@pbs:DatastoreBackup
928 check_roles(&tree
, &user1
, "/", "Admin");
929 check_roles(&tree
, &user1
, "/storage", "NoAccess");
930 check_roles(&tree
, &user1
, "/storage/store1", "DatastoreBackup");
931 check_roles(&tree
, &user1
, "/storage/store2", "Admin");
932 check_roles(&tree
, &user1
, "/system", "Admin");
938 fn test_role_add_delete() -> Result
<(), Error
> {
939 let mut tree
= AclTree
::new();
941 let user1
: Authid
= "user1@pbs".parse()?
;
943 tree
.insert_user_role("/", &user1
, "Admin", true);
944 tree
.insert_user_role("/", &user1
, "Audit", true);
946 check_roles(&tree
, &user1
, "/", "Admin,Audit");
948 tree
.insert_user_role("/", &user1
, "NoAccess", true);
949 check_roles(&tree
, &user1
, "/", "NoAccess");
951 let mut raw
: Vec
<u8> = Vec
::new();
952 tree
.write_config(&mut raw
)?
;
953 let raw
= std
::str::from_utf8(&raw
)?
;
955 assert_eq
!(raw
, "acl:1:/:user1@pbs:NoAccess\n");
961 fn test_no_access_overwrite() -> Result
<(), Error
> {
962 let mut tree
= AclTree
::new();
964 let user1
: Authid
= "user1@pbs".parse()?
;
966 tree
.insert_user_role("/storage", &user1
, "NoAccess", true);
968 check_roles(&tree
, &user1
, "/storage", "NoAccess");
970 tree
.insert_user_role("/storage", &user1
, "Admin", true);
971 tree
.insert_user_role("/storage", &user1
, "Audit", true);
973 check_roles(&tree
, &user1
, "/storage", "Admin,Audit");
975 tree
.insert_user_role("/storage", &user1
, "NoAccess", true);
977 check_roles(&tree
, &user1
, "/storage", "NoAccess");