[pdm-devel] [PATCH proxmox v2 2/4] access-control: move functions querying privileges to the AclTree

Shannon Sterz s.sterz at proxmox.com
Fri Oct 24 16:51:18 CEST 2025


instead of keeping them in the CachedUserInfo. there is nothing that's
specific to the CachedUserInfo in these functions and having them in
the AclTree makes it possible to use them with the `acl` feature only
too.

to keep backward compatability, we keep the original functions in
CachedUserInfo but make them call the functions on the AclTree.

Signed-off-by: Shannon Sterz <s.sterz at proxmox.com>
---
 proxmox-access-control/src/acl.rs             | 108 ++++++++++++++++++
 .../src/cached_user_info.rs                   |  91 +--------------
 2 files changed, 114 insertions(+), 85 deletions(-)

diff --git a/proxmox-access-control/src/acl.rs b/proxmox-access-control/src/acl.rs
index 6f0a8251..9fb97f55 100644
--- a/proxmox-access-control/src/acl.rs
+++ b/proxmox-access-control/src/acl.rs
@@ -562,6 +562,102 @@ impl AclTree {
 
         Ok(res)
     }
+
+    pub fn check_privs(
+        &self,
+        auth_id: &Authid,
+        path: &[&str],
+        required_privs: u64,
+        partial: bool,
+    ) -> Result<(), Error> {
+        let privs = self.lookup_privs(auth_id, path);
+        let allowed = if partial {
+            (privs & required_privs) != 0
+        } else {
+            (privs & required_privs) == required_privs
+        };
+        if !allowed {
+            // printing the path doesn't leak any information as long as we
+            // always check privilege before resource existence
+            let priv_names = privs_to_priv_names(required_privs);
+            let priv_names = if partial {
+                priv_names.join("|")
+            } else {
+                priv_names.join("&")
+            };
+            bail!(
+                "missing permissions '{priv_names}' on '/{}'",
+                path.join("/")
+            );
+        }
+        Ok(())
+    }
+
+    pub fn lookup_privs(&self, auth_id: &Authid, path: &[&str]) -> u64 {
+        let (privs, _) = self.lookup_privs_details(auth_id, path);
+        privs
+    }
+
+    pub fn lookup_privs_details(&self, auth_id: &Authid, path: &[&str]) -> (u64, u64) {
+        if access_conf().is_superuser(auth_id) {
+            let acm_config = access_conf();
+            if let Some(admin) = acm_config.role_admin() {
+                if let Some((admin, _)) = acm_config.roles().get(admin) {
+                    return (*admin, *admin);
+                }
+            }
+        }
+
+        let roles = self.roles(auth_id, path);
+        let mut privs: u64 = 0;
+        let mut propagated_privs: u64 = 0;
+        for (role, propagate) in roles {
+            if let Some((role_privs, _)) = access_conf().roles().get(role.as_str()) {
+                if propagate {
+                    propagated_privs |= role_privs;
+                }
+                privs |= role_privs;
+            }
+        }
+
+        if auth_id.is_token() {
+            // limit privs to that of owning user
+            let user_auth_id = Authid::from(auth_id.user().clone());
+            let (owner_privs, owner_propagated_privs) =
+                self.lookup_privs_details(&user_auth_id, path);
+            privs &= owner_privs;
+            propagated_privs &= owner_propagated_privs;
+        }
+
+        (privs, propagated_privs)
+    }
+
+    /// Checks whether the `auth_id` has any of the privileges `privs` on any object below `path`.
+    pub fn any_privs_below(
+        &self,
+        auth_id: &Authid,
+        path: &[&str],
+        privs: u64,
+    ) -> Result<bool, Error> {
+        // if the anchor path itself has matching propagated privs, we skip checking children
+        let (_privs, propagated_privs) = self.lookup_privs_details(auth_id, path);
+        if propagated_privs & privs != 0 {
+            return Ok(true);
+        }
+
+        // get all sub-paths with roles defined for `auth_id`
+        let paths = self.get_child_paths(auth_id, path)?;
+
+        for path in paths.iter() {
+            // early return if any sub-path has any of the privs we are looking for
+            if privs & self.lookup_privs(auth_id, &[path.as_str()]) != 0 {
+                return Ok(true);
+            }
+        }
+
+        // no paths or no matching paths
+        Ok(false)
+    }
 }
 
 /// Get exclusive lock
@@ -650,6 +746,18 @@ pub fn save_config(acl: &AclTree) -> Result<(), Error> {
     Ok(())
 }
 
+fn privs_to_priv_names(privs: u64) -> Vec<&'static str> {
+    access_conf()
+        .privileges()
+        .iter()
+        .fold(Vec::new(), |mut priv_names, (name, value)| {
+            if value & privs != 0 {
+                priv_names.push(name);
+            }
+            priv_names
+        })
+}
+
 #[cfg(test)]
 mod test {
     use std::{collections::HashMap, sync::OnceLock};
diff --git a/proxmox-access-control/src/cached_user_info.rs b/proxmox-access-control/src/cached_user_info.rs
index f5ed2858..8db37727 100644
--- a/proxmox-access-control/src/cached_user_info.rs
+++ b/proxmox-access-control/src/cached_user_info.rs
@@ -2,7 +2,7 @@
 
 use std::sync::{Arc, OnceLock, RwLock};
 
-use anyhow::{bail, Error};
+use anyhow::Error;
 
 use proxmox_auth_api::types::{Authid, Userid};
 use proxmox_router::UserInformation;
@@ -118,66 +118,16 @@ impl CachedUserInfo {
         required_privs: u64,
         partial: bool,
     ) -> Result<(), Error> {
-        let privs = self.lookup_privs(auth_id, path);
-        let allowed = if partial {
-            (privs & required_privs) != 0
-        } else {
-            (privs & required_privs) == required_privs
-        };
-        if !allowed {
-            // printing the path doesn't leak any information as long as we
-            // always check privilege before resource existence
-            let priv_names = privs_to_priv_names(required_privs);
-            let priv_names = if partial {
-                priv_names.join("|")
-            } else {
-                priv_names.join("&")
-            };
-            bail!(
-                "missing permissions '{priv_names}' on '/{}'",
-                path.join("/")
-            );
-        }
-        Ok(())
+        self.acl_tree
+            .check_privs(auth_id, path, required_privs, partial)
     }
 
     pub fn lookup_privs(&self, auth_id: &Authid, path: &[&str]) -> u64 {
-        let (privs, _) = self.lookup_privs_details(auth_id, path);
-        privs
+        self.acl_tree.lookup_privs(auth_id, path)
     }
 
     pub fn lookup_privs_details(&self, auth_id: &Authid, path: &[&str]) -> (u64, u64) {
-        if self.is_superuser(auth_id) {
-            let acm_config = access_conf();
-            if let Some(admin) = acm_config.role_admin() {
-                if let Some((admin, _)) = acm_config.roles().get(admin) {
-                    return (*admin, *admin);
-                }
-            }
-        }
-
-        let roles = self.acl_tree.roles(auth_id, path);
-        let mut privs: u64 = 0;
-        let mut propagated_privs: u64 = 0;
-        for (role, propagate) in roles {
-            if let Some((role_privs, _)) = access_conf().roles().get(role.as_str()) {
-                if propagate {
-                    propagated_privs |= role_privs;
-                }
-                privs |= role_privs;
-            }
-        }
-
-        if auth_id.is_token() {
-            // limit privs to that of owning user
-            let user_auth_id = Authid::from(auth_id.user().clone());
-            let (owner_privs, owner_propagated_privs) =
-                self.lookup_privs_details(&user_auth_id, path);
-            privs &= owner_privs;
-            propagated_privs &= owner_propagated_privs;
-        }
-
-        (privs, propagated_privs)
+        self.acl_tree.lookup_privs_details(auth_id, path)
     }
 
     /// Checks whether the `auth_id` has any of the privileges `privs` on any object below `path`.
@@ -187,24 +137,7 @@ impl CachedUserInfo {
         path: &[&str],
         privs: u64,
     ) -> Result<bool, Error> {
-        // if the anchor path itself has matching propagated privs, we skip checking children
-        let (_privs, propagated_privs) = self.lookup_privs_details(auth_id, path);
-        if propagated_privs & privs != 0 {
-            return Ok(true);
-        }
-
-        // get all sub-paths with roles defined for `auth_id`
-        let paths = self.acl_tree.get_child_paths(auth_id, path)?;
-
-        for path in paths.iter() {
-            // early return if any sub-path has any of the privs we are looking for
-            if privs & self.lookup_privs(auth_id, &[path.as_str()]) != 0 {
-                return Ok(true);
-            }
-        }
-
-        // no paths or no matching paths
-        Ok(false)
+        self.acl_tree.any_privs_below(auth_id, path, privs)
     }
 }
 
@@ -232,15 +165,3 @@ impl UserInformation for CachedUserInfo {
         }
     }
 }
-
-pub fn privs_to_priv_names(privs: u64) -> Vec<&'static str> {
-    access_conf()
-        .privileges()
-        .iter()
-        .fold(Vec::new(), |mut priv_names, (name, value)| {
-            if value & privs != 0 {
-                priv_names.push(name);
-            }
-            priv_names
-        })
-}
-- 
2.47.3





More information about the pdm-devel mailing list